OpenClaw WhatsApp 插件:从原理到实践

OpenClaw 的 WhatsApp 插件架构解析:Baileys 协议、Plugin SDK、信道抽象、多账号管理、安全策略,以及系统通知混入和 428 重试两个踩坑记录

by 于成季 ·

一、概述

OpenClaw 的 WhatsApp 插件是整个平台的信道接入层,负责把 AI Agent 的能力通过 WhatsApp 对外提供服务。用户发一条消息,Agent 回复一条消息——这背后是一套完整的消息路由、安全策略、多账号管理机制。

核心技术栈:

  • Baileys@whiskeysockets/baileys):WhatsApp Web 协议的 Node.js 实现,负责与 WhatsApp 服务器的底层通信
  • Plugin SDK:OpenClaw 的插件抽象层,把信道能力标准化为可配置、可组合的组件
  • Channel Plugin:具体的信道插件实现

插件路径:源码位于 OpenClaw Git 仓库的 extensions/whatsapp/ 目录

二、架构总览

三条主线贯穿整个插件:

  1. 入站 — Baileys 收消息 → 规范化 → 安全检查 → 路由到 Agent
  2. 出站 — Agent 回复 → 文本分片 → Baileys 发送 → WhatsApp 送达
  3. 安全策略 — dmPolicy / allowFrom / groupPolicy / groupAllowFrom

三、最小可用配置

channels:
  whatsapp:
    allowFrom:
      - "+8615840042341"
    actions:
      reactions: true
      polls: true
# 1. 扫码登录
openclaw gateway login whatsapp

# 2. 重启网关
openclaw gateway restart

# 3. 测试
openclaw message send --channel whatsapp --target +8615840042341 --message "连接成功"

四、channel.ts 核心模块拆解

4.1 配置解析

每个账号对应一个 authDir(Baileys 认证状态存储路径)。webAuthExists() 检查该目录下是否存在有效会话文件。

4.2 安全模型

DM 政策(dmPolicy):

策略行为
pairing默认值,需要配对(用户先发一条消息,agent 确认后才放行)
allowlist只接受 allowFrom 名单里的人
open任何人可以对话(谨慎使用)
disabled完全忽略 WhatsApp 私信,所有消息不处理

群组政策(groupPolicy):

策略行为
open群成员都能发消息,mention-gating 生效
allowlist只有 groupAllowFrom / allowFrom 名单里的人能发
disabled完全屏蔽群消息

4.3 出站(消息发送)

超过 4000 字符自动分片发送。支持:图片、视频、音频、文档、GIF。

4.4 心跳检测

三个检查项:插件启用 → 已登录 → Web Socket 活跃。

五、消息前缀机制与系统通知混入问题

这是本次排查的核心问题。

5.1 根因:系统通知和真实消息共用出站路径

WhatsApp 插件的出站消息统一走 sendMessageWhatsApp(),无论是 Agent 回复还是系统通知,最终都被 WhatsApp 服务器标记为"这个账号发的"——服务器不区分来源。

系统通知 ──┐
Agent回复 ─┴─→ sendMessageWhatsApp() ──→ WhatsApp 服务器
                                 都被打上相同 sender_id

5.2 流程:系统通知如何混入用户消息

  1. OpenClaw 内部产生系统通知
  2. sendMessageWhatsApp() 发出
  3. WhatsApp 服务器标记 sender = 账号所有者(不区分来源)
  4. 消息回落(inbound),被当作"用户发来的消息",senderLabel = "+8615840042341"
  5. Session 匹配找到用户
  6. Agent 看到消息,自动回复

5.3 关于消息前缀字段

配置中存在 messagePrefix / responsePrefix 字段(定义在 WhatsAppConfigCore 中),用于自定义消息前缀,但目前不确定是否实际用于系统通知标记——源码中未明确追踪到此字段与系统通知路由的关联。如有读者在代码中确认,欢迎指正。

