diff --git a/gateway/platforms/matrix.py b/gateway/platforms/matrix.py index a5f9352b55..3ec3f7eaf3 100644 --- a/gateway/platforms/matrix.py +++ b/gateway/platforms/matrix.py @@ -18,6 +18,7 @@ Environment variables: MATRIX_REQUIRE_MENTION Require @mention in rooms (default: true) MATRIX_FREE_RESPONSE_ROOMS Comma-separated room IDs exempt from mention requirement MATRIX_AUTO_THREAD Auto-create threads for room messages (default: true) + MATRIX_DM_AUTO_THREAD Auto-create threads for DM messages (default: false) MATRIX_RECOVERY_KEY Recovery key for cross-signing verification after device key rotation MATRIX_DM_MENTION_THREADS Create a thread when bot is @mentioned in a DM (default: false) """ @@ -260,6 +261,9 @@ class MatrixAdapter(BasePlatformAdapter): "1", "yes", ) + self._dm_auto_thread: bool = os.getenv( + "MATRIX_DM_AUTO_THREAD", "false" + ).lower() in ("true", "1", "yes") self._dm_mention_threads: bool = os.getenv( "MATRIX_DM_MENTION_THREADS", "false" ).lower() in ("true", "1", "yes") @@ -1278,7 +1282,7 @@ class MatrixAdapter(BasePlatformAdapter): body = self._strip_mention(body) # Auto-thread. - if not is_dm and not thread_id and self._auto_thread: + if not thread_id and ((not is_dm and self._auto_thread) or (is_dm and self._dm_auto_thread)): thread_id = event_id self._threads.mark(thread_id) diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 7678287a0e..a04702c7eb 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -56,7 +56,7 @@ _EXTRA_ENV_KEYS = frozenset({ "WHATSAPP_MODE", "WHATSAPP_ENABLED", "MATTERMOST_HOME_CHANNEL", "MATTERMOST_REPLY_MODE", "MATRIX_PASSWORD", "MATRIX_ENCRYPTION", "MATRIX_DEVICE_ID", "MATRIX_HOME_ROOM", - "MATRIX_REQUIRE_MENTION", "MATRIX_FREE_RESPONSE_ROOMS", "MATRIX_AUTO_THREAD", + "MATRIX_REQUIRE_MENTION", "MATRIX_FREE_RESPONSE_ROOMS", "MATRIX_AUTO_THREAD", "MATRIX_DM_AUTO_THREAD", "MATRIX_RECOVERY_KEY", }) import yaml @@ -1699,6 +1699,14 @@ OPTIONAL_ENV_VARS = { "category": "messaging", "advanced": True, }, + "MATRIX_DM_AUTO_THREAD": { + "description": "Auto-create threads for DM messages in Matrix (default: false)", + "prompt": "Auto-create threads in DMs (true/false)", + "url": None, + "password": False, + "category": "messaging", + "advanced": True, + }, "MATRIX_DEVICE_ID": { "description": "Stable Matrix device ID for E2EE persistence across restarts (e.g. HERMES_BOT)", "prompt": "Matrix device ID (stable across restarts)", diff --git a/tests/gateway/test_matrix.py b/tests/gateway/test_matrix.py index a088ad9ba8..f71abeb423 100644 --- a/tests/gateway/test_matrix.py +++ b/tests/gateway/test_matrix.py @@ -1952,3 +1952,53 @@ class TestMatrixPresence: self.adapter._client = None result = await self.adapter.set_presence("online") assert result is False + +# --------------------------------------------------------------------------- +# DM auto-thread +# --------------------------------------------------------------------------- + +class TestMatrixDmAutoThread: + def setup_method(self): + self.adapter = _make_adapter() + self.adapter._is_dm_room = AsyncMock(return_value=True) + self.adapter._get_display_name = AsyncMock(return_value="Alice") + self.adapter._background_read_receipt = MagicMock() + # Disable require_mention so DMs pass gating + self.adapter._require_mention = False + + @pytest.mark.asyncio + async def test_dm_auto_thread_enabled_creates_thread(self): + """When dm_auto_thread is True, DM messages get auto-threaded.""" + self.adapter._dm_auto_thread = True + + ctx = await self.adapter._resolve_message_context( + room_id="!dm:ex", + sender="@alice:ex", + event_id="$ev1", + body="hello", + source_content={"body": "hello"}, + relates_to={}, + ) + + assert ctx is not None + _body, _is_dm, _chat_type, thread_id, _display, _source = ctx + assert thread_id == "$ev1" + + @pytest.mark.asyncio + async def test_dm_auto_thread_disabled_no_thread(self): + """When dm_auto_thread is False (default), DMs have no auto-thread.""" + self.adapter._dm_auto_thread = False + + ctx = await self.adapter._resolve_message_context( + room_id="!dm:ex", + sender="@alice:ex", + event_id="$ev2", + body="hello", + source_content={"body": "hello"}, + relates_to={}, + ) + + assert ctx is not None + _body, _is_dm, _chat_type, thread_id, _display, _source = ctx + assert thread_id is None +