File size: 8,008 Bytes
eeeb2b6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0f215fe
 
 
 
 
 
 
 
 
 
eeeb2b6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# 远程桥接 / Remote Control

## 概述

Bridge(桥接)模式将本地 CLI 连接到 Anthropic 的远程会话基础设施(CCR, Claude Code Remote),使用户可以通过 claude.ai/code 从浏览器控制本地终端。

---

## 两种传输模式

### 1. 基于环境 (env-based)

通过 Environments API 进行 poll/dispatch:

1. CLI 注册为一个 "环境"(environment)到 Anthropic 服务器
2. 服务器通过 `pollForWork` API 分发工作
3. 客户端领取(acknowledge)工作、执行、发送心跳、返回结果

**核心 API(`src/bridge/bridgeApi.ts`)**:

| 端点 | 方法 | 用途 |
|------|------|------|
| `POST /v1/environments/bridge` | registerBridgeEnvironment | 注册当前终端为可用的执行环境 |
| `GET .../work/poll` | pollForWork | 轮询待处理的工作 |
| `POST .../work/{id}/ack` | acknowledgeWork | 确认领取工作 |
| `POST .../work/{id}/heartbeat` | heartbeatWork | 发送心跳(延长租约) |
| `POST .../work/{id}/stop` | stopWork | 停止工作 |
| `DELETE .../environments/bridge/{id}` | deregisterEnvironment | 注销环境 |

**认证**:使用 OAuth token 或 Bridge Access Token。

### 2. 无环境 (env-less)

直接通过 OAuth → Worker JWT 交换来连接远程会话:

