15 KiB
QUALITY-839 — Auto-queue prompts during agent-requested long-running commands
See specs/QUALITY-839/PRODUCT.md for behavior. Researched at commit 8e984f0d784f38684472054978db10f39ff7ea5c (branch harry/quality-839-auto-enable-prompt-queueing-during-lrc, stacked on the APP-4717 empty-input-Enter send-now work).
Context
All read sites for "is queue mode on?" already funnel through one method, so the core of this feature is making that method LRC-aware:
app/src/ai/blocklist/queued_query.rs:366 @ 8e984f0d—QueuedQueryModel::is_queue_next_prompt_enabled: per-conversation override falling back to the cachedAISettings::default_prompt_submission_mode.ConversationQueueState(L152-164) holds the per-conversation override;toggle_queue_next_prompt(L377) flips it.app/src/terminal/input.rs:13778 @ 8e984f0d—maybe_queue_input_for_in_progress_conversation: the submission intercept; consultsis_queue_next_prompt_enabledand conversation in-progress/blocked status. During an eligible agent-requested LRC the conversation status isInProgress(orBlocked), so no change is needed to its status gating.app/src/terminal/input.rs:6141 @ 8e984f0d—agent_mode_hint_text: ghost text switches to the queue hint (AGENT_MODE_AI_ENABLED_QUEUE_HINT_TEXT_*, L453-455) whenis_queue_next_prompt_enabledis true and the conversation is in progress. PRODUCT §13 falls out automatically.app/src/ai/blocklist/block/status_bar.rs:838 @ 8e984f0d— the queue chip (queue_next_prompt_button) renders accent-colored whenis_queue_next_prompt_enabledis true. PRODUCT §12 falls out automatically.app/src/terminal/model/block/interaction_mode.rs:102 @ 8e984f0d—Block::is_agent_in_controlplusBlock::is_agent_requested_command()form the trigger condition (PRODUCT §1-2). This covers the blocked-on-approval state (LongRunningCommandControlState::Agent { is_blocked, .. }) for agent-requested commands, while excluding user-in-control, tagged-in-only, and user-started LRCs where the user explicitly tagged in the agent.app/src/terminal/view.rs:27035 @ 8e984f0d—ToggleQueueNextPrompthandler (chip click +Cmd-Shift-J): resolves the active conversation and callsQueuedQueryModel::toggle_queue_next_prompt.TerminalViewholdsself.model, so it can check LRC control state when routing the toggle.- Re-render on LRC transitions is already wired: the status bar notifies on
CLISubagentEvent::UpdatedControlandModelEvent::BlockCompleted(status_bar.rs:231-248, 312-330), and its warping-indicator render already locks the terminal model and readsis_agent_in_control(L752-770). The input likewise already locksself.modelon hot paths (e.g.is_input_mode_toggle_disabled,input.rs:14409). app/src/settings/ai.rs:496-533 @ 8e984f0d—PromptSubmissionModesetting; the new enum setting is defined next to it and follows the sameimplement_setting_for_enum!pattern.app/src/settings_view/ai_page.rs:5771-5790 @ 8e984f0d—AIInputWidget::renderplaces the "Default prompt submission mode" dropdown underFeatureFlag::QueueSlashCommand; the new dropdown goes directly below it. Palette wiring pattern for the sibling setting:init_actions_from_parent_view(L367-388) + context flags insettings_view/mod.rs:521-522+ flag computation inworkspace/view.rs:22371.
No new feature flag (per decision): everything ships under the existing QueueSlashCommand gate that already wraps the chip, the queue panel, and the submission intercept.
Proposed changes
1. Setting (app/src/settings/ai.rs)
New enum setting in AISettings, defined next to PromptSubmissionMode:
pub enum LongRunningCommandSubmissionMode {
SendImmediately,
#[default]
QueueUntilCommandCompletes,
}
registered via implement_setting_for_enum! (cloud-synced, toml_path: "agents.warp_agent.other.long_running_command_submission_mode", feature_flag: FeatureFlag::QueueSlashCommand) and stored as the long_running_command_submission_mode field. display_name() returns "Send immediately" / "Queue until command finishes"; command_palette_description() the matching "Set long-running command submission: …" strings. The settings macro generates the matching AISettingsChangedEvent::LongRunningCommandSubmissionMode variant used below.
2. LRC-aware enablement, computed at the call sites (queued_query.rs, input.rs, status_bar.rs)
No LRC state is pushed into QueuedQueryModel; each call site determines the LRC context itself from the terminal model it already holds, and the model only answers the enablement question given that context.
QueuedQueryModel::is_queue_next_prompt_enabledgains alrc_auto_queue_active: boolparameter, computed via the sharedis_lrc_auto_queue_activehelper (queued_query.rs): true exactly when the queue feature flag is on,default_prompt_submission_mode == Interrupt,long_running_command_submission_mode == QueueUntilCommandCompletes, and the active block's agent controls an agent-requested command for this conversation (is_agent_requested_command()). The Interrupt requirement keeps the whole LRC machinery inert in Queue mode (PRODUCT §5). When true, return the LRC-scoped override if set, elsetrue(auto-enabled); when false, existing logic (persistent override → cached default). The persistent override is never consulted or written while the LRC branch is in effect, which yields the revert-on-LRC-end semantics for PRODUCT §14, §17.- One new field on
ConversationQueueState:queue_next_lrc_prompt_override: Option<bool>— a manual toggle made during an eligible agent-requested LRC. It is explicitly cleared when the command ends:TerminalViewcallsclear_queue_next_lrc_prompt_overrideonCLISubagentEvent::FinishedSubagent(PRODUCT §15-16, §23). Also dropped with the conversation's queue state. - Call sites that compute
lrc_auto_queue_active(each already holds the terminal model):maybe_queue_input_for_in_progress_conversation(input.rs) — the routing decision stays in the input, as today.agent_mode_hint_text(input.rs) — ghost text (PRODUCT §13).render_warping_indicator_for_latest_exchange(status_bar.rs) — the chip'sis_active(PRODUCT §12); this render already readsis_agent_in_controlfrom the locked terminal model.
- The settings are read directly from
AISettingsat each call site (no cache), so mid-LRC setting flips take effect on the next render/submission (PRODUCT §22). For chip/hint re-render on the setting change,QueuedQueryModel'sAISettingsChangedEventsubscription also re-emitsDefaultModeChangedfor theLongRunningCommandSubmissionModevariant.
3. Toggle routing (app/src/terminal/view.rs)
In the ToggleQueueNextPrompt handler, check is_lrc_auto_queue_active: when true, call QueuedQueryModel::toggle_queue_next_prompt_during_lrc(conversation_id, ctx), which writes queue_next_lrc_prompt_override = Some(!current_effective); otherwise the existing toggle_queue_next_prompt. Both emit QueueNextPromptToggled, which the status bar and input already subscribe to. Re-render on control transitions themselves (agent takes/loses control) is covered by the status bar's existing UpdatedControl/BlockCompleted notifies; the input additionally subscribes to CLISubagentEvent (SpawnedSubagent/UpdatedControl/FinishedSubagent/ControlHandedBackAfterTransfer) to refresh the ghost text, since its hint subscriptions did not previously cover control transitions.
4. Queued-row origin (queued_query.rs, input.rs, server/telemetry/events.rs)
New QueuedQueryOrigin::LrcAutoQueue variant (and matching TelemetryQueuedQueryOrigin value). maybe_queue_input_for_in_progress_conversation uses it instead of AutoQueueToggle when the LRC branch is the effective enabler (lrc_auto_queue_active is true and the persistent non-LRC queue toggle/default would be off), the submission is a prompt, and the current queue head is absent or already has LrcAutoQueue origin. If regular queue mode is already enabled, or if the current queue head has any other origin, the new prompt keeps AutoQueueToggle so command-finish delivery cannot jump it over older queued rows. Command rows always keep AutoQueueToggle since they cannot be delivered to the agent (PRODUCT §11). The origin drives both the send-on-command-finish behavior (§5 below) and the panel row suffix (§6 below), and distinguishes the rows in QueuedPrompt* telemetry. Exhaustive matches on the enum get the new arm.
5. Send queued prompts when the command finishes (app/src/terminal/view.rs)
New TerminalView::send_lrc_queued_prompts(conversation_id, ctx): collects the conversation's leading queued rows with LrcAutoQueue origin, and for each (in queue order) dispatches it via Input::submit_queued_prompt_for_active_pane + QueuedQueryModel::remove_fired_row — the same path the panel's send-now button uses. Called from the CLISubagentEvent::FinishedSubagent handler, right after clear_queue_next_lrc_prompt_override (PRODUCT §10). FinishedSubagent fires when the command block completes regardless of who held control at that moment, which gives the fire-after-manual-takeover behavior of §10. Rows with other origins stop the command-finish drain and keep the existing end-of-response drain (drain_queued_prompts).
6. Queued row suffix (app/src/terminal/view/queued_prompts_panel.rs)
In render_row, non-command rows with LrcAutoQueue origin render an italic sub_text_color suffix — "(queued until the command finishes)" (LRC_AUTO_QUEUE_ROW_SUFFIX) — after the preview text, mirroring the model picker's "(selected)" treatment. The preview is wrapped in Shrinkable::new(1., …) so it shrinks to its text (clipping with an ellipsis when long) and the suffix hugs it.
7. Settings UI (app/src/settings_view/ai_page.rs)
Inside the existing FeatureFlag::QueueSlashCommand.is_enabled() block in AIInputWidget::render, after the "Default prompt submission mode" dropdown: a second render_dropdown_item labeled "Default long-running command submission mode", rendered only when default_prompt_submission_mode == Interrupt (PRODUCT §19). The dropdown handle (lrc_submission_mode_dropdown) lives on AISettingsPageView, is built by OtherAIWidget::create_lrc_submission_mode_dropdown (the create_default_prompt_submission_mode_dropdown pattern), and re-syncs its selection on AISettingsChangedEvent::LongRunningCommandSubmissionMode. A new AISettingsPageAction::SetLongRunningCommandSubmissionMode(mode) persists via set_value (the SetPromptSubmissionMode pattern). LRC terms live in AIInputWidget::search_terms.
8. Command palette (settings_view/mod.rs, workspace/view.rs, ai_page.rs)
- New context flags
LRC_SUBMISSION_SEND_IMMEDIATELY/LRC_SUBMISSION_QUEUE_UNTIL_COMMAND_COMPLETESinsettings_view/mod.rsflags, set fromai_settings.long_running_command_submission_modein the workspace context computation (thePROMPT_SUBMISSION_*pattern). - Per-mode
FixedBindings registered inai_page::init_actions_from_parent_viewnext to thePromptSubmissionModebindings, additionally gated onPROMPT_SUBMISSION_INTERRUPTso the entries hide when the setting is hidden (PRODUCT §21).
Testing and validation
app/src/ai/blocklist/queued_query_tests.rs(model-level, maps to PRODUCT invariants):is_queue_next_prompt_enabledwithlrc_auto_queue_active→ enabled by default; without → existing behavior unchanged (§1, §14, §20).toggle_queue_next_prompt_during_lrcwrites the LRC-scoped override, leaves the persistent override untouched, and re-toggling re-enables (§15, §16);clear_queue_next_lrc_prompt_override(command end) restores auto-enable for the next LRC (§23) and the pre-LRC state is what the non-LRC path returns afterward (§14, §17).
app/src/terminal/input_tests.rs(host-level, next to the existing queue host tests): with the active block's agent in control for an agent-requested command and the default settings, a non-empty AI submission queues instead of submitting, withLrcAutoQueueorigin when regular queue mode is off and the queue is empty or its head is alreadyLrcAutoQueue(§6); if regular queue mode is on or the current queue head is notLrcAutoQueue, the submission keeps the generic origin and does not fire at command finish (§7, §10); user-tagged LRCs do not auto-queue (§2); ghost text returns the queue hint (§13); "Send immediately" → submission routes as today (§20, §22); Queue default mode → row queues with the generic origin (§5);send_lrc_queued_promptsfires leadingLrcAutoQueuerows in order and leaves other rows queued (§10).- Chip state (§12) is a pure read of
is_queue_next_prompt_enabled— covered by the model tests; verify visually in the manual smoke. - Manual smoke: run a dev-server-style command via the agent, let the agent take control, submit prompts into an empty queue while regular queue mode is off (they queue with the row suffix and fire together at command finish), repeat with regular queue mode on or a non-LRC queued row at the head (the new row has no suffix and does not fire at command finish), toggle the chip off mid-LRC (submission steers immediately), and flip the dropdown in Settings → AI mid-LRC (including hiding it by switching the default mode to Queue).
cargo check+./script/format; full presubmit before PR per repo workflow.
Parallelization
Not beneficial: the change is a single coupled chain (setting → model API → call sites → settings UI) where each step consumes the previous one's types. A single agent implements it on this branch (harry/quality-839-auto-enable-prompt-queueing-during-lrc).