【OpenClaw】源码剖析(四):Channel 与 Skills——连接 25+ 平台的适配魔法
前三篇我们拆完了 OpenClaw 的骨架(全局架构)、神经中枢(Gateway)和大脑(Agent Loop)。今天进入两个最"接地气"的子系统——Channel 和 Skills。Channel 解决的问题是:如何让同一个 AI 助手同时跑在 Telegram、Discord、微信、飞书、Slack、WhatsApp、iMessage 等 25+ 个平台上,而且消息不串、格式不错、能力不丢?
OpenClaw 源码剖析(四):Channel 与 Skills——连接 25+ 平台的适配魔法

写在前面:前三篇我们拆完了 OpenClaw 的骨架(全局架构)、神经中枢(Gateway)和大脑(Agent Loop)。今天进入两个最"接地气"的子系统——Channel 和 Skills。Channel 解决的问题是:如何让同一个 AI 助手同时跑在 Telegram、Discord、微信、飞书、Slack、WhatsApp、iMessage 等 25+ 个平台上,而且消息不串、格式不错、能力不丢? Skills 解决的问题是:如何让 AI 助手的能力无限扩展,而不需要改一行框架代码? 这两个系统看似独立,实则共享同一个设计哲学——适配器模式 + 插件化架构。理解了它们,你就理解了 OpenClaw 为什么能"一套框架,多个通道,无限技能"。

📑 文章目录
- 📌 一、Channel 系统:25+ 平台,一套抽象
- 🏗️ 二、三层架构:Agent → 抽象层 → 平台实现
- 🪶 三、Dock vs Plugin:轻量注册与重量加载
- 📨 四、消息管道:Inbound 与 Outbound 的完整旅程
- 📊 五、平台能力矩阵:不是所有通道都生而平等
- ⚡ 六、Skills 系统:AI 的"技能树"是怎么长的
- 📦 七、SKILL.md 详解:一个技能的完整解剖
- 🏪 八、ClawHub:从开发到发布的完整闭环
- 🧩 九、源码导航
- 🔮 十、系列预告
📌 一、Channel 系统:25+ 平台,一套抽象
1.1 问题的本质
让 AI 助手接入一个消息平台不难——写个 Telegram Bot,调几个 API,半天搞定。难的是接入 25+ 个平台,而且每个平台的 API 风格、消息格式、能力集、速率限制、认证方式都完全不同。如果每个平台都写一套独立的消息处理逻辑,代码会变成一个巨大的 if-else 地狱,维护成本指数级增长。
OpenClaw 的解法是经典的适配器模式(Adapter Pattern)——定义一套统一的 Channel 抽象接口,每个平台实现自己的适配器。上层代码(Gateway、Agent)只依赖抽象接口,不依赖任何平台特定代码。新增一个平台?写一个适配器,注册到 PluginRegistry,完事。
1.2 Channel 系统的设计目标
| 目标 | 实现方式 |
|---|---|
| 平台无关 | 统一的 ChannelPlugin 接口,上层代码零平台依赖 |
| 按需加载 | Dock 轻量注册 + Plugin 懒加载,不用的平台不占内存 |
| 能力声明 | capabilities 显式声明每个平台支持什么,Agent 据此调整行为 |
| 安全隔离 | 每个 Channel 有独立的 allowlist、mention 规则、DM 策略 |
| 热插拔 | 通道可以运行时启停,不影响其他通道 |
🏗️ 二、三层架构:Agent → 抽象层 → 平台实现

