☰
Warp 编排会话 Agent 名称闭环:基于 agent_config_snapshot.name 的短标签链路改造(QUALITY-731)
2026/10/5 6:34:12 网站建设 项目流程
  • 桌面应用
  • 开发者工具
  • 人工智能
  • AI 应用
  • AI Agent
  • 代码智能体

【免费下载链接】warp

Warp is an agentic development environment, born out of the terminal.

项目地址:https://gitcode.com/GitHub_Trending/wa/warp
点击查看免费下载

导读

Warp 的共享会话(Shared Session)观看者(Viewer)在查看编排(Orchestration)会话时,需要为每个子 Agent 渲染一个简短名称标签(用于药丸条 Pill、Hover 卡片、面包屑、状态卡片与对话参与者标签)。QUALITY-731 修复了一个 bug:编排者(Orchestrator)在创建子 Agent 时使用agent_run_configs[i].name作为短标签,但 Viewer 只能从服务端任务记录重建子会话,拿不到这个短名,只能退回到title(往往是一长串描述性句子或被截断的 prompt)。本指南围绕这一轮「编排者 → 服务端 → Viewer」的短名称闭环改造展开:它复用既有的AgentConfigSnapshot.name字段作为权威载体,重写AmbientAgentTask::display_name()的优先级规则,并完成客户端出站请求的装箱(stamping)与 v1 并行字段的回滚。读完本文你将掌握:Warp 中AgentConfigSnapshot的完整链路(REST / GraphQL / 本地 harness)、display_name()的三级回退语义、normalize_orchestrator_agent_name的 trim 契约,以及如何在本地复现和验证这套改动。

背景与问题:Viewer 侧短名称缺失

Bug 的表现形式

在共享会话场景中,编排者客户端会用自己的agent_run_configs[i].name(如frontend-tests)给子 Agent 打短标签,见 run_agents.rs。但 Viewer 不是编排者,它只能通过服务端任务记录(task record)重建子会话。旧实现中 Viewer 侧缺少对这个短名的访问路径,于是所有依赖名称的界面元素全部退化为title——可能是「Fix the CSS layout bug in the settings page reported by the design team yesterday」这样一长段描述,也可能是被截断的 prompt。

具体受影响的面包括:

  • 编排药丸条(orchestration pill bar)中的 Pill 标签;
  • Hover 卡片中的参与者标签;
  • 面包屑(breadcrumb);
  • 子 Agent 状态卡片;
  • 对话记录(transcript)中的参与者标注。

修复思路的收敛

QUALITY-731 的核心决策是:不为 task 或 request 类型引入并行的顶层name字段,而是复用已经存在的agent_config_snapshot.name作为「编排者提供的短标签」的权威位置。这一选择与服务端配对 spec 保持一致——服务端同样以AgentConfigSnapshot.Name作为权威载体。其优势是:

  • 线上(wire)与模型层不需要新增字段;
  • 出站路径只需在既有AgentConfigSnapshot { ... }构造器里把编排者短名装进去;
  • Viewer 的display_name()直接读agent_config_snapshot.name;
  • 对任何已经通过其他途径填充该字段的 task 天然向后兼容。

现状盘点:客户端已有的承载能力

在改造之前,客户端各层其实已经具备传输AgentConfigSnapshot的能力,这为「零新增字段」方案提供了前提:

层位置现状
REST 请求app/src/server/server_api/ai.rsSpawnAgentRequest.config: Option<AgentConfigSnapshot>已存在;CreateAgentTaskInput.agent_config_snapshot: Option<String>(序列化 JSON)已存在
任务模型app/src/ai/ambient_agents/task.rsAmbientAgentTask.agent_config_snapshot: Option<AgentConfigSnapshot>已从服务端反序列化;快照内的name: Option<String>带#[serde(default, skip_serializing_if = "Option::is_none")]
编排执行run_agents.rsRunAgentsExecutor按RunAgentsAgentRunConfig扇出,cfg.name是客户端侧短名的事实来源
远端子进程terminal_pane.rslaunch_remote_child用AgentConfigSnapshot构造SpawnAgentRequest.config
本地子进程local_harness_launch.rsprepare_local_harness_child_launch构造local_child_task_config快照
Viewer 模型orchestration_viewer_model.rsapply_children_fetch中task.display_name()曾经读取 v1 的AmbientAgentTask.name字段

