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
16 changes: 11 additions & 5 deletions plugins/platforms/slack/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,11 +281,17 @@ 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 the legacy ``assistant.threads.setStatus``.

The Agent Sessions API's ``agents.sessions.setStatus`` takes a CLOSED enum
(``active|processing|suspended|closed``) with no free-text field, so it cannot carry
the live verb phrases Hermes sends (``is thinking...``, ``still working… (2m03s)``)
nor the empty string used to clear the status — every such call returns
``invalid_arguments`` and the status line silently never appears. The legacy
``assistant.threads.setStatus`` accepts free text and remains supported until the
Feb 2027 assistant-view deprecation, so the status line stays on it; only the title
routes through ``agents.sessions.rename`` (see ``_session_title_method``).
"""
return client.assistant_threads_setStatus


Expand Down
25 changes: 15 additions & 10 deletions tests/gateway/test_slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -5968,9 +5968,12 @@ 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, the TITLE
routes through agents.sessions.rename. The STATUS line stays on the legacy
assistant.threads.setStatus even then: agents.sessions.setStatus takes a closed
enum (active|processing|suspended|closed) with no free-text field, so it cannot
carry the live verb phrases ("is thinking...", "still working… (2m03s)") nor the
empty string that clears the status. Older SDKs keep the legacy title method too.
"""

def _adapter(self):
Expand All @@ -5981,18 +5984,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_status_when_agent_sessions_supported(self):
# agents.sessions.setStatus is enum-only, so the free-text status line must
# stay on the legacy assistant.threads.setStatus even when the SDK ships it.
_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 +6012,20 @@ 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_status_when_agent_sessions_supported(self):
_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
4 changes: 2 additions & 2 deletions website/docs/user-guide/messaging/slack.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ These are the most commonly missed scopes.
| Scope | Purpose |
|-------|---------|
| `groups:read` | List and get info about private channels |
| `assistant:write` | Render the working-state status line ("is thinking…") next to the bot name while it processes a message. Without this scope the status call (`agents.sessions.setStatus` on slack-sdk 3.44+, `assistant.threads.setStatus` on older SDKs) fails silently and Slack shows its own rotating generic placeholders instead ("Finding answers…", "Reviewing findings…", …) — Hermes never controls the text. Required for `typing_status_text` to have any visible effect. |
| `assistant:write` | Render the working-state status line ("is thinking…") next to the bot name while it processes a message. Without this scope the status call (`assistant.threads.setStatus`) fails silently and Slack shows its own rotating generic placeholders instead ("Finding answers…", "Reviewing findings…", …) — Hermes never controls the text. Required for `typing_status_text` to have any visible effect. |

---

Expand Down Expand Up @@ -504,7 +504,7 @@ platforms:
| `platforms.slack.typing_status_text` | `"is thinking..."` | Text of the working-state status line shown while the agent processes a message. Requires the `assistant:write` scope — without it the status call fails silently and Slack renders its own generic placeholder, whatever this is set to. Set `typing_indicator: false` to disable the status line entirely. |

:::note Where the status renders
The custom status appears in the **footer beneath the reply composer** ("*BotName* is thinking…"), not inline in the message list. The inline "Generating response…" / "Finding answers…" lines Slack shows in the message area while an AI app works are **Slack's own rotating indicators** — the status API (`agents.sessions.setStatus` / `assistant.threads.setStatus`) does not control those, and both can appear at the same time.
The custom status appears in the **footer beneath the reply composer** ("*BotName* is thinking…"), not inline in the message list. The inline "Generating response…" / "Finding answers…" lines Slack shows in the message area while an AI app works are **Slack's own rotating indicators** — the status API (`assistant.threads.setStatus`) does not control those, and both can appear at the same time.
:::

The same key customizes Google Chat's visible working-state marker message
Expand Down