Skip to content

插件内部架构

这是深度架构参考文档。如需实践指南,请参见:

本页介绍 OpenClaw 插件系统的内部架构。

公开能力模型

能力(Capability)是 OpenClaw 内部原生插件模型的核心。每个原生 OpenClaw 插件都要注册一种或多种能力类型:

能力类型注册方法示例插件
文本推理api.registerProvider(...)openaianthropic
CLI 推理后端api.registerCliBackend(...)openaianthropic
语音api.registerSpeechProvider(...)elevenlabsmicrosoft
媒体理解api.registerMediaUnderstandingProvider(...)openaigoogle
图像生成api.registerImageGenerationProvider(...)openaigoogle
网络搜索api.registerWebSearchProvider(...)google
Channel / 消息api.registerChannel(...)msteamsmatrix

注册了零个能力但提供 hook、工具或服务的插件是遗留 hook-only 插件。该模式仍完全受支持。

外部兼容性立场

能力模型已落地到核心并被捆绑/原生插件使用,但外部插件兼容性仍需要比"已导出即冻结"更严格的标准。

当前指导原则:

  • 现有外部插件: 保持基于 hook 的集成正常工作;将其视为兼容基准
  • 新的捆绑/原生插件: 优先使用显式能力注册,而非特定厂商的直接访问或新的 hook-only 设计
  • 采用能力注册的外部插件: 允许,但除非文档明确标记某个契约为稳定,否则将能力特定的辅助接口视为演进中的内容

实用规则:

  • 能力注册 API 是预期方向
  • 遗留 hook 仍是外部插件在过渡期间最安全的无破坏路径
  • 导出的辅助子路径并非同等稳定;优先使用文档明确的契约,而非偶然的辅助导出

插件形态