正是因为AgentConfigSnapshot已经能同时穿过 REST 与 GraphQL 两条通道,改造才可以把短标签「装箱」进既有信封(envelope),而不是再造新字段。

设计方案对比:为什么否决并行标量字段

QUALITY-731 曾考虑过另一套方案(v1):

  • 在 task / request 类型上各加一个并行的name字段:AmbientAgentTask.name、SpawnAgentRequest.name、AIClient::create_agent_task的agent_name参数、GraphQLCreateAgentTaskInput.agentName。

这套方案的问题在于线上与模型层会出现两个名称字段(name与config.name),语义重叠、易失配,且每个消费方都要决定读哪一个。该方案被 reviewer 否决后,改造转向了「复用agent_config_snapshot.name」:出站路径把编排者短名印进既有AgentConfigSnapshot { ... }构造器,Viewer 的display_name()从agent_config_snapshot.name读取。最终交付即「双 PR 对线契约」(client 与 server 各自回滚 v1 并行字段)。

改造方案详述

出站请求装箱(Outbound request wiring)

所有出站边界统一遵循一个约定:在请求构造点把编排者提供的短名装进AgentConfigSnapshot;在构造点做 trim;空串 / 纯空白视为缺失。

  1. 远端路径:launch_remote_child(terminal_pane.rs)构建放在SpawnAgentRequest.config上的AgentConfigSnapshot时,在结构体字面量中增加name: ...,值为 trim 后且过滤掉空值的request.name。不再克隆顶层request.name,也不存在SpawnAgentRequest.name字段(该字段在 v1 回滚中删除)。

  2. 本地 Oz 路径:launch_local_no_harness_child(同样位于 terminal_pane.rs)此前向create_agent_task传None作为 config 快照,现改为:

    Some(AgentConfigSnapshot { name: normalize_orchestrator_agent_name(&request.name), ..Default::default() })
  3. 本地第三方 harness 路径:launch_local_harness_child/prepare_local_harness_child_launch(local_harness_launch.rs)经由local_child_task_config(harness)构造快照。改造后该函数新增agent_name: Option<String>参数,并在返回的快照内盖章:

    pub(super) fn local_child_task_config( harness: Harness, agent_name: Option<String>, ) -> Option<AgentConfigSnapshot> { let agent_name = agent_name .as_deref() .and_then(normalize_orchestrator_agent_name); match harness { Harness::Oz | Harness::Unknown => None, Harness::Claude | Harness::OpenCode | Harness::Gemini | Harness::Codex => { Some(AgentConfigSnapshot { name: agent_name, harness: Some(HarnessConfig::from_harness_type(harness)), ..Default::default() }) } } }

    注意:Harness::Oz与Harness::Unknown返回None,即本地 Oz 子任务不携带快照;只有 Claude / OpenCode / Gemini / Codex 这类第三方 CLI harness 会带。同时,AIClient::create_agent_task上 QUALITY-731 v1 增加的agent_name参数及其实现内的 trim 逻辑被移除——构造点现在是唯一来源。

  4. SDK REST 路径:agent_sdk/ambient.rs的agent run-cloudCLI(REST)已经设置了config.name = args.name,无需改动。

  5. Handoff 与独立 cloud-mode:build_handoff_spawn_request(handoff)与spawn_agent(独立 cloud-mode)今天不提供名称,无需改动。

入站响应读取:display_name() 三级回退

重写 task.rs 中的AmbientAgentTask::display_name(&self) -> &str,查找顺序如下:

  1. 当agent_config_snapshot.as_ref().and_then(|c| c.name.as_deref())存在且 trim 后非空时,返回该值;
  2. 否则返回 trim 后非空的title;
  3. 否则返回字面量"Agent"。

仓库中的最终实现(task.rs)与配套的 trim 辅助函数normalize_orchestrator_agent_name(task.rs):