OpenClaw 的 Channel 系统采用经典的三层架构,每一层都有明确的职责边界:
2.1 顶层:Agent / LLM 层
这是消息的"消费者"。Agent Loop 接收到标准化的 MsgContext 后,不关心消息来自哪个平台——它只看到一个统一的对话上下文。同样,Agent 生成回复时,也不需要知道回复会发到哪个平台——它只输出标准文本,由下层负责格式化。
这种设计的好处是显而易见的:Agent 的代码完全不需要 if (platform === 'telegram') 这样的分支判断。你换一个平台,Agent 的行为完全不变。
2.2 中间层:Channel 抽象层
这是整个系统的"翻译官"。它定义了三个核心抽象:
- ChannelPlugin 接口:每个平台必须实现的标准方法集(outbound、security、status 等)
- MsgContext:统一的消息上下文,封装了文本、媒体、元信息、平台 ID
- ChannelOutboundAdapter:出站消息的统一发送接口,处理分块、格式化、发送
抽象层还负责"能力协商"——通过 capabilities 字段,每个平台声明自己支持什么(DM、群组、线程、反应、图片、音频、文档、投票、流式输出)。Agent 在生成回复时,会参考这些能力来决定输出格式。比如,如果平台不支持流式输出,Agent 就不会尝试逐 token 推送。
2.3 底层:平台实现层
每个平台一个独立模块,实现 ChannelPlugin 接口。这是唯一需要处理平台特定逻辑的地方——Telegram 用 grammY、Discord 用 discord.js、WhatsApp 用 Baileys、Slack 用 Bolt SDK、iMessage 用 BlueBubbles、Signal 用 signal-cli、飞书用 WebSocket API。
每个平台实现都像一个"翻译官"——把平台特定的 API 调用翻译成统一的 MsgContext,把统一的 SendContext 翻译成平台特定的 API 调用。
🪶 三、Dock vs Plugin:轻量注册与重量加载

