fix(gateway): honor queue mode in runner PRIORITY interrupt path

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().
This commit is contained in:
knockyai 2026-04-24 15:17:12 -07:00 committed by Teknium
parent e3a1a9c24d
commit 3e6c108565
2 changed files with 31 additions and 0 deletions

View file

@ -3591,6 +3591,10 @@ class GatewayRunner:
if self._queue_during_drain_enabled() if self._queue_during_drain_enabled()
else f"⏳ Gateway is {self._status_action_gerund()} and is not accepting another turn right now." 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]) logger.debug("PRIORITY interrupt for session %s", _quick_key[:20])
running_agent.interrupt(event.text) running_agent.interrupt(event.text)
if _quick_key in self._pending_messages: if _quick_key in self._pending_messages:

View file

@ -70,6 +70,9 @@ def _make_runner():
runner.session_store = None runner.session_store = None
runner.hooks = MagicMock() runner.hooks = MagicMock()
runner.hooks.emit = AsyncMock() 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 return runner, _AGENT_PENDING_SENTINEL
@ -91,6 +94,30 @@ def _make_adapter(platform_val="telegram"):
class TestBusySessionAck: class TestBusySessionAck:
"""User sends a message while agent is running — should get acknowledgment.""" """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 @pytest.mark.asyncio
async def test_sends_ack_when_agent_running(self): async def test_sends_ack_when_agent_running(self):
"""First message during busy session should get a status ack.""" """First message during busy session should get a status ack."""