Warp Cloud 到 Cloud 续接(Handoff):完成态 Cloud 对话在 Cloud Mode 中的可续接 Transcript 实现

原创2026-10-02 18:17:03632 阅读
文章标签:桌面应用开发者工具人工智能AI 应用AI Agent代码智能体

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 中可续接,而不是只能读或只能转本地。

这一目标与仓库中已存在的三条基础脉络直接相关:

目标行为: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:

  1. action 携带的任务 id(若存在);
  2. Cloud 对话元数据中的任务 id(若存在);
  3. 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 历史恢复",都要在提交前收敛到同一条链路:

  1. pane 处于断开的 Cloud Mode 状态;
  2. AmbientAgentViewModel 持有 ambient 任务 id;
  3. 下一条非空提示词通过 TerminalView::try_submit_pending_cloud_followup 提交;
  4. 它调用 AmbientAgentViewModel::submit_cloud_followup;
  5. ambient 模型先后发出 FollowupDispatched 与 FollowupSessionReady;
  6. 延迟的 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 真实能力对齐。

登录后查看全文
warp