Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions plugins/platforms/slack/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,9 @@ def _sdk_supports_agent_sessions() -> bool:
Slack is deprecating the Assistant messaging experience in February 2027:
``assistant.threads.setStatus`` / ``assistant.threads.setTitle`` are
replaced by ``agents.sessions.setStatus`` / ``agents.sessions.rename``
(typed methods landed in slack-sdk 3.44.0). Checked on the SDK class —
(typed methods landed in slack-sdk 3.44.0). Only ``rename`` is adopted —
see ``_session_status_method`` for why status stays on the legacy method.
Checked on the SDK class —
never on a client instance, where mock auto-attributes would lie.
"""
global _AGENT_SESSIONS_SUPPORTED
Expand All @@ -280,11 +282,22 @@ def _sdk_supports_agent_sessions() -> bool:


def _session_status_method(client: Any):
"""Return the status setter: Agent Sessions API when available, else legacy."""
if _sdk_supports_agent_sessions():
method = getattr(client, "agents_sessions_setStatus", None)
if method is not None:
return method
"""Return the status setter: always legacy ``assistant.threads.setStatus``.

Deliberately NOT routed through ``agents.sessions.setStatus`` (#110374).
That API takes a closed enum (``active|processing|suspended|closed``) while
every Hermes status payload is free text — live per-tool phrases, the
elapsed-time heartbeat, ``typing_status_text`` — plus an EMPTY string to
clear. All of those are rejected with ``invalid_arguments``, and because
status failures are debug-logged the indicator vanishes silently.

The enum also has no auto-clear (``processing`` lingers up to an hour after
the app replies), which would re-open the stuck-indicator bug class of
#64621 / #24117. The legacy method keeps free text and auto-clear until its
February 2027 deprecation; migrating means mapping lifecycle states onto the
enum and accepting the loss of custom status text, which is a product call.
Titles are unaffected — ``agents.sessions.rename`` takes free text.
"""
return client.assistant_threads_setStatus


Expand Down
26 changes: 16 additions & 10 deletions tests/gateway/test_slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -5968,9 +5968,11 @@ def test_block_kit_dump_still_describes_bot_ui_blocks(self):
class TestAgentSessionsApiRouting:
"""slack-sdk 3.44.0 Agent Sessions API (assistant_view deprecation Feb 2027).

When the installed slack-sdk ships agents.sessions.* typed methods, status
and title calls route through them; older SDKs keep using the legacy
assistant.threads.* methods (compat bridge on Slack's side).
When the installed slack-sdk ships agents.sessions.* typed methods, TITLE
calls route through agents.sessions.rename; older SDKs keep using the legacy
assistant.threads.* methods (compat bridge on Slack's side). Status always
stays on assistant.threads.setStatus — the Agent Sessions status API takes a
closed enum and rejects Hermes' free-text phrases and empty clear (#110374).
"""

def _adapter(self):
Expand All @@ -5981,18 +5983,20 @@ def _adapter(self):
return a

@pytest.mark.asyncio
async def test_typing_uses_agent_sessions_when_supported(self):
async def test_typing_stays_on_legacy_even_with_sdk_support(self):
"""#110374: agents.sessions.setStatus takes a closed enum, so free-text
status phrases must keep going to assistant.threads.setStatus."""
_slack_mod._AGENT_SESSIONS_SUPPORTED = True
a = self._adapter()
a._app.client.agents_sessions_setStatus = AsyncMock()
a._app.client.assistant_threads_setStatus = AsyncMock()
await a.send_typing("C123", metadata={"thread_id": "parent_ts"})
a._app.client.agents_sessions_setStatus.assert_called_once_with(
a._app.client.assistant_threads_setStatus.assert_called_once_with(
channel_id="C123",
thread_ts="parent_ts",
status="is thinking...",
)
a._app.client.assistant_threads_setStatus.assert_not_called()
a._app.client.agents_sessions_setStatus.assert_not_called()

@pytest.mark.asyncio
async def test_typing_falls_back_to_legacy_without_sdk_support(self):
Expand All @@ -6007,20 +6011,22 @@ async def test_typing_falls_back_to_legacy_without_sdk_support(self):
)

@pytest.mark.asyncio
async def test_stop_typing_clears_via_agent_sessions(self):
async def test_stop_typing_clears_via_legacy_empty_status(self):
"""The empty-string clear is rejected by the enum API (#110374), so the
clear path stays on assistant.threads.setStatus too."""
_slack_mod._AGENT_SESSIONS_SUPPORTED = True
a = self._adapter()
a._app.client.agents_sessions_setStatus = AsyncMock()
a._app.client.assistant_threads_setStatus = AsyncMock()
await a.send_typing("C123", metadata={"thread_id": "parent_ts"})
a._app.client.agents_sessions_setStatus.reset_mock()
a._app.client.assistant_threads_setStatus.reset_mock()
await a.stop_typing("C123", metadata={"thread_id": "parent_ts"})
a._app.client.agents_sessions_setStatus.assert_called_once_with(
a._app.client.assistant_threads_setStatus.assert_called_once_with(
channel_id="C123",
thread_ts="parent_ts",
status="",
)
a._app.client.assistant_threads_setStatus.assert_not_called()
a._app.client.agents_sessions_setStatus.assert_not_called()

@pytest.mark.asyncio
async def test_thread_title_uses_agents_sessions_rename(self):
Expand Down