Files

17 KiB

APP-4365: Use Queued Query UI for Oz Cloud Mode Queries — Tech Spec

Context

PRODUCT.md defines the user-visible target: initial and follow-up Oz cloud mode prompts should use the same queued-query UI as third-party cloud agents, setup-command rich content should remain unchanged, and the real transcript query should render normally when it arrives. Cloud mode panes are deferred shared-session viewers. create_cloud_mode_view creates a deferred viewer manager, subscribes it to AmbientAgentViewModel, and connects or appends a session on SessionReady / FollowupSessionReady in app/src/terminal/view/ambient_agent/mod.rs (42-105). is_cloud_agent_pre_first_exchange identifies the setup interval after a session is ready but before the first exchange arrives, including the third-party harness-start escape hatch in app/src/terminal/view/ambient_agent/mod.rs (110-162). AmbientAgentViewModel owns cloud run state. It tracks Status::WaitingForSession { kind: InitialRun | Followup }, the selected harness, task/session IDs, pending follow-up prompt, and today also tracks Oz optimistic-query state via has_inserted_cloud_mode_user_query_block and optimistically_rendered_user_queries in app/src/terminal/view/ambient_agent/model.rs (64-145). Initial cloud runs call spawn_internal, store the request, transition to WaitingForSession, and emit DispatchedAgent in app/src/terminal/view/ambient_agent/model.rs (660-679). Follow-up runs call submit_cloud_followup, set pending_followup_prompt, transition to WaitingForSession, and emit FollowupDispatched in app/src/terminal/view/ambient_agent/model.rs (492-529). The queued-query visual pattern already exists as PendingUserQueryBlock: it renders the prompt with user avatar, dimmed text, and a Queued badge, with optional close/send-now controls in app/src/ai/blocklist/block/pending_user_query_block.rs (16-175). TerminalView::insert_cloud_mode_queued_user_query_block inserts that block without buttons and without a queued callback, specifically for cloud mode lifecycle-owned prompts, in app/src/terminal/view/pending_user_query.rs (76-91). It is inserted as rich content with RichContentMetadata::PendingUserQuery and PinToBottom in app/src/terminal/view/pending_user_query.rs (28-74) and app/src/terminal/view/rich_content.rs (183-244). The current event handling creates divergent UI. On DispatchedAgent, third-party harnesses rebuild the display prompt with display_user_query_with_mode and call insert_cloud_mode_queued_user_query_block, while Oz creates CloudModeInitialUserQuery, inserts it as rich content, sets has_inserted_cloud_mode_user_query_block, and records the stripped prompt as optimistic UI in app/src/terminal/view/ambient_agent/view_impl.rs (129-189). On FollowupDispatched, Oz creates CloudModeFollowupUserQuery, inserts it, and records the optimistic prompt in app/src/terminal/view/ambient_agent/view_impl.rs (200-229). The bespoke optimistic views live in app/src/terminal/view/ambient_agent/block/query.rs (20-159). They share normal AI query rendering and show a Failed label when the ambient model has an error. They are re-exported through app/src/terminal/view/ambient_agent/block.rs (1-8). The duplicate-hiding logic lives in the AI block renderer. should_hide_ai_block_query_and_header hides a real AI block query/header for the first cloud exchange or any recorded optimistic follow-up prompt while the viewer is live and not receiving replay in app/src/ai/blocklist/block/view_impl.rs (99-123). AIBlock::render asks the ambient model for has_inserted_cloud_mode_user_query_block and has_optimistic_user_query before deciding whether to render the actual query/header in app/src/ai/blocklist/block/view_impl.rs (842-907). Tests currently assert this hiding behavior in app/src/ai/blocklist/block/view_impl/cloud_mode_setup_tests.rs (1-37). Setup-command rich content is independent of the optimistic prompt blocks. maybe_insert_setup_command_blocks gates on CloudModeSetupV2 and is_cloud_agent_pre_first_exchange, then inserts the setup intro and setup-command block before the real terminal block in app/src/terminal/view/ambient_agent/view_impl.rs (373-453). When the first Oz exchange is appended, handle_ai_history_model_event turns off is_executing_oz_environment_startup_commands before inserting the real AI block in app/src/terminal/view.rs (5086-5159). Follow-up submission is already routed away from the stale shared-session network. try_submit_pending_cloud_followup intercepts InputEvent::SendAgentPrompt, validates the task, calls submit_cloud_followup, and resets the input in app/src/terminal/view.rs (19735-19794). If not intercepted, TerminalViewEvent::SendAgentPrompt still goes to the viewer network in app/src/terminal/shared_session/viewer/terminal_manager.rs (1323-1398). Existing follow-up and tombstone tests cover this route in app/src/terminal/view_test.rs (552-576) and app/src/terminal/view/shared_session/view_impl_test.rs (638-805).

Proposed changes

Use one cloud queued-query insertion path