/// Returns the trimmed orchestrator agent name, or `None` when empty / whitespace-only. pub fn normalize_orchestrator_agent_name(raw: &str) -> Option<String> { let trimmed = raw.trim(); (!trimmed.is_empty()).then(|| trimmed.to_string()) } /// Returns the short label for this task: trimmed `agent_config_snapshot.name`, /// trimmed `title`, or `"Agent"`. pub fn display_name(&self) -> &str { if let Some(name) = self .agent_config_snapshot .as_ref() .and_then(|c| c.name.as_deref()) { let trimmed = name.trim(); if !trimmed.is_empty() { return trimmed; } } let trimmed_title = self.title.trim(); if !trimmed_title.is_empty() { return trimmed_title; } "Agent" }

Viewer 侧OrchestrationViewerModel::apply_children_fetch(实际消费点是 orchestration_viewer_model.rs 的register_child)保持原有的一行let name = task.display_name().to_string();不变。与此同时,conversation.set_fallback_display_title(task.title.clone())的调用得以保留——描述性长标题仍然可以通过AIConversation::title()的回退路径取到(见 conversation.rs)。

v1 字段回滚(Removals)

由于契约从「并行name字段」转回「agent_config_snapshot.name」,v1 引入的字段需要系统性移除:

源码移除:

  • app/src/server/server_api/ai.rs:删除SpawnAgentRequest.name字段及其 serde 属性;删除AIClient::create_agent_tasktrait 方法及其 impl 中的agent_name: Option<String>参数(含 trim 块与CreateAgentTaskVariables内的agent_name行)。
  • app/src/ai/ambient_agents/task.rs:删除AmbientAgentTask.name: Option<String>字段及其 serde default;保留display_name()作为辅助方法,仅重写其函数体。
  • app/src/pane_group/pane/terminal_pane.rs:删除launch_remote_child中的request.name.clone()管线、launch_local_no_harness_child中的agent_name_for_create = Some(request_name.clone())及第 5 个位置参数、launch_local_harness_child中的agent_name_for_task = Some(request_name.clone())及第 5 个参数。
  • app/src/pane_group/pane/local_harness_launch.rs:删除prepare_local_harness_child_launch的agent_name: Option<String>参数及穿进create_agent_task的第 5 个位置参数;若该函数上的#[allow(clippy::too_many_arguments)]因此不再必要则一并移除。
  • app/src/ai/conversation_details_panel.rs:保留 v1 在from_task上加的 deferral 注释,侧栏行为不变(仍读task.title)。
  • crates/warp_graphql_schema/api/schema.graphql:删除CreateAgentTaskInput下的agentName: String字段及 docstring。
  • 清理 v1 在所有SpawnAgentRequest { ... }构造器与AmbientAgentTask { ... }测试夹具上遗留的name: None字面量。

测试移除 / 改写:

  • app/src/server/server_api/ai_tests.rs:删除spawn_agent_request_serializes_name_when_present、spawn_agent_request_omits_name_when_none以及make_spawn_agent_request夹具中的name: None行。
  • task_tests.rs:重写 5 个display_name_*测试,改为用agent_config_snapshot.name值(或None)构造AmbientAgentTask,保持原有优先级覆盖形状。
  • orchestration_viewer_model_tests.rs:重写 4 个registers_child_agent_name_*测试,make_task/make_task_with_name辅助函数把 name 移入 config snapshot 夹具。
  • local_harness_launch_tests.rs:重写两个prepare_local_codex_child_*测试,断言local_child_task_config(或构造出的快照)正确携带name。

端到端数据流

改造后的完整链路如下(mermaid 流程图,与原 spec 一致):

关键点拆解:

  • RunAgentsExecutor扇出每个RunAgentsAgentRunConfig,cfg.name是客户端侧的短名来源;
  • 远端走SpawnAgentRequest.config.name,本地 harness 走createAgentTask的agentConfigSnapshot.name;
  • warp-server 把agent_config_snapshot.name与title一并落库,GET /agent/runs返回时同时携带;
  • Viewer 侧display_name()取快照短名,长标题只作fallback_display_title兜底。

