{"repo":"NousResearch/hermes-agent","public_id":"15415","display_label":"Telegram/Slack mention gating and bot filtering","summary":"These issues and PRs address how require_mention rules are enforced in Telegram group chats and Slack group DMs, including slash command handling, bot-originated message filtering, and a proposed TELEGRAM_ALLOW_BOTS configuration option.","subsystem":null,"counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":16379,"merged_at":"2026-04-27T05:00:19+00:00"},"resolution":{"state":"resolved","by":"pr-16379"},"freshness":{"state":"stale","last_verified_at":"2026-04-27T05:00:19+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":15415,"pr_number":16379,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-04-27T05:00:19+00:00"}],"members":[{"kind":"issue","number":15415,"title":"Telegram bot-menu slash commands (`/cmd@botname`) dropped in groups with require_mention=true","url":null,"state":"closed"},{"kind":"pr","number":15417,"title":"fix(telegram): accept /cmd@botname from bot menu in groups","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"3ff3dfb5a","number":0},{"kind":"pr","value":"#16379","number":16379},{"kind":"pr","value":"#15415","number":15415}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":16379,"title":"fix(telegram): accept /cmd@botname from bot menu in groups","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["gateway/platforms/telegram.py","tests/gateway/test_telegram_group_gating.py"],"fix_hunks":[{"file":"gateway/platforms/telegram.py","hunks":["@@ -2353,6 +2353,26 @@ def _iter_sources():\n                     user = getattr(entity, \"user\", None)\n                     if user and getattr(user, \"id\", None) == bot_id:\n                         return True\n+                elif entity_type == \"bot_command\" and expected:\n+                    # Telegram's official group-disambiguation form for slash\n+                    # commands (``/cmd@botname``) is emitted as a single\n+                    # ``bot_command`` entity covering the whole span — there\n+                    # is no accompanying ``mention`` entity. Treat it as a\n+                    # direct address to this bot when the ``@botname`` suffix\n+                    # matches. This is the form Telegram's own command menu\n+                    # autocomplete produces in groups, so dropping it at the\n+                    # mention gate would break /new, /reset, /help, ... for\n+                    # every group that has ``require_mention`` enabled (#15415).\n+                    offset = int(getattr(entity, \"offset\", -1))\n+                    length = int(getattr(entity, \"length\", 0))\n+                    if offset < 0 or length <= 0:\n+                        continue\n+                    command_text = source_text[offset:offset + length]\n+                    at_index = command_text.find(\"@\")\n+                    if at_index < 0:\n+                        continue\n+                    if command_text[at_index:].strip().lower() == expected:\n+                        return True\n         return False\n \n     def _message_matches_mention_patterns(self, message: Message) -> bool:"]},{"file":"tests/gateway/test_telegram_group_gating.py","hunks":["@@ -59,6 +59,17 @@ def _mention_entity(text, mention=\"@hermes_bot\"):\n     return SimpleNamespace(type=\"mention\", offset=offset, length=len(mention))\n \n \n+def _bot_command_entity(text, command):\n+    \"\"\"Entity Telegram emits for a ``/cmd`` or ``/cmd@botname`` token.\n+\n+    Telegram parses slash commands server-side. For ``/cmd@botname`` the\n+    client does NOT emit a separate ``mention`` entity — the whole span\n+    is a single ``bot_command`` entity.\n+    \"\"\"\n+    offset = text.index(command)\n+    return SimpleNamespace(type=\"bot_command\", offset=offset, length=len(command))\n+\n+\n def test_group_messages_can_be_opened_via_config():\n     adapter = _make_adapter(require_mention=False)\n ","@@ -73,12 +84,34 @@ def test_group_messages_can_require_direct_trigger_via_config():\n     assert adapter._should_process_message(_group_message(\"replying\", reply_to_bot=True)) is True\n     # Commands must also respect require_mention when it is enabled\n     assert adapter._should_process_message(_group_message(\"/status\"), is_command=True) is False\n-    # But commands with @mention still pass (Telegram emits a MENTION entity\n-    # for /cmd@botname — the bot menu and python-telegram-bot's CommandHandler\n-    # rely on this same mechanism)\n+    # Telegram's group command menu sends ``/cmd@botname`` as a single\n+    # ``bot_command`` entity spanning the whole token (no separate mention\n+    # entity). We must accept it so the menu works when require_mention is on.\n     assert adapter._should_process_message(\n-        _group_message(\"/status@hermes_bot\", entities=[_mention_entity(\"/status@hermes_bot\")])\n+        _group_message(\n+            \"/status@hermes_bot\",\n+            entities=[_bot_command_entity(\"/status@hermes_bot\", \"/status@hermes_bot\")],\n+        ),\n+        is_command=True,\n     ) is True\n+    # A bot_command entity addressed at a different bot must not satisfy\n+    # the mention gate — Telegram groups can host multiple bots that\n+    # register the same command name.\n+    assert adapter._should_process_message(\n+        _group_message(\n+            \"/status@other_bot\",\n+            entities=[_bot_command_entity(\"/status@other_bot\", \"/status@other_bot\")],\n+        ),\n+        is_command=True,\n+    ) is False\n+    # Bare ``/status`` (no @botname) must still be dropped in groups with\n+    # require_mention=True — Telegram delivers it only when the bot's\n+    # privacy mode is off, and even then we should not respond unless the\n+    # user explicitly addressed the bot.\n+    assert adapter._should_process_message(\n+        _group_message(\"/status\", entities=[_bot_command_entity(\"/status\", \"/status\")]),\n+        is_command=True,\n+    ) is False\n     # And commands still pass unconditionally when require_mention is disabled\n     adapter_no_mention = _make_adapter(require_mention=False)\n     assert adapter_no_mention._should_process_message(_group_message(\"/status\"), is_command=True) is True\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}