{"repo":"NousResearch/hermes-agent","public_id":"6296","display_label":"Mattermost slash command handling and registration improvements","summary":"These issues and pull requests address problems with Mattermost slash command parsing (leading whitespace, escaping, mobile compatibility) and add features such as registering commands with autocomplete and interactive approval buttons.","subsystem":"gateway","counts":{"members":11,"open_issues":2,"open_issues_distinct":2,"open_prs":1,"closed_issues":3,"merged_prs":1,"attempt_prs":5},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":59127},"resolution":{"state":"partially_resolved","by":"pr-59127"},"freshness":{"state":"verified","last_verified_at":"2026-07-05T21:41:58+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.15345454545454548},"fix_edges":[{"issue_number":20649,"pr_number":59127,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":25181,"pr_number":59127,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":25184,"pr_number":59127,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":6296,"title":"[Feature]: Mattermost adapter should register COMMAND_REGISTRY as custom slash commands with autocomplete","url":null,"state":"open"},{"kind":"issue","number":20649,"title":"[Feature]: Do `lstrip` user command is used to support MatterMost.","url":null,"state":"closed"},{"kind":"issue","number":25184,"title":"[Bug]: Mattermost mobile users are unable to send commands.","url":null,"state":"closed"},{"kind":"issue","number":27587,"title":"[Feature]: Mattermost: Add interactive button-based approval for dangerous commands","url":null,"state":"open"},{"kind":"issue","number":59535,"title":"Mattermost: /approve interrupted by leading whitespace from autocomplete (incomplete fix from #46866)","url":null,"state":"closed"},{"kind":"pr","number":6298,"title":"feat(gateway/mattermost): register COMMAND_REGISTRY as custom slash commands","url":null,"state":"closed","close_reason":{"category":"stale_abandoned","implication":"active"},"merged":false},{"kind":"pr","number":21070,"title":"fix(gateway): handle Mattermost escaped slash commands","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#59127","number":59127},{"kind":"pr","value":"#32210","number":32210}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":25187,"title":"fix(gateway): Mattermost Thread and Command Handling Fixes","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"06161c6ed8d3b878e0076f0eba244de8f3208bc8","number":0},{"kind":"commit","value":"1197d2bc966727194b52fb227c805793e7327a15","number":0},{"kind":"pr","value":"#59127","number":59127},{"kind":"pr","value":"#20874","number":20874}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":29373,"title":"feat(mattermost): add interactive approval buttons via send_exec_approval","url":null,"state":"open","close_reason":{"refs":[{"kind":"commit","value":"aabad7b042","number":0},{"kind":"commit","value":"15fad75a29","number":0}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":32210,"title":"fix(mattermost): accept leading-space slash commands","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#59127","number":59127},{"kind":"pr","value":"#21070","number":21070}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":59127,"title":"fix(mattermost): accept leading-space slash commands","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["plugins/platforms/mattermost/adapter.py","scripts/release.py","tests/gateway/test_mattermost.py"],"fix_hunks":[{"file":"plugins/platforms/mattermost/adapter.py","hunks":["@@ -878,6 +878,8 @@ async def _handle_ws_event(self, event: Dict[str, Any]) -> None:\n         # Determine message type.\n         file_ids = post.get(\"file_ids\") or []\n         msg_type = MessageType.TEXT\n+        if message_text[:1].isspace() and message_text.lstrip().startswith(\"/\"):\n+            message_text = message_text.lstrip()\n         if message_text.startswith(\"/\"):\n             msg_type = MessageType.COMMAND\n "]},{"file":"scripts/release.py","hunks":["@@ -45,6 +45,7 @@\n \n # Auto-extracted from noreply emails + manual overrides\n AUTHOR_MAP = {\n+    \"[redacted:email]\": \"l0h1nth\",  # PR #32210 salvage (mattermost: accept leading-space slash commands from mobile clients; #25184)\n     \"[redacted:email]\": \"devatnull\",  # PR #58704 salvage (whatsapp: native Baileys polls, clarify-as-poll, location pins, structured quoted replies, PTT/audio split, bridge_helpers extraction)\n     \"[redacted:email]\": \"tjp2021\",  # PR #4097 salvage (copilot: per-turn x-initiator header so user prompts bill as premium requests; #3040)\n     \"[redacted:email]\": \"kevinrajaram\",  # PR #3850 salvage (gateway: add POSIX system dirs to PATH so launchctl/systemctl resolve under UV's minimal-PATH bundled Python; #3849)"]},{"file":"tests/gateway/test_mattermost.py","hunks":["@@ -6,6 +6,7 @@\n from unittest.mock import MagicMock, patch, AsyncMock\n \n from gateway.config import Platform, PlatformConfig\n+from gateway.platforms.base import MessageType\n from gateway.run import (\n     _resolve_gateway_display_bool,\n     _resolve_progress_thread_id,","@@ -588,6 +589,55 @@ async def test_channel_type_mapping(self):\n         msg_event = self.adapter.handle_message.call_args[0][0]\n         assert msg_event.source.chat_type == \"dm\"\n \n+    @pytest.mark.asyncio\n+    async def test_leading_space_slash_command_is_command(self):\n+        \"\"\"Mattermost mobile suggests leading-space slash commands.\"\"\"\n+        post_data = {\n+            \"id\": \"post_cmd\",\n+            \"user_id\": \"user_123\",\n+            \"channel_id\": \"chan_dm\",\n+            \"message\": \" /new\",\n+        }\n+        event = {\n+            \"event\": \"posted\",\n+            \"data\": {\n+                \"post\": json.dumps(post_data),\n+                \"channel_type\": \"D\",\n+                \"sender_name\": \"@bob\",\n+            },\n+        }\n+\n+        await self.adapter._handle_ws_event(event)\n+        assert self.adapter.handle_message.called\n+        msg_event = self.adapter.handle_message.call_args[0][0]\n+        assert msg_event.text == \"/new\"\n+        assert msg_event.message_type is MessageType.COMMAND\n+        assert msg_event.get_command() == \"new\"\n+\n+    @pytest.mark.asyncio\n+    async def test_leading_space_normal_text_is_preserved(self):\n+        \"\"\"Only command-shaped mobile messages should be normalized.\"\"\"\n+        post_data = {\n+            \"id\": \"post_text\",\n+            \"user_id\": \"user_123\",\n+            \"channel_id\": \"chan_dm\",\n+            \"message\": \" hello\",\n+        }\n+        event = {\n+            \"event\": \"posted\",\n+            \"data\": {\n+                \"post\": json.dumps(post_data),\n+                \"channel_type\": \"D\",\n+                \"sender_name\": \"@bob\",\n+            },\n+        }\n+\n+        await self.adapter._handle_ws_event(event)\n+        assert self.adapter.handle_message.called\n+        msg_event = self.adapter.handle_message.call_args[0][0]\n+        assert msg_event.text == \" hello\"\n+        assert msg_event.message_type is MessageType.TEXT\n+\n     @pytest.mark.asyncio\n     async def test_thread_id_from_root_id(self):\n         \"\"\"Post with root_id should have thread_id set.\"\"\"\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}