first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# Cloud-to-cloud handoff PR 1 tech spec
|
||||
## Problem statement
|
||||
The master cloud-to-cloud handoff plan needs an initial mergeable PR that adds the scaffolding required by later follow-up orchestration and UI work without changing end-user behavior. PR 1 should make the client aware of the `HandoffCloudCloud` rollout boundary, add a typed client method for the server follow-up API, and start isolating task/run identity from execution/session-scoped data in the client model.
|
||||
This PR intentionally does not add the tombstone Continue entrypoint, does not submit follow-ups from the UI, and does not attach a follow-up shared session to an existing terminal. Those behaviors belong in later PRs after the foundational APIs exist.
|
||||
## Current state
|
||||
The current branch already adds the UI/model layer needed to attach a new backing shared session to an existing shared-session viewer; that foundation is documented in `specs/REMOTE-1478/TECH.md`. The broader sequencing and intended user flow are documented in `specs/handoff-cloud-cloud/TECH.md`.
|
||||
Feature flags are defined in `crates/warp_features/src/lib.rs`. `CloudModeSetupV2` already exists near the end of the enum at `crates/warp_features/src/lib.rs:827`, and the rollout arrays are defined at `crates/warp_features/src/lib.rs:852`, `crates/warp_features/src/lib.rs:910`, and `crates/warp_features/src/lib.rs:926`. There is currently no `HandoffCloudCloud` flag. The Cargo feature graph in `app/Cargo.toml` is the right place to encode that `handoff_cloud_cloud` depends on `cloud_mode_setup_v2`.
|
||||
The public API client already has ambient run methods for spawn, list, and get. The `AIClient` trait includes `spawn_agent`, `list_ambient_agent_tasks`, and `get_ambient_agent_task` at `app/src/server/server_api/ai.rs:799`, and the `ServerApi` implementation posts to `agent/run`, lists `agent/runs`, and gets `agent/runs/{task_id}` at `app/src/server/server_api/ai.rs:1404`. Adjacent public API methods post to run-scoped subresources such as `agent/runs/{task_id}/attachments/prepare` at `app/src/server/server_api/ai.rs:1785`, which is the natural implementation pattern for the follow-up API.
|
||||
The server follow-up API is `POST /api/v1/agent/runs/{runId}/followups` with a JSON body containing `message`. The server route and request type are in the server worktree at `/Users/zachbai/.warp-dev/worktrees/warp-server/cloud-agent-task-name/public_api/openapi.yaml:644`, `/Users/zachbai/.warp-dev/worktrees/warp-server/cloud-agent-task-name/router/handlers/public_api/agent_webhooks.go:181`, `/Users/zachbai/.warp-dev/worktrees/warp-server/cloud-agent-task-name/router/handlers/public_api/agent_webhooks.go:608`, and `/Users/zachbai/.warp-dev/worktrees/warp-server/cloud-agent-task-name/public_api/types/types.gen.go:1225`.
|
||||
`AmbientAgentTask` currently exposes execution-scoped fields directly: `session_id`, `session_link`, `conversation_id`, `request_usage`, and `is_sandbox_running` at `app/src/ai/ambient_agents/task.rs:217`. `is_no_longer_running` already combines sandbox liveness with run state at `app/src/ai/ambient_agents/task.rs:257`. `SessionJoinInfo::from_task` reads `task.session_link` and `task.session_id` directly at `app/src/ai/ambient_agents/spawn.rs:32`. Agent management and details code also reads these flattened fields directly for session status, links, conversation dedupe, open actions, and details-panel display, including `app/src/ai/agent_conversations_model.rs:468`, `app/src/ai/agent_conversations_model.rs:515`, `app/src/ai/agent_conversations_model.rs:528`, `app/src/ai/agent_conversations_model.rs:553`, `app/src/ai/agent_conversations_model.rs:1293`, and `app/src/ai/conversation_details_panel.rs:337`.
|
||||
## Goals
|
||||
Add a disabled `HandoffCloudCloud` feature flag, with the app Cargo feature depending on `cloud_mode_setup_v2`.
|
||||
Add typed client support for submitting a follow-up prompt to a run via the public API.
|
||||
Introduce run/execution-aware accessors on `AmbientAgentTask` that preserve current behavior while giving later PRs a place to route active/latest-execution semantics.
|
||||
Move the most important existing call sites from direct flattened fields to the new accessors when doing so is behavior-preserving and low-risk.
|
||||
Add targeted unit tests for the new API serialization/path helper and task accessor behavior.
|
||||
## Non-goals
|
||||
No visible cloud conversation tombstone changes.
|
||||
No terminal input submission changes.
|
||||
No cloud mode setup UI changes.
|
||||
No follow-up polling or shared-session hotswap orchestration.
|
||||
No attempt to parse or store a full run-executions array unless the public API already returns it to the client in the current schema. PR 1 should define seams that can absorb that shape later.
|
||||
No rollout enablement in `DOGFOOD_FLAGS`, `PREVIEW_FLAGS`, or `RELEASE_FLAGS`.
|
||||
## Proposed changes
|
||||
### Feature flag scaffolding
|
||||
Add `HandoffCloudCloud` to `FeatureFlag` in `crates/warp_features/src/lib.rs`, preserving the enum's chronological ordering. Keep the description concise and product-focused, for example gating cloud-to-cloud continuation of cloud mode conversations.
|
||||
Do not add the flag to any rollout arrays in PR 1. The flag should be available for local overrides and future PRs, but disabled by default.
|
||||
Add `handoff_cloud_cloud = ["cloud_mode_setup_v2"]` to `app/Cargo.toml` and map the Cargo feature to `FeatureFlag::HandoffCloudCloud` in `app/src/lib.rs`. This keeps the dependency explicit in the build feature graph without adding rollout dependency tests.
|
||||
### Follow-up API client
|
||||
Add a request type near `SpawnAgentRequest` in `app/src/server/server_api/ai.rs`:
|
||||
`RunFollowupRequest { message: String }`, serialized as `{"message":"..."}`.
|
||||
Add an `AIClient` method named `submit_run_followup` with parameters `run_id: &AmbientAgentTaskId` and `request: RunFollowupRequest`, returning `anyhow::Result<(), anyhow::Error>`. The current server behavior does not expose a client-needed response payload, so implement with `post_public_api_unit(&build_run_followup_url(run_id), &request)`.
|
||||
Add a small path helper, for example `build_run_followup_url(run_id: &AmbientAgentTaskId) -> String`, mirroring `build_list_agent_runs_url` at `app/src/server/server_api/ai.rs:521`. This makes unit testing the endpoint path independent from HTTP mocking.
|
||||
Do not call this new method from UI or orchestration code in PR 1.
|
||||
### Run/execution-aware task accessors
|
||||
Add lightweight accessors to `AmbientAgentTask` in `app/src/ai/ambient_agents/task.rs`. For PR 1 these should project the existing flattened fields as the active/latest execution:
|
||||
`run_id() -> AmbientAgentTaskId` returning `task_id`.
|
||||
`conversation_id() -> Option<&str>` returning `conversation_id.as_deref()`.
|
||||
`active_run_execution() -> RunExecution<'_>` returning a borrowed projection of the flattened execution-scoped fields.
|
||||
Add `RunExecution<'a>` with borrowed `session_id`, non-empty `session_link`, `request_usage`, and `is_sandbox_running` fields. The key is that callers stop encoding the assumption that task-level fields are intrinsically task-scoped.
|
||||
Update `is_no_longer_running` to use `active_run_execution().is_sandbox_running` and preserve existing behavior.
|
||||
Move `SessionJoinInfo::from_task` in `app/src/ai/ambient_agents/spawn.rs:32` to use `active_run_execution()`.
|
||||
Update low-risk UI/model call sites that only need the current projection:
|
||||
`ConversationOrTask::session_id` should parse `active_run_execution().session_id`.
|
||||
`link_preference` should use `active_run_execution().is_sandbox_running`.
|
||||
`session_or_conversation_link` should use `active_run_execution().session_link` and `conversation_id()`.
|
||||
`get_session_status` should use the session/link accessors.
|
||||
`get_open_action` and conversation shadowing should use `run_id()` and `conversation_id()`.
|
||||
`ConversationDetailsData::from_task` should use `conversation_id()` for the details-panel conversation id and `active_run_execution().request_usage` for credits.
|
||||
Avoid broad mechanical churn in call sites where fields are clearly task metadata rather than execution data, such as title, prompt, state, source, creator, artifact list, and config snapshot.
|
||||
### Optional naming cleanup
|
||||
Do not rename `AmbientAgentTaskId` or public UI labels in PR 1. The server and much of the client still use run/task terminology interchangeably, and a broad rename would create churn without improving mergeability. The new `run_id()` accessor is enough to clarify stable identity for later PRs.
|
||||
## Testing strategy
|
||||
Add public API helper tests in `app/src/server/server_api/ai_test.rs` for the follow-up endpoint path. If the request type is public enough to serialize directly, add a serialization test asserting the JSON shape is exactly `{"message":"..."}`.
|
||||
Add or update ambient task tests in `app/src/ai/ambient_agents/spawn_tests.rs` or a new `app/src/ai/ambient_agents/task_tests.rs` to cover:
|
||||
`SessionJoinInfo::from_task` still prefers server-provided session links.
|
||||
It still falls back to constructing a join link from `session_id`.
|
||||
It returns `None` when neither an active link nor parseable session id exists.
|
||||
The new accessors preserve current flattened-field behavior.
|
||||
Run targeted checks after implementation:
|
||||
`cargo nextest run -p warp server::server_api::ai::tests::build_run_followup_url_routes_to_run_followups server::server_api::ai::tests::serialize_run_followup_request ai::ambient_agents::spawn::tests`
|
||||
`cargo nextest run -p warp ai::agent_conversations_model::tests ai::conversation_details_panel::tests`
|
||||
`cargo check -p warp --features handoff_cloud_cloud`
|
||||
The exact package names should be verified during implementation from `Cargo.toml` before running. Do not use `cargo fmt --all` or file-specific `cargo fmt`; if formatting is needed before review, use the repo’s standard `cargo fmt` per project guidance.
|
||||
## Rollout and compatibility
|
||||
The flag is disabled by default, so PR 1 should not change runtime behavior.
|
||||
The new API client method is unused in PR 1 and should therefore be safe to merge before server rollout, as long as it compiles against existing client code.
|
||||
The accessor migration should be behavior-preserving because each accessor initially projects the same flattened fields. If a direct field use is ambiguous or risky, leave it in place and document it as follow-up rather than expanding the PR scope.
|
||||
## Risks and mitigations
|
||||
The largest risk is accidentally changing management view link selection or session-open behavior while replacing direct field reads. Keep changes small, prefer local accessor substitutions, and rely on existing spawn and agent management tests where available.
|
||||
The follow-up endpoint response shape may differ from the assumed empty response. During implementation, verify the server contract before choosing `post_public_api_unit`; if the server returns a body, add a minimal response type and test deserialization.
|
||||
Runtime implication of `HandoffCloudCloud` to `CloudModeSetupV2` is intentionally not implemented in PR 1. The Cargo feature dependency covers compiled builds, while local runtime overrides can still force unusual states for targeted testing.
|
||||
The accessor names may need to change once the client consumes first-class run-execution data. Keep PR 1 names descriptive but avoid adding a large abstraction that is not backed by current API data.
|
||||
## Definition of done
|
||||
`HandoffCloudCloud` exists and is disabled by default.
|
||||
`handoff_cloud_cloud` in `app/Cargo.toml` depends on `cloud_mode_setup_v2`.
|
||||
`AIClient` and `ServerApi` expose a typed follow-up submission method for `POST agent/runs/{run_id}/followups`.
|
||||
`AmbientAgentTask` exposes run/execution-aware accessors and the main session/conversation call sites use them without behavior changes.
|
||||
Targeted tests cover the follow-up API path/serialization and task/session accessor behavior, and a feature-gated compile check passes.
|
||||
@@ -0,0 +1,69 @@
|
||||
# Cloud-to-cloud handoff PR 2 tech spec
|
||||
## Problem statement
|
||||
PR 2 should add the orchestration layer that turns an existing cloud agent run into a follow-up execution and hands the resulting fresh shared session to the hotswap path. This PR should remain mergeable while `HandoffCloudCloud` is disabled by default, and it should not add the user-visible tombstone Continue button or terminal-input submission route yet.
|
||||
The intended boundary is model/API behavior: a future UI can call one ambient-model method with a follow-up prompt, the client submits `POST agent/runs/{runId}/followups`, polls the same run until a new joinable session appears, ignores the ended session, and emits `FollowupSessionReady` so the existing viewer manager attaches the new session in append mode.
|
||||
## Current state
|
||||
PR 1 added the disabled `HandoffCloudCloud` flag and encoded the Cargo feature dependency on `cloud_mode_setup_v2` in `app/Cargo.toml:924`. It also added `RunFollowupRequest`, `AIClient::submit_run_followup`, and `build_run_followup_url` in `app/src/server/server_api/ai.rs (211-216, 837-840, 1467-1471)`, with endpoint/serialization tests in `app/src/server/server_api/ai_test.rs (988-1000)`.
|
||||
`AmbientAgentTask` now has `run_id()`, `conversation_id()`, and `active_run_execution()` accessors that project the current flattened response fields into a `RunExecution` view in `app/src/ai/ambient_agents/task.rs (247-279)`. `SessionJoinInfo::from_task` already consumes that projection in `app/src/ai/ambient_agents/spawn.rs (31-57)`.
|
||||
Initial cloud startup is still a single combined helper: `spawn_task` creates a run, polls `get_ambient_agent_task`, emits state changes, and ends when the first session is joinable in `app/src/ai/ambient_agents/spawn.rs (85-176)`. There is no reusable “poll an existing run until a new execution session is ready” helper yet.
|
||||
`AmbientAgentViewModel` still models startup as `Status::WaitingForSession { progress }` without distinguishing an initial run from a follow-up execution in `app/src/terminal/view/ambient_agent/model.rs (50-64)`. The existing `SessionStarted` handler infers follow-up readiness from being already in `AgentRunning`, which is too implicit for a model-driven follow-up flow in `app/src/terminal/view/ambient_agent/model.rs (627-638)`. The current `attach_followup_session` method simply emits `FollowupSessionReady` for a known session ID, which is useful test scaffolding but does not submit or poll a follow-up in `app/src/terminal/view/ambient_agent/model.rs:349`.
|
||||
The hotswap receiver already exists. `create_cloud_mode_view` routes `SessionReady` to `connect_to_session` and `FollowupSessionReady` to `attach_followup_session` in `app/src/terminal/view/ambient_agent/mod.rs (69-82)`. The viewer manager’s follow-up attach path replaces the active network and joins with append-mode scrollback in `app/src/terminal/shared_session/viewer/terminal_manager.rs (338-384)`.
|
||||
The UI has important side effects tied to initial dispatch. `DispatchedAgent` inserts the initial optimistic user query in `TerminalView::handle_ambient_agent_event` and drives the ambient entry-block insertion subscription in `app/src/terminal/view/ambient_agent/view_impl.rs (105-131, 445-481)`. PR 2 should avoid reusing that event for follow-ups, because doing so would blur initial-run and follow-up behavior before the UX PR.
|
||||
## Goals
|
||||
Add reusable follow-up orchestration that submits a prompt to an existing run and waits for a fresh active execution session.
|
||||
Make the ambient view model explicitly track whether it is waiting for an initial session or a follow-up session.
|
||||
Track the active or previous execution session ID so follow-up polling can ignore stale readiness from the ended session.
|
||||
Emit the already-supported `FollowupSessionReady` event when the new session is ready, allowing the existing hotswap path to attach it.
|
||||
Reuse existing Cloud Mode setup/loading/error state machinery for follow-up waiting and failures, but without adding a visible Continue entrypoint.
|
||||
Keep the implementation behind `FeatureFlag::HandoffCloudCloud` and preserve behavior with the flag off.
|
||||
## Non-goals
|
||||
No tombstone Continue button, action, or copy changes.
|
||||
No terminal input routing changes for submitting follow-up prompts.
|
||||
No embedded follow-up prompt editor in the tombstone.
|
||||
No product decision on tombstone stacking or update-in-place behavior.
|
||||
No first-class server execution-array parsing unless the public API response shape already exposes it in this branch.
|
||||
No rollout enablement for `HandoffCloudCloud`.
|
||||
## Proposed changes
|
||||
### Reusable run polling and follow-up helper
|
||||
Refactor `spawn_task` in `app/src/ai/ambient_agents/spawn.rs` so run creation and run readiness monitoring are separate. Keep the public `spawn_task(request, ai_client, timeout)` behavior the same by having it call a new internal polling helper after `spawn_agent` succeeds.
|
||||
Add a helper such as `poll_run_until_joinable_session(run_id, ai_client, previous_session_id, timeout)` that repeatedly calls `get_ambient_agent_task(&run_id)`, emits `StateChanged` when state changes, and returns `SessionStarted` only when the task is `InProgress` and `SessionJoinInfo::from_task` contains a parseable `session_id` that differs from `previous_session_id` when one was provided.
|
||||
Add a follow-up stream/helper such as `submit_run_followup(prompt, run_id, previous_session_id, ai_client, timeout)`. It should call `AIClient::submit_run_followup(run_id, RunFollowupRequest { message: prompt })` first, then call the polling helper. API failure before acceptance should yield an error without polling. Polling errors should surface through the same error path as initial spawn.
|
||||
For initial spawn, preserve the existing tolerance for a session link without a parsed session ID if any caller still needs that metadata. For follow-up readiness, require a parsed session ID because the hotswap API needs a `SessionId`.
|
||||
Terminal states before a fresh session is found should not leave the follow-up wait indefinitely. Failure-like states should emit the state change and then surface the task status message as an error; successful terminal completion without a new session should complete with a clear “no follow-up session became available” error.
|
||||
### Explicit ambient model startup kind
|
||||
Add a small enum such as `SessionStartupKind { InitialRun, Followup }` and change `Status::WaitingForSession` to carry `{ progress, kind }`. Existing accessors like `agent_progress()` and `is_waiting_for_session()` should remain behavior-preserving.
|
||||
Add fields to `AmbientAgentViewModel` for follow-up bookkeeping: the active execution `SessionId`, the last ended execution `SessionId` if available, and the currently submitted follow-up prompt. The prompt field is for PR 3’s optimistic rendering; PR 2 should store it but not insert a visible follow-up query block.
|
||||
Update initial spawn to set `WaitingForSession { kind: InitialRun }`. Update `AmbientAgentEvent::SessionStarted` handling to emit `SessionReady` for `InitialRun` and `FollowupSessionReady` for `Followup`, rather than relying on whether the current status happens to be `AgentRunning`.
|
||||
Add `AmbientAgentViewModel::submit_cloud_followup(prompt, ctx)`. It should require `FeatureFlag::HandoffCloudCloud`, require an existing `task_id`/run ID, capture the previous active or ended session ID, set `WaitingForSession { kind: Followup }`, start the progress timer, store the pending prompt, emit a distinct follow-up dispatch event, and spawn the follow-up helper stream.
|
||||
On follow-up success, stop the timer, set `status` to `AgentRunning`, update the active execution session ID, clear the pending prompt, and emit `FollowupSessionReady { session_id }`. On failure, reuse the existing failure/auth/quota/capacity mapping logic as much as possible so follow-up setup errors render through the same state as initial setup errors.
|
||||
### Execution-ended bookkeeping without visible UI
|
||||
Extend the ambient session-ended path only enough for bookkeeping. `viewer::TerminalManager::ambient_session_ended` currently leaves the pane resumable and clears the active network in `app/src/terminal/shared_session/viewer/terminal_manager.rs (1490-1515)`. In PR 2 it can notify the ambient view model of the ended session ID behind `HandoffCloudCloud`, so the model records `last_ended_execution_session_id` and can reject duplicate readiness from that session.
|
||||
This notification should not call `TerminalView::on_session_share_ended`, should not insert a tombstone, should not set `SharedSessionStatus::FinishedViewer`, and should not cancel the local conversation. Those UI and lifecycle decisions remain PR 3 scope.
|
||||
### Event and view integration
|
||||
Add a new model event such as `FollowupDispatched` instead of reusing `DispatchedAgent`. `create_cloud_mode_view` only needs an exhaustive-match update for the new event because `FollowupSessionReady` is already wired to `attach_followup_session`.
|
||||
Update `TerminalView::handle_ambient_agent_event` to handle `FollowupDispatched` by notifying/re-rendering progress UI and marking the active ambient conversation as `ConversationStatus::InProgress` if one exists. It should not insert `CloudModeInitialUserQuery`, should not insert a second `AmbientAgentEntryBlock`, and should not auto-open new UI beyond the existing setup/progress rendering.
|
||||
The existing loading screen in `app/src/terminal/view/ambient_agent/view_impl.rs (529-571)` can continue to derive messages from `AgentProgress` for PR 2. If copy changes are desired for follow-ups, keep them minimal and keyed off `SessionStartupKind`, but deferring user-facing copy to PR 3 is acceptable.
|
||||
## Testing strategy
|
||||
Add stream-level tests in `app/src/ai/ambient_agents/spawn_tests.rs` covering follow-up submission and polling. The important cases are: the helper calls `submit_run_followup` before polling; it ignores the previous session ID returned by the server; it emits `SessionStarted` for a different new session ID; it propagates API errors before polling; it surfaces terminal failure before readiness.
|
||||
Preserve existing `spawn_task` tests so initial spawn behavior remains unchanged after the refactor.
|
||||
Add model-level tests if there is a lightweight existing harness for `AmbientAgentViewModel`; otherwise keep model changes small and validate via stream tests plus targeted compile checks. Model assertions should cover `submit_cloud_followup` preconditions, `WaitingForSession { kind: Followup }`, and `FollowupSessionReady` emission on a fresh session.
|
||||
Run targeted validation after implementation: `cargo nextest run -p warp ai::ambient_agents::spawn::tests server::server_api::ai::tests::build_run_followup_url_routes_to_run_followups server::server_api::ai::tests::serialize_run_followup_request` and `cargo check -p warp --features handoff_cloud_cloud`. If model or terminal-view tests are added, include their module filters. Do not use `cargo fmt --all` or file-specific `cargo fmt`; use the repo’s standard formatting command only when preparing a PR update.
|
||||
## Rollout and compatibility
|
||||
With `HandoffCloudCloud` off, no production UI should call the new follow-up method and existing initial Cloud Mode startup should behave as it does today.
|
||||
With the flag on, PR 2 only exposes an internal/model-level follow-up path. The absence of a visible entrypoint makes this safe to merge before product UX lands, while unit tests can still exercise the orchestration path.
|
||||
The runtime code may assume `CloudModeSetupV2` when `HandoffCloudCloud` is enabled because the Cargo feature dependency was added in PR 1.
|
||||
## Risks and mitigations
|
||||
The server may briefly return the ended execution’s session fields after accepting a follow-up. Mitigate by passing the previous session ID into the polling helper and requiring a different parsed session ID before emitting readiness.
|
||||
Reusing `DispatchedAgent` for follow-ups would insert initial-run UI artifacts again. Mitigate with a distinct follow-up event and explicit startup kind.
|
||||
Refactoring `spawn_task` could regress initial Cloud Mode startup. Mitigate by preserving the public stream contract and keeping existing spawn tests green.
|
||||
A follow-up may be accepted but fail before any session becomes joinable. Mitigate by reusing the existing failed/auth/quota/capacity UI states and leaving future UI free to retry from the tombstone in PR 3.
|
||||
Model bookkeeping could drift if session-ended notifications are missed. Mitigate by also falling back to the last active execution session ID when submitting a follow-up.
|
||||
## Parallelization
|
||||
This PR is small enough to implement sequentially, but two independent tracks could run in parallel if needed. One track can refactor and test `spawn.rs` follow-up polling with mocked `AIClient`; the other can wire `AmbientAgentViewModel` state/events and terminal-manager bookkeeping. They converge at `submit_cloud_followup` consuming the follow-up helper and emitting `FollowupSessionReady`.
|
||||
## Definition of done
|
||||
`spawn_task` still behaves the same for initial runs after extracting reusable polling.
|
||||
A follow-up helper submits a prompt, polls the stable run, ignores stale session IDs, and returns a fresh joinable session.
|
||||
`AmbientAgentViewModel::submit_cloud_followup` exists behind `HandoffCloudCloud` and drives `WaitingForSession { kind: Followup }` through success and error states.
|
||||
`FollowupSessionReady` is emitted for fresh sessions and continues to attach through the existing hotswap path.
|
||||
No tombstone Continue UI or terminal-input follow-up route is added in this PR.
|
||||
Targeted tests and `cargo check -p warp --features handoff_cloud_cloud` pass.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Cloud-to-cloud handoff resumable completed conversations
|
||||
## Summary
|
||||
Completed cloud agent conversations should be resumable in cloud mode when cloud-to-cloud handoff is available. Opening a completed cloud task should restore a Cloud Mode pane seeded with the prior conversation history, preserving the familiar completed transcript/tombstone experience while allowing the user to submit a follow-up prompt that starts a new cloud execution in the same conversation.
|
||||
## Problem
|
||||
Before cloud-to-cloud handoff, a cloud task with no active execution was permanently complete from the client’s perspective, so the transcript viewer and “Continue locally” affordance were sufficient. With multi-execution cloud runs, the same visual state can now be a pause between executions. Users should not hit a dead-end toast when the product offers a cloud Continue action.
|
||||
## Figma
|
||||
Figma: none provided. The current failure state is represented by the screenshot attached to the request.
|
||||
## Behavior
|
||||
1. When a user opens a cloud agent conversation that has no active execution and cloud-to-cloud continuation is available, Warp opens a Cloud Mode pane seeded with the completed conversation history instead of starting a new blank task.
|
||||
2. The restored Cloud Mode pane preserves the existing completed-conversation presentation:
|
||||
- The prior conversation output remains visible.
|
||||
- The completed task tombstone remains visible at the end of the transcript.
|
||||
- Existing metadata, artifacts, error status, runtime, credits, source, skill, and working-directory details remain available wherever they are already shown.
|
||||
3. For a resumable cloud task, the tombstone shows a primary `Continue` action for continuing in cloud mode.
|
||||
4. The existing `Continue locally` action remains available where it is supported today. Adding cloud continuation must not remove the local fork path.
|
||||
5. If the task cannot be continued in cloud mode, Warp does not show a cloud `Continue` action. Users should not be offered an action that can only produce a generic “couldn't continue” failure.
|
||||
6. Clicking cloud `Continue` does not immediately create a new execution. It prepares the same Cloud Mode pane for a follow-up prompt and focuses the existing terminal input.
|
||||
7. While the pane is waiting for a follow-up prompt, the user can type, edit, or abandon the prompt using the normal terminal input behavior.
|
||||
8. Submitting a non-empty follow-up prompt starts a new execution for the same cloud task/run, not a new user-visible conversation.
|
||||
9. Submitting an empty follow-up prompt does not start a new execution. The pane stays usable and focused so the user can type a real prompt or leave the view.
|
||||
10. After the follow-up prompt is submitted, the restored pane transitions into Cloud Mode setup/progress UI in the same pane.
|
||||
11. During follow-up setup, the user sees their submitted prompt represented optimistically so the pane does not appear to ignore the input while the new cloud execution is starting.
|
||||
12. When the new execution starts, Warp attaches to the new cloud session in the same pane. It does not open a separate tab, split, or replacement conversation unless the user separately chooses another navigation action.
|
||||
13. The same logical conversation/run identity is preserved across the original transcript and every follow-up execution.
|
||||
14. New output from the follow-up appears after the existing transcript content in chronological order.
|
||||
15. Returning to the conversation list, agent management view, details panel, or another navigation surface should focus the already-open pane for this task while it is open.
|
||||
16. If a follow-up execution finishes, the pane returns to a completed-between-executions state and can offer cloud `Continue` again when the task is still resumable.
|
||||
17. Repeated follow-ups are allowed. A second or later follow-up should behave the same as the first: prompt input, setup/progress, same-pane session attachment, and preserved conversation identity.
|
||||
18. A failed, blocked, cancelled, timed-out, unauthorized, or quota-limited follow-up attempt surfaces through the same user-facing Cloud Mode error/auth/capacity/credits states used by cloud task startup.
|
||||
19. If the follow-up request fails before the prompt is accepted, the user’s prompt is restored in the input so they can edit or retry.
|
||||
20. If the follow-up request is accepted but the new execution fails before a session is available, the pane shows the appropriate failure state and does not silently discard the completed conversation history.
|
||||
21. Closing the pane while a follow-up execution is starting stops only the local viewing/waiting experience. It must not imply that the remote cloud run is cancelled unless the user explicitly invokes a cancel action.
|
||||
22. Opening a completed local agent conversation still uses the existing local transcript behavior. Cloud continuation is only for cloud/ambient agent conversations that are resumable in cloud mode.
|
||||
23. Opening a non-owned or otherwise non-resumable cloud transcript still works as a read-only transcript. It must not incorrectly expose cloud continuation.
|
||||
24. If the cloud-to-cloud continuation feature is unavailable or disabled, completed cloud conversations continue to use the existing transcript/tombstone behavior.
|
||||
25. Existing transcript share/open behavior should continue to work while the view is idle between cloud executions.
|
||||
26. Keyboard focus should move to the prompt input after cloud `Continue` is clicked. Users should not need to click the input manually before typing the follow-up prompt.
|
||||
27. The UI should avoid stacking confusing duplicate tombstones for the same between-executions state. After a follow-up starts, the old tombstone should not remain as an actionable end marker above the active setup/progress state.
|
||||
28. The generic “Couldn't continue this cloud task.” toast is only acceptable for genuinely unexpected inconsistencies. It should not be part of the normal completed-cloud-task continuation flow.
|
||||
@@ -0,0 +1,91 @@
|
||||
# Cloud-to-cloud handoff resumable completed cloud panes
|
||||
## Context
|
||||
`PRODUCT.md` defines the target behavior: completed cloud conversations should restore into Cloud Mode panes seeded with prior conversation history, keep the completed transcript/tombstone presentation, and become resumable in cloud mode when cloud-to-cloud handoff is available.
|
||||
The follow-up execution path mostly exists. `ConversationEndedTombstoneView` creates a cloud `Continue` action when it has an ambient task id and `FeatureFlag::HandoffCloudCloud` is enabled in `app/src/terminal/view/shared_session/conversation_ended_tombstone_view.rs (202-235)`, then emits `ConversationEndedTombstoneEvent::ContinueInCloud` from `app/src/terminal/view/shared_session/conversation_ended_tombstone_view.rs (631-640)`. `TerminalView::start_cloud_followup_from_tombstone` sets `pending_cloud_followup_task_id` and focuses the existing input in `app/src/terminal/view/shared_session/view_impl.rs (810-842)`. `TerminalView::try_submit_pending_cloud_followup` routes the next prompt through `AmbientAgentViewModel::submit_cloud_followup` in `app/src/terminal/view.rs (19979-20075)`. The ambient model emits `FollowupSessionReady` after polling for a fresh session in `app/src/terminal/view/ambient_agent/model.rs (686-744)`, and the deferred Cloud Mode manager attaches it through `TerminalManager::attach_followup_session` in `app/src/terminal/view/ambient_agent/mod.rs (60-118)` and `app/src/terminal/shared_session/viewer/terminal_manager.rs (310-384)`.
|
||||
The follow-up submit trigger should be explicit Cloud Mode behavior, not a side effect of shared-session viewer submission. Fresh Cloud Mode prompts already bypass `Input::submit_ai_query` while the ambient model is in `Composing` and call `AmbientAgentViewModel::spawn_agent` directly. Follow-up prompts should follow the same pattern: before generic AI submission and before generic shared-session viewer permission checks, `Input` should detect a disconnected Cloud Mode follow-up composer and emit a dedicated Cloud follow-up event handled by `TerminalView::try_submit_pending_cloud_followup`. Both cases must use this same path: a pane whose initial cloud execution just ended, and a pane restored from ambient conversation history.
|
||||
Disconnected Cloud Mode panes should also stop representing themselves as shared-session viewers. `SharedSessionStatus::ViewPending`, `ActiveViewer`, and `FinishedViewer` should mean a live or ended collaborative viewer state with shared-session permission semantics. A fresh, restored, or between-executions Cloud Mode pane that has no attached session should instead use `SharedSessionStatus::NotShared` plus the existing Cloud Mode/ambient model state. `connect_session` and `attach_followup_session` should transition into `ViewPending` only when a real shared-session id is available.
|
||||
The broken path is pane construction. `TerminalView::new` creates `ambient_agent_view_model` only when `is_cloud_mode` is true in `app/src/terminal/view.rs (3003-3030)`. Historical transcript panes are currently created through `MockTerminalManager::create_model` in `app/src/pane_group/mod.rs (5675-5728)` and loaded through `PaneGroup::load_data_into_transcript_viewer` in `app/src/pane_group/mod.rs (3825-3928)`. Those panes can have `ConversationTranscriptViewerStatus::ViewingAmbientConversation(task_id)`, but that is a workaround for treating a generic transcript viewer like an ambient session. It still does not give the pane the ambient model or deferred viewer manager needed to submit and attach a cloud follow-up.
|
||||
`WorkspaceAction::OpenConversationTranscriptViewer` already carries `ambient_agent_task_id`, but the action handler discards it and calls `load_cloud_conversation_into_new_transcript_viewer(conversation_id, ctx)` in `app/src/workspace/view.rs (21677-21698)`. The loader then creates a generic transcript-loading tab in `app/src/workspace/view.rs (3998-4054)`. By the time cloud conversation data is loaded, `PaneGroup` can recover an ambient task id from metadata, but it still mutates a non-cloud transcript viewer rather than creating a resumable cloud viewer.
|
||||
The desired invariant is that viewing any ambient agent conversation, including a restored completed one, puts the user in a Cloud Mode pane. The fetched conversation history is seeded UI state for scrollback and completed-tombstone presentation, not the server-side agent context for the next follow-up request. Generic transcript-viewer state should remain only for non-ambient conversations and loading/fallback paths.
|
||||
## Proposed changes
|
||||
### Preserve ambient task identity through restore loading
|
||||
Change `Workspace::load_cloud_conversation_into_new_transcript_viewer` to accept `ambient_agent_task_id: Option<AmbientAgentTaskId>`, and pass the action’s task id through from `WorkspaceAction::OpenConversationTranscriptViewer`.
|
||||
When `load_conversation_from_server` returns `CloudConversationData`, resolve the effective task id as:
|
||||
1. the task id from the action, if present;
|
||||
2. the task id from cloud conversation metadata, if present;
|
||||
3. `None`.
|
||||
Use the effective task id to choose the pane construction path, seed the ambient model, register active ambient views, and decide whether the tombstone can offer cloud `Continue`.
|
||||
### Route ambient restores into Cloud Mode pane construction
|
||||
Add a PaneGroup helper that creates a restored ambient Cloud Mode pane when all of these are true:
|
||||
- `FeatureFlag::HandoffCloudCloud` is enabled;
|
||||
- an effective ambient task id exists;
|
||||
- the loaded conversation is a cloud/ambient conversation, not a purely local transcript.
|
||||
A concrete shape:
|
||||
- `PaneGroup::create_restored_ambient_cloud_mode_pane(conversation, task_id, resources, initial_size, ctx)`
|
||||
- `PaneGroup::replace_loading_pane_with_restored_ambient_cloud_mode_pane(loading_pane_id, cloud_conversation, task_id, ctx)`
|
||||
- or a branch in the cloud-conversation loading callback that replaces the loading pane with a Cloud Mode pane instead of calling the generic transcript restoration path.
|
||||
The helper should use `terminal::view::ambient_agent::create_cloud_mode_view` or the existing `PaneGroup::create_cloud_mode_terminal` wrapper, then restore the fetched conversation history into that view. This gives the view `ambient_agent_view_model`, the `FollowupSessionReady` subscription, and a deferred viewer manager that can later call `attach_followup_session`.
|
||||
Do not create a `MockTerminalManager` transcript viewer and then retrofit Cloud Mode behavior onto it. Also do not set `ConversationTranscriptViewerStatus::ViewingAmbientConversation(task_id)` for the new path. The pane should be ambient because it is a Cloud Mode pane, not because a transcript-viewer marker carries a task id.
|
||||
### Seed the Cloud Mode pane with historical UI state
|
||||
After creating the restored Cloud Mode pane:
|
||||
- restore the fetched conversation into the terminal view so prior blocks and rich content render as historical UI state;
|
||||
- insert the completed-conversation tombstone at the end of the restored history;
|
||||
- call `AmbientAgentViewModel::enter_viewing_existing_session(task_id, ctx)` so the model stores the stable task id and fetches run config metadata;
|
||||
- if the loaded conversation has a local `AIConversationId`, call `set_conversation_id(Some(id))`;
|
||||
- register `ActiveAgentViewsModel::register_ambient_session(terminal_view.id(), task_id, ctx)`;
|
||||
- enter the same agent-view/header/details state as a normal Cloud Mode pane for that task.
|
||||
This should mirror existing remote-child restoration in `app/src/pane_group/mod.rs (3161-3258)` and the existing ambient Cloud Mode construction path in `app/src/pane_group/mod.rs (3249-3276)`. The restored history is only the pane’s initial UI state. `submit_cloud_followup` should still send the follow-up prompt and task/session identity through the existing cloud-to-cloud follow-up API rather than replaying the transcript as prompt context.
|
||||
If the current `AmbientAgentViewModel::Status::AgentRunning` is too strong for a completed task with no live session, introduce or reuse a between-executions state that represents “viewing an existing ambient task with no active session.” The important behavior is that the pane looks like the completed Cloud Mode state produced after a fresh Cloud Mode execution ends and the VM/session is no longer active.
|
||||
When an ambient shared session ends, owner panes should transition back to this disconnected Cloud Mode follow-up composer state and use `SharedSessionStatus::NotShared`. Non-owner panes should remain read-only ended viewer surfaces and must not expose editable Cloud follow-up input.
|
||||
### Use one Cloud follow-up submission path
|
||||
Do not maintain separate follow-up submission paths based on whether the pane was previously attached to a shared session. The post-session-ended case and restored-from-history case should converge before submission:
|
||||
- the pane is in disconnected Cloud Mode state;
|
||||
- it has an ambient task id in `AmbientAgentViewModel`;
|
||||
- the next non-empty prompt is submitted through `TerminalView::try_submit_pending_cloud_followup`;
|
||||
- `try_submit_pending_cloud_followup` calls `AmbientAgentViewModel::submit_cloud_followup`;
|
||||
- the ambient model emits `FollowupDispatched` and later `FollowupSessionReady`;
|
||||
- the deferred Cloud Mode manager attaches the fresh shared session through `attach_followup_session`.
|
||||
`Input::submit_ai_query` should no longer be the mechanism that routes follow-up prompts through `submit_viewer_ai_query` and `InputEvent::SendAgentPrompt`. That path is still appropriate for a true live shared-session viewer sending a prompt to the sharer, but it is not the semantic model for starting a new cloud follow-up execution.
|
||||
### Stop using ambient transcript-viewer status as an ambient identity source
|
||||
Remove `ConversationTranscriptViewerStatus::ViewingAmbientConversation` from the new design. If the variant can be deleted cleanly, replace its call sites with Cloud Mode predicates:
|
||||
- `TerminalModel::ambient_agent_task_id` should derive ambient identity from `shared_session_source_type` and/or the `AmbientAgentViewModel`, not from transcript-viewer status.
|
||||
- `TerminalPane::snapshot` should snapshot restored ambient panes as `LeafContents::AmbientAgent`, because they are Cloud Mode panes.
|
||||
- tab and pane-header ambient indicators should use `is_shared_ambient_agent_session`, `TerminalView::ambient_agent_view_model`, or another explicit Cloud Mode ambient predicate instead of checking transcript-viewer status.
|
||||
- transcript share-link behavior should remain on true transcript viewers; restored ambient panes should use the Cloud Mode/shared-session share/open behavior available while idle between executions.
|
||||
It is acceptable to keep `ConversationTranscriptViewerStatus::Loading` and `ViewingLocalConversation` for generic local/non-ambient transcript viewers. The key invariant is that ambient restored conversations do not become read-only because they are marked as transcript viewers.
|
||||
### Keep generic transcript behavior as the fallback
|
||||
Continue using the current `MockTerminalManager` transcript path when:
|
||||
- `HandoffCloudCloud` is disabled;
|
||||
- no effective task id exists;
|
||||
- the conversation data is not cloud/ambient;
|
||||
- the pane is opened on a platform or flow that cannot continue cloud runs.
|
||||
This preserves `PRODUCT.md` invariants 22-24 and keeps the fix scoped to resumable cloud tasks.
|
||||
### Align tombstone capability with pane capability
|
||||
`ConversationEndedTombstoneView::new` currently infers cloud Continue eligibility from task id plus feature flag. Prefer adding a capability argument from `TerminalView::insert_conversation_ended_tombstone`, for example `CloudContinueCapability::Available(AmbientAgentTaskId)` vs `Unavailable`, so the button reflects whether the surrounding view can actually submit a cloud follow-up.
|
||||
If restored ambient panes are always Cloud Mode panes with an ambient model, the first implementation can keep the existing constructor logic and add a regression test. The capability argument is safer because it prevents a stale task id on a generic transcript viewer from showing a cloud `Continue` action that can only fall back to the generic toast.
|
||||
## Testing and validation
|
||||
Map tests to `PRODUCT.md` behavior invariants instead of duplicating product requirements:
|
||||
- Invariants 1-5 and 22-24: add PaneGroup/workspace tests that opening a completed cloud task with `HandoffCloudCloud` enabled creates a Cloud Mode pane with an `AmbientAgentViewModel` and no ambient transcript-viewer status, while disabled/no-task/local cases use the existing transcript path.
|
||||
- Invariants 6-12 and 26-28: add terminal/shared-session tests that tombstone Continue in a restored ambient Cloud Mode pane sets `pending_cloud_followup_task_id`, focuses input, submits through `AmbientAgentViewModel`, and does not hit the generic toast path.
|
||||
- Add an input/terminal test that a disconnected Cloud Mode follow-up prompt emits the dedicated Cloud follow-up event before generic shared-session viewer gating. Cover both restored panes and panes whose initial shared session just ended so they share the same submission path.
|
||||
- Add a state test that fresh/restored/between-executions Cloud Mode panes with no live session use `SharedSessionStatus::NotShared`, then transition to `ViewPending` only when `connect_session` or `attach_followup_session` starts joining a real session.
|
||||
- Invariants 13-17 and 25: add tests that the view remains registered for the same ambient task, repeated `FollowupSessionReady` attaches through the deferred manager, and follow-up completion returns the pane to a between-executions Cloud Mode tombstone state.
|
||||
- Invariants 18-21: extend existing follow-up error tests to cover transcript-originated follow-ups, including prompt restoration before request acceptance and Cloud Mode error state after accepted-but-failed startup.
|
||||
Targeted checks:
|
||||
- `cargo nextest run -p warp terminal::view::shared_session::view_impl_test terminal::view_test`
|
||||
- `cargo nextest run -p warp pane_group::mod_tests ai::agent_conversations_model_tests`
|
||||
- `cargo check -p warp --features handoff_cloud_cloud`
|
||||
Use the repo-standard formatting command when preparing the PR; do not run `cargo fmt --all` or file-specific `cargo fmt`.
|
||||
## Risks and mitigations
|
||||
### Duplicate transcript restoration
|
||||
Creating a Cloud Mode view and then loading historical blocks could duplicate content if a follow-up session replays prior blocks. Keep follow-up joins on `SharedSessionInitialLoadMode::AppendFollowupScrollback` and rely on the existing block-id dedupe in `TerminalModel::append_followup_shared_session_scrollback`.
|
||||
### Completed Cloud Mode state does not currently have a precise model status
|
||||
`AmbientAgentViewModel::enter_viewing_existing_session` currently sets `Status::AgentRunning`, which may not accurately describe a completed task with no live session. If this causes incorrect setup/progress/footer behavior, add a between-executions/viewing-existing status instead of falling back to transcript-viewer state.
|
||||
### Read-only transcript state blocks input
|
||||
`TerminalModel::is_read_only` returns true when `conversation_transcript_viewer_status` is set in `app/src/terminal/model/terminal_model.rs (1534-1554)`. Restored ambient panes should avoid setting transcript-viewer status entirely so cloud follow-up input is not blocked. Generic transcript viewers can keep the existing read-only behavior.
|
||||
### Manager/view mismatch
|
||||
Retrofitting a `MockTerminalManager` transcript viewer with cloud follow-up subscriptions would duplicate Cloud Mode setup logic. Replace the loading pane with a deferred shared-session viewer manager for restored ambient conversations instead.
|
||||
### Authorization and stale task state
|
||||
The client may think a task is resumable when the server rejects follow-up creation or returns stale no-execution data. Treat the follow-up API as authoritative and surface errors through existing Cloud Mode error/auth/capacity/credits states.
|
||||
## Parallelization
|
||||
This fix is small enough to implement sequentially because the key change is one coherent pane-construction path. If split, one agent can own workspace/pane-group creation and restore behavior while another owns tombstone/input/read-only tests. They converge at the restored ambient Cloud Mode pane helper and should avoid simultaneous edits to the same `PaneGroup` creation functions.
|
||||
Reference in New Issue
Block a user