OpenClaw权威指南 第 14 篇|插件与扩展系统:Extension SDK 的架构设计

OpenClaw权威指南 第 14 篇|插件与扩展系统:Extension SDK 的架构设计核心与边缘的分离 OpenClaw 主仓库的 src 目录是核心 它包含 Gateway Session Agent 运行时 内存系统这些跑起来就必须有的东西 但一个可持续的开源项目不能把所有东西都塞进核心 Matrix 频道集成 LanceDB 内存后端 微软 Teams 适配器 OpenTelemetr 链路追踪 这些东西有人需要 但不是所有人都需要

大家好,我是讯享网,很高兴认识大家。这里提供最前沿的Ai技术和互联网信息。



核心与边缘的分离

OpenClaw 主仓库的 src/ 目录是核心——它包含 Gateway、Session、Agent 运行时、内存系统这些跑起来就必须有的东西。但一个可持续的开源项目不能把所有东西都塞进核心。Matrix 频道集成、LanceDB 内存后端、微软 Teams 适配器、OpenTelemetry 链路追踪——这些东西有人需要,但不是所有人都需要,而且维护它们需要各自领域的专业知识。

OpenClaw 的插件系统解决的就是这个”核心要精简,边缘要可扩展”的工程矛盾。

它的设计原则是一句话:发现和验证不需要执行代码,执行才需要。插件的元数据、配置 Schema、UI 标签、依赖声明,全部来自 manifest 文件,在运行时模块被加载之前就可以被读取、校验、展示。只有确认插件被启用且配置合法,才会执行实际的 TypeScript 代码。这个分离让 OpenClaw 能在不加载任何插件代码的情况下,就告诉你”这个插件缺少哪个配置项”、”那个插件与当前 OpenClaw 版本不兼容”。


Gateway 启动时,src/plugins/discovery.ts 里的 discoverOpenClawPlugins() 函数扫描四个位置,收集所有候选插件。

Bundled 插件是随 OpenClaw npm 包一同分发的内置插件,放在 extensions/ 目录里,每次安装就已经在本地了。这类插件包括各核心平台的通道实现(Discord、Telegram、WhatsApp 的高级功能扩展)、browser 插件(浏览器工具的实际执行后端)、voice-call 插件(Twilio 语音通话集成)、以及 Provider 插件(Anthropic、OpenAI、Google、OpenRouter 等模型 Provider 的认证流程)。

全局插件安装在 ~/.openclaw/extensions/ 目录里,通过 openclaw plugins install 命令安装,对当前用户的所有 Agent 可见。这是第三方社区插件的标准安装位置。

工作区插件在当前 workspace 根目录下的 .openclaw/extensions/ 里,只对这个特定工作区的 Agent 可见。适合为特定项目安装项目级的专属插件,不影响其他工作区。

额外路径通过 plugins.load.paths 配置数组挂载,允许指向任意目录,主要用于本地开发调试——用 openclaw plugins install -l ./my-plugin 以软链接方式挂载本地插件目录,修改代码后重启 Gateway 即可测试,不需要发布 npm 包。

这四个来源的插件 ID 如果冲突,高优先级的覆盖低优先级:工作区 > 全局 > 额外路径 > bundled。一个工作区本地插件可以同名覆盖全局安装的插件,这让在不修改原始包的情况下做局部 patch 成为可能。


发现流程读取的第一个文件不是 TypeScript,而是 openclaw.plugin.json——插件的静态 manifest 文件:

