first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
@@ -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.