插件装了却"只有一半能用":Hermes 终于统一了插件名字解析

你打开 Hermes Desktop 的插件中心,装了一个自带桌面面板的插件。Agent 那一半的开关亮了,可它右边那个面板一直不动——你去翻目录,发现工具有了、命令有了,就是界面没出现。或者更简单的一种:hermes plugins install hermes-memory-wiki 提示装好了,你接着敲 hermes plugins enable hermes-memory-wiki,它回你一句 No plugin named hermes-memory-wiki。你已经装完了,它却说不认识这个名字。这不是你记错了名字,是 Hermes 内部同时存在三个互相不一致的名字解析器。10 月 8 日合并的 PR #135030 把这件事收干净了。
症状:同一个插件,三个名字
先把症状列清楚,因为这三种报错看起来毫不相关,其实是同一个根因:
- 装得上,管不动。 目录里的安装名(catalog name)和插件自己清单里写的名字(manifest name)可以不一样。官方列出的数据很具体:484 个目录条目里有 17 个装下来的清单名与目录名不同,比如
hermes-memory-wiki→memory-wiki、touchdesigner→td、hermes-subscription-meter→subscription-meter。install认目录名,所以成功;但enable、disable、show、remove、update在那之前认的是清单名或 key,于是同一个名字在这几个动作上全部失败。 - 一个包显示成两行。 如果你按插件 README 的建议自己 clone 到
~/.hermes/plugins/hermes-subscription-meter,桌面插件中心会把它渲染成两行:一行是孤零零的桌面半边,提示你给它“Install here”,可它的 Agent 半边其实早就装好了;另一行才是 Agent。原因是桌面端拿清单名去和 Agent 那一行配对,却拿文件夹名去找桌面那一半。 - 开关各管一边。 桌面半边出厂是 opt-in 的(代码里就是“有桌面标记时默认不启用”),所以你把 Agent 开关打开,不会顺带把桌面那一半点亮。
这三件事的共同点:名字在四个地方被各自解析,而且规则还不一样。
原因:五个解析器,各认一套名字
改动前的实现里,解析名字的地方有五个,分属三条路径:
| 位置 | 认什么名字 |
|---|---|
hermes_cli/plugins_cmd.py 的 _resolve_plugin_key_and_source(以及 _find_plugin_entry、_require_installed_plugin、_user_installed_plugin_dir 几处副本) |
清单名 / 规范 key / 唯一的叶子名 / 文件夹名,而且每一处支持的子集都不同 |
plugins_cmd_install.py 的 _catalog_install_on_disk |
目录名,但只在安装那一刻用得上 |
桌面端 plugin-packages.ts 与 electron/desktop-plugins-root.ts |
一个按清单名配对,另一个按文件夹的 basename 配对 |
“每一处支持的子集都不同”才是真正致命的:不是“名字不好使”,而是同一条命令序列里,前一步和下一步认的规矩根本不一样。所以你会看到 install 过了、enable 挂了这种组合。
现在:一个解析器,一个开关
改动之后:
_find_plugin_entry是唯一的名字解析器,所有动词(enable / disable / show / capabilities / remove / update,以及 Dashboard 和桌面插件中心)都走它。它的顺序是:先试规范 key 或清单名 → 再试唯一的叶子名(langfuse→observability/langfuse)→ 最后试“安装时用过的目录名”。而且它有一条明确承诺:永远不在两个候选之间猜(歧义就返回失败,而不是随便挑一个)。- 目录名的匹配读的是安装器自己写的记录(
.install-metadata.json),不是插件仓库里的任何文件。这一点很重要:一个用 URL 装进来的陌生仓库,不能靠自己仓库里的文件“冒充”某个官方目录条目的名字。 - 一次开关就能开一个包。 安装对话框里的 “Enable agent plugin after install”(或那一行的 Agent 开关)在打开时,会把它的桌面半边也一起打开——除非你自己单独把桌面半边关过。反向不成立:关掉是按半边算的,因为桌面半边是所有 profile 共享的。
对应的命令行例子,注意两个名字现在都认:
# 目录名和清单名不一致的经典例子:touchdesigner 装下来叫 td
hermes plugins install touchdesigner
hermes plugins enable touchdesigner # 等价于 hermes plugins enable td
# 装的时候就把开关打开,省掉第二步
hermes plugins install touchdesigner --enable
之前只有 hermes plugins install <name> 能用目录名,其余动词报错;现在 enable / disable / show / capabilities / update / remove 都接受目录名或清单名中的任意一个。我们站内的 hermes plugins 命令参考 和插件目录那篇可以配合着看——前者是命令清单,后者讲目录条目的信任模型。
顺带:桌面插件的“改代码不生效”也修了
同一批合并里还有 PR #135017,解决的是开发者视角的同类问题:你改了 desktop/plugin.js,桌面端跑的却还是那份拷贝。
Electron 会把 plugins/<name>/desktop/ 拷到 desktop-plugins/<name>/,渲染进程加载的是拷贝,而且只有在“源文件 mtime 更新了、并且有人主动触发”时才重新拷贝;热重载监听的是拷贝,从来不是源文件。更尴尬的是:Python 加载器认识 plugins/ 下的符号链接,但负责拷贝的那段代码用 isDirectory() 列包,符号链接不是目录,于是“用符号链接做开发”这条最自然的路,桌面半边压根不会被拷过去。
现在的行为:
# 开发时把这行当成"开发模式"
ln -s ~/src/my-plugin ~/.hermes/plugins/my-plugin
# 装的是普通拷贝、但你在原地改源码时,让 doctor 告诉你是不是在跑旧拷贝
hermes plugins doctor my-plugin
符号链接的包现在会被识别(标记里带 linked: true),拷贝改成按字节差异同步(不再靠 mtime,git stash pop 之后也能正确同步),并且会监听源文件、存盘即热重载。普通安装的包维持 mtime 规则不变。hermes plugins doctor <id> 现在会明确告诉你“桌面端跑的是一份旧拷贝”,指出是哪一份,并给出刷新方式(重新扫描或重启)。
状态:在 main 上,还没进任何 tag
要说清楚一件事:#135030、#135017、#135033 都是 10 月 8 日合并进 main 的,没有进 v0.21.6 这个 tag(tag 的切点在它们之前)。所以你现在从 main 装的 git 版本能用上新解析器,跑 tag 的安装(含 Docker 镜像)还没有。判断方法就是上一条命令:hermes plugins enable touchdesigner 能过,就是新代码;报 No plugin named,就是旧代码。
小结
- 症状是“装得上、管不动”和“桌面半边不亮”,根因是五个解析器各认一套名字。
- 现在只有一个解析器
_find_plugin_entry,顺序是 key/清单名 → 唯一叶子名 → 安装记录里的目录名,且歧义时直接失败、不猜。 - 目录名走安装器自己的记录,陌生仓库无法冒充目录条目的名字。
- 一个包一次开关;关掉仍按半边算,因为桌面半边是所有 profile 共享的。
- 桌面插件开发用符号链接现在真正生效,
hermes plugins doctor会指出旧拷贝。
如果你的插件本来就装得顺、也没在做桌面插件,这次改动对你是“行为更可预期”,没有需要迁移的配置。想继续往深里看,可以接着读插件隔离到宿主进程那篇和可移植插件的 MCP 信任门禁——那两篇管的是“代码跑在哪、能碰什么”,这篇管的是“它到底叫什么名字”。