)


🌺The Begin🌺点点关注,收藏不迷路🌺

1. 引言:为什么要做这层抽象?

如果你要给一个 AI Agent 系统接入多个即时通讯渠道——比如 Telegram、飞书、钉钉、WhatsApp、Discord——你很快就发现每个平台的“脾气”完全不同:

  • 消息格式:Telegram 用 JSON,飞书用事件卡片,钉钉用 Webhook 回调
  • 认证方式:Telegram 用 Bot Token,WhatsApp 用 Twilio,飞书用 App Secret
  • 会话 ID 规则:每个平台都有自己的用户标识格式
  • 回复方式:有的支持消息编辑,有的只能发新消息,有的支持 Markdown,有的只支持纯文本

如果为每个平台单独写一套完整集成,维护成本会随着渠道数量指数级增长。 OpenClaw 的解法是:将渠道视为可插拔模块,通过统一的 Channel Plugin 接口抽象,让核心引擎与具体渠道解耦

2. 抽象层的核心原则:“契约先行,实现自由”

OpenClaw 的 Channel Plugin 抽象设计遵循一个核心原则:Channel plugins do not need their own send/edit/react tools. OpenClaw keeps one shared message tool in core(渠道插件不需要自己的发送/编辑/回复工具。OpenClaw 在核心中保留一个共用的消息工具)。

这意味着什么?核心引擎只关心“发一条消息”这个抽象动作,至于这条消息到了 Telegram 是发文本还是卡片,到了飞书是发富文本还是消息卡片,这是插件自己的事。

2.1 插件职责划分

根据 OpenClaw 官方文档,Channel Plugin 负责以下内容:

职责 说明
Config 账号解析与配置向导
Security DM 政策与允许列表
Pairing DM 审批流程
Session grammar 如何将供应商特定的会话 ID 映射到基础聊天、线程 ID 与父级回退
Outbound 将文字、媒体、投票发送到平台
Threading 回复如何串接
Heartbeat typing 可选的心跳输入中/忙碌信号

而核心引擎(Core)则负责:共用的 message 工具、提示词装配、外层会话键结构、通用 :thread: 簿记与消息分发

渲染错误: Mermaid 渲染失败: Lexical error on line 2. Unrecognized text. ...art TD subgraph “Core Engine” ----------------------^

3. 多渠道设计的关键抽象机制

3.1 统一会话标识:sessionKey

不同渠道的用户 ID 格式完全不同:WhatsApp 是 +1234567890,Telegram 是数字 ID 123456789,Web 是随机字符串。OpenClaw 通过标准化 sessionKey 统一标识:

渠道 sessionKey 格式
WhatsApp wa:+1234567890
Telegram tg:123456789
飞书 feishu:ou_xxxxx
钉钉 ding:user_xxxxx
Web web:abc123

所有会话状态、记忆、任务都按 sessionKey 存储,用户从 WhatsApp 切换到 Telegram,只要绑定同一身份,对话上下文可以无缝衔接

3.2 统一消息模型:ACP 协议

渠道插件不直接与 AI 核心交互,而是通过 ACP(Agent Communication Protocol) 消息总线。

Telegram Webhook

TelegramPlugin

飞书事件

FeishuPlugin

钉钉回调

DingPlugin

ACP 消息总线

Agent Core

Tool Execution

TelegramPlugin.send

FeishuPlugin.send

DingPlugin.send

ACP 是渠道与智能体之间的通用语言,消息格式统一为 JSON Schema,包含 sourcemessage_idcontentattachmentstimestamp 等标准字段。

3.3 渠道发现与元数据

在插件包加载之前,OpenClaw 通过 package.json 中的 openclaw.channel 元数据完成渠道发现:

{
  "openclaw": {
    "channel": {
      "id": "acme-chat",
      "label": "Acme Chat",
      "blurb": "Connect to Acme Chat messaging platform",
      "docsPath": "/channels/acme-chat",
      "markdownCapable": true,
      "exposure": {
        "configured": true,
        "setup": true,
        "docs": true
      }
    }
  }
}

exposure 控制渠道在设定列表、交互式选择器和文档页面中的可见性。

4. 以 createChatChannelPlugin 为入口的插件构建

对于开发者而言,不需要手动实现复杂的底层适配器。OpenClaw 提供了 createChatChannelPlugin 构建器,传入声明式选项即可组合完整插件。

