diff --git a/gateway/platforms/matrix.py b/gateway/platforms/matrix.py index 9216d5f2d5..4f77e920a8 100644 --- a/gateway/platforms/matrix.py +++ b/gateway/platforms/matrix.py @@ -922,11 +922,9 @@ class MatrixAdapter(BasePlatformAdapter): if not self._is_bot_mentioned(body, formatted_body): return - # Strip mention from body when present. - if self._is_bot_mentioned(body, source_content.get("formatted_body")): - body = self._strip_mention(body) - if not body: - return + # Strip mention from body when present (including in DMs). + if self._is_bot_mentioned(body, source_content.get("formatted_body")): + body = self._strip_mention(body) # Auto-thread: create a thread for non-DM, non-threaded messages. if not is_dm and not thread_id: @@ -1076,11 +1074,14 @@ class MatrixAdapter(BasePlatformAdapter): in_bot_thread = bool(thread_id and thread_id in self._bot_participated_threads) if require_mention and not is_free_room and not in_bot_thread: - # Media messages have no formatted_body; check plain body only. formatted_body = source_content.get("formatted_body") if not self._is_bot_mentioned(body, formatted_body): return + # Strip mention from body when present (including in DMs). + if self._is_bot_mentioned(body, source_content.get("formatted_body")): + body = self._strip_mention(body) + # Auto-thread: create a thread for non-DM, non-threaded messages. if not is_dm and not thread_id: auto_thread = os.getenv("MATRIX_AUTO_THREAD", "true").lower() in ("true", "1", "yes") diff --git a/tests/gateway/test_matrix_mention.py b/tests/gateway/test_matrix_mention.py index f8d90f281e..dee7586d22 100644 --- a/tests/gateway/test_matrix_mention.py +++ b/tests/gateway/test_matrix_mention.py @@ -207,6 +207,40 @@ async def test_require_mention_dm_always_responds(monkeypatch): adapter.handle_message.assert_awaited_once() +@pytest.mark.asyncio +async def test_dm_strips_mention(monkeypatch): + """DMs strip mention from body, matching Discord behavior.""" + monkeypatch.delenv("MATRIX_REQUIRE_MENTION", raising=False) + monkeypatch.delenv("MATRIX_FREE_RESPONSE_ROOMS", raising=False) + monkeypatch.setenv("MATRIX_AUTO_THREAD", "false") + + adapter = _make_adapter() + room = _make_room(member_count=2) + event = _make_event("@hermes:example.org help me") + + await adapter._on_room_message(room, event) + adapter.handle_message.assert_awaited_once() + msg = adapter.handle_message.await_args.args[0] + assert msg.text == "help me" + + +@pytest.mark.asyncio +async def test_bare_mention_passes_empty_string(monkeypatch): + """A message that is only a mention should pass through as empty, not be dropped.""" + monkeypatch.delenv("MATRIX_REQUIRE_MENTION", raising=False) + monkeypatch.delenv("MATRIX_FREE_RESPONSE_ROOMS", raising=False) + monkeypatch.setenv("MATRIX_AUTO_THREAD", "false") + + adapter = _make_adapter() + room = _make_room() + event = _make_event("@hermes:example.org") + + await adapter._on_room_message(room, event) + adapter.handle_message.assert_awaited_once() + msg = adapter.handle_message.await_args.args[0] + assert msg.text == "" + + @pytest.mark.asyncio async def test_require_mention_free_response_room(monkeypatch): """Free-response rooms bypass mention requirement."""