A Matrix `attach_to_session` cron delivery and a CLI→Matrix `/handoff` now land in their own
thread AND a human reply in that thread continues the seeded session (#112918).
The salvaged `MatrixAdapter.create_handoff_thread` (ea-s21, #108367) gives both seeders a
thread root. The second half of the bug is a key-shape mismatch: the handoff watcher and
`_seed_cron_thread_session` seeded `matrix🧵<room>:<root>` while the adapter keys every
in-thread reply on the ROOM's type (`matrix:group|dm:<room>:<root>`), so seed and reply never
met. Following the direction landed for Slack in 1f3f45e87b (#111896), the seeders now mirror
the adapter instead of the adapter moving onto a `thread` slot: rekeying inbound Matrix threads
would orphan every existing Matrix thread session and drop `is_group` in authz for in-thread
messages (`_GROUP_CHAT_TYPES` has no `thread`).
- gateway/run_startup.py: Matrix destinations key `dm`/`group` from the adapter's
`get_chat_info` (`_handoff_home_is_dm`).
- cron/scheduler_delivery.py: `_THREAD_REPLY_CHAT_TYPE` — Slack and Matrix non-DM thread seeds
use `group`. Slack channel cron threads had the same mismatch (adapter `build_source` keys
`group`; the seed said `thread`); the two cron tests that pinned `thread` for Slack channels
asserted the wrong shape and now assert the adapter's.
- Dropped from #108367: the `chat_type="thread"` inbound rekey (see above) and its tests;
contributor tests trimmed to two invariants.
- Docs: Matrix listed among thread-capable handoff/cron platforms.
Live probe (in-process, real MatrixAdapter + real handoff destination + real cron seeder):
before create_handoff_thread -> None; handoff key matrix🧵… ≠ inbound matrix:group:…;
cron seed matrix🧵… ≠ inbound
after create_handoff_thread -> '$seed'; handoff == inbound == cron seed (room and DM cases)
Co-authored-by: ea-s21 <190767603+ea-s21@users.noreply.github.com>
56 lines
2.1 KiB
Python
56 lines
2.1 KiB
Python
"""Tests for MatrixAdapter.create_handoff_thread.
|
|
|
|
Matrix has no channel-level "create thread" API, so the adapter seeds a root
|
|
message and returns its event_id as the thread handle (Slack-style — mirrors
|
|
``tests/gateway/test_slack_sdk_response.py::TestHandoffThread``). These tests
|
|
cover: the seed event id becomes the thread id (and the root is registered as a
|
|
participated thread), and ``None`` is returned when the seed send fails (#112918).
|
|
"""
|
|
|
|
import asyncio
|
|
import time
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
from gateway.config import PlatformConfig
|
|
from gateway.platforms.base import SendResult
|
|
|
|
|
|
def _make_adapter():
|
|
"""Create a MatrixAdapter with mocked config (no live client)."""
|
|
from plugins.platforms.matrix.adapter import MatrixAdapter
|
|
|
|
config = PlatformConfig(
|
|
enabled=True,
|
|
token="syt_test_token",
|
|
extra={
|
|
"homeserver": "https://matrix.example.org",
|
|
"user_id": "@hermes:example.org",
|
|
},
|
|
)
|
|
adapter = MatrixAdapter(config)
|
|
adapter._startup_ts = time.time() - 10
|
|
return adapter
|
|
|
|
|
|
class TestHandoffThread:
|
|
"""``create_handoff_thread`` anchors a session on the seed event's id."""
|
|
|
|
def test_seed_event_becomes_the_thread_id(self):
|
|
adapter = _make_adapter()
|
|
adapter._client = MagicMock() # truthy: a client is connected
|
|
adapter.send = AsyncMock(return_value=SendResult(success=True, message_id="$root_evt"))
|
|
|
|
thread_id = asyncio.run(adapter.create_handoff_thread("!room1:example.org", "Hermes — daily brief"))
|
|
|
|
assert thread_id == "$root_evt"
|
|
adapter.send.assert_awaited_once_with("!room1:example.org", "Hermes — daily brief")
|
|
# Root registered so inbound replies in this thread are recognised.
|
|
assert "$root_evt" in adapter._threads
|
|
|
|
def test_failed_seed_send_yields_no_thread(self):
|
|
adapter = _make_adapter()
|
|
adapter._client = MagicMock()
|
|
adapter.send = AsyncMock(return_value=SendResult(success=False, error="boom"))
|
|
|
|
assert asyncio.run(adapter.create_handoff_thread("!room1:example.org", "x")) is None
|