☰
Warp 自定义工具栏命令绑定 CLI Agent:从 Unknown 会话到完整 Agent 体验
2026/10/5 13:12:36 网站建设 项目流程
  • 桌面应用
  • 开发者工具
  • 人工智能
  • AI 应用
  • AI Agent
  • 代码智能体

【免费下载链接】warp

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

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

自定义命令(如公司内部脚本uber-cli、my-claude-wrapper)在 Warp 中匹配工具栏规则后,过去只能得到CLIAgent::Unknown会话,缺失图标、品牌色、Rich Input 提交策略、技能与插件监听等专属体验。本篇文章基于 Warp 开源仓库中的 APP-4060 产品规格 与技术规格 TECH.md,结合源码实现,完整讲解「一条自定义工具栏命令绑定一个 CLI Agent」的配置方式、底层数据结构、检测链路与插件生命周期。读完后你将掌握:如何在设置界面为自定义命令选择 Agent、其配置如何持久化与云同步、命令检测如何携带 Agent 信息,以及自定义命令如何获得与原生检测 Agent 完全一致的体验。

背景与问题:自定义命令为何总是沦为 Unknown 会话

Warp 会把长时间运行的命令(超过LONG_RUNNING_COMMAND_DURATION_MS阈值后)与具体 CLI Agent 关联,从而在终端底部显示对应 Agent 的工具栏(Toolbar)。对于原生命令(claude、codex、gemini等),CLIAgent::detect 可以直接通过命令前缀识别。但现实中很多用户会通过包装脚本或别名调用 CLI Agent,例如 Uber 团队使用的aifx agent run claude,或者形如my-claude-wrapper的自定义别名。

这类命令无法被前缀规则命中,只能依靠用户在设置中添加正则模式("Commands that enable the toolbar")来启用工具栏。问题在于:旧实现中正则匹配只返回一个布尔值,命中的会话被无条件标记为CLIAgent::Unknown,于是:

  1. 工具栏显示通用外观,没有 Agent 的图标与品牌色;
  2. 会话被标记为CLIAgent::Unknown,导致不会创建插件监听器(没有富状态、Rich Input 的自动显隐);
  3. Rich Input 提交策略退化为默认的Inline,而不是该 Agent 最合适的策略;
  4. 斜杠菜单中缺少该 Agent 专属的 skill provider;
  5. 通知消息缺少 Agent 的显示名与图标。

产品规格(PRODUCT.md)的核心目标,就是给「正则模式 → CLI Agent」之间架起一座桥:让用户显式告诉 Warp 某个自定义模式代表哪个 Agent。

目标与非目标

目标:

  1. 允许用户为每条自定义工具栏命令模式指定一个具体 CLI Agent;
  2. 模式命中后,会话表现与原生检测到的 Agent 完全一致(图标、品牌、插件监听资格、Rich Input 策略、技能、通知);
  3. 保持aifx agent run claude对 Uber 团队成员的既有 Claude 检测逻辑不变。

非目标:

  • 不改变已知 Agent 的自动检测逻辑;
  • 不支持一个模式绑定多个 Agent(一个模式 = 一个 Agent);
  • 不新增新的 CLI Agent 类型;
  • 不改变自定义命令的插件安装/更新流程(现有 install chip 逻辑已按 Agent 做插件检查)。

设置界面:为每条命令选择 Agent

行布局

设置列表中每条命令新增一个右侧下拉框,整体布局为:

[命令正则文本] [Agent Dropdown ▼] [× 移除]

命令文本左对齐且可收缩(等宽字体),下拉框固定宽度并与命令文本同组靠左,移除按钮右对齐。折叠状态下,下拉框顶部栏直接显示已选 Agent 的名称。

下拉项

下拉菜单列出所有已知 CLI Agent,每一项展示:

  • Agent 图标(来自CLIAgent::icon());
  • Agent 显示名(来自CLIAgent::display_name(),如 "Claude Code"、"Gemini"、"Codex");
  • 首个默认项 "CLI Agent"(无图标),新添加的模式默认选中它。

