From 3e6c1085659544f33eacee0ff938faed09d8e442 Mon Sep 17 00:00:00 2001 From: knockyai <276495825+knockyai@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:17:12 -0700 Subject: [PATCH] fix(gateway): honor queue mode in runner PRIORITY interrupt path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When display.busy_input_mode is 'queue', the runner-level PRIORITY block in _handle_message was still calling running_agent.interrupt() for every text follow-up to an active session. The adapter-level busy handler already honors queue mode (commit 9d147f7fd), but this runner-level path was an unconditional interrupt regardless of config. Adds a queue-mode branch that queues the follow-up via _queue_or_replace_pending_event() and returns without interrupting. Salvages the useful part of #12070 (@knockyai). The config fan-out to per-platform extra was redundant — runner already loads busy_input_mode directly via _load_busy_input_mode(). --- gateway/run.py | 4 ++++ tests/gateway/test_busy_session_ack.py | 27 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/gateway/run.py b/gateway/run.py index e0d213b0c..14bd3ff0d 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -3591,6 +3591,10 @@ class GatewayRunner: if self._queue_during_drain_enabled() else f"⏳ Gateway is {self._status_action_gerund()} and is not accepting another turn right now." ) + if self._busy_input_mode == "queue": + logger.debug("PRIORITY queue follow-up for session %s", _quick_key[:20]) + self._queue_or_replace_pending_event(_quick_key, event) + return None logger.debug("PRIORITY interrupt for session %s", _quick_key[:20]) running_agent.interrupt(event.text) if _quick_key in self._pending_messages: diff --git a/tests/gateway/test_busy_session_ack.py b/tests/gateway/test_busy_session_ack.py index 52d4c23df..290c1a4b8 100644 --- a/tests/gateway/test_busy_session_ack.py +++ b/tests/gateway/test_busy_session_ack.py @@ -70,6 +70,9 @@ def _make_runner(): runner.session_store = None runner.hooks = MagicMock() runner.hooks.emit = AsyncMock() + runner.pairing_store = MagicMock() + runner.pairing_store.is_approved.return_value = True + runner._is_user_authorized = lambda _source: True return runner, _AGENT_PENDING_SENTINEL @@ -91,6 +94,30 @@ def _make_adapter(platform_val="telegram"): class TestBusySessionAck: """User sends a message while agent is running — should get acknowledgment.""" + @pytest.mark.asyncio + async def test_handle_message_queue_mode_queues_without_interrupt(self): + """Runner queue mode must not interrupt an active agent for text follow-ups.""" + from gateway.run import GatewayRunner + + runner, _sentinel = _make_runner() + adapter = _make_adapter() + + event = _make_event(text="follow up in queue mode") + sk = build_session_key(event.source) + + running_agent = MagicMock() + runner._busy_input_mode = "queue" + runner._running_agents[sk] = running_agent + runner.adapters[event.source.platform] = adapter + + result = await GatewayRunner._handle_message(runner, event) + + assert result is None + assert sk in adapter._pending_messages + assert adapter._pending_messages[sk] is event + assert sk not in runner._pending_messages + running_agent.interrupt.assert_not_called() + @pytest.mark.asyncio async def test_sends_ack_when_agent_running(self): """First message during busy session should get a status ack."""