← 文章

OpenClaw

插件装上了,OpenClaw 为什么还不会用?

插件已经出现在列表里,助手为什么仍然不会用?从同步注册、失败回滚到服务启停,走完一项能力进入 OpenClaw 的最后一段路。

假设那份项目清单已经能检查、能保存,你又想给助手加一项本事:持续观察任务变化,有新结果时通知你。于是装了一个插件,配置也填好了。插件出现在列表里,你发出指令,助手却仍然说没有这项工具。

这一次,问题可能既不在模型,也不在沙箱。插件已经躺在磁盘上,但它的能力还没有走进当前运行的 OpenClaw。

“安装成功”是文件系统里的事实,“可以调用”是运行时里的事实,中间隔着一段装配过程。作为这个系列的最后一篇,我们顺着这段过程,看一项新能力怎样获得身份、进入注册表、开始工作,又在结束时交还资源。

故事中的通知插件是假设示例。源码继续固定在 v2026.5.22 对应提交 a374c3a,重点讨论原生 OpenClaw 插件;兼容其他产品的插件包另有格式和加载路径,不混为一谈。

门口的名牌,不是正在营业的证明

原生插件根目录里的 openclaw.plugin.json,更像一张可以先检查的名牌:插件是谁,配置应是什么形状,声明了哪些能力。OpenClaw 可以读取这些信息并验证配置,而不执行插件代码。代码入口和包安装信息则有自己的位置,不能把 manifest 当成启动脚本。原生插件清单

这个先后关系很实用。假如通知插件需要一个轮询间隔,把字符串错填进数字字段,系统可以先告诉你配置不对,而不是先启动连接,再等某个定时器深处报错。清单把一部分错误留在了“尚未营业”的阶段。

加载器还要处理候选发现、身份、启用条件与配置校验。存在于磁盘上的插件可能未启用,也可能因为配置或加载错误只留下诊断。普通运行时加载路径会使用清单中的 schema 校验插件配置,然后才进入相应模块加载与注册流程。候选发现 · 清单注册表 · 加载主流程

因此,插件列表是一条线索,不是最终证据。官方使用文档特意区分静态检查和运行时检查:要证明工具、hooks、服务等已经注册,应查看 openclaw plugins inspect <plugin-id> --runtime --json。还要确认查看的是实际服务于当前渠道的 Gateway。安装后的验证

回到故事,如果你检查的是终端中新启动的进程,而聊天仍连接旧 Gateway,就很容易出现“两边都没说谎,却彼此对不上”的情况。排查应该先确认运行实例,再确认能力,而不是反复重装同一份文件。

插件从清单发现到同步注册、服务启动与停止的阶段
安装、声明、注册和运行是不同阶段;某一阶段成功,不能代替下一阶段的验证。

先把菜单写好,再去厨房忙

加载到模块以后,OpenClaw 会解析导出,寻找 register,同时兼容相应的 activate 导出。插件拿到 API,用它登记工具、hooks、服务等能力。这里有个看似严格、实际上很有价值的约束:注册函数必须同步完成。注册调用与导出解析

为什么不能在 register 里先请求一个远程接口,等结果回来再决定加哪些工具?想象 Gateway 已经根据注册表对外提供服务,过了一会儿插件又偷偷塞进一项能力。此时“加载完成”就不再是稳定的边界,诊断、缓存和错误回滚也会变得难以解释。

源码的做法很直接:执行 register 后检查返回值是否像 Promise;如果是,就报“注册必须同步”的错误。这个检查不仅识别原生 Promise,也识别带有可调用 then 的对象或函数。它会处理异步拒绝,避免遗留未处理的 rejection,但不会等待异步初始化完成。

更有意思的是,API 外面包了一层 Proxy。注册窗口关闭后,普通注册方法的迟到调用会被忽略。即使插件保存了 API,等 await 之后再调用 registerTool,也不能靠这条路径把能力补进注册表。注册窗口守卫

这并不意味着所有 API 从此失效。实现单独列出允许运行时调用的方法,例如事件发送、会话附件发送、安排后续会话轮次与按标签取消安排。注册窗口限制的是装配时机,运行期操作仍有明确入口。API 生命周期策略 · 分组 API 转发

通知插件可以同步登记一个查询工具和一个后台服务。真正打开连接、等待远端响应的工作,放进服务启动或工具执行阶段。菜单先稳定下来,厨房随后开始忙;工具的执行函数当然可以异步,限制只针对这里讨论的注册入口。

写到一半出错,不能留下半张菜单

假设插件先登记了查询工具,接着登记 hook 时抛出异常。如果系统只记录错误,却保留前半段结果,用户就会面对一项“看似加载失败,但部分能力又能使用”的插件。

OpenClaw 在调用 register 前保存注册表快照,还记录若干进程级扩展状态。注册失败后,恢复快照,回滚受管理的全局副作用,再记录插件错误。注册表包括工具、hooks、服务、路由等多类条目;命令、内部 hooks、部分运行时提供者则有相应恢复逻辑。注册失败处理 · 全局副作用回滚

