fix(gateway): clear slack assistant thread status

This commit is contained in:
Amr Essam 2026-04-30 10:28:10 +04:00 committed by kshitij
parent a147164d3c
commit d05a87e686
2 changed files with 105 additions and 1 deletions

View file

@ -734,6 +734,10 @@ class SlackAdapter(BasePlatformAdapter):
last_result = await self._get_client(chat_id).chat_postMessage(**kwargs)
# Clear Slack Assistant status as soon as the final message is posted.
if thread_ts:
await self.stop_typing(chat_id)
# Track the sent message ts so we can auto-respond to thread
# replies without requiring @mention.
sent_ts = last_result.get("ts") if last_result else None
@ -811,6 +815,8 @@ class SlackAdapter(BasePlatformAdapter):
ts=message_id,
text=formatted,
)
if finalize:
await self.stop_typing(chat_id)
return SendResult(success=True, message_id=message_id)
except Exception as e: # pragma: no cover - defensive logging
logger.error(
@ -851,7 +857,7 @@ class SlackAdapter(BasePlatformAdapter):
# in an assistant-enabled context. Falls back to reactions.
logger.debug("[Slack] assistant.threads.setStatus failed: %s", e)
async def stop_typing(self, chat_id: str) -> None:
async def stop_typing(self, chat_id: str, metadata=None) -> None:
"""Clear the assistant thread status indicator."""
if not self._app:
return

View file

@ -1153,6 +1153,104 @@ class TestSendTyping:
status="is thinking...",
)
@pytest.mark.asyncio
async def test_stop_typing_clears_tracked_thread(self, adapter):
adapter._app.client.assistant_threads_setStatus = AsyncMock()
await adapter.send_typing("C123", metadata={"thread_id": "parent_ts"})
await adapter.stop_typing("C123", metadata={"thread_id": "parent_ts"})
assert adapter._app.client.assistant_threads_setStatus.call_args_list[1] == call(
channel_id="C123",
thread_ts="parent_ts",
status="",
)
assert "C123" not in adapter._active_status_threads
@pytest.mark.asyncio
async def test_stop_typing_noop_without_tracked_thread(self, adapter):
adapter._app.client.assistant_threads_setStatus = AsyncMock()
await adapter.stop_typing("C123")
adapter._app.client.assistant_threads_setStatus.assert_not_called()
@pytest.mark.asyncio
async def test_stop_typing_handles_api_error_gracefully(self, adapter):
adapter._active_status_threads["C123"] = "parent_ts"
adapter._app.client.assistant_threads_setStatus = AsyncMock(
side_effect=Exception("missing_scope")
)
await adapter.stop_typing("C123")
adapter._app.client.assistant_threads_setStatus.assert_called_once_with(
channel_id="C123",
thread_ts="parent_ts",
status="",
)
assert "C123" not in adapter._active_status_threads
@pytest.mark.asyncio
async def test_send_clears_status_after_final_post(self, adapter):
adapter._app.client.chat_postMessage = AsyncMock(return_value={"ts": "reply_ts"})
adapter._app.client.assistant_threads_setStatus = AsyncMock()
adapter._active_status_threads["C123"] = "parent_ts"
result = await adapter.send("C123", "done", metadata={"thread_id": "parent_ts"})
assert result.success
adapter._app.client.chat_postMessage.assert_called_once()
adapter._app.client.assistant_threads_setStatus.assert_called_once_with(
channel_id="C123",
thread_ts="parent_ts",
status="",
)
assert "C123" not in adapter._active_status_threads
@pytest.mark.asyncio
async def test_streaming_final_edit_clears_status(self, adapter):
adapter._app.client.chat_update = AsyncMock()
adapter._app.client.assistant_threads_setStatus = AsyncMock()
adapter._active_status_threads["C123"] = "parent_ts"
result = await adapter.edit_message(
"C123",
"reply_ts",
"done",
finalize=True,
)
assert result.success
adapter._app.client.chat_update.assert_called_once_with(
channel="C123",
ts="reply_ts",
text="done",
)
adapter._app.client.assistant_threads_setStatus.assert_called_once_with(
channel_id="C123",
thread_ts="parent_ts",
status="",
)
assert "C123" not in adapter._active_status_threads
@pytest.mark.asyncio
async def test_streaming_intermediate_edit_keeps_status(self, adapter):
adapter._app.client.chat_update = AsyncMock()
adapter._app.client.assistant_threads_setStatus = AsyncMock()
adapter._active_status_threads["C123"] = "parent_ts"
result = await adapter.edit_message(
"C123",
"reply_ts",
"partial",
finalize=False,
)
assert result.success
adapter._app.client.assistant_threads_setStatus.assert_not_called()
assert adapter._active_status_threads["C123"] == "parent_ts"
# ---------------------------------------------------------------------------
# TestFormatMessage — Markdown → mrkdwn conversion