Remote Control Bridge Deep-Dive

April 4, 2026 · View on GitHub

离开电脑后 Agent 需要人类审批权限——当前无法远程操作。本文基于 Claude Code 源码分析(bridge/ 目录 ~12600 行)和 Qwen Code(channels/ + ACP 架构)的源码对比,深度介绍 Claude Code 的 Remote Control Bridge 机制及其与 Qwen Code 的架构差异。


1. 问题定义

AI Agent 执行长任务时经常需要人类审批(文件写入、shell 命令、网络访问)。如果用户离开电脑,Agent 就卡在权限对话框上——无法继续工作。

场景无远程控制有远程控制
用户外出Agent 暂停等待审批手机/浏览器远程审批
跨设备必须回到同一终端Web/手机/iPad 均可操作
长任务监控无法查看进度实时查看输出和工具调用
崩溃恢复session 丢失Bridge Pointer 自动恢复

2. Claude Code:Remote Control Bridge 架构

2.1 整体架构

┌─────────────┐     WebSocket/SSE      ┌─────────────┐     HTTPS      ┌─────────────┐
│  Terminal    │ ◄──────────────────── │  Cloud API   │ ◄──────────── │  Web/Mobile  │
│  (本地 CLI)  │ ──────────────────── │  (中继服务)   │ ──────────── │  (浏览器/App) │
│             │     HTTP POST          │             │     OAuth      │             │
│  Outbound   │                        │  Session     │                │  用户审批    │
│  Only Mode  │                        │  Ingress     │                │  上下文补充   │
└─────────────┘                        └─────────────┘                └─────────────┘

关键设计决策:Terminal 端是 Outbound-only 模式——主动向云端推送事件,不接受入站连接。这避免了 NAT 穿透、防火墙配置、端口暴露等问题。

2.2 三层传输协议

Claude Code 实现了 3 种传输协议,按优先级降级:

// 源码: cli/transports/ 目录

// V1: WebSocket 读 + HTTP POST 写(HybridTransport)
// 读: wss://.../{version}/session_ingress/ws/{sessionId}
// 写: POST /v1/sessions/{id}/events (批量,100ms 合并窗口)

// V2: SSE 读 + HTTP POST 写(SSETransport + CCRClient)
// 读: GET /worker/events/stream(Last-Event-ID 续传)
// 写: POST /worker/events (SerialBatchEventUploader)

// 降级链: WebSocket → SSE → HTTP 轮询

保活机制

层级间隔超时
WebSocket ping/pong10s
Keep-alive 数据帧5min
SSE comment 帧15s(服务端)45s(客户端判定死亡)
Session 心跳120s(2min)

重连策略

