@huangkemeng/daochat-plugin
v0.0.25
Published
DaoChat channel plugin for OpenClaw - connects to DaoChat messaging platform
Readme
DaoChat Plugin
OpenClaw 的 DaoChat 即时通讯(IM)频道插件。通过 SignalR WebSocket 连接 DaoChat 消息平台,将用户消息路由到 OpenClaw AI Agent 处理,并将 AI 回复以流式 chunk 实时返回。
目录
架构概览
┌──────────────────────────────────────────────────┐
│ OpenClaw 宿主 │
│ ┌────────────────────────────────────────────┐ │
│ │ daochat-plugin (本插件) │ │
│ │ │ │
│ │ channel.ts ← 插件入口,生命周期管理 │ │
│ │ ├─ gateway.startAccount() → 启动 WS 连接 │ │
│ │ ├─ gateway.stopAccount() → 关闭 WS 连接 │ │
│ │ ├─ outbound.sendText() → 发送消息 │ │
│ │ └─ heartbeat → 输入状态指示 │ │
│ │ │ │
│ │ signalr-manager.ts ← WebSocket 连接封装 │ │
│ │ ├─ start / stop / isConnected │ │
│ │ ├─ onUserMessage / onRobotStatusChanged │ │
│ │ └─ sendReplyChunk() │ │
│ │ │ │
│ │ reply-dispatcher.ts ← 流式回复编排 │ │
│ │ └─ createStreamingChunkDeliverer() │ │
│ │ block → isCompleted=false │ │
│ │ final → isCompleted=true │ │
│ └──────────────┬─────────────────────────────┘ │
│ │ SignalR WebSocket │
└─────────────────┼────────────────────────────────┘
│
┌──────────────▼──────────────────┐
│ DaoChat IM 服务器 │
│ /daoclawhub?apiKey=xxx │
│ │
│ ← DaoClawUserMessageEvent │
│ → SendReplyChunk (chunk 流) │
└──────────────────────────────────┘核心数据流:
用户发消息 → IM WebSocket 推送 DaoClawUserMessageEvent
→ SignalRManager 接收
→ createInboundMsgContext() 转为 OpenClaw 格式
→ dispatchInboundMessageToAgent() 调度到 AI
→ AI 逐块生成
→ createStreamingChunkDeliverer 按 kind 映射:
"block" → SendReplyChunk(isCompleted=false) 增量显示
"final" → SendReplyChunk(isCompleted=true) 最终落库快速开始:5 分钟跑起来
前提条件
| 要求 | 版本 | 检查命令 |
|------|------|---------|
| Node.js | ≥ 22.0.0 | node -v |
| npm | ≥ 9 | npm -v |
1. 克隆 & 安装
git clone <repo-url> daochat-plugin
cd daochat-plugin
npm install2. 编译
npm run build检查 dist/ 目录是否生成了 .js 文件:
ls dist/
# 应看到:channel.js index.js reply-dispatcher.js signalr-manager.js types.js ...3. 跑测试
npm test期望输出:Tests 46 passed (46)
4. 配置环境变量
在 OpenClaw 配置中(或项目根目录 .env)设置:
DAOCHAT_API_KEY=你的daoclaw-api-key
# DAOCHAT_WS_URL 可选,默认已内置5. 启动 OpenClaw(加载插件)
插件是 OpenClaw 的频道插件,不能独立运行。它在 OpenClaw 启动时自动加载:
# 在 OpenClaw 项目中,确保 openclaw.plugin.json 正确配置
# 插件随 openclaw start 自动激活项目目录结构
daochat-plugin/
├── src/
│ ├── index.ts # 插件入口,注册到 OpenClaw
│ ├── setup-entry.ts # 安装向导入口
│ ├── channel.ts # ★ 核心:频道插件定义、网关、消息分发
│ ├── signalr-manager.ts # ★ SignalR WebSocket 连接管理器
│ ├── signalr-manager.test.ts # 21 个单元测试
│ ├── reply-dispatcher.ts # ★ 流式回复编排器
│ ├── reply-dispatcher.test.ts # 12 个单元测试
│ ├── types.ts # 协议数据类型定义
│ └── channel.test.ts # 13 个契约与集成测试
├── dist/ # 编译输出(不提交 Git)
├── daoclaw设计方案文档.md # 设计参考文档
├── package.json # 依赖与脚本
├── openclaw.plugin.json # ★ OpenClaw 插件清单
├── tsconfig.json # TypeScript 配置
├── .gitignore
└── .plan/ # 开发计划(不提交,自动忽略)每个文件干什么?
| 文件 | 一句话说明 | 什么时候改 |
|------|-----------|-----------|
| index.ts | 注册插件到 OpenClaw,声明 id、名字、入口 | 改插件名、描述时 |
| channel.ts | 频道全部逻辑:配置校验、网关启停、消息收发、typing | 加功能、改业务逻辑时 |
| signalr-manager.ts | 封装 SignalR 连接:建连、断连、重连、收发事件 | WS 协议有变化时 |
| reply-dispatcher.ts | 把 AI 的输出块映射成 SignalR chunk | 改流式回复策略时 |
| types.ts | IM 协议中事件和请求的 TS 类型 | 服务端协议字段有变化时 |
| openclaw.plugin.json | OpenClaw 识别此插件的清单文件 | 改环境变量、activation 策略时 |
配置
环境变量
| 变量 | 必填 | 默认值 | 说明 |
|------|------|--------|------|
| DAOCHAT_API_KEY | 是 | 无 | DaoClaw 机器人的 apiKey,用于 WebSocket 认证 |
| DAOCHAT_WS_URL | 否 | https://daozesk-im-signalr-api.testomenow.com/daoclawhub | SignalR Hub 地址 |
OpenClaw 配置示例
在 OpenClaw 主配置(openclaw.json 或 YAML)中添加:
channels:
daochat:
apiKey: "${DAOCHAT_API_KEY}" # 从环境变量读取
wsUrl: "https://daozesk-im-signalr-api.testomenow.com/daoclawhub" # 可选
allowFrom: # DM 白名单
- "employee-id-1"
- "employee-id-2"配置校验规则
apiKey必填 — 缺少则isConfigured()返回false,插件跳过连接wsUrl可选 — 不填使用内置默认值allowFrom可选 — 控制哪些 IM 用户允许触发 DM 对话
日常开发
开发模式(自动编译)
npm run dev
# tsc --watch:文件改动时自动重新编译到 dist/跑测试(一次性)
npm test
# 等价于:npx vitest run跑测试(监听模式)
npx vitest
# 文件改动时自动重新跑相关测试只跑某一个测试文件
npx vitest run src/signalr-manager.test.ts
npx vitest run src/reply-dispatcher.test.ts
npx vitest run src/channel.test.ts类型检查(不生成文件)
npx tsc --noEmit注意:当前有一些已知的 SDK 类型兼容警告(如
PluginRuntime.log、ChannelMeta.blurb),不影响编译和运行。这些来自 OpenClaw SDK 的类型宽松化设计。
调试
1. 本地日志
插件通过 OpenClaw 宿主提供的 log 对象输出日志,所有日志带有 [daochat] 前缀:
[daochat] Starting account: default → wss://...
[daochat] SignalR connection established
[daochat] Received user message: messageId=xxx, from=employee-1, text=你好
[daochat] Reply dispatch settled for message xxx
[daochat] Reply completed: requestId=reply-xxx, chunks=12, chars=4562. 单元测试断点
在 VS Code 中:
- 打开
src/reply-dispatcher.test.ts(或其他测试文件) - 在需要断点处点击行号左侧
- 终端运行:
npx vitest --inspect-brk src/reply-dispatcher.test.ts - 在 Chrome 打开
chrome://inspect,点击 Node 进程 → 调试
3. 验证 WebSocket 连接
# 检查是否连上(需真实 apiKey)
# 观察日志:出现 "SignalR connection established" 即成功
# 用 wscat 手动测试(可选,验证端点可达)
npx wscat -c "wss://daozesk-im-signalr-api.testomenow.com/daoclawhub"4. 模拟用户消息(手动验证闭环)
如果有真实的 DaoChat 测试环境:
- 配置有效
apiKey→ 插件启动 → 自动连接 - 用测试账号在 IM 中给机器人发一条消息
- 观察日志:
Received user message→ 收到Dispatch complete: queuedFinal=true→ AI 处理完毕Reply completed: chunks=N→ 流式回复发送完毕
5. 常见调试场景
| 现象 | 可能原因 | 排查方法 |
|------|---------|---------|
| 插件不加载 | openclaw.plugin.json 路径不对 | 检查 package.json 中 openclaw.extensions 配置 |
| 连不上 WebSocket | apiKey 无效、URL 不对 | 看日志中 Failed to start SignalR 的详细错误 |
| 收到消息但没 AI 回复 | channelRuntime 不存在 | 日志中搜 channelRuntime not available |
| 只有一条完整回复,没有流式 | reply-dispatcher 的 block 没触发 | 确认 AI provider 支持 block streaming |
| 构建报错 | SDK 类型兼容性 | 大多数是 SDK 已知问题,dist/ 仍会生成 |
测试
测试覆盖
| 测试文件 | 测试数 | 覆盖内容 |
|---------|--------|---------|
| signalr-manager.test.ts | 21 | 连接建连/断连、事件注册/注销、sendReplyChunk 调用、URL 构建含 apiKey、自动重连 handler 重注册、abort signal 清理 |
| reply-dispatcher.test.ts | 12 | requestId 生成、sequence 递增、block→isCompleted=false、final→isCompleted=true+全文、tool 默认跳过、sendToolResults 选项、跨轮次独立 |
| channel.test.ts | 13 | 能力声明、send.text 走 SignalR、replyToId 映射、无连接时抛异常、live/finalizer 能力校验 |
| 合计 | 46 | |
写新测试
测试框架是 Vitest。约定:
- 测试文件与源文件同目录,命名
*.test.ts - Mock 使用
vi.mock()/vi.fn() - 测试辅助函数通过
_test_前缀导出(如_test_setAccountConnection)
// 最小测试示例
import { describe, it, expect, vi } from "vitest";
describe("我的新功能", () => {
it("应该做某件事", () => {
const result = doSomething("input");
expect(result).toBe("expected");
});
});构建 & 发布
构建
npm run build
# 等价于:tsc
# 输出:dist/ 目录下的 .js + .d.ts + .map 文件构建产物不经 bundler,直接由 tsc 编译。因为 OpenClaw 宿主本身是 Node.js 环境,原生支持 ES modules。
发版 Checklist
- [ ]
npm test全部通过(46 tests) - [ ]
npm run build成功(dist/ 有最新文件) - [ ]
package.json中的version已更新 - [ ] 如果有新增/变更的环境变量,
openclaw.plugin.json的channelEnvVars已更新 - [ ] Git 提交并打 tag
发布到 npm
# 1. 确认版本号
npm version patch # 1.0.0 → 1.0.1(小修复)
npm version minor # 1.0.0 → 1.1.0(新功能)
npm version major # 1.0.0 → 2.0.0(破坏性变更)
# 2. 构建
npm run build
# 3. 发布
npm publish --access public包名为
@openclaw/daochat-plugin,需要@openclawscope 的发布权限。
安装到 OpenClaw
方法 1:npm 安装(推荐,发布后使用)
# 在 OpenClaw 项目目录下
npm install @openclaw/daochat-plugin然后在 OpenClaw 配置中添加频道配置:
channels:
daochat:
apiKey: "${DAOCHAT_API_KEY}"OpenClaw 启动时会自动发现 node_modules 中以 @openclaw/ 开头的插件。
方法 2:本地链接(开发时使用)
# 1. 在插件目录创建全局链接
cd daochat-plugin
npm link
# 2. 在 OpenClaw 项目目录链接到本地
cd ../openclaw-project
npm link @openclaw/daochat-plugin
# 3. 启动 OpenClaw,插件从本地 dist/ 加载
openclaw start方法 3:直接路径引用
在 OpenClaw 的 openclaw.json 中:
{
"plugins": {
"paths": ["../daochat-plugin"]
}
}验证安装成功
启动 OpenClaw 后,查看日志:
[openclaw] Loading plugin: daochat-plugin (daoChat)
[daochat] Starting account: default → wss://...
[daochat] SignalR connection established
[daochat] Account default started successfully看到 SignalR connection established 即表示插件已正确安装并连接。
FAQ / 踩坑记录
Q: 为什么 npm run build 有类型错误但还是能生成 dist/?
OpenClaw SDK 的类型定义使用了宽松的索引签名(如 [key: string]: unknown),tsc 默认行为是即使有类型错误也会输出 JS 文件(除非 noEmitOnError: true)。当前已知的类型警告是 SDK 侧的设计权衡,不影响运行。
Q: 插件能脱离 OpenClaw 独立运行吗?
不能。本插件是 OpenClaw 的频道适配器,依赖 OpenClaw 宿主提供 PluginRuntime、ChannelGatewayContext、消息分发管道等基础设施。所有业务逻辑在宿主生命周期回调中被调用。
Q: WebSocket 断了会自动重连吗?
会。SignalRManager 配置了自动重连:[0, 2000, 5000, 10000, 30000](毫秒)。首次立即重试,之后按 2s → 5s → 10s → 30s 递增,最多 5 次。重连成功后自动重新注册所有事件监听器。
Q: 如何支持多账号(多个 apiKey)?
在 OpenClaw 配置中设置 accounts:
channels:
daochat:
accounts:
bot-a:
apiKey: "key-for-bot-a"
bot-b:
apiKey: "key-for-bot-b"每个账号独立维护一个 WebSocket 连接。
Q: 消息发了但对方收不到?
检查顺序:
- 日志中是否有
SignalR connection established?→ 没连上则先排查 apiKey - 日志中
sendReplyChunk是否被调用?→ 未调用则排查 dispatch 管道 sendReplyChunk是否抛错not connected?→ 检查连接状态- 服务端是否收到但前端没渲染?→ 服务端问题,检查
isCompleted和fullContentText字段
Q: 设计文档中的 /daoclawhub 和 /daozeskimhub 是什么关系?
设计文档定义了两个 Hub:
/daozeskimhub— 前端用户客户端连接/daoclawhub— DaoClaw 机器人端连接(本插件使用)
Q: Node.js 版本要求为什么是 ≥22?
因为 @microsoft/signalr v8 和 OpenClaw SDK 使用了 ES2022+ 特性,以及 Node.js 22 内置的原生 WebSocket(无需额外安装 ws 包)。
技术栈
| 层 | 技术 |
|---|------|
| 语言 | TypeScript 5.7 |
| 运行时 | Node.js ≥22 |
| 模块 | ES Modules |
| WebSocket | SignalR(@microsoft/signalr v8) |
| 测试 | Vitest 2.x |
| 宿主框架 | OpenClaw ≥2026.3.22 |