以下是一个构建自定义渠道插件的示例:

import { createChatChannelPlugin, createChannelPluginBase } from "openclaw/plugin-sdk";

// 1. 账号解析:从配置中读取该渠道的 token
function resolveAccount(cfg, accountId) {
  const section = cfg.channels?.["acme-chat"];
  if (!section?.token) throw new Error("acme-chat: token is required");
  return { accountId, token: section.token };
}

// 2. 构建插件
export const acmeChatPlugin = createChatChannelPlugin({
  base: createChannelPluginBase({
    id: "acme-chat",
    setup: { resolveAccount }
  }),

  // DM 安全策略
  security: {
    dm: {
      channelKey: "acme-chat",
      resolvePolicy: (account) => account.dmPolicy,
      resolveAllowFrom: (account) => account.allowFrom,
      defaultPolicy: "allowlist",
    },
  },

  // 配对待定:新联系人需要验证
  pairing: {
    text: {
      idLabel: "Acme Chat username",
      message: "Send this code to verify your identity:",
      notify: async ({ target, code }) => {
        await acmeChatApi.sendDm(target, `Pairing code: ${code}`);
      },
    },
  },

  // 线程模式
  threading: { topLevelReplyToMode: "reply" },

  // 出站消息发送
  outbound: {
    attachedResults: {
      sendText: async (params) => {
        const result = await acmeChatApi.sendMessage(params.to, params.text);
        return { messageId: result.id };
      },
    },
    base: {
      sendMedia: async (params) => {
        await acmeChatApi.sendFile(params.to, params.filePath);
      },
    },
  },
});

5. 消息生命周期:Receive → Dispatch → Send

Channel Plugin 的抽象不仅是“接口定义”,还覆盖了消息的完整生命周期:

5.1 Receive(接收)

入站消息通过 runChannelInboundEvent 处理:

  1. 规范化:将平台事件转为标准结构
  2. 去重:防止 WebSocket 重连后重复投递
  3. 路由:确定消息归属哪个会话
  4. 记录:写入会话转录
  5. 分发:交给 Agent 处理

接收确认策略是显式声明的:

政策 含义
after_receive_record 入站事件完成解析并记录后即可确认
after_agent_dispatch Agent 分派被接受后才确认
after_durable_send 最终递送有持久化决策后才确认
manual 插件完全掌控确认流程

5.2 Dispatch & Send(分发与发送)

发送侧同样基于上下文:

生成回复内容

创建发送意图

开始持久化

平台 I/O 发送

获取回执

提交/确认

返回调用方

核心是 Intent(意图)先于传输 I/O 存在:在尝试平台发送之前,出站消息意图已经持久化;成功后提交平台回执。这样即使在发送过程中崩溃,系统也具备至少一次的恢复能力。

sendDurableMessageBatch 返回明确的生命周期结果:

  • sent:至少有一条可见的平台消息已传递
  • suppressed:被钩子取消,不应投递
  • partial_failed:部分成功、部分失败
  • failed:全部失败

6. 一张图看懂 Channel Plugin 抽象层设计

Core Engine

ACP 消息总线

Channel Plugin 层

用户入口

Telegram

飞书

钉钉

WhatsApp

Discord

TelegramPlugin

FeishuPlugin

DingPlugin

WhatsAppPlugin

DiscordPlugin

统一消息模型
入站

统一消息模型
出站

会话管理
sessionKey

Agent Loop
推理/工具调用

消息分发
调度/路由

7. 结语:让渠道适配成为“可插拔”

OpenClaw Channel Plugin 接口的设计智慧可以概括为三句话:

  1. 契约先行,实现自由——核心只定义 ChannelPlugin 接口,具体实现由插件负责
  2. 统一消息模型,消除协议差异——ACP 协议作为渠道与核心之间的通用语言
  3. 声明式构建,降低开发门槛——createChatChannelPlugin 让开发者只需配置,无需重写底层

当你需要给 Agent 系统新增一个渠道时,不需要修改一行核心代码,只需实现一个 Channel Plugin 并注册到系统中。 这正是抽象层的核心价值:把“多渠道接入”从架构负担转化为可组合能力,让开发者用最低成本接入新平台。

在这里插入图片描述


🌺The End🌺点点关注,收藏不迷路🌺

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