Stop dropping steers routed to a non-active conversation, and clear the input after a CLI-harness follow-up - #14682
Conversation
…he input after a CLI-harness follow-up
Two defects made steering a running Warp session unreliable (REMOTE-2486).
1. `send_query` cancelled progress only on the terminal surface's *active*
(most recently streamed) conversation, while sending the query to the
*target* conversation. When those diverge — a shared-session steer routed by
server token, a fired queued row, or a conversation whose stream started
passively — the target's stream stayed in flight, `send_request_input`
refused the query ("Not sending AI input because there is an in-flight
request") and it was discarded with no user-visible error. No `SentRequest`
fired either, so the input was never cleared. The target conversation's
progress is now cancelled as well.
2. A shared-session follow-up forwarded to a third-party CLI harness's PTY
never reaches the Oz dispatch that emits `SentRequest`, so nothing issued
`system_clear_buffer` and the submitted prompt stayed in the shared CRDT
input for the sharer and the requesting viewer. The sharer now unfreezes and
clears the input explicitly on that branch.
Co-Authored-By: Warp <agent@warp.dev>
Co-Authored-By: Warp Agent <agent@warp.dev>
…-steer-drop-and-input-clear
There was a problem hiding this comment.
Overview
This PR fixes the non-active-conversation steering drop and explicit shared-input clearing for the third-party CLI-harness path. The code change is independently reviewable, but a real shared-session recording remains required before acceptance.
Concerns
No reviewer is assigned because the requester’s Slack identity could not be resolved; a human must assign one before merge. The PR description also incorrectly says the issue carries triage-done; that cleanup and the missing wiring-level regression test are being relayed to implementation.
Verdict
Checks: CI ✅ (all required checks) · local presubmit
Found: 0 critical, 1 important, 0 suggestions, 2 questions
Request changes
Review run
https://oz.staging.warp.dev/runs/019fcaab-4ac5-7544-a90d-2f0c0abc579c
| // optimistic display-only clear is materialized, for the requester too. | ||
| view.input().update(ctx, |input, ctx| { | ||
| input.unfreeze_agent_input(false, ctx); | ||
| input.clear_buffer_after_shared_session_agent_prompt(ctx); |
There was a problem hiding this comment.
❓ [QUESTION] The proposed two-step manual procedure correctly covers both affected paths, but the attached screenshots show only a signed-out terminal. Please have a reviewer with a live shared session run it and attach a short recording showing (1) the mid-turn steer is delivered and (2) a CLI-harness follow-up clears the input for both participants before this merges.
Description
Steering a running Warp session was unreliable in two ways (REMOTE-2486): a mid-session steer was sometimes refused and then never completed or silently dropped, and when it did go through the message was left sitting in the input. Both symptoms trace back to the same place — a send path that never reaches
BlocklistAIControllerEvent::SentRequest, which is what both dispatches the request and clears the input.1. A steer routed to a non-active conversation was dropped on the floor.
BlocklistAIController::send_querycancelled progress on the terminal surface's active conversation — the one most recently streamed — while sending the query to the target conversation. Those are explicitly different concepts (BlocklistAIHistoryModel::active_conversation_iddocuments that follow-up targeting usescontext_model.selected_conversation_idinstead), and they diverge in practice: a shared-session participant's steer is routed to whichever conversation itsserver_conversation_tokenmaps to, a fired queued row targets the conversation it was queued on, and a passive request registers a stream without ever marking its conversation active.When they diverged, the target's response stream stayed in flight, so
send_request_inputhit its in-flight guard and bailed withNot sending AI input because there is an in-flight request. That error is swallowed bysend_query(report_error!only) — no toast, no queue row, no retry. The prompt was gone, and becauseSentRequestnever fired the input was never cleared either.send_querynow also cancels the target conversation's progress when it differs from the active one.2. A follow-up handed to a third-party CLI harness never cleared the shared input.
On the sharer,
NetworkEvent::AgentPromptRequestedwrites the prompt straight to the CLI agent's PTY when a third-party harness (e.g. Claude Code) is running, then returns. That branch bypasses the Oz dispatch entirely, so noSentRequestfires and nothing callssystem_clear_buffer— which is the only thing that emits the CRDT delete ops clearing the shared input for the sharer and every viewer. The requesting viewer gets an optimistic display-only empty ephemeral fromAgentPromptRequestInFlight, but that ephemeral is discarded on materialization, so the original text reappeared as soon as they typed again. The sharer now unfreezes and clears the input explicitly on that branch, matching what the Oz-harness branch gets for free.Linked Issue
REMOTE-2486 (carries
triage-done; triage recordedspec_recommended: false, so no spec was written).Testing
Two regression tests, each failing before the corresponding fix and passing after.
ai::blocklist::controller::tests::steering_a_non_active_conversation_dispatches_the_request— puts an in-flight stream on the conversation being steered while a different conversation is the surface's active one, sends a user query into the steered conversation, and asserts aSentRequestwas emitted. Reverting thesend_querychange makes it fail with the exact drop path:panicked at app/src/ai/blocklist/controller.rs:2439: Not sending AI input because there is an in-flight request.terminal::input::tests::clear_buffer_after_shared_session_agent_prompt_empties_the_shared_input— asserts the sharer-side sequence run after the prompt is written to the CLI agent's PTY leaves the shared input empty. It pairs with the existingunfreeze_agent_input_does_not_clear_buffer, which documents that unfreezing alone deliberately leaves the text behind — that gap is what left the prompt in the input.Repository checks run from the repo root:
./script/format --check— pass./script/check_no_inline_test_modules— passcargo clippy -p warp --all-targets --tests -- -D warnings— passcargo nextest run -p warpscoped to every touched module (ai::blocklist::*,terminal::input*,terminal::local_tty::*,terminal::view::*,terminal::shared_session::*) — 1330 tests, 1328 passed. The 2 failures (terminal::input::decorations::tests::test_decorations_with_multibyte_chars,terminal::input::tests::test_histignorespace_support_in_zsh) reproduce identically on unmodifiedmasterin this sandbox and are environment gaps, not regressions.I have manually tested my changes locally with
./script/runScreenshots / Videos
Visual proof of the changed paths is outstanding — please confirm the rendered behavior before merging.
The desktop build from this branch was compiled and launched successfully on a headless runner (screenshots below show the app running from this branch), but neither changed path can be reached there. Both require a signed-in account plus a live shared session with a second executor participant, and one of them additionally requires a third-party CLI harness running on the sharer. The sandbox has no account credentials and no second client, so the app opens straight into a signed-out plain terminal with no agent conversation. Capturing proof on the wrong surface would be worse than none, so it was not attempted.
Suggested manual check for a reviewer with a real session:
Agent Mode
Originating thread: https://warpdev.slack.com/archives/C09E37H1NMA/p1785807618502619
Computer-use screenshots
Conversation: https://staging.warp.dev/conversation/eb5dd26c-b524-4297-ab5c-76d6850392ef
Run: https://oz.staging.warp.dev/runs/019fca77-91da-78d8-a7fd-204bfeb38e0d
This PR was generated with Oz.