☰
OpenClaw 插件系统边界解析:manifest-first 架构、加载管线与契约执行
2026/10/16 2:12:52 网站建设 项目流程
  • 人工智能
  • 大模型
  • LLM 网关
  • AI Agent
  • 即时通讯

【免费下载链接】openclaw-zero-token

OpenClaw: Use All Major AI Models NO API Token! Claude/ChatGPT/Gemini/DeepSeek/Doubao/Grok/Qwen/Manus/Kimi

项目地址:https://gitcode.com/gh_mirrors/op/openclaw-zero-token
点击查看免费下载

OpenClaw 将插件发现(discovery)、manifest 验证、插件加载、注册表组装(registry assembly)与契约执行(contract enforcement)整体收敛在src/plugins/边界目录内,并以此为核心建立了「先元数据、后运行时」的插件架构。本文以 src/plugins/AGENTS.md 定义的边界规则为主线,结合 docs/plugins/manifest.md、docs/tools/plugin.md 以及src/plugins/下的真实实现,系统讲解 OpenClaw 插件系统的分层职责、manifest 契约、发现与加载管线、公开契约与边界约束,帮助你理解如何编写、发布、调试插件,以及核心维护者应当如何在不破坏契约的前提下演进插件系统。

一、插件边界目录的职责划分

src/plugins/是 OpenClaw 中插件能力的唯一归属方(boundary owner),它明确拥有以下五项职责:

职责说明
插件发现(discovery)扫描配置路径、工作区、全局目录与内置(bundled)目录中的插件候选
Manifest 验证(manifest validation)读取并校验openclaw.plugin.json,不执行插件代码
插件加载(loading)通过 jiti 运行时加载 TypeScript/JavaScript 插件模块
注册表组装(registry assembly)将插件贡献的 tools、hooks、channels、providers、commands、services 等汇总为统一注册表
契约执行(contract enforcement)保证内置插件与外部插件遵循同一套公开契约与测试

该边界同时定义了它的公开契约(Public Contracts),即插件作者与核心维护者之间的事实约定:

  • 文档面:docs/plugins/architecture.md、docs/plugins/manifest.md、docs/plugins/sdk-overview.md、docs/plugins/sdk-entrypoints.md(其中 manifest 规范与插件总览的当前版本见 docs/plugins/manifest.md 与 docs/tools/plugin.md);
  • 定义面:类型与契约集中在 src/plugins/types.ts、src/plugins/runtime/types.ts、src/plugins/contracts/registry.ts 与src/plugins/public-artifacts.ts。

从源码结构看,这套边界并非松散约定,而是被 loader 的加载流程、manifest 的强制校验、以及contracts/下的契约测试(如loader.contract.test.ts、registry.contract.test.ts、discovery.contract.test.ts)具体落实为可执行的工程约束。

二、manifest-first:先校验元数据,再执行插件代码

OpenClaw 插件系统最核心的设计原则是manifest-first(manifest 优先):插件发现、配置校验与设置过程应当在插件运行时执行之前,仅凭元数据即可完成。这一原则直接决定了插件的门槛形态:

每个插件必须在插件根目录(plugin root)下提供一份openclaw.plugin.json。缺失或无效的 manifest 会被视为插件错误并阻断配置校验——即使该插件在本地文件系统中加载也同样适用。

这与「配置校验不执行插件代码」的安全模型互为表里:manifest 只用于发现与校验,插件模块仍由运行时另行加载。因此在配置阶段读取或写入配置时,OpenClaw 永远不会因为插件本身的 bug 而崩溃,这为 Doctor、配置修复等只读/离线流程提供了保障。

2.1 manifest 的必需字段

manifest.md 给出的最小可用 manifest 如下:

{ "id": "voice-call", "configSchema": { "type": "object", "additionalProperties": false, "properties": {} } }

必需键:

  • id(string):插件的规范 id(canonical plugin id);
  • configSchema(object):插件配置的内联 JSON Schema。

2.2 manifest 的可选字段

