Bridge 与 Remote
Claude Code 如何将本地 CLI 会话转变为双向云连接环境 - 从两阶段环境 API 到无环境 CCR v2 路径、SSE/WebSocket 混合、权限桥接和 claude remote-control command.
Contents
架构概述
远程控制将本地 Claude Code REPL(或无头桥接服务器)连接到 claude.ai Web 前端。 这种联系有两个不同的方面:
- 出站(本地 → 云): Claude 的消息、工具活动和结果事件会流出到 CCR(Cloud Code Runner)会话,以便 claude.ai 可以实时渲染它们。
- 入站(云 → 本地): 在 claude.ai 上输入的用户提示、权限批准/拒绝决策、中断信号和控制消息流回正在运行的 Claude 进程。
身份验证边界至关重要:所有桥 API 调用都使用用户的 claude.ai OAuth 令牌。 CCR 工作端点还验证一个短暂的 JWT,该 JWT 编码了 session_id and role=worker 声明 — OAuth 令牌将被拒绝。
Bridge v1 与 v2 — 基于环境与无环境
桥接系统的根本区别是会话是否通过 环境API (v1) 或使用新的工作 JWT(v2,“env-less”)直接连接到 CCR。
v1 — 基于环境(initBridgeCore)
- 通过注册环境
POST /v1/environments - 工作投票通过
GET /v1/environments/{id}/work - 解码一个 WorkSecret (base64url JSON) 获取会话 JWT
- 确认、心跳和停止工作项目
- 支持永久模式(通过崩溃恢复
bridge-pointer.json) - 支持多会话生成(
--spawn,工作树模式) - Transport:
HybridTransport(WebSocket 读取 + HTTP POST) - 使用者:REPL、守护进程、
claude remote-control服务器模式
v2 — 无环境 (initEnvLessBridgeCore)
- 无环境 API — 跳过注册/轮询/确认/心跳
POST /v1/code/sessions→ 会话 IDPOST /v1/code/sessions/{id}/bridge→ 工人 JWT + 纪元- Each
/bridge打电话是工人登记 - Transport:
SSETransport(读)+CCRClient(writes) - 门控由
tengu_bridge_repl_v2成长书旗帜 - REPL-only — 守护进程/打印保留在 v1 上
- 令牌刷新调用
/bridge再次(新 JWT + 新纪元)
WorkSecret 解码 — v1 握手令牌内的内容
当环境 API 交付工作时,它会附加一个不透明的 secret 字段是一个 base64url 编码的 JSON blob。 decodeWorkSecret() in bridge/workSecret.ts 解压它:
export function decodeWorkSecret(secret: string): WorkSecret {
const json = Buffer.from(secret, 'base64url').toString('utf-8')
const parsed: unknown = jsonParse(json)
// validates version === 1, session_ingress_token, api_base_url
return parsed as WorkSecret
}
type WorkSecret = {
version: 1
session_ingress_token: string // JWT for CCR worker endpoints
api_base_url: string
sources: Array<{ type: string; git_info?: ... }>
auth: Array<{ type: string; token: string }>
use_code_sessions?: boolean // server-driven CCR v2 selector
}
The session_ingress_token 是授权工作层操作的短暂 JWT。 OAuth 令牌不能在其位置使用 - CCR 验证 JWT session_id and role=worker 直接索赔。
门逻辑输入 initReplBridge
v1/v2 分支已解决 bridge/initReplBridge.ts 所有身份验证检查通过后:
// tengu_bridge_repl_v2 enables env-less (v2) path for the REPL.
// perpetual=true falls back to v1 — bridge-pointer not yet wired to v2.
if (isEnvLessBridgeEnabled() && !perpetual) {
const versionError = await checkEnvLessBridgeMinVersion()
if (versionError) {
onStateChange?.('failed', 'run `claude update` to upgrade')
return null
}
const { initEnvLessBridgeCore } = await import('./remoteBridgeCore.js')
return initEnvLessBridgeCore({ baseUrl, orgUUID, title, ... })
}
// v1 path: env-based register/poll/ack/heartbeat
return initBridgeCore({ dir, machineName, branch, gitRepoUrl, ... })
有独立版本楼层: tengu_bridge_min_version 门 v1; tengu_bridge_repl_v2_config.min_version 盖茨 v2. 两者都是 GrowthBook 动态配置 - 操作可以通过将下限降低到 0.0.0.
会话 ID 兼容 — cse_* vs session_*
CCR v2 兼容层造成分裂:基础设施端点分发 cse_* ID,而 claude.ai 前端路由 session_*。 两者都是相同的 UUID,但具有不同的前缀。
// bridge/sessionIdCompat.ts
export function toCompatSessionId(id: string): string {
if (!id.startsWith('cse_')) return id
if (_isCseShimEnabled && !_isCseShimEnabled()) return id
return 'session_' + id.slice('cse_'.length)
}
export function toInfraSessionId(id: string): string {
if (!id.startsWith('session_')) return id
return 'cse_' + id.slice('session_'.length)
}
// sameSessionId() ignores prefix so the poll loop doesn't
// reject its own session as "foreign" when the compat gate is on
export function sameSessionId(a: string, b: string): boolean {
const aBody = a.slice(a.lastIndexOf('_') + 1)
const bBody = b.slice(b.lastIndexOf('_') + 1)
return aBody.length >= 4 && aBody === bBody
}
The isCseShimEnabled 终止开关通过注入 setCseShimGate() 以避免将 GrowthBook 导入 Agent SDK 捆绑包。
传输层 - WebSocket、SSE 和混合
传输抽象存在于 bridge/replBridgeTransport.ts。 它定义了一个单一的 ReplBridgeTransport 接口和两个工厂函数(一个用于 v1,一个用于 v2),因此桥接代码的其余部分永远不知道下面是哪个协议。
ReplBridgeTransport接口
export type ReplBridgeTransport = {
write(message: StdoutMessage): Promise<void>
writeBatch(messages: StdoutMessage[]): Promise<void>
close(): void
isConnectedStatus(): boolean
getStateLabel(): string
setOnData(cb: (data: string) => void): void
setOnClose(cb: (closeCode?: number) => void): void
setOnConnect(cb: () => void): void
connect(): void
getLastSequenceNum(): number // v1 always returns 0
readonly droppedBatchCount: number
reportState(state: SessionState): void // v2 only; v1 no-op
reportMetadata(m: Record<string, unknown>): void // v2 only
reportDelivery(id: string, s: 'processing'|'processed'): void
flush(): Promise<void> // v2 drains queue; v1 resolves immediately
}
v1 运输 — HybridTransport adapter
createV1ReplTransport() 是一个薄的传递,包裹 HybridTransport。 HybridTransport 为入站消息打开到 Session-Ingress 的 WebSocket,并为出站消息使用 HTTP POST。 v1 从不使用 SSE 序列号——服务器端游标处理重播。
export function createV1ReplTransport(
hybrid: HybridTransport,
): ReplBridgeTransport {
return {
write: msg => hybrid.write(msg),
writeBatch: msgs => hybrid.writeBatch(msgs),
close: () => hybrid.close(),
isConnectedStatus: () => hybrid.isConnectedStatus(),
getLastSequenceNum: () => 0, // WS replay != SSE seq nums
reportState: () => {}, // no-op
reportMetadata: () => {},
reportDelivery: () => {},
flush: () => Promise.resolve(), // POSTs are awaited per-write
// ... other pass-throughs
}
}
v2 传输 — SSETransport + CCRClient
v2 传输是不对称的: reads 过来SSE(服务器发送事件); writes 经过 CCRClient 哪个帖子到 /worker/events via SerialBatchEventUploader。 这种分割是有意为之的——入站 SSE 流可以暂停,而 CCRClient 的心跳和写入路径保持活动状态。
export async function createV2ReplTransport(opts: {
sessionUrl: string
ingressToken: string
sessionId: string
initialSequenceNum?: number // resume from this SSE seq on reconnect
epoch?: number // skip registerWorker if provided by /bridge
getAuthToken?: () => string | undefined // multi-session safe
outboundOnly?: boolean // skip SSE read (mirror mode)
}): Promise<ReplBridgeTransport> {
// registerWorker returns worker_epoch — required for CCRClient
const epoch = opts.epoch ?? (await registerWorker(sessionUrl, ingressToken))
const sse = new SSETransport(sseUrl, {}, sessionId, ...)
const ccr = new CCRClient(sse, new URL(sessionUrl), {
getAuthHeaders,
onEpochMismatch: () => {
// 409 from server: our epoch was superseded by another worker
ccr.close(); sse.close()
onCloseCb?.(4090) // poll-loop recovery code
throw new Error('epoch superseded')
},
})
// ACK 'processed' immediately alongside 'received' to prevent
// phantom prompt floods on daemon restart (CC-1263)
sse.setOnEvent(event => {
ccr.reportDelivery(event.event_id, 'received')
ccr.reportDelivery(event.event_id, 'processed')
})
return { write: msg => ccr.writeEvent(msg), ... }
}
FlushGate — 防止历史交错 + 实时消息
当桥接会话启动时,它会 POST 刷新历史对话。 在该刷新期间到达的任何新消息都将与服务器上的历史记录交错。 FlushGate 将它们排队直到刷新完成:
class FlushGate<T> {
start(): void // mark flush in-progress, enqueue() starts queuing
end(): T[] // flush done; return queued items for draining
enqueue(...items: T[]): boolean // true if active (queued), false if pass-through
drop(): number // discard queue (transport closed permanently)
deactivate(): void // transport swapped — new one will drain
}
deactivate() 当传输被替换时被调用(例如,环境丢失后重新连接)——项目被保留用于新的传输 end() call.
getLastSequenceNum() 返回上证所高水位线。 当交换传输(纪元不匹配、401、SSE 丢弃)时,新的 SSETransport 是用创建的 initialSequenceNum 所以它发送 from_sequence_num 并且服务器在不重播完整历史记录的情况下恢复。 v1 始终返回 0,因为 WS 重播是基于游标的服务器端。
许可桥
远程控制通过界面显示工具使用权限提示 控制请求/控制响应 协议。 当 Claude 想要运行潜在危险的工具时,REPL 通常会询问本地用户。 在远程控制模式下,这个问题会通过网桥传送到 claude.ai。
控制消息类型
control_request— Bridge 问 claude.ai“这个工具可以运行吗?”control_response— claude.ai 回答允许/拒绝,可选择更新输入control_cancel_request— 服务器取消待处理的提示(例如,会话结束)
权限响应类型和类型保护
// bridge/bridgePermissionCallbacks.ts
type BridgePermissionResponse = {
behavior: 'allow' | 'deny'
updatedInput?: Record<string, unknown>
updatedPermissions?: PermissionUpdate[]
message?: string
}
function isBridgePermissionResponse(value: unknown): value is BridgePermissionResponse {
if (!value || typeof value !== 'object') return false
return (
'behavior' in value &&
(value.behavior === 'allow' || value.behavior === 'deny')
)
}
RemoteSessionManager — 权限请求/响应流程
remote/RemoteSessionManager.ts 协调本地 CLI 正在查看的 CCR 托管会话的客户端。 它通过 WebSocket 接收来自 CCR 的权限请求并保留它们直到用户响应:
class RemoteSessionManager {
private pendingPermissionRequests = new Map<string, SDKControlPermissionRequest>()
private handleControlRequest(req: SDKControlRequest): void {
const { request_id, request: inner } = req
if (inner.subtype === 'can_use_tool') {
this.pendingPermissionRequests.set(request_id, inner)
this.callbacks.onPermissionRequest(inner, request_id)
} else {
// Unsupported subtype: send error response immediately so server doesn't hang
this.websocket?.sendControlResponse({ type: 'control_response', response: {
subtype: 'error', request_id, error: 'Unsupported subtype'
}})
}
}
respondToPermissionRequest(requestId: string, result: RemotePermissionResponse): void {
this.pendingPermissionRequests.delete(requestId)
this.websocket?.sendControlResponse({
type: 'control_response',
response: {
subtype: 'success', request_id: requestId,
response: {
behavior: result.behavior,
...(result.behavior === 'allow'
? { updatedInput: result.updatedInput }
: { message: result.message }),
},
},
})
}
}
用于远程权限提示的合成 AssistantMessage
REPL 的权限 UI 需要真实的 AssistantMessage 与工具使用块。 当权限请求来自远程 CCR 容器时,本地不存在此类消息。 remote/remotePermissionBridge.ts 制作一个:
export function createSyntheticAssistantMessage(
request: SDKControlPermissionRequest,
requestId: string,
): AssistantMessage {
return {
type: 'assistant',
uuid: randomUUID(),
message: {
id: `remote-${requestId}`,
type: 'message',
role: 'assistant',
content: [{
type: 'tool_use',
id: request.tool_use_id,
name: request.tool_name,
input: request.input,
}],
// zero-usage stub fields ...
} as AssistantMessage['message'],
requestId: undefined,
timestamp: new Date().toISOString(),
}
}
// For tools not known locally (e.g. MCP tools on the remote container):
export function createToolStub(toolName: string): Tool {
return {
name: toolName,
isEnabled: () => true,
needsPermissions: () => true,
// ... renders first 3 input key:value pairs for display
call: async () => ({ data: '' }),
} as unknown as Tool
}
claude remote-control 服务器模式,权限请求来自 child 克劳德进程被拦截 sessionRunner.ts,通过转发到服务器 api.sendPermissionResponseEvent(),并将响应写回子级的标准输入。
远程控制命令 — /remote-control
The /remote-control 斜杠命令位于 commands/bridge/bridge.tsx。 它是在 REPL 的 Ink 终端 UI 内呈现的 React 组件。
它的作用
- 检查网桥是否已连接 — 如果是,则显示带有会话 URL 和 QR 代码选项的断开连接对话框。
- 如果未连接,则运行飞行前检查(
checkBridgePrerequisites)然后设置replBridgeEnabled: trueinAppState. useReplBridgeinREPL.tsxwatchesreplBridgeEnabled并打电话initReplBridge().- The
name争论 (/remote-control my-session) 设置明确的会话标题。
BridgeToggle组件逻辑(已编译React)
// commands/bridge/bridge.tsx (compiled)
function BridgeToggle({ onDone, name }) {
const replBridgeConnected = useAppState(s => s.replBridgeConnected)
const replBridgeEnabled = useAppState(s => s.replBridgeEnabled)
const [showDisconnectDialog, setShow] = useState(false)
useEffect(() => {
if ((replBridgeConnected || replBridgeEnabled) && !replBridgeOutboundOnly) {
setShow(true) // already connected → show dialog
return
}
(async () => {
const error = await checkBridgePrerequisites()
if (error) { onDone(error, { display: 'system' }); return }
setAppState(prev => ({
...prev,
replBridgeEnabled: true,
replBridgeExplicit: true,
replBridgeOutboundOnly: false,
replBridgeInitialName: name,
}))
onDone('Remote Control connecting…', { display: 'system' })
})()
}, []) // fires once on mount
}
权利门——谁可以使用远程控制
之前五项检查必须全部通过 initReplBridge proceeds:
- 运行时门:
isBridgeEnabledBlocking()— 需要tengu_ccr_bridgeGrowthBook 标志和 claude.ai OAuth 订阅(无 Bedrock/Vertex/API 密钥身份验证)。 - OAuth 存在令牌:
getBridgeAccessToken()必须返回一个值。 - 组织方针:
isPolicyAllowed('allow_remote_control')— 企业管理员可以为所有组织成员禁用 RC。 - 代币新鲜度:主动刷新 + 如果过期且不可刷新则跳过(避免保证 401 循环)。
- 最低版本:
checkBridgeMinVersion()对于 v1,checkEnvLessBridgeMinVersion()对于 v2 — 操作员可以强制整个机队进行升级。
如果任何一扇门出现故障, onStateChange?.('failed', reason) 被调用并且函数返回 null.
CCR 镜像模式(outboundOnly)
When isCcrMirrorEnabled() 是真的(环境变量 CLAUDE_CODE_CCR_MIRROR 或 GrowthBook 标志),每个本地会话都会启动一个仅出站的桥。 SSE 读取流被跳过——桥仅将事件流式传输到 claude.ai,而不接受入站提示。 该会话以只读视图的形式显示在 claude.ai 会话列表中。
generateSessionTitle)覆盖完整的对话文本。 明确的标题来自 /remote-control <name> or /rename 永远不会被自动覆盖。
CCR 集成 — 云代码运行器
CCR(云代码运行器)是服务器端执行环境,用于处理从 claude.ai 请求的会话,而无需本地 CLI 存在。 这 local 桥连接到 CCR 的会话入口层,以呈现输出并处理最初远程创建的会话的权限。
SessionsWebSocket — 订阅 CCR 会话
remote/SessionsWebSocket.ts 连接到 wss://api.anthropic.com/v1/sessions/ws/{sessionId}/subscribe 接收活动 CCR 会话的实时事件流。
连接和重新连接逻辑
const RECONNECT_DELAY_MS = 2000
const MAX_RECONNECT_ATTEMPTS = 5
const MAX_SESSION_NOT_FOUND_RETRIES = 3 // 4001 can be transient during compaction
const PERMANENT_CLOSE_CODES = new Set([
4003, // unauthorized — stop immediately
])
private handleClose(closeCode: number): void {
if (PERMANENT_CLOSE_CODES.has(closeCode)) {
this.callbacks.onClose?.()
return
}
if (closeCode === 4001) {
// session not found — retry up to 3 times with linear backoff
this.sessionNotFoundRetries++
if (this.sessionNotFoundRetries > MAX_SESSION_NOT_FOUND_RETRIES) {
this.callbacks.onClose?.()
return
}
this.scheduleReconnect(RECONNECT_DELAY_MS * this.sessionNotFoundRetries, ...)
return
}
if (previousState === 'connected' && this.reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
this.reconnectAttempts++
this.scheduleReconnect(RECONNECT_DELAY_MS, ...)
}
}
Bun 的本机 WebSocket 通过标头传递身份验证; 节点使用 ws 具有相同身份验证标头的包(订阅端点不需要连接后身份验证消息)。
SDK消息适配器
CCR 发送 SDK 格式消息(assistant, stream_event, result, system, tool_progress, ETC。)。 remote/sdkMessageAdapter.ts 将它们转换为 REPL 的内部 Message 用于本地渲染的类型。
消息转换表
function convertSDKMessage(msg: SDKMessage, opts?: ConvertOptions): ConvertedMessage {
switch (msg.type) {
case 'assistant':
return { type: 'message', message: convertAssistantMessage(msg) }
case 'stream_event':
return { type: 'stream_event', event: convertStreamEvent(msg) }
case 'result':
// Only show errors — success results are noise in multi-turn
return msg.subtype !== 'success'
? { type: 'message', message: convertResultMessage(msg) }
: { type: 'ignored' }
case 'system':
if (msg.subtype === 'init') return { type: 'message', message: convertInitMessage(msg) }
if (msg.subtype === 'status') return { ... } // 'compacting' → banner
if (msg.subtype === 'compact_boundary') return { ... } // marks compaction point
return { type: 'ignored' }
case 'tool_progress': return { type: 'message', message: convertToolProgressMessage(msg) }
case 'user': return { type: 'ignored' } // already added locally by REPL
case 'auth_status': return { type: 'ignored' }
default: return { type: 'ignored' } // forward-compat: unknown types silently dropped
}
}
convertUserTextMessages: true 仅在重播历史事件时设置。
独立桥 — claude remote-control 服务器模式
bridge/bridgeMain.ts 实施 runBridgeLoop() 使用的函数 claude remote-control 作为持久服务器。 与 REPL 桥(一个会话,内联)不同,独立桥管理一组并发子 Claude 进程。
关键概念
- SpawnMode:
single-session(一个会话,桥退出),worktree(每个会话都有一个独立的 git 工作树),same-dir(会话共享 cwd — 可以互相踩踏)。 - maxSessions:可配置的池大小(默认32); 网桥在达到容量时暂停轮询并使用
capacityWake会话完成后立即恢复。 - 令牌刷新:v1 会话通过以下方式接收更新的 OAuth 令牌
handle.updateAccessToken(); v2 会话调用reconnectSession使用新的 JWT 触发服务器重新调度(OAuth 令牌不能在 CCR 工作端点中使用)。
轮询循环退避和重新连接策略
const DEFAULT_BACKOFF: BackoffConfig = {
connInitialMs: 2_000,
connCapMs: 120_000, // 2 min
connGiveUpMs: 600_000, // 10 min
generalInitialMs: 500,
generalCapMs: 30_000,
generalGiveUpMs:600_000,
}
// Sleep detection: if a poll tick is delayed by >2× the cap,
// the machine probably slept — reset error budget and reconnect immediately
function pollSleepDetectionThresholdMs(b: BackoffConfig): number {
return b.connCapMs * 2 // 240_000ms — above max backoff cap
}
连接错误和一般轮询错误具有独立的退避预算。 连接错误(注册/WebSocket 失败)在 10 分钟后放弃。 一般错误(工作轮询中的 HTTP 500)也会在 10 分钟后放弃——服务器是会话活跃度的权威。
子进程生成和会话跟踪
// bridgeMain.ts — activeSessions map tracks all running sessions
const activeSessions = new Map<string, SessionHandle>()
const sessionStartTimes = new Map<string, number>()
const sessionIngressTokens = new Map<string, string>()
const sessionTimers = new Map<string, ReturnType<typeof setTimeout>>()
// Per-session timeout watchdog (default 24h)
const DEFAULT_SESSION_TIMEOUT_MS = 24 * 60 * 60 * 1000
// SessionHandle exposes kill()/forceKill(), writeStdin(), activities ring buffer
type SessionHandle = {
sessionId: string
done: Promise<SessionDoneStatus>
kill(): void
forceKill(): void
activities: SessionActivity[] // ring buffer (last 10)
currentActivity: SessionActivity | null
accessToken: string
lastStderr: string[] // ring buffer (last 10 lines)
writeStdin(data: string): void
updateAccessToken(token: string): void
}
心跳和 JWT 过期恢复
活动工作项按照 GrowthBook 配置的时间间隔进行检测。 心跳使用会话入口 JWT(不是 OAuth) SessionIngressAuth — 轻量级的无数据库 JWT 验证。 在 401/403(JWT 过期)上,网桥调用 reconnectSession 重新排队工作,以便下一次轮询提供新的凭据:
async function heartbeatActiveWorkItems() {
for (const [sessionId] of activeSessions) {
const ingressToken = sessionIngressTokens.get(sessionId)
try {
await api.heartbeatWork(environmentId, workId, ingressToken)
} catch (err) {
if (err.status === 401 || err.status === 403) {
// JWT expired — re-dispatch so next poll delivers fresh token
await api.reconnectSession(environmentId, sessionId)
}
}
}
}
主动令牌刷新调度程序也会在到期前 5 分钟触发。 v1 会话直接接收新的 OAuth 令牌; v2 会话经过 reconnectSession 因为 CCR 工作端点拒绝 OAuth 令牌。
BridgeWorkerType
每个环境注册都包含一个 worker_type 字符串发送为 metadata.worker_type。 Web UI 使用它来过滤其会话选择器中的会话:
"claude_code"— 标准 REPL 会话"claude_code_assistant"— 助手模式(KAIROS 功能标志)"cowork"— Desktop Cowork(由 claude.ai 桌面应用程序发送,不是此代码库)
要点
- 两种桥接架构共存: v1(基于 env 的 poll/ack/heartbeat 循环)和 v2(“无环境” — 直接 OAuth → 工作 JWT 通过
POST /bridge)。 成长书标志tengu_bridge_repl_v2控制 REPL 采用的路径。 - v2 中的传输是不对称的: 入站使用 SSE(带有用于无缝重新连接的序列号),出站使用
CCRClient发布到/worker/events。 v1 使用 WebSocket 进行双向传输HybridTransport. - The
FlushGate防止历史/实时消息交错: 历史消息在连接时作为一批 HTTP 刷新; 在该窗口期间到达的任何实时消息都会在刷新完成后排队并排出。 - 权限流在同一传输上是双向的:
control_request从 Claude → 服务器 → claude.ai 出发;control_response(允许/拒绝)返回。 远程工具存根是在本地合成客户端不知道的工具的。 - 会话 ID 有两种外观: 基础设施层使用
cse_*,compat/面向客户端的 API 使用session_*.sameSessionId()通过 UUID 主体进行比较,因此轮询循环不会拒绝其自己的会话。toCompatSessionId()通过 GrowthBook 进行终止切换。 - 工作端点的身份验证需要 JWT,而不是 OAuth: CCR 验证 JWT
session_id索赔和role=worker。 v2 会话的令牌刷新会触发服务器重新调度(reconnectSession)而不是将新的 OAuth 令牌推送到正在运行的进程。 claude remote-control服务器模式 支持多会话并发执行single-session,worktree, 和same-dir生成模式、可配置的池大小和 24 小时每个会话超时看门狗。
知识检查
1. 在v2(env-less)桥接路径中,什么是 POST /v1/code/sessions/{id}/bridge 返回取代整个环境 API 轮询工作流程?
2.为什么v2传输立即ACK processed (不仅 received) 收到 SSE 事件后?
3.A cse_abc123 会话 ID 来自工作轮询。 哪个函数将其转换为与 client-facing 会话 API(/v1/sessions/{id}/archive)?
4. 目的是什么 FlushGate.deactivate() (相对于 drop())?
5. 在 claude remote-control 服务器模式(独立桥),当 v2 会话的 JWT 在心跳期间过期时会发生什么?
6. createToolStub(toolName) in remote/remotePermissionBridge.ts 存在是因为: