Warp Cloud 到 Cloud 续接(Handoff):完成态 Cloud 对话在 Cloud Mode 中的可续接 Transcript 实现
Warp Cloud 到 Cloud 续接(Handoff):完成态 Cloud 对话在 Cloud Mode 中的可续接 Transcript 实现
导读
本文围绕 Warp 开源仓库中 specs/APP-4319/handoff-cloud-cloud-resumable-transcripts/PRODUCT.md 定义的核心需求展开:当 Cloud-to-Cloud Handoff(HandoffCloudCloud)功能可用时,一条"已完成"的 Cloud Agent 对话不应再是死胡同,而应能在 Cloud Mode 中恢复为带历史 transcript 的会话、保留墓碑(tombstone)展示,并允许用户在同一个对话身份下提交 follow-up 提示词启动新的 Cloud 执行。读完本文,你将完整掌握这一交互的全部 28 条行为规范、底层技术实现路径(pane 构建、任务身份贯穿、follow-up 提交链路、能力对齐与测试策略),以及实现过程中需要规避的关键风险。
背景:为什么"已完成"的 Cloud 对话需要变得可续接
在 Cloud-to-Cloud Handoff 出现之前,一个没有活跃执行的 Cloud 任务在客户端视角下是"永久完成"的:用户能做的只有查看 transcript(转录记录),或通过 "Continue locally" 把对话 fork 到本地继续。此时 transcript 查看器加本地续接入口已经够用。
但随着多执行(multi-execution)Cloud 运行的出现,同一个视觉状态——"没有活跃执行"——现在可能只是两次执行之间的暂停间隙(between-executions)。如果产品已经提供 Cloud 侧的 Continue 能力,用户就不应该再撞上死胡同提示(dead-end toast)。这正是本规格要解决的问题:让完成态的 Cloud 对话在 Cloud Mode 中可续接,而不是只能读或只能转本地。
这一目标与仓库中已存在的三条基础脉络直接相关:
- 位于 crates/warp_features/src/lib.rs 的
FeatureFlag::HandoffCloudCloud特性开关(默认禁用,属于 Cloud Mode Setup V2 之后的滚动边界); - app/src/terminal/view/shared_session/conversation_ended_tombstone_view.rs 中的结束对话墓碑视图(
ConversationEndedTombstoneView),它是 Cloud Continue 入口的宿主; - app/src/terminal/view/ambient_agent/model.rs 中已存在的
submit_cloud_followup提交链路。
目标行为:28 条产品规范全景
关联文档用 28 条行为规范(Behavior invariants)精确刻画了期望体验,按其内在逻辑可归纳为六大主题。下文逐条继承并标注关键实现落点。
1. 恢复打开:从 transcript 到 Cloud Mode pane(规范 1、2)
规范 1:当用户打开一条没有活跃执行、且 Cloud-to-Cloud 续接可用的 Cloud Agent 对话时,Warp 应打开一个已用完成态对话历史做种子(seeded)的 Cloud Mode pane,而不是新建一个空白任务。
规范 2:恢复出来的 Cloud Mode pane 必须保留既有"已完成对话"的呈现方式:
- 先前的对话输出仍然可见;
- 已完成任务的墓碑仍显示在 transcript 末尾;
- 已有的元数据、artifacts(产物)、错误状态、运行时(runtime)、credits(额度)、来源(source)、技能(skill)与工作目录(working-directory)细节,凡此前展示过的地方都继续可用。
关键落点:技术文档明确了一个不变式——"查看任何 ambient agent 对话(包括恢复的已完成对话)都应让用户处于 Cloud Mode pane 中"。获取到的对话历史只是 pane 的种子 UI 状态(scrollback 与墓碑呈现),并不是下一次 follow-up 请求所需的服务器端 agent 上下文。这也解释了为什么不能用通用 transcript 查看器来打补丁:通用 transcript 状态只应保留给非 ambient 对话以及加载/回退路径。
2. 续接入口:墓碑上的双按钮与能力对齐(规范 3、4、5、28)
- 规范 3:对于可续接的 Cloud 任务,墓碑显示一个主按钮
Continue,用于在 Cloud Mode 中继续。 - 规范 4:现有
Continue locally入口在受支持的地方继续保留——云续接不得移除本地 fork 路径。 - 规范 5:如果任务不能在 Cloud Mode 中续接,Warp 不显示 Cloud
Continue按钮——不能给用户一个只能产出 "couldn't continue" 通用失败的入口。 - 规范 28:通用的 "Couldn't continue this cloud task." toast 只允许用于真正意外的状态不一致,不得成为完成态 Cloud 任务续接流程的常态组成部分。
源码印证:conversation_ended_tombstone_view.rs 中,当 tombstone_cta 匹配 Some(TombstoneCta::ContinueInCloud { task_id }) 时渲染一个标题为 "Continue"、tooltip 为 "Continue this cloud conversation" 的主按钮,点击后派发 ConversationEndedTombstoneAction::ContinueInCloud { task_id };同一文件 L215-L228 在非 wasm 平台渲染 "Continue locally"("Fork this conversation locally")。两个按钮互斥渲染,共同满足规范 3、4。点击后的动作处理位于 L633-L643:发送 TombstoneContinueInCloud 遥测事件,然后发出 ConversationEndedTombstoneEvent::ContinueInCloud 事件,交由上层 TerminalView 处理。
技术文档进一步建议:ConversationEndedTombstoneView::new 目前仅凭"任务 id + 特性开关"推断 Cloud Continue 资格;更稳妥的做法是让 TerminalView::insert_conversation_ended_tombstone 传入能力参数(如 CloudContinueCapability::Available(AmbientAgentTaskId) vs Unavailable),使按钮真实反映所在视图能否实际提交 cloud follow-up,从而从源头规避"通用 transcript 查看器上带着过期任务 id 却只能弹出通用 toast"的坏路径(对应规范 5、28)。
3. 点击 Continue 之后:等待提示词,而非立即启动执行(规范 6、7、26、27)
- 规范 6:点击 Cloud
Continue不会立即创建新执行。它只是把同一个 Cloud Mode pane 准备成"等待 follow-up 提示词"状态,并把焦点放到既有终端输入框上。 - 规范 7:等待期间,用户可以用常规终端输入行为正常打字、编辑或放弃该提示词。
- 规范 26:点击 Cloud
Continue后键盘焦点应自动移到提示词输入框,用户无需手动点击输入框。 - 规范 27:UI 应避免为同一个 between-executions 状态堆叠出令人困惑的重复墓碑。follow-up 启动后,旧墓碑不应继续作为可操作的终点标记悬挂在活跃的 setup/progress 状态之上。
源码印证:TerminalView::start_cloud_followup_from_tombstone 在 app/src/terminal/view/shared_session/view_impl.rs 附近,其核心动作是把 pending_cloud_followup_task_id 设为该任务 id(L207)并聚焦既有输入框,与规范 6、26 完全对应。
4. 提交 follow-up 提示词:同对话新执行(规范 8、9、10、11、12、13、14)
- 规范 8:提交非空 follow-up 提示词,将为同一个 Cloud 任务/run 启动新执行,而不是产生一个新的用户可见对话。
- 规范 9:提交空提示词不启动新执行,pane 保持可用与聚焦,用户可以输入真实提示词或离开视图。
- 规范 10:follow-up 提交后,恢复出来的 pane 在同一 pane 内转入 Cloud Mode 的 setup/progress UI。
- 规范 11:setup 期间用户能看到自己提交的提示词被乐观呈现(optimistically),避免新执行启动过程中 pane 看起来像"忽略"了输入。
- 规范 12:新执行启动后,Warp 在同一 pane 内挂接新的 Cloud 会话;除非用户另行导航,否则不会打开独立 tab、分屏或替换对话。
- 规范 13:原始 transcript 与每一次 follow-up 执行之间保持同一条逻辑对话/run 身份。
- 规范 14:follow-up 的新输出按时间顺序出现在既有 transcript 内容之后。
源码印证:TerminalView::try_submit_pending_cloud_followup(位于 app/src/terminal/view.rs 约 L19979 之后区域,完整实现参见技术文档给出的 app/src/terminal/view.rs#L19979-L20075 区间)负责把下一条提示词路由到 AmbientAgentViewModel::submit_cloud_followup。后者在 app/src/terminal/view/ambient_agent/model.rs#L938-L944 中先检查 FeatureFlag::HandoffCloudCloud,未启用则直接告警返回,启用则调用 submit_run_followup_unchecked 走认证的 follow-up 服务调用(构造 RunFollowupRequest { message })。ambient 模型轮询到新会话后发出 FollowupSessionReady 事件(对应规范 12 的挂接信号),再由延迟初始化的 Cloud Mode 管理器通过 attach_followup_session 把新共享会话挂到同一 pane。
5. 生命周期延续:可重复 follow-up 与错误面(规范 15、16、17、18、19、20、21、25)
- 规范 15:返回对话列表、agent 管理视图、详情面板或其他导航面时,应聚焦该任务已打开的 pane(只要它还开着)。
- 规范 16:若某次 follow-up 执行结束,pane 回到"执行间完成态"(completed-between-executions),只要任务仍可续接,就再次提供 Cloud
Continue。 - 规范 17:允许重复 follow-up。第二次及以后的 follow-up 与第一次行为一致:提示词输入 → setup/progress → 同 pane 挂接 → 对话身份保持。
- 规范 18:失败、被阻塞、取消、超时、未授权或额度受限的 follow-up 尝试,统一通过 Cloud Mode 既有的 error/auth/capacity/credits 状态呈现(与 Cloud 任务启动一致)。
- 规范 19:若 follow-up 请求在提示词被接受之前失败,用户提示词要恢复到输入框中,可编辑可重试。
- 规范 20:若请求被接受但新执行在会话可用之前失败,pane 显示相应失败状态,不得静默丢弃已完成对话历史。
- 规范 21:follow-up 启动期间关闭 pane 只停止本地查看/等待体验,绝不暗示远端 Cloud 运行被取消——除非用户显式调用取消动作。
- 规范 25:视图在两次 Cloud 执行之间空闲时,既有 transcript 的分享/打开行为应继续正常工作。
实现呼应:record_ambient_execution_ended(app/src/terminal/view/ambient_agent/model.rs#L909-L919)在会话结束时清空 active_execution_session_id 并记录 last_ended_execution_session_id,为规范 16 的"回到 between-executions 状态"提供状态基础;pending_followup_prompt 字段在 attach_execution_session 中被清空(L933),同时承担规范 11 的乐观呈现与规范 19 的失败恢复载体。
6. 边界与降级:本地对话、非属主 transcript、功能关闭(规范 22、23、24)
- 规范 22:打开已完成的本地 Agent 对话仍走既有本地 transcript 行为。Cloud 续接只针对可在 Cloud Mode 中续接的 cloud/ambient agent 对话。
- 规范 23:打开非属主或不可续接的 Cloud transcript 仍按只读 transcript 工作,不得错误暴露 Cloud 续接入口。
- 规范 24:若 Cloud-to-Cloud 续接功能不可用或被禁用,已完成 Cloud 对话继续使用既有 transcript/墓碑行为。
实现呼应:技术文档明确,当 HandoffCloudCloud 禁用、无有效任务 id、对话数据非 cloud/ambient、或所在平台/流程无法续接 Cloud 运行这四种情况,继续走现有 MockTerminalManager transcript 路径——这就是规范 22-24 的落地方式,也是把修复范围严格限定在"可续接 Cloud 任务"上的关键。
技术实现路径:从源码结构看如何落地
关联目录下的 TECH.md 给出了与 28 条规范一一映射的实现方案,核心是五条改动主线。
主线一:在恢复加载中贯穿 ambient 任务身份
当前 WorkspaceAction::OpenConversationTranscriptViewer 已携带 ambient_agent_task_id,但 action handler 丢弃了它,转而调用 load_cloud_conversation_into_new_transcript_viewer(conversation_id, ctx)(app/src/workspace/view.rs#L21677-L21698),最终建出的是通用 transcript 加载 tab(app/src/workspace/view.rs#L3998-L4054)。
改动要点:让该加载函数接受 ambient_agent_task_id: Option<AmbientAgentTaskId>,当 load_conversation_from_server 返回 CloudConversationData 时按以下优先级解析有效任务 id:
- action 携带的任务 id(若存在);
- Cloud 对话元数据中的任务 id(若存在);
None。
随后用这个有效任务 id 决定 pane 构建路径、给 ambient model 做种子、注册活跃 ambient 视图,以及判定墓碑能否提供 Cloud Continue。
主线二:把 ambient 恢复路由进 Cloud Mode pane 构建
新增一个 PaneGroup 辅助函数,在同时满足以下条件时创建恢复态 ambient Cloud Mode pane:
FeatureFlag::HandoffCloudCloud已启用;- 存在有效 ambient 任务 id;
- 加载的对话是 cloud/ambient 对话(而非纯本地 transcript)。
技术文档给出的具体形态示例(实现时以仓库实际代码为准):
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)- 或在 cloud 对话加载回调中直接分支:用 Cloud Mode pane 替换 loading pane,而不是走通用 transcript 恢复路径。
该辅助函数应通过 terminal::view::ambient_agent::create_cloud_mode_view 或既有 PaneGroup::create_cloud_mode_terminal 包装来建 pane,再把拉取到的对话历史恢复进该视图。这样视图自然获得 ambient_agent_view_model、FollowupSessionReady 订阅,以及日后能调用 attach_followup_session 的延迟共享会话管理器。
两个明确禁忌:不要先建 MockTerminalManager transcript 查看器再回头给它"套"上 Cloud Mode 行为;也不要在新路径上设置 ConversationTranscriptViewerStatus::ViewingAmbientConversation(task_id)。pane 应当是 ambient 的,因为它是 Cloud Mode pane,而不是因为某个 transcript 查看器标记携带了任务 id。
主线三:用历史 UI 状态给 Cloud Mode pane 做种子
创建完恢复态 Cloud Mode pane 后,需要按顺序完成:
- 把拉取到的对话恢复进终端视图,让先前的 block 与富内容以历史 UI 状态渲染;
- 在恢复历史末尾插入完成态对话墓碑;
- 调用
AmbientAgentViewModel::enter_viewing_existing_session(task_id, ctx),让模型存储稳定任务 id 并拉取 run 配置元数据; - 若加载的对话有本地
AIConversationId,调用set_conversation_id(Some(id)); - 通过
ActiveAgentViewsModel::register_ambient_session(terminal_view.id(), task_id, ctx)注册活跃 ambient 视图; - 进入与同任务普通 Cloud Mode pane 一致的 agent 视图/头部/详情状态。
技术文档提醒:恢复的历史只是 pane 的初始 UI 状态,submit_cloud_followup 仍应通过既有 Cloud-to-Cloud follow-up API 发送提示词与任务/会话身份,而不是把 transcript 重放为提示词上下文。此外,若当前 AmbientAgentViewModel::Status::AgentRunning 对"已完成且无活跃会话"的任务语义过强,应引入或复用 between-executions 状态,其关键是让 pane 看起来像"一次新鲜 Cloud Mode 执行结束后的完成态"。
当 ambient 共享会话结束时:属主 pane 应退回这种"断开的 Cloud Mode follow-up composer"状态并使用 SharedSessionStatus::NotShared;非属主 pane 保持只读 ended viewer 表面,绝不能暴露可编辑的 Cloud follow-up 输入(呼应规范 23)。
主线四:统一唯一的 Cloud follow-up 提交路径
不要根据"pane 之前是否挂过共享会话"维护两条 follow-up 提交路径。无论是"会话刚结束"还是"从 ambient 历史恢复",都要在提交前收敛到同一条链路:
- pane 处于断开的 Cloud Mode 状态;
AmbientAgentViewModel持有 ambient 任务 id;- 下一条非空提示词通过
TerminalView::try_submit_pending_cloud_followup提交; - 它调用
AmbientAgentViewModel::submit_cloud_followup; - ambient 模型先后发出
FollowupDispatched与FollowupSessionReady; - 延迟的 Cloud Mode 管理器通过
attach_followup_session挂接新共享会话。
同时,Input::submit_ai_query 不再是 follow-up 提示词的路由机制——那条路径(经 submit_viewer_ai_query、InputEvent::SendAgentPrompt)仍适用于真实活跃共享会话查看器向分享者发提示词,但语义上不适合"启动新的 Cloud follow-up 执行"。技术文档进一步要求在通用 AI 提交与通用共享会话查看器权限检查之前,Input 要能识别"断开的 Cloud Mode follow-up composer"并发出专用 Cloud follow-up 事件,由 TerminalView::try_submit_pending_cloud_followup 处理。
主线五:让墓碑能力与 pane 能力对齐
ConversationEndedTombstoneView::new 目前从"任务 id + 特性开关"推断 Cloud Continue 资格。更稳健的做法是:由 TerminalView::insert_conversation_ended_tombstone 传入能力参数(CloudContinueCapability::Available(AmbientAgentTaskId) 或 Unavailable),让按钮是否出现由"周边视图能否真的提交 cloud follow-up"决定。由于恢复态 ambient pane 必然是带 ambient model 的 Cloud Mode pane,第一版实现可以保留既有构造逻辑并补回归测试,但能力参数能防止"通用 transcript 查看器上的过期任务 id 渲染出只能退化成通用 toast 的 Cloud Continue"。
技术文档还建议删除 ConversationTranscriptViewerStatus::ViewingAmbientConversation 变体,用明确的 Cloud Mode 谓词取代其调用点:TerminalModel::ambient_agent_task_id 从 shared_session_source_type 和/或 AmbientAgentViewModel 推导 ambient 身份;TerminalPane::snapshot 把恢复态 ambient pane 快照为 LeafContents::AmbientAgent;tab 与 pane 头部的 ambient 指示器改用 is_shared_ambient_agent_session / TerminalView::ambient_agent_view_model 等显式谓词。Loading 与 ViewingLocalConversation 变体可保留给通用本地/非 ambient transcript 查看器,关键不变式是:ambient 恢复对话不能因为被标记为 transcript 查看器而变成只读(见风险三)。
测试与验证策略
技术文档要求测试直接映射 PRODUCT.md 的行为规范,而不是重复产品需求文本:
- 规范 1-5、22-24:PaneGroup/workspace 测试——
HandoffCloudCloud启用时打开完成态 Cloud 任务创建的是带AmbientAgentViewModel的 Cloud Mode pane 且无 ambient transcript-viewer 状态;禁用/无任务/本地场景走既有 transcript 路径。 - 规范 6-12、26-28:terminal/shared-session 测试——恢复态 ambient Cloud Mode pane 中墓碑 Continue 会设置
pending_cloud_followup_task_id、聚焦输入、经AmbientAgentViewModel提交,且不落入通用 toast 路径。 - input/terminal 测试:断开的 Cloud Mode follow-up 提示词在通用共享会话查看器门控之前发出专用 Cloud follow-up 事件;同时覆盖恢复态 pane 与"初始共享会话刚结束"的 pane,证明它们共用同一提交路径。
- 状态测试:新建/恢复/执行间隙的 Cloud Mode pane(无活跃会话)使用
SharedSessionStatus::NotShared,仅在connect_session或attach_followup_session真正开始加入真实会话时转入ViewPending。 - 规范 13-17、25:同一 ambient 任务下视图保持注册、重复
FollowupSessionReady经延迟管理器挂接、follow-up 完成后回到 between-executions 墓碑状态。 - 规范 18-21:扩展既有 follow-up 错误测试,覆盖 transcript 起源的 follow-up——包括"请求接受前失败要恢复提示词"与"接受后启动失败要进入 Cloud Mode 错误状态"。
技术文档给出的针对性检查命令(以仓库实际包名与特性为准执行):
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
提交前使用仓库标准格式化命令(script/format),不要执行 cargo fmt --all 或针对单文件的 cargo fmt。
关键风险与缓解
重复 transcript 恢复
先建 Cloud Mode 视图再加载历史 block,可能在 follow-up 会话重放先前 block 时造成内容重复。缓解:follow-up 加入保持 SharedSessionInitialLoadMode::AppendFollowupScrollback,并依赖 TerminalModel::append_followup_shared_session_scrollback 中既有的 block-id 去重。
完成态 Cloud Mode 缺少精确的模型状态
AmbientAgentViewModel::enter_viewing_existing_session 当前设置 Status::AgentRunning,对"已完成且无活跃会话"的任务可能语义失准,进而导致 setup/progress/footer 行为异常。若发生,应新增 between-executions / viewing-existing 状态,而不是退回 transcript-viewer 状态。
只读 transcript 状态挡住输入
TerminalModel::is_read_only 在设置了 conversation_transcript_viewer_status 时返回 true(app/src/terminal/model/terminal_model.rs#L1534-L1554)。恢复态 ambient pane 完全不要设置 transcript-viewer 状态,否则 Cloud follow-up 输入会被只读性阻断;通用 transcript 查看器则保留既有只读行为。
管理器/视图错配
给 MockTerminalManager transcript 查看器"后补" Cloud follow-up 订阅会复制 Cloud Mode setup 逻辑。正确做法是用延迟共享会话查看器管理器替换 loading pane。
授权与过期任务状态
客户端可能以为任务可续接,但服务器拒绝创建 follow-up 或返回过期无执行数据。缓解:以 follow-up API 为权威,错误统一走既有 Cloud Mode 的 error/auth/capacity/credits 状态(呼应规范 18)。
并行化建议
该修复体量较小,核心是一条连贯的 pane 构建路径,适合顺序实施。若需拆分:一个 agent 负责 workspace/pane-group 的创建与恢复行为,另一个负责墓碑/输入/只读测试,二者在"恢复态 ambient Cloud Mode pane 辅助函数"处汇合,应避免同时改动同一个 PaneGroup 创建函数。
结语
Cloud-to-Cloud 续接把"已完成"的 Cloud 对话从只读死胡同升级为可继续推进的工作台:恢复打开时保持熟悉的 transcript + 墓碑体验,点击 Continue 后在同一 pane 内等待并提交 follow-up 提示词,以同一对话/run 身份挂接新执行,并支持重复续接。围绕 HandoffCloudCloud 特性开关,仓库中 conversation_ended_tombstone_view.rs、view_impl.rs、ambient_agent/model.rs 与 ambient_agent/mod.rs 共同构成从墓碑点击到同 pane 会话挂接的完整链路。实现时只需牢记三个不变式:ambient 恢复对话必须是 Cloud Mode pane、follow-up 只走唯一提交路径、墓碑能力必须与 pane 真实能力对齐。