Update TerminalView::handle_ambient_agent_event so both Oz and third-party cloud runs use insert_cloud_mode_queued_user_query_block while waiting for the real cloud transcript. For DispatchedAgent:

  • Keep the existing viewer short-circuit and CloudModeSetupV2 gate.
  • Remove the is_third_party_harness() branch that sends Oz down the bespoke optimistic path.
  • Build the display prompt from AmbientAgentViewModel::request() using the existing display_user_query_with_mode(request.mode, &request.prompt) helper, matching the current third-party path and satisfying PRODUCT.md Behavior 4.
  • Insert the queued-query block when the display prompt is non-empty.
  • Do not call set_has_inserted_cloud_mode_user_query_block or record_optimistic_user_query. For FollowupDispatched:
  • Keep the conversation-status update to ConversationStatus::InProgress.
  • Read pending_followup_prompt() and call insert_cloud_mode_queued_user_query_block(prompt, ctx) instead of creating CloudModeFollowupUserQuery.
  • Do not record the prompt as optimistic UI. This intentionally reuses PendingUserQueryBlock rather than QueuedQueryModel. QueuedQueryModel in app/src/ai/blocklist/queued_query.rs (1-67) is not currently wired into the terminal rich-content path, while insert_cloud_mode_queued_user_query_block is the existing third-party cloud UI surface the product spec names as the reference.

Remove the queued item only when the real transcript user query arrives

Add cloud-specific removal at the point the real Oz AI exchange with a renderable user query is appended. In handle_ai_history_model_event, after turning off is_executing_oz_environment_startup_commands for ambient sessions and before inserting the AIBlock, call remove_pending_user_query_block(ctx) only when:

  • CloudModeSetupV2 is enabled,
  • the terminal is an ambient agent session,
  • the appended exchange belongs to the visible root task that will produce an AI block,
  • the exchange contains an input that will render a user query for the submitted prompt, and
  • pending_user_query_view_id is set. The key invariant is that the queued item survives session attach and any intermediate shared-session output until the actual user-query element from the real shared-session response exists. SessionReady, FollowupSessionReady, progress updates, setup-command blocks, harness-start transitions, and generic output without a renderable user query must not remove the queued item. The removal should not depend on prompt string matching unless the existing exchange/input model exposes a cheap exact display-query comparison. The cloud queued-query helper owns a single lifecycle-managed pending item, and the first visible real user-query exchange for that ambient conversation is the replacement surface. This avoids retaining a stale queued item while also avoiding the old real-query hiding logic. If the exchange is filtered out by blocklist_filter::should_show_task_in_blocklist, or if it does not contain a renderable user query, leave the pending item untouched until a later visible user-query exchange, failure, cancellation, auth event, or other terminal lifecycle event handles it. Keep the existing error/auth/cancel cleanup at the top of handle_ambient_agent_event in app/src/terminal/view/ambient_agent/view_impl.rs (111-126). That cleanup already removes lifecycle-owned cloud queued items and should continue to apply to Oz and third-party runs. Do not remove the queued item on SessionReady or FollowupSessionReady; those events mean a shared session exists, not that the real user-query transcript has rendered.

Delete Oz optimistic-query rendering and tracking

Remove CloudModeInitialUserQuery, CloudModeFollowupUserQuery, and the private render_user_query helper from app/src/terminal/view/ambient_agent/block/query.rs. If that leaves the file empty, remove mod query; and pub use query::*; from app/src/terminal/view/ambient_agent/block.rs. Remove AmbientAgentViewModel fields and methods that only support optimistic query hiding:

  • has_inserted_cloud_mode_user_query_block
  • optimistically_rendered_user_queries
  • has_inserted_cloud_mode_user_query_block()
  • set_has_inserted_cloud_mode_user_query_block(...)
  • record_optimistic_user_query(...)
  • has_optimistic_user_query(...) Also remove their initialization and reset logic in new and reset_for_new_cloud_prompt. Remove should_hide_ai_block_query_and_header from app/src/ai/blocklist/block/view_impl.rs, delete the ambient-model lookup in AIBlock::render, and let the existing query_and_index path render the real query/header normally. This implements PRODUCT.md Behavior 11-14 directly: the queued rich content is the transient placeholder, and the AI block is the real transcript representation.

Preserve setup-command rich content

Do not change maybe_insert_setup_command_blocks, CloudModeSetupTextBlock, CloudModeSetupCommandBlock, is_cloud_agent_pre_first_exchange, or the block-list startup-command flags. The queued-query change should be limited to which pending prompt rich content is inserted and when it is removed. This preserves PRODUCT.md Behavior 7-8 and avoids changing the setup-command grouping/order behavior already covered by CloudModeSetupV2.

Update comments and names where they become stale

Update comments on insert_cloud_mode_queued_user_query_block from “non-oz Cloud Mode run” to “Cloud Mode run waiting for the real transcript” so it accurately covers Oz and third-party runs. Update comments in pending_user_query_view_id only if needed; the field remains shared by normal /queue prompts and lifecycle-owned cloud queued prompts, but cloud insertion still uses the callback-free helper. No new feature flag is needed. Initial-run behavior remains under CloudModeSetupV2, and cloud follow-up behavior remains reachable only through the existing HandoffCloudCloud follow-up entrypoints.

