QUALITY-780: client core — WaitingForEvents status variant + predicates + persistence (Wave 1)#11998
Draft
cephalonaut wants to merge 13 commits into
Draft
QUALITY-780: client core — WaitingForEvents status variant + predicates + persistence (Wave 1)#11998cephalonaut wants to merge 13 commits into
cephalonaut wants to merge 13 commits into
Conversation
…es + persistence (Wave 1) Adds first-class WaitingForEvents conversation status, the is_terminal / is_quiescent predicate split (removing is_done), and the persistence round-trip for the yielded state. Bumps warp_multi_agent_api to the Wave 0 proto SHA (792d5878). Per the QUALITY-780 client TECH spec sections §1–§3: - §1: ConversationStatus::WaitingForEvents variant with Display label "Waiting for events", Icon::ClockSnooze, and ansi_fg_blue() / ansi_bg_blue() colors. Adds ansi_bg_blue() to WarpTheme and blue_listening_icon() to icons.rs. TODO(design) markers flag final visual. - §2: is_terminal() (Success | Error | Cancelled) and is_quiescent() (!InProgress) replace the legacy is_done() predicate. Migrates all five §2.1 callers to is_terminal(): conversation-list view, command palette fork item + data_source, /cost slash command, and the orchestration pill bar overflow menu (delete-vs-kill gating). Adds a is_waiting_for_events() probe. Drops is_done() entirely with a comment explaining the split. - §3: AgentConversationData.waiting_for_events bool (#[serde(default, skip_serializing_if = is_false)]) round-trips through write_updated_conversation_state and new_restored_synthesizing_on_empty. The restore-site override applies to both the empty-tasks and populated-tasks branches so a child conversation persisted while yielded re-enters WaitingForEvents on next launch instead of being silently reclassified as Success by derive_status_from_root_task. Wave 2 placeholder arms with TODO(client-{driver,sync-notif,pill-bar, detection}) comments wherever the compiler surfaced a non-exhaustive match against ConversationStatus or the new ToolType::WaitForEvents / ToolCallResultType::WaitForEvents proto variants. local_agent_task_sync_model::map_conversation_status uses the final §5 shape (AgentTaskState::InProgress, None) per the prompt. orchestration_topology::aggregated_orchestrator_status and the pill bar sort key co-bucket WaitingForEvents with InProgress for now; the final §7 precedence ordering is owned by client-pill-bar. Test coverage in conversation_tests.rs: - Display label asserts the placeholder text. - is_terminal / is_quiescent / is_waiting_for_events / is_in_progress matrix across all six variants. - Restore-site override fires in both populated- and empty-task paths. - Persistence JSON round-trip (true and false), default skip behavior, and legacy-row deserialization. Validation: - cargo fmt --check - cargo clippy --workspace --exclude warp_completer --all-targets --tests -- -D warnings - cargo clippy -p warp_completer --all-targets --tests -- -D warnings - cargo nextest run -p warp -p persistence (4801 tests pass) This branch is the merge base for Wave 2 PRs (client-driver, client-sync-notif, client-pill-bar, client-detection); Wave 2 PRs will target this branch rather than master. Proto dependency: warpdotdev/warp-proto-apis#315 Co-Authored-By: Oz <oz-agent@warp.dev>
Implement client TECH §7 in the orchestration pill bar: * aggregated_orchestrator_status now uses the full precedence `InProgress > Blocked > WaitingForEvents > Error > Cancelled > Success`. Adds a dedicated `any_waiting` accumulator instead of co-bucketing `WaitingForEvents` with `InProgress`, and updates the doc-comment precedence list. * pill_status_sort_key gives `WaitingForEvents` its own explicit slot with the same sort key as `InProgress`, keeping yielded pills in the active half of the bar and strictly left of the done bucket. * New precedence tests pin each WaitingForEvents boundary (covers PRODUCT.md (22)) and a sort-key test confirms WaitingForEvents shares the InProgress bucket and is < DONE_STATUS_KEY (covers (24)). Co-Authored-By: Oz <oz-agent@warp.dev>
3 tasks
Implements client TECH §4: when the conversation transitions into `ConversationStatus::WaitingForEvents`, the Oz CLI driver's `UpdatedConversationStatus` handler now schedules a local watchdog timer instead of resolving `run_exit` (so the worker stays alive across the yield) and the watchdog uses the server-supplied `idle_timeout_seconds` from the unresolved `wait_for_events` tool call, falling back to a new `DEFAULT_ORCHESTRATED_IDLE_TIMEOUT_SECONDS` (30 minutes) when unset. The watchdog is decoupled from `run_exit`: on fire it does NOT cancel the run; instead it (will) emit a `WaitForEventsResult` tool-call result that the agent's next turn processes. A `TODO(integration, QUALITY-780)` marker at `emit_wait_for_events_timeout_result` documents the exact helper signature client-detection's Wave 2 work will expose, so the integrator can replace the stub with a single line. A generation counter (mirroring the existing `IdleTimeoutSender` pattern, but separate so the watchdog and the `idle_on_complete` timer don't share state) supersedes the watchdog when the conversation returns to `InProgress` via the normal resume path (per client TECH §8.3). `find_unresolved_wait_for_events_call` scans the conversation's linearized messages for the most recent unresolved `WaitForEvents` tool call, intentionally avoiding coupling to client-detection's internal `mark_/clear_conversation_waiting_for_events` storage. Tests cover: - Default-timeout constant (30 minutes). - `watchdog_timeout_for_call` honors positive server-supplied values, falls back to default for `0` (prost flattened-scalar unset), and clamps negative values to the default. - `find_unresolved_wait_for_events_call` returns `None` when there's no `WaitForEvents` call, returns the call when present and unresolved, returns `None` when a later `ToolCallResult` matches the `tool_call_id`, and returns the most-recent unresolved call when multiple yields appear in the same conversation. - Roundtrip: `idle_timeout_seconds=0` is preserved through the detection helper and resolved to the default at the timeout-helper boundary. PRODUCT.md invariants (11), (12), (13) covered at the unit level. The full subscription-driven scheduling/cancellation and end-to-end fire-then-resume flows are intentionally deferred to the Wave 3 integration tests against the fake server (per client TECH §"Testing and validation"). Co-Authored-By: Oz <oz-agent@warp.dev>
5 tasks
Implements client TECH §5 and §6 for QUALITY-780 by replacing the WaitingForEvents placeholders left by client-core. §5 — `local_agent_task_sync_model::map_conversation_status`: keep the final shape `WaitingForEvents => (AgentTaskState::InProgress, None)` and remove the client-sync-notif TODO marker on the comment now that this is the canonical mapping. §6 — `agent_management_model`: - `handle_history_event_for_mailbox`'s `WaitingForEvents` arm now mirrors `InProgress` and clears any stale notification for the origin via `remove_notification_by_source`. No new toast fires for yielded conversations. - `should_trigger_notification` is rewritten as an exhaustive match so a future `ConversationStatus` variant forces a deliberate decision instead of silently returning `false`. Behavior preserved: Success/Blocked/Error → true; InProgress/WaitingForEvents/Cancelled → false. Also finalizes `AgentRunDisplayStatus::from_conversation_status` — the conversation-list display keeps yielded runs in the `ConversationInProgress` bucket, matching the task-sync mapping. Visual differentiation of waiting vs streaming is owned by the orchestration pill bar (client TECH §7), not this status enum. Tests: - `local_agent_task_sync_model_tests`: a new unit test routes a conversation through the history model to flip its status to `WaitingForEvents` and asserts `map_conversation_status` returns `(AgentTaskState::InProgress, None)`. A second test covers the `InProgress` mapping for completeness. - `agent_management_model_tests`: six new pure-function tests cover every `ConversationStatus` variant for `should_trigger_notification`. Two end-to-end tests drive `UpdatedConversationStatus::Changed` through `update_status` and assert that a stale notification is cleared and no new toast is queued for `WaitingForEvents` and for the `WaitingForEvents → InProgress` resume. Co-Authored-By: Oz <oz-agent@warp.dev>
3 tasks
Refreshes the warp_multi_agent_api dependency to pick up the Wave 0 proto follow-up that: - Adds `WaitForEventsResult` as a top-level message (previously nested inside `Message::ToolCallResult`). - Adds `WaitForEventsResult` as a variant in `Request::Input::ToolCallResult.result` oneof at field 35, so the client watchdog timeout result can round-trip across the wire. No code changes needed in this Wave 1 branch: all Wave 1 references to the new variants use variant-name patterns (`Tool::WaitForEvents(_)`, `ToolCallResultType::WaitForEvents(_)`), not the inner message type, so the `message::tool_call_result::WaitForEventsResult` -> `WaitForEventsResult` rename is a no-op here. Validation: - cargo fmt --check - cargo clippy --workspace --exclude warp_completer --all-targets --tests -- -D warnings - cargo clippy -p warp_completer --all-targets --tests -- -D warnings - cargo nextest run -p warp 'ai::agent::conversation::tests::' (31/31 passed) Proto dependency: warpdotdev/warp-proto-apis#315 (HEAD d622e1c) Co-Authored-By: Oz <oz-agent@warp.dev>
Implements §8 of the QUALITY-780 client TECH spec: - §8.1 detection: BlocklistAIHistoryModel.apply_client_actions now pattern-matches Message::ToolCall::WaitForEvents in inbound AddMessagesToTask actions and transitions the owning conversation to WaitingForEvents via the new mark_conversation_waiting_for_events helper. The in-memory unresolved tool_call_id is stored on the AIConversation so the later resume signal can be matched against it (the id is intentionally NOT persisted; restart hydrates to None and the post-restart fallback scans the transcript via find_unresolved_wait_for_events_tool_call_id). - §8.3 resume detection: the same apply_client_actions hook also pattern-matches Message::ToolCallResult.WaitForEvents (server echo of the watchdog-timeout result) and Message::ToolCallResult.Cancel (the generic supersede-by-inbound-input marker), routing both through clear_conversation_waiting_for_events_if_matches so only the unresolved tool_call_id collapses the waiting state. - §8.2 ordering rule: the BlocklistAIController action subscriber that fires the post-stream Success transition now skips the update_conversation_status(Success) call when the conversation is already in WaitingForEvents, so the detection transition isn't clobbered by the response-stream completion path. The response stream itself is still marked completed successfully via mark_response_stream_completed_successfully. - §4 emission helper (scope-expansion from orchestrator): adds a public submit_wait_for_events_timeout(conversation_id, tool_call_id, ctx) on BlocklistAIController that the driver wave will invoke from its local watchdog when the idle timer fires. The helper performs the WaitingForEvents → InProgress transition immediately via clear_conversation_waiting_for_events_if_matches; the wire form (Request.Input.ToolCallResult.WaitForEvents) is emitted on the next outbound request via the new TryFrom<WaitForEventsResult> proto conversion. - Adds AIAgentActionResultType::WaitForEvents(WaitForEventsResult) with Display / description / is_successful / redaction / SDK-output arms. Wires Wave 0 proto SHA bump d622e1cec8de5c50b55c2d4f3050ce48dae464c4 (Request.Input.ToolCallResult.WaitForEvents variant + top-level WaitForEventsResult). - Cleans up TODO(client-detection) markers in convert_conversation.rs: both convert_tool_call_result_to_input and create_cancelled_result_ for_tool_call return None for WaitForEvents because the wait-state transition lives in apply_client_actions, not on the exchange input stream — restored transcripts stay symmetric with the Server variant. Tests: history_model_tests covers mark/clear helpers, mismatch noop, unconditional clear, and new-conversation no-inheritance (PRODUCT.md (31)). Co-Authored-By: Oz <oz-agent@warp.dev>
…integration) Replaces the client-driver Wave 2 `TODO(integration)` stub in `emit_wait_for_events_timeout_result` with the actual call into client-detection's `BlocklistAIController::submit_wait_for_events_timeout` helper. Resolves the cross-agent boundary at integration time per the orchestration plan. Co-Authored-By: Oz <oz-agent@warp.dev>
Adds api::ToolType::WaitForEvents to the orchestration block of get_supported_tools so the server can detect that this client build can pattern-match the new public wait_for_events tool-call variant. Without this declaration, the server's new gating logic falls back to the legacy server-handled emission to protect older clients. Co-Authored-By: Oz <oz-agent@warp.dev>
Adds an explicit `Tool::WaitForEvents` arm to the action-conversion match next to `Tool::Server`. Without it, the wildcard fall-through raised `ToolToAIAgentActionError::UnexpectedTool` whenever `apply_client_actions` processed a `WaitForEvents` tool-call message, surfacing as "Failed to apply client actions to conversation: Conversation(UpdateTask(ConversionError(ToolError(UnexpectedTool))))" in the controller log. `WaitForEvents` is handled separately via `BlocklistAIHistoryModel::mark_conversation_waiting_for_events` (client TECH §8.1), so the action-conversion path returns `MaybeAIAgentAction::NoClientRepresentation` \u2014 same pattern as the existing `Tool::Server` arm. Co-Authored-By: Oz <oz-agent@warp.dev>
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.
Description
Wave 1 of QUALITY-780: adds the first-class
WaitingForEventsconversation status, theis_terminal/is_quiescentpredicate split (removingis_done), and the persistence round-trip for the yielded state. Bumpswarp_multi_agent_apito the Wave 0 proto SHA (792d5878).Per the client TECH spec sections §1–§3:
ConversationStatus::WaitingForEventsvariant with Display label "Waiting for events",Icon::ClockSnooze, andansi_fg_blue()/ansi_bg_blue()colors. Addsansi_bg_blue()toWarpThemeandblue_listening_icon()toicons.rs.TODO(design)markers flag the final visual.is_terminal()(Success | Error | Cancelled) andis_quiescent()(!InProgress) replace the legacyis_done()predicate. Migrates all five §2.1 callers tois_terminal(): conversation-list view, command palette fork item + data_source,/costslash command, and the orchestration pill bar overflow menu (delete-vs-kill gating). Adds anis_waiting_for_events()probe. Dropsis_done()entirely with a comment explaining the split.AgentConversationData.waiting_for_eventsbool (#[serde(default, skip_serializing_if = "is_false")]) round-trips throughwrite_updated_conversation_stateandnew_restored_synthesizing_on_empty. The restore-site override applies to both the empty-tasks and populated-tasks branches so a child conversation persisted while yielded re-entersWaitingForEventson next launch instead of being silently reclassified asSuccessbyderive_status_from_root_task.Wave 2 placeholder arms with
TODO(client-{driver,sync-notif,pill-bar,detection})comments are added wherever the compiler surfaced a non-exhaustive match againstConversationStatusor the newToolType::WaitForEvents/ToolCallResultType::WaitForEventsproto variants.local_agent_task_sync_model::map_conversation_statususes the final §5 shape (AgentTaskState::InProgress, None) per the prompt.orchestration_topology::aggregated_orchestrator_statusand the pill bar sort key co-bucketWaitingForEventswithInProgressfor now; the final §7 precedence ordering is owned byclient-pill-bar.This branch is the merge base for Wave 2 PRs (
client-driver,client-sync-notif,client-pill-bar,client-detection); those PRs will target this branch rather than master.Proto dependency: warpdotdev/warp-proto-apis#315
Linked Issue
QUALITY-780
ready-to-specorready-to-implement.Testing
New tests in
app/src/ai/agent/conversation_tests.rs:is_terminal/is_quiescent/is_waiting_for_events/is_in_progressmatrix across all six variants.Validation run:
cargo fmt --checkcargo clippy --workspace --exclude warp_completer --all-targets --tests -- -D warningscargo clippy -p warp_completer --all-targets --tests -- -D warningscargo nextest run -p warp -p persistence(4801 tests pass)I have manually tested my changes locally with
./script/runAgent Mode
Conversation: https://staging.warp.dev/conversation/bc28de12-e272-4eff-a89c-416e32888bd9
Run: https://oz.staging.warp.dev/runs/019e837c-4230-72a4-82ea-73e4abf89e77
This PR was generated with Oz.