CLIAgent::Unknown不会作为可选项目出现在下拉中——它被 "CLI Agent" 这个默认项所代表。产品规格中默认项文案为 "CLI Agent",而当前仓库落地实现(cli_agents_page.rs)将其命名为 "Other",并把菜单头部文案覆盖为 "Select coding agent",语义一致。

添加 / 移除 / 修改

  • 添加:通过文本输入框提交新命令后,以默认 "CLI Agent"(空值)加入列表,用户随后可用下拉框修改;
  • 移除:删除命令时,其 Agent 映射一并从底层设置中清理(同一 HashMap 条目内模式与 Agent 共存,天然无孤儿数据);
  • 修改:选择下拉中的某个 Agent 会立即持久化,并通过与其他 AI 设置相同的机制同步到云端。

从当前源码看,该设置项定义在 app/src/settings/ai.rs 的cli_agent_footer_enabled_commands(TOML 路径agents.third_party.cli_agent_toolbar_enabled_commands),其sync_to_cloud为Globally(RespectUserSyncSetting::Yes),即遵循用户云同步开关进行全局同步。

下拉框的源码级实现

设置页的每个下拉框由create_cli_agent_dropdowns创建(cli_agents_page.rs),核心配置包括:

  • set_top_bar_max_width(160.):紧凑的顶部栏宽度;
  • set_menu_width(180.):容纳「图标 + 名称」的菜单宽度;
  • set_main_axis_size(MainAxisSize::Min):按内容宽度自适应;
  • 菜单项遍历enum_iterator::all::<CLIAgent>(),跳过Unknown,为每个 Agent 构造MenuItemFields::new(agent.display_name()).with_icon(agent.icon()),选中后派发SetCLIAgentForCommand { pattern, agent }动作;
  • 通过set_selected_by_name依据当前保存值(空值显示为 "Other")恢复选中状态。

下拉组件本身是通用的Dropdown(view_components/dropdown.rs),菜单项支持with_icon(menu.rs)。

底层数据结构:ToolbarCommandMap 与旧数据迁移

产品与技术规格要求把设置类型从Vec<String>升级为「模式 → Agent」的映射结构。当前仓库以ToolbarCommandMap实现(app/src/settings/ai.rs):

/// 键为命令正则模式(保序),值为序列化的 CLIAgent 名(如 "Claude")。 /// 空字符串值表示 "CLI Agent"(即 CLIAgent::Unknown)。 pub struct ToolbarCommandMap(IndexMap<String, String>);

两个关键设计:

1. 用IndexMap而非HashMap保序。技术规格的风险章节明确提到HashMap不保证迭代顺序、可能导致设置列表乱序;落地实现选择了IndexMap,使设置界面列表顺序确定(按插入序)。

2. 向后兼容的反序列化。旧的设置格式是字符串数组(["pattern1", "pattern2"]),新格式是映射({"pattern1": "Claude", "pattern2": ""})。ToolbarCommandMap通过#[serde(untagged)]枚举同时兼容两种格式:

#[derive(Deserialize)] #[serde(untagged)] enum MapOrVec { Map(IndexMap<String, String>), Vec(Vec<String>), } // Map 格式直接使用;Vec 格式逐项转为空值键: // ["pattern1", "pattern2"] -> {"pattern1": "", "pattern2": ""}

SettingsValue::from_file_value也遵循同样的双格式逻辑:先尝试对象(map)解析,再回退数组(legacy)解析。由于用户不会降级,只需前向迁移。若 TOML 值既不是字符串数组也不是映射(损坏配置),则回退到默认空映射,与既有损坏设置的处理行为一致。

三个设置操作方法

对应 app/src/settings/ai.rs 中的三个方法:

  • add_cli_agent_footer_enabled_command(command):trim 后判空、去重,以空字符串值插入键;
  • remove_cli_agent_footer_enabled_command(command):shift_remove删除键,Agent 映射随键一并消失;
  • set_cli_agent_for_command(pattern, agent: Option<CLIAgent>):None写空字符串,Some(agent)写agent.to_serialized_name();仅当键已存在时更新。