End-to-end flow

  1. User submits an initial Oz cloud prompt.
  2. AmbientAgentViewModel::spawn_internal stores the stripped request and emits DispatchedAgent.
  3. TerminalView::handle_ambient_agent_event reconstructs the display prompt and inserts a callback-free PendingUserQueryBlock.
  4. Setup commands, if any, continue through maybe_insert_setup_command_blocks.
  5. When the real shared-session response appends an Oz exchange with a renderable user query, handle_ai_history_model_event removes the queued-query rich content and inserts the normal AIBlock.
  6. AIBlock::render renders the real query/header because the optimistic-query hiding gate is gone.
  7. For follow-up prompts, try_submit_pending_cloud_followup calls submit_cloud_followup, FollowupDispatched inserts the same queued-query block, and the next appended exchange that contains the actual follow-up user query replaces it the same way.

Testing and validation

Unit tests should cover the product invariants without depending on pixel rendering:

  • Add or update a terminal view test that enables CloudModeSetupV2, dispatches an Oz initial cloud run, and asserts that pending_user_query_view_id is set and the corresponding rich content metadata is PendingUserQuery. This covers Behavior 1, 3, 5, 18, 20, and 25.
  • Add the same assertion for a non-Oz harness to ensure the existing third-party path is unchanged. This covers Behavior 3 and 26.
  • Add a follow-up test around FollowupDispatched or try_submit_pending_cloud_followup that asserts a queued-query block is inserted instead of an optimistic follow-up view. This covers Behavior 2, 6, 19, 20, and 25.
  • Add a history-model append test for an ambient cloud conversation that starts with a queued-query block, appends a visible exchange containing a renderable user query, and asserts the pending block is removed while an AI block remains. This covers Behavior 9-14 and 24.
  • Add a negative history/session test that attaches a session, emits progress/setup-command/generic output, or appends an exchange without a renderable user query, and asserts the queued-query block remains visible. This covers Behavior 10.
  • Remove or rewrite app/src/ai/blocklist/block/view_impl/cloud_mode_setup_tests.rs (1-37). The old assertions should no longer hold; if a lightweight replacement is useful, assert that normal AI block query/header rendering is not suppressed for cloud exchanges.
  • Keep existing setup-command tests or add a regression assertion that maybe_insert_setup_command_blocks still inserts setup text and command blocks while a queued-query block exists and does not remove that queued item. This covers Behavior 7-8, 10, and 23.
  • Keep existing follow-up route tests in app/src/terminal/view_test.rs (552-576) and app/src/terminal/view/shared_session/view_impl_test.rs (638-805) passing; extend them only if useful to assert queued-query insertion after submit_cloud_followup. Targeted validation commands:
  • cargo test -p warp cloud_mode_setup_tests
  • cargo test -p warp pending_cloud_followup
  • cargo test -p warp append_followup
  • cargo check -p warp --features handoff_cloud_cloud Before opening or updating a PR, follow repository rules for formatting and clippy. Do not use cargo fmt --all or file-specific cargo fmt. Manual validation:
  • With CloudModeSetupV2 enabled, submit an initial Oz cloud prompt and verify the queued-query item appears immediately, remains visible after session attach and while setup-command rich content runs, and is replaced only when the real transcript user query appears.
  • Submit a follow-up from an eligible cloud tombstone or owned follow-up input and verify the same queued-query item appears and is replaced only by the real follow-up transcript query.
  • Repeat the same initial run with a third-party harness to confirm its queued-query behavior is unchanged.
  • Test failure/auth/cancel during startup and confirm the queued item follows the existing third-party lifecycle and no bespoke Failed optimistic-query block remains.

Risks and mitigations

Removing the queued item too early

SessionReady, setup-command output, progress events, or an exchange without a renderable user query can arrive before the real user-query transcript is rendered. Removing the queued item there would create a visible gap. Mitigate by removing only when a visible exchange contains the actual user query from the shared-session transcript.

Removing unrelated /queue prompts

pending_user_query_view_id is shared by normal queued prompts and cloud lifecycle prompts. Mitigate by only using the new AppendedExchange removal inside ambient cloud-session handling, and only after a visible cloud exchange with a renderable user query is about to render.

Prompt display mismatch for /plan and /orchestrate

The spawn request stores the stripped prompt and separate mode. Mitigate by keeping the existing display_user_query_with_mode reconstruction already used by third-party cloud runs.

Setup-command regressions

Setup-command insertion is adjacent to the query UI in the same event flow. Mitigate by not touching maybe_insert_setup_command_blocks or startup-command flags, and by adding a regression check that setup blocks still render alongside the queued item.

Stale optimistic-query code paths

Leaving optimistic tracking in place could continue hiding real transcript queries. Mitigate by deleting the model fields/methods and the AI block hiding helper in the same change that switches insertion to queued-query UI.

Parallelization

Parallel implementation is not beneficial for this change. The work is tightly coupled across one event handler, one model cleanup, one AI-block render gate, and a small set of targeted tests; splitting it across agents would increase merge conflicts more than it would reduce wall-clock time.