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` | 桥接模块类型定义 |
|