source dump of claude code
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}