P32 32_进化篇_插件功能
P32 · 32_进化篇_插件功能(7:27)
课时概要
进化篇 · 插件功能。Hermes 的插件体系:运行时扩展 + 插件目录(plugin-catalog)安装。官方文档有 Plugins 与 Plugin catalog 两页。
本分P 没有逐P 的官方简介(视频简介只有资源链接)。以下「本节要点 / 关键概念 / 代码实操」均改写自 Hermes 官方文档 Plugins 页(含 capability 与同意 / plugin catalog / plugin packs / 安装期安全扫描四节)与官方仓库原文并标注来源;「我的收获」里标 本机实测 的是在本机实跑得到的真实状态。
视频:32_进化篇_插件功能 | 时长 7:27 | P32
本节要点
- 插件的最小结构(官方原文):
~/.hermes/plugins/my-plugin/下放plugin.yaml(manifest)+ Python 代码(__init__.py里的register(),可再拆schemas.py/tools.py)→ 启动 Hermes,你的工具就出现在内置工具旁边,模型立刻能调用。 - 官方给了一个完整的最小可用插件:一个
hello_world工具 + 一个记录所有工具调用的钩子。核心就两行注册:ctx.register_tool(name=…, toolset=…, schema=…, handler=…)与ctx.register_hook("post_tool_call", callback)。 ⚠️ 官方特别说明:面向模型的工具描述写在schema["description"];ctx.register_tool(description=…)是另一套ToolEntry注册表元数据,它不会被回填进没有 description 的 schema——所以「只在一处定义这段文字」最稳。 - 项目级插件默认禁用:
./.hermes/plugins/下的插件默认关闭,只对可信仓库用HERMES_ENABLE_PROJECT_PLUGINS=true打开。 - 插件能做什么(官方能力表,21 项):加工具 | 加钩子 | 加斜杠命令(CLI 与 gateway 都能用)| 从命令派发工具(
ctx.dispatch_tool,自动接上父 agent 上下文)| 加 CLI 子命令(hermes <plugin> <subcommand>)| 注入消息 | 携带数据文件 | 打包技能(ctx.register_skill,命名空间plugin:skill)| 用requires_env声明所需环境变量(安装时提示)| 通过 pipentry_points分发 | 注册 gateway 平台 | 注册生图后端 | 注册视频生成后端 | 注册上下文压缩引擎 | 注册终端执行后端(云沙箱)| 路由人工审批提示 | 注册记忆后端(用单独的一套发现系统)| 宿主侧 LLM 调用(ctx.llm.complete,借用用户当前模型与认证)| 调用 MCP 工具(能力门控)| 注册推理后端(LLM provider profile)。 - 发现来源有五种(后者覆盖前者,所以同名用户插件会替换 bundled):
<repo>/plugins/(bundled,随 Hermes 发布)|~/.hermes/plugins/(user,个人插件)|.hermes/plugins/(project,需上述开关)| pip 的hermes_agent.pluginsentry_points | Nix 声明式(services.hermes-agent.extraPlugins)。 - 子分类目录会路由到专门的发现系统(官方表):根
plugins/= 通用插件(工具/钩子/斜杠命令/CLI 命令/打包技能)|plugins/platforms/<name>/= gateway 频道适配器 |plugins/image_gen/<name>/= 生图后端 |plugins/memory/<name>/= 记忆 provider(有自己独立的 loader,kindexclusive—— 同时只能有一个激活) |plugins/context_engine/<name>/= 上下文压缩引擎(同时一个)|plugins/model-providers/<name>/= LLM provider profile(惰性扫描)。 ⚠️ 两处同名冲突的裁决方向是相反的:user 的plugins/model-providers/<name>/覆盖 bundled 同名(后写者赢,所以你能不改仓库就替换内置 provider);而plugins/memory/<name>/是 bundled 赢(顺序 bundled → user → project → entry points,先见者赢)→ 自建记忆 provider 必须起一个唯一的名字。 - 插件默认是 opt-in(官方原文):通用插件与用户安装的后端默认禁用 —— 发现机制能找到它们(所以它们出现在
hermes plugins与/plugins里),但在你把名字加进~/.hermes/config.yaml的plugins.enabled之前,任何带钩子或工具的东西都不会加载。这挡住了第三方代码在你未明确同意的情况下运行。 config.yaml的plugins:块(官方原文的关键键):enabled(allow-list)|disabled(可选 deny-list,同一个名字同时出现在两边时 disabled 赢)|clone_timeout_seconds: 300(每次 git clone/fetch/checkout 的期限)|hook_callback_timeout: 30(超时受限的进程内 Python 钩子回调;超时的pre_tool_call回调 fail closed 即阻断工具;subagent_stop这类调用线程钩子永不被搬到超时 worker 上)|load_timeout_seconds: 10(单个插件的 import+register 期限,超了就跳过它并报load timed out after Ns,其余插件继续加载,卡住的线程被放弃)。- 翻转状态的三种方式:
hermes plugins(交互勾选,空格切换)|hermes plugins enable <name>(加进 allow-list)|hermes plugins disable <name>(从 allow-list 移除 + 加进 disabled)。hermes plugins install owner/repo之后会问Enable 'name' now? [y/N],默认是否;脚本化安装用--enable/--no-enable跳过提问。 - 可复现安装(钉 commit):
hermes plugins install owner/repo --ref <完整40位commit sha>—— 官方明确说 tag / 分支 / 缩写 SHA 都不接受。它会 detached checkout 并校验 HEAD 与该 SHA 完全相等,并把规范来源、安装修订、钉住状态记进当前 profile。被钉住的插件hermes plugins update拒绝移动,要显式--force --ref <新commit>。Desktop 的 Skills → Plugins → Install from Git 也有 Pin to commit 字段,列表上显示pinned @ <sha8>徽章。 - 私库安装的凭证解析顺序(官方原文,值得记):每次 clone 都先匿名试(公库永远看不到你的凭证,所以过期 token 不会弄坏公库安装),只有远端拒绝匿名访问时才找凭证 —— 顺序是 ①
GITHUB_TOKEN或GH_TOKEN(仅 GitHub host)②ghCLI 的登录(仅 GitHub)③ 你的 git credential helper(git credential fill,对 GitLab / Bitbucket / 自建都有效)。凭证只作为该次安装/更新的一次性 HTTP header 发送,绝不写进插件的.git/config或安装元数据;SSH 源(git@host:owner/repo.git)照旧走 ssh-agent。 - ⚠️
plugins.enabled管不到哪些东西(官方的例外表 —— 因为它们属于 Hermes 内置面,被门控会让基本功能坏掉):bundled 平台插件(自动加载,频道本身靠gateway.platforms.<name>.enabled开)| bundled 后端(生图等,靠<category>.provider选,如image_gen.provider: openai)| 记忆 provider(全部被发现,正好一个激活,由memory.provider选) | 上下文引擎(由context.engine选)| 模型 provider(由--provider或 config 选)。另外 gateway 事件钩子(~/.hermes/hooks/)也不是插件、不受它管。 - 插件能力与同意(consent):插件可在
plugin.yaml声明capabilities:(如tools.override替换内置工具、llm.model_override选宿主 LLM 调用的模型)。安装或启用时会列出每个能力的一行风险说明并问一次;同意会把授予记录在plugins.entries.<id>.granted_capabilities(连同 consent hash 与时间戳);拒绝则插件仍启用、但那些能力是关的(规范插件会用ctx.has_capability()探测并优雅降级)。 - · 更新要重新同意:若更新声明了你没授予的能力,
hermes plugins update会展示新增项并再问一次 —— 新能力在你同意前一直关闭,插件更新永远不能静默扩权。 · 目录重钉更严:新 pin 若新增了工具、钩子、Python 依赖、宿主能力或桌面 UI 半边,CLI 会显示 delta 并问y/N才动,拒绝(或非交互会话)就留在旧 pin。 · ⚠️ 非交互会话 fail closed:没有 TTY 时安装/更新能完成,但声明的能力不会被授予 —— 之后要交互式跑hermes plugins enable <id>才补上。 · 查看:hermes plugins capabilities [<id>](声明 vs 已授予)。 · ⚠️ 官方红字:能力不是沙箱,是同意与审计层。插件就是普通进程内 Python,恶意插件可以无视这里的每一个门。授予能力是「对插件作者的信任声明」,不是代码审计,Hermes 也没审过该插件的代码。 - platform actions(
ctx.platform_actions):给插件一套最小、且受能力门控的动作动词,通过活的 gateway adapter 注册表操作已连接的聊天平台 —— 这是官方认可的、替代「猴补适配器」的做法。默认关闭,每次调用都复查gateway.platform_actions能力,未授予时返回结构化错误而不是执行。v1 只有 Telegram 与 Discord,两个动词:add_reaction与set_thread_title;成功是{ok: True, action: …},失败是{ok: False, error: <稳定错误码>, detail: …}。 ⚠️ 官方安全提醒:这是**「以 bot 身份发消息」的权力** —— 被授予的插件可以在 gateway bot 能触达的任何聊天里加反应、改线程名,不只是触发钩子的那个聊天。所以只授予你信任的插件。 - 插件目录(catalog):
hermes plugins search <term>搜的是 hermes-agent 仓库里维护的 curated、SHA 钉住的目录(plugin-catalog/),匹配范围覆盖条目名、描述与声明的工具;hermes plugins browse看全部条目、hermes plugins info <name>看单个详情。找到后用裸名安装即可(名字会解析到该条目仓库的固定 commit SHA)。 - 插件包(plugin packs):
hermes-pack.yaml是一个声明式、可分享的 YAML(官方比喻:像分享 modpack),钉住一组插件。hermes plugins pack show / install / export [--enabled-only]。 · 供应链姿态:每个条目的ref必须是完整 40 位 commit SHA(tag 与分支名会被拒绝并点名该条目)—— 与目录同一规则;包安装走与hermes plugins install --ref <sha>完全相同的钉住安装路径,并在plugins/.install-metadata.json记录同样的来源信息。 · 同意从不批量授予:包安装先显示强制 review 屏(每个插件、来源、钉住的 ref、它声明的能力),然后就包内容问一次确认;之后每个插件声明的能力仍走标准的逐插件同意提示。没有--yes,且非交互会话不能安装包。 · 密钥永不随包传输:config:种子只允许非密钥的plugins.entries.<id>键 —— 密钥形状的键名(*token*、*key*、*password*…)、能力授予、以及废弃的allow_*信任门在安装时被拒绝、导出时被剥离;用户的既有值总是盖过包里的种子。 · 部分失败:每个插件独立安装,失败按插件报告、其余继续,只要有插件失败命令就退非零。 - 安装期安全扫描:每次
hermes plugins install与update都会在激活之前对插件树跑一次静态安全扫描 —— 复用与 Skills Hub 守卫同一套威胁模式引擎(凭证库外泄、反弹 shell、破坏性命令、持久化机制、混淆执行、文档里的提示注入),但带插件感知豁免(provider 插件按文档化的requires_env模式读自己的 API key 不算问题)。 · 三种裁决(对齐 Cowork 的 pass/warn/fail):safe正常安装无额外输出 |caution显示发现并要求你确认Install anyway? [y/N](或传--force)|dangerous直接阻断,--force也覆盖不了。 · 更新时出现dangerous→ 插件被禁用,直到你复查发现并重新启用;阻断会点名导致它的关键发现(如1 critical of 42 findings (destructive_root_rm)),不会被总数盖住。 · 文档文本算「上下文」不算「行为」:README.md/AGENTS.md/docs/**/.txt/.rst/.html里引用的命令或凭证路径降一级(README 里删自己插件目录的rm -rf "$HOME/.hermes/plugins/<name>"只算 note);但面向 agent 的形态保持满severity(提示注入、Markdown 外泄、agent 配置改动、curl … | sh一行流、追加authorized_keys、泄漏的 provider key),且 bundledskills/树与after-install.md也是满severity(agent 会把它们当指令读)。每条发现都留在报告里带文件与行号。 - 三种状态:enabled / disabled / not enabled(官方表):
enabled= 下次会话加载(在plugins.enabled)|disabled= 显式关闭,即便也出现在enabled里也不加载(在plugins.disabled)|not enabled= 被发现但从未 opt-in(两边都不在)。新安装或 bundled 插件的默认状态就是not enabled。hermes plugins list会把三者显示成不同的状态,好让你分清「被显式关掉」和「只是还没启用」;运行中的会话用/plugins看当前实际加载了哪些。 - 交互 UI:不带参数跑
hermes plugins会开一个复合界面 —— General Plugins 段是复选框(空格切换:勾上=进plugins.enabled,取消=进plugins.disabled);Provider Plugins 段显示当前选择,按 ENTER 进单选选择器挑一个激活的 provider;bundled 插件带[bundled]标签。provider 的选择写进config.yaml(memory.provider/context.engine,空字符串 = 只用内置)。 - 注入消息(
ctx.inject_message(content, role="user", session_key=…)):CLI 里若 agent 空闲(在等输入)就把消息排成下一个输入并开新回合;若 agent 正在回合中,消息会打断当前操作 —— 等同用户输入一条新消息并按回车。非"user"角色会加[role]前缀(如[system] …)。gateway 模式下session_key必填,且必须是稳定的路由键(不是 CLI 会话 ID);插件不能通过这个 API 提供新的聊天路由;Hermes 会在派发前按当前授权规则复查存储的路由;注入的文本永远是对话输入 —— 它不能调斜杠命令、不能批准工具、也不能解决待确认/待澄清的提示。
关键概念
- plugin.yaml +
register(ctx):插件的两个必备件 —— manifest 与注册入口。 ctx.register_tool/ctx.register_hook:往里加工具与钩子(钩子事件与 Shell/Plugin hooks 共用一套VALID_HOOKS)。plugins.enabled/plugins.disabled/not enabled:三态;disabled 赢;默认是not enabled。- capabilities + consent hash:插件声明特权能力、安装/启用时逐项同意、更新要重新同意(不能静默扩权)。
- plugin catalog(
plugin-catalog/):仓库里 SHA 钉住的 curated 目录,search覆盖名字/描述/声明的工具。 - plugin pack(
hermes-pack.yaml):钉一组插件的可分享声明文件;ref 必须完整 commit SHA;同意不批量授予。 - 安装期安全扫描:safe / caution / dangerous 三裁决;dangerous 连
--force都挡。 - 发现来源五级 + 子分类目录:bundled → user → project → pip → Nix;
memory/与model-providers/的冲突裁决方向相反。 ctx.inject_message:把消息注入正在进行的对话(空闲则排队,忙则打断)。- platform actions:受能力门控的最小平台动词集(v1 Telegram/Discord 两个动词)。
代码 / 实操
官方的最小可用插件(完整两文件):
yaml
# ~/.hermes/plugins/hello-world/plugin.yaml
name: hello-world
version: "1.0"
description: A minimal example pluginpython
# ~/.hermes/plugins/hello-world/__init__.py
import json
def register(ctx):
# --- 工具:hello_world ---
schema = {
"name": "hello_world",
"description": "Returns a friendly greeting for the given name.",
"parameters": {
"type": "object",
"properties": {"name": {"type": "string", "description": "Name to greet"}},
"required": ["name"],
},
}
def handle_hello(params, **kwargs):
name = params.get("name", "World")
return json.dumps({"success": True, "greeting": f"Hello, {name}!"})
ctx.register_tool(name="hello_world", toolset="hello_world", schema=schema, handler=handle_hello)
# --- 钩子:记录每次工具调用 ---
def on_tool_call(tool_name, params, result):
print(f"[hello-world] tool called: {tool_name}")
ctx.register_hook("post_tool_call", on_tool_call)config.yaml 的 plugins 块(官方原文):
yaml
plugins:
enabled:
- my-tool-plugin
disabled: # deny-list:同时在两边时它赢
- noisy-plugin
clone_timeout_seconds: 300 # git clone/fetch/checkout 期限(默认 300,>3600 夹到 3600)
hook_callback_timeout: 30 # 热路径钩子回调;超时的 pre_tool_call 会 fail closed 阻断
load_timeout_seconds: 10 # 单插件 import+register 期限;超时跳过它,其余继续能力声明 + 钉 commit 安装:
yaml
# plugin.yaml
capabilities:
- tools.override # 替换内置工具
- llm.model_override # 为宿主 LLM 调用选模型bash
hermes plugins install owner/repo --ref 0123456789abcdef0123456789abcdef01234567 # 必须完整 40 位
hermes plugins capabilities my-plugin # 声明 vs 已授予
hermes plugins search telegram # 搜 curated 目录
hermes plugins pack show ./hermes-pack.yaml # 包:先 dry-run 审阅
hermes plugins # 交互开关(空格勾选 / ENTER 选 provider)本机实测(2026-09-25):
$ ls ~/.hermes/plugins/
echomind/ (git 来源,v1.0.8,5/15 装)
orca-status/ (user 来源,v1.0.0,7/1 装)
$ hermes plugins list → 61 条条目:59 bundled + 1 user + 1 git | 54 enabled / 7 not enabled
$ config.yaml → plugins.enabled: [orca-status] (只有这一项)
我的收获
用户此前已学过本模块;以下是本机实测状态(2026-09-25):
- 本机
~/.hermes/plugins/实测只装了 2 个插件:echomind(git 来源,v1.0.8,5/15 装,plugin.yaml声明hermes: type: memory_provider+entry: adapters.hermes_provider,目录里确实有adapters/hermes_provider.py)与orca-status(user 来源,v1.0.0,7/1 装,kind: standalone,声明了 10 个钩子:on_session_start / pre_llm_call / post_llm_call / pre_tool_call / post_tool_call / pre_approval_request / post_approval_response / on_session_end / on_session_finalize / on_session_reset)。 hermes plugins list实测 61 条插件条目:59 bundled + 1 user(orca-status)+ 1 git(echomind),状态 54 enabled / 7 not enabled。也就是说本机没往模型视野里引入任何第三方工具面,54 个 enabled 都是平台插件/后端那类自动加载的内置件。- 7 个 not enabled 的完整名单(实测):
chronos、disk-cleanup、google_meet、langfuse、security-guidance、teams_pipeline(都是 bundled,属于「要靠配置选」的那类)+echomind(git)。 - ⭐ 本机正好是官方「
plugins.enabled管不到什么」那张表的活样本:config.yaml的plugins.enabled里只有orca-status,而echomind是记忆 provider 型 —— 按官方表,记忆 provider 全部被发现、由memory.provider选一个激活,不受plugins.enabled管。而本机memory.provider是空字符串(= 只用内置)→ echomind 既没进 enabled、也没被选为激活 provider,处于「装了但两边都不生效」的悬空状态(5/15 装、已闲置四个多月)。 - ⚠️ 上面这条还牵出一个位置不符官方约定的问题:echomind 装在
~/.hermes/plugins/echomind/(根目录),而官方说记忆 provider 应放plugins/memory/<name>/(那里有自己独立的 loader)。实测hermes memory status列出的 8 个 provider(byterover / holographic / honcho / mem0 / memtensor / openviking / retaindb / supermemory)里面没有 echomind —— 与「位置不对 → 进不了那套 loader」的判断一致。 - ⚠️
orca-status值得单独留意:它声明了pre_tool_call—— 按官方规则这意味着它有能力阻断工具调用(而且超时的pre_tool_call回调是 fail closed 即阻断)。它的plugin.yaml头部还写着「Managed by Orca. Do not edit; changes may be overwritten」—— 也就是由外部工具托管,改它会被覆盖。 - 💡 一个现成的对照:本机那 7 个 not enabled 里
disk-cleanup、security-guidance这类 bundled 插件,正是官方表里「默认not enabled、等你显式同意才加载」的典型 —— 与本机的plugins.enabled只有 1 项恰好互相印证:这台机器上第三方代码的执行面几乎为零(唯一的第三方是 Orca 托管的 orca-status)。
(个人感想部分待补写。)
待深入
(待填:没听懂、想回头查的。)