@smart-link/openclaw-channel
v0.1.3
Published
OpenClaw channel plugin for Smart Link IM (gedim): bot-account realtime text, image, voice and file send/receive. Multiple accounts configured via accounts[] each with a robot secretKey; user identity (userId/userName/avatars) is fetched from /auth/me aft
Readme
openclaw-channel
OpenClaw 的 channel plugin(channel id = smart-link-chat):让 OpenClaw agent 通过一个机器人账号接入 smart-link IM(gedim,即 @smart-link/im-base 对应的 IM 服务端),用户在政务 App 内与 AI 单聊 / 群内 @ 对话(文本、图片、语音与文件)。
实现语言:Node.js (≥20) + TypeScript + socket.io-client。依赖 OpenClaw ≥ 2026.6(原生插件 SDK:openclaw.plugin.json manifest + defineChannelPluginEntry)。
本工程采用"无头薄协议客户端"路线:不复用 im-base 的 RN 依赖(sqlite/react-native/Redux),只按 im-base 的协议语义实现实时收发所需子集。
形态与现状
已适配 OpenClaw 2026.6.x 原生 channel 插件格式,覆盖 收发双向:
openclaw.plugin.json— plugin manifest:id: smart-link-chat、channels、channelConfigs.smart-link-chat.schema/uiHints(校验并渲染channels.smart-link-chat)、configSchema、activationsrc/index.ts— extension entry:defineChannelPluginEntry(自动api.registerChannel),注册channelPluginsrc/channel.ts—ChannelPlugin对象:config.resolveAccount读取channels.smart-link-chat(server/userId/userName/cookie…),缺失即抛错提示outbound.sendText:按需懒建GedimClient,messagePrepare→ emitc2cChatMessage/c2gChatMessage发文本outbound.sendMedia:加载媒体字节 → 按 MIME kind 路由(图片→picture,音频→voice,文档/视频→file)→ 上传文件服务取fileId→ emit 对应 payload 消息outbound.sendPayload:按“文本+媒体”序列复用 sendText/sendMedia- 出站目标解析:
group:*→ C2G,其余(含user:/无前缀)→ C2C
src/inbound.ts— 入站(挂gateway.startAccount,账号 enabled+configured 时常驻):- 常驻 socket 监听
c2cChatMessage/c2gChatMessage(与出站共享同一个GedimClient单例) - 过滤:自己发的消息(echo)/ 不支持的载荷类型 /
ignoreUserIds/ignoreConversationIds/ 入站去重(TTL) - 媒体(图片/语音/文件):从文件服务下载 → 落盘入宿主媒体库 → 写入
MsgContext.MediaPaths/MediaTypes交给 agent - 群聊默认 requireMention:
atList含机器人 userId 或文本含@显示名才回复(可配置关闭) - 经宿主标准管线 dispatch(
routing.resolveAgentRoute+dispatchInboundMessageWithBufferedDispatcher)交给 agent, 回复(文本/媒体)通过deliver回调逐条发回原会话(消费回复)
- 常驻 socket 监听
src/setup-entry.ts— 轻量 setup 入口(defineSetupPluginEntry)src/gedim/file.ts— 文件服务 HTTP 客户端(图片/语音/文件上传/下载,复用 TLS 配置)
未实现: 撤回/已读;cookie 过期自动刷新;登录接口自动化。
目录结构
packages/openclaw-channel/
├── openclaw.plugin.json # 插件 manifest(必须随包分发)
├── src/
│ ├── index.ts # extension entry:defineChannelPluginEntry(default export)
│ ├── channel.ts # ChannelPlugin:config 解析 + outbound.sendText + gateway 挂载
│ ├── inbound.ts # 入站:c2c/c2g 监听 → 过滤 → agent dispatch → 消费回复发回
│ ├── setup-entry.ts # setup 入口:defineSetupPluginEntry
│ └── gedim/
│ ├── protocol.ts # 事件/枚举/报文类型(契约)
│ ├── client.ts # socket.io 无头客户端(收发/重连)
│ └── file.ts # 文件服务 HTTP 客户端(图片上传/下载)
├── config.example.json5 # channels.smart-link-chat 配置示例
└── tsconfig.json构建
pnpm install # workspace 根执行(openclaw 作为 dev/peer 依赖安装)
pnpm --filter openclaw-channel build # tsc → dist/(ESM)
pnpm --filter openclaw-channel typecheckdevDependencies.openclaw 固定为 2026.6.34(与宿主同版本),peerDependencies.openclaw 为 >=2026.6.0 —— 插件 API 实验性,务必钉住并测试所声明的版本。
配置与加载
当前接入方式:bot 身份与 cookie 由外部获取后填入配置(后续后端完善登录接口后可优化为 setup 向导)。
两种配置模式(二选一):
模式 A — 单账号扁平格式(向后兼容)
// ~/.openclaw/openclaw.json
{
channels: {
'smart-link-chat': {
enabled: true,
server: 'https://im.example.gov.cn',
userId: 'bot-user-id',
userName: 'AI助手',
cookie: '${GEDIM_BOT_COOKIE}', // 完整 Cookie 头值,OpenClaw 会做 ${ENV} 展开
requireMention: true, // 群聊需 @ 机器人才回复
mediaMaxMb: 20, // 媒体收发大小上限(MB)
},
},
}模式 B — 多账号 accounts 数组(推荐)
// ~/.openclaw/openclaw.json
{
channels: {
'smart-link-chat': {
enabled: true,
accounts: [
{
id: 'bot-1', // 可选,默认 = server|userId
server: 'https://im.example.gov.cn',
userId: 'bot-user-id-1',
userName: 'AI助手一号',
cookie: '${GEDIM_BOT_COOKIE_1}',
requireMention: true,
mediaMaxMb: 20,
},
{
id: 'bot-2',
server: 'https://im.example.gov.cn',
userId: 'bot-user-id-2',
userName: 'AI助手二号',
cookie: '${GEDIM_BOT_COOKIE_2}',
requireMention: true,
mediaMaxMb: 20,
},
],
},
},
}多账号模式下,每个账号独立创建 socket 连接,独立接收消息。出站时 ctx.accountId 由路由绑定自动传递,确保回复发回正确的账号。
安装与加载:
cd packages/openclaw-channel
pnpm pack # → smart-link-openclaw-channel-0.1.0.tgz(内含 openclaw.plugin.json 与 dist/ 双入口)
openclaw plugins install ./smart-link-openclaw-channel-0.1.0.tgz
openclaw status # 应能看到 smart-link-chat 通道
openclaw channel status smart-link-chat⚠️ 不要
openclaw plugins install <本包源码目录>:pnpm workspace 里 devDep(rimraf等)是指向 store 的符号链接,会被安全扫描拒绝(symlink target outside install root)。tarball/npm 安装的files白名单不含 node_modules,无此问题。
常见问题(排障)
症状:网关日志 connect_error: xhr poll error / websocket error 反复出现(auto-restart attempt N/10)。
多数情况下是 TLS 证书校验失败:Node 使用内置 Mozilla 信任库,若服务器证书链使用它不认识的根证书(例如 UAT 证书链到 Let's Encrypt 新根 ISRG Root YR),curl 浏览器正常但 Node 连不上,表现为 xhr poll error。排查:
# 1. 确认是 TLS 问题
node -e "require('tls').connect({host:'你的IM域名',port:443},()=>process.exit(0)).on('error',e=>console.log(e.message))"
# → self-signed certificate in certificate chain = TLS 信任问题
# 2. 首选:把服务器根证书(PEM)配进 tlsCa(仍校验证书)
echo | openssl s_client -connect 你的IM域名:443 -servername 你的IM域名 -showcerts 2>/dev/null \
| awk '/BEGIN CERT/{n++} n==3' # 第 3 张通常是根证书;加到配置 tlsCa
# 3. 应急(仅测试环境):channels.smart-link-chat.tlsRejectUnauthorized = false其它排查:
- cookie 无效/过期 → 服务端 401(
curl -I https://你的IM域名/socket.io/无 cookie 返回 401 属正常;带 cookie 应返回 200 handshake) - 连接地址不对:
[gedim] connecting <url>里应能看到正确 host/path;socket.io 若挂在根路径就不要配namespace - 一直
auto-restart:说明gateway.startAccount抛错退出(看日志最后一条 error / channel status 的 lastError),修好后重启 gateway 观察
待确认契约(对接前必读 ⚠️)
| # | 项 | 说明 | 落点 |
|---|----|------|------|
| 1 | socket 握手 | URL server+namespace?client_type=、Cookie/User-Agent 头、是否需要先 session 初始化才推送 | gedim/client.ts |
| 2 | 收发报文样例 | c2c/c2g 消息实际字段、messagePrepare 响应格式、ack code 语义 | gedim/protocol.ts/client.ts |
| 3 | cookie 有效时长 | bot cookie 过期策略、是否需重连前刷新 | gedim/client.ts |
| 4 | 文件服务报文 | /api-file/file/uploadFile、/downloadFile 的实际响应/鉴权(是否需 Cookie、是否重定向) | gedim/file.ts |
| 5 | 风控/多端 | 常驻 bot 是否被踢(kickout/mulClientKickout)、是否需限速 | gedim/client.ts |
路线图
- [x] 原生插件壳:manifest + entry + setup entry(OpenClaw 2026.6.x 可安装/加载/注册通道)
- [x] 配置解析(
server/userId/userName/cookie)+ 缺失校验 + 目录元数据 - [x]
outbound.sendText:懒建 GedimClient 两段式发文本(messagePrepare→ emit c2c/c2g) - [x] 入站:socket 常驻 +
c2cChatMessage/c2gChatMessage监听 → agent dispatch → 消费回复发回 (gateway.startAccount,与出站共享单例客户端;过滤:自己的消息 / 不支持的载荷 / ignore 名单 / 去重) - [x] 群聊 @ 门控(
requireMention,默认 true;ignoreUserIds/ignoreConversationIds已在 schema) - [x] 图片:入站下载 → 宿主媒体库 →
MediaPaths;出站sendMedia上传文件服务 →picture消息 - [x] 语音/文件:入站下载(voice/file 同上)→ 宿主媒体库;出站
sendMedia按 MIME kind 路由 →voice/file消息 - [ ] 撤回/已读回执语义映射
- [ ] cookie 过期检测/刷新
- [ ] 登录接口完善后补 setup 向导
说明
- 会话身份:单聊
conversation.id = 对方 userId、群= groupId(与 im-base 会话键一致),保证 agent 对同一用户记忆连续。 - 本工程是 monorepo 普通 workspace 成员,不依赖 RN;主仓库
.gitignore的packages/im-base忽略项与本工程无关。 - 发布到 npm/ClawHub 前:把
package.json的"private": true去掉,并核对compat/build版本声明与peerDependencies。
