- 桌面应用
- 开发者工具
- 人工智能
- AI 应用
- AI Agent
- 代码智能体
【免费下载链接】warp
Warp is an agentic development environment, born out of the terminal.
自定义命令(如公司内部脚本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,于是:
- 工具栏显示通用外观,没有 Agent 的图标与品牌色;
- 会话被标记为
CLIAgent::Unknown,导致不会创建插件监听器(没有富状态、Rich Input 的自动显隐); - Rich Input 提交策略退化为默认的
Inline,而不是该 Agent 最合适的策略; - 斜杠菜单中缺少该 Agent 专属的 skill provider;
- 通知消息缺少 Agent 的显示名与图标。
产品规格(PRODUCT.md)的核心目标,就是给「正则模式 → CLI Agent」之间架起一座桥:让用户显式告诉 Warp 某个自定义模式代表哪个 Agent。
目标与非目标
目标:
- 允许用户为每条自定义工具栏命令模式指定一个具体 CLI Agent;
- 模式命中后,会话表现与原生检测到的 Agent 完全一致(图标、品牌、插件监听资格、Rich Input 策略、技能、通知);
- 保持
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)在命令成为长期运行命令后被调用,检测顺序如下:
- 先走原生检测
CLIAgent::detect:解析命令首词(支持跳过FOO=1这类环境变量前缀)、按 shell 别名表解析别名、再与各 Agent 的command_prefixes()比对,同时检查aifx agent run claude特例; - 若原生检测返回
None,回退到CompiledCommandsForCodingAgentToolbar::matched_agent,命中用户自定义模式则返回该模式绑定的具体 Agent; - 若返回的 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、Hermes | BracketedPaste(括号粘贴) |
| Copilot | BracketedPasteDelayedEnter(括号粘贴 + 延迟回车) |
| Claude、OpenCode、Gemini、Auggie、Grok、CursorCli | DelayedEnter(延迟回车) |
| Amp、Droid、Pi、Kiro、Goose、Vibe、Antigravity、WarpTui、Unknown | Inline(直接写入) |
- 斜杠菜单技能:
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,插件生命周期将自动生效,无需额外改动:
- 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永远不会创建监听器; - Codex 主动注册:Codex 使用 OSC 9 纯文本通知,检测到
CLIAgent::Codex后立即调用register_cli_agent_listener(约 10069 行)——自定义模式绑定 Codex 同样触发该路径; - 安装/更新芯片:
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 与成功标准,推荐按以下清单验证:
- 设置界面:每条命令的行内都出现下拉框;下拉包含 "CLI Agent"(无图标)与除
Unknown外的全部 Agent(正确图标 + 显示名); - 默认值:新添加的命令默认 "CLI Agent";
- 绑定验证:添加
my-claude-wrapper模式并绑定 Claude,在终端运行该命令,检查:工具栏显示 Claude 图标与品牌、Rich Input 使用 Claude 的提交策略、插件未安装时出现安装 chip、通知显示 "Claude Code"; - 插件监听:绑定 Claude/Codex/OpenCode 的模式命中后,插件生命周期(install chip、update chip、监听器注册)与原生检测一致;
- 持久化:修改选择后重启 Warp,下拉框仍保持所选状态(设置随其他 AI 设置云端同步);
- 移除清理:删除命令后重新添加同一条命令,应恢复为默认 "CLI Agent";
- 默认行为兼容:不绑定 Agent 的既有模式仍产生
CLIAgent::Unknown会话,工具栏外观与旧版一致; - 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.
相关推荐
Warp 自定义工具栏命令与 CLI Agent 关联:从 `CLIAgent::Unknown` 到完整插件生命周期的设计实践
Warp 自定义工具栏命令与 CLI Agent 关联:从 CLIAgent::Unknown 到完整插件生命周期的设计实践 导读 本文以 specs/APP
桌面应用开发者工具人工智能AI 应用AI Agent代码智能体为 omp-coding-agent 编写自定义工具(Custom Tools):从工厂模式到会话状态管理的完整实践指南
为 omp coding agent 编写自定义工具(Custom Tools):从工厂模式到会话状态管理的完整实践指南 自定义工具(Custom Tools)
人工智能AI Agent代码智能体工具调用CLIMCP Clientsclaude-agent-sdk-python 自定义 Agent 完整指南:从 Markdown 文件到 AgentDefinition 编程式定义
claude agent sdk python 自定义 Agent 完整指南:从 Markdown 文件到 AgentDefinition 编程式定义 本指南以
人工智能AI Agent工具调用MCP Clients大模型
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考