// 源码: bridge/bridgeMain.ts#L72-75
const DEFAULT_BACKOFF = {
  connInitialMs: 2_000,       // 初始 2 秒
  connCapMs: 120_000,         // 上限 2 分钟
  connGiveUpMs: 600_000,      // 放弃 10 分钟
}
  • 指数退避 + 抖动(delay × 2 + jitter
  • 睡眠检测:连续两次尝试间隔 > cap × 2,判定系统休眠,重置退避
  • 永久关闭码(1002/4001/4003)不重连

2.3 会话生命周期

注册与创建

Terminal                         Cloud API
   │                               │
   ├─ POST /v1/environments/bridge ─→  注册环境(OAuth)
   │                               │
   │← environmentId + secret ──────┤
   │                               │
   ├─ POST /v1/sessions ──────────→  创建会话
   │                               │
   │← sessionId ──────────────────┤
   │                               │
   ├─ GET /work/poll ─────────────→  等待 Web 端发起工作
   │                               │  (30-60s 长轮询)
   │← work_secret(base64url JSON)┤
   │                               │
   ├─ POST /work/{id}/ack ────────→  确认接收
   │                               │
   ├─ ws://session_ingress/ws/{id} → 建立 WebSocket
   │                               │
   │← 实时事件流 ─────────────────┤

work_secret 解码(源码: bridge/workSecret.ts):

// Base64url JSON 包含(源码: bridge/workSecret.ts 解码验证字段):
{
  version: number,                // 协议版本
  session_ingress_token: string,  // JWT 访问令牌
  api_base_url: string,           // API 端点
  // 额外字段(由 WorkSecret 类型定义,解码器不做强校验):
  sources?: unknown,              // 数据源配置
  mcp_config?: unknown            // MCP 服务器配置
}

崩溃恢复

// 源码: bridge/bridgePointer.ts
// 会话创建后立即写入指针文件
// 路径: ~/.claude/projects/{sanitized_dir}/bridge-pointer.json
{
  sessionId: string,
  environmentId: string,
  source: 'standalone' | 'repl'
}
// TTL: 4 小时(基于 mtime,非嵌入时间戳)

恢复流程:

  1. 启动时读取 bridge pointer(mtime < 4h)
  2. POST /v1/environments/bridge 携带 reuseEnvironmentId
  3. 如果环境仍存活,reconnectSession() 恢复
  4. Poll 循环从中断处继续

Worktree 感知恢复(源码: bridgePointer.ts#L129-184):

// 在 git worktree 兄弟目录中扇出查找最新 pointer
// 最多并行 stat() 50 个 worktree(MAX_WORKTREE_FANOUT)
readBridgePointerAcrossWorktrees()
// 选择 ageMs 最小的 pointer

2.4 消息协议

出站(Terminal → Cloud)

消息类型内容触发时机
SDKAssistantMessage模型文本输出每个 content block
SDKPartialAssistantMessage流式增量每个 text_delta
SDKToolProgressMessage工具执行进度工具运行中
SDKResultMessage轮次完成end_turn
SDKControlResponse权限审批响应用户审批后

入站(Cloud → Terminal)

消息类型内容来源
SDKUserMessage用户输入Web/Mobile 输入框
SDKControlRequest控制请求模型切换/中断/权限
SDKControlResponse权限决策Web 审批对话框

消息去重

// 源码: bridge/bridgeMessaging.ts#L429-461
// BoundedUUIDSet: 环形缓冲区 + Set,O(1) 查重
// 容量 2000,FIFO 淘汰

// 两层去重:
recentPostedUUIDs  // 我发出的消息——忽略回声
recentInboundUUIDs // 已处理的入站——忽略历史重放

2.5 远程权限处理

这是 Remote Control Bridge 最核心的价值——用户在 Web/Mobile 上审批权限请求:

Terminal                    Cloud                    Web/Mobile
   │                         │                          │
   │  模型请求 Bash("rm -rf") │                          │
   │                         │                          │
   ├─ control_request ──────→│                          │
   │  {                      │                          │
   │    subtype: 'can_use_tool',                        │
   │    tool_name: 'Bash',   │─── 转发权限请求 ─────────→│
   │    input: {command: 'rm -rf build/'},               │
   │    tool_use_id: 'xxx'   │                   用户看到│
   │  }                      │                 权限对话框│
   │                         │                          │
   │                         │←── 用户点击 Allow ────────┤
   │                         │    {behavior: 'allow'}   │
   │← control_response ─────┤                          │
   │  {                      │                          │
   │    subtype: 'success',  │                          │
   │    response: {          │                          │
   │      behavior: 'allow', │                          │
   │      updatedInput: ..., │                          │
   │      updatedPermissions: [...] │                   │
   │    }                    │                          │
   │  }                      │                          │
   │                         │                          │
   │  执行 rm -rf build/     │                          │

权限建议(Permission Suggestions):

// 源码: bridge/bridgePermissionCallbacks.ts
// 发送请求时附带建议规则
{
  permissionSuggestions: [
    { tool: 'Bash', rule: 'rm -rf build/', scope: 'session' }
  ]
}
// Web 端可一键接受建议——后续相同命令自动批准

2.6 安全模型

层级机制实现
API 认证OAuth Bearer TokengetBridgeAccessToken() 从 Keychain
会话认证Session Ingress JWTwork_secret 解码获取
设备信任Trusted Device Token90 天滚动过期,Keychain 存储
ID 验证正则白名单 [a-zA-Z0-9_-]+防路径遍历和注入
消息净化hooks/useReplBridge.tsx 引用 webhookSanitizer.js(运行时生成)防 XSS 和注入

Trusted Device Token(源码: bridge/trustedDevice.ts):

// 注册: POST /api/auth/trusted_devices(登录后 <10 分钟内)
// 存储: macOS Keychain
// 有效期: 90 天滚动
// Header: X-Trusted-Device-Token
// 门控: tengu_sessions_elevated_auth_enforcement(GrowthBook)

2.7 多会话模式

// 源码: bridge/bridgeMain.ts
// 三种模式:
'single-session'  // claude remote-control(会话结束即退出)
'worktree'        // 持久服务,每个会话独立 git worktree
'same-dir'        // 持久服务,共享工作目录(可能冲突)

容量管理(源码: bridge/pollConfigDefaults.ts):

{
  poll_interval_ms_not_at_capacity: 2_000,     // 空闲时 2s 轮询
  poll_interval_ms_at_capacity: 600_000,       // 满载时 10min 轮询
  multisession_poll_interval_ms_partial_capacity: 2_000, // 部分满载 2s
  reclaim_older_than_ms: 5_000,                // 5s 无活动可回收
  session_keepalive_interval_v2_ms: 120_000    // 2min 心跳
}

Capacity Wake(源码: bridge/capacityWake.ts):会话结束时发送唤醒信号,中断 at-capacity 睡眠,立即轮询新工作。

2.8 文件附件

Web 端用户可上传文件(截图、文档):

// 源码: bridge/inboundAttachments.ts
// 1. Web 上传: POST /api/{org}/upload(Cookie 认证)
// 2. Bridge 下载: GET /api/oauth/files/{uuid}/content
// 3. 存储: ~/.claude/uploads/{sessionId}/{uuid-prefix}-{filename}
// 4. 注入: 在入站消息末尾追加 @"path" 引用

3. Qwen Code:Channels + ACP 架构

3.1 Channels 系统

Qwen Code 通过 Channels 架构实现多平台接入:

┌─────────────┐                    ┌─────────────┐
│  DingTalk    │──── AcpBridge ────│             │
├─────────────┤    (stdio ndjson)  │  Qwen Code  │
│  Telegram   │──── AcpBridge ────│  CLI 子进程  │
├─────────────┤                    │             │
│  WeChat     │──── AcpBridge ────│  (--acp)    │
├─────────────┤                    │             │
│  VSCode     │──── HTTP MCP ─────│             │
└─────────────┘                    └─────────────┘

AcpBridge(源码: packages/channels/base/src/AcpBridge.ts):

  • spawn CLI 子进程 + --acp flag
  • stdio ndjson 双向 IPC
  • Agent Client Protocol (ACP) SDK 集成
  • 崩溃后指数退避重启

会话路由(SessionRouter):

  • Key 格式按作用域不同:user<channel>:<senderId>:<chatId>thread<channel>:<threadId|chatId>single<channel>:__single__
  • 作用域: user(每用户独立)/ thread(每线程)/ single(全局单一)
  • 消息调度(ChannelBase 层): collect(收集)/ steer(转向,默认)/ followup(追加)

3.2 与 Claude Code Bridge 的关键差异

维度Claude Code BridgeQwen Code Channels
方向Terminal → Cloud(Outbound-only)Platform → CLI(Inbound-only)
场景用户远程驱动自己的终端外部平台用户向 Agent 提问
认证OAuth + JWT + Trusted DevicePlatform-specific(Bot Token)
权限审批远程转发到 Web,用户审批本地 CLI 权限规则自动决策
会话持有者终端用户(同一人远程操作)平台用户(不同人向 Agent 提问)
崩溃恢复Bridge Pointer 自动恢复AcpBridge 进程重启
文件附件Web 上传 → Bridge 下载 → @引用Platform 消息附件 → CLI
连接模型长连接 WebSocket/SSE子进程 stdio

核心区别

  • Claude Code Bridge = 同一用户从 Web/Mobile 远程控制自己的终端 Agent
  • Qwen Code Channels = 不同用户从 DingTalk/Telegram 向服务端 Agent 提问

Channels 不是 Remote Control Bridge 的替代——它们解决完全不同的问题。


4. 差距分析

Qwen Code 缺失的能力

能力说明优先级
远程权限审批手机/浏览器审批终端 Agent 的权限请求P1
远程会话查看Web 端实时查看终端输出和工具调用P1
远程上下文补充Web 端向运行中的 Agent 发送补充信息P1
崩溃恢复指针进程死亡后通过 pointer 文件自动恢复P2
多会话服务模式单机运行多个独立会话P2
Worktree 感知恢复跨 git worktree 查找最近会话P2
文件附件远程上传Web 端上传截图供 Agent 使用P2

建议实现路径

阶段 1: 基础 Remote Control(对接已有基础设施)
├── 对接阿里云 WebSocket 服务(或自建 relay)
├── 实现 Outbound-only 连接模式
├── 权限请求远程转发
└── Bridge Pointer 崩溃恢复

阶段 2: Web 端 UI
├── 权限审批对话框
├── 实时输出查看
└── 消息发送/文件上传

阶段 3: 多会话 + 高级功能
├── Worktree 隔离的多会话
├── Capacity Wake 智能轮询
└── Trusted Device 安全增强

5. 源码文件索引

Claude Code Bridge 核心文件

文件行数职责
bridge/bridgeMain.ts2999Poll-dispatch 主循环、多会话编排
bridge/replBridge.ts2406REPL 桥接核心、Session Ingress
bridge/bridgeMessaging.ts461消息路由、BoundedUUIDSet 去重
bridge/bridgeApi.ts539HTTP API 封装(环境/会话端点)
bridge/bridgePointer.ts210崩溃恢复指针管理
bridge/codeSessionApi.ts168CCR v2 会话 API
bridge/bridgePermissionCallbacks.ts43权限回调协议
bridge/workSecret.ts127Work Secret 解码
bridge/trustedDevice.ts210Trusted Device 注册与管理
bridge/flushGate.ts71初始消息刷新门控
bridge/inboundAttachments.ts175Web 文件附件下载
bridge/sessionRunner.ts550子 CLI 进程管理
cli/transports/HybridTransport.ts282WS 读 + HTTP 写
cli/transports/SSETransport.ts711SSE 传输
cli/transports/WebSocketTransport.ts800WebSocket 传输

Qwen Code Channels 文件

文件职责
packages/channels/base/src/AcpBridge.tsACP 子进程 IPC
packages/channels/base/src/ChannelBase.tsChannel 基类
packages/channels/base/src/SessionRouter.ts会话路由
packages/channels/dingtalk/src/钉钉适配器
packages/channels/telegram/src/Telegram 适配器
packages/channels/weixin/src/微信适配器

6. 总结

Claude Code 的 Remote Control Bridge 解决了一个实际痛点:用户离开电脑后 Agent 无法继续工作。通过 Outbound-only WebSocket/SSE 连接到云端中继服务,实现了:

  1. 远程权限审批——手机上看到 Agent 请求执行 npm install,一键批准
  2. 实时进度查看——浏览器中看到 Agent 正在编辑哪些文件
  3. 上下文补充——远程发送截图或补充信息
  4. 崩溃恢复——Bridge Pointer + 4h TTL 自动恢复

Qwen Code 的 Channels 架构解决的是不同的问题(多平台接入),但可以复用 AcpBridge 的 ndjson IPC 机制作为 Remote Control 的传输层基础。

免责声明: 以上分析基于 2026 年 Q1 源码快照,可能已过时。