å€§å®¶å¥œïŒææ¯äžçŽåšç ç©¶ Claude Code æºç çææ¯ç±å¥œè ãä»å€©æ¯æä»¬æºç è§£æç³»åçæåäžç¯ïŒè®©æä»¬äžèµ·æ¥æåŒè¿çšæ§å¶åæ¡¥æ¥æºå¶çç¥ç§é¢çº±ã
çžä¿¡åŸå€äœ¿çšè¿ Claude Code çååŠéœäœéªè¿è¿çšæ§å¶åèœïŒæ 论æ¯åšææºäžæ§å¶æ¡é¢ç«¯ïŒè¿æ¯éè¿ Web ç颿äœïŒèåéœçŠ»äžåŒè¿å¥å€æèç²Ÿå·§çæ¡¥æ¥ç³»ç»ãä»å€©æå°±åžŠå€§å®¶æ·±å
¥å° src/bridge/ ç®åœïŒççè¿äžåæ¯åŠäœå®ç°çã
äžãäž€ç§æ¡¥æ¥æš¡åŒïŒç¯å¢æš¡åŒ vs æ ç¯å¢æš¡åŒ
Claude Code çè¿çšæ§å¶å®ç°äºäž€ç§æªç¶äžåçæ¶ææš¡åŒïŒè¿æ¯æåšæºç äžåç°çææææç讟计ä¹äžã
1.1 äŒ ç»ç¯å¢æš¡åŒïŒv1ïŒ
è¿æ¯èŸæ©çå®ç°æ¹åŒïŒæ žå¿æµçšåŠäžïŒ
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â ç¯å¢æš¡åŒæ¡¥æ¥æµçš â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
â 1. POST /v1/environments â æ³šåç¯å¢ â
â â â
â 2. POST /v1/sessions â å建äŒè¯ â
â â â
â 3. 蜮询 pollForWork() â è·åå·¥äœé¡¹ â
â â â
â 4. WebSocket è¿æ¥ â 建ç«ååéä¿¡ â
â â â
â 5. å¿è·³æ£æµ heartbeatWork() â 绎æè¿æ¥ â
â â â
â 6. å·¥äœå®æ stopWork() â æž
çèµæº â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
æ žå¿å
¥å£åš replBridge.ts ç initBridgeCore åœæ°ïŒ
export async function initBridgeCore(
params: BridgeCoreParams,
): Promise<BridgeCoreHandle | null> {
// 1. å建 Bridge API 客æ·ç«¯
const api = createBridgeApiClient({
baseUrl,
getAccessToken,
runnerVersion: MACRO.VERSION,
onAuth401,
getTrustedDeviceToken,
})
// 2. æ³šåæ¡¥æ¥ç¯å¢
const bridgeConfig: BridgeConfig = {
dir,
machineName,
branch,
gitRepoUrl,
maxSessions: 1,
spawnMode: 'single-session',
bridgeId: randomUUID(),
environmentId: randomUUID(),
// ...
}
const reg = await api.registerBridgeEnvironment(bridgeConfig)
environmentId = reg.environment_id
// 3. å建äŒè¯
const createdSessionId = await createSession({
environmentId,
title,
gitRepoUrl,
branch,
signal: AbortSignal.timeout(15_000),
})
// 4. å¯åšèœ®è¯¢åŸªç¯...
}
å ³é®ç¹æ§ïŒ
- éèŠå 泚åç¯å¢ïŒenvironmentïŒïŒåå建äŒè¯ïŒsessionïŒ
- éè¿èœ®è¯¢æºå¶è·åæå¡ç«¯æŽŸåçå·¥äœé¡¹
- äœ¿çš WebSocket è¿è¡å®æ¶ååéä¿¡
- æ¯æå¿è·³æ£æµåèªåšéè¿
1.2 æ ç¯å¢æš¡åŒïŒCCR v2ïŒ
è¿æ¯èŸæ°ç蜻é级å®ç°ïŒä»£ç åš remoteBridgeCore.ts äžïŒ
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â æ ç¯å¢æš¡åŒæ¡¥æ¥æµçš â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
â 1. POST /v1/code/sessions â çŽæ¥å建äŒè¯ â
â â â
â 2. POST /v1/code/sessions/{id}/bridge â è·å worker JWT â
â â â
â 3. SSE + CCRClient â 建ç«äŒ èŸå± â
â â â
â 4. JWT å·æ°è°åºŠåš â äž»åšå·æ°ä»€ç â
â â â
â 5. 401 èªåšæ¢å€ â 讀è¯å€±èŽ¥èªåšéè¿ â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
æ žå¿å®ç°æŽå ç®æŽïŒ
export async function initEnvLessBridgeCore(
params: EnvLessBridgeParams,
): Promise<ReplBridgeHandle | null> {
// 1. çŽæ¥å建äŒè¯ïŒæ éç¯å¢æ³šåïŒ
const createdSessionId = await createCodeSession(
baseUrl,
accessToken,
title,
cfg.http_timeout_ms,
tags
)
// 2. è·åæ¡¥æ¥åè¯ïŒworker JWTïŒ
const credentials = await fetchRemoteCredentials(
sessionId,
baseUrl,
accessToken,
cfg.http_timeout_ms,
)
// 3. æå»º v2 äŒ èŸå±ïŒSSE + CCRClientïŒ
transport = await createV2ReplTransport({
sessionUrl: buildCCRv2SdkUrl(credentials.api_base_url, sessionId),
ingressToken: credentials.worker_jwt,
sessionId,
epoch: credentials.worker_epoch,
getAuthToken: () => credentials.worker_jwt,
})
// 4. 泚ååè°å€ç
wireTransportCallbacks()
// 5. å¯åš JWT å·æ°è°åºŠåš
const refresh = createTokenRefreshScheduler({
refreshBufferMs: cfg.token_refresh_buffer_ms,
getAccessToken: async () => { /* ... */ },
onRefresh: (sid, oauthToken) => {
// è·åæ°åè¯å¹¶éå»ºäŒ èŸå±
const fresh = await fetchRemoteCredentials(sid, baseUrl, oauthToken, cfg.http_timeout_ms)
await rebuildTransport(fresh, 'proactive_refresh')
},
})
refresh.scheduleFromExpiresIn(sessionId, credentials.expires_in)
}
对æ¯äž€ç§æš¡åŒïŒ
| ç¹æ§ | ç¯å¢æš¡åŒïŒv1ïŒ | æ ç¯å¢æš¡åŒïŒv2ïŒ |
|---|---|---|
| æ¶æå±æ¬¡ | ç¯å¢å± + äŒè¯å± | çŽæ¥äŒè¯å± |
| 蜮询æºå¶ | éèŠèœ®è¯¢è·åå·¥äœ | çŽæ¥è¿æ¥ |
| è®€è¯æ¹åŒ | OAuth æ JWT | å¿ é¡» JWT |
| äŒ èŸåè®® | WebSocket | SSE + CCRClient |
| éçšåºæ¯ | 宿€è¿çšæš¡åŒ | REPL äº€äºæš¡åŒ |
äºãäŒ èŸå±è®Ÿè®¡ïŒååè®®èå
Claude Code çäŒ èŸå±è®Ÿè®¡é垞粟åŠïŒæ¯æäž€ç§åè®®çæ çŒåæ¢ïŒ
2.1 v1 äŒ èŸïŒSession-Ingress WebSocket
// å建 v1 äŒ èŸïŒWebSocketïŒ
const transport = createV1ReplTransport({
sessionIngressUrl,
authToken: v1OauthToken,
sessionId,
})
ç¹ç¹ïŒ
- äœ¿çšæ å WebSocket è¿æ¥
- æ¯æ OAuth 什ç讀è¯
- æ¶æ¯åå宿¶äŒ èŸ
2.2 v2 äŒ èŸïŒSSE + CCRClient
// å建 v2 äŒ èŸïŒSSE + CCRïŒ
const transport = createV2ReplTransport({
sessionUrl: buildCCRv2SdkUrl(apiBaseUrl, sessionId),
ingressToken: credentials.worker_jwt,
sessionId,
epoch: credentials.worker_epoch,
getAuthToken: () => credentials.worker_jwt,
})
ç¹ç¹ïŒ
- SSEïŒServer-Sent EventsïŒçšäºæå¡ç«¯æšé
- CCRClient çšäºå®¢æ·ç«¯åé
- å¿ é¡»äœ¿çš JWT 讀è¯ïŒå å« session_id 声æïŒ
- æ¯æ epoch çæ¬æ§å¶ïŒé²æ¢è¿æè¿æ¥
2.3 åè®®éæ©çç¥
æºç äžå®ç°äºæºèœçåè®®éæ©æºå¶ïŒ
// åš onWorkReceived äžå³å®äœ¿çšåªç§åè®®
const useCcrV2 =
serverUseCcrV2 || isEnvTruthy(process.env.CLAUDE_BRIDGE_USE_CCR_V2)
if (!useCcrV2) {
// v1 è·¯åŸïŒäœ¿çš OAuth 什ç
v1OauthToken = getOAuthToken()
updateSessionIngressAuthToken(v1OauthToken)
}
å³çæµçšïŒ
- æå¡ç«¯æ§å¶ïŒéè¿å·¥äœé¡¹äžç
use_code_sessionsåæ®µå³å® - åŒåè°è¯ïŒç¯å¢åé
CLAUDE_BRIDGE_USE_CCR_V2区å¶äœ¿çš v2 - ååå Œå®¹ïŒé»è®€äœ¿çš v1ïŒç¡®ä¿æ§çæ¬å®¢æ·ç«¯æ£åžžå·¥äœ
äžã容éäžæ¢å€æºå¶
è¿çšæ§å¶æå ³é®çå°±æ¯å¯é æ§ïŒClaude Code å®ç°äºå€å±æ¬¡çå®¹éæºå¶ã
3.1 ç¯å¢äž¢å€±æ¢å€çç¥
åœç¯å¢è¢«æå¡ç«¯åæ¶åïŒåŠé¿æ¶éŽç©ºé²ïŒïŒç³»ç»äŒå°è¯äž€ç§æ¢å€çç¥ïŒ
async function doReconnect(): Promise<boolean> {
// çç¥ 1ïŒåå°éè¿ïŒäŒå
ïŒ
if (await tryReconnectInPlace(requestedEnvId, currentSessionId)) {
// æåïŒäŒè¯ ID ä¿æäžåïŒURL äŸç¶ææ
return true
}
// çç¥ 2ïŒå建æ°äŒè¯ïŒé级ïŒ
await archiveSession(currentSessionId)
const newSessionId = await createSession({
environmentId,
title: currentTitle,
gitRepoUrl,
branch,
signal: AbortSignal.timeout(15_000),
})
currentSessionId = newSessionId
// éçœ®äŒ èŸç¶æ
lastTransportSequenceNum = 0
recentInboundUUIDs.clear()
return true
}
çç¥å¯¹æ¯ïŒ
| çç¥ | äŒç¹ | çŒºç¹ |
|---|---|---|
| åå°éè¿ | ä¿æäŒè¯ IDïŒURL äžå | ç¯å¢å¿ é¡»æªè¿æ |
| æ°å»ºäŒè¯ | å¯é æ§é« | URL æ¹åïŒå岿¶æ¯ééäŒ |
3.2 JWT èªåšå·æ°
v2 æš¡åŒå®ç°äºäž»åšç JWT å·æ°æºå¶ïŒ
const refresh = createTokenRefreshScheduler({
refreshBufferMs: 5 * 60 * 1000, // è¿æå 5 åéå·æ°
getAccessToken: async () => {
// æ æ¡ä»¶å·æ° OAuth 什ç
const stale = getAccessToken()
if (onAuth401) await onAuth401(stale ?? '')
return getAccessToken() ?? stale
},
onRefresh: async (sid, oauthToken) => {
// è·åæ°ç worker JWT
const fresh = await fetchRemoteCredentials(sid, baseUrl, oauthToken, timeoutMs)
// éå»ºäŒ èŸå±ïŒå
æ¬æ°ç epochïŒ
await rebuildTransport(fresh, 'proactive_refresh')
},
})
å ³é®è®Ÿè®¡ç¹ïŒ
- æåå·æ°ïŒåšä»€çè¿æå 5 åéäž»åšå·æ°ïŒé¿å äžæ
- epoch éå¢ïŒæ¯æ¬¡å·æ°éœäŒå¢å epochïŒé²æ¢äœ¿çšè¿æè¿æ¥
- äŒ èŸé建ïŒå·æ°åé建æŽäžªäŒ èŸå±ïŒç¡®ä¿äœ¿çšææ°åè¯
3.3 401 é误æ¢å€
åœéå°è®€è¯å€±èŽ¥æ¶ïŒç³»ç»äŒèªåšå°è¯æ¢å€ïŒ
async function recoverFromAuthFailure(): Promise<void> {
// 鲿¢å¹¶åæ¢å€
if (authRecoveryInFlight) return
authRecoveryInFlight = true
try {
// 1. å·æ° OAuth 什ç
const stale = getAccessToken()
if (onAuth401) await onAuth401(stale ?? '')
const oauthToken = getAccessToken() ?? stale
// 2. è·åæ°ç worker JWT
const fresh = await fetchRemoteCredentials(sessionId, baseUrl, oauthToken, timeoutMs)
// 3. éå»ºäŒ èŸå±
initialFlushDone = false // å
è®žéæ°å·æ°å岿¶æ¯
await rebuildTransport(fresh, 'auth_401_recovery')
} catch (err) {
// æ¢å€å€±èŽ¥ïŒæ 记䞺éè¯¯ç¶æ
onStateChange?.('failed', `JWT refresh failed: ${errorMessage(err)}`)
} finally {
authRecoveryInFlight = false
}
}
åãæ žå¿æ°æ®æµäžæ¶æ¯å€ç
4.1 æ¶æ¯å»éæºå¶
ç³»ç»å®ç°äºåå±å»éïŒé²æ¢é倿¶æ¯ïŒ
// å·²åéæ¶æ¯ç UUID çŒå²åºïŒçšäºè¿æ»€å声ïŒ
const recentPostedUUIDs = new BoundedUUIDSet(2000)
// å·²æ¥æ¶æ¶æ¯ç UUID çŒå²åºïŒçšäºé²æ¢éäŒ ïŒ
const recentInboundUUIDs = new BoundedUUIDSet(2000)
// åå§æ¶æ¯ UUIDïŒæä¹
åå»éïŒ
const initialMessageUUIDs = new Set<string>()
å»éæµçšïŒ
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â æ¶æ¯å»éæµçš â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
â åéæ¶æ¯ â æ·»å å° recentPostedUUIDs â
â â â
â æå¡åšåæŸ â æ£æ¥ recentPostedUUIDs â è¿æ»€ â
â â â
â æ¥æ¶æ¶æ¯ â æ£æ¥ recentInboundUUIDs â è¿æ»€éå€ â
â â â
â 蜬åå° REPL â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
4.2 æ¶æ¯å·æ°éšæ§
åšåå§å岿¶æ¯å·æ°æéŽïŒæ°æ¶æ¯äŒè¢«äžŽæ¶çŒåïŒ
// åå»ºå·æ°éšæ§
const flushGate = new FlushGate<Message>()
// åŒå§å·æ°åå²
flushGate.start()
// æ°æ¶æ¯è¿å
¥éå
if (flushGate.enqueue(...filtered)) {
// åšå·æ°å®æåïŒæ¶æ¯äŒè¢«çŒå
}
// å·æ°å®æåéæŸéå
function drainFlushGate(): void {
const msgs = flushGate.end()
// åéçŒåçæ¶æ¯
for (const msg of msgs) recentPostedUUIDs.add(msg.uuid)
const events = toSDKMessages(msgs).map(m => ({ ...m, session_id: sessionId }))
void transport.writeBatch(events)
}
äºãæ¶æè®Ÿè®¡äº®ç¹æ»ç»
5.1 åæš¡åŒæ¶æçäŒå¿
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â åæš¡åŒæ¶æäŒå¿ â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
â âââââââââââââââ âââââââââââââââ â
â â REPL æš¡åŒ â â 宿€è¿çšæš¡åŒ â â
â ââââââââââââââ†ââââââââââââââ†â
â â v2 æ ç¯å¢ â â v1 ç¯å¢æš¡åŒ â â
â â 蜻é级å¯åš â â æä¹
åç¯å¢ â â
â â å¿«éè¿æ¥ â â å€äŒè¯æ¯æ â â
â âââââââââââââââ âââââââââââââââ â
â â â â
â ââââââââââââ¬ââââââââââââââ â
â â â
â ç»äžç BridgeHandle API â
â â â
â äžå±ä»£ç æ éå
³å¿åºå±å·®åŒ â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
5.2 å ³é®è®Ÿè®¡æš¡åŒ
- çç¥æš¡åŒïŒéè¿é çœ®åšæéæ©äŒ èŸåè®®
- è§å¯è æš¡åŒïŒç¶æååéç¥æºå¶
- éšé¢æš¡åŒïŒç»äžç BridgeHandle API æœè±¡
- éè¯æš¡åŒïŒææ°éé¿éè¯æºå¶
5.3 å·¥çšå®è·µç»éª
æåšåææºç æ¶åç°çå 䞪工çšäº®ç¹ïŒ
- æžè¿åŒé级ïŒv2 倱莥æ¶èªåšéçº§å° v1
- ç¶ææºé©±åšïŒæç¡®çè¿æ¥ç¶æèœ¬æ¢ïŒready â connected â reconnecting â failedïŒ
- èµæºæž
çä¿éïŒéè¿
registerCleanupç¡®ä¿èµæºéæŸ - çæ§å®å€ïŒäž°å¯çæ¥å¿åçæ§ææ
å ãæ»ç»äžå±æ
Claude Code çè¿çšæ§å¶æ¡¥æ¥æºå¶æ¯äžäžªéåžžæççååžåŒç³»ç»å®ç°ïŒäž»èŠç¹ç¹ïŒ
- åæš¡åŒæ¯æïŒç¯å¢æš¡åŒåæ ç¯å¢æš¡åŒæé忢
- åè®®æŒè¿ïŒä» WebSocket å° SSE + CCR çå¹³æ»è¿æž¡
- 容é讟计ïŒå€å±æ¢å€æºå¶ç¡®ä¿é«å¯çšæ§
- å¯è§æµæ§ïŒå®åçæ¥å¿åçæ§äœç³»
è¿å°±æ¯æä»¬ Claude Code æºç è§£æç³»åçæåäžç¯äºïŒä»æ¶ææŠè§å°ç»ç«¯æž²æåŒæïŒåå°ä»å€©çè¿çšæ§å¶æºå¶ïŒæä»¬äžèµ·èµ°è¿äºæŽäžªä»£ç åºçæ žå¿æš¡åã
äºåšé®é¢ïŒ äœ åšäœ¿çš Claude Code è¿çšæ§å¶åèœæ¶éå°è¿ä»ä¹é®é¢ïŒäœ è§åŸè¿å¥æ¶æè¿æåªäºå¯ä»¥æ¹è¿çå°æ¹ïŒæ¬¢è¿åšè¯è®ºåºäº€æµïŒ
ååäžæïŒåŠæè¿ç¯æç« å¯¹äœ æåž®å©ïŒæ¬¢è¿ç¹èµå ³æ³šæ¯æäžäžïŒ