docs/prds/conversations/custom/custom-agent.md
本文档覆盖「设置 → Agents → 本地 Agents」页面中 Custom Agent 相关的全部功能,包括列表展示、创建/编辑/删除/启用禁用、连接测试、Agent 自动检测机制。 基于静态代码分析和动态 UI 验证综合整理,经 DA 质疑和 Tester 反馈修正定稿。
用户故事:作为用户,我希望在设置中有一个统一的 Agent 管理入口,可以分别管理本地 Agent 和远端 Agent。
正常流程(用户视角):
#/settings/agent)?tab=local / ?tab=remote异常情况:
tab 参数非 local/remote:保持当前 Tab 选中状态不变(首次加载时为 local)(AgentModalContent.tsx:22-29——useEffect 不执行时 activeTab 保持上一次设置的值)viewMode === 'page')下禁用自定义滚动,走原生滚动验收标准:
?tab=remote 可定位到远端 Tab用户故事:作为用户,我希望看到系统自动检测到的本地已安装 Agent CLI 工具,了解哪些 Agent 可用。
前置条件:系统启动时已通过 which 命令扫描 POTENTIAL_ACP_CLIS 列表
正常流程(用户视角):
settings.agentManagement.localAgentsDescription),末尾有"识别自定义 Agent"链接按钮(i18n key: settings.agentManagement.detectCustomAgent)settings.agentManagement.detected)/settings/aionrs 和 /settings/geminisettings.agentManagement.settingsDisabledHint)异常情况:
getAvailableAgents 失败或返回 success === false:列表为空,显示空状态文案(i18n key: settings.agentManagement.localAgentsEmpty)技术说明:
ipcBridge.acpConversation.getAvailableAgents.invoke() → 主进程 AgentRegistrybackend === 'remote'、backend === 'custom'、isPreset === true(LocalAgents.tsx:30)。注意此过滤规则与 useDetectedAgents hook 不同——后者仅排除 isPreset 和 remote,不排除 custom(详见 F-CAGENT-15)acp.agents.available.settingsresolveAgentLogo 映射 → fallback emoji '🤖'验收标准:
getAvailableAgents 返回空数组)用户故事:作为用户,我希望看到我手动添加的自定义 Agent 列表,包括名称、命令、启用状态,以便管理。
正常流程(用户视角):
settings.agentManagement.customAgents)color-fill-2,无 emoji 时透明背景 + Robot 图标defaultCliPath + acpArgs 空格拼接),单行截断enabled !== false,即 undefined 和 true 均视为开启)异常情况:
技术说明:
ConfigStorage.get('acp.customAgents') — 渲染进程本地读取acp.customAgents.settingsAcpBackendConfig[]验收标准:
enabled 字段一致用户故事:作为用户,我希望通过表单添加一个新的自定义 Agent,配置其 CLI 命令和参数后保存使用。
正常流程(用户视角):
settings.agentManagement.detectCustomAgent(中文参考:识别自定义 Agent),右上角有关闭按钮settings.agentNamePlaceholder(中文参考:请输入代理名称)settings.commandPlaceholder(中文参考:例如 my-agent 或 /usr/local/bin/my-agent)settings.argsPlaceholder(中文参考:例如 --acp --verbose)AcpBackendConfig 对象ConfigStorage('acp.customAgents'),追加到列表末尾异常情况:
验收标准:
settings.agentManagement.detectCustomAgentInlineAgentEditor.tsx:221)用户故事:作为用户,我希望修改已有 custom agent 的配置(如名称、命令、参数等)。
正常流程(用户视角):
settings.agentManagement.editCustomAgent(中文参考:编辑自定义 Agent)defaultCliPathacpArgs 以空格拼接env 对象转为 key-value 列表InlineAgentEditor.tsx:131——setShowAdvanced(false) 在 agent effect 中执行)异常情况:
agent.acpArgs 为 undefined:参数字段显示为空agent.env 为 undefined 或空对象:环境变量列表为空验收标准:
settings.agentManagement.editCustomAgent用户故事:作为用户,我希望为自定义 Agent 选择一个 emoji 头像,以便在列表中快速识别。
正常流程(用户视角):
异常情况:无
验收标准:
用户故事:作为用户,我希望输入自定义 Agent 的名称和 CLI 命令,这是配置 Agent 的最基本信息。
正常流程(用户视角):
settings.agentDisplayName(中文参考:显示名称)settings.agentNamePlaceholdersettings.commandLabel(中文参考:命令)settings.commandPlaceholdersettings.commandHelp(中文参考:运行 agent CLI 的可执行命令)按钮禁用逻辑(InlineAgentEditor.tsx:221-222):
| 条件 | 保存按钮 | 测试连接按钮 |
|---|---|---|
| 名称为空 AND 命令为空 | disabled | disabled |
| 名称有值 AND 命令为空 | disabled | disabled |
| 名称为空 AND 命令有值 | disabled | enabled |
| 名称有值 AND 命令有值 | enabled | enabled |
异常情况:
验收标准:
用户故事:作为用户,我希望为 CLI 命令配置额外的启动参数(如 --acp、--verbose)。
正常流程(用户视角):
settings.argsLabel(中文参考:参数)settings.argsPlaceholder(中文参考:例如 --acp --verbose)settings.argsHelp(中文参考:传递给命令的空格分隔参数)解析规则(parseArgsString,InlineAgentEditor.tsx:34-59):
异常情况:
acpArgs 设为 undefined(不传空数组)验收标准:
用户故事:作为用户,我希望为 Agent CLI 进程配置自定义环境变量(如 API Key、DEBUG 开关等)。
正常流程(用户视角):
settings.envLabel(中文参考:环境变量)settings.addEnvVar),底部追加一行:Key 输入框 + Value 输入框 + 删除按钮异常情况:
env 设为 undefined(不传空对象)验收标准:
用户故事:作为高级用户,我希望直接编辑 Agent 配置的 JSON 源码,方便批量修改或精确控制配置。
正常流程(用户视角):
settings.advancedMode),创建和编辑模式下均默认收起(InlineAgentEditor.tsx:131——setShowAdvanced(false)){
"name": "TestAgent",
"defaultCliPath": "bun",
"enabled": true,
"acpArgs": ["run", ".../agent.ts", "acp"],
"env": {}
}
avatar 字段不在 JSON 中——Emoji 头像仅通过 EmojiPicker 设置异常情况:
enabled 字段完全不被 handleSubmit 读取(InlineAgentEditor.tsx:214——enabled: agent?.enabled !== false)。新建时 enabled 固定为 true(undefined !== false → true);编辑时保留 props 传入的原值isJsonEditingRef 竞争窗口)技术说明:
isJsonEditingRef 控制:JSON 编辑后 500ms(setTimeout)自动切回表单主导验收标准:
建议验证策略:Step 1 CLI 检测可通过单元测试覆盖;Step 2 ACP 连接通过集成测试(mock ProcessAcpClient);E2E 验证 UI 状态切换(有 fake agent 可触发三种结果)
用户故事:作为用户,我希望在保存前测试 Agent 的连接是否正常,确认 CLI 命令可用且 ACP 协议能正常工作。
前置条件:命令字段已填写(非空)
正常流程(用户视角):
settings.testConnectionBtn)settings.testConnectionTesting 文案)which(macOS/Linux)或 where(Windows)检查 command.split(' ')[0] 是否存在(超时 5 秒,execFileSync + timeout: 5000)ProcessAcpClient.start() 启动并连接 CLI 进程(内部包含进程 spawn 和 ACP 协议初始化),成功后调用 client.close() 清理role="alert" + aria-live="assertive"):| 结果 | Alert 类型 | 图标 | i18n Key | 当前中文参考 |
|---|---|---|---|---|
| 成功 | success(绿色) | CheckOne | settings.testConnectionSuccess | 连接成功!CLI 存在且 ACP 协议正常工作。 |
| CLI 未找到 | error(红色) | CloseOne | settings.testConnectionFailCli | 未找到命令。请确保已安装并在 PATH 中。 |
| ACP 初始化失败 | warning(黄色) | CloseOne | settings.testConnectionFailAcp | 找到 CLI 但 ACP 初始化失败。 |
异常情况:
InlineAgentEditor.tsx:201-203)技术说明:
acpConversation.testCustomAgent.invoke({ command, acpArgs?, env? }) → 主进程 testCustomAgentConnection()execFileSync(同步阻塞),timeout 5000msProcessAcpClient + spawnGenericBackend('custom', ...),工作目录为 os.tmpdir()验收标准:
用户故事:作为用户,我希望保存自定义 Agent 配置后立即在列表中看到更新。
正常流程(用户视角):
common.save)AcpBackendConfig 对象:
id = uuid(),编辑时保留原 idenabled:新建时固定为 true;编辑时保留 props 传入的原值(agent?.enabled !== false)acpArgs:解析参数字符串,为空则 undefinedenv:转换 key-value 列表,为空则 undefined异常情况:
技术说明:
InlineAgentEditor.tsx:206-219)重新构建 AcpBackendConfig 对象,仅包含表单暴露的 5 个字段(name, avatar, defaultCliPath, acpArgs, env)+ id + enabled。JSON 高级编辑器中手动添加的额外字段会被丢弃——详见附录 D验收标准:
用户故事:作为用户,我希望删除不再需要的自定义 Agent。
正常流程(用户视角):
异常情况:
已知局限:
验收标准:
用户故事:作为用户,我希望临时禁用某个自定义 Agent 而不删除它,需要时再启用。
正常流程(用户视角):
enabled !== false)onChange 回调传入切换后的目标 enabled 值(非取反逻辑),直接写入 ConfigStorageenabled 字段异常情况:
updatedAgents.some() 阻止写入useCustomAgentsLoader 通过 availableCustomAgentIds 过滤,禁用的 agent 不会出现在对话选择中验收标准:
建议验证策略:检测逻辑为后端实现,E2E 仅验证已检测列表的显示结果
用户故事:作为用户,我希望系统能自动检测本地安装的 Agent CLI 工具,无需手动配置即可使用。
正常流程(用户视角):
which 命令检测是否安装DetectedAgent 类型返回,包含 kind-specific 字段useDetectedAgents hook 提供 refreshAgentDetection() 方法可触发重新扫描检测范围:
ACP_BACKENDS_ALL 中 enabled=true 且有 cliCommand 的后端(排除 custom)DetectedAgentKind,detectedAgent.ts:27):gemini、acp、remote、aionrs、openclaw-gateway、nanobot。注意此类型与 ACP 协议层分类 AcpBackendAll(18 种 ACP 后端)是不同维度——DetectedAgentKind 区分执行引擎/通信协议,AcpBackendAll 区分具体 ACP CLI 产品过滤规则差异:
LocalAgents.tsx:30:排除 remote + custom + presetuseDetectedAgents.ts:26:仅排除 preset + remote(不排除 custom)——此 hook 用于后端选择器(如 AssistantEditDrawer),需要包含 custom 类型异常情况:
fetchDetectedAgents catch → [])refreshCustomAgents.invoke() 失败:静默忽略技术说明:
POTENTIAL_ACP_CLIS 使用 Proxy 延迟初始化,从 ACP_BACKENDS_ALL 自动生成,避免数据冗余和循环依赖验收标准:
建议验证策略:合并逻辑通过 React Testing Library mock ConfigStorage 和 IPC 覆盖;E2E 仅验证最终显示结果
用户故事:作为用户,我希望在新建对话选择 Agent 时看到所有可用的 custom agent,包括预设助手和扩展贡献的 agent。
正常流程(用户视角):
ConfigStorage('assistants') 中 isPreset === true)ConfigStorage('acp.customAgents') 中被 availableCustomAgentIds 过滤的条目)ipcBridge.extensions.getAssistants.invoke(),去重——已有 id 跳过)customAgentAvatarMap(id → avatar 映射)异常情况:
extensions.getAssistants.invoke() 失败:catch → 空数组,不影响其他数据loadCustomAgents 整体失败:console.error,不影响应用其他功能技术说明(两次加载机制):
useCustomAgentsLoader 内部有两个独立的 useEffect:
useCustomAgentsLoader.ts:73-75):仅读 ConfigStorage + extensions,触发条件为 loadCustomAgents 引用变化useCustomAgentsLoader.ts:88-90):调用 IPC refreshCustomAgents.invoke() → SWR mutate(DETECTED_AGENTS_SWR_KEY) → 重新读 ConfigStorage。触发条件为 refreshCustomAgents 引用变化两次加载可能导致列表短暂闪烁(已知局限)。
验收标准:
┌──────────────────────────────────────────────────────────────────┐
│ 渲染进程 (Renderer) │
│ │
│ LocalAgents │
│ ├─ ConfigStorage.get('acp.customAgents') → 本地读取 │
│ ├─ ConfigStorage.set('acp.customAgents', [...])→ 本地写入 │
│ └─ ipcBridge.acpConversation │
│ .getAvailableAgents.invoke() → IPC invoke │
│ │
│ InlineAgentEditor │
│ └─ acpConversation │
│ .testCustomAgent.invoke({...}) → IPC invoke │
│ │
│ useDetectedAgents │
│ ├─ acpConversation.getAvailableAgents.invoke() → IPC invoke │
│ └─ acpConversation.refreshCustomAgents.invoke()→ IPC invoke │
│ │
│ useCustomAgentsLoader │
│ ├─ ConfigStorage.get('assistants') → 本地读取 │
│ ├─ ConfigStorage.get('acp.customAgents') → 本地读取 │
│ ├─ extensions.getAssistants.invoke() → IPC invoke │
│ └─ acpConversation.refreshCustomAgents.invoke()→ IPC invoke │
└────────────────────────┬─────────────────────────────────────────┘
│ IPC Bridge
┌────────────────────────▼─────────────────────────────────────────┐
│ 主进程 (Main) │
│ │
│ testCustomAgentConnection.ts │
│ ├─ Step 1: execFileSync('which'/'where', [baseCmd]) — 5s 超时 │
│ └─ Step 2: ProcessAcpClient.start() → spawn + ACP init │
│ │
│ AgentRegistry (getAvailableAgents provider) │
│ └─ 扫描 POTENTIAL_ACP_CLIS + ConfigStorage custom agents │
│ │
│ refreshCustomAgents provider │
│ └─ 重新扫描并更新 agent 可用性 │
└──────────────────────────────────────────────────────────────────┘
以 i18n key 为权威列,中文文案为当前参考(以 i18n 翻译文件为准)
| 场景 | 组件 | 类型 | i18n Key | 当前中文参考 |
|---|---|---|---|---|
| 连接测试成功 | Alert (success) | 绿色 | settings.testConnectionSuccess | 连接成功!CLI 存在且 ACP 协议正常工作。 |
| CLI 检测失败 | Alert (error) | 红色 | settings.testConnectionFailCli | 未找到命令。请确保已安装并在 PATH 中。 |
| ACP 初始化失败 | Alert (warning) | 黄色 | settings.testConnectionFailAcp | 找到 CLI 但 ACP 初始化失败。 |
| JSON 解析失败 | 内联文本 | 红色 | 硬编码 | "Invalid JSON"(未 i18n) |
| # | 功能点 | 局限描述 |
|---|---|---|
| 1 | F-CAGENT-13 | 删除无确认对话框,不可撤销,存在误操作风险 |
| 2 | F-CAGENT-11 | ACP 连接步骤(Step 2)无超时机制,CLI 进程 hang 时测试永远不返回 |
| 3 | F-CAGENT-11 | 测试进行中关闭弹窗,后台进程不会被取消(设计约束,非 bug) |
| 4 | F-CAGENT-10 | "Invalid JSON" 错误信息未 i18n |
| 5 | F-CAGENT-10 | handleSubmit 重建对象时丢弃 JSON 高级编辑器中的额外字段(详见附录 D) |
| 6 | F-CAGENT-12 | ConfigStorage 读写操作无 try-catch,失败时弹窗可能异常 |
| 7 | F-CAGENT-07 | 名称和命令字段无最大长度限制 |
| 8 | F-CAGENT-09 | 环境变量无数量限制,无 Key/Value 格式校验 |
| 9 | F-CAGENT-08 | 未闭合引号静默处理,可能导致参数解析不符合用户预期 |
| 10 | F-CAGENT-16 | useEffect 双重加载(initial + refresh)可能导致列表短暂闪烁 |
AcpBackendConfig 接口(acpTypes.ts:124-302)包含 30+ 字段,但 Custom Agent 编辑器(InlineAgentEditor)仅暴露以下 5 个字段供用户配置:
| 表单字段 | AcpBackendConfig 字段 | 说明 |
|---|---|---|
| 显示名称 | name | 必填 |
| Avatar | avatar | emoji,仅通过 EmojiPicker 设置(不在 JSON 中) |
| 命令 | defaultCliPath | 必填 |
| 参数 | acpArgs | 可选,空格分隔解析为数组 |
| 环境变量 | env | 可选,key-value 对 |
以下字段由系统自动管理,不在编辑器中暴露:
id:新建时自动生成 uuid,编辑时保留原值enabled:通过列表中的 Switch 控制其余 AcpBackendConfig 字段(如 authRequired, supportsStreaming, skillsDirs, isPreset, context, models, enabledSkills 等)对 custom agent 不适用或不可配置。如果用户通过 JSON 高级编辑器手动添加这些字段,handleSubmit 会丢弃它们(重新构建对象仅包含上述字段)。
LocalAgents.tsx:104-132 包含一个 Agent Hub 市场入口横幅,仅在 process.env.NODE_ENV === 'development' 时渲染。生产环境用户不可见。该功能为开发中的 Agent 市场预留入口,不属于当前正式功能范围。