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
CloudModeSetupV2gate. - Remove the
is_third_party_harness()branch that sends Oz down the bespoke optimistic path. - Build the display prompt from
AmbientAgentViewModel::request()using the existingdisplay_user_query_with_mode(request.mode, &request.prompt)helper, matching the current third-party path and satisfyingPRODUCT.mdBehavior 4. - Insert the queued-query block when the display prompt is non-empty.
- Do not call
set_has_inserted_cloud_mode_user_query_blockorrecord_optimistic_user_query. ForFollowupDispatched: - Keep the conversation-status update to
ConversationStatus::InProgress. - Read
pending_followup_prompt()and callinsert_cloud_mode_queued_user_query_block(prompt, ctx)instead of creatingCloudModeFollowupUserQuery. - Do not record the prompt as optimistic UI.
This intentionally reuses
PendingUserQueryBlockrather thanQueuedQueryModel.QueuedQueryModelinapp/src/ai/blocklist/queued_query.rs (1-67)is not currently wired into the terminal rich-content path, whileinsert_cloud_mode_queued_user_query_blockis 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:
CloudModeSetupV2is 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_idis 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 byblocklist_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 ofhandle_ambient_agent_eventinapp/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 onSessionReadyorFollowupSessionReady; 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_blockoptimistically_rendered_user_querieshas_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 innewandreset_for_new_cloud_prompt. Removeshould_hide_ai_block_query_and_headerfromapp/src/ai/blocklist/block/view_impl.rs, delete the ambient-model lookup inAIBlock::render, and let the existingquery_and_indexpath render the real query/header normally. This implementsPRODUCT.mdBehavior 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
- User submits an initial Oz cloud prompt.
AmbientAgentViewModel::spawn_internalstores the stripped request and emitsDispatchedAgent.TerminalView::handle_ambient_agent_eventreconstructs the display prompt and inserts a callback-freePendingUserQueryBlock.- Setup commands, if any, continue through
maybe_insert_setup_command_blocks. - When the real shared-session response appends an Oz exchange with a renderable user query,
handle_ai_history_model_eventremoves the queued-query rich content and inserts the normalAIBlock. AIBlock::renderrenders the real query/header because the optimistic-query hiding gate is gone.- For follow-up prompts,
try_submit_pending_cloud_followupcallssubmit_cloud_followup,FollowupDispatchedinserts 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 thatpending_user_query_view_idis set and the corresponding rich content metadata isPendingUserQuery. 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
FollowupDispatchedortry_submit_pending_cloud_followupthat 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_blocksstill 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)andapp/src/terminal/view/shared_session/view_impl_test.rs (638-805)passing; extend them only if useful to assert queued-query insertion aftersubmit_cloud_followup. Targeted validation commands: cargo test -p warp cloud_mode_setup_testscargo test -p warp pending_cloud_followupcargo test -p warp append_followupcargo check -p warp --features handoff_cloud_cloudBefore opening or updating a PR, follow repository rules for formatting and clippy. Do not usecargo fmt --allor file-specificcargo 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
Failedoptimistic-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.