乐于分享
好东西不私藏

【深入浅出OpenClaw】第五篇:插件架构 – 可扩展性设计

【深入浅出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
公开 API,供插件开发者使用
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
添加 Agent 工具(LLM 可调用的函数)
registerHook
订阅生命周期事件
registerHttpHandler
通用 HTTP 请求处理
registerHttpRoute
规范化 HTTP 路由(冲突检测)
registerChannel
添加新消息渠道
registerProvider
注册 LLM/API 提供商
registerGatewayMethod
添加 Gateway RPC 方法
registerCli
扩展 CLI 命令
registerService
启动后台服务
registerCommand
注册绕过 LLM 的文本命令
on
类型化 Hook 订阅

Hook 系统

23 个 Hook 点

OpenClaw 提供 23 个 Hook 点,覆盖完整的 Agent 生命周期:

Agent 生命周期:

Hook
触发时机
before_model_resolve
可覆盖模型/提供商选择
before_prompt_build
注入 System Prompt / 上下文
before_agent_start
遗留:组合以上两者
llm_input
观察发送给 LLM 的最终负载
llm_output
观察 LLM 响应
agent_end
Agent 执行完成
before_compaction
历史压缩前
after_compaction
历史压缩后
before_reset
/new 或 /reset 清除会话前

消息处理:

Hook
触发时机
message_received
从渠道收到入站消息
message_sending
发送到渠道前(可修改/取消)
message_sent
发送确认或失败

工具执行:

Hook
触发时机
before_tool_call
可修改参数或阻止
after_tool_call
观察 Hook
tool_result_persist
同步 Hook(消息序列化热路径)

消息持久化:

Hook
触发时机
before_message_write
同步,可阻止或修改 JSONL 写入

会话 & 子 Agent:

Hook
触发时机
session_start
新会话开始
session_end
会话结束
subagent_spawning
生成子 Agent 前
subagent_delivery_target
路由到子 Agent
subagent_spawned
子 Agent 启动
subagent_ended
子 Agent 终止

Gateway:

Hook
触发时机
gateway_start
Gateway 启动
gateway_stop
Gateway 停止

Hook 执行模型

┌─────────────────────────────────────────────────────────────────┐
│                    Hook 执行模型                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Void Hooks(并行执行)                                          │
│  ├─ 所有处理器并发运行                                           │
│  ├─ 无返回值合并                                                 │
│  └─ 错误处理由 catchErrors 选项控制                              │
│                                                                  │
│  Modifying Hooks(按优先级顺序执行)                             │
│  ├─ 按优先级顺序执行(最高优先级先执行)                         │
│  ├─ 结果通过 mergeResults 回调合并                              │
│  ├─ 覆盖:先定义者优先                                          │
│  └─ 上下文:拼接                                                │
│                                                                  │
│  Synchronous Hooks(同步热路径)                                 │
│  ├─ 不允许 async(Promise 检测 + 警告/错误)                    │
│  ├─ 按优先级顺序执行                                            │
│  ├─ 消息通过处理器链传递                                        │
│  └─ { block: true } 可提前退出                                  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Hook 合并策略

Hook
合并策略
before_model_resolve
先定义者覆盖(高优先级)
before_prompt_build
拼接上下文,先定义的 System Prompt 优先
subagent_spawning
合并 threadBindingReady (OR),错误停止链
message_sending
合并内容 + 取消标志
before_tool_call
合并参数 + 阻止标志
tool_result_persist
消息通过处理器传递
before_message_write
消息传递,block 时提前退出

插件发现

发现来源

插件从四个来源发现:

来源
路径
优先级
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
          添加候选()
      否则:
        递归发现目录内容()

安全检查

检查
说明
路径逃逸
源路径不能逃出插件根目录
写权限
路径不能是全局可写(mode & 0o002 == 0)
所有权
非内置插件检查可疑所有权(uid != process.uid && uid != 0)

插件清单

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
✅
JSON Schema 用于验证
kind
“memory” 用于内存插件(每槽位只允许一个)
channels
提供的渠道 ID 列表
providers
提供的提供商 ID 列表
skills
技能/功能列表
name
显示名称
description
描述
version
版本
uiHints
Dashboard 配置表单的 UI 提示

插件加载

加载序列

┌─────────────────────────────────────────────────────────────────┐
│                    插件加载序列                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  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 等                    │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

错误处理

错误类型
处理
加载错误
status: “error”,插件仍添加到注册表
注册错误
status: “error”,不再调用插件
配置验证错误
status: “error”,跳过插件

缓存

  • 注册表按 (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[];          // 诊断信息
};

注册验证

验证
结果
重复渠道 ID
错误诊断
重复提供商 ID
错误诊断
重复 Gateway 方法
错误诊断
重复 HTTP 路由
错误诊断
重复命令名称
错误诊断
缺少必需字段
警告

运行时助手

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:

渠道
API
Discord
messageActions, auditPermissions, directory, probe, allowlists
Slack
directory, probe, allowlists, messageActions
Telegram
auditMembership, groupUnmentionedTracking, probe, messageActions
Signal
probe, send, monitor
iMessage
monitor, probe, send
WhatsApp
webLogin (QR/manual), monitor, actions, send, polls
LINE
accountManagement, flexMessages, templateMessages, quickReplies

授权与访问控制

命令授权

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 源码分析撰写。如有疑问或建议,欢迎反馈。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 【深入浅出OpenClaw】第五篇:插件架构 – 可扩展性设计

评论 抢沙发

1 + 2 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