From 91ca82035a18c37874067f281a17134bccee355e Mon Sep 17 00:00:00 2001 From: Xubin Ren Date: Tue, 17 Mar 2026 08:00:05 +0000 Subject: [PATCH] feat(slack): add default done reaction on completion --- nanobot/channels/slack.py | 1 + tests/test_slack_channel.py | 57 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/nanobot/channels/slack.py b/nanobot/channels/slack.py index 1b683f7..87194ac 100644 --- a/nanobot/channels/slack.py +++ b/nanobot/channels/slack.py @@ -38,6 +38,7 @@ class SlackConfig(Base): user_token_read_only: bool = True reply_in_thread: bool = True react_emoji: str = "eyes" + done_emoji: str = "white_check_mark" allow_from: list[str] = Field(default_factory=list) group_policy: str = "mention" group_allow_from: list[str] = Field(default_factory=list) diff --git a/tests/test_slack_channel.py b/tests/test_slack_channel.py index b4d9492..d243235 100644 --- a/tests/test_slack_channel.py +++ b/tests/test_slack_channel.py @@ -12,6 +12,8 @@ class _FakeAsyncWebClient: def __init__(self) -> None: self.chat_post_calls: list[dict[str, object | None]] = [] self.file_upload_calls: list[dict[str, object | None]] = [] + self.reactions_add_calls: list[dict[str, object | None]] = [] + self.reactions_remove_calls: list[dict[str, object | None]] = [] async def chat_postMessage( self, @@ -43,6 +45,36 @@ class _FakeAsyncWebClient: } ) + async def reactions_add( + self, + *, + channel: str, + name: str, + timestamp: str, + ) -> None: + self.reactions_add_calls.append( + { + "channel": channel, + "name": name, + "timestamp": timestamp, + } + ) + + async def reactions_remove( + self, + *, + channel: str, + name: str, + timestamp: str, + ) -> None: + self.reactions_remove_calls.append( + { + "channel": channel, + "name": name, + "timestamp": timestamp, + } + ) + @pytest.mark.asyncio async def test_send_uses_thread_for_channel_messages() -> None: @@ -88,3 +120,28 @@ async def test_send_omits_thread_for_dm_messages() -> None: assert fake_web.chat_post_calls[0]["thread_ts"] is None assert len(fake_web.file_upload_calls) == 1 assert fake_web.file_upload_calls[0]["thread_ts"] is None + + +@pytest.mark.asyncio +async def test_send_updates_reaction_when_final_response_sent() -> None: + channel = SlackChannel(SlackConfig(enabled=True, react_emoji="eyes"), MessageBus()) + fake_web = _FakeAsyncWebClient() + channel._web_client = fake_web + + await channel.send( + OutboundMessage( + channel="slack", + chat_id="C123", + content="done", + metadata={ + "slack": {"event": {"ts": "1700000000.000100"}, "channel_type": "channel"}, + }, + ) + ) + + assert fake_web.reactions_remove_calls == [ + {"channel": "C123", "name": "eyes", "timestamp": "1700000000.000100"} + ] + assert fake_web.reactions_add_calls == [ + {"channel": "C123", "name": "white_check_mark", "timestamp": "1700000000.000100"} + ]