Skip to content

Commit 6ad821d

Browse files
7418claude
andcommitted
feat: 多 IM 远程桥接系统 + Bridge 一级导航 + bug 修复
**Bridge 系统核心(src/lib/bridge/)** - bridge-manager: 生命周期编排、adapter 事件循环、会话级并发控制 - channel-adapter: 抽象基类 + 注册式 adapter 工厂 - channel-router: IM 地址 → CodePilot session 自动映射 - conversation-engine: 服务端消费 streamClaude() SSE 流 - permission-broker: 权限请求转发到 IM 内联按钮 - delivery-layer: 出站分片、限流、重试退避、HTML 降级 - telegram-adapter: Telegram 长轮询 + offset 安全水位 + 幂等去重 - security/: 路径校验、危险输入检测、滑动窗口限流 **Telegram 通知 Bot(src/lib/telegram-bot.ts)** - 独立通知模式,与 bridge 模式互斥 - bridgeModeActive 使用 globalThis 存储,避免 HMR 重置导致轮询冲突 **API 路由** - /api/bridge: GET 状态查询 + POST start/stop/auto-start - /api/bridge/channels: 活跃通道列表 - /api/bridge/settings: bridge 设置读写 - /api/settings/telegram: 凭据管理(含 allowed_users 白名单) - /api/settings/telegram/verify: 连接验证 + chat ID 自动检测 **UI 重构:Bridge 提升为一级导航** - NavRail 新增 Bridge 图标导航项 - /bridge 页面:BridgeLayout hash 路由,Bridge + Telegram 两个子 tab - BridgeSection: 总开关、状态、启停、通道开关、适配器状态、默认设置 - TelegramBridgeSection: 凭据配置 + 白名单 + Setup Guide(6 步) - Settings 移除 Telegram 和 Bridge tab,只保留 4 个核心 tab **Bug 修复** - bridge-manager: runAdapterLoop 在 state.running=true 之前被调用, while 条件同步求值为 false 导致消费者循环不启动。修复为先设 running 再启动 loop - telegram-bot: bridgeModeActive 改用 globalThis 防止 HMR 丢失状态 - settings/telegram route: TELEGRAM_KEYS 补充 telegram_bridge_allowed_users **Electron** - 窗口关闭时 bridge 活跃则最小化到系统托盘而非退出 - 启动时通过 auto-start API 恢复桥接 - preload 暴露 bridge 相关 IPC **文档** - docs/bridge-system.md: 完整架构文档 - docs/chat-sdk-integration-feasibility.md: Chat SDK 集成调研 - CLAUDE.md 添加 bridge 文档链接 chore: bump v0.21.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f7735e0 commit 6ad821d

35 files changed

+5525
-8
lines changed

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ CodePilot — Claude Code 的桌面 GUI 客户端,基于 Electron + Next.js。
6868
## Architecture Docs
6969

7070
- [Agent Tooling & TodoWrite Bridge](docs/agent-tooling-todo-bridge.md) — SDK → SSE → DB 事件流、TodoWrite 字段映射、tool_result 三层去重策略
71+
- [多 IM 远程会话桥接系统](docs/bridge-system.md) — Telegram 远程操控 Claude 会话,含目录结构、数据流、关键设计决策
7172

7273
## Build Notes
7374

