- 桌面应用
- 开发者工具
- 人工智能
- AI 应用
- AI Agent
- 代码智能体
【免费下载链接】warp
Warp is an agentic development environment, born out of the terminal.
导读
本文以 Warp 开源仓库中的技术规格 specs/APP-4364/TECH.md 为主体,系统讲解 PR 3 如何把「云 Agent 会话结束后在同一终端窗格内继续追问」这一能力接入终端 UI:从功能开关HandoffCloudCloud、会话结束墓碑(tombstone)卡片上的Continue按钮,到AmbientAgentViewModel::submit_cloud_followup的提交链路、乐观渲染与共享会话热切换(hotswap)的完整闭环。读完本文,你将理解这套「云到云 handoff」在 Warp 桌面端 TUI 中的端到端实现原理,以及围绕它的测试、风险与验收标准。
背景:为什么需要「云到云」续跑
Warp 的 Cloud Mode(云模式)中,Agent 的每次运行都运行在一台临时云 VM 上,并通过一个共享会话(shared session)将输出流式渲染到本地终端窗格。当一次执行结束、共享会话断开后,若用户想在同一个终端窗格里对同一任务继续追问,就面临一个关键问题:已结束的共享会话网络(Network)不再存在,普通的「viewer 提交提示词 → 转发给 sharer」路径已经走不通。
PR 1 引入了默认关闭的HandoffCloudCloud功能开关、类型化的 follow-up API 客户端以及带执行状态的任务访问器;PR 2 在模型层实现了 follow-up 编排(AmbientAgentViewModel::submit_cloud_followup提交提示词、等待新会话、发出FollowupSessionReady事件)。PR 3(即本文所依据的技术规格)在此基础上,把这条链路接入终端 UI、共享会话结束路径、任务活性模型、回放过滤与针对性测试,实现了第一个用户可见的云到云 follow-up 入口。
功能开关与前置条件
整个云到云续跑能力由编译特性与运行开关共同控制:
HandoffCloudCloud:核心开关。PR 3 不负责开启该开关的默认启用(rollout),相关测试通过FeatureFlag::HandoffCloudCloud.override_enabled(true/false)模拟开/关两种状态(见 app/src/terminal/view/shared_session/view_impl_tests.rs)。CloudModeSetupV2:Cloud Mode 的 setup-v2 UI(启动进度与错误展示)开关,follow-up 的加载 UI 复用该模式。相关特性定义见 app/src/features.rs。- AI 可用性(AI enabled)与桌面端编译(非 wasm):
Continue云按钮的渲染还要求 AI 功能启用且仅存在于桌面端。
验证命令(规格中指定的针对性验证):
cargo check -p warp --features handoff_cloud_cloud注意仓库规则要求:提交前按仓库规范运行格式化与 clippy,但不要使用cargo fmt --all或针对单个文件的cargo fmt。
会话结束路径:按所有权分流
PR 3 的关键设计之一是「按会话所有权」拆分结束处理逻辑,避免用通用清理逻辑破坏可恢复的 ambient 执行:
- 通用路径不变:
TerminalView::on_session_share_ended仍负责普通共享会话的宽泛清理(清空 presence manager、处理 sharer 的 inactivity 定时器等),仅当处于CloudModeSetupV2且是「非 owner 的 ambient viewer 会话」时,才会在结束 share 之前插入一个墓碑卡片(见 app/src/terminal/view/shared_session/view_impl.rs)。 - 专用路径:新增
on_ambient_agent_execution_ended与on_ambient_agent_session_ended,二者统一委托给handle_non_running_ambient_agent_task(见 app/src/terminal/view/shared_session/view_impl.rs)。该 helper 会:- 在
AgentConversationsModel中标记任务执行结束(mark_task_execution_ended); - 刷新详情面板;
- 以
HandoffCloudCloud、CloudModeSetupV2、尚无墓碑、无 pending follow-up 为门控条件更新 UI:- owner 的 ambient 窗格:不插墓碑;若没有存活的共享会话,调用
enable_cloud_followup_input_after_conversation_end(task_id, ctx)让用户直接在现有输入框继续打字; - 非 owner 的 viewer 窗格:插入单个被跟踪的墓碑卡片。
- owner 的 ambient 窗格:不插墓碑;若没有存活的共享会话,调用
- 在
在 viewer manager 侧,TerminalManager处理 ambientSessionEnded时同样区分 owner 与非 owner:owner 的会话状态被置为SharedSessionStatus::NotShared(保持可编辑),非 owner 保持FinishedViewer(只读 viewer),并且不将窗格标记为「已完成的 viewer」,随后调用terminal_view.on_ambient_agent_execution_ended(ctx)并记录record_ambient_execution_ended(session_id, ctx)(见 app/src/terminal/shared_session/viewer/terminal_manager.rs)。
单个被跟踪的墓碑
每个终端视图只维护一个墓碑卡片 ID(conversation_ended_tombstone_view_id):
insert_conversation_ended_tombstone_with_cta是幂等的——若已有墓碑则先移除再插入,插入位置放在 pending user query 之后(避免被钉在底部的 cloud-mode queued prompt 遮挡),见 app/src/terminal/view/shared_session/view_impl.rs;- 用户点击云续跑后
remove_conversation_ended_tombstone移除卡片; - 当任务状态在空闲时变化导致墓碑过期时,只跟踪一个 ID + 点击时校验 task_id 归属的策略保证了 UI 聚焦于当前可恢复的边界。
墓碑卡片上的Continue云按钮
ConversationEndedTombstoneView在桌面端会创建一个可选的Continue云按钮,条件为:墓碑携带 ambienttask_id且HandoffCloudCloud开启;渲染还要求 AI 可用且为桌面端编译(见 app/src/terminal/view/shared_session/conversation_ended_tombstone_view.rs)。
按钮动作定义为:
pub enum ConversationEndedTombstoneAction { ContinueInCloud { task_id: AmbientAgentTaskId }, // 桌面端:本地续跑 ContinueLocally(AIConversationId), // wasm 端:在 Warp 中打开 OpenInWarp(AIConversationId), }ContinueInCloud被点击后(见 conversation_ended_tombstone_view.rs):
- 记录遥测事件
AgentManagementTelemetryEvent::TombstoneContinueInCloud { task_id }(task_id 序列化为稳定字符串); - 发出
ConversationEndedTombstoneEvent::ContinueInCloud { task_id }; TerminalView::start_cloud_followup_from_tombstone(task_id, ctx)移除墓碑、聚焦现有输入框、设置pending_cloud_followup_task_id(见 view_impl.rs)。进入 compose 之前会校验当前 ambient view model 仍持有被点击的task_id,否则弹出错误 toast。
按钮排布规则:云Continue按钮渲染在第一位,后面才是Continue locally(Oz/普通会话保持「本地续跑」入口);对于非 Oz harness(如claude、gemini)的第三方 harness 运行,本地续跑被隐藏——因为这些运行无法 fork 成本地 Warp 会话。当HandoffCloudCloud关闭时,不创建云按钮,墓碑行为与旧版完全一致。
follow-up 输入模式与提交路由
输入态管理
TerminalView持有pending_cloud_followup_task_id: Option<AmbientAgentTaskId>。墓碑点击与 owner 执行结束路径都会调用enable_cloud_followup_input(task_id, ctx)(见 view_impl.rs),它:
- 设置 pending task_id;
input.reset_after_cloud_followup_submission重置输入、input.set_input_mode_agent(true, ctx)进入 agent 输入模式;- 编辑器设为可编辑(
InteractionState::Editable); - 更新窗格配置并 notify。
这样,墓碑只是「揭示/聚焦」入口,真正的编辑器仍然是终端既有输入框,不会在墓碑里内嵌第二个编辑器。
提交拦截:绕过已结束的网络
终端输入提交原本假定存在存活的共享会话网络:Input::submit_viewer_ai_query冻结输入、收集上下文附件并发出InputEvent::SendAgentPrompt(见 app/src/terminal/input.rs)。但在两次云执行之间故意没有当前网络,因此 PR 3 需要独立的 follow-up 提交路由。
TerminalView::try_submit_pending_cloud_followup(prompt, ctx)在InputEvent::SendAgentPrompt到达时、于常规TerminalViewEvent::SendAgentPrompt路径之前进行拦截(见 app/src/terminal/view.rs):
fn try_submit_pending_cloud_followup(&mut self, prompt: String, ctx) -> bool { if !FeatureFlag::HandoffCloudCloud.is_enabled() { return false; // 开关关闭:走旧路径 } // 阻止 follow-up 的会话/任务来源检查 let blocks_cloud_followups = ...; // blocks_cloud_followups_for_ambient_agent_session_from_model if blocks_cloud_followups { pending = None; return false; } let Some(task_id) = self.pending_cloud_followup_task_id .or_else(|| self.owned_ambient_agent_task_id(ctx)) else { return false; }; if prompt.trim().is_empty() { // 空提示:保持 compose 路由可用,恢复 agent 输入并聚焦 input.reset_after_cloud_followup_submission(ctx); input.set_input_mode_agent(true, ctx); update_pane_configuration(ctx); focus_input_box(ctx); ctx.notify(); return true; } let Some(ambient_agent_view_model) = self.ambient_agent_view_model.clone() else { self.restore_followup_prompt_after_failed_submission(&prompt, ctx); self.show_error_toast("Couldn't continue this cloud task.".to_string(), ctx); return true; }; if ambient_agent_view_model.as_ref(ctx).task_id() != Some(task_id) { self.restore_followup_prompt_after_failed_submission(&prompt, ctx); self.show_error_toast("Couldn't continue this cloud task.".to_string(), ctx); return true; } ambient_agent_view_model.update(ctx, |model, ctx| { model.submit_cloud_followup(prompt, ctx); // 路由到 follow-up API,而非 ended network }); input.reset_after_cloud_followup_submission(ctx); input.set_input_mode_agent(true, ctx); update_pane_configuration(ctx); ctx.notify(); true }要点:
- 校验链:功能开关 → 会话来源是否允许 follow-up → task_id(pending 或 owner 任务)→ 非空提示 → ambient model 的 task_id 是否与点击/持有的一致;
- 失败恢复:校验失败或 model 缺失时,
restore_followup_prompt_after_failed_submission会把提示词放回输入框、清空 pending 状态、聚焦输入并显示错误 toast; - slash 命令保持本地行为:
/fork、/fork-and-compact等仍走Input::submit_viewer_ai_query的本地执行路径(slash 命令在 viewer 端本机执行,只有/compact、/plan、/orchestrate等作为提示词转发),follow-up 路由只处理普通非空 agent 提示词。
加载 UI 与乐观渲染
复用 setup-v2 加载/错误 UI
PR 2 已在模型层把状态置为Status::WaitingForSession { kind: SessionStartupKind::Followup }并发出FollowupDispatched(见 app/src/terminal/view/ambient_agent/model.rs)。PR 3 复用该状态:follow-up 提交后、轮询新执行会话期间,终端渲染与初次 Cloud Mode 相同的 setup-v2 加载 UI。
submit_cloud_followup(prompt, ctx)先检查HandoffCloudCloud开关(关闭则仅告警并返回),随后调用submit_run_followup_unchecked:读取当前/最近结束的 execution session ID(active_execution_session_id.or(last_ended_execution_session_id))作为previous_session_id,调用submit_run_followup(prompt, task_id, previous_session_id, ai_client, None)发起流式请求,置pending_followup_prompt、切换状态到WaitingForSession { kind: Followup }并发出FollowupDispatched(见 model.rs)。执行结束信息由record_ambient_execution_ended(session_id, ctx)记录在last_ended_execution_session_id中(见 model.rs),使轮询可以等待一个「不同于上一次」的新会话。
乐观渲染与去重
CloudModeFollowupUserQuery与CloudModeInitialUserQuery共享同一套render_user_query样式。关键区别是插入时机:
- 初始运行:
DispatchedAgent时插入初始用户查询; - follow-up:
FollowupDispatched时才插入CloudModeFollowupUserQuery,初始运行行为完全不变。
AmbientAgentViewModel通过三个新状态支撑乐观渲染与去重:
pending_followup_prompt: Option<String>:已提交但尚未挂到新会话的提示词;should_show_followup_progress():pending 提示词存在且状态处于WaitingForSession | Failed | NeedsGithubAuth | Cancelled时返回 true(见 model.rs);optimistically_rendered_user_queries:记录已渲染的提示词,共享会话回放时同一提示词不再渲染第二次,避免重复。
view 侧在收到FollowupDispatched后(见 app/src/terminal/view/ambient_agent/view_impl.rs):在CloudModeSetupV2下开启新 setup 命令组、把会话状态更新为ConversationStatus::InProgress,并把 pending 提示词渲染为 queued user query block(QueuedPromptsV2开启时走enqueue_initial_cloud_mode_prompt,否则直接insert_cloud_mode_queued_user_query_block)。结束 setup 的终端状态会清空 pending/progress 状态,确保被拒绝的提交不会永久追加乐观 UI。
错误、重试、回放与状态清理
错误与重试
- API/轮询错误复用既有的
AmbientAgentViewModelEvent::Failed、auth、quota、capacity 事件,setup 屏与初次 Cloud Mode 显示相同的错误 UI; - 若提交在模型接受之前失败,
restore_followup_prompt_after_failed_submission将提示词放回输入框、重新进入 agent 输入模式并聚焦输入以便重试; - 若 follow-up 被接受但在会话就绪前失败,本地会话保持相同 task/run ID,ambient 状态进入既有的错误/auth/cancelled UI,重试仍走同一条 pending follow-up/输入路径,不会分配新的本地会话。
回放去重
新执行会话 attach 后,共享会话回放控制器新增should_skip_current_replayed_response与should_skip_replayed_response_for_existing_conversation状态:当某条响应已在本地会话中呈现时,回放不再重复追加。viewer 事件循环侧还有对应的「抑制既有 agent 会话回放」机制(should_suppress_existing_agent_conversation_replay,见 app/src/terminal/shared_session/viewer/event_loop.rs),其测试用例见 event_loop_tests.rs。
成功热切换
当FollowupSessionReady触发时:清除 follow-up compose/输入状态与 pending 乐观状态,走既有FollowupSessionReady -> attach_followup_session热切换路径。viewer manager 侧attach_execution_session(session_id, ctx)会先关闭旧网络(若存在),再以追加模式(append mode)连接新共享会话,新输出流式进入同一个终端窗格(见 app/src/terminal/shared_session/viewer/terminal_manager.rs)。
create_cloud_mode_view(app/src/terminal/view/ambient_agent/mod.rs)创建的云模式窗格使用「deferred」viewer manager:ambient 提示词在一个未初始化的会话共享 viewer 窗格中编辑,会话就绪后再 join;wire_ambient_agent_session_events订阅模型事件——SessionReady对应初次运行连接,ExecutionSessionReady对应 follow-up 新 VM 会话的重新 attach(见 mod.rs)。
端到端流程
规格给出的完整闭环如下:
- 一次 Cloud Mode 执行结束,viewer manager 收到
SessionEnded; ambient_session_ended记录已结束的会话 ID,并调用 ambient 执行结束的 terminal-view 方法;- 终端视图标记任务执行结束、刷新任务/详情状态,owner 直接启用 follow-up 输入,非 owner 插入单个被跟踪的墓碑,同时保持窗格/输入可恢复;
- 非 owner 的 viewer 点击墓碑上的
Continue; - 终端移除墓碑、聚焦既有输入框,并把下一个普通 agent 提示词标记为云 follow-up;
- 用户提交提示词;
- 输入/视图把提示词路由到
AmbientAgentViewModel::submit_cloud_followup; - 视图插入一条乐观 follow-up 用户查询、记录为已渲染,并展示 setup-v2 加载 UI;
- 模型提交 follow-up API 请求并轮询新会话;
FollowupSessionReady到达create_cloud_mode_view,调用attach_followup_session;- viewer manager 以追加模式 join 新共享会话,新输出流式进入同一终端窗格。
整个过程中,终端窗格、本地会话、稳定的 run/task ID 与 hotswap attach 路径均保持不变。
测试与验证
PR 3 的单元测试覆盖了所有已交付的接缝(seams):
- 任务 active/joinable 辅助函数与会话展示状态:
app/src/ai/agent_conversations_model_tests.rs; - 云 follow-up compose 恢复:
app/src/terminal/view_test.rs; - 墓碑插入/移除、owner follow-up 输入、过期任务拒绝、任务结束处理:
app/src/terminal/view/shared_session/view_impl_tests.rs(例如test_on_ambient_agent_execution_ended_enables_followup_input_without_tombstone_for_owned_task、test_try_submit_pending_cloud_followup_rejects_task_source_that_blocks_followups); - ambient 会话结束/网络行为与回放处理:
app/src/terminal/shared_session/viewer/event_loop_tests.rs; - 待补覆盖:
HandoffCloudCloud开/关、有无task_id、AI 开关下的墓碑按钮可见性,以及非 Oz harness 隐藏本地续跑。
ambient view 覆盖验证:FollowupDispatched独立于DispatchedAgent插入/渲染乐观 follow-up 提示词、记录已渲染提示词、重试时清空/重新启用 follow-up 状态。viewer manager/event-loop 覆盖验证:ambientSessionEnded插入或启用 follow-up UI 时不设置SharedSessionStatus::FinishedViewer、不通过过期网络发送提示词、不重复回放响应。
规格给出的人工验证清单:
- 关闭
HandoffCloudCloud:完成一次 Cloud Mode 运行,验证墓碑不变; - 开启开关:完成一次 Cloud Mode 运行,点击
Continue、提交提示词,验证 setup UI 出现、新共享会话在同一个窗格 attach; - 重复上述一次以捕获过期会话 ID、重复墓碑与订阅泄漏;
- 验证
Continue locally仍从墓碑 fork 本地会话; - 验证普通共享会话 viewer 在会话结束时仍变为只读/已完成。
风险与缓解
| 风险 | 缓解措施 |
|---|---|
提示词被路由到过期/缺失的Network | follow-up compose 模式在 viewer-manager 网络路径之前拦截提交(try_submit_pending_cloud_followup优先于TerminalViewEvent::SendAgentPrompt) |
| 墓碑插入回归通用 viewer 清理 | 执行边界走专用 ambient 方法(on_ambient_agent_execution_ended/handle_non_running_ambient_agent_task),普通共享会话清理保留在on_session_share_ended |
| 乐观提示词/响应重复 | 模型跟踪optimistically_rendered_user_queries+ 共享会话回放 skip 状态 |
| 重试状态漂移(输入冻结或墓碑隐藏) | 在 ambient 模型失败/cancel/auth 事件上集中清理,确保墓碑始终可用 |
| 墓碑状态过期 | 只跟踪一个墓碑 ID,点击时对照当前 ambient 模型校验task_id,云续跑开始时移除墓碑 |
完成标准(Definition of Done)
HandoffCloudCloud关闭时:墓碑与共享会话行为完全不变;- 开关开启时:合格的 ambient Cloud Mode 墓碑显示云
Continue动作,Oz/普通会话仍保留Continue locally; - 点击
Continue聚焦/揭示既有终端输入,提交普通提示词调用AmbientAgentViewModel::submit_cloud_followup; - follow-up 提示词不经过已结束的共享会话网络;
- 新会话启动期间显示 setup-v2 加载/错误 UI,已渲染的回放内容不重复;
- 乐观 follow-up 用户查询在 setup 期间渲染,不复用初始运行 dispatch UI;
- 新会话就绪后,既有
FollowupSessionReady热切换路径把会话 attach 到同一个窗格; - 针对性测试与
cargo check -p warp --features handoff_cloud_cloud全部通过。
延伸阅读
- 技术规格原文:specs/APP-4364/TECH.md
- 云模式窗格创建与会话事件接线:app/src/terminal/view/ambient_agent/mod.rs
- follow-up 模型编排(
submit_cloud_followup、乐观渲染、事件定义):app/src/terminal/view/ambient_agent/model.rs - 会话结束与墓碑插入/移除:app/src/terminal/view/shared_session/view_impl.rs
- 墓碑渲染与
ContinueInCloud动作:app/src/terminal/view/shared_session/conversation_ended_tombstone_view.rs - viewer manager 的会话 attach 与结束处理:app/src/terminal/shared_session/viewer/terminal_manager.rs
- 回放抑制与事件循环:app/src/terminal/shared_session/viewer/event_loop.rs
- 桌面应用
- 开发者工具
- 人工智能
- AI 应用
- AI Agent
- 代码智能体
【免费下载链接】warp
Warp is an agentic development environment, born out of the terminal.
相关推荐
Warp 的 Cloud-to-Cloud 交接:已完成的云对话如何在 Cloud Mode 中恢复并继续(可恢复 Transcript 技术方案解析)
Warp 的 Cloud to Cloud 交接:已完成的云对话如何在 Cloud Mode 中恢复并继续(可恢复 Transcript 技术方案解析) 在 W
桌面应用开发者工具人工智能AI 应用AI Agent代码智能体Warp Cloud 到 Cloud 续接(Handoff):完成态 Cloud 对话在 Cloud Mode 中的可续接 Transcript 实现
Warp Cloud 到 Cloud 续接(Handoff):完成态 Cloud 对话在 Cloud Mode 中的可续接 Transcript 实现 导读 本
桌面应用开发者工具人工智能AI 应用AI Agent代码智能体Warp TUI 会话续聊:`--resume` 恢复机制与源码实现解析
Warp TUI 会话续聊: resume 恢复机制与源码实现解析 Warp TUI 在退出时会打印一条可续聊指令,用户下次启动时携带该指令中的服务器会话令牌(
桌面应用开发者工具人工智能AI 应用AI Agent代码智能体
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考