CC源码剖析 #03 · Bridge 模块:主进程与渲染进程的通信机制

基于 2026-03-31 公开的源码快照,~512,000 行代码


一、Bridge 在整个架构中的位置

Bridge 是 Claude Code 最复杂的模块,负责 IDE 扩展(VSCode/JetBrains)与 CLI 进程之间的双向通信

┌─────────────────────────────────────────────────────────────┐
│                    Claude.ai Web                           │
│                 (Remote Control UI)                        │
└──────────────────────────┬──────────────────────────────────┘
                           │ WebSocket / HTTPS
┌──────────────────────────▼──────────────────────────────────┐
│                     Bridge Module                           │
│  src/bridge/                                             │
│  ├── bridgeMain.ts      # 主循环:轮询 + 会话管理           │
│  ├── bridgeApi.ts       # REST API 客户端                   │
│  ├── replBridge.ts      # REPL ↔ Bridge 桥接核心            │
│  ├── replBridgeTransport.ts  # WebSocket 传输层             │
│  ├── bridgeMessaging.ts # 消息协议 + 类型守卫                │
│  ├── sessionRunner.ts   # 子进程 Spawn 管理                  │
│  └── jwtUtils.ts        # JWT 刷新 + 心跳                    │
└────┬────────────────────┬──────────────────────────────────┘
     │                    │
┌────▼────────┐     ┌─────▼────────┐
│ VSCode 扩展  │     │ JetBrains 扩展│
│ (TypeScript)│     │  (Kotlin)   │
└────┬────────┘     └─────┬────────┘
     │ IPC(UDS)          │ IPC(UDS)
┌────▼────────────────────▼────┐
│    CLI 主进程 (claude)       │
│  src/main.tsx               │
│  ├── launchRepl()           │
│  └── REPL.tsx               │
└─────────────────────────────┘

二、三种 Spawn Mode(会话隔离策略)

Mode行为适用场景
single-session每个工作请求独占 cwd,结束后销毁临时任务
worktree每个会话分配独立 git worktree持久服务器,多会话并行
same-dir多会话共享同一目录(可互相覆盖)轻量多会话

关键文件:src/bridge/types.ts:69

1
export type SpawnMode = 'single-session' | 'worktree' | 'same-dir'

三、bridgeMain.ts — 主循环与会话管理

文件:src/bridge/bridgeMain.ts,约 1,000 行

3.1 runBridgeLoop 主循环

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
export async function runBridgeLoop(
  config: BridgeConfig,
  environmentId: string,
  environmentSecret: string,
  api: BridgeApiClient,
  spawner: SessionSpawner,
  logger: BridgeLogger,
  signal: AbortSignal,
  backoffConfig: BackoffConfig = DEFAULT_BACKOFF,
): Promise<void> {
  const activeSessions = new Map<string, SessionHandle>()
  const sessionWorkIds = new Map<string, string>()
  const sessionIngressTokens = new Map<string, string>()  // JWT 心跳认证
  const capacityWake = createCapacityWake(loopSignal)      // 容量满时唤醒信号
  // ...
}

3.2 核心数据结构

1
2
3
4
5
6
7
interface SessionHandle {
  sessionId: string
  workId: string
  process: ChildProcess
  accessToken: string
  transport: ReplBridgeTransport
}

3.3 心跳机制

1
2
3
4
// heartbeatWorker() 每 1s 发送一次活性证明
// JWT 在 ~3h55m 后过期,通过 refreshToken 续期
const sessionIngressTokens = new Map<string, string>()
// token refresh scheduler 更新 accessToken 字段

3.4 退避重连配置

1
2
3
4
5
6
7
const DEFAULT_BACKOFF: BackoffConfig = {
  connInitialMs: 2_000,    // 初始 2s
  connCapMs: 120_000,      // 上限 2 分钟
  connGiveUpMs: 600_000,   // 放弃上限 10 分钟
  generalInitialMs: 500,
  generalCapMs: 30_000,
}