{  “id”: “nextcloud-talk”,  “name”: “Nextcloud Talk”,  “description”: “Self-hosted chat via Nextcloud Talk webhook bots.”,  “version”: “0.1.0”,  “kind”: “channel”,  “channels”: [“nextcloud-talk”],  “configSchema”: {    “type”: “object”,    “additionalProperties”: false,    “properties”: {      “serverUrl”: { “type”: “string” },      “botToken”:  { “type”: “string” }    },    “required”: [“serverUrl”, “botToken”]  },  “uiHints”: {    “serverUrl”: { “label”: “Nextcloud Server URL”, “placeholder”: https://cloud.example.com” },    “botToken”:  { “label”: “Bot Token”, “sensitive”: true }  }}

configSchema 是 JSON Schema,用于在不执行任何插件代码的情况下就对用户的配置做类型验证和 UI 渲染。Web UI 的配置表单、openclaw config validate 命令、Zod schema 的动态合并,全部基于这个 schema 工作。uiHints 为每个字段提供人类可读的标签和占位符,sensitive: true 让 Web UI 对该字段做密码框处理。

如果插件目录里没有 openclaw.plugin.json,发现器会退而求其次读 package.json 里的 openclaw 字段——这是兼容老格式的回退路径,新插件应该优先提供独立的 manifest 文件。

候选插件收集完毕后,每个候选经过三项安全前检:路径解析——用 realpath() 解析候选入口文件路径,确认解析后的路径仍然在插件根目录之内(防止符号链接逃逸);权限检查——非 bundled 插件的目录不能是 world-writable(其他用户可写),否则存在恶意覆盖风险;所有权验证——检查目录的实际所有者,和当前进程的有效 UID 是否一致。任何一项检查不通过,这个候选直接被丢弃,不进入后续的加载流程,openclaw plugins doctor 会报告被丢弃的原因。


通过安全前检的候选插件,进入启用状态解析,由 resolveEnableState() 函数决定这个插件最终是 enableddisabledblocked 还是 slot-selected

plugins.allow 和 plugins.deny 提供白名单和黑名单控制。deny 优先于 allow——出现在 deny 里的插件永远不加载,不管 allow 里有没有它。如果配置了 allow 白名单但不是空数组,则只有白名单里的插件才被允许加载,其余全部 blocked。这个机制适合生产部署时锁定插件集合,防止意外安装新插件改变系统行为。

plugins.entries. .enabled  控制单个插件的启用开关,false 时插件被 disabled,不执行任何代码,但 manifest 仍然被读取,配置 Schema 仍然合并进来(让你在重新启用时不需要重新填写配置)。

Slots 是一个特殊的独占机制,用于”只能有一个实现”的扩展点。plugins.slots.memory 定义内存后端的独占 slot,默认使用 builtin 后端,设为 “memory-lancedb” 就切换到 LanceDB 后端。同一个 slot 只能有一个插件被激活,后来者自动进入 slot-not-selected 状态,不报错,只是不执行。


通过启用状态解析后,插件才进入运行时加载阶段。这里用的是 jiti——一个能直接加载和执行 TypeScript 文件的运行时模块加载器,不需要预先编译:

const jiti createJiti(import.meta.url, {  interopDefaulttrue,  extensions: [”.ts””.tsx””.mts””.cts””.js””.mjs””.cjs””.json”],  alias: {    “openclaw/plugin-sdk”: pluginSdkAlias,  },});const mod jiti(candidate.source) as OpenClawPluginModule;

alias 配置把所有 openclaw/plugin-sdk 的导入重定向到 pluginSdkAlias——一个指向主包 dist 目录里 Plugin SDK 子路径导出的绝对路径。这是 Plugin SDK 的设计约束:插件只能通过 openclaw/plugin-sdk 及其子路径(openclaw/plugin-sdk/coreopenclaw/plugin-sdk/telegram 等)导入主包的内容,不能用相对路径绕过这个边界直接访问 src/ 目录的内部实现。

这个边界有双重意义:对插件开发者来说,Plugin SDK 是稳定的公开 API,破坏性变更会走版本号管理;对 OpenClaw 核心来说,内部实现可以自由重构,只要 Plugin SDK 的接口不变,所有插件就不会受影响。

bundled 插件在开发时使用 openclaw/plugin-sdk 自引用路径,package.json 里有 “openclaw/plugin-sdk”: “.” 的 self-import 声明。外部插件使用同样的路径,jiti 的 alias 把它指向安装好的主包的 dist 目录。两者在写法上完全一致,只是解析目标不同——这让 bundled 插件的开发体验和外部插件完全相同,共用同一套开发规范。


加载完成的模块必须导出一个 register 函数(或导出一个带 register 方法的对象)。这个函数接收 OpenClawPluginApi 对象作为唯一参数,通过这个 API 向系统注册所有能力:

import type { OpenClawPluginApi } from “openclaw/plugin-sdk”;export function register(api: OpenClawPluginApi) {  // 注册通道  api.registerChannel({ … });  // 注册工具  api.registerTool({ … });  // 注册模型 Provider  api.registerProvider({ … });  // 注册 Hook  api.registerHook({ … });  // 注册 HTTP 路由  api.registerHttpRoute({ … });  // 注册 CLI 命令  api.registerCommand({ … });}

registerChannel 是最复杂的注册类型,需要提供四个 Adapter 实现:ChannelConfigAdapter(解析和校验通道配置)、ChannelOutboundAdapter(发送消息)、ChannelSecurityAdapterdmPolicy 访问控制)、ChannelStatusAdapter(健康检查和状态报告)。这四个 Adapter 覆盖了第六篇讲过的通道生命周期的全部职责,让外部通道插件和内置通道在行为上完全对等。

registerProvider 让插件向 OpenClaw 的认证系统注入新的模型 Provider,包括 OAuth 流程、API Key 验证、Device Code 认证三种方式。注册后,用户可以通过 openclaw models auth login –provider  在终端里完成认证,不需要任何外部脚本。这是 Provider 插件(OpenAI、Google、MiniMax 等)能做到”一行命令完成 OAuth 授权”的原因。

registerHook 让插件向工作流的关键节点注入代码,在消息到达前后、Agent 执行前后、通道连接时、Gateway 启动关闭时触发。Hook 目录结构遵循 HOOK.md(给 Agent 看的钩子说明)+ handler.ts(实际执行代码)的格式,这让 Hook 也可以有 Skills 那样的自然语言描述,Agent 可以理解这个钩子在做什么。

registerHttpRoute 把自定义 HTTP 路由挂载到 Gateway 的 HTTP 服务器上,比如 Webhook 类通道(Slack、Google Chat)需要注册一个 POST 路由来接收平台推送的消息。这个路由和 Gateway 内置的 /v1/* 路由共享同一个 HTTP 服务器实例,但路径前缀隔离。


Plugin SDK 发布在 openclaw 主包的子路径导出下,分三个模块组织。

openclaw/plugin-sdk(核心模块)导出所有类型定义、definePluginEntry 工具函数(包装 register 函数,提供运行时安全检查)、以及 registerPluginHooksFromDir(批量注册一个目录下的所有 Hook,是最常用的 Hook 注册方式)。

openclaw/plugin-sdk/core 导出 Gateway 内部的核心工具函数,包括 Session 工具、消息规范化、配置工具等。这些是内部 API,破坏性变更概率更高,建议只在必要时使用。

openclaw/plugin-sdk/  系列(telegramdiscordslack 等)导出各通道适配器的基类和工具函数。如果你在写一个基于现有通道做增强的插件,可以直接复用这些基类,不需要从零实现 Adapter 接口。

有一条需要牢记的边界规则:插件内部不能使用 openclaw/plugin-sdk/  格式的路径自引用(即在插件代码里引用另一个 bundled 扩展的子路径),因为这些路径只在主包内部有解析规则,在插件的 jiti 运行时环境里不存在对应的解析。scripts/check-extension-plugin-sdk-boundary.mjs 脚本在 CI 流水线里静态检测这类违规导入。


extensions/ 目录里的 bundled 插件值得单独罗列,它们代表了 OpenClaw 对”哪些能力值得内置”的判断:

browser 是浏览器工具的执行后端,默认启用,如果你要替换浏览器实现,需要先禁用它。voice-call 提供 Twilio 和 log 两种后端的语音通话能力,默认禁用。copilot-proxy 是 VS Code Copilot Proxy 桥接,把 GitHub Copilot 协议流量转发给 OpenClaw Agent,默认禁用。memory-lancedb 是基于 LanceDB 的高性能向量内存后端,作为 memory slot 的可选替代实现。diagnostics-otel 提供 OpenTelemetry 链路追踪和指标导出,适合需要监控 Agent 执行性能的场景。nostr 是 Nostr 去中心化协议的通道适配器。zai 是 z.ai 模型服务的 Provider 插件。

所有 Provider 插件(Anthropic、OpenAI、Google、OpenRouter 等 28 个)也是 bundled 插件,但它们以轻量 manifest 形式存在——只提供认证流程和模型 Provider 注册,不包含大量业务逻辑,保持了体积精简。


插件系统和 Skills 系统面临同样的供应链安全挑战,但因为插件是真正的 TypeScript 代码而不是 Markdown 说明书,风险等级更高。OpenClaw 为插件设置了三道防线。

第一道:安装时禁用 lifecycle scripts。openclaw plugins install 内部调用 npm install –ignore-scripts,npm 的 preinstallpostinstallprepare 钩子全部不执行。这消除了”安装即 RCE(远程代码执行)”这类最常见的 npm 供应链攻击路径。

第二道:pnpm.onlyBuiltDependencies 白名单。monorepo 根目录的 pnpm-workspace.yaml 维护了一个允许执行构建脚本的依赖白名单,只有真正需要编译原生模块的包(better-sqlite3sharpcanvas 等)才在名单里,其余所有包的构建脚本都被 pnpm 忽略。这防止了依赖链深处的恶意包通过构建脚本悄悄执行代码。

第三道:路径安全检查。发现阶段的 realpath() + 所有权验证,防止符号链接劫持和其他用户的目录污染。

三道防线之后,官方文档仍然明确写着:插件在进程内与 Gateway 共享运行,把它当成受信任的代码对待,只安装你信任的插件。安全机制降低了攻击面,但不能代替信任判断。


理解了整个体系,写一个最小可用插件的步骤非常清晰。

创建目录结构:

my-plugin/├── openclaw.plugin.json   ← manifest├── package.json           ← 声明 openclaw.extensions└── index.ts               ← 入口,导出 register 函数

package.json

{  “name”: “my-plugin”,  “openclaw”: {    “extensions”: [”./index.ts”]  }}

openclaw.plugin.json

{  “id”: “my-plugin”,  “name”: “My Plugin”,  “description”: “做某件事的插件”,  “version”: “1.0.0”}

index.ts

import type { OpenClawPluginApi } from “openclaw/plugin-sdk”;export functionregister(api: OpenClawPluginApi{  api.registerTool({    name“my_tool”,    description“这个工具做某件事”,    inputSchema: {      type“object”,      properties: {        input: { type“string”, description“输入” }      },      required: [“input”]    },    executeasync ({ input }) => {      return { result: 处理了: ${input} };    }  });}

本地安装测试:

openclaw plugins install -l ./my-pluginopenclaw gateway restartopenclaw plugins list  # 确认插件出现

整个过程不需要发布 npm,不需要构建步骤,jiti 在运行时直接加载 TypeScript 源文件。


OpenClaw 的插件系统是一个控制平面(manifest + 配置 Schema)与数据平面(register 函数 + 运行时行为)严格分离的架构。控制平面不执行代码,保证了配置验证、UI 渲染、依赖检查的安全性;数据平面通过 jiti 在运行时动态加载,保证了开发体验的零构建开销。Plugin SDK 的子路径导出边界让核心可以自由重构,插件开发者只需要依赖稳定的公开 API。

这套设计让一个 Matrix 通道插件的作者,能用和 OpenClaw 核心团队完全一致的开发体验构建他的扩展,不需要 fork,不需要修改主仓库,不需要等待合并请求。

下一篇,我们进入多 Agent 系统——当一个任务复杂到单个 Agent 处理不了,父 Agent 是如何生成子 Agent、编排工作流、以及整个系统是如何维持确定性的。


源码参考:src/plugins/discovery.ts · src/plugins/loader.ts · src/plugins/registry.ts · src/plugins/manifest-registry.ts · src/plugin-sdk/ · extensions/voice-call/ · docs/tools/plugin.md基于 commit bf6ec64f 版本

小讯
上一篇 2026-04-09 19:02
下一篇 2026-04-09 19:00

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/253283.html