返回博客

OpenClaw 源码解读(4): 多渠道接入(Channels)源码实战

2026年3月9日

深入 OpenClaw 源码,解析它如何通过适配器模式统一 20+ 通讯平台的收发接口,以及群聊中的提及门控、回复追踪和长消息智能分块策略。


在上一篇中,我们探讨了 OpenClaw 的“多重宇宙”——通过 Workspace 和路由机制,让 Gateway 知道该把消息交给哪个 Agent 处理。但是,现实世界中,用户发消息的入口千奇百怪:有人爱用 WhatsApp,有人常驻 Telegram,团队协作离不开 Slack,甚至还有 Discord、微信等。

每家平台的 API 协议、鉴权方式、数据结构都完全不同。如果把这些逻辑和 AI 核心代码耦合在一起,整个项目很快就会变成一座难以维护的“屎山”。

今天,我们就来翻一翻 OpenClaw 的源码,看看它是如何通过优雅的通道抽象层(Channels Abstraction)群组调度逻辑,将这 20 多种不同的协议“驯服”成统一收发接口的。

img1


化繁为简的适配器模式

OpenClaw 解决多渠道接入的核心思想非常经典:面向接口编程,底层使用适配器模式(Adapter Pattern)

在源码中,无论底层是 WhatsApp (基于 Baileys 库)、Telegram (基于 grammY 库) 还是 Slack (基于 Bolt 框架),它们在 OpenClaw 看来,统统只是一个实现了 IChannelProvider 接口的黑盒。

img2

我们先来看看这个抽象层的核心基类定义:

// src/core/channels/IChannelProvider.ts (源码概念简化版)

export interface IChannelProvider {
  /** 渠道唯一标识 (如 'whatsapp_01', 'telegram_bot') */
  channelId: string;
  
  /** 初始化并连接平台 */
  connect(): Promise<void>;
  
  /** 统一格式的发送消息接口 */
  sendMessage(to: string, content: string, options?: SendOptions): Promise<MessageReceipt>;
  
  /** 注册消息接收的回调函数 */
  onMessageReceived(handler: (msg: NormalizedMessage) => Promise<void>): void;
}

紧接着,各个平台的适配器只需要实现这个接口。以 Telegram 为例,OpenClaw 会在内部把 Telegram 的专属消息对象映射为全局通用的 NormalizedMessage

// src/packages/channels/telegram/TelegramAdapter.ts (片段)

import { Bot } from 'grammy';
import { IChannelProvider, NormalizedMessage } from '@openclaw/core';

export class TelegramAdapter implements IChannelProvider {
  private bot: Bot;
  
  constructor(public channelId: string, token: string) {
    this.bot = new Bot(token);
  }

  public async connect() {
    this.bot.start(); // 启动长轮询或 Webhook
  }

  public onMessageReceived(handler: (msg: NormalizedMessage) => Promise<void>) {
    this.bot.on('message:text', async (ctx) => {
      // 核心动作:将 TG 特有数据「洗」成 OpenClaw 标准格式
      const standardMsg: NormalizedMessage = {
        id: ctx.message.message_id.toString(),
        senderId: ctx.from.id.toString(),
        channelId: this.channelId,
        content: ctx.message.text,
        isGroup: ctx.chat.type === 'group' || ctx.chat.type === 'supergroup',
        rawPayload: ctx.message // 保留原始数据以备高级调用
      };
      await handler(standardMsg);
    });
  }
  
  // ... sendMessage 实现略
}

这种设计把脏活累活都封在了各个 Adapter 包里。Gateway 的控制平面和 Agent 运行时根本不需要知道当前是在给 WhatsApp 发消息还是给 Slack 发消息,它们只需调用 sendMessage 即可。这也是为什么 OpenClaw 能够快速扩展支持 20+ 个渠道的秘诀。


Agent 如何知道该“接话”了?

单聊很简单,收到消息就回复。但在几百人的大群里,如果 Agent 对每一句话都做响应,不仅会迅速耗尽 Token 额度,还会变成彻头彻尾的“复读机群宠”。

因此,对于 Non-main sessions(群组/频道),OpenClaw 设计了非常严谨的门控与追踪机制

@ 提及门控

这是最基础的一层防御。网关在收到群消息时,会首先通过网关拦截器进行判断:这条消息是不是专门说给 Agent 听的?

// src/core/router/MentionGating.ts

export function shouldProcessGroupMessage(msg: NormalizedMessage, botIdentity: string[]): boolean {
  const text = msg.content;
  
  // 1. 显式 @提及
  const isMentioned = botIdentity.some(id => text.includes(`@${id}`));
  
  // 2. 唤醒词匹配 (比如 "Molty, 帮我查一下...")
  const hasWakeWord = botIdentity.some(word => text.toLowerCase().startsWith(word.toLowerCase()));

  return isMentioned || hasWakeWord;
}

回复标签追踪

如果群友没有 @ Agent,而是**直接引用(Reply)**了 Agent 之前发的一条消息,Agent 也应该能聪明地接上话茬。

OpenClaw 维护了一个会话上下文缓存。当 Agent 发送消息时,会将消息 ID 存入该群组的活跃上下文池。当接收到新消息时,即使没有 @ 提及,只要新消息的 replyToMessageId 命中了 Agent 发过的消息,门控同样会放行。


长消息分块处理机制

用过大模型 API 的朋友都知道,LLM 动辄生成几千字的 Markdown 长文。如果直接把这几千字作为一个整块推送到 WhatsApp 或 Telegram,体验极差(部分平台甚至会直接截断报错)。人类在聊天软件里的阅读习惯是“一条一条看”。

OpenClaw 实现了一个非常巧妙的 Chunking(分块)策略,将大段文本拆解为符合人类呼吸节奏的短句。

img3

// src/core/utils/MessageChunker.ts

export class MessageChunker {
  /**
   * 将长文本智能切割为消息数组
   */
  public static splitIntoChunks(text: string, maxLength: number = 2000): string[] {
    const chunks: string[] = [];
    
    // 1. 优先按双换行符(段落)切割
    const paragraphs = text.split('\n\n');
    
    let currentChunk = '';
    
    for (const p of paragraphs) {
      if ((currentChunk.length + p.length) < maxLength) {
        currentChunk += (currentChunk ? '\n\n' : '') + p;
      } else {
        // 如果当前块满了,推入数组,开启新块
        chunks.push(currentChunk);
        currentChunk = p;
      }
    }
    if (currentChunk) chunks.push(currentChunk);
    
    return chunks;
  }
}

在实际的发送管道中,OpenClaw 还会在每个 Chunk 之间加入短暂的 delay(比如 1-2 秒的模拟打字停顿),甚至会在某些通道触发 typing... 状态,让 Agent 表现得像一个真实的群成员在连续发报。


总结

化繁为简,是架构设计的最高境界。

OpenClaw 通过 IChannelProvider 接口 将乱七八糟的平台协议收敛为统一的数据流;通过 Mention Gating 与回复追踪 保护了群聊环境下的计算资源与用户体验;最后通过 智能 Chunking 还原了拟真的人类交互节奏。

了解了 OpenClaw 是如何处理“文字和对话”后,下一篇,我们将进入一个更令人兴奋的领域。这只叫 OpenClaw 的龙虾不仅会聊天,它还拥有“眼睛”和“手”!老金带你看看 Agent 是如何操作界面的。我们下期见!