这里要克制地使用“事务”这个词。快照复制的是数组和映射的外层,不是对每个对象做深拷贝;它也不能撤销插件已经写出的文件、已经发出的网络请求,或者模块顶层偷偷创建的定时器。恢复注册表成员关系,和恢复整个进程世界,是完全不同的承诺。

这也是为什么注册阶段越接近“声明”,越容易可靠。把真实资源申请混进 register,就把回滚范围推到了宿主看不见的地方。即使异步 register 被拒绝,已经开始的异步函数也不会被这条检查自动取消;API 守卫能拦住迟到登记,不能让任意 JavaScript 副作用倒流。

登记本身也并非照单全收。这个版本要求 Agent 工具先在 contracts.tools 中声明,注册时会检查工具名称是否属于声明范围。服务则按 ID 检查重复:同一插件再次登记同名服务保留第一份,不同插件争用同一服务 ID 会留下错误诊断。工具与服务登记

有一点容易漏掉:诊断不一定表现为异常。某次登记可以被拒绝、写入错误诊断并返回,因此不能只看 register 是否抛错,就断言所有声明的能力都登记成功。应继续检查实际条目与诊断。工具登记成功之后,仍要经过会话中的工具构造与权限筛选,才可能出现在模型面前。

注册失败回滚与服务资源清理的责任划分
宿主管受管理的登记状态,插件管自己申请的资源;服务启动失败尤其需要自行清理半成品。

真正开工以后,谁负责关灯

服务管理器把启动和注册分开了。它按注册表顺序逐个等待 service.start;一个服务启动失败会被记录,但后面的服务仍会继续尝试。只有成功返回的服务才进入 running 列表。服务生命周期

这意味着插件加载成功,不保证它的后台服务已经健康运行。我们的通知插件可能已经提供查询工具,后台连接却在启动时失败。UI 如果只显示一个绿色的“已启用”,就把这些不同状态压扁了。更有帮助的状态是:发现、注册、启动、运行错误,各有证据。

结束时,管理器逆序遍历成功启动的服务,调用存在的 stop;某个 stop 失败,也不会阻止继续关闭其他服务。这种顺序适合释放按顺序建立的资源,但实现并没有在这里构造依赖图,不能据此声称它会自动计算服务依赖或拓扑排序。

最需要插件作者注意的是启动失败的半成品。假如 start 先打开连接 A,再建立连接 B,B 失败后抛错,这个服务不会进入 running,也就不会得到管理器后续的 stop。A 应在 start 自己的错误处理里关闭。将“启动过程失败时清理”与“正常停止时清理”共用一个资源释放函数,会更容易保持一致。

另外,这个返回的 stop 句柄没有一次性关闭标记,再调用一次仍会尝试停止那些服务。资源释放最好能容忍重复执行:清空已取消的定时器引用,识别已关闭的连接,避免重复提交结束事件。这里是从实际实现推导的设计建议,不是在宣称宿主已经替插件实现幂等。

顺序等待也有代价:这一层没有给每个 start 自动套上超时,长期不返回的启动会拖住后续服务。插件应为自己的外部等待设定明确边界。本文局部实验验证了启动失败继续、逆序停止、停止失败继续和重复 stop;没有启动真实通知连接。

对于我们的通知插件,可以把连接句柄、定时器和订阅取消函数集中放进一个服务私有对象。查询工具通过清晰的接口读取状态,停止时由服务统一释放。这样以后替换通知渠道,只需要替换连接适配器,不必改写插件发现和注册流程。

快照里的插件,不一定就是在线的插件

加载器还区分不激活的快照加载和实际激活。快照适合检查能力;相关路径会避免或恢复受管理的进程级状态,避免一次检查悄悄替换在线命令等内容。缓存命中时,激活路径又可能恢复缓存里的登记状态并设置活动注册表,而不是每次都重新执行插件 register。缓存与激活路径

但“快照”不能理解成执行不受信任代码的沙箱。原生插件是进程内代码,模块导入和插件自己的副作用依然需要单独考虑。上一篇讲的工具沙箱,也不会自动把插件加载过程变成一个隔离进程。

把这些边界放回故事,排查顺序就很自然:文件是否被发现,配置是否通过,当前 Gateway 是否登记了工具,后台服务是否启动,当前会话是否允许调用。每一步都有不同的证据,也有不同的修复位置。

本篇使用固定源码的连续函数片段,去除 TypeScript 类型后完成 25 项局部检查,覆盖注册窗口、运行时例外、异步注册拒绝、浅快照与服务启停。服务上下文、追踪和 HTTP 注册表包装使用测试适配器;完整加载器、全局回滚、真实 Gateway 与插件导入没有做端到端复现。

从第一篇消息怎样找到会话,到最后一篇能力怎样进入运行时,OpenClaw 最值得带走的经验是把边界做清楚:消息归谁,状态放哪,工具在哪执行,资源由谁释放。插件系统让能力增长,而这些边界让增长之后的系统仍然能被理解和维护。

继续阅读