Appearance
插件内部架构
这是深度架构参考文档。如需实践指南,请参见:
- 安装和使用插件 —— 用户指南
- 快速入门 —— 第一个插件教程
- Channel 插件 —— 构建消息频道
- Provider 插件 —— 构建模型提供商
- SDK 概览 —— 导入映射和注册 API
本页介绍 OpenClaw 插件系统的内部架构。
公开能力模型
能力(Capability)是 OpenClaw 内部原生插件模型的核心。每个原生 OpenClaw 插件都要注册一种或多种能力类型:
| 能力类型 | 注册方法 | 示例插件 |
|---|---|---|
| 文本推理 | api.registerProvider(...) | openai、anthropic |
| CLI 推理后端 | api.registerCliBackend(...) | openai、anthropic |
| 语音 | api.registerSpeechProvider(...) | elevenlabs、microsoft |
| 媒体理解 | api.registerMediaUnderstandingProvider(...) | openai、google |
| 图像生成 | api.registerImageGenerationProvider(...) | openai、google |
| 网络搜索 | api.registerWebSearchProvider(...) | google |
| Channel / 消息 | api.registerChannel(...) | msteams、matrix |
注册了零个能力但提供 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 doctor 或 openclaw plugins inspect <id> 时,可能会看到以下标签之一:
| 信号 | 含义 |
|---|---|
| config valid | 配置解析正常,插件可解析 |
| compatibility advisory | 插件使用了受支持但较旧的模式(如 hook-only) |
| legacy warning | 插件使用了已废弃的 before_agent_start |
| hard error | 配置无效或插件加载失败 |
hook-only 和 before_agent_start 目前都不会导致插件失效——hook-only 是建议性的,before_agent_start 仅触发警告。这些信号也会出现在 openclaw status --all 和 openclaw plugins doctor 中。
架构概览
OpenClaw 的插件系统分四个层次:
- 清单 + 发现 OpenClaw 从已配置路径、工作区根目录、全局扩展根目录和捆绑扩展中查找候选插件。发现过程首先读取原生
openclaw.plugin.json清单和支持的 bundle 清单。 - 启用 + 验证 核心决定已发现的插件是启用、禁用、阻止还是为 memory 等独占插槽选择。
- 运行时加载 原生 OpenClaw 插件通过 jiti 进程内加载,并将能力注册到中央注册表中。兼容 bundle 被规范化为注册表记录,不导入运行时代码。
- 接口消费 OpenClaw 的其余部分读取注册表来暴露工具、频道、提供商配置、hook、HTTP 路由、CLI 命令和服务。
重要设计边界:
- 发现和配置验证应从清单/schema 元数据工作,无需执行插件代码
- 原生运行时行为来自插件模块的
register(api)路径
这种分离使 OpenClaw 可以在完整运行时激活前验证配置、解释缺失/禁用的插件并构建 UI/schema 提示。
Channel 插件与共享消息工具
Channel 插件无需为普通聊天操作注册单独的发送/编辑/反应工具。OpenClaw 在核心保留一个共享的 message 工具,channel 插件负责其背后的频道特定发现和执行。
当前边界是:
- 核心拥有共享
message工具宿主、提示词连接、会话/线程记账和执行调度 - channel 插件拥有范围化的操作发现、能力发现和任何频道特定的 schema 片段
- channel 插件通过其操作适配器执行最终操作
对于 channel 插件,SDK 接口是 ChannelMessageActionAdapter.describeMessageTool(...)。该统一发现调用让插件能够一起返回其可见操作、能力和 schema 贡献,使这些部分不会产生漂移。
核心将运行时范围传入该发现步骤。重要字段包括:
accountIdcurrentChannelIdcurrentThreadTscurrentMessageIdsessionKeysessionIdagentId- 受信任的入站
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 策略、回退顺序、偏好和频道传递
openai、elevenlabs和microsoft拥有合成实现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
- 启动时可以为格式错误的注册提供可操作的诊断
- 契约测试可以强制捆绑插件所有权并防止静默漂移
有两层执行:
- 运行时注册执行 插件注册表在插件加载时验证注册。例如:重复的提供商 id、重复的语音提供商 id 和格式错误的注册会产生插件诊断,而不是未定义的行为。
- 契约测试 捆绑插件在测试运行期间被捕获到契约注册表中,使 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 大致执行以下操作:
- 发现候选插件根目录
- 读取原生或兼容 bundle 清单和包元数据
- 拒绝不安全的候选者
- 规范化插件配置(
plugins.enabled、allow、deny、entries、slots、load.paths) - 决定每个候选者的启用状态
- 通过 jiti 加载已启用的原生模块
- 调用原生
register(api)hook 并将注册收集到插件注册表中 - 向命令/运行时接口暴露注册表
安全门在运行时执行之前发生。当条目逃脱插件根目录、路径是世界可写的或路径所有权对非捆绑插件来说看起来可疑时,候选者会被阻止。
清单优先行为
清单是控制平面的真相来源。OpenClaw 使用它来:
- 识别插件
- 发现声明的频道/技能/配置 schema 或 bundle 能力
- 验证
plugins.entries.<id>.config - 增强控制 UI 标签/占位符
- 显示安装/目录元数据
对于原生插件,运行时模块是数据平面部分。它注册 hook、工具、命令或提供商流程等实际行为。
加载器缓存内容
OpenClaw 为以下内容保留短暂的进程内缓存:
- 发现结果
- 清单注册表数据
- 已加载的插件注册表
这些缓存减少了突发启动和重复命令开销。可以将其视为短暂的性能缓存,而非持久化。
性能说明:
- 设置
OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE=1或OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE=1来禁用这些缓存。 - 使用
OPENCLAW_PLUGIN_DISCOVERY_CACHE_MS和OPENCLAW_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:
resolveDynamicModel、prepareDynamicModel、normalizeResolvedModel、capabilities、prepareExtraParams、wrapStreamFn、formatApiKey、refreshOAuth、buildAuthDoctorHint、isCacheTtlEligible、buildMissingAuthMessage、suppressBuiltInModel、augmentModelCatalog、isBinaryThinking、supportsXHighThinking、resolveDefaultThinkingLevel、isModernModelRef、prepareRuntimeAuth、resolveUsageAuth、fetchUsageSnapshot
OpenClaw 仍拥有通用 agent 循环、故障转移、转录处理和工具策略。这些 hook 是提供商特定行为的扩展接口,无需完整的自定义推理传输。
Hook 顺序与用途
对于模型/提供商插件,OpenClaw 大致按以下顺序调用 hook。"使用时机"列是快速决策指南。
| # | Hook | 功能 | 使用时机 |
|---|---|---|---|
| 1 | catalog | 在 models.json 生成期间将提供商配置发布到 models.providers | 提供商拥有目录或基础 URL 默认值 |
| 2 | resolveDynamicModel | 针对尚未在本地注册表中的提供商拥有的模型 id 的同步回退 | 提供商接受任意上游模型 id |
| 3 | prepareDynamicModel | 异步预热,然后 resolveDynamicModel 再次运行 | 提供商在解析未知 id 前需要网络元数据 |
| 4 | normalizeResolvedModel | 嵌入式运行器使用已解析模型前的最终重写 | 提供商需要传输重写但仍使用核心传输 |
| 5 | capabilities | 提供商拥有的转录/工具元数据,供共享核心逻辑使用 | 提供商需要转录/提供商族特殊处理 |
| 6 | prepareExtraParams | 通用流选项包装器前的请求参数规范化 | 提供商需要默认请求参数或按提供商的参数清理 |
| 7 | wrapStreamFn | 通用包装器应用后的流包装器 | 提供商需要请求头/正文/模型兼容包装器而不需要自定义传输 |
| 8 | formatApiKey | 认证配置格式化器:存储的配置成为运行时 apiKey 字符串 | 提供商存储额外认证元数据并需要自定义运行时令牌形态 |
| 9 | refreshOAuth | 针对自定义刷新端点或刷新失败策略的 OAuth 刷新覆盖 | 提供商不适合共享的 pi-ai 刷新器 |
| 10 | buildAuthDoctorHint | OAuth 刷新失败时附加的修复提示 | 提供商需要刷新失败后提供商拥有的认证修复指导 |
| 19 | prepareRuntimeAuth | 推理前将配置的凭据交换为实际运行时令牌/密钥 | 提供商需要令牌交换或短期请求凭据 |
| 20 | resolveUsageAuth | 为 /usage 和相关状态接口解析使用量/计费凭据 | 提供商需要自定义使用量/配额令牌解析或不同的使用量凭据 |
| 21 | fetchUsageSnapshot | 认证解析后获取并规范化提供商特定的使用量/配额快照 | 提供商需要提供商特定的使用量端点或载荷解析器 |
如果提供商需要完全自定义的线路协议或自定义请求执行器,那是另一类扩展。这些 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-setup、openclaw/plugin-sdk/channel-pairing、openclaw/plugin-sdk/channel-contract等
兼容性说明:
- 对新代码避免根
openclaw/plugin-sdkbarrel - 优先使用较窄的稳定原语
- 捆绑扩展特定辅助 barrel 默认不稳定
添加新能力
当插件需要当前 API 不适合的行为时,不要绕过插件系统进行私有直接访问,而是添加缺失的能力。
推荐顺序:
- 定义核心契约
- 添加类型化的插件注册/运行时接口
- 连接核心 + channel/功能消费者
- 注册厂商实现
- 添加契约覆盖
这就是 OpenClaw 保持主观而不被硬编码到一个提供商世界观中的方式。详见 Capability Cookbook 获取具体的文件清单和完整示例。