四、bridgeApi.ts — REST API 客户端

4.1 核心接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
export type BridgeApiClient = {
  registerBridgeEnvironment(config: BridgeConfig): Promise<{
    environment_id: string
    environment_secret: string
  }>
  pollForWork(
    environmentId: string,
    environmentSecret: string,
    signal?: AbortSignal,
    reclaimOlderThanMs?: number,
  ): Promise<WorkResponse | null>
  acknowledgeWork(
    environmentId: string,
    workId: string,
    sessionToken: string,
  ): Promise<void>
  stopWork(environmentId: string, workId: string, force: boolean): Promise<void>
}

4.2 WorkSecret 解密

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
export type WorkSecret = {
  version: number
  session_ingress_token: string
  api_base_url: string
  sources: Array<{
    type: string
    git_info?: { type: string; repo: string; ref?: string; token?: string }
  }>
  auth: Array<{ type: string; token: string }>
  claude_code_args?: Record<string, string>
  mcp_config?: unknown
  environment_variables?: Record<string, string>
  use_code_sessions?: boolean  // CCR v2 兼容层标志
}

五、replBridge.ts — REPL ↔ Bridge 桥接核心

文件:src/bridge/replBridge.ts,约 700 行

5.1 双向桥接架构

IDE Extension                    CLI (REPL)
    │                                │
    │──── init-session ────────────▶│
    │                                │
    │◀───── session_established ────│
    │                                │
    │──── user-message ──────────────▶│
    │◀───── tool_result ─────────────│
    │◀───── tool_use ────────────────│
    │                                │
    │──── control_request ◀─────────│ (服务器下发控制)
    │◀───── control_response ───────│

5.2 ReplBridgeHandle 接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
export type ReplBridgeHandle = {
  bridgeSessionId: string
  environmentId: string
  sessionIngressUrl: string
  writeMessages(messages: Message[]): void       // 写入对话消息
  writeSdkMessages(messages: SDKMessage[]): void // 写入 SDK 消息
  sendControlRequest(request: SDKControlRequest): void   // 发控制请求
  sendControlResponse(response: SDKControlResponse): void // 发控制响应
  sendControlCancelRequest(requestId: string): void     // 取消请求
  sendResult(): void                   // 发送结果
  teardown(): Promise<void>            // 清理
}

5.3 消息路由

writeMessages() 将 REPL 内部消息转换为 SDK 格式后写入 transport:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 消息筛选规则:只转发 user/assistant 和 local_command 系统事件
export function isEligibleBridgeMessage(m: Message): boolean {
  if ((m.type === 'user' || m.type === 'assistant') && m.isVirtual) {
    return false  // 虚拟消息(REPL 内部调用)不转发
  }
  return (
    m.type === 'user' ||
    m.type === 'assistant' ||
    (m.type === 'system' && m.subtype === 'local_command')
  )
}

5.4 BridgeCoreParams — 参数注入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
export type BridgeCoreParams = {
  dir: string
  machineName: string
  branch: string
  gitRepoUrl: string | null
  title: string
  baseUrl: string
  sessionIngressUrl: string
  workerType: string
  getAccessToken: () => string | undefined
  createSession: (opts: {...}) => Promise<string | null>
  archiveSession: (sessionId: string) => Promise<void>
  getCurrentTitle?: () => string
  toSDKMessages?: (messages: Message[]) => SDKMessage[]
}

注入原因:避免将 REPL 相关模块(commands.ts、React 树)拖入 Agent SDK bundle。


六、replBridgeTransport.ts — WebSocket 传输层

6.1 V1 vs V2 传输协议

1
2
3
4
5
// V1: 基础 WebSocket
createV1ReplTransport(ingressUrl, token, handlers)

// V2: 带 seq-num 的可靠消息协议(防重放/防丢失)
createV2ReplTransport(ingressUrl, token, handlers)

