乐于分享
好东西不私藏

【深入浅出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

返回: allowedgroupPolicyproviderMissingFallbackAppliedreason

高级模式

工具注册

静态工具

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 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