【深入浅出OpenClaw】第五篇:插件架构 – 可扩展性设计
《深入浅出 OpenClaw》
一个系统性介绍 OpenClaw 架构与实现的专栏系列。
文章列表
# 主题 1 总览:OpenClaw 全景图 2 Gateway:控制中枢 3 Channel 系统:统一的消息抽象 4 Agent 系统:AI 大脑 5 插件架构:可扩展性设计 6 配置与安全 7 Memory 系统:语义记忆 8 跨平台原生应用 9 工程实践 阅读建议
入门读者:从第 1 篇开始,建立全局认知 特定兴趣:根据目录跳转到感兴趣的模块 贡献者:重点阅读第 5 篇(插件架构)和第 9 篇(工程实践) 关于
本专栏基于 OpenClaw 代码库分析撰写,旨在帮助开发者理解其设计理念和实现细节。
第五篇:插件架构 – 可扩展性设计
插件系统是 OpenClaw 生态的基石,使 35+ 消息渠道和各种集成得以共存。
概述
OpenClaw 的插件架构建立在三大支柱之上:
|
|
|
|---|---|
| Plugin SDK |
|
| Plugin Management |
|
| Extensions |
|
源码结构
src/plugin-sdk/ # 插件公开 API (36 文件)
├── index.ts # 主导出 (~150+ 重导出)
├── types.ts # 核心类型定义
├── runtime.ts # 运行时助手
├── account-id.ts # 账户标识
├── command-auth.ts # 命令授权
├── allow-from.ts # Allowlist 匹配
├── group-access.ts # 群组访问控制
├── pairing-access.ts # 配对访问
├── json-store.ts # JSON 存储
├── webhook-targets.ts # Webhook 管理
├── tool-send.ts # 工具发送
├── onboarding.ts # 引导流程
├── reply-payload.ts # 回复负载
├── text-chunking.ts # 文本分块
└── ...
src/plugins/ # 插件管理
├── loader.ts # 主加载器 (726 行)
├── discovery.ts # 插件发现 (636 行)
├── manifest.ts # 清单加载
├── registry.ts # 注册表 (520 行)
├── config-state.ts # 启用/禁用逻辑
├── hooks.ts # Hook 执行引擎 (754 行)
└── runtime/ # 插件运行时
├── index.ts # 运行时创建 (467 行)
└── types.ts # 运行时类型
extensions/ # 44+ 插件
├── discord/
├── slack/
├── telegram/
├── voice-call/
├── memory-core/
├── memory-lancedb/
└── ...
插件定义
模块结构
插件可以导出两种形式:
// 形式 1: 定义对象
exportdefault {
id: "my-plugin",
name: "My Plugin",
description: "A custom plugin",
version: "1.0.0",
configSchema: { ... },
register(api) {
// 注册功能
},
activate(api) {
// 激活后执行
}
};
// 形式 2: 函数(语法糖)
exportdefaultfunction(api: OpenClawPluginApi) {
// 直接注册
}
类型定义
type OpenClawPluginModule =
| OpenClawPluginDefinition
| ((api: OpenClawPluginApi) => void | Promise<void>);
typeOpenClawPluginDefinition = {
id?: string;
name?: string;
description?: string;
version?: string;
kind?: "memory"; // 特殊类型:内存插件
configSchema?: OpenClawPluginConfigSchema;
register?: (api: OpenClawPluginApi) =>void | Promise<void>;
activate?: (api: OpenClawPluginApi) =>void | Promise<void>;
};
Plugin API
API 表面
OpenClawPluginApi 是插件与核心的契约:
type OpenClawPluginApi = {
// 元数据
id: string; // 插件 ID
name: string; // 显示名称
version?: string; // 版本
description?: string; // 描述
source: string; // 源文件绝对路径
config: OpenClawConfig; // 完整应用配置
pluginConfig?: Record<string, unknown>; // 插件特定配置
// 运行时
runtime: PluginRuntime; // 运行时助手
logger: PluginLogger; // 结构化日志
resolvePath: (input: string) =>string; // 路径解析
// 注册方法
registerTool(...);
registerHook(...);
registerHttpHandler(...);
registerHttpRoute(...);
registerChannel(...);
registerProvider(...);
registerGatewayMethod(...);
registerCli(...);
registerService(...);
registerCommand(...);
on(...);
};
注册方法详解
|
|
|
|---|---|
registerTool |
|
registerHook |
|
registerHttpHandler |
|
registerHttpRoute |
|
registerChannel |
|
registerProvider |
|
registerGatewayMethod |
|
registerCli |
|
registerService |
|
registerCommand |
|
on |
|
Hook 系统
23 个 Hook 点
OpenClaw 提供 23 个 Hook 点,覆盖完整的 Agent 生命周期:
Agent 生命周期:
|
|
|
|---|---|
before_model_resolve |
|
before_prompt_build |
|
before_agent_start |
|
llm_input |
|
llm_output |
|
agent_end |
|
before_compaction |
|
after_compaction |
|
before_reset |
|
消息处理:
|
|
|
|---|---|
message_received |
|
message_sending |
|
message_sent |
|
工具执行:
|
|
|
|---|---|
before_tool_call |
|
after_tool_call |
|
tool_result_persist |
|
消息持久化:
|
|
|
|---|---|
before_message_write |
|
会话 & 子 Agent:
|
|
|
|---|---|
session_start |
|
session_end |
|
subagent_spawning |
|
subagent_delivery_target |
|
subagent_spawned |
|
subagent_ended |
|
Gateway:
|
|
|
|---|---|
gateway_start |
|
gateway_stop |
|
Hook 执行模型
┌─────────────────────────────────────────────────────────────────┐
│ Hook 执行模型 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Void Hooks(并行执行) │
│ ├─ 所有处理器并发运行 │
│ ├─ 无返回值合并 │
│ └─ 错误处理由 catchErrors 选项控制 │
│ │
│ Modifying Hooks(按优先级顺序执行) │
│ ├─ 按优先级顺序执行(最高优先级先执行) │
│ ├─ 结果通过 mergeResults 回调合并 │
│ ├─ 覆盖:先定义者优先 │
│ └─ 上下文:拼接 │
│ │
│ Synchronous Hooks(同步热路径) │
│ ├─ 不允许 async(Promise 检测 + 警告/错误) │
│ ├─ 按优先级顺序执行 │
│ ├─ 消息通过处理器链传递 │
│ └─ { block: true } 可提前退出 │
│ │
└─────────────────────────────────────────────────────────────────┘
Hook 合并策略
|
|
|
|---|---|
before_model_resolve |
|
before_prompt_build |
|
subagent_spawning |
|
message_sending |
|
before_tool_call |
|
tool_result_persist |
|
before_message_write |
|
插件发现
发现来源
插件从四个来源发现:
|
|
|
|
|---|---|---|
config |
plugins.loadPaths[] |
|
workspace |
.openclaw/extensions/ |
|
global |
~/.openclaw/extensions/ |
|
bundled |
|
|
发现算法
对于每个来源 in [config, workspace, global, bundled]:
对于 来源路径 中的每个 目录/文件:
如果是文件且有扩展名 (.ts, .js, .mts, 等):
添加候选(file)
如果是目录:
如果存在 package.json:
如果定义了 openclaw.extensions[]:
对于 extensions 中的每个条目:
安全解析包内路径()
添加候选()
否则:
查找 index.ts/js/mjs/cjs
添加候选()
否则:
递归发现目录内容()
安全检查
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
插件清单
openclaw.plugin.json
{
"id": "voice-call",
"name": "Voice Call Plugin",
"description": "Phone call integration",
"version": "2026.2.15",
"kind": "memory",
"channels": ["voice-call"],
"providers": [],
"skills": [],
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": { "type": "boolean" },
"provider": { "enum": ["telnyx", "twilio", "plivo", "mock"] }
}
},
"uiHints": {
"provider": {
"label": "Provider",
"help": "Use twilio, telnyx, or mock..."
},
"telnyx.apiKey": {
"label": "Telnyx API Key",
"sensitive": true
}
}
}
清单字段
|
|
|
|
|---|---|---|
id |
|
|
configSchema |
|
|
kind |
|
|
channels |
|
|
providers |
|
|
skills |
|
|
name |
|
|
description |
|
|
version |
|
|
uiHints |
|
插件加载
加载序列
┌─────────────────────────────────────────────────────────────────┐
│ 插件加载序列 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 发现 & 过滤 │
│ ├─ 从所有来源发现候选 │
│ ├─ 为所有候选加载清单 │
│ └─ 按启用/禁用状态过滤 │
│ │
│ 2. 启用/禁用逻辑 │
│ ├─ plugins.enabled === false → 全部禁用 │
│ ├─ bundled 来源且不在 allow 列表 → 禁用 │
│ └─ 否则 → 启用 │
│ │
│ 3. 内存槽位选择 │
│ ├─ 同时只能激活一个 "memory" 插件 │
│ └─ plugins.slots.memory 指定选中的 ID │
│ │
│ 4. 模块加载(使用 Jiti) │
│ ├─ 懒创建 Jiti 加载器 │
│ ├─ 配置别名: │
│ │ openclaw/plugin-sdk → 实际 SDK 路径 │
│ ├─ 支持 .ts, .js, .mts, .cts, .mjs, .cjs, .json │
│ └─ 解析 default 导出或直接导出 │
│ │
│ 5. 配置验证 │
│ ├─ 根据清单 Schema 验证插件配置 │
│ └─ 无效时返回错误 │
│ │
│ 6. 注册 │
│ ├─ 为插件创建 OpenClawPluginApi │
│ ├─ 调用 register(api) 或 activate(api) │
│ └─ 追踪注册的 tools, hooks, channels 等 │
│ │
└─────────────────────────────────────────────────────────────────┘
错误处理
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
缓存
-
注册表按 (workspaceDir, plugins config hash) 缓存 -
缓存键: workspaceDir::JSON.stringify(normalizedPluginsConfig)
插件注册表
注册表结构
type PluginRegistry = {
plugins: PluginRecord[]; // 已加载插件 + 元数据
tools: PluginToolRegistration[]; // 工具
hooks: PluginHookRegistration[]; // Hook
typedHooks: PluginHookRegistration<K>[]; // 类型化 Hook
channels: PluginChannelRegistration[]; // 渠道
providers: PluginProviderRegistration[]; // 提供商
gatewayHandlers: GatewayRequestHandlers; // Gateway 方法
httpHandlers: PluginHttpRegistration[]; // HTTP 处理器
httpRoutes: PluginHttpRouteRegistration[]; // HTTP 路由
cliRegistrars: PluginCliRegistration[]; // CLI 注册器
services: PluginServiceRegistration[]; // 服务
commands: PluginCommandRegistration[]; // 命令
diagnostics: PluginDiagnostic[]; // 诊断信息
};
注册验证
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
运行时助手
PluginRuntime
提供对核心功能的广泛 API 访问:
配置管理:
runtime.loadConfig(); // 加载配置
runtime.writeConfigFile(); // 写入配置
系统:
runtime.enqueueSystemEvent(); // 触发系统事件
runtime.runCommandWithTimeout(); // 执行外部进程
runtime.formatNativeDependencyHint(); // 原生依赖帮助文本
媒体处理:
runtime.detectMime(); // MIME 检测
runtime.mediaKindFromMime(); // 媒体类型
runtime.getImageMetadata(); // 图片元数据
runtime.resizeToJpeg(); // 图片压缩
runtime.isVoiceCompatibleAudio(); // 音频兼容性
runtime.loadWebMedia(); // 加载网络媒体
渠道助手:
// 文本工具
runtime.chunkText(); // 文本分块
runtime.detectControlCommand(); // 命令检测
// 回复分发
runtime.dispatchBufferedReply(); // 缓冲回复
runtime.sendTypingIndicator(); // 输入状态
// 路由
runtime.resolveAgentRoute(); // Agent 路由解析
// 会话
runtime.readTranscript(); // 读取记录
runtime.writeTranscript(); // 写入记录
渠道特定 API:
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
授权与访问控制
命令授权
functionresolveSenderCommandAuthorization(params: {
cfg: OpenClawConfig;
isGroup: boolean;
dmPolicy: "allowlist" | "open" | ...;
senderId: string;
isSenderAllowed: (senderId, allowFrom) => boolean;
}): {
effectiveAllowFrom: string[];
senderAllowedForCommands: boolean;
commandAuthorized: boolean | undefined;
}
群组访问控制
functionevaluateSenderGroupAccess(params: {
providerConfigPresent: boolean;
configuredGroupPolicy?: "allowlist" | "open" | "disabled";
defaultGroupPolicy?: GroupPolicy;
groupAllowFrom: string[];
senderId: string;
isSenderAllowed: (senderId, allowFrom) => boolean;
}): SenderGroupAccessDecision
返回: allowed, groupPolicy, providerMissingFallbackApplied, reason
高级模式
工具注册
静态工具:
api.registerTool({
name: "send_message",
description: "Send a message",
inputSchema: {
type: "object",
properties: {
to: { type: "string" },
text: { type: "string" }
},
required: ["to", "text"]
},
execute: async (params) => {
// 执行逻辑
return { success: true };
}
});
动态工具工厂:
api.registerTool(
(ctx: OpenClawPluginToolContext) => {
// 根据上下文决定是否提供工具
if (!ctx.sessionKey) returnnull;
return {
name: "session_tool",
execute: async (params) => { ... }
};
},
{ names: ["session_tool"], optional: true }
);
渠道注册
api.registerChannel({
plugin: {
id: "my-channel",
meta: {
label: "My Channel",
docsPath: "/channels/my-channel"
},
capabilities: {
chatTypes: ["direct", "group"],
reactions: true,
media: true
},
config: {
listAccountIds: (cfg) => [...],
resolveAccount: (cfg, id) => { ... }
},
outbound: {
deliveryMode: "direct",
sendText: async (ctx) => { ... }
}
},
dock: {
// UI 元数据
}
});
服务注册
api.registerService({
id: "my-background-service",
start: async (ctx) => {
// 启动后台任务
setInterval(() => {
ctx.logger.info("Service tick");
}, 60000);
},
stop: async (ctx) => {
// 清理资源
}
});
CLI 扩展
api.registerCli((ctx) => {
ctx.program
.command("my-command <arg>")
.description("My custom command")
.option("--flag", "A flag option")
.action(async (arg, opts) => {
console.log(`Running with ${arg}, flag=${opts.flag}`);
});
}, { commands: ["my-command"] });
HTTP 路由
api.registerHttpRoute({
path: "/webhook/my-plugin",
handler: async (req, res) => {
const body = await readJsonBodyWithLimit(req, 1024 * 1024);
// 处理 webhook
res.statusCode = 200;
res.end(JSON.stringify({ ok: true }));
}
});
Gateway 方法
api.registerGatewayMethod("plugin.getStatus", async (params, respond) => {
respond({
ok: true,
data: {
status: "running",
uptime: process.uptime()
}
});
});
自定义命令
api.registerCommand({
name: "tts",
description: "Toggle text-to-speech",
acceptsArgs: false,
requireAuth: true,
handler: async (ctx: PluginCommandContext) => {
if (!ctx.isAuthorizedSender) {
return { text: "Unauthorized" };
}
// 切换 TTS 状态
const enabled = toggleTts(ctx.sessionKey);
return { text: `TTS ${enabled ? "enabled" : "disabled"}` };
}
});
命令在 Agent 调用前处理,可短路 LLM 调用。
配置集成
openclaw.json 中的插件配置
{
"plugins": {
"enabled": true,
"allow": ["discord", "voice-call"],
"loadPaths": ["/opt/custom-plugins"],
"slots": {
"memory": "memory-core"
},
"entries": {
"voice-call": {
"enabled": true,
"config": {
"provider": "twilio",
"twilio": {
"accountSid": "...",
"authToken": "..."
}
}
}
},
"installs": {
"voice-call": {
"installPath": "/home/user/.openclaw/extensions/voice-call",
"sourcePath": "src/index.ts"
}
}
}
}
package.json 中的插件清单
{
"name": "@openclaw/voice-call",
"version": "2026.2.15",
"openclaw": {
"extensions": ["src/index.ts"],
"channel": {
"id": "voice-call",
"label": "Voice Call",
"docsPath": "/voice-call"
}
}
}
扩展示例
最小示例:Discord
// openclaw.plugin.json
{
"id": "discord",
"channels": ["discord"],
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {}
}
}
// src/index.ts
exportdefaultfunction(api: OpenClawPluginApi) {
api.registerChannel({
plugin: discordChannelPlugin,
});
}
复杂示例:Voice Call
// openclaw.plugin.json
{
"id": "voice-call",
"configSchema": {
"type": "object",
"properties": {
"enabled": { "type": "boolean" },
"provider": { "enum": ["telnyx", "twilio", "plivo", "mock"] },
"telnyx": {
"type": "object",
"properties": {
"apiKey": { "type": "string" },
"connectionId": { "type": "string" }
}
},
"twilio": {
"type": "object",
"properties": {
"accountSid": { "type": "string" },
"authToken": { "type": "string" }
}
}
}
},
"uiHints": {
"provider": {
"label": "Provider",
"help": "Use twilio, telnyx, or mock for testing"
},
"telnyx.apiKey": {
"label": "Telnyx API Key",
"sensitive": true
},
"twilio.authToken": {
"label": "Twilio Auth Token",
"sensitive": true
}
}
}
设计原则
1. 模块化分离
SDK 导出 600+ 项,但插件通过 tree-shaking 只导入需要的部分。
2. 安全优先
-
边界检查的文件读取 -
权限检查(全局可写、所有权) -
非内置插件的 Allow-list -
Jiti 沙箱模块加载
3. 性能优化
-
懒加载(WebAssembly、重型模块按需加载) -
Void Hook 并行,Modifying Hook 顺序执行 -
同步热路径 Hook(无 async) -
按配置哈希缓存注册表
4. 错误弹性
-
插件错误隔离(不崩溃核心) -
收集诊断信息用于调试 -
配置无效时优雅回退
5. 扩展性
-
23 个 Hook 点覆盖完整生命周期 -
工具、渠道、提供商、服务、命令 -
HTTP 路由、CLI 命令、Gateway 方法 -
自定义配置 Schema
6. 开发体验
-
TypeScript 优先(Jiti 处理编译) -
丰富的上下文对象 -
全面的运行时助手 -
清晰的错误消息和诊断
下一篇预告
第六篇:配置与安全 — 深入探索 OpenClaw 的配置系统和安全模型:
-
Zod Schema 配置验证 -
多层配置继承 -
安全审计系统 -
凭证加密存储 -
权限模型与访问控制
本文由象信AI基于 OpenClaw v2026.2.27 源码分析撰写。如有疑问或建议,欢迎反馈。
夜雨聆风