to_serialized_name/from_serialized_name定义于 cli_agent.rs,是CLIAgent与字符串之间的序列化互转,空字符串反序列化回CLIAgent::Unknown。

对应的 TOML 示例(设置界面之外直接查看/配置底层文件时):

[agents.third_party] # 新格式:模式 -> Agent 序列化名,空字符串表示 "CLI Agent" cli_agent_toolbar_enabled_commands = { "my-claude-wrapper" = "Claude", "uber-cli" = "Codex", "legacy-cmd" = "" }

检测链路:matched_agent 如何携带 Agent

技术规格(TECH.md)把检测链路的改造作为核心:让bool匹配升级为「携带 Agent 的匹配」。

编译后的正则单例

CompiledCommandsForCodingAgentToolbar是缓存编译正则的单例模型,将ToolbarCommandMap编译为「正则 + Agent」对:

pub struct CompiledCommandsForCodingAgentToolbar { regexes: Vec<(Regex, CLIAgent)>, }

其parse遍历设置映射:编译键为正则(失败则跳过),值经CLIAgent::from_serialized_name解析为具体 Agent。单例注册时订阅AISettingsChangedEvent::CLIAgentToolbarEnabledCommands变更事件,任何增删改都会触发正则重建。

匹配入口matched_agent返回Option<CLIAgent>:

pub fn matched_agent(app: &AppContext, command: &str) -> Option<CLIAgent> { Self::as_ref(app) .regexes .iter() .find(|(regex, _)| regex.is_match(command)) .map(|(_, agent)| *agent) }

is_match针对完整命令字符串进行匹配,因此正则需要能覆盖包装脚本的整条命令(例如.*my-claude-wrapper.*或直接写命令名)。

终端侧的检测顺序

终端视图的detect_cli_agent_from_model(use_agent_footer/mod.rs)在命令成为长期运行命令后被调用,检测顺序如下:

  1. 先走原生检测CLIAgent::detect:解析命令首词(支持跳过FOO=1这类环境变量前缀)、按 shell 别名表解析别名、再与各 Agent 的command_prefixes()比对,同时检查aifx agent run claude特例;
  2. 若原生检测返回None,回退到CompiledCommandsForCodingAgentToolbar::matched_agent,命中用户自定义模式则返回该模式绑定的具体 Agent;
  3. 若返回的 Agent 来自自定义模式,函数还会顺带返回自定义命令前缀(命令首词),供后续 UI 使用。

这样,模式命中不再无条件返回CLIAgent::Unknown,而是把用户在设置中选择的 Agent 一路传递到会话创建。

会话创建与完整 Agent 体验

检测结果最终传给CLIAgentSessionsModel::set_session,存储在CLIAgentSession.agent字段(cli_agent_sessions/mod.rs)。所有下游消费方都从这个会话读取 Agent:

  • 工具栏渲染:使用session.agent的图标、显示名与品牌色。品牌色在 cli_agent.rs 中定义,例如 Claude 橙色、Gemini 蓝、Grok 近黑等;
  • Rich Input 提交策略:由rich_input_submit_strategy(agent)选择(use_agent_footer/mod.rs),当前实现如下:
Agent提交策略
Codex、OhMyPi、HermesBracketedPaste(括号粘贴)
CopilotBracketedPasteDelayedEnter(括号粘贴 + 延迟回车)
Claude、OpenCode、Gemini、Auggie、Grok、CursorCliDelayedEnter(延迟回车)
Amp、Droid、Pi、Kiro、Goose、Vibe、Antigravity、WarpTui、UnknownInline(直接写入)
  • 斜杠菜单技能:supported_skill_providers()决定 Rich Input 斜杠菜单展示哪些 skill provider,例如 Claude 只显示SkillProvider::Claude,Codex 显示 Agents/Claude/Codex 三个,Unknown 为空;
  • 通知:会话携带 Agent 后,通知可显示正确的显示名与图标。

需要说明的是,产品规格中描述的提交策略映射与当前源码实现存在细微差异(如规格将 Claude 归为Inline、Copilot 归为DelayedEnter,而源码实际为 Claude →DelayedEnter、Copilot →BracketedPasteDelayedEnter),本文章以仓库当前代码为准。