字段类型说明
kindstring插件种类,如"memory"、"context-engine"
channelsarray该插件注册的 channel id 列表,如["matrix"]
providersarray该插件注册的 provider id 列表
skillsarray相对插件根目录的 skill 目录列表
namestring插件显示名
descriptionstring插件简介
uiHintsobject配置字段的 label/placeholder/sensitive 标记,供 UI 渲染
versionstring插件版本(仅信息性)

在源码实现 src/plugins/manifest.ts 中,可发现 manifest 类型还支持更多进阶字段,并会被严格归一化:

  • enabledByDefault:是否默认启用;
  • legacyPluginIds:应归一化到当前 id 的旧插件 id;
  • autoEnableWhenConfiguredProviders:当配置中引用了这些 provider id 时自动启用该插件;
  • cliBackends:插件拥有的 CLI 推理后端 id(用于启动期廉价激活查找);
  • providerAuthEnvVars:provider 相关的认证环境变量(无需启动插件运行时即可查找);
  • providerAuthChoices:onboarding/auth-choice 路由元数据,供配置校验、CLI 帮助与非运行时 auth-choice 路由使用;
  • contracts:静态能力归属快照(见下文「契约执行」一节);
  • channelConfigs:channel 级配置 schema 与 UI 提示。

2.3 JSON Schema 的强制要求

  • 每个插件都必须附带 JSON Schema,即使它不接受任何配置;
  • 空 schema 是合法的,例如{ "type": "object", "additionalProperties": false };
  • Schema 在配置读写时校验,而不是运行时——这意味着配置校验路径完全无插件代码参与。

仓库中大量真实插件遵循这一形态。例如 bluebubbles/openclaw.plugin.json:

{ "id": "bluebubbles", "channels": ["bluebubbles"], "configSchema": { "type": "object", "additionalProperties": false, "properties": {} } }

extensions/memory-core/openclaw.plugin.json 则展示了kind的用法:

{ "id": "memory-core", "kind": "memory", "configSchema": { "type": "object", "additionalProperties": false, "properties": {} } }

而 brave/openclaw.plugin.json 是进阶形态的范本:它同时携带providerAuthEnvVars、uiHints与contracts,并对webSearch.apiKey(标记为sensitive)与webSearch.mode(枚举web/llm-context)做了精细的 schema 约束:

{ "id": "brave", "providerAuthEnvVars": { "brave": ["BRAVE_API_KEY"] }, "uiHints": { "webSearch.apiKey": { "label": "Brave Search API Key", "help": "Brave Search API key (fallback: BRAVE_API_KEY env var).", "sensitive": true, "placeholder": "BSA..." }, "webSearch.mode": { "label": "Brave Search Mode", "help": "Brave Search mode: web or llm-context." } }, "contracts": { "webSearchProviders": ["brave"] }, "configSchema": { "type": "object", "additionalProperties": false, "properties": { "webSearch": { "type": "object", "additionalProperties": false, "properties": { "apiKey": { "type": ["string", "object"] }, "mode": { "type": "string", "enum": ["web", "llm-context"] } } } } } }

2.4 校验行为的严格规则

manifest.md 明确了以下校验语义:

  • 未知的channels.*键是错误,除非该 channel id 已被某个插件 manifest 声明;
  • plugins.entries.<id>、plugins.allow、plugins.deny、plugins.slots.*必须引用可发现的插件 id,未知 id 一律视为错误;
  • 已安装但 manifest 或 schema 损坏/缺失的插件,会令校验失败,并由 Doctor 上报插件错误;
  • 插件已配置但被禁用时,配置会被保留,并在 Doctor 与日志中给出警告(而非错误)。

2.5 源码级的 manifest 加载校验