V2 通过序列号确保消息不重复、不丢失。

6.2 HybridTransport

1
2
3
4
// 混合传输:同时支持 WebSocket 和 HTTP 流式
class HybridTransport {
  // 优先 WebSocket,fallback HTTP
}

七、sessionRunner.ts — 子进程 Spawn 管理

7.1 SessionSpawner 接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
export type SessionSpawnOpts = {
  workId: string
  workSecret: WorkSecret
  dir: string
  branch?: string
  title?: string
  sessionTimeoutMs?: number
}

export type SessionSpawner = {
  spawn(opts: SessionSpawnOpts, dir: string): SessionHandle
  abort(sessionId: string): void
}

7.2 Spawn 参数处理

1
2
3
4
5
6
7
8
function spawnScriptArgs(): string[] {
  // npm 安装(node 运行 cli.js)需要传入脚本路径
  // 直接运行二进制则不需要
  if (isInBundledMode() || !process.argv[1]) {
    return []
  }
  return [process.argv[1]]
}

八、消息协议与类型守卫

文件:src/bridge/bridgeMessaging.ts

8.1 核心类型守卫

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 判定是否是合法 SDK 消息
export function isSDKMessage(value: unknown): value is SDKMessage {
  return value !== null && typeof value === 'object' && 'type' in value
}

// 控制响应(权限决策)
export function isSDKControlResponse(value: unknown): value is SDKControlResponse {
  return (
    value !== null &&
    typeof value === 'object' &&
    'type' in value &&
    value.type === 'control_response' &&
    'response' in value
  )
}

// 控制请求(服务器下发)
export function isSDKControlRequest(value: unknown): value is SDKControlRequest {
  return (
    value !== null &&
    typeof value === 'object' &&
    'type' in value &&
    value.type === 'control_request' &&
    'request_id' in value &&
    'request' in value
  )
}

8.2 Ingress 消息处理

1
2
3
4
5
6
7
8
export function handleIngressMessage(
  data: string,
  recentPostedUUIDs: BoundedUUIDSet,    // 已发送 UUID(去重回声)
  recentInboundUUIDs: BoundedUUIDSet,  // 已接收 UUID(防重放)
  onInboundMessage: ((msg: SDKMessage) => void) | undefined,
  onPermissionResponse?: ((response: SDKControlResponse) => void),
  onControlRequest?: ((request: SDKControlRequest) => void),
): void

九、JWT 认证与刷新

文件:src/bridge/jwtUtils.ts

9.1 Token 刷新调度器

1
2
3
4
5
6
7
// OAuth token ~3h55m 过期,需要刷新
const tokenRefreshScheduler = createTokenRefreshScheduler(
  sessionIngressTokens, // Map<sessionId, token>
  api,
)

// 每隔固定时间检查即将过期的 token,提前刷新

9.2 Trusted Device Token

1
2
getTrustedDeviceToken()  // macOS Keychain 读取
// 用于长期设备认证,避免每次重新登录

十、容量控制与唤醒

文件:src/bridge/capacityWake.ts

1
2
3
4
5
// 容量满时(activeSessions >= maxSessions),
// 新工作请求会进入等待队列而非立即创建会话
export const capacityWake = createCapacityWake(loopSignal)

// 当会话完成时,wake 唤醒等待中的 poll,继续接受新工作

十一、启动模式对比

模式来源通信方式
claude (本地)CLI 直接运行无 Bridge
claude remote-control后台守护进程WebSocket → bridgeMain
VSCode/JetBrains 扩展IDE 插件UDS IPC → bridgeMain → REPL
Agent SDK外部进程HTTP/WebSocket → replBridge

下一步

下一篇:04 - Coordinator:多阶段任务编排器,深入 coordinator/ 模块的多 Agent 协作机制。


Claude Code 源码研究系列 · 2026 · skyseraph

SkySeraph
SkySeraph
AI for All & All for AI
留言 Comments