插件监听器与安装/更新芯片

自定义模式一旦绑定到具体 Agent,插件生命周期将自动生效,无需额外改动:

  1. OSC 777 哨兵路径:插件发送warp://cli-agent的SessionStart事件时(view.rs中约 10960 行),register_listener被调用。只要is_agent_supported(&CLIAgent::Claude)为真(listener/mod.rs 当前对 Claude、OpenCode、Codex、Gemini、Auggie、Droid、Pi、OhMyPi、Grok、WarpTui 均返回 true),监听器即注册成功;而Unknown永远不会创建监听器;
  2. Codex 主动注册:Codex 使用 OSC 9 纯文本通知,检测到CLIAgent::Codex后立即调用register_cli_agent_listener(约 10069 行)——自定义模式绑定 Codex 同样触发该路径;
  3. 安装/更新芯片:plugin_manager_for(agent)(plugin_manager/mod.rs)返回对应 Agent 的插件管理器,从而显示插件安装/更新 chip。

简言之:只要detect_cli_agent_from_model返回具体 Agent,会话就会携带它,整条插件链路(注册监听器、安装 chip、更新 chip、富状态)自动按该 Agent 运行,与原生检测完全一致。

aifx agent run claude 特例

CLIAgent::detect中的is_aifx_agent_run_claude判断(cli_agent.rs)做了两件事:命令以aifx agent run claude开头,且用户属于 Uber 团队(工作区团队 UID 匹配UBER_TEAM_UID)。命中即返回CLIAgent::Claude,无需任何配置。该特例保持原样、不随本次改动变化;非 Uber 用户若希望获得相同效果,只需添加自定义模式并绑定到 Claude。

验证清单:从设置到运行的一整套检查

结合产品规格的 Validation 与成功标准,推荐按以下清单验证:

  1. 设置界面:每条命令的行内都出现下拉框;下拉包含 "CLI Agent"(无图标)与除Unknown外的全部 Agent(正确图标 + 显示名);
  2. 默认值:新添加的命令默认 "CLI Agent";
  3. 绑定验证:添加my-claude-wrapper模式并绑定 Claude,在终端运行该命令,检查:工具栏显示 Claude 图标与品牌、Rich Input 使用 Claude 的提交策略、插件未安装时出现安装 chip、通知显示 "Claude Code";
  4. 插件监听:绑定 Claude/Codex/OpenCode 的模式命中后,插件生命周期(install chip、update chip、监听器注册)与原生检测一致;
  5. 持久化:修改选择后重启 Warp,下拉框仍保持所选状态(设置随其他 AI 设置云端同步);
  6. 移除清理:删除命令后重新添加同一条命令,应恢复为默认 "CLI Agent";
  7. 默认行为兼容:不绑定 Agent 的既有模式仍产生CLIAgent::Unknown会话,工具栏外观与旧版一致;
  8. aifx 无回归:Uber 团队成员运行aifx agent run claude仍识别为 Claude。

风险与注意事项

  • 设置迁移:自定义Deserialize必须同时处理旧Vec<String>与新HashMap两种格式,损坏值回退为空映射;
  • 迭代顺序:产品规格时代使用HashMap时列表顺序可能不保证,当前实现改用IndexMap已解决此问题(设置界面展示顺序稳定);
  • 孤儿条目:模式与 Agent 同处一个映射条目,删除模式必然清除其 Agent 绑定,无需额外清理逻辑。

综上,该方案通过「设置类型升级(Vec<String>→ 模式→Agent 映射)+ 编译正则单例携带 Agent(matched_agent)+ 检测链路传递(detect_cli_agent_from_model)+ 现有会话/插件机制复用」四步改造,让自定义工具栏命令获得与原生 CLI Agent 完全一致的开箱体验。相关实现细节可继续在仓库中追溯:settings/ai.rs、terminal/cli_agent.rs、terminal/view/use_agent_footer/mod.rs、terminal/cli_agent_sessions/listener/mod.rs 与 settings_view/cli_agents_page.rs。

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

【免费下载链接】warp

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

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

相关推荐

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

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

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

立即咨询