docs/bridge-system.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# 多 IM 远程会话桥接系统
2+
3+
## 核心思路
4+
5+
让用户通过 Telegram(后续可扩展 Discord/飞书等)远程操控 CodePilot 中的 Claude 会话。复用现有 `streamClaude()` 管线,在服务端消费 SSE 流,而非通过浏览器。
6+
7+
## 目录结构
8+
9+
```
10+
src/lib/bridge/
11+
├── types.ts # 共享类型(ChannelBinding, BridgeStatus, InboundMessage 等)
12+
├── channel-adapter.ts # 抽象基类 + adapter 注册表(registerAdapterFactory/createAdapter)
13+
├── channel-router.ts # (channel, user, thread) → session 映射,自动创建/绑定会话
14+
├── conversation-engine.ts # 服务端消费 streamClaude() SSE 流,保存消息到 DB
15+
├── permission-broker.ts # 权限请求转发到 IM 内联按钮,处理回调审批
16+
├── delivery-layer.ts # 出站消息分片、限流、重试退避、HTML 降级
17+
├── bridge-manager.ts # 生命周期编排,adapter 事件循环,/stop abort,命令路由
18+
├── adapters/
19+
│ ├── index.ts # Adapter 目录文件(side-effect import 自注册所有 adapter)
20+
│ ├── telegram-adapter.ts # Telegram 长轮询 + offset 安全水位 + 自注册
21+
│ └── telegram-utils.ts # callTelegramApi / escapeHtml / splitMessage
22+
└── security/
23+
├── rate-limiter.ts # 按 chat 滑动窗口限流(20 条/分钟)
24+
└── validators.ts # 路径/SessionID/危险输入校验
25+
```
26+
27+
## 数据流
28+
29+
```
30+
Telegram 消息 → TelegramAdapter.pollLoop() → enqueue()
31+
→ BridgeManager.runAdapterLoop() → handleMessage()
32+
→ 命令? → handleCommand() 处理 /new /bind /cwd /mode /stop 等
33+
→ 普通消息? → ChannelRouter.resolve() 获取 ChannelBinding
34+
→ ConversationEngine.processMessage()
35+
→ streamClaude() 获取 SSE 流
36+
→ consumeStream() 服务端消费
37+
→ permission_request → 立即回调 → PermissionBroker 转发到 IM
38+
→ text/tool_use/tool_result → 累积内容块
39+
→ result → 捕获 tokenUsage + sdkSessionId
40+
→ addMessage() 保存到 DB
41+
→ DeliveryLayer.deliver() → 分片 + 限流 + 发送到 Telegram
42+
→ finally: adapter.acknowledgeUpdate(updateId) → 推进 committedOffset 并持久化
43+
```
44+
45+
## DB 表(在 db.ts migrateDb 中)
46+
47+
|| 用途 |
48+
|---|------|
49+
| channel_bindings | IM 地址 → CodePilot session 映射 |
50+
| channel_offsets | 轮询 offset 持久化(key 为 bot user ID,通过 getMe API 获取) |
51+
| channel_dedupe | 出站消息幂等去重 |
52+
| channel_outbound_refs | 平台消息 ID 映射 |
53+
| channel_audit_logs | 审计日志 |
54+
| channel_permission_links | 权限请求 → IM 消息映射(含 resolved 标记) |
55+
56+
## 关键设计决策
57+
58+
**1. 权限请求死锁解决**
59+
SSE 流在 `permission_request` 事件处会阻塞等待审批。`consumeStream()` 通过 `onPermissionRequest` 回调在流消费过程中立即转发到 IM,而非等流结束后再转发。
60+
61+
**2. Offset 安全水位**
62+
分离 `fetchOffset`(用于 getUpdates API)和 `committedOffset`(持久化到 DB)。消息入队时仅推进 fetchOffset,只有在 bridge-manager 完整处理完消息后(handleMessage 的 finally 块),才调用 `adapter.acknowledgeUpdate(updateId)` 推进 committedOffset 并持久化到 DB。这确保崩溃时未处理完的消息会被重新投递。内存 dedup set 防止重启后重复处理。
63+
64+
**2a. Bot 身份标识**
65+
Offset 的 DB key 使用 Telegram `getMe` API 返回的 bot user ID(如 `telegram:bot123456`),而非 token hash。好处是 token 轮换后 offset 不丢失。首次迁移时自动将旧 token-hash key 的值复制到新 bot-ID key。
66+
67+
**3. 并发模型**
68+
`processWithSessionLock()` 实现同会话串行、跨会话并行。不同用户的消息不互相阻塞。
69+
70+
**4. Adapter 注册式架构**
71+
新 IM 只需实现 `BaseChannelAdapter` 并调用 `registerAdapterFactory()` 自注册,然后在 `adapters/index.ts` 中添加一行 side-effect import。bridge-manager 通过 `import './adapters'` 加载目录,registry 自动发现所有已注册的 adapter,无硬编码依赖。
72+
73+
**5. 权限回调安全**
74+
PermissionBroker 在处理 IM 内联按钮回调时,验证 callbackData 中的 chatId 和 messageId 与存储的 permission_link 记录匹配,防止跨聊天伪造审批。`markPermissionLinkResolved()` 使用 `AND resolved = 0` 原子条件更新,确保同一权限请求不被重复审批。
75+
76+
**6. 输入校验**
77+
`security/validators.ts` 对所有 IM 入站命令参数做校验:工作目录路径(拒绝 `..`、null 字节、shell 元字符)、session ID(hex/UUID 格式)、危险输入检测(命令注入、管道符)。`sanitizeInput()` 剥离控制字符并限制 32K 长度。
78+
79+
**7. runAdapterLoop 必须在 state.running = true 之后启动**
80+
`runAdapterLoop` 内部是 fire-and-forget 的 async IIFE,循环条件 `while (state.running && ...)` 在第一个 `await` 之前同步求值。如果调用时 `state.running` 还是 `false`,循环直接跳过,消费者永远不会启动,消息入队后无人消费。`start()` 中必须先设 `state.running = true`,再调用 `runAdapterLoop`
81+
82+
**8. 出站限流**
83+
`security/rate-limiter.ts` 按 chatId 滑动窗口限流(默认 20 条/分钟)。`DeliveryLayer` 在每次发送前调用 `rateLimiter.acquire(chatId)` 阻塞等待配额,分片间额外加 300ms 节流。错误分类:429 尊重 `retry_after`、5xx 指数退避、4xx 不重试、解析错误降级纯文本。
84+
85+
## 设置项(settings 表)
86+
87+
| Key | 说明 |
88+
|-----|------|
89+
| remote_bridge_enabled | 总开关 |
90+
| bridge_telegram_enabled | Telegram 通道开关 |
91+
| bridge_auto_start | 服务启动时自动拉起桥接 |
92+
| bridge_default_work_dir | 新建会话默认工作目录 |
93+
| bridge_default_model | 新建会话默认模型 |
94+
| bridge_default_provider_id | 新建会话默认服务商 |
95+
| telegram_bridge_allowed_users | 白名单用户 ID(逗号分隔) |
96+
97+
## API 路由
98+
99+
| 路由 | 方法 | 功能 |
100+
|------|------|------|
101+
| /api/bridge | GET | 返回 BridgeStatus(纯查询,无副作用) |
102+
| /api/bridge | POST | `{ action: 'start' \| 'stop' \| 'auto-start' }` |
103+
| /api/bridge/channels | GET | 列出活跃通道(支持 `?active=true/false` 过滤) |
104+
| /api/bridge/settings | GET/PUT | 读写 bridge 设置 |
105+
106+
## Telegram 命令
107+
108+
| 命令 | 功能 |
109+
|------|------|
110+
| /new [path] | 新建会话 |
111+
| /bind \<session_id\> | 绑定已有会话 |
112+
| /cwd /path | 切换工作目录 |
113+
| /mode plan\|code\|ask | 切换模式 |
114+
| /status | 当前状态 |
115+
| /sessions | 列出会话 |
116+
| /stop | 中止运行中任务 |
117+
| /help | 帮助 |
118+
119+
## 相关文件(bridge 之外)
120+
121+
- `src/lib/telegram-bot.ts` — 通知模式(UI 发起会话的通知),与 bridge 模式互斥
122+
- `src/lib/permission-registry.ts` — 权限 Promise 注册表,bridge 和 UI 共用
123+
- `src/lib/claude-client.ts` — streamClaude(),bridge 和 UI 共用
124+
- `src/components/bridge/BridgeSection.tsx` — Bridge 设置 UI(一级导航 /bridge)
125+
- `src/components/bridge/TelegramBridgeSection.tsx` — Telegram 凭据 + 白名单设置 UI(/bridge#telegram)
126+
- `electron/main.ts` — 窗口关闭时 bridge 活跃则保持后台运行;启动时通过 POST `auto-start` 触发桥接恢复
127+
- `src/app/api/settings/telegram/verify/route.ts` — 支持 `register_commands` action 注册 Telegram 命令菜单
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Chat SDK 集成可行性调研报告
2+
3+
## 背景
4+
5+
调研 Vercel 最新推出的 Chat SDK(`npm install chat`)是否能集成到 CodePilot 中,实现通过 Telegram 等 IM 远程控制 AI 应用。
6+
7+
---
8+
9+
## 一、Chat SDK 是什么
10+
11+
Chat SDK(包名 `chat`)是 Vercel 推出的**独立于 AI SDK 的新包**,核心能力是:**一套代码部署到多个聊天平台**
12+
13+
- 包名:`chat`(不是 `ai`
14+
- 已支持平台:Slack、Microsoft Teams、Google Chat、Discord、**Telegram**、GitHub、Linear
15+
- 通过 Matrix 适配器(Beeper Cloud)还可桥接 WhatsApp、Instagram、Signal 等
16+
- 与 AI SDK 是互补关系:AI SDK 负责 LLM 调用,Chat SDK 负责多平台消息分发
17+
18+
## 二、CodePilot 当前架构
19+
20+
| 层级 | 技术 | 说明 |
21+
|------|------|------|
22+
| 核心引擎 | `@anthropic-ai/claude-agent-sdk` | 主要的 Claude Code 交互,工具执行、权限管理 |
23+
| 辅助生成 | `ai` + `@ai-sdk/*` | 仅用于图片生成和文本批处理规划 |
24+
| 通信协议 | 自定义 SSE | 12 种事件类型(text、tool_use、permission_request 等) |
25+
| 前端 | Next.js + 自定义组件 | 不使用 useChat,完全自建的流管理和 UI |
26+
| 运行环境 | Electron 桌面端 | 本地进程,操作用户本地文件系统 |
27+
28+
**关键点**:CodePilot 的核心是 Claude Agent SDK 驱动的**本地代码执行引擎**,不是简单的 LLM 聊天。它涉及文件读写、终端命令、权限审批等本地操作。
29+
30+
## 三、能否集成?技术分析
31+
32+
### 可行的部分
33+
34+
Chat SDK 的 Telegram 适配器可以实现:
35+
- 接收 Telegram 消息并转发给后端
36+
- 将文本回复推送回 Telegram
37+
- 支持按钮交互(inline keyboard)
38+
- AI SDK 流式输出直接推送到 Telegram
39+
40+
### 不可行 / 困难的部分
41+
42+
| 问题 | 说明 |
43+
|------|------|
44+
| **权限审批** | Claude Code 执行工具前需要用户授权(文件写入、命令执行),Telegram 的交互能力有限(callback data 仅 64 字节),难以承载复杂的权限审批流 |
45+
| **富内容展示** | CodePilot 的 UI 包含代码高亮、diff 预览、文件树、终端输出、思维链等,Telegram 仅支持纯文本 + Markdown |
46+
| **本地文件操作** | Claude Code 需要操作本地文件系统,而 Chat SDK 是服务端部署的,需要额外的远程访问层 |
47+
| **会话状态** | 当前的 SSE 流管理和 session 恢复机制是为 Electron 设计的,需要大幅改造才能适配 Chat SDK 的事件模型 |
48+
| **部署模型不同** | Chat SDK 需要一个常驻服务端来接收 webhook,而 CodePilot 是桌面应用 |
49+
50+
### 架构冲突
51+
52+
```
53+
当前:用户 ←→ Electron UI ←→ Next.js API ←→ Claude Agent SDK ←→ 本地文件系统
54+
55+
Chat SDK 需要:Telegram ←→ 云端服务 ←→ ??? ←→ 本地文件系统(断裂)
56+
```
57+
58+
Chat SDK 本质是让 **服务端 bot** 部署到多个平台。而 CodePilot 是**本地桌面应用**直接操作用户机器上的文件。这两个模型之间存在根本性的架构鸿沟。
59+
60+
## 四、结论
61+
62+
**不建议集成 Chat SDK 到 CodePilot。** 原因:
63+
64+
1. **用途不匹配**:Chat SDK 解决的是「一个 bot 部署到多个聊天平台」的问题,而 CodePilot 是本地代码执行工具,不是聊天 bot
65+
2. **改造成本极高**:需要引入云端中转服务、远程文件访问、简化版权限审批、消息格式降级等大量基础设施
66+
3. **体验必然降级**:Telegram 无法承载 CodePilot 当前的富交互体验(代码 diff、文件树、工具审批等)
67+
4. **安全风险**:通过 Telegram 远程触发本地文件操作和终端命令,需要非常谨慎的安全设计
68+
69+
### 如果确实想要远程控制能力
70+
71+
更合理的路径是:
72+
- **方案 A**:为 CodePilot 加一个 Web 远程访问层(类似 VS Code Remote),保留完整 UI 体验
73+
- **方案 B**:做一个极简的 Telegram bot 只负责监控和通知(任务进度、错误告警),不做执行控制

0 commit comments

Comments
 (0)