5.4 解决方向

  • 方案 A(推荐):在 dispatch 层识别系统通知前缀(如 [openclaw][Queued messages]),跳过不进入 Agent 处理流程
  • 方案 B:系统通知走内部事件总线,不经过 WhatsApp 出站通道

六、428 Gateway Timeout 与重复消息

6.1 什么是 428

428 是 WhatsApp Web 协议在 Baileys 中的状态码,表示"请求超时 / 设备端未确认"。WhatsApp 服务器在一定时间内没收到设备确认时返回此状态。

6.2 Baileys 的重试机制

发送消息 → 失败(428) → Baileys 查找幂等 key(key.id) → 重发 → 用户看到两条

关键调试字段(很多人不知道):

字段含义重试时表现
key.id消息的业务 ID(幂等 key)重试时保持不变
key.stanzaIdWhatsApp 服务器分配的流水号每次发送/重试都不同

日志中相同 key.id + 不同 stanzaId = 重试发送。

6.3 解决思路

debounceMs 解决的是入站防抖(减少对同一发送者的重复 LLM 调用),对出站重试无效。真正的解法:

  1. 出站消息幂等去重:用 correlationId 记录本次发送请求,相同 ID 不重复发送
  2. 调整 Baileys 重试阈值:配置重试次数上限或超时时间

6.4 本次根因

用户发消息 → Agent 回复 → 发送成功但 WhatsApp 返回 428 → Baileys 自动重试 → 用户收到两条相同回复。

七、多账号与多 Agent 场景

场景:个人号 TangTou + 工作号神秘人

个人号 (+8615811111111)   工作号 (+8615822222222)
    │ dmPolicy: pairing       │ dmPolicy: allowlist
    │ → TangTou Agent        │ → 神秘人 Agent
                            │ groupPolicy: disabled

配置示例:

channels:
  whatsapp:
    defaultAccount: "personal"
    accounts:
      personal:
        name: "个人号"
        authDir: "~/.openclaw/whatsapp-personal"
        enabled: true
        dmPolicy: "pairing"
        actions:
          reactions: true
      work:
        name: "工作号"
        authDir: "~/.openclaw/whatsapp-work"
        enabled: true
        dmPolicy: "allowlist"
        allowFrom:
          - "+8615822222222"
          - "+8615833333333"
        actions:
          reactions: true
          polls: true
        groupPolicy: "disabled"

每个账号独立:authDir(认证状态)、安全策略、Web Socket 监听器、绑定的 Agent。

八、踩坑记录

坑 1:系统通知混入用户消息

项目内容
现象带有 [openclaw] / [Queued messages] 前缀的消息出现在用户消息列表,Agent 误接话
根因系统通知走 sendMessageWhatsApp() 出站,WhatsApp 服务器标记 sender 为账号所有者,消息回落时被归到用户名下
影响Agent 无意义回复,消耗 token,误触发 cron/心跳
调试方法查看消息内容是否有 [openclaw] 前缀,对比 senderLabel
解决方向dispatch 层识别前缀跳过;或系统通知走事件总线

坑 2:428 重试导致重复回复

项目内容
现象同一 Agent 回复出现了两次
根因WhatsApp 428 超时 → Baileys 自动重试 → 同一条消息发送两次
影响用户收到两条相同回复,体验差
调试方法日志中对比 key.id(相同=同一消息)和 stanzaId(不同=重试发送)
解决方向出站幂等去重(correlationId);调高 Baileys 重试阈值

九、总结

理解 OpenClaw 的 WhatsApp 插件,关键在于抓住三条线:消息入站(Baileys → Session Router → Agent)、消息出站(Agent → Channel Plugin → Baileys)、安全策略(dmPolicy / groupPolicy / allowFrom)。掌握这三条线,就能从容应对各种配置和调试场景。

本文基于 OpenClaw WhatsApp 插件源码整理

← 所有文章