装了插件不用重启网关:Hermes 现在会给它当场接线

你在 Telegram 群里给自己的机器人装了个插件,插件带一个新的斜杠命令。你兴冲冲地在群里敲下去——机器人像没听见。翻文档,答案是“重启网关”。可重启意味着正在流式输出的回答断掉、跑着的后台任务没了、所有会话都要重来一遍。为了一个斜杠命令,代价太大了。
所以大多数人就这么算了:等下次重启吧。最近这次改动(#119266,修的是 #87770)把这件事改了——网关还在跑的时候装插件,它的平台处理器会当场接线。
为什么以前必须重启
问题出在“什么时候去发现插件”这件事上:
discover_plugins()在已经发现过之后会直接短路返回,除非显式传force=True。- 运行中安装的几条路径都没有传这个参数。更糟的是,
tools/mcp_tool_config._portable_mcp_servers用的是非强制调用——所以你在运行中装完插件再执行mcp.reload,重新加载的其实是旧的 server 集合。 - TUI 的
plugins.manage install调用完安装函数就返回了,压根没有重新扫描。 - 平台适配器(Telegram、Slack 等)的处理器注册表是在启动时建好的,之后不会再看一眼新插件。
于是结果就是:插件文件在硬盘上了,配置也开了,但这个正在运行的进程对它一无所知。
一张激活清单:现在生效的 vs 下个会话的
现在每条运行中加载的路径都是一次真实的强制重扫,并且从重扫内部发出一个回调(PluginManager.on_plugin_loaded),每个新加载的插件带一份激活摘要。看着像这样:
{"name": "late-mcp", "key": "late-mcp",
"activated_now": {"gateway_commands": ["late"], "callbacks": ["telegram"]},
"deferred": {"tools": ["late_tool"], "prompt": ["late.section"], "mcp_servers": ["worker"]}}
翻译成人话:
| 类别 | 什么时候生效 | 具体是什么 |
|---|---|---|
| 网关斜杠命令 | 立刻 | 插件注册的命令名 |
| 网关变换钩子 | 立刻 | transform_llm_output、transform_tool_result、transform_terminal_output、pre_gateway_dispatch、gateway_platform_event、pre_command |
| 其他钩子 | 立刻 | 其余钩子名 |
| 平台回调 | 立刻 | register_platform_handler 工厂所在的平台,以及 Slack action id |
| 工具 | 下个会话 | 工具名 |
| 系统提示段落 | 下个会话 | 段落 id |
| 便携 MCP server | 一次 mcp.reload 或下个会话 |
用 mcp.json 里的原始名字 |
三个“立刻”里最实用的是平台回调:它意味着插件注册的按钮回调、入站消息变换,不用重启就能用。
接线为什么不会重复、也不会被核心处理器抢走
“立刻生效”听起来简单,做起来有两个坑:
第一个坑:重复注册。 强制重载会交回全新的函数对象,所以不能靠函数身份去判断“这个装过了”。办法是给每次接线打上 (插件, 工厂限定名) 的键——同一个插件同一个工厂只接一次。这就是为什么那个方法叫幂等接线(rewire_plugin_handlers()):它重新读一遍工厂注册表,只跑那些还没接到活客户端上的工厂。
第二个坑:顺序。 Telegram 那边有个细节很值得说:新加的处理器会被提到核心的通配处理器前面。原因是 PTB 每个分组只派发第一个匹配的处理器,而核心注册了一个通配的 filters.COMMAND——如果新处理器排在后面,你的斜杠命令永远轮不到。Slack 那边则是按 AsyncApp 去重 register_slack_action_handler。
网关运行时会按它服务的每个 profile 在启动时订阅这个事件,收到之后在事件循环上给每个活着的适配器重新接线。这一切都发生在网关进程内部,不在安装它的那个进程里。
每个安装入口都会说实话
不管是 CLI、桌面 App、TUI 还是 dashboard 的 REST 安装,装完之后都会明确告诉你“现在生效了什么、什么要等”:
hermes plugins install late-mcp --enable
命令会先通知正在运行的网关(控制套接字上的 reload-plugins 动词,让它对指定的 home 强制重扫),然后打印上面那张“立即/延后”的清单。桌面和 TUI 的 plugins.manage install/toggle/update 则返回 activation 和 gateway_reloaded 两个字段;restart_required 只有在没有任何网关应答的时候才是 true。
这里有个很实在的设计取向:宁可不做,也不假装做到。 所以:
- 插件中途被加载,它的工具和系统提示段落不会在本会话里生效。原因和
/skills install一样——正在跑的会话,提示词和工具 schema 要保持缓存稳定,中途变脸会让缓存失效。 - 便携 MCP server 要等
mcp.reload。 - 禁用不撤销接线。 运行中禁用一个插件,它已经接好的处理器会一直留到网关重启。所有界面都会这么告诉你,而不是让你以为立刻就干净了。
用起来是什么样
一个典型流程:
# 1. 装并启用(会通知正在跑的网关重扫)
hermes plugins install late-mcp --enable
# 2. 输出里会写清楚:gateway_commands: ["late"] 已经生效
# 这时候在 Telegram 群里敲 /late 就能用了,不用重启
# 3. 如果这个插件还带了 MCP server,让它也连上
# (通过 mcp.reload 或直接开新会话)
需要注意的一点:你的网关必须是在插件所在的那个 home 上服务的。 事件是按 profile 传播的,一个没服务这个 profile 的网关不会替它接线。多 profile / 多网关部署下,这一点比单机更容易踩到——相关背景可以看一主机一网关。
边界与注意事项
- 这一版只覆盖处理器。 工具、系统提示段落、便携 MCP server 都不在“立刻生效”范围内,理由分别是提示词缓存和连接生命周期。
- 没有反向操作。 接线是单向的:装上立刻生效,禁用了要等重启才真正干净。升级插件同理。
- 插件目录本身的可信度没变。 这是运行时的接线机制,不是信任机制的松动;想了解插件从哪来、怎么审的,看插件目录的信任模型和桌面插件 SDK。
- 终端后端类插件是另一条线,见终端后端插件。
小结
“装完重启”这种要求,在个人玩具上可以忍,在跑着真事的网关上就是一道硬门槛——它会让升级永远排在凌晨。现在这道门槛拆了一半:该立刻生效的(平台处理器、斜杠命令、变换钩子)立刻生效,该等下一轮的(工具、提示段落、MCP 连接)明确说等下一轮,禁用不撤销也如实告知。 剩下那一半——工具和提示段落——短期内不会动,因为它踩的是提示词缓存这条底线。