1. 通过 OAuth 获取访问令牌
2. 直接连接到远程会话 WebSocket(`/v1/sessions/ws/{sessionId}/subscribe`3. 使用 SDK 消息格式进行双向通信

**核心组件(`src/remote/`)**:

| 文件 | 路径 | 用途 |
|------|------|------|
| SessionsWebSocket.ts | `src/remote/SessionsWebSocket.ts` | WebSocket 客户端 |
| sdkMessageAdapter.ts | `src/remote/sdkMessageAdapter.ts` | SDK 消息转换适配器 |

---

## 核心组件

### bridgeApi.ts(`src/bridge/bridgeApi.ts`)

HTTP 客户端,封装了所有 Bridge API 调用:

- **OAuth 认证**:自动 401 重试 + token 刷新(通过 `onAuth401` 回调)
- **BridgeFatalError**:不可重试的错误(认证失败、权限不足、会话过期)
- **ID 验证**`validateBridgeId()` 防止路径遍历攻击
- **错误处理**:对 401/403/404/410/429 状态码分别处理

```typescript
export class BridgeFatalError extends Error {
  readonly status: number
  readonly errorType: string | undefined
}
```

### bridgeMain.ts(`src/bridge/bridgeMain.ts`)

主桥接逻辑,约 3000 行。负责:

- 环境注册与生命周期管理
- 工作(work)的 poll/dispatch 循环
- 会话(session)的创建、运行、恢复
- 多会话支持(`--spawn`, `--capacity`, `--create-session-in-dir`- 优雅关闭(SIGTERM → SIGKILL 宽限期)
- 退避策略(连接退避、通用退避、stopWork 退避)

```typescript
export type BackoffConfig = {
  connInitialMs: number      // 连接初始延迟
  connCapMs: number           // 连接最大延迟 (2min)
  connGiveUpMs: number        // 连接放弃时间 (10min)
  generalInitialMs: number    // 通用初始延迟
  generalCapMs: number        // 通用最大延迟 (30s)
  generalGiveUpMs: number     // 通用放弃时间 (10min)
  shutdownGraceMs?: number    // SIGTERM→SIGKILL 宽限期
}
```

### replBridge.ts(`src/bridge/replBridge.ts`)

REPL 集成桥接,约 2400 行。在 REPL(交互式终端)模式下将 CLI 连接到远程会话:

- 通过 `HybridTransport` 实现消息转发
- 支持 CCR v1/v2 协议(`createV1ReplTransport` / `createV2ReplTransport`- 消息入口(ingress)处理
- 控制请求/响应(`SDKControlRequest` / `SDKControlResponse`- 容量唤醒(capacity wake)信号

```typescript
export type ReplBridgeHandle = {
  bridgeSessionId: string
  environmentId: string
  sessionIngressUrl: string
  writeMessages(messages: Message[]): void
  writeSdkMessages(messages: SDKMessage[]): void
  sendControlRequest(request: SDKControlRequest): void
  sendControlResponse(response: SDKControlResponse): void
  sendControlCancelRequest(requestId: string): void
  sendResult(): void
  teardown(): Promise<void>
}
```

### SessionsWebSocket.ts(`src/remote/SessionsWebSocket.ts`)

WebSocket 客户端,用于直接连接远程会话:

**协议**1. 连接到 `wss://api.anthropic.com/v1/sessions/ws/{sessionId}/subscribe?organization_uuid=...`
2. 发送认证消息:`{ type: 'auth', credential: { type: 'oauth', token: '...' } }`
3. 接收 SDK 消息流

**重连机制**- `RECONNECT_DELAY_MS = 2000`:重连延迟 2 秒
- `MAX_RECONNECT_ATTEMPTS = 5`:最大重连次数
- `PING_INTERVAL_MS = 30000`:30 秒心跳间隔
- `PERMANENT_CLOSE_CODES = new Set([4003])`:4003(未授权)为永久关闭,不重连
- `MAX_SESSION_NOT_FOUND_RETRIES = 3`:4001(会话未找到)有限重试(压缩期间可能短暂出现)

```typescript
type SessionsWebSocketCallbacks = {
  onMessage: (message: SessionsMessage) => void
  onClose?: () => void
  onError?: (error: Error) => void
  onConnected?: () => void
  onReconnecting?: () => void
}
```

### sdkMessageAdapter.ts(`src/remote/sdkMessageAdapter.ts`)

SDK 消息格式转换器。将 CCR 发送的 SDK 格式消息(`SDKMessage`)转换为 CLI 内部的消息类型(`Message`):

- `convertAssistantMessage()`: `SDKAssistantMessage``AssistantMessage`
- `convertStreamEvent()`: `SDKPartialAssistantMessage``StreamEvent`
- 处理多种消息类型:assistant、system、compact_boundary、status、tool_progress、result 等

### remotePermissionBridge.ts

在远程会话中处理权限请求桥接(位于 `src/hooks/useSSHSession.ts``useRemoteSession.ts``useDirectConnect.ts`),将远程权限提示通过 WebSocket 转发给用户。

### AskUserQuestion 远程转发

`AskUserQuestion` 走独立问答通道(详见 `docs/architecture/safety-and-permissions.md` 附录),桥接连接时会把问题作为 `can_use_tool` control_request 转发给远程用户(claude.ai),与本地 overlay 竞速应答:

- 远程 `allow` + `updatedInput.answers` → 按题映射回填;通用 `allow` 降级为每题首个选项。
- 远程 `deny` → 拒绝该问题。
- 任一端先应答即 `cancelRequest` 另一端,避免残留 prompt。

实现位于 `src/screens/REPL.tsx` 对 `questionService` 事件(`asked` / `replied` / `rejected`)的订阅。

---

## 认证机制

| 机制 | 说明 |
|------|------|
| **OAuth Token** | 通过 OAuth 2.0 流程获取的访问令牌 |
| **Bridge Access Token** | 桥接模式专用的访问令牌,通过 `workSecret.ts` 中的 `decodeWorkSecret()` 解码 |
| **Trusted Device Token** | `X-Trusted-Device-Token` 头部,用于权限提升 |
| **Token 刷新** | `handleOAuth401Error` 在 401 时自动刷新 |

---

## WebSocket 协议(CCR v1/v2)

### 认证流程

```
Client → Server: { type: "auth", credential: { type: "oauth", token: "..." } }
Server → Client: { type: "auth_ok" }  或  { type: "auth_error" }
```

### 消息格式

- **CCR v1**:通过 `replBridgeTransport.ts` 的 `createV1ReplTransport` 处理
- **CCR v2**:通过 `createV2ReplTransport` 处理,使用 `buildCCRv2SdkUrl()` 构建 URL

### 心跳保活

- 标准 WebSocket ping:30 秒间隔
- 会话活动信号(`sendSessionActivitySignal()`)在压缩等长时间操作期间发送,防止 WebSocket 因 idle 超时被断开

---

## 其他组件

| 文件 | 路径 | 用途 |
|------|------|------|
| bridgeConfig.ts | `src/bridge/bridgeConfig.ts` | 桥接配置管理 |
| bridgeMessaging.ts | `src/bridge/bridgeMessaging.ts` | 桥接消息处理逻辑 |
| capacityWake.ts | `src/bridge/capacityWake.ts` | 容量唤醒信号 |
| codeSessionApi.ts | `src/bridge/codeSessionApi.ts` | 代码会话 API |
| trustedDevice.ts | `src/bridge/trustedDevice.ts` | 受信任设备管理 |
| workSecret.ts | `src/bridge/workSecret.ts` | Work Secret 编解码 |
| sessionIdCompat.ts | `src/bridge/sessionIdCompat.ts` | 会话 ID 兼容性转换 |
| pollConfig.ts | `src/bridge/pollConfig.ts` | 轮询间隔配置 |
| types.ts | `src/bridge/types.ts` | 桥接模块类型定义 |