AgentManageRow 用一份写在前端的 npm Agent 清单决定要不要显示「更新」按钮。这份清单是 agents.lock.json 之外的第二份真源,已经漂移了——OpenClaw 上周合入后没人同步它。同一个按钮还不检查 Agent 是否已安装。
两份真源已经分叉
AgentManageRow.tsx:12-18:
const npmAgents = new Set(["codex", "claude-code", "opencode", "kilo-cli"]);
const npmPackages: Record<string, string> = {
codex: "@openai/codex",
"claude-code": "@anthropic-ai/claude-code",
opencode: "opencode-ai",
"kilo-cli": "@kilocode/cli",
};
对着 lock 核了一遍:
| Agent |
lock 里的 manager |
在 npmAgents 里 |
| codex |
npm |
✅ |
| claude-code |
npm |
✅ |
| opencode |
npm |
✅ |
| kilo-cli |
npm |
✅ |
| openclaw |
npm |
❌ 漏了 |
| aider |
uv |
不适用 |
OpenClaw 是 #53 加进来的可安装 npm Agent,但在管理页拿不到「更新」按钮,因为它不在这个 Set 里。npmPackages 同样缺它,所以详情区(AgentManageRow.tsx:263)也不显示包名。
后端根本不需要前端告诉它这些。 internal/app/update.go:26-32 自己从 lock 读,并且自己校验:
manifest, err := catalog.LoadEmbedded()
agent, ok := manifest.Agents[strings.TrimSpace(agentID)]
if !ok || agent.Package == nil || agent.Package.Manager != "npm" {
return AgentUpdateResult{}, oneerrors.New(oneerrors.InvalidRequest, "Agent is not npm-managed: "+agentID)
}
args := []string{npm, "update", "-g", agent.Package.Name}
包名也是从 lock 取的。所以前端这两个常量是纯粹的重复,且是会烂掉的那种重复:新增 npm Agent 时没有任何机制提醒你改它,CI 也不会红。
AgentCatalogItem 里已经有 configMode、protocol 这类字段从后端投影过来,多带一个"是否 npm 托管"和包名并不算扩张 DTO。
未安装也显示「更新」
AgentManageRow.tsx:214 的条件只有 npmAgents.has(agentId):
{npmAgents.has(agentId) ? (
<button ... onClick={() => void update()} title={t("执行 npm update")}>
同一个组件里 :137 明明算过安装状态,「启动」按钮也用了它(:229 的 canLaunch),但「更新」没用:
const canLaunch = status.installed;
所以一个装都没装的 Agent,界面上会给你一个「更新」按钮。点下去后端不会报错——npm update -g <没装过的包> 是空操作,会成功返回——于是任务中心显示「更新完成」,而那个 Agent 依然没装。这比报错更糟,因为它给了一个成功的假象。
后端也没有这道判断:update.go 只检查 manager == "npm" 和 npm 可用,不检查 Agent 是否已安装。
建议
- 让后端把「是否 npm 托管」和包名投影到
AgentCatalogItem,前端删掉 npmAgents 和 npmPackages。这样新增 Agent 只需要改 lock 一处。
- 「更新」按钮的显示条件加上
status.installed,与同组件里「启动」按钮的做法对齐。
UpdateAgent 里补一道未安装的前置检查,返回明确错误,而不是让 npm update -g 静默成功。第 2 条只挡住 UI,任务中心恢复的卡片和 CLI 仍可能打进来。
相关
与 #60 是同一类问题:界面按钮的可用性由一个与实际状态无关的条件决定。#60 是入口语义不看安装状态,这个是操作按钮不看安装状态,另外多了一层硬编码清单漂移。
AgentManageRow用一份写在前端的 npm Agent 清单决定要不要显示「更新」按钮。这份清单是agents.lock.json之外的第二份真源,已经漂移了——OpenClaw 上周合入后没人同步它。同一个按钮还不检查 Agent 是否已安装。两份真源已经分叉
AgentManageRow.tsx:12-18:对着 lock 核了一遍:
npmAgents里OpenClaw 是 #53 加进来的可安装 npm Agent,但在管理页拿不到「更新」按钮,因为它不在这个 Set 里。
npmPackages同样缺它,所以详情区(AgentManageRow.tsx:263)也不显示包名。后端根本不需要前端告诉它这些。
internal/app/update.go:26-32自己从 lock 读,并且自己校验:包名也是从 lock 取的。所以前端这两个常量是纯粹的重复,且是会烂掉的那种重复:新增 npm Agent 时没有任何机制提醒你改它,CI 也不会红。
AgentCatalogItem里已经有configMode、protocol这类字段从后端投影过来,多带一个"是否 npm 托管"和包名并不算扩张 DTO。未安装也显示「更新」
AgentManageRow.tsx:214的条件只有npmAgents.has(agentId):同一个组件里
:137明明算过安装状态,「启动」按钮也用了它(:229的canLaunch),但「更新」没用:所以一个装都没装的 Agent,界面上会给你一个「更新」按钮。点下去后端不会报错——
npm update -g <没装过的包>是空操作,会成功返回——于是任务中心显示「更新完成」,而那个 Agent 依然没装。这比报错更糟,因为它给了一个成功的假象。后端也没有这道判断:
update.go只检查manager == "npm"和 npm 可用,不检查 Agent 是否已安装。建议
AgentCatalogItem,前端删掉npmAgents和npmPackages。这样新增 Agent 只需要改 lock 一处。status.installed,与同组件里「启动」按钮的做法对齐。UpdateAgent里补一道未安装的前置检查,返回明确错误,而不是让npm update -g静默成功。第 2 条只挡住 UI,任务中心恢复的卡片和 CLI 仍可能打进来。相关
与 #60 是同一类问题:界面按钮的可用性由一个与实际状态无关的条件决定。#60 是入口语义不看安装状态,这个是操作按钮不看安装状态,另外多了一层硬编码清单漂移。