在 src/plugins/manifest.ts 的loadPluginManifest中,可以看到这些规则的具体落点:

  • 通过resolvePluginManifestPath在插件根目录定位openclaw.plugin.json;
  • 文件打开走openBoundaryFileSync边界检查(rejectHardlinks默认拒绝硬链接),路径逃逸会返回unsafe plugin manifest path错误;
  • JSON 解析失败返回failed to parse plugin manifest;
  • id缺失返回plugin manifest requires id;
  • configSchema非对象返回plugin manifest requires configSchema;
  • 字符串列表字段(channels、providers、skills、contracts 等)统一经过normalizeStringList去空白过滤。

三、插件发现管线:四层扫描与优先级

docs/tools/plugin.md 描述了插件的发现顺序,src/plugins/discovery.ts则提供了实现支撑。OpenClaw 按以下顺序扫描候选:

  1. 配置路径(Config paths):plugins.load.paths(文件或目录);
  2. 工作区扩展(Workspace extensions):<workspace>/.openclaw/extensions/*.ts与<workspace>/.openclaw/extensions/*/index.ts;
  3. 全局扩展(Global extensions):~/.openclaw/extensions/*.ts与~/.openclaw/extensions/*/index.ts;
  4. 内置扩展(Bundled extensions):随 OpenClaw 发布的<openclaw>/extensions/*,多数默认禁用。

多个候选解析到同一插件 id 时,按上述顺序靠前者胜出,低优先级副本被忽略并被标记为overridden by <origin> plugin。在 src/plugins/loader.ts 中,resolveCandidateDuplicateRank给出了精确的优先级:config(0)> 显式 install 的global(1)>bundled(2)>workspace(3)> 其他(4)。

3.1 发现阶段的缓存与安全防线

  • 缓存:discovery.ts维护一个短窗口内存缓存(默认 1000ms,可用OPENCLAW_PLUGIN_DISCOVERY_CACHE_MS调整,OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE=1可整体关闭),用于吸收启动/重载期的突发扫描;
  • 路径安全:候选路径在进入发现之前就经过安全校验,以下情况会被拦截:
    • 扩展入口解析后逃出插件根目录(含符号链接/路径穿越);
    • 插件根目录或源码路径是全局可写(world-writable);
    • 非内置插件的路径属主可疑(POSIX 属主既不是当前 uid 也不是 root);
  • 来源追踪(provenance):未命中 install/load-path 来源追踪的非内置插件会发出警告,提示通过plugins.allow固定信任或通过plugins.installs记录安装来源。

3.2 package pack 形式的扩展

插件目录还可以携带package.json,通过openclaw.extensions声明多个扩展入口:

{ "name": "my-pack", "openclaw": { "extensions": ["./src/safety.ts", "./src/tools.ts"] } }

每个入口都会成为独立插件;若列出多个扩展,插件 id 变为name/<fileBase>。安全护栏:每个openclaw.extensions入口在符号链接解析后必须留在插件目录内,越界即被拒绝。

四、加载器与注册表组装:从候选到可运行插件

src/plugins/loader.ts是整条管线的编排者(loadOpenClawPlugins),它将发现结果、manifest 注册表与插件运行时组装为最终的PluginRegistry。关键环节包括:

1. 启用状态解析。每个候选经resolveEffectiveEnableState综合enabledByDefault、plugins.allow、plugins.deny、plugins.entries.<id>.enabled与候选来源计算启用与否。未启用的插件仍会以disabled状态进入注册表并携带禁用原因,保证配置诊断可见。

2. 模块加载。通过 jiti 在运行时加载 TS/JS 入口(index.ts/index.js/index.mjs/index.cjs均为默认候选)。加载器针对源码图与构建产物分别决定是否使用原生加载(tryNative),并对每个插件根目录做边界文件打开校验(非 bundled 插件拒绝硬链接)。

3. 导出形态归一。resolvePluginModuleExport兼容两种导出:

  • 函数形式:(api) => { ... };
  • 对象形式:{ id, name, configSchema, register(api) { ... } }(也接受activate作为register别名)。

若导出对象声明了id且与 manifest id 不一致,加载器会报plugin id mismatch错误;若 manifest 的kind与导出的kind不一致,则给出警告。

4. 配置校验前置。模块加载后、register调用前,先以 manifest 中的configSchema校验plugins.entries.<id>.config(validateJsonSchemaValue,含applyDefaults),校验失败即pushPluginLoadError("invalid config: ..."),插件不会进入激活状态。

5. 延迟运行时(lazy runtime)。加载器通过 Proxy 延迟实例化插件运行时,只有真正访问运行时能力(如version、config、channel、events等)时才创建,避免启动路径上急切加载所有 channel/runtime 依赖树。

6. 注册表缓存。以(workspace/global/stock 根、插件配置、installs、loadPaths、作用域等)为键缓存 registry(上限 128 条),并保持 LRU 刷新,频繁复用的 registry 免于逐出。

4.1 插件的可注册能力

完成注册的插件可以向api注册(对应 docs/tools/plugin.md 的清单):

  • Gateway RPC 方法(api.registerGatewayMethod,命名pluginId.action);
  • Gateway HTTP 路由(api.registerHttpRoute,必须显式声明auth: "gateway" | "plugin",match支持exact/prefix);
  • Agent 工具(api.registerTool);
  • CLI 命令(api.registerCli);
  • 背景服务(api.registerService);
  • Context engine(api.registerContextEngine);
  • 自动回复命令(api.registerCommand,无需调用 AI agent 即可执行,命令处理先于内置命令与 agent);
  • Provider 认证流程(api.registerProvider,含 OAuth/API key/device code 等方法);
  • 消息 channel(api.registerChannel({ plugin }));
  • 运行时钩子(api.registerHook与类型化的api.on)。

五、契约执行:bundled registry 上的能力归属校验

「契约执行」是src/plugins/边界的一大特色。插件 manifest 可以通过contracts字段声明其静态能力归属:

type PluginManifestContracts = { speechProviders?: string[]; mediaUnderstandingProviders?: string[]; imageGenerationProviders?: string[]; webSearchProviders?: string[]; tools?: string[]; };

这些声明与 src/plugins/contracts/registry.ts 中的契约测试联动。契约 registry 基于BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS与BUNDLED_*_PLUGIN_IDS等生成式元数据,验证插件声明的 provider id、tool 名与 CLI backend 是否与运行时注册一致。配套的契约测试包括loader.contract.test.ts、registry.contract.test.ts、discovery.contract.test.ts、shape.contract.test.ts等,覆盖发现、加载、注册表与能力形状。

边界规则特别强调:契约加载与契约测试必须停留在专用 bundled registry 路径上,不得让契约验证依赖通过无关生产解析流程激活 providers。这样契约测试始终可复现、可离线运行,不会因某个 provider 的认证流程失败而阻塞。

六、边界规则逐条拆解

src/plugins/AGENTS.md 给出的六条边界规则是核心维护者与插件作者都必须遵守的行为约束:

6.1 保持 manifest-first 行为

发现、配置校验与设置应在插件运行时执行之前,仅凭元数据完成。这要求任何新能力在引入时都要考虑:能否先在 manifest 层面表达?能否不执行插件代码就完成验证?从 loader.ts 的流程可以看到,配置校验(validatePluginConfig)严格发生在register(api)之前,正是这一原则的实现证据。

6.2 不创建私有后门

Loader 行为必须与文档化的 Plugin SDK 与 manifest 契约保持一致,不得为 bundled 插件创建外部插件无法使用的私有后门。换言之,内置插件与第三方插件走同一套api表面、同一套 manifest 校验、同一套边界文件检查(内置插件仅豁免硬链接拒绝这一项安全差异)。

6.3 影响插件作者时更新公开契约

如果 loader 或 registry 的改动会影响插件作者,正确做法是同步更新公开 SDK、文档与契约测试,而不是依赖偶然的内部实现细节。这也解释了为什么src/plugins/边界文档明确列出「公开契约」清单——它们是演进时的护栏。

6.4 不将 plugin-owned 规范化为 core-owned

禁止通过在不相关的核心路径中散落直接读取plugins.entries.<id>.config来把「插件拥有的」配置变成「核心拥有的」。正确姿势是:通用辅助函数(generic helpers)、插件运行时钩子、manifest 元数据,以及显式的 auto-enable 接线(例如 manifest 中的autoEnableWhenConfiguredProviders)。这保证了插件配置的读写面收敛、可审计,核心代码不会悄悄依赖某个插件的私有字段。

6.5 保持契约通用并尊重禁用与 SecretRef

当插件拥有的工具或 provider fallback 需要核心参与时,契约必须保持通用,并且尊重插件禁用状态与 SecretRef 语义:插件被禁用后其工具/回退不应生效,配置中的 SecretRef(而非明文密钥)在核心路径上同样要被正确解析。这与provider-auth-ref.ts、provider-auth-storage.ts等认证抽象的设计目标一致——核心只消费通用凭据语义,不关心具体 provider 的存储细节。

6.6 契约测试留在 bundled registry 路径

契约加载与契约测试固定在专用 bundled registry 路径(src/plugins/contracts/registry.ts)上,不得让契约验证依赖激活 providers 的无关生产解析流程。该约束保证了测试确定性,也防止契约校验被外部环境(网络、认证状态)干扰。

七、插件配置与 CLI 实操

7.1 配置示例

{ plugins: { enabled: true, allow: ["voice-call"], deny: ["untrusted-plugin"], load: { paths: ["~/Projects/oss/voice-call-extension"] }, entries: { "voice-call": { enabled: true, config: { provider: "twilio" } }, }, }, }

字段语义:

  • enabled:总开关(默认true);
  • allow:白名单(可选);
  • deny:黑名单(可选,deny 优先);
  • load.paths:额外插件文件/目录;
  • slots:独占槽位选择器,如memory、contextEngine;
  • entries.<id>:单插件开关与配置。

配置变更需要重启 Gateway才能生效。

7.2 独占槽位(slots)

某些插件类别是独占的,同一时刻只激活一个:

{ plugins: { slots: { memory: "memory-core", // 或 "none" 禁用 memory 插件 contextEngine: "legacy", // 或某个插件 id,如 "lossless-claw" }, }, }
  • memory:激活的 memory 插件,"none"表示禁用所有 memory 插件;默认槽位插件是memory-core;
  • contextEngine:激活的 context engine 插件,"legacy"为内置默认。

若多个插件声明kind: "memory"或kind: "context-engine",只有被槽位选中的插件会被加载,其余以诊断信息标记为禁用。memory 插件还会在 loader 中走「快速路径」:当槽位策略保证其被禁用时,直接跳过导入重型插件模块。

7.3 CLI 命令

openclaw plugins list openclaw plugins info <id> openclaw plugins install <path> # 复制本地文件/目录到 ~/.openclaw/extensions/<id> openclaw plugins install ./extensions/voice-call openclaw plugins install ./plugin.tgz openclaw plugins install ./plugin.zip openclaw plugins install -l ./extensions/voice-call # 开发用软链(不复制) openclaw plugins install @openclaw/voice-call # 从 npm 安装 openclaw plugins install @openclaw/voice-call --pin # 记录精确 name@version openclaw plugins update <id> openclaw plugins update --all openclaw plugins enable <id> openclaw plugins disable <id> openclaw plugins doctor

要点:

  • npm spec仅支持 registry 包(包名 + 可选精确版本或 dist-tag),Git/URL/文件 spec 与 semver 区间会被拒绝;
  • 裸 spec 与@latest停留在稳定轨道;若 npm 解析出预发布版本,OpenClaw 会停下并要求显式选择@beta/@rc或精确预发布版本;
  • plugins install以npm install --ignore-scripts安装依赖(不执行生命周期脚本),因此插件依赖树应保持「纯 JS/TS」,避免需要postinstall构建的包;
  • plugins update只对记录在plugins.installs下的 npm 安装生效,完整性元数据变化时会警告并要求确认(可用全局--yes跳过);
  • 原生模块依赖需要文档化构建步骤与包管理器白名单要求(例如 pnpm 的allow-build-scripts与pnpm rebuild <package>)。

八、插件工具与运行时钩子的边界示例

8.1 注册 Agent 工具

agent-tools.md 给出了两种形态。基础工具:

import { Type } from "@sinclair/typebox"; export default function (api) { api.registerTool({ name: "my_tool", description: "Do a thing", parameters: Type.Object({ input: Type.String(), }), async execute(_id, params) { return { content: [{ type: "text", text: params.input }] }; }, }); }

可选工具(opt-in,永远不会被自动启用,必须加入 allowlist):

export default function (api) { api.registerTool( { name: "workflow_tool", description: "Run a local workflow", parameters: { type: "object", properties: { pipeline: { type: "string" } }, required: ["pipeline"], }, async execute(_id, params) { return { content: [{ type: "text", text: params.pipeline }] }; }, }, { optional: true }, ); }

在agents.list[].tools.allow(或全局tools.allow)中启用:

{ agents: { list: [ { id: "main", tools: { allow: [ "workflow_tool", // 具体工具名 "workflow", // 插件 id(启用该插件全部工具) "group:plugins", // 所有插件工具 ], }, }, ], }, }

规则提示:工具名不得与核心工具重名(冲突工具会被跳过);插件 id 用于 allowlist 时也不得与核心工具名冲突;会触发副作用或依赖额外二进制/凭据的工具优先标记为optional: true。

8.2 注册运行时钩子

export default function register(api) { api.on( "before_prompt_build", (event, ctx) => { return { prependSystemContext: "Follow company style guide." }; }, { priority: 10 }, ); }

提示词构建相关的关键钩子:before_model_resolve(会话加载前,可确定性覆盖modelOverride/providerOverride)、before_prompt_build(会话加载后,塑造提示词输入)、before_agent_start(兼容旧钩子)。核心还允许运维按插件关闭提示词篡改类钩子:plugins.entries.<id>.hooks.allowPromptInjection: false。

九、安全模型与发布注意

  • 插件与 Gateway同进程运行,属于可信代码:只安装信任的插件,优先使用plugins.allow白名单;
  • plugins.allow为空且存在可发现的非内置插件时,启动会打印包含插件 id 与来源的警告;
  • 未带安装/加载路径来源追踪的已加载非内置插件会触发警告,用于固定信任(plugins.allow)或安装追踪(plugins.installs);
  • 发布到 npm 的插件包,package.json必须包含openclaw.extensions入口;入口可为.js或.ts(jiti 运行时加载 TS);scoped 包在plugins.entries.*中会归一化为unscopedid;
  • 独立发布的插件应自带 CI(lint/build/test),并保证openclaw.extensions指向构建产物入口(如dist/index.js)。

十、总结

OpenClaw 的插件边界不是一张功能清单,而是一套可执行的架构契约:manifest-first 保证配置校验与插件代码解耦;统一的发现/加载管线保证内置与外部插件行为一致;bundled registry 契约测试保证能力归属可验证;六条边界规则约束核心演进不侵蚀插件所有权。无论是编写一个openclaw.plugin.json的最小插件,还是为 OpenClaw 增加新的插件能力,以上约定都值得作为第一份参考资料——它们同时定义了「插件如何工作」与「插件系统如何被安全地扩展」。

  • 人工智能
  • 大模型
  • LLM 网关
  • AI Agent
  • 即时通讯

【免费下载链接】openclaw-zero-token

OpenClaw: Use All Major AI Models NO API Token! Claude/ChatGPT/Gemini/DeepSeek/Doubao/Grok/Qwen/Manus/Kimi

项目地址:https://gitcode.com/gh_mirrors/op/openclaw-zero-token
点击查看免费下载

相关推荐

上一篇:Bootstrap-rtl的未来:为什么它仍然是Bootstrap 3.x项目的首选RTL解决方案
下一篇:VRCT跨语言交流解决方案:打破VRChat虚拟世界的语言隔阂

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询