source dump of claude code
at main 262 lines 10 kB view raw
1/** Default per-session timeout (24 hours). */ 2export const DEFAULT_SESSION_TIMEOUT_MS = 24 * 60 * 60 * 1000 3 4/** Reusable login guidance appended to bridge auth errors. */ 5export const BRIDGE_LOGIN_INSTRUCTION = 6 'Remote Control is only available with claude.ai subscriptions. Please use `/login` to sign in with your claude.ai account.' 7 8/** Full error printed when `claude remote-control` is run without auth. */ 9export const BRIDGE_LOGIN_ERROR = 10 'Error: You must be logged in to use Remote Control.\n\n' + 11 BRIDGE_LOGIN_INSTRUCTION 12 13/** Shown when the user disconnects Remote Control (via /remote-control or ultraplan launch). */ 14export const REMOTE_CONTROL_DISCONNECTED_MSG = 'Remote Control disconnected.' 15 16// --- Protocol types for the environments API --- 17 18export type WorkData = { 19 type: 'session' | 'healthcheck' 20 id: string 21} 22 23export type WorkResponse = { 24 id: string 25 type: 'work' 26 environment_id: string 27 state: string 28 data: WorkData 29 secret: string // base64url-encoded JSON 30 created_at: string 31} 32 33export type WorkSecret = { 34 version: number 35 session_ingress_token: string 36 api_base_url: string 37 sources: Array<{ 38 type: string 39 git_info?: { type: string; repo: string; ref?: string; token?: string } 40 }> 41 auth: Array<{ type: string; token: string }> 42 claude_code_args?: Record<string, string> | null 43 mcp_config?: unknown | null 44 environment_variables?: Record<string, string> | null 45 /** 46 * Server-driven CCR v2 selector. Set by prepare_work_secret() when the 47 * session was created via the v2 compat layer (ccr_v2_compat_enabled). 48 * Same field the BYOC runner reads at environment-runner/sessionExecutor.ts. 49 */ 50 use_code_sessions?: boolean 51} 52 53export type SessionDoneStatus = 'completed' | 'failed' | 'interrupted' 54 55export type SessionActivityType = 'tool_start' | 'text' | 'result' | 'error' 56 57export type SessionActivity = { 58 type: SessionActivityType 59 summary: string // e.g. "Editing src/foo.ts", "Reading package.json" 60 timestamp: number 61} 62 63/** 64 * How `claude remote-control` chooses session working directories. 65 * - `single-session`: one session in cwd, bridge tears down when it ends 66 * - `worktree`: persistent server, every session gets an isolated git worktree 67 * - `same-dir`: persistent server, every session shares cwd (can stomp each other) 68 */ 69export type SpawnMode = 'single-session' | 'worktree' | 'same-dir' 70 71/** 72 * Well-known worker_type values THIS codebase produces. Sent as 73 * `metadata.worker_type` at environment registration so claude.ai can filter 74 * the session picker by origin (e.g. assistant tab only shows assistant 75 * workers). The backend treats this as an opaque string — desktop cowork 76 * sends `"cowork"`, which isn't in this union. REPL code uses this narrow 77 * type for its own exhaustiveness; wire-level fields accept any string. 78 */ 79export type BridgeWorkerType = 'claude_code' | 'claude_code_assistant' 80 81export type BridgeConfig = { 82 dir: string 83 machineName: string 84 branch: string 85 gitRepoUrl: string | null 86 maxSessions: number 87 spawnMode: SpawnMode 88 verbose: boolean 89 sandbox: boolean 90 /** Client-generated UUID identifying this bridge instance. */ 91 bridgeId: string 92 /** 93 * Sent as metadata.worker_type so web clients can filter by origin. 94 * Backend treats this as opaque — any string, not just BridgeWorkerType. 95 */ 96 workerType: string 97 /** Client-generated UUID for idempotent environment registration. */ 98 environmentId: string 99 /** 100 * Backend-issued environment_id to reuse on re-register. When set, the 101 * backend treats registration as a reconnect to the existing environment 102 * instead of creating a new one. Used by `claude remote-control 103 * --session-id` resume. Must be a backend-format ID — client UUIDs are 104 * rejected with 400. 105 */ 106 reuseEnvironmentId?: string 107 /** API base URL the bridge is connected to (used for polling). */ 108 apiBaseUrl: string 109 /** Session ingress base URL for WebSocket connections (may differ from apiBaseUrl locally). */ 110 sessionIngressUrl: string 111 /** Debug file path passed via --debug-file. */ 112 debugFile?: string 113 /** Per-session timeout in milliseconds. Sessions exceeding this are killed. */ 114 sessionTimeoutMs?: number 115} 116 117// --- Dependency interfaces (for testability) --- 118 119/** 120 * A control_response event sent back to a session (e.g. a permission decision). 121 * The `subtype` is `'success'` per the SDK protocol; the inner `response` 122 * carries the permission decision payload (e.g. `{ behavior: 'allow' }`). 123 */ 124export type PermissionResponseEvent = { 125 type: 'control_response' 126 response: { 127 subtype: 'success' 128 request_id: string 129 response: Record<string, unknown> 130 } 131} 132 133export type BridgeApiClient = { 134 registerBridgeEnvironment(config: BridgeConfig): Promise<{ 135 environment_id: string 136 environment_secret: string 137 }> 138 pollForWork( 139 environmentId: string, 140 environmentSecret: string, 141 signal?: AbortSignal, 142 reclaimOlderThanMs?: number, 143 ): Promise<WorkResponse | null> 144 acknowledgeWork( 145 environmentId: string, 146 workId: string, 147 sessionToken: string, 148 ): Promise<void> 149 /** Stop a work item via the environments API. */ 150 stopWork(environmentId: string, workId: string, force: boolean): Promise<void> 151 /** Deregister/delete the bridge environment on graceful shutdown. */ 152 deregisterEnvironment(environmentId: string): Promise<void> 153 /** Send a permission response (control_response) to a session via the session events API. */ 154 sendPermissionResponseEvent( 155 sessionId: string, 156 event: PermissionResponseEvent, 157 sessionToken: string, 158 ): Promise<void> 159 /** Archive a session so it no longer appears as active on the server. */ 160 archiveSession(sessionId: string): Promise<void> 161 /** 162 * Force-stop stale worker instances and re-queue a session on an environment. 163 * Used by `--session-id` to resume a session after the original bridge died. 164 */ 165 reconnectSession(environmentId: string, sessionId: string): Promise<void> 166 /** 167 * Send a lightweight heartbeat for an active work item, extending its lease. 168 * Uses SessionIngressAuth (JWT, no DB hit) instead of EnvironmentSecretAuth. 169 * Returns the server's response with lease status. 170 */ 171 heartbeatWork( 172 environmentId: string, 173 workId: string, 174 sessionToken: string, 175 ): Promise<{ lease_extended: boolean; state: string }> 176} 177 178export type SessionHandle = { 179 sessionId: string 180 done: Promise<SessionDoneStatus> 181 kill(): void 182 forceKill(): void 183 activities: SessionActivity[] // ring buffer of recent activities (last ~10) 184 currentActivity: SessionActivity | null // most recent 185 accessToken: string // session_ingress_token for API calls 186 lastStderr: string[] // ring buffer of last stderr lines 187 writeStdin(data: string): void // write directly to child stdin 188 /** Update the access token for a running session (e.g. after token refresh). */ 189 updateAccessToken(token: string): void 190} 191 192export type SessionSpawnOpts = { 193 sessionId: string 194 sdkUrl: string 195 accessToken: string 196 /** When true, spawn the child with CCR v2 env vars (SSE transport + CCRClient). */ 197 useCcrV2?: boolean 198 /** Required when useCcrV2 is true. Obtained from POST /worker/register. */ 199 workerEpoch?: number 200 /** 201 * Fires once with the text of the first real user message seen on the 202 * child's stdout (via --replay-user-messages). Lets the caller derive a 203 * session title when none exists yet. Tool-result and synthetic user 204 * messages are skipped. 205 */ 206 onFirstUserMessage?: (text: string) => void 207} 208 209export type SessionSpawner = { 210 spawn(opts: SessionSpawnOpts, dir: string): SessionHandle 211} 212 213export type BridgeLogger = { 214 printBanner(config: BridgeConfig, environmentId: string): void 215 logSessionStart(sessionId: string, prompt: string): void 216 logSessionComplete(sessionId: string, durationMs: number): void 217 logSessionFailed(sessionId: string, error: string): void 218 logStatus(message: string): void 219 logVerbose(message: string): void 220 logError(message: string): void 221 /** Log a reconnection success event after recovering from connection errors. */ 222 logReconnected(disconnectedMs: number): void 223 /** Show idle status with repo/branch info and shimmer animation. */ 224 updateIdleStatus(): void 225 /** Show reconnecting status in the live display. */ 226 updateReconnectingStatus(delayStr: string, elapsedStr: string): void 227 updateSessionStatus( 228 sessionId: string, 229 elapsed: string, 230 activity: SessionActivity, 231 trail: string[], 232 ): void 233 clearStatus(): void 234 /** Set repository info for status line display. */ 235 setRepoInfo(repoName: string, branch: string): void 236 /** Set debug log glob shown above the status line (ant users). */ 237 setDebugLogPath(path: string): void 238 /** Transition to "Attached" state when a session starts. */ 239 setAttached(sessionId: string): void 240 /** Show failed status in the live display. */ 241 updateFailedStatus(error: string): void 242 /** Toggle QR code visibility. */ 243 toggleQr(): void 244 /** Update the "<n> of <m> sessions" indicator and spawn mode hint. */ 245 updateSessionCount(active: number, max: number, mode: SpawnMode): void 246 /** Update the spawn mode shown in the session-count line. Pass null to hide (single-session or toggle unavailable). */ 247 setSpawnModeDisplay(mode: 'same-dir' | 'worktree' | null): void 248 /** Register a new session for multi-session display (called after spawn succeeds). */ 249 addSession(sessionId: string, url: string): void 250 /** Update the per-session activity summary (tool being run) in the multi-session list. */ 251 updateSessionActivity(sessionId: string, activity: SessionActivity): void 252 /** 253 * Set a session's display title. In multi-session mode, updates the bullet list 254 * entry. In single-session mode, also shows the title in the main status line. 255 * Triggers a render (guarded against reconnecting/failed states). 256 */ 257 setSessionTitle(sessionId: string, title: string): void 258 /** Remove a session from the multi-session display when it ends. */ 259 removeSession(sessionId: string): void 260 /** Force a re-render of the status display (for multi-session activity refresh). */ 261 refreshDisplay(): void 262}