npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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 install

2. 编译

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.logChannelMeta.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=456

2. 单元测试断点

在 VS Code 中:

  1. 打开 src/reply-dispatcher.test.ts(或其他测试文件)
  2. 在需要断点处点击行号左侧
  3. 终端运行:
    npx vitest --inspect-brk src/reply-dispatcher.test.ts
  4. 在 Chrome 打开 chrome://inspect,点击 Node 进程 → 调试

3. 验证 WebSocket 连接

# 检查是否连上(需真实 apiKey)
# 观察日志:出现 "SignalR connection established" 即成功

# 用 wscat 手动测试(可选,验证端点可达)
npx wscat -c "wss://daozesk-im-signalr-api.testomenow.com/daoclawhub"

4. 模拟用户消息(手动验证闭环)

如果有真实的 DaoChat 测试环境:

  1. 配置有效 apiKey → 插件启动 → 自动连接
  2. 用测试账号在 IM 中给机器人发一条消息
  3. 观察日志:
    • Received user message → 收到
    • Dispatch complete: queuedFinal=true → AI 处理完毕
    • Reply completed: chunks=N → 流式回复发送完毕

5. 常见调试场景

| 现象 | 可能原因 | 排查方法 | |------|---------|---------| | 插件不加载 | openclaw.plugin.json 路径不对 | 检查 package.jsonopenclaw.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.jsonchannelEnvVars 已更新
  • [ ] 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,需要 @openclaw scope 的发布权限。


安装到 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 宿主提供 PluginRuntimeChannelGatewayContext、消息分发管道等基础设施。所有业务逻辑在宿主生命周期回调中被调用。

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: 消息发了但对方收不到?

检查顺序:

  1. 日志中是否有 SignalR connection established?→ 没连上则先排查 apiKey
  2. 日志中 sendReplyChunk 是否被调用?→ 未调用则排查 dispatch 管道
  3. sendReplyChunk 是否抛错 not connected?→ 检查连接状态
  4. 服务端是否收到但前端没渲染?→ 服务端问题,检查 isCompletedfullContentText 字段

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 |