fix(gateway): route plain-text approval responses (salvage #46924) - #55884
Merged
Conversation
When the agent is blocked waiting for a dangerous-command approval,
plain-text responses like "yes" or "approve" were being steered into
the running agent instead of being delivered to the approval handler.
This meant approval via messaging platforms (Signal, Telegram, etc.)
never succeeded — the user's response was consumed by the steer logic
and the approval timed out.
Add an early check in `_handle_active_session_busy_message` that routes
approval-like responses ("yes", "approve", "deny", etc.) to the
approval handler when `has_blocking_approval()` is true for the session.
Fixes #46866
(cherry picked from commit b37ec1e)
…provals Follow-up to liuhao1024's #46924. Route plain-text approval replies through the canonical /approve and /deny handlers (resolve thread, resume typing, return localized confirmation) and deliver that confirmation back to the user — previously a plain 'yes' resolved silently. Synthesize a literal '/'-prefixed command so get_command_args() parses always/session modifiers on every platform (is_command() only recognizes '/'). Add E2E tests covering approve/deny/always/session vocab plus the no-pending and unrelated-text fall-through cases.
13 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replying "yes" / "approve" / "deny" (plain text, no slash) now resolves a pending dangerous-command approval on messaging platforms — previously it deadlocked into an auto-deny.
Root cause: when the agent is blocked inside
tools/approval.pywaiting for approval, a bare-word reply fell through to the steer/queue/interrupt logic in_handle_active_session_busy_message. The reply got queued behind a turn that can't start until the approval resolves, so the approval timed out and auto-denied. Slash forms (/approve,/deny) already worked; bare words (what Signal/SMS users naturally type) did not.Salvage of @liuhao1024's #46924 — their commit's authorship is preserved. Our follow-up commit reuses the canonical handlers and delivers the confirmation reply.
Changes
gateway/run.py: in_handle_active_session_busy_message, whenhas_blocking_approval(session_key)is true, route bare-word approval vocab (yes/approve/ok/y/confirm/deny/no/reject/cancel/n/always/session) through the existing/approveand/denyhandlers — which resolve the waiting thread, resume typing, and return a localized confirmation — then deliver that confirmation to the user (it was silent before). Synthesizes a literal/-prefixed command soget_command_args()parsesalways/sessionon every platform (is_command()only recognizes/).tests/gateway/test_plaintext_approval_routing.py: E2E tests over the real busy-handler path.Why this location is correct
The base-adapter guard (
gateway/platforms/base.py) invokes the busy-session handler before falling back to queueing, so plain text does reach this handler. The fix sits before the steer/queue logic and after the early-return guards (draining, internal synthetic events). Thehas_blocking_approvalgate is the disambiguator — a conversational "yes" with no pending approval is never treated as command approval (preserving the design intent atrun.py's "Pending exec approvals are handled by /approve and /deny" note).Validation
always/sessionmodifiers/approve <arg>14 E2E tests green; adjacent approval/busy suites (
test_approve_deny_commands.py,test_busy_session_ack.py) pass with no regressions.Infographic
Closes #46866.