🌍 Architecture Overview
Outbound (local → cloud)
Claude's messages, tool activities, and result events stream to CCR for real-time rendering on claude.ai.
Inbound (cloud → local)
User prompts, permission decisions, interrupt signals, and control messages flow back to the Claude process.
Auth Boundary
Bridge API calls use OAuth token. CCR worker endpoints validate a short-lived JWT encoding session_id and role=worker claims.
🔄 Bridge v1 vs v2
v1 — Environment-based
Registers via POST /v1/environments, polls for work, decodes WorkSecret (base64url JSON), supports perpetual mode and multi-session spawn. Transport: HybridTransport (WebSocket reads + HTTP POSTs).
v2 — Environment-less
No Environments API. POST /v1/code/sessions → session ID. POST /v1/code/sessions/{id}/bridge → worker JWT + epoch. Transport: SSETransport (reads) + CCRClient (writes). Gated by tengu_bridge_repl_v2 GrowthBook flag.
💡 WorkSecret Decode
When the Environments API delivers work, it attaches an opaque secret field that is a base64url-encoded JSON blob containing session_ingress_token (JWT), api_base_url, sources, and auth arrays.
🔌 Transport Layer
ReplBridgeTransport Interface
Unified interface: write(), writeBatch(), close(), isConnectedStatus(), setOnData(), connect(), getLastSequenceNum(), flush(). v2 adds reportState(), reportMetadata(), reportDelivery().
v1 HybridTransport
WebSocket for inbound, HTTP POST for outbound. v1 never uses SSE sequence numbers — server-side cursor handles replay.
v2 SSETransport + CCRClient
Asymmetric: reads via SSE, writes via CCRClient posting to /worker/events. ACK 'processed' immediately to prevent phantom prompt floods.
💡 FlushGate
Prevents history/live message interleaving. Historical messages are flushed as one HTTP batch on connect; live messages arriving during that window are queued and drained after flush completes.
🔒 Permission Bridge
Remote Control surfaces tool-use permission prompts through the control_request / control_response protocol. When Claude wants to run a potentially dangerous tool, the question travels through the bridge to claude.ai.
control_request
Bridge asks claude.ai 'can this tool run?'
control_response
claude.ai answers allow/deny, optionally with updated input.
Synthetic AssistantMessage
When a permission request comes from remote CCR, no local message exists. remotePermissionBridge.ts fabricates one with tool stubs for unknown MCP tools.
⚙️ Remote Control Command
The /remote-control slash command checks if a bridge is already connected, runs pre-flight checks (checkBridgePrerequisites), then sets replBridgeEnabled: true in AppState.
💡 Five Entitlement Gates
Runtime gate (GrowthBook flag + OAuth sub), OAuth token present, Organization policy (allow_remote_control), Token freshness (proactive refresh), Min version check. If any gate fails, onStateChange 'failed' is called.
📡 CCR Integration
SessionsWebSocket
Connects to wss://api.anthropic.com/v1/sessions/ws/{sessionId}/subscribe for real-time event stream.
SDKMessage Adapter
Translates SDK-format messages (assistant, stream_event, result, system) to REPL's internal Message type.
Standalone Bridge
claude remote-control server mode manages concurrent child Claude processes with configurable pool size (default 32) and 24h per-session timeout.
💡 Session ID Compatibility
Infrastructure uses cse_* IDs; frontend uses session_*. Both are the same UUID with different prefixes. sameSessionId() compares by UUID body.