OpenClaw
← 总览
01
plugins · the architecture

插件系统总览

OpenClaw 是插件优先(plugin-first)的运行时:仓库内有 141 个扩展、16 个包。Provider、消息渠道、工具、MCP 服务器、外部 Agent Harness、记忆后端——几乎所有能力都不是硬编码进核心,而是通过插件注册进来的。

把内核看作一个极薄的协调层:它只负责加载 manifest、构造一份精简过的 API 对象、再把插件注册进来的能力汇聚到一个全局注册表中。所有「OpenClaw 能做什么」的答案,最终都落在某个插件的注册调用上。这种设计让模型供应商、消息平台、工具集可以各自独立演进、独立发版,而无需触碰内核。

插件系统的核心由四个源文件构成,它们的职责边界非常清晰:types.ts 定义对插件暴露的公共 API 面,manifest.ts 负责 openclaw.plugin.json 的解析与缓存,api-builder.ts 按需裁剪并构造每个插件实际拿到的 API 对象,而 runtime.ts 则在 globalThis 上维护一个进程级的全局注册表,协调热替换与回收。理解这四个文件,就理解了整个插件层。

src/plugins

types.ts

公共 API 面定义——OpenClawPluginApi 接口与全部 register* 方法签名。

src/plugins

manifest.ts

解析 openclaw.plugin.json,带 SLRU 缓存与 mtime/ctime 失效跟踪。

src/plugins

api-builder.ts

用 BuildPluginApiParams 按需裁剪并构造插件实际持有的 API 对象。

src/plugins

runtime.ts

挂在 globalThis 上的全局 RegistryState 协调器,管理热替换与回收。

02
api · the public surface

OpenClawPluginApi 公共接口

插件能调用的一切,都收敛在 src/plugins/types.ts 定义的 OpenClawPluginApi 接口上;而 api-builder.ts 则用 BuildPluginApiParams 决定每个插件实际拿到哪些方法。

api-builder.ts · BuildPluginApiParams
export type BuildPluginApiParams = {
  id: string;
  name: string;
  version?: string;
  source: string;
  registrationMode: OpenClawPluginApi["registrationMode"];
  config: OpenClawConfig;
  runtime: PluginRuntime;
  logger: PluginLogger;
  handlers?: Partial<Pick<OpenClawPluginApi,
    | "registerTool"          // 注册工具
    | "registerHook"          // 注册生命周期钩子
    | "registerHttpRoute"     // 注册 HTTP 路由
    | "registerChannel"       // 注册消息渠道
    | "registerGatewayMethod" // 注册 Gateway 方法
    | "registerCli"           // 注册 CLI 命令
    | "registerProvider"      // 注册 Provider(模型/语音/图像等)
    | "registerAgentHarness"  // 注册外部 Agent Harness(ACP)
    | "registerMcpServer"     // 注册 MCP 服务器
    | ...(共 30+ 项)
  >>;
};

注意 handlers 的类型:Partial<Pick<OpenClawPluginApi, ...>>。这意味着插件只需实现自己关心的注册方法——一个纯 Provider 插件可能只提供 registerProvider,宿主会据此裁剪它实际拿到的 API 面,避免把无关能力暴露给插件。下表按能力域归纳了这 30+ 个注册方法。

表 01 · 注册方法按能力域归纳
能力域注册方法作用
模型与执行 registerTool · registerProvider · registerMcpServer 暴露工具、接入模型/语音/图像 provider、挂载 MCP server
外部 Agent registerAgentHarness 把 Claude Code / Codex 等 ACP harness 接进来
消息与路由 registerChannel · registerHttpRoute · registerGatewayMethod 新增渠道、HTTP 端点、Gateway RPC 方法
生命周期与 CLI registerHook · registerCli 注入 start/stop/enable/disable 钩子、扩展 CLI 命令
裁剪而非全开:handlers 是 Partial<Pick<...>>——插件只实现它关心的那几个注册方法,宿主据此收缩 API 面。这既限制了插件的能力边界,也让类型系统在编译期就能反映「这个插件能做什么」。
03
manifest · plugin metadata

Manifest 解析与缓存

每个插件都由一份 openclaw.plugin.json 描述。src/plugins/manifest.ts 负责解析这份清单,并用一套带失效跟踪的 SLRU 缓存避免重复读盘与重复解析。

manifest.ts
export const PLUGIN_MANIFEST_FILENAME = "openclaw.plugin.json";
export const MAX_PLUGIN_MANIFEST_BYTES = 256 * 1024;

// manifest 解析缓存(SLRU,512 条目)
const pluginManifestLoadCache = new PluginLruCache<PluginManifestLoadCacheEntry>(
  MAX_PLUGIN_MANIFEST_LOAD_CACHE_ENTRIES,
);

export function createPluginCacheKey(...): PluginCacheKey;
export function clearPluginManifestLoadCache(): void;

manifest 承载了插件的关键元数据:版本号、入口点、配置 schema、命令列表、对外暴露的 provider、以及生命周期 hook。解析结果会进入一个最多 512 条目的 SLRU 缓存(PluginLruCache);每条缓存项都记录源文件的 mtimeMs 与 ctimeMs,一旦文件被修改即自动失效并重解析。文件体积有 256 KiB 的硬上限(MAX_PLUGIN_MANIFEST_BYTES),防止异常清单拖垮加载。

convention

文件名固定

约定文件名 openclaw.plugin.json,由 PLUGIN_MANIFEST_FILENAME 常量统一引用。

limit

256 KiB 上限

MAX_PLUGIN_MANIFEST_BYTES = 256 * 1024,超限即拒绝解析。

cache

SLRU · 512

