IM Gateway 方案设计 [历史设计稿]
像 OpenClaw 一样,让 Claude Code Desktop 快速接入任意 IM 平台
状态更新:当前实际可用的接入方式请看
docs/im/。 本文保留为方案演进记录,不再作为接入说明。
背景 · OpenClaw · 方案 · 协议 · 消息流 · Adapter · 文件清单 · 验证 · 对比 · 开放问题
一、背景与动机
现状
Claude Code 源码中已有完整的 Channel 系统(详见 01-channel-system.md),支持通过 MCP 协议接入 IM 平台。但该系统被六层访问控制锁死:
- 编译时门控 —
feature('KAIROS')/feature('KAIROS_CHANNELS')编译标志 - 运行时门控 — GrowthBook
tengu_harbor(默认 false) - OAuth 门控 — 需要 claude.ai OAuth 登录
- 组织策略门控 — Team/Enterprise 必须显式启用
channelsEnabled: true - 会话白名单 — 需要
--channelsCLI 参数指定 - 插件市场审批 — 插件必须通过 Anthropic 市场审批
这些限制使得在我们自托管的桌面端 App 中无法直接使用 Channel 功能。
目标
参考 OpenClaw 的 IM Gateway 架构,在现有桌面端服务器基础上,以最小改动量实现 IM 平台接入,让用户可以从 Telegram、飞书、Slack、Discord 等 IM 直接与 Claude 对话并审批权限请求。
核心策略
不拆解 MCP Channel 的门控,而是在服务端新增 /im/ WebSocket 入口,直接复用 conversationService(CLI 子进程管理),绕过整个 MCP 层。
二、OpenClaw 参考分析
OpenClaw 是 GitHub 上 351k+ star 的开源 AI 助手项目,其最大特色是 IM 集成。
支持的 IM 平台(23+)
WhatsApp、Telegram、Slack、Discord、Google Chat、Signal、iMessage、IRC、Microsoft Teams、Matrix、飞书(Feishu)、LINE、Mattermost、Nextcloud Talk、Nostr、Synology Chat、Tlon、Twitch、Zalo、微信(WeChat)、WebChat 等。
架构模式
IM 平台 (Telegram / WeChat / Slack / ...)
|
v
┌─────────────────────┐
│ Gateway │
│ (WebSocket 控制面) │
│ ws://127.0.0.1:18789│
└──────────┬──────────┘
|
├── AI Agent (RPC) — 模型调用
├── CLI
├── WebChat UI
└── 移动端 App关键设计
- Gateway 是控制面:所有 IM 消息通过 Gateway 路由到 AI Agent
- Adapter 模式:每个 IM 平台一个独立 Adapter,连接到 Gateway
- 多用户隔离:不同用户/聊天对应不同 Agent 会话
- 安全机制:DM 配对码验证未知发送者
中国 IM 生态
社区插件仓库 openclaw-china 额外支持:飞书、钉钉、QQ、企业微信。
三、方案设计
架构总览
Telegram / 飞书 / Slack / Discord / 微信 ...
|
| (各平台 SDK)
v
IM Adapter(独立进程) ← 每个平台一个
|
| ws://localhost:3456/im/<adapterId>
v
┌──────────────────────┐
│ IM Gateway │ ← 新增模块 src/server/im/
│ (WebSocket handler) │
└──────────┬───────────┘
|
| 复用 conversationService
v
┌──────────────────────┐
│ CLI 子进程 (Claude) │ ← 已有基础设施,零改动
└──────────────────────┘设计决策
为什么不直接解锁 MCP Channel 门控?
MCP Channel 系统设计用于 CLI 交互模式(React/Ink 渲染),需要修改编译标志、GrowthBook 配置、OAuth 逻辑等 6 层代码。而我们的桌面端服务器有完全独立的架构(REST + WS + CLI 子进程),直接在服务端接入更简洁。
为什么 Adapter 独立进程?
- IM SDK 很重(telegraf ~50 deps,wechaty 更多),不应污染服务端
- 可以独立重启/更新,不影响服务器
- 自然隔离,一个 Adapter 崩溃不影响其他
- 与 OpenClaw 架构模式一致
为什么用 WebSocket?
双向实时通信是核心需求(流式回复、权限请求/回复),服务器已有 WebSocket 基础设施,HTTP 轮询会增加延迟和复杂度。
复用清单
以下已有代码可直接复用,无需修改:
| 函数 / 模块 | 文件位置 | 用途 |
|---|---|---|
conversationService.* | src/server/services/conversationService.ts | CLI 子进程管理全套 API |
shortRequestId() | src/services/mcp/channelPermissions.ts:140 | 生成 IM 友好的 5 字母权限 ID |
truncateForPreview() | src/services/mcp/channelPermissions.ts:160 | 工具输入截断为手机预览大小 |
PERMISSION_REPLY_RE | src/services/mcp/channelPermissions.ts:75 | 权限回复格式正则匹配 |
translateCliMessage() | src/server/ws/handler.ts:277 | CLI 消息翻译逻辑(作为参考) |
sessionService | src/server/services/sessionService.ts | 会话文件管理 |
四、消息协议
Adapter -> Gateway
// 注册
{ type: 'register'; platform: string; adapterId: string; secret?: string }
// IM 消息
{ type: 'im_message'; chatId: string; userId: string; userName?: string; content: string; meta?: Record<string, string> }
// 权限回复
{ type: 'permission_reply'; sessionId: string; requestId: string; allowed: boolean }
// 停止生成
{ type: 'stop'; chatId: string }
// 新建会话(用户 /new 命令)
{ type: 'new_session'; chatId: string }Gateway -> Adapter
// 注册确认
{ type: 'registered'; adapterId: string }
// 文本回复(流式)
{ type: 'text'; chatId: string; content: string; isComplete: boolean }
// 思考过程
{ type: 'thinking'; chatId: string; content: string }
// 工具调用
{ type: 'tool_use'; chatId: string; toolName: string; toolUseId: string; input: any }
// 工具结果
{ type: 'tool_result'; chatId: string; toolUseId: string; content: any; isError: boolean }
// 权限请求
{
type: 'permission_request';
chatId: string;
sessionId: string;
requestId: string;
shortId: string; // 5 字母 ID,如 "tbxkq"
toolName: string;
description?: string;
inputPreview: string; // 截断后的工具输入
}
// 状态变更
{ type: 'status'; chatId: string; state: 'thinking' | 'streaming' | 'tool_executing' | 'idle' }
// 错误
{ type: 'error'; chatId: string; message: string; code?: string }
// 完成
{ type: 'complete'; chatId: string; usage: { input_tokens: number; output_tokens: number } }五、消息流详解
正常对话流
用户在 Telegram 发送 "检查 main.ts 有没有 bug"
→ Telegram Bot 收到消息
→ Adapter 通过 WebSocket 发送:
{ type: "im_message", chatId: "12345", userId: "alice", content: "检查 main.ts" }
→ Gateway 收到
→ 查找 chatId->sessionId 映射(不存在则创建新 session)
→ conversationService.startSession(sessionId, workDir, sdkUrl)
→ conversationService.sendMessage(sessionId, content)
→ CLI 子进程启动,调用 Claude API
→ CLI 输出 stream_event
→ conversationService.onOutput() 回调触发
→ Gateway 翻译为 GatewayMessage
→ WebSocket 发送: { type: "text", chatId: "12345", content: "让我看看...", isComplete: false }
→ Adapter 调用 ctx.reply("让我看看...")
→ 用户在 Telegram 看到回复权限审批流
CLI 发送 control_request:
{ subtype: "can_use_tool", tool_name: "Bash", input: { command: "npm test" } }
→ Gateway 收到
→ 生成 shortId = shortRequestId(toolUseId) → "tbxkq"
→ WebSocket 发送:
{ type: "permission_request", chatId: "12345", shortId: "tbxkq",
toolName: "Bash", inputPreview: '{"command":"npm test"}' }
→ Adapter 在 Telegram 发送:
"需要权限确认
工具: Bash
内容: npm test
[允许] [拒绝]"
→ 用户点击 [允许]
→ Adapter 发送: { type: "permission_reply", requestId: "tbxkq", allowed: true }
→ Gateway 调用 conversationService.respondToPermission(sessionId, requestId, true)
→ CLI 继续执行六、Adapter 实现
目录结构
adapters/
telegram/
index.ts — Telegram Bot Adapter(基于 telegraf)
package.json — 依赖声明
feishu/
index.ts — 飞书 Adapter(基于 @larksuiteoapi/node-sdk)
package.json
slack/
index.ts — Slack Adapter(基于 Bolt)
package.json
discord/
index.ts — Discord Adapter(基于 discord.js)
package.json
wechat/
index.ts — 微信 Adapter(基于 wechaty)
package.jsonTelegram Adapter 示例
// 伪代码 — 核心逻辑
import { Telegraf } from 'telegraf'
import WebSocket from 'ws'
const bot = new Telegraf(process.env.BOT_TOKEN)
const ws = new WebSocket('ws://localhost:3456/im/telegram-001')
// IM -> Gateway
bot.on('text', (ctx) => {
ws.send(JSON.stringify({
type: 'im_message',
chatId: String(ctx.chat.id),
userId: String(ctx.from.id),
userName: ctx.from.first_name,
content: ctx.message.text,
}))
})
// Gateway -> IM
ws.on('message', (data) => {
const msg = JSON.parse(data)
switch (msg.type) {
case 'text':
bot.telegram.sendMessage(msg.chatId, msg.content, { parse_mode: 'Markdown' })
break
case 'permission_request':
bot.telegram.sendMessage(msg.chatId,
`需要权限: ${msg.toolName}\n${msg.inputPreview}`,
{ reply_markup: {
inline_keyboard: [[
{ text: '允许', callback_data: `permit:${msg.requestId}:yes` },
{ text: '拒绝', callback_data: `permit:${msg.requestId}:no` },
]]
}}
)
break
}
})
// 处理按钮回调
bot.on('callback_query', (ctx) => {
const [, requestId, decision] = ctx.callbackQuery.data.split(':')
ws.send(JSON.stringify({
type: 'permission_reply',
requestId,
allowed: decision === 'yes',
}))
})飞书 Adapter 要点
- 使用飞书事件订阅(HTTP 回调模式)接收消息
- 权限请求使用交互式卡片(Message Card),按钮体验更好
- 支持富文本回复
各平台消息限制
| 平台 | 消息长度限制 | 处理方式 |
|---|---|---|
| Telegram | 4096 字符 | 自动分段发送 |
| 飞书 | 无硬限制(建议 < 30KB) | 长消息可折叠 |
| Slack | 40000 字符 | 分 Block 发送 |
| Discord | 2000 字符 | 分段 + Embed |
| 微信 | 2048 字符 | 分段发送 |
七、文件清单
需要修改的现有文件(约 35 行改动)
| 文件 | 改动量 | 内容 |
|---|---|---|
src/server/index.ts | ~20 行 | 新增 /im/ WebSocket 升级路径 |
src/server/ws/handler.ts | ~15 行 | WebSocketData 类型扩展为 'client' | 'sdk' | 'im',open/message/close 中委托 IM 消息给 gateway |
需要新建的文件
| 文件 | 说明 |
|---|---|
src/server/im/types.ts | IM 消息协议类型定义 |
src/server/im/gateway.ts | Gateway 核心 — Adapter 连接管理、chatId->session 路由、消息翻译 |
src/server/im/sessionMap.ts | chatId-sessionId 映射管理、可选持久化 |
src/server/im/config.ts | ~/.claude/im-gateway.json 配置读取 |
adapters/telegram/index.ts | Telegram Bot Adapter |
adapters/telegram/package.json | 依赖 telegraf + ws |
adapters/feishu/index.ts | 飞书 Adapter |
adapters/feishu/package.json | 依赖 @larksuiteoapi/node-sdk + ws |
配置文件
~/.claude/im-gateway.json:
{
"enabled": true,
"defaultWorkDir": "/path/to/default/project",
"defaultPermissionMode": "default",
"adapters": {
"telegram": {
"secret": "optional-shared-secret",
"allowedUsers": ["123456789"],
"workDir": "/path/to/project"
},
"feishu": {
"secret": "optional-shared-secret",
"allowedUsers": ["ou_xxx"],
"workDir": "/path/to/another/project"
}
}
}八、验证方案
1. WebSocket 连通性测试
# 启动服务器
bun run server
# 用 wscat 模拟 Adapter
wscat -c ws://localhost:3456/im/test-adapter
> {"type":"register","platform":"test","adapterId":"test-001"}
# 期望收到: {"type":"registered","adapterId":"test-001"}
> {"type":"im_message","chatId":"chat-1","userId":"user-1","content":"hello"}
# 期望收到: 一系列 text/thinking/status/complete 消息2. Telegram 端到端测试
- 创建 Telegram Bot(@BotFather)
- 配置 Bot Token 到 Adapter
- 启动服务器 + Telegram Adapter
- 在 Telegram 发送 "hello"
- 验证收到 Claude 回复
- 发送 "读取 package.json" 触发工具调用
- 验证权限请求按钮出现
- 点击允许,验证工具执行和结果返回
3. 多会话隔离测试
- 从不同 Telegram 聊天发消息,验证会话独立
- 发送
/new命令,验证新会话创建 - 验证旧会话不受影响
4. 异常场景测试
- Adapter 断开重连,验证会话恢复
- CLI 进程崩溃,验证错误消息发送到 IM
- 权限请求超时,验证优雅处理
九、与 OpenClaw 对比
| 特性 | OpenClaw | 我们的方案 |
|---|---|---|
| 架构 | Gateway + Agent (RPC) | Gateway + CLI 子进程 (SDK WS) |
| IM 平台 | 23+ 内置 | Adapter 模式,按需添加 |
| 权限审批 | 无(直接执行) | 5 字母 ID 审批(复用已有系统) |
| 多用户 | 全局单 Agent | 每 chatId 独立 session |
| AI 模型 | OpenAI / Claude / Gemini 等 | Claude(通过现有 API key) |
| 工具能力 | 浏览器控制、语音等 | 完整 Claude Code 工具集(文件读写、终端、搜索等) |
| 部署方式 | 单体 + 插件 | 服务端 + 独立 Adapter 进程 |
| 改动量 | 全新项目 | 现有服务端 ~35 行改动 + 3 个新模块 |
我们的优势
- Claude Code 原生工具链:Adapter 连接的不是普通聊天机器人,而是完整的 Claude Code Agent,具备文件读写、代码搜索、Git 操作、终端执行等全部能力
- 权限安全:内置权限审批机制,从 IM 端可以审批敏感操作
- 改动量极小:核心只改 2 个文件 ~35 行,新增 3 个模块
OpenClaw 的优势
- IM 平台覆盖:23+ 平台开箱即用
- 社区生态:5400+ 社区 Skills
- 语音交互:支持语音唤醒和对话
- 多模型:支持 OpenAI、Gemini 等多种模型
十、开放问题
以下问题在实现前需要进一步思考和决策。
10.1 安全性
- Adapter 连接到 Gateway 时是否需要认证?(shared secret / API key)
- 如何防止未授权的 IM 用户与 Claude 对话?(allowedUsers 白名单是否足够?)
- 是否需要 IP 白名单或 Tailscale 等网络层隔离?
10.2 多用户 / 多项目
- 一个 IM 用户是否能切换不同项目目录?(如
/project /path/to/repo) - 是否支持多个用户同时使用同一个 Bot?
- 会话上限和资源管理(同时运行多少个 CLI 子进程?)
10.3 消息体验
- 长回复如何处理?逐段发送 vs 等待完成后发送?
- 用户快速连发多条消息,是否需要聚合?
- 代码块在 IM 中的渲染质量(Telegram Markdown vs 飞书富文本 vs 微信纯文本)
10.4 运维
- Adapter 进程管理(systemd / pm2 / Docker?)
- 日志和监控
- 服务器重启后 session 恢复
10.5 功能边界
- 是否支持文件/图片上传?(用户在 IM 中发送截图给 Claude)
- 是否支持 Claude 返回的图片/文件?(如截图、生成的文件)
- 是否支持 slash commands(
/help、/status、/new)?
参考资料
- OpenClaw GitHub — 351k star 开源 AI 助手
- OpenClaw China — 中国 IM 社区插件
- Channel 系统架构解析 — 源码 Channel 系统文档
- Telegraf — Telegram Bot Framework
- @larksuiteoapi/node-sdk — 飞书 SDK