Skip to content

第3课:多通道系统实现原理

支持的通信渠道概览

Clawdbot的多通道系统是其核心特色之一,支持广泛的通信平台:

  • WhatsApp:通过Baileys库实现的Web API
  • Telegram:基于grammY框架
  • Discord:使用discord.js库
  • Slack:通过Slack Bolt框架
  • Google Chat:使用官方API
  • Signal:通过signal-cli
  • iMessage:原生macOS集成
  • 蓝泡泡(BlueBubbles):扩展插件
  • Microsoft Teams:扩展插件
  • Matrix:扩展插件
  • Zalo:扩展插件
  • WebChat:内置网页界面

通道抽象层设计

Clawdbot通过一套精心设计的抽象层来统一管理各种通信渠道,使得核心功能可以跨平台复用。

通道插件架构

让我们看看WhatsApp通道的具体实现:

typescript
// 来自 extensions/whatsapp/src/channel.ts 的简化版
import { getWhatsAppRuntime } from "./runtime.js";

export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
  id: "whatsapp",
  meta: {
    // 通道元数据
    name: "WhatsApp",
    showConfigured: false,
    quickstartAllowFrom: true,
    forceAccountBinding: true,
    preferSessionLookupForAnnounceTarget: true,
  },
  // 通道的生命周期管理
  onboarding: whatsappOnboardingAdapter,
  // 代理工具定义
  agentTools: () => [getWhatsAppRuntime().channel.whatsapp.createLoginTool()],
  // 配对功能
  pairing: {
    idLabel: "whatsappSenderId",
  },
  // 功能能力声明
  capabilities: {
    chatTypes: ["direct", "group"],  // 支持直接消息和群聊
    polls: true,                     // 支持投票
    reactions: true,                 // 支持反应/表情
    media: true,                     // 支持媒体文件
  },
  // 配置模式定义
  reload: { configPrefixes: ["web"], noopPrefixes: ["channels.whatsapp"] },
  gatewayMethods: ["web.login.start", "web.login.wait"],
  // 配置模式定义
  configSchema: buildChannelConfigSchema(WhatsAppConfigSchema),
  // 配置管理
  config: {
    listAccountIds: (cfg) => listWhatsAppAccountIds(cfg),
    resolveAccount: (cfg, accountId) => resolveWhatsAppAccount({ cfg, accountId }),
    defaultAccountId: (cfg) => resolveDefaultWhatsAppAccountId(cfg),
    // ... 更多配置方法
  },
  // 安全管理
  security: {
    resolveDmPolicy: ({ cfg, accountId, account }) => {
      // 定义直接消息策略
      return {
        policy: account.dmPolicy ?? "pairing",  // 默认为配对模式
        allowFrom: account.allowFrom ?? [],
        policyPath: `channels.whatsapp.dmPolicy`,
        allowFromPath: "channels.whatsapp.",
        approveHint: formatPairingApproveHint("whatsapp"),
        normalizeEntry: (raw) => normalizeE164(raw),
      };
    },
    // ... 更多安全配置
  },
  // 群组功能
  groups: {
    resolveRequireMention: resolveWhatsAppGroupRequireMention,
    resolveToolPolicy: resolveWhatsAppGroupToolPolicy,
    // ...
  },
  // 出站消息功能
  outbound: {
    deliveryMode: "gateway",  // 通过网关交付
    chunker: (text, limit) => getWhatsAppRuntime().channel.text.chunkText(text, limit),
    chunkerMode: "text",
    textChunkLimit: 4000,
    pollMaxOptions: 12,
    // 消息发送功能
    sendText: async ({ to, text, accountId, deps, gifPlayback }) => {
      const send = deps?.sendWhatsApp ?? getWhatsAppRuntime().channel.whatsapp.sendMessageWhatsApp;
      const result = await send(to, text, {
        verbose: false,
        accountId: accountId ?? undefined,
        gifPlayback,
      });
      return { channel: "whatsapp", ...result };
    },
    // ... 其他出站方法
  },
  // 网关集成
  gateway: {
    startAccount: async (ctx) => {
      // 启动WhatsApp账户监听器
      return getWhatsAppRuntime().channel.whatsapp.monitorWebChannel(
        /* 参数 */
      );
    },
    // 登录和登出功能
    loginWithQrStart: async ({ accountId, force, timeoutMs, verbose }) => 
      await getWhatsAppRuntime().channel.whatsapp.startWebLoginWithQr({/* ... */}),
    // ... 其他方法
  },
};