测试与验证策略

单元 / 客户端测试

  • display_name()优先级与 trim(task_tests.rs):仓库现有测试覆盖了快照名优先于标题、快照缺失时回退标题、纯空白快照名回退标题、空标题返回"Agent"、纯空白标题返回"Agent"、以及每一层 trim 行为。例如:

    #[test] fn display_name_prefers_agent_config_snapshot_name_over_title() { let task = make_task(Some("frontend-tests"), "Long descriptive task title"); assert_eq!(task.display_name(), "frontend-tests"); } #[test] fn display_name_falls_back_to_title_when_snapshot_name_is_missing() { let task = make_task(None, "Long descriptive task title"); assert_eq!(task.display_name(), "Long descriptive task title"); } #[test] fn display_name_returns_literal_agent_when_both_sources_are_empty() { let task = make_task(None, ""); assert_eq!(task.display_name(), "Agent"); }
  • Viewer 注册(orchestration_viewer_model_tests.rs):验证通过agent_config_snapshot.name注册编排者名称、回退到 title(用不同的快照 / 标题值以便区分两条通道)、最终回退"Agent",以及纯空白 title 对set_fallback_display_title的门控。

  • 本地 harness(local_harness_launch_tests.rs):local_child_task_config在快照中携带编排者名称、trim 空白、对 Oz / Unknown harness 返回None;normalize_orchestrator_agent_name覆盖 trim 与 empty-vs-Some 契约。

  • 构造点间接覆盖:launch_remote_child中SpawnAgentRequest.config.name用normalize_orchestrator_agent_name(&request.name)的返回值盖章,主要由normalize_orchestrator_agent_name单元测试加terminal_pane.rs结构体字面量的可见性检查覆盖。给组装后的SpawnAgentRequest写专门测试需要抽取build_spawn_request辅助函数(该函数要&mut PaneGroup+ViewContext<PaneGroup>,并借它们解析运行时技能与快照禁用标志,当前不可单测),因此被推迟到范围外。

手动验证步骤

  1. 启动或加载一个编排共享会话,其出站 spawn 使agent_config_snapshot.name = "frontend-tests"、title为长描述。
  2. 以 Viewer 身份打开该会话。
  3. 确认 Pill 标签、Hover 卡片参与者标签、面包屑、子状态卡片与 transcript 参与者都显示frontend-tests。
  4. 确认长标题仍通过AIConversation::title()回退路径可用。
  5. 确认未设置名称的子任务仍显示服务端提供的 skill 派生默认值。
  6. 会话详情侧栏按 v1 deferral 仍停留在task.title。

实现后运行命令

# 聚焦相关模块的 Rust 测试 cargo test -p warp -- ai::ambient_agents::task cargo test -p warp -- terminal::shared_session::viewer::orchestration_viewer_model_tests cargo test -p warp -- pane_group::pane::local_harness_launch_tests # 格式化与提交前检查 cargo fmt ./script/presubmit

其中./script/presubmit若本机 corepack/yarn-4 阻塞command-signatures-v2步骤可本地跳过,CI 为准。最终还需连接配对 pivot 改动后的服务端做 UI 手工验证。

并行化与 PR 卫生

该改造拆分为两个配对 PR,服务端与客户端可并行推进:

  • 服务端:本地 warp-server 分支matthew/roundtrip-agent-name(base:origin/matthew/restore-remote-orch-conversations),负责服务端回滚 + 辅助函数 + REST/GraphQL 契约删除。
  • 客户端:本地 warp 分支matthew/roundtrip-agent-name(base:origin/master),负责客户端回滚 + 出站快照盖章 +display_name()重写。

时序要点:

  • 两个 PR 可并行 force-push——wire 契约(复用既有agent_config_snapshot信封、删除 v1 并行字段)已事先完全对齐;
  • 端到端手动验证必须等两条分支都切到 pivot 契约后进行;
  • force-push 会从 PR 历史中移除 v1 提交,需重写 PR 描述说明 pivot 并互相链接配对 PR;在客户端 PR 模板勾选Warp Agent Mode;两个 PR 保持 draft。