OpenClaw 根据插件实际的注册行为(而非静态元数据)将每个已加载插件分类为一种形态:

  • plain-capability —— 仅注册一种能力类型(例如仅提供商插件 mistral
  • hybrid-capability —— 注册多种能力类型(例如 openai 拥有文本推理、语音、媒体理解和图像生成)
  • hook-only —— 仅注册 hook(类型化或自定义),无能力、工具、命令或服务
  • non-capability —— 注册工具、命令、服务或路由,但无能力

使用 openclaw plugins inspect <id> 查看插件的形态和能力分解。详见 CLI 参考

遗留 Hook

before_agent_start hook 作为 hook-only 插件的兼容路径继续受支持。现实中的遗留插件仍依赖它。

方向:

  • 保持其正常工作
  • 记录为遗留功能
  • 对于模型/提供商覆盖工作,优先使用 before_model_resolve
  • 对于提示词变更工作,优先使用 before_prompt_build
  • 仅在真实使用率下降且测试覆盖证明迁移安全后才移除

兼容性信号

运行 openclaw doctoropenclaw plugins inspect <id> 时,可能会看到以下标签之一:

信号含义
config valid配置解析正常,插件可解析
compatibility advisory插件使用了受支持但较旧的模式(如 hook-only
legacy warning插件使用了已废弃的 before_agent_start
hard error配置无效或插件加载失败

hook-onlybefore_agent_start 目前都不会导致插件失效——hook-only 是建议性的,before_agent_start 仅触发警告。这些信号也会出现在 openclaw status --allopenclaw plugins doctor 中。

架构概览

OpenClaw 的插件系统分四个层次:

  1. 清单 + 发现 OpenClaw 从已配置路径、工作区根目录、全局扩展根目录和捆绑扩展中查找候选插件。发现过程首先读取原生 openclaw.plugin.json 清单和支持的 bundle 清单。
  2. 启用 + 验证 核心决定已发现的插件是启用、禁用、阻止还是为 memory 等独占插槽选择。
  3. 运行时加载 原生 OpenClaw 插件通过 jiti 进程内加载,并将能力注册到中央注册表中。兼容 bundle 被规范化为注册表记录,不导入运行时代码。
  4. 接口消费 OpenClaw 的其余部分读取注册表来暴露工具、频道、提供商配置、hook、HTTP 路由、CLI 命令和服务。

重要设计边界:

  • 发现和配置验证应从清单/schema 元数据工作,无需执行插件代码
  • 原生运行时行为来自插件模块的 register(api) 路径

这种分离使 OpenClaw 可以在完整运行时激活前验证配置、解释缺失/禁用的插件并构建 UI/schema 提示。

Channel 插件与共享消息工具

Channel 插件无需为普通聊天操作注册单独的发送/编辑/反应工具。OpenClaw 在核心保留一个共享的 message 工具,channel 插件负责其背后的频道特定发现和执行。

当前边界是:

  • 核心拥有共享 message 工具宿主、提示词连接、会话/线程记账和执行调度
  • channel 插件拥有范围化的操作发现、能力发现和任何频道特定的 schema 片段
  • channel 插件通过其操作适配器执行最终操作

对于 channel 插件,SDK 接口是 ChannelMessageActionAdapter.describeMessageTool(...)。该统一发现调用让插件能够一起返回其可见操作、能力和 schema 贡献,使这些部分不会产生漂移。

核心将运行时范围传入该发现步骤。重要字段包括:

  • accountId
  • currentChannelId
  • currentThreadTs
  • currentMessageId
  • sessionKey
  • sessionId
  • agentId
  • 受信任的入站 requesterSenderId

这对上下文敏感的插件很重要。channel 可以根据活动账户、当前房间/线程/消息或受信任的请求者身份隐藏或暴露消息操作,无需在核心 message 工具中硬编码 channel 特定的分支。

参见加载管道了解完整启动顺序。

能力所有权模型

OpenClaw 将原生插件视为公司功能的所有权边界,而非无关集成的大杂烩。

这意味着:

  • 公司插件通常应拥有该公司所有面向 OpenClaw 的接口
  • 功能插件通常应拥有它引入的完整功能接口
  • channel 应消费共享核心能力,而不是临时重新实现提供商行为

示例:

  • 捆绑 openai 插件拥有 OpenAI 模型提供商行为以及 OpenAI 语音、媒体理解和图像生成行为
  • 捆绑 elevenlabs 插件拥有 ElevenLabs 语音行为
  • 捆绑 microsoft 插件拥有 Microsoft 语音行为
  • 捆绑 google 插件拥有 Google 模型提供商行为以及 Google 媒体理解、图像生成和网络搜索行为
  • voice-call 插件是功能插件:它拥有通话传输、工具、CLI、路由和运行时,但消费核心 TTS/STT 能力而非创造第二套语音栈

预期的最终状态:

  • OpenAI 即使跨越文本模型、语音、图像和未来视频,也只存在于一个插件中
  • 其他厂商也可以对其自己的接口面积做同样的事情
  • channel 不关心哪个厂商插件拥有提供商;它们消费核心暴露的共享能力契约

这是关键区别:

  • plugin = 所有权边界
  • capability = 核心契约,多个插件可以实现或消费

因此,如果 OpenClaw 添加视频等新领域,第一个问题不是"哪个提供商应该硬编码视频处理?"而是"核心视频能力契约是什么?"一旦该契约存在,厂商插件就可以注册它,channel/功能插件可以消费它。

能力分层

决定代码归属时使用此思维模型:

  • 核心能力层:共享编排、策略、回退、配置合并规则、传递语义和类型化契约
  • 厂商插件层:厂商特定 API、认证、模型目录、语音合成、图像生成、未来视频后端、使用量端点
  • channel/功能插件层:消费核心能力并在接口上呈现的 Slack/Discord/voice-call 等集成

例如,TTS 遵循这种形态:

  • 核心拥有回复时 TTS 策略、回退顺序、偏好和频道传递
  • openaielevenlabsmicrosoft 拥有合成实现
  • voice-call 消费电话 TTS 运行时辅助

同样的模式应优先用于未来的能力。

多能力公司插件示例

公司插件从外部看应感觉连贯。如果 OpenClaw 有模型、语音、媒体理解和网络搜索的共享契约,厂商可以在一个地方拥有其所有接口:

ts
import type { OpenClawPluginDefinition } from "openclaw/plugin-sdk";
import {
  buildOpenAISpeechProvider,
  createPluginBackedWebSearchProvider,
  describeImageWithModel,
  transcribeOpenAiCompatibleAudio,
} from "openclaw/plugin-sdk";

const plugin: OpenClawPluginDefinition = {
  id: "exampleai",
  name: "ExampleAI",
  register(api) {
    api.registerProvider({
      id: "exampleai",
      // 认证/模型目录/运行时 hook
    });

    api.registerSpeechProvider(
      buildOpenAISpeechProvider({
        id: "exampleai",
        // 厂商语音配置
      }),
    );

    api.registerMediaUnderstandingProvider({
      id: "exampleai",
      capabilities: ["image", "audio", "video"],
      async describeImage(req) {
        return describeImageWithModel({
          provider: "exampleai",
          model: req.model,
          input: req.input,
        });
      },
      async transcribeAudio(req) {
        return transcribeOpenAiCompatibleAudio({
          provider: "exampleai",
          model: req.model,
          input: req.input,
        });
      },
    });

    api.registerWebSearchProvider(
      createPluginBackedWebSearchProvider({
        id: "exampleai-search",
        // 凭据 + 请求逻辑
      }),
    );
  },
};

export default plugin;

重要的不是确切的辅助函数名称,而是形态:

  • 一个插件拥有厂商接口
  • 核心仍拥有能力契约
  • channel 和功能插件消费 api.runtime.* 辅助函数,而非厂商代码
  • 契约测试可以断言插件注册了它声称拥有的能力

契约与执行

插件 API 接口有意在 OpenClawPluginApi 中类型化并集中化。该契约定义了支持的注册点以及插件可依赖的运行时辅助函数。

这很重要的原因:

  • 插件作者获得一个稳定的内部标准
  • 核心可以拒绝重复所有权,如两个插件注册相同的提供商 id
  • 启动时可以为格式错误的注册提供可操作的诊断
  • 契约测试可以强制捆绑插件所有权并防止静默漂移

有两层执行:

  1. 运行时注册执行 插件注册表在插件加载时验证注册。例如:重复的提供商 id、重复的语音提供商 id 和格式错误的注册会产生插件诊断,而不是未定义的行为。
  2. 契约测试 捆绑插件在测试运行期间被捕获到契约注册表中,使 OpenClaw 可以显式断言所有权。目前用于模型提供商、语音提供商、网络搜索提供商和捆绑注册所有权。

实际效果是 OpenClaw 预先知道哪个插件拥有哪个接口,使核心和 channel 能够无缝组合,因为所有权是声明的、类型化的和可测试的,而非隐式的。

执行模型

原生 OpenClaw 插件在进程内与 Gateway 一起运行。它们没有沙盒化。已加载的原生插件与核心代码具有相同的进程级信任边界。

含义:

  • 原生插件可以注册工具、网络处理程序、hook 和服务
  • 原生插件的 bug 可能导致 Gateway 崩溃或不稳定
  • 恶意原生插件等同于在 OpenClaw 进程内执行任意代码

兼容 bundle 默认更安全,因为 OpenClaw 目前将其视为元数据/内容包。在当前版本中,这主要意味着捆绑技能。

对非捆绑插件使用允许列表和显式安装/加载路径。将工作区插件视为开发时代码,而非生产默认值。

对于捆绑工作区包名,在 npm 名称中锚定插件 id:默认为 @openclaw/<id>,或当包有意暴露较窄的插件角色时,使用经批准的类型后缀,如 -provider-plugin-speech-sandbox-media-understanding

重要信任说明:

  • plugins.allow 信任插件 id,而非来源出处。
  • 具有与捆绑插件相同 id 的工作区插件在该工作区插件启用/允许列表时会有意遮蔽捆绑副本。
  • 这对于本地开发、补丁测试和热修复是正常且有用的。

加载管道

启动时,OpenClaw 大致执行以下操作:

  1. 发现候选插件根目录
  2. 读取原生或兼容 bundle 清单和包元数据
  3. 拒绝不安全的候选者
  4. 规范化插件配置(plugins.enabledallowdenyentriesslotsload.paths
  5. 决定每个候选者的启用状态
  6. 通过 jiti 加载已启用的原生模块
  7. 调用原生 register(api) hook 并将注册收集到插件注册表中
  8. 向命令/运行时接口暴露注册表

安全门在运行时执行之前发生。当条目逃脱插件根目录、路径是世界可写的或路径所有权对非捆绑插件来说看起来可疑时,候选者会被阻止。

清单优先行为

清单是控制平面的真相来源。OpenClaw 使用它来:

  • 识别插件
  • 发现声明的频道/技能/配置 schema 或 bundle 能力
  • 验证 plugins.entries.<id>.config
  • 增强控制 UI 标签/占位符
  • 显示安装/目录元数据

对于原生插件,运行时模块是数据平面部分。它注册 hook、工具、命令或提供商流程等实际行为。

加载器缓存内容

OpenClaw 为以下内容保留短暂的进程内缓存:

  • 发现结果
  • 清单注册表数据
  • 已加载的插件注册表

这些缓存减少了突发启动和重复命令开销。可以将其视为短暂的性能缓存,而非持久化。

性能说明:

  • 设置 OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE=1OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE=1 来禁用这些缓存。
  • 使用 OPENCLAW_PLUGIN_DISCOVERY_CACHE_MSOPENCLAW_PLUGIN_MANIFEST_CACHE_MS 调整缓存窗口。

注册表模型

已加载的插件不直接修改随机的核心全局变量,它们注册到中央插件注册表中。

注册表跟踪:

  • 插件记录(身份、来源、状态、诊断)
  • 工具
  • 遗留 hook 和类型化 hook
  • channel
  • 提供商
  • Gateway RPC 处理程序
  • HTTP 路由
  • CLI 注册器
  • 后台服务
  • 插件拥有的命令

核心功能然后从该注册表读取,而不是直接与插件模块交互。这保持了加载的单向性:

  • 插件模块 -> 注册表注册
  • 核心运行时 -> 注册表消费

该分离对可维护性很重要。这意味着大多数核心接口只需要一个集成点:"读取注册表",而不是"对每个插件模块进行特殊处理"。

对话绑定回调

绑定对话的插件可以在审批解决时做出反应。

使用 api.onConversationBindingResolved(...) 在绑定请求批准或拒绝后接收回调:

ts
export default {
  id: "my-plugin",
  register(api) {
    api.onConversationBindingResolved(async (event) => {
      if (event.status === "approved") {
        // 此插件 + 对话现在存在绑定
        console.log(event.binding?.conversationId);
        return;
      }

      // 请求被拒绝;清除任何本地待处理状态
      console.log(event.request.conversation.conversationId);
    });
  },
};

回调载荷字段:

  • status"approved""denied"
  • decision"allow-once""allow-always""deny"
  • binding:已批准请求的已解析绑定
  • request:原始请求摘要、解除提示、发送者 id 和对话元数据

此回调仅用于通知。它不改变谁可以绑定对话,并在核心审批处理完成后运行。

Provider 运行时 Hook

Provider 插件现在有两个层次:

  • 清单元数据:providerAuthEnvVars 用于在运行时加载前便宜的环境认证查找,加上 providerAuthChoices 用于在运行时加载前便宜的引导/认证选择标签和 CLI 标志元数据
  • 配置时 hook:catalog / 遗留 discovery
  • 运行时 hook:resolveDynamicModelprepareDynamicModelnormalizeResolvedModelcapabilitiesprepareExtraParamswrapStreamFnformatApiKeyrefreshOAuthbuildAuthDoctorHintisCacheTtlEligiblebuildMissingAuthMessagesuppressBuiltInModelaugmentModelCatalogisBinaryThinkingsupportsXHighThinkingresolveDefaultThinkingLevelisModernModelRefprepareRuntimeAuthresolveUsageAuthfetchUsageSnapshot

OpenClaw 仍拥有通用 agent 循环、故障转移、转录处理和工具策略。这些 hook 是提供商特定行为的扩展接口,无需完整的自定义推理传输。

Hook 顺序与用途

对于模型/提供商插件,OpenClaw 大致按以下顺序调用 hook。"使用时机"列是快速决策指南。

#Hook功能使用时机
1catalogmodels.json 生成期间将提供商配置发布到 models.providers提供商拥有目录或基础 URL 默认值
2resolveDynamicModel针对尚未在本地注册表中的提供商拥有的模型 id 的同步回退提供商接受任意上游模型 id
3prepareDynamicModel异步预热,然后 resolveDynamicModel 再次运行提供商在解析未知 id 前需要网络元数据
4normalizeResolvedModel嵌入式运行器使用已解析模型前的最终重写提供商需要传输重写但仍使用核心传输
5capabilities提供商拥有的转录/工具元数据,供共享核心逻辑使用提供商需要转录/提供商族特殊处理
6prepareExtraParams通用流选项包装器前的请求参数规范化提供商需要默认请求参数或按提供商的参数清理
7wrapStreamFn通用包装器应用后的流包装器提供商需要请求头/正文/模型兼容包装器而不需要自定义传输
8formatApiKey认证配置格式化器:存储的配置成为运行时 apiKey 字符串提供商存储额外认证元数据并需要自定义运行时令牌形态
9refreshOAuth针对自定义刷新端点或刷新失败策略的 OAuth 刷新覆盖提供商不适合共享的 pi-ai 刷新器
10buildAuthDoctorHintOAuth 刷新失败时附加的修复提示提供商需要刷新失败后提供商拥有的认证修复指导
19prepareRuntimeAuth推理前将配置的凭据交换为实际运行时令牌/密钥提供商需要令牌交换或短期请求凭据
20resolveUsageAuth/usage 和相关状态接口解析使用量/计费凭据提供商需要自定义使用量/配额令牌解析或不同的使用量凭据
21fetchUsageSnapshot认证解析后获取并规范化提供商特定的使用量/配额快照提供商需要提供商特定的使用量端点或载荷解析器

如果提供商需要完全自定义的线路协议或自定义请求执行器,那是另一类扩展。这些 hook 用于仍在 OpenClaw 正常推理循环上运行的提供商特定行为。

运行时辅助函数

插件可以通过 api.runtime 访问选定的核心辅助函数。TTS 示例:

ts
const clip = await api.runtime.tts.textToSpeech({
  text: "Hello from OpenClaw",
  cfg: api.config,
});

const voices = await api.runtime.tts.listVoices({
  provider: "elevenlabs",
  cfg: api.config,
});

插件也可以通过 api.registerSpeechProvider(...) 注册语音提供商。

对于图像/音频/视频理解,插件注册一个类型化的媒体理解提供商:

ts
api.registerMediaUnderstandingProvider({
  id: "google",
  capabilities: ["image", "audio", "video"],
  describeImage: async (req) => ({ text: "..." }),
  transcribeAudio: async (req) => ({ text: "..." }),
  describeVideo: async (req) => ({ text: "..." }),
});

插件还可以通过 api.runtime.subagent 启动后台子 agent 运行:

ts
const result = await api.runtime.subagent.run({
  sessionKey: "agent:main:subagent:search-helper",
  message: "Expand this query into focused follow-up searches.",
  provider: "openai",
  model: "gpt-4.1-mini",
  deliver: false,
});

对于网络搜索,插件可以消费共享运行时辅助函数:

ts
const result = await api.runtime.webSearch.search({
  config: api.config,
  args: {
    query: "OpenClaw plugin runtime helpers",
    count: 5,
  },
});

Gateway HTTP 路由

插件可以通过 api.registerHttpRoute(...) 暴露 HTTP 端点:

ts
api.registerHttpRoute({
  path: "/acme/webhook",
  auth: "plugin",
  match: "exact",
  handler: async (_req, res) => {
    res.statusCode = 200;
    res.end("ok");
    return true;
  },
});

路由字段:

  • path:Gateway HTTP 服务器下的路由路径
  • auth:必填。使用 "gateway" 要求正常的 Gateway 认证,或 "plugin" 用于插件管理的认证/webhook 验证
  • match:可选。"exact"(默认)或 "prefix"
  • replaceExisting:可选。允许同一插件替换其自己的现有路由注册
  • handler:路由处理请求时返回 true

注意:api.registerHttpHandler(...) 已过时,请使用 api.registerHttpRoute(...)

插件 SDK 导入路径

编写插件时,使用 SDK 子路径而非整体 openclaw/plugin-sdk 导入:

  • openclaw/plugin-sdk/plugin-entry 用于插件注册原语
  • openclaw/plugin-sdk/core 用于通用共享的插件面向契约
  • 稳定的 channel 原语,如 openclaw/plugin-sdk/channel-setupopenclaw/plugin-sdk/channel-pairingopenclaw/plugin-sdk/channel-contract

兼容性说明:

  • 对新代码避免根 openclaw/plugin-sdk barrel
  • 优先使用较窄的稳定原语
  • 捆绑扩展特定辅助 barrel 默认不稳定

添加新能力

当插件需要当前 API 不适合的行为时,不要绕过插件系统进行私有直接访问,而是添加缺失的能力。

推荐顺序:

  1. 定义核心契约
  2. 添加类型化的插件注册/运行时接口
  3. 连接核心 + channel/功能消费者
  4. 注册厂商实现
  5. 添加契约覆盖

这就是 OpenClaw 保持主观而不被硬编码到一个提供商世界观中的方式。详见 Capability Cookbook 获取具体的文件清单和完整示例。