解析结果进入 512 条目的 SLRU 缓存,复用读盘与解析成本。

invalidate

mtime + ctime

每条缓存项跟踪 mtimeMs/ctimeMs,文件变更即失效。

测试钩子:createPluginCacheKey() 暴露缓存键的构造逻辑,clearPluginManifestLoadCache() 则用于在测试或热重载场景下整体清空缓存——它们是这套缓存对外的两个显式接缝。
04
runtime · registry coordinator

运行时协调器

src/plugins/runtime.ts 在 globalThis 上维护唯一的 RegistryState——一份进程级的全局注册表,所有插件注册进来的能力都汇聚于此,并由它协调热替换与回收。

runtime.ts · 全局 RegistryState
// runtime.ts — 全局 RegistryState 保存在 globalThis 上
const state: RegistryState = (() => {
  const globalState = globalThis as typeof globalThis & {
    [PLUGIN_REGISTRY_STATE]?: RegistryState;
  };
  let registryState = globalState[PLUGIN_REGISTRY_STATE];
  if (!registryState) {
    registryState = {
      activeRegistry: null,
      activeVersion: 0,
      httpRoute: { registry: null, pinned: false, version: 0 },
      channel: { registry: null, pinned: false, version: 0 },
      importedPluginIds: new Set<string>(),
      runtimeSubagentMode: "default",
    };
    globalState[PLUGIN_REGISTRY_STATE] = registryState;
  }
  return registryState;
})();

function asPluginRegistry(registry: RegistryState["activeRegistry"]): PluginRegistry | null;
async function cleanupPreviousPluginHostRegistry(params: {...}): Promise<void>;
function isRegistryLive(registry: PluginRegistry): boolean;
function isRegistryPinned(registry: PluginRegistry): boolean;

关键设计是每进程一份全局注册表:状态挂在 globalThis[PLUGIN_REGISTRY_STATE] 上,即使模块被多次加载也只会有一份。在这份注册表之上有三个视角,它们指向同一份底层数据但各自带版本号:activeRegistry(配 activeVersion,每次热替换递增)、httpRoute 与 channel(各自带 pinned 与 version)。pinned 标记用来保护「正在被使用」的注册表不被回收。

view · active

activeRegistry 主视角

当前生效的注册表,配 activeVersion,每次热替换自增以标记代际。

view · http

httpRoute 路由视角

HTTP 路由维度的注册表视图,独立持有 pinned 与 version。

view · channel

channel 渠道视角

消息渠道维度的注册表视图,同样独立追踪 pinned 与 version。

回收前的双重检查:cleanupPreviousPluginHostRegistry() 在回收旧的宿主注册表前,会先用 isRegistryLive() 与 isRegistryPinned() 双重确认——被 pin 住(仍在服务请求)的注册表会被跳过,避免把还在用的能力拆掉。
05
cold plane · activation boundary

冷控制面与激活边界

深读源码时浮现出一条关键设计线——插件激活边界(plugin-activation-boundary):manifest 解析、模型目录、ID 归一化这些路径,刻意不导入插件运行时代码。

这意味着「选哪个模型」「列出有哪些能力」这类操作,可以在插件真正激活之前完成。冷路径不触碰 runtime,因此既快又稳定,还有一个额外好处:因为这些前置步骤不引入随插件激活而变化的状态,它们对提示缓存(prompt caching)友好——模型目录与归一化结果在激活前后保持一致,缓存命中不被打破。

而真正的「热路径」发生在激活时:runtime 的 registerApiProvider() 把每个 provider 的 API 类型字符串(如 anthropic-messages / openai-completions / google-generative-ai ……)连同它的 stream 函数一起登记进一张表。只有走到这一步,provider 才真正具备发起流式请求的能力。

边界即性能:把「读元数据/选模型/归一化」与「激活/注册 stream」清晰分开,是这套架构能在大量插件下保持启动可控、缓存稳定的关键。冷路径永不触发激活。
cold path

冷路径 · 不触发激活

manifest 解析、模型目录、ID 归一化——均不导入插件 runtime,可在激活前运行,且对 prompt caching 友好。

hot path

热路径 · registerApiProvider

激活时 registerApiProvider() 注册 API 类型字符串与 stream 函数,provider 至此才能真正发流。

06
ecosystem · the numbers

插件生态规模

把镜头拉远看数字:141 个扩展覆盖了从模型供应到消息渠道、记忆后端、基础设施的全部能力——而这一切都建立在前面那套统一的注册接口之上。

在这 141 个扩展中,约 68 个是 provider(模型/语音/图像供应方),约 20 个是 channel(消息渠道),其余为记忆后端、工具与基础设施类。供应侧有 30+ 家厂商,但它们最终收敛到约 10 个 API 家族;工具 schema 会按家族做归一化——例如 Gemini 会剥掉 pattern/minLength,OpenAI 则把工具包进 strict JSON。

表 02 · 141 个扩展按类型拆分
类型数量说明
Provider(模型/语音/图像)~6830+ 厂商收敛到 ~10 个 API 家族,schema 按家族归一化
Channel(消息渠道)~20新增渠道经 registerChannel 接入
记忆 / 工具 / 基础设施其余memory 后端、工具集与底层基础设施扩展
合计141分布在 16 个包中,全部经统一注册接口接入
为什么 plugin-first 能 scale:每一项能力都是一个隔离、可独立发版的扩展——provider 升级 API、channel 接入新平台、工具修补 schema,都不必触碰内核,也不会互相牵连。内核只做协调,规模便随插件数量线性铺开而非耦合膨胀。
Plugin · 插件系统
src/plugins/ · types · manifest · api-builder · runtime
← 返回总览 · github ↗