范围外(后续跟进项)

pivot 交付的是 wire 契约与编排 Viewer 药丸条。以下界面仍直接渲染task.title(或反规范化后的entry.display.title),需要后续把display_name()扇出到它们,但每处都需要额外的管线决策(基于 entry 的面板当前无法访问agent_config_snapshot):

  • app/src/ai/agent_conversations_model/entry.rs:AgentConversationEntry由ListConversationsItem水合,目前只携带display.title;要在这里路由display_name(),需先把agent_config_snapshot.name反规范化到 entry(或渲染前拉取 task)。
  • app/src/ai/conversation_details_panel.rs 的from_agent_conversation_entry:数据源同上,位于 entry 下游。
  • conversation_ended_tombstone_view.rs:tombstone 用task.title作头部、并复用agent_config_snapshot.name作为skill_name(后者现在名不副实,源码中有QUALITY-731 follow-up内联注释标记);把「编排者提供的 agent 名」与「skill 渲染」拆开属于后续工作。
  • app/src/ai/agent_sdk/ambient.rs(约 845 行):CLI/SDK 侧已能读 task 记录,一旦display_name()从该路径可达即可采用。
  • app/src/workspace/view/conversation_list/item.rs 与 app/src/workspace/view.rs:workspace 会话列表标签来自entry.display.title,管线决策与 entry 类面板相同。

ConversationDetailsData::from_task侧栏头部刻意不在本次范围内:产品仍在评估是否同时展示短名与描述性标题,内联 deferral 注释保留。

风险与缓解

  • display_name()变更:任何直接读AmbientAgentTask.name(而非走辅助方法)的地方必须改走新来源。缓解:删除字段会让所有直接读取点在编译期报错,逐个在调用点修复。
  • 既有夹具:带显式name: None的测试夹具将无法编译。缓解:随同变更统一回滚这些name: None行。
  • 未来双通道冲突:若未来某调用方同时设置agent_config.name与其他渠道的编排者名称——服务端强制「始终覆盖」优先级,客户端只在提供了编排者名称时才盖章,客户端侧不存在冲突。
  • 详情面板:按 v1 deferral 停留在task.title,conversation_details_panel.rs的 deferral 注释保留。
  • 纯空白task.title的失配:若不 trim,agent_name()(trim 后 →"Agent")会与title()(未 trim)失配。缓解:OrchestrationViewerModel::apply_children_fetch中的门控在检查与存储前先调用task.title.trim().to_string();专门的测试registers_child_agent_name_does_not_set_fallback_for_whitespace_only_title锁定该行为。
  • force-push 历史:v1 提交被移除后,旧提交上的 review 评论会挂在孤儿提交上。缓解:PR 描述说明 pivot 并链接 v1 评审历史。

小结

QUALITY-731 的最终形态是一套零新增字段的契约收敛:短名只存在于agent_config_snapshot.name,客户端所有出站边界在构造点装箱(trim 后过滤空值),Viewer 通过重写后的display_name()以「快照名 → title → 'Agent'」三级回退读取,长标题继续通过fallback_display_title保留。对当前仓库而言,app/src/ai/ambient_agents/task.rs的display_name()与normalize_orchestrator_agent_name、app/src/pane_group/pane/local_harness_launch.rs的local_child_task_config、app/src/terminal/shared_session/viewer/orchestration_viewer_model.rs的register_child是这次改造落地后最值得继续跟踪的三个锚点,后续跟进项也以它们为起点继续扇出display_name()。

  • 桌面应用
  • 开发者工具
  • 人工智能
  • AI 应用
  • AI Agent
  • 代码智能体

【免费下载链接】warp

Warp is an agentic development environment, born out of the terminal.

项目地址:https://gitcode.com/GitHub_Trending/wa/warp
点击查看免费下载

相关推荐

上一篇:LearnPrompt数字人教程:零成本打造你的AI虚拟分身
下一篇:Gittle:Pythonic Git for Humans - 终极Python Git操作指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询