fix(slack): preserve thread context for proactive replies

Capture Slack thread metadata for cron and message-tool deliveries so replies stay in the originating thread, and hydrate first thread mentions with recent Slack context.

Made-with: Cursor
This commit is contained in:
Xubin Ren
2026-04-27 02:10:38 +08:00
committed by Xubin Ren
parent 7037764186
commit 038a140ad3
13 changed files with 366 additions and 19 deletions
+50
View File
@@ -20,9 +20,11 @@ class _FakeAsyncWebClient:
self.reactions_add_calls: list[dict[str, object | None]] = []
self.reactions_remove_calls: list[dict[str, object | None]] = []
self.conversations_list_calls: list[dict[str, object | None]] = []
self.conversations_replies_calls: list[dict[str, object | None]] = []
self.users_list_calls: list[dict[str, object | None]] = []
self.conversations_open_calls: list[dict[str, object | None]] = []
self._conversations_pages: list[dict[str, object]] = []
self._conversations_replies_response: dict[str, object] = {"messages": []}
self._users_pages: list[dict[str, object]] = []
self._open_dm_response: dict[str, object] = {"channel": {"id": "D_OPENED"}}
@@ -92,6 +94,10 @@ class _FakeAsyncWebClient:
return self._conversations_pages.pop(0)
return {"channels": [], "response_metadata": {"next_cursor": ""}}
async def conversations_replies(self, **kwargs):
self.conversations_replies_calls.append(kwargs)
return self._conversations_replies_response
async def users_list(self, **kwargs):
self.users_list_calls.append(kwargs)
if self._users_pages:
@@ -316,3 +322,47 @@ async def test_send_raises_when_named_target_cannot_be_resolved() -> None:
content="hello",
)
)
@pytest.mark.asyncio
async def test_with_thread_context_fetches_root_once() -> None:
channel = SlackChannel(SlackConfig(enabled=True), MessageBus())
channel._bot_user_id = "UBOT"
fake_web = _FakeAsyncWebClient()
fake_web._conversations_replies_response = {
"messages": [
{"ts": "111.000", "user": "UROOT", "text": "drink water"},
{"ts": "112.000", "user": "U2", "text": "good idea"},
{"ts": "113.000", "user": "U3", "text": "<@UBOT> what did you see?"},
]
}
channel._web_client = fake_web
content = await channel._with_thread_context(
"what did you see?",
chat_id="C123",
channel_type="channel",
thread_ts="111.000",
raw_thread_ts="111.000",
current_ts="113.000",
)
assert fake_web.conversations_replies_calls == [
{"channel": "C123", "ts": "111.000", "limit": 20}
]
assert "Slack thread context before this mention:" in content
assert "- <@UROOT>: drink water" in content
assert "- <@U2>: good idea" in content
assert "U3" not in content
assert content.endswith("Current message:\nwhat did you see?")
second = await channel._with_thread_context(
"again",
chat_id="C123",
channel_type="channel",
thread_ts="111.000",
raw_thread_ts="111.000",
current_ts="114.000",
)
assert second == "again"
assert len(fake_web.conversations_replies_calls) == 1