通道插件接口定义

从类型定义可以看到,通道插件需要实现多个适配器接口:

typescript
// 通道插件的核心接口定义
export type ChannelPlugin<ResolvedAccount = any> = {
  id: ChannelId;                    // 通道唯一标识符
  meta: ChannelMeta;                // 通道元数据
  capabilities: ChannelCapabilities; // 功能能力声明
  defaults?: {                      // 默认配置
    queue?: {
      debounceMs?: number;          // 队列防抖时间
    };
  };
  reload?: {                        // 重载配置
    configPrefixes: string[];
    noopPrefixes?: string[];
  };
  onboarding?: ChannelOnboardingAdapter;     // 引导适配器
  config: ChannelConfigAdapter<ResolvedAccount>; // 配置适配器
  configSchema?: ChannelConfigSchema;        // 配置模式
  setup?: ChannelSetupAdapter;               // 设置适配器
  pairing?: ChannelPairingAdapter;           // 配对适配器
  security?: ChannelSecurityAdapter<ResolvedAccount>; // 安全适配器
  groups?: ChannelGroupAdapter;              // 群组适配器
  outbound?: ChannelOutboundAdapter;         // 出站适配器
  gatewayMethods?: string[];                 // 网关方法
  gateway?: ChannelGatewayAdapter<ResolvedAccount>; // 网关适配器
  // ... 更多适配器
};

WhatsApp通道实现详解

让我们深入WhatsApp通道的实现细节:

1. 配置管理

WhatsApp通道的配置管理包括账户管理和安全策略:

typescript
config: {
  listAccountIds: (cfg) => listWhatsAppAccountIds(cfg),
  resolveAccount: (cfg, accountId) => resolveWhatsAppAccount({ cfg, accountId }),
  defaultAccountId: (cfg) => resolveDefaultWhatsAppAccountId(cfg),
  // 启用/禁用账户
  setAccountEnabled: ({ cfg, accountId, enabled }) => {
    const accountKey = accountId || DEFAULT_ACCOUNT_ID;
    const accounts = { ...cfg.channels?.whatsapp?.accounts };
    const existing = accounts[accountKey] ?? {};
    return {
      ...cfg,
      channels: {
        ...cfg.channels,
        whatsapp: {
          ...cfg.channels?.whatsapp,
          accounts: {
            ...accounts,
            [accountKey]: {
              ...existing,
              enabled,
            },
          },
        },
      };
    };
  },
  // 其他配置方法...
},

2. 安全策略

WhatsApp通道实现了严格的安全策略,默认采用配对模式防止未授权访问:

typescript
security: {
  resolveDmPolicy: ({ cfg, accountId, account }) => {
    return {
      policy: account.dmPolicy ?? "pairing",  // 默认配对策略
      allowFrom: account.allowFrom ?? [],     // 白名单
      policyPath: `${basePath}dmPolicy`,      // 配置路径
      allowFromPath: basePath,                // 白名单路径
      approveHint: formatPairingApproveHint("whatsapp"), // 配对提示
      normalizeEntry: (raw) => normalizeE164(raw), // 标准化输入
    };
  },
  // 收集安全警告
  collectWarnings: ({ account, cfg }) => {
    const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
    const groupPolicy = account.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
    if (groupPolicy !== "open") return [];
    // 返回安全警告信息
    return [
      `- WhatsApp groups: groupPolicy="open" allows any member in allowed groups to trigger...`
    ];
  },
},

3. 消息路由机制

WhatsApp通道实现了智能的消息路由和目标解析:

