2026-09-24 · 23 min read

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 声明所需环境变量(安装时提示)| 通过 pip entry_points 分发 | 注册 gateway 平台 | 注册生图后端 | 注册视频生成后端 | 注册上下文压缩引擎 | 注册终端执行后端(云沙箱)| 路由人工审批提示 | 注册记忆后端(用单独的一套发现系统)| 宿主侧 LLM 调用(ctx.llm.complete,借用用户当前模型与认证)| 调用 MCP 工具(能力门控)| 注册推理后端(LLM provider profile)。
  • 发现来源有五种(后者覆盖前者,所以同名用户插件会替换 bundled):<repo>/plugins/(bundled,随 Hermes 发布)| ~/.hermes/plugins/(user,个人插件)| .hermes/plugins/(project,需上述开关)| pip 的 hermes_agent.plugins entry_points | Nix 声明式(services.hermes-agent.extraPlugins)。
  • 子分类目录会路由到专门的发现系统(官方表):根 plugins/ = 通用插件(工具/钩子/斜杠命令/CLI 命令/打包技能)| plugins/platforms/<name>/ = gateway 频道适配器 | plugins/image_gen/<name>/ = 生图后端 | plugins/memory/<name>/ = 记忆 provider(有自己独立的 loader,kind exclusive —— 同时只能有一个激活) | 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)② gh CLI 的登录(仅 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),且 bundled skills/ 树与 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 plugin
python
# ~/.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)。

(个人感想部分待补写。)

待深入

(待填:没听懂、想回头查的。)