diff --git a/gateway/run.py b/gateway/run.py index 8f35d157a7..50f33aa35c 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -10384,6 +10384,16 @@ class GatewayRunner: pending = pending_event.text or _build_media_placeholder(pending_event) logger.debug("Processing queued message after agent completion: '%s...'", pending[:40]) + # Leftover /steer: if a steer arrived after the last tool batch + # (e.g. during the final API call), the agent couldn't inject it + # and returned it in result["pending_steer"]. Deliver it as the + # next user turn so it isn't silently dropped. + if result and not pending and not pending_event: + _leftover_steer = result.get("pending_steer") + if _leftover_steer: + pending = _leftover_steer + logger.debug("Delivering leftover /steer as next turn: '%s...'", pending[:40]) + # Safety net: if the pending text is a slash command (e.g. "/stop", # "/new"), discard it — commands should never be passed to the agent # as user input. The primary fix is in base.py (commands bypass the diff --git a/run_agent.py b/run_agent.py index f8b0423b98..007cb1a652 100644 --- a/run_agent.py +++ b/run_agent.py @@ -8404,6 +8404,11 @@ class AIAgent: } messages.append(tool_msg) + # ── Per-tool /steer drain ─────────────────────────────────── + # Same as the sequential path: drain between each collected + # result so the steer lands as early as possible. + self._apply_pending_steer_to_tool_results(messages, 1) + # ── Per-turn aggregate budget enforcement ───────────────────────── num_tools = len(parsed_calls) if num_tools > 0: @@ -8767,6 +8772,12 @@ class AIAgent: } messages.append(tool_msg) + # ── Per-tool /steer drain ─────────────────────────────────── + # Drain pending steer BETWEEN individual tool calls so the + # injection lands as soon as a tool finishes — not after the + # entire batch. The model sees it on the next API iteration. + self._apply_pending_steer_to_tool_results(messages, 1) + if not self.quiet_mode: if self.verbose_logging: print(f" ✅ Tool {i} completed in {tool_duration:.2f}s")