这是 OpenClaw Channel 系统中最精妙的设计之一——两级加载策略。
3.1 为什么需要两级加载?
想象一下:你有 25 个 Channel 配置,但用户只启用了 Telegram 和 Discord。如果启动时就把所有 25 个平台的 SDK 都加载进来,不仅浪费内存(WhatsApp 的 Baileys 就很重),还会拖慢启动速度。OpenClaw 的解法是:先注册元信息(Dock),再按需加载实现(Plugin)。
3.2 Dock:轻量注册
Dock(src/channels/dock.ts)在 Gateway 启动时运行,只注册每个通道的元信息:
// Dock 注册的内容(极轻量)
{
id: 'telegram',
label: 'Telegram',
capabilities: { chatTypes: ['direct', 'group'], threads: true, reactions: true, ... },
allowlistFormat: 'comma-separated',
mentionRules: { groupRequireMention: true },
threadingDefaults: { enabled: true }
}
Dock 不加载任何 SDK,不建立任何连接。它的唯一目的是让 Gateway 知道"有这个通道可用",以便在配置界面显示、在路由时查找。类比:电话簿里的名字——知道谁在,但不拨号。
3.3 Plugin:重量加载
Plugin(src/channels/plugins/*)在通道实际启动时才加载,包含完整的 ChannelPlugin 实现:
// Plugin 实现的内容(完整功能)
interface ChannelPlugin {
meta: { id, label, selectionLabel, docsPath, blurb, aliases };
capabilities: { chatTypes, media, threads, reactions };
config: { listAccountIds, resolveAccount };
outbound: { deliveryMode, sendText, sendMedia };
setup?: SetupWizard; // 可选:配置向导
security?: SecurityPolicy; // 可选:安全策略
status?: HealthChecker; // 可选:健康检查
gateway?: GatewayManager; // 可选:Gateway 守护进程
mentions?: MentionHandler; // 可选:@提及处理
threading?: ThreadingHandler; // 可选:线程管理
streaming?: StreamingHandler; // 可选:流式输出
actions?: MessageActions; // 可选:按钮/卡片
}
Plugin 通过 PluginRegistry 注册,支持懒加载和缓存:
// 懒加载 + 缓存
export async function loadChannelPlugin(id: ChannelId) {
const cached = cache.get(id);
if (cached) return cached; // 命中缓存直接返回
const entry = registry.channels.find(e => e.plugin.id === id);
if (entry) {
cache.set(id, entry.plugin); // 缓存供下次使用
return entry.plugin;
}
return undefined;
}
缓存会在 PluginRegistry 变更时自动失效(比如热重载),确保开发时的体验流畅。
📨 四、消息管道:Inbound 与 Outbound 的完整旅程
4.1 Inbound:平台事件 → Agent
每条消息从平台到达 Agent,都要经过一条标准化的管道:
Platform Event → Monitor/Handler → Normalize → Gate → Dispatch → Agent Loop
Step 1: Monitor/Handler — 平台特定的事件监听器捕获原始事件(Telegram Update、Discord Message Create 等)。
Step 2: Normalize — 将平台特定的事件格式转换为统一的 MsgContext。这是适配器最核心的工作——提取文本内容、解析媒体附件、统一用户 ID 格式、识别消息类型(DM/群组/频道)。
Step 3: Gate — 三道安全门:
- AllowList 检查:该用户/群组是否在白名单内?
- Mention 检查:群组消息是否 @了机器人?(群组默认需要 @才响应)
- Command 检查:是否是斜杠命令?(如
/help、/skill)
Step 4: Dispatch — 通过 resolveRoute 确定目标 Agent 和 SessionKey,然后通过 Lane Queue 串行化投递(第二篇讲过的)。
4.2 Outbound:Agent 回复 → 平台
Agent 生成回复后,出站管道同样标准化:
Agent Response → resolveTarget → chunk text → format → send
Step 1: resolveTarget — 解析目标地址。每个平台的地址格式不同:Telegram 用 telegram:<chatId>,Discord 用 Snowflake ID,WhatsApp 用 E.164 JID,Slack 用 Channel ID。
Step 2: chunk text — 将长文本按平台限制分块。Telegram 单条消息上限 4096 字符,Discord 2000 字符,WhatsApp 65536 字符。分块逻辑会尽量在句子边界切分,保持语义完整。
Step 3: format — 平台特定的格式化。Telegram 支持 MarkdownV2,Discord 支持 Markdown,WhatsApp 支持简单的加粗和斜体,Slack 有自己的 mrkdwn 格式。格式化器会做必要的转义和降级。
Step 4: send — 调用平台 API 发送。这里有三种投递模式:
| 模式 | 说明 | 使用平台 |
|---|---|---|
| direct | 进程内直接调用 API | Telegram、Discord、Slack、Signal、iMessage |
| gateway | 通过 Gateway 守护进程路由 | WhatsApp(Baileys 在 Gateway 内运行) |
| hybrid | 先 direct,失败回退 gateway | 预留,未来使用 |
WhatsApp 使用 gateway 模式是因为 Baileys(无头 WhatsApp Web)运行在 Gateway 守护进程内,出站消息必须通过它路由,而不是独立发起 API 调用。
📊 五、平台能力矩阵:不是所有通道都生而平等

这是 Channel 系统中最容易被忽视、却最影响用户体验的设计——能力声明(Capabilities)。每个平台支持的功能集差异巨大,OpenClaw 通过 capabilities 字段显式声明这些差异,Agent 据此调整行为。
5.1 关键能力差异
| 能力 | 影响范围 | 差异示例 |
|---|---|---|
| 线程 | 回复是否以线程形式组织 | Telegram/Discord/Slack/飞书支持;WhatsApp/Signal/iMessage 不支持 |
| 反应 | 用户能否对消息加表情 | 大部分支持;iMessage 仅 Tapback(有限) |
| 流式输出 | Agent 能否逐 token 推送 | Telegram 支持(编辑消息);WhatsApp/Signal 不支持 |
| 投票 | 能否创建投票 | 仅 Discord 和 WhatsApp 原生支持 |
| 媒体上限 | 文件大小限制 | 飞书 20MB > WhatsApp/iMessage 16MB > Telegram 5MB |
5.2 Agent 如何适配能力差异
Agent 在生成回复时,会检查当前通道的 capabilities:
- 不支持线程 → 所有回复以平铺消息发送,不使用 reply_to
- 不支持流式 → 等完整回复生成后一次性发送,不做逐 token 推送
- 媒体超限 → 自动压缩或转为链接分享
- 不支持投票 → 用文本列表替代原生投票组件
这种"能力协商"的设计,确保了 AI 助手在每个平台上的体验都是该平台上的最佳体验,而不是"最低公分母"体验。
⚡ 六、Skills 系统:AI 的"技能树"是怎么长的
如果说 Channel 是 OpenClaw 的"手"(连接外部世界),那 Skills 就是 OpenClaw 的"技能"(决定能做什么)。Skills 系统的设计哲学和 Channel 一脉相承——插件化、声明式、按需加载。
6.1 什么是 Skill?
Skill 是一个自包含的能力单元,由一个 SKILL.md 文件和可选的支撑脚本组成。它定义了 AI 助手的一项新能力——可以是"搜索网页"、“生成图片”、“操作 GitHub”、“发送邮件”、"转换文件格式"等任何你能想到的事情。
关键设计决策:Skill 不是代码插件,而是提示词插件。SKILL.md 告诉 AI “你能做什么、怎么做”,AI 通过已有的工具(bash、read_file、write_file 等)来执行。这意味着你不需要写复杂的 TypeScript 插件代码,只需要写一份清晰的 Markdown 说明文件。
6.2 Skill 的三层优先级
OpenClaw 的 Skill 加载遵循严格的三层优先级:
| 优先级 | 位置 | 说明 |
|---|---|---|
| 最高 | ~/.openclaw/skills/ |
用户自定义技能,覆盖一切 |
| 中 | ClawHub 安装的技能 | 社区技能,openclaw skill install 安装 |
| 最低 | src/skills/ |
OpenClaw 内置 49 个技能 |
如果用户自定义了一个与内置技能同名的 Skill,自定义版本会覆盖内置版本。这让用户可以轻松定制 AI 的行为,而不需要修改框架代码。
6.3 Skill 的生命周期
一个 Skill 从创建到被 Agent 使用,经历四个阶段:
1. 发现(Discover) — Gateway 启动时扫描技能目录,读取所有 SKILL.md,构建技能索引。技能快照在会话创建时固化——如果你在会话中途添加了新技能,需要重启会话才能生效。
2. 加载(Load) — 会话创建时,buildSkillsSection() 将所有活跃技能的摘要信息拼装到 System Prompt 中。注意:这里只加载摘要,不加载完整内容,以节省 token。
3. 触发(Trigger) — Agent 在推理时,根据 System Prompt 中的技能描述,决定是否使用某个技能。触发方式有两种:
- 斜杠命令:用户输入
/weather 北京,直接触发 weather 技能 - 自然语言:用户说"北京今天天气怎么样",Agent 自主判断应该调用 weather 技能
4. 执行(Execute) — Agent 读取 SKILL.md 的完整内容(通过 read_file 工具),按照其中的指令调用支撑脚本或工具。执行在沙箱中进行,受 Tool Policy 约束。
📦 七、SKILL.md 详解:一个技能的完整解剖
一个完整的 SKILL.md 包含以下 Section:
---
name: notebook-to-docx
description: 将 Jupyter Notebook 转换为 Word 文档
trigger: /notebook-to-docx <notebook_path>
---
# Notebook to DOCX
将 .ipynb 文件转换为格式化的 .docx 文档。
## Features
- 保留 Markdown 单元格的格式(标题、列表、链接、图片)
- 代码单元格带语法高亮
- 输出单元格(文本、图片、表格)完整保留
- 自动生成目录
## Usage
当用户要求转换 notebook 时,运行以下命令:
\`\`\`bash
python3 ~/.openclaw/skills/notebook-to-docx/notebook_to_docx.py <notebook_path>
\`\`\`
## Requirements
- Python 3.10+
- pip install nbformat python-docx pygments
## Notes
- 输出文件默认与输入文件同目录
- 大型 notebook(>50MB)可能需要较长时间
7.1 Front Matter(元信息)
--- 包裹的 YAML 头部,定义技能的元信息:名称、描述、触发命令。这些信息会被 buildSkillsSection() 提取并注入 System Prompt,让 Agent 知道"有这个技能可用"。
7.2 正文(指令)
Markdown 正文是给 AI 看的"操作手册"。它告诉 AI:
- 什么时候用:触发条件(用户要求转换 notebook)
- 怎么用:具体命令(运行 Python 脚本)
- 依赖什么:前置条件(Python 3.10+、pip 包)
- 注意事项:边界情况(大型文件可能耗时)
7.3 支撑脚本
SKILL.md 可以引用同目录下的脚本文件(Python、Shell 等)。这些脚本在沙箱中执行,受 Tool Policy 约束。脚本负责实际的业务逻辑——调用 API、处理文件、执行计算等。
🏪 八、ClawHub:从开发到发布的完整闭环
ClawHub 是 OpenClaw 的官方技能市场,托管了 5700+ 个社区技能。它提供了从开发到发布的完整闭环:
8.1 开发流程
1. 创建技能目录:mkdir -p ~/.openclaw/skills/my-skill
2. 编写 SKILL.md:描述技能的功能、用法、依赖
3. 编写支撑脚本:实现具体业务逻辑
4. 本地测试:重启会话,用斜杠命令或自然语言触发
8.2 发布流程
1. 认证:clawhub auth login
2. 发布:clawhub publish ~/.openclaw/skills/my-skill
3. 审核:社区审核(自动 + 人工)
4. 上线:其他用户可以搜索和安装
8.3 安装流程
1. 搜索:openclaw skill search weather
2. 安装:openclaw skill install weather-forecast
3. 更新:openclaw skill update --all
4. 列表:openclaw skill list
8.4 安全考量
ClawHub 的技能是社区贡献的,OpenClaw 采取了多层安全措施:
- 沙箱隔离:所有技能脚本在 Docker 沙箱中执行,无法访问宿主机的文件系统
- Tool Policy:技能只能使用 Tool Policy 允许的工具
- 元信息门控:SKILL.md 中可以声明需要的权限,用户在安装时可以看到
- 社区审核:ClawHub 有自动化的安全扫描和人工审核流程
- 用户控制:用户可以随时卸载技能,也可以覆盖社区技能
🧩 九、源码导航
Channel 核心文件
| 文件 | 职责 |
|---|---|
src/channels/dock.ts |
轻量级 Dock 注册,定义通道元信息 |
src/channels/plugins/index.ts |
PluginRegistry,管理所有通道插件 |
src/channels/plugins/telegram/ |
Telegram 适配器(grammY) |
src/channels/plugins/discord/ |
Discord 适配器(discord.js) |
src/channels/plugins/whatsapp/ |
WhatsApp 适配器(Baileys) |
src/channels/plugins/slack/ |
Slack 适配器(Bolt SDK) |
src/channels/plugins/signal/ |
Signal 适配器(signal-cli) |
src/channels/plugins/bluebubbles/ |
iMessage 适配器(BlueBubbles) |
src/channels/plugins/feishu/ |
飞书适配器(WebSocket API) |
src/gateway/server-channels.ts |
Channel 生命周期管理(启停/状态) |
Skills 核心文件
| 文件 | 职责 |
|---|---|
src/agents/system-prompt.ts |
buildSkillsSection() 构建技能提示词 |
src/agents/skills/ |
技能快照、提示词构建 |
src/skills/ |
内置 49 个技能 |
src/plugins/registry.ts |
插件注册中心(含技能注册) |
~/.openclaw/skills/ |
用户自定义技能目录 |
🔟 十、系列预告
第五篇(也是最后一篇),我们将拆解 OpenClaw 的记忆与安全系统:
| 核心问题 |
|---|
| Memory 的分层架构(短期/长期/工作记忆)是如何设计的? |
| MemoryIndexManager 如何用 SQLite + sqlite-vec 实现混合 BM25+向量检索? |
| PRISM 安全层如何实现零 Fork 的运行时安全防护? |
| 生产部署的最佳实践有哪些? |
关注我,不要错过最终篇!
🎁 总结速查卡
Channel 核心概念
| 概念 | 一句话解释 |
|---|---|
| 三层架构 | Agent 层(消费)→ 抽象层(翻译)→ 平台层(实现) |
| Dock | 轻量注册元信息,Gateway 启动时加载,不占内存 |
| Plugin | 重量加载完整实现,通道启动时懒加载,缓存复用 |
| MsgContext | 统一消息上下文,封装平台差异 |
| Capabilities | 能力声明,Agent 据此调整行为 |
| 三种投递模式 | direct(进程内)/ gateway(守护进程)/ hybrid(预留) |
Skills 核心概念
| 概念 | 一句话解释 |
|---|---|
| SKILL.md | 技能的"身份证+操作手册",Markdown 格式 |
| 提示词插件 | Skill 不是代码插件,而是告诉 AI 怎么用现有工具 |
| 三层优先级 | 用户自定义 > ClawHub 安装 > 内置 |
| 四阶段生命周期 | 发现 → 加载 → 触发 → 执行 |
| ClawHub | 官方技能市场,5700+ 社区技能 |
| 沙箱隔离 | 技能脚本在 Docker 中执行,受 Tool Policy 约束 |
一句话总结
Channel 用适配器模式抹平了 25+ 平台的差异,Skills 用提示词插件实现了能力的无限扩展。两者共享同一个设计哲学——框架只定义契约,具体实现交给插件。这就是 OpenClaw "一套框架,多个通道,无限技能"的魔法所在。
参考链接:
更多推荐



所有评论(0)