diff --git a/gateway/run.py b/gateway/run.py index f584089ebffd..f6e86b29fd9a 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -9208,6 +9208,21 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew "Gateway intercepted clarify text response (session=%s, id=%s)", _quick_key, _pending_clarify.clarify_id, ) + # The clarify callback pauses the platform typing/status + # indicator while waiting so Slack users can type their + # answer. The active agent resumes as soon as this reply + # resolves the wait, so re-enable its indicator here too. + # Without this, Slack stays silent until the independent + # long-running heartbeat fires (three minutes by default). + _clarify_adapter = self._adapter_for_source(source) + if _clarify_adapter: + try: + _clarify_adapter.resume_typing_for_chat(source.chat_id) + except Exception: + logger.debug( + "Failed to resume typing after clarify response", + exc_info=True, + ) # Acknowledge with empty string so adapters that emit # the agent's response don't double-post. The agent # itself will produce the next user-facing message. diff --git a/tests/gateway/test_clarify_active_session_bypass.py b/tests/gateway/test_clarify_active_session_bypass.py index bfa5ffff5617..0f5e6614251f 100644 --- a/tests/gateway/test_clarify_active_session_bypass.py +++ b/tests/gateway/test_clarify_active_session_bypass.py @@ -1,7 +1,7 @@ """Regression tests for clarify replies while a gateway session is busy.""" import asyncio -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, patch import pytest @@ -85,3 +85,37 @@ async def test_active_session_routes_typed_choice_clarify_reply_to_runner_not_bu adapter._message_handler.assert_awaited_once_with(event) adapter._busy_session_handler.assert_not_awaited() assert adapter._pending_messages == {} + + +@pytest.mark.asyncio +async def test_gateway_clarify_reply_resumes_typing_before_returning_empty_ack(): + """A clarify answer must re-enable the active run's typing indicator. + + Clarify pauses typing while waiting so Slack's Assistant API does not + disable the compose box. The typed answer is intercepted by the gateway + and returns an empty acknowledgment instead of starting a second run; that + interception path must therefore resume the original run's indicator. + """ + _clear_clarify_state() + from gateway.run import GatewayRunner + from tools import clarify_gateway as cm + + adapter = _ClarifyBypassAdapter() + adapter.pause_typing_for_chat("12345") + event = _event("the missing details") + + runner = GatewayRunner.__new__(GatewayRunner) + runner._startup_restore_in_progress = False + runner._scale_to_zero_note_real_inbound = lambda: None + runner._is_user_authorized = lambda source: True + runner._session_key_for_source = lambda source: "clarify-session" + runner._adapter_for_source = lambda source: adapter + runner._update_prompt_pending = {} + + cm.register("clarify-2", "clarify-session", "What is missing?", None) + + with patch("hermes_cli.plugins.invoke_hook", return_value=[]): + result = await runner._handle_message(event) + + assert result == "" + assert "12345" not in adapter._typing_paused