typescript
outbound: {
  resolveTarget: ({ to, allowFrom, mode }) => {
    const trimmed = to?.trim() ?? "";
    const allowListRaw = (allowFrom ?? []).map((entry) => String(entry).trim()).filter(Boolean);
    const hasWildcard = allowListRaw.includes("*");
    const allowList = allowListRaw
      .filter((entry) => entry !== "*")
      .map((entry) => normalizeWhatsAppTarget(entry))
      .filter((entry): entry is string => Boolean(entry));

    if (trimmed) {
      const normalizedTo = normalizeWhatsAppTarget(trimmed);
      if (!normalizedTo) {
        if ((mode === "implicit" || mode === "heartbeat") && allowList.length > 0) {
          return { ok: true, to: allowList[0] };  // 使用白名单第一个
        }
        return {
          ok: false,
          error: missingTargetError(
            "WhatsApp",
            "<E.164|group JID> or channels.whatsapp.allowFrom[0]",
          ),
        };
      }
      
      // 处理群组JID
      if (isWhatsAppGroupJid(normalizedTo)) {
        return { ok: true, to: normalizedTo };
      }
      
      // 处理普通消息
      if (mode === "implicit" || mode === "heartbeat") {
        if (hasWildcard || allowList.length === 0) {
          return { ok: true, to: normalizedTo };
        }
        if (allowList.includes(normalizedTo)) {
          return { ok: true, to: normalizedTo };
        }
        return { ok: true, to: allowList[0] };
      }
      return { ok: true, to: normalizedTo };
    }

    if (allowList.length > 0) {
      return { ok: true, to: allowList[0] };
    }
    return {
      ok: false,
      error: missingTargetError(
        "WhatsApp",
        "<E.164|group JID> or channels.whatsapp.allowFrom[0]",
      ),
    };
  },
  // 发送文本消息
  sendText: async ({ to, text, accountId, deps, gifPlayback }) => {
    const send = deps?.sendWhatsApp ?? getWhatsAppRuntime().channel.whatsapp.sendMessageWhatsApp;
    const result = await send(to, text, {
      verbose: false,
      accountId: accountId ?? undefined,
      gifPlayback,
    });
    return { channel: "whatsapp", ...result };
  },
},

消息路由机制

Clawdbot的消息路由机制确保了消息能够正确地在不同通道之间传递:

1. 通道识别和归一化

系统使用标准化的目标识别和归一化机制:

typescript
// 消息目标归一化
messaging: {
  normalizeTarget: normalizeWhatsAppMessagingTarget,
  targetResolver: {
    looksLikeId: looksLikeWhatsAppTargetId,
    hint: "<E.164|group JID>",  // 目标格式提示
  },
},

2. 目录服务

通道实现了目录服务以支持用户和群组的查找:

typescript
directory: {
  self: async ({ cfg, accountId }) => {
    // 获取当前账户信息
    const account = resolveWhatsAppAccount({ cfg, accountId });
    const { e164, jid } = getWhatsAppRuntime().channel.whatsapp.readWebSelfId(account.authDir);
    const id = e164 ?? jid;
    if (!id) return null;
    return {
      kind: "user",
      id,
      name: account.name,
      raw: { e164, jid },
    };
  },
  listPeers: async (params) => listWhatsAppDirectoryPeersFromConfig(params),
  listGroups: async (params) => listWhatsAppDirectoryGroupsFromConfig(params),
},

3. 心跳检测

系统实现了心跳检测机制以确保通道的可用性:

typescript
heartbeat: {
  checkReady: async ({ cfg, accountId, deps }) => {
    if (cfg.web?.enabled === false) {
      return { ok: false, reason: "whatsapp-disabled" };
    }
    const account = resolveWhatsAppAccount({ cfg, accountId });
    const authExists = await (deps?.webAuthExists ??
      getWhatsAppRuntime().channel.whatsapp.webAuthExists)(account.authDir);
    if (!authExists) {
      return { ok: false, reason: "whatsapp-not-linked" };
    }
    const listenerActive = deps?.hasActiveWebListener
      ? deps.hasActiveWebListener()
      : Boolean(getWhatsAppRuntime().channel.whatsapp.getActiveWebListener());
    if (!listenerActive) {
      return { ok: false, reason: "whatsapp-not-running" };
    }
    return { ok: true, reason: "ok" };
  },
  resolveRecipients: ({ cfg, opts }) => resolveWhatsAppHeartbeatRecipients(cfg, opts),
},

总结

Clawdbot的多通道系统通过精心设计的插件架构实现了对多种通信平台的统一支持。每个通道都遵循相同的接口规范,但可以根据平台特性实现特定的功能。这种设计使得添加新的通信渠道变得相对简单,同时保持了系统的整体一致性和安全性。

在下一课中,我们将深入探讨代理(Agent)系统的架构,包括会话管理、工具调用和沙箱安全模型。

Created by Gnod. This site is for educational purposes.