/** * The `claude` processes a box hosts for the desktop app. * * A session outlives the connection that started it: the Mac can sleep, lose * the port-forward, or quit, and the work carries on. Each session keeps a * bounded replay buffer, so a returning app asks for everything after the last * message it saw and gets either the gap or an honest "truncated" marker. * * Every process detail is injected — the spawner, the clock, the id minting — * so all of this is testable without a box. */ import { type PermissionMode } from "./session-host-protocol.js"; export interface ChildProcessPort { readonly pid: number | undefined; /** Resolves once the child is running, or rejects when it could not spawn. */ whenSpawned?: () => Promise; writeStdin(data: string): void; closeStdin(): void; kill(signal: NodeJS.Signals): void; onStdoutLine(listener: (line: string) => void): void; onStderrLine(listener: (line: string) => void): void; onExit(listener: (code: number | null, signal: NodeJS.Signals | null) => void): void; } export type SpawnPort = (command: string, args: string[], options: { cwd: string; env: Record; }) => ChildProcessPort; export interface StartSpec { permissionMode: PermissionMode; cwd?: string; sessionId?: string; resume?: string; model?: string; effort?: string; thinkingDisplay?: string; } export interface SessionSummary { session: string; claudeSessionId: string; cwd: string; model?: string; permissionMode: PermissionMode; state: "running" | "exited"; startedAt: string; lastSeq: number; /** How many connections are watching this session right now. */ attached: number; exitCode?: number | null; exitSignal?: string | null; } export type SessionOutbound = { t: "frame"; session: string; seq: number; frame: Record; } | { t: "truncated"; session: string; fromSeq: number; toSeq: number; } | { t: "log"; session: string; stream: "stdout" | "stderr"; text: string; } | { t: "exited"; session: string; code: number | null; signal: string | null; }; export type SessionListener = (event: SessionOutbound) => void; export type StartFailureCode = "bad-cwd" | "too-many-sessions" | "session-busy" | "spawn-failed" | "shutting-down"; export type StartResult = { ok: true; summary: SessionSummary; } | { ok: false; code: StartFailureCode; message: string; }; export interface SessionManagerOptions { spawn: SpawnPort; claudeCommand: string; defaultCwd: string; isDirectory: (path: string) => Promise; env?: Record; newId?: () => string; newUuid?: () => string; /** Epoch milliseconds. */ now?: () => number; maxRunning?: number; replayBufferBytes?: number; maxRetainedExited?: number; endGraceMs?: number; killGraceMs?: number; log?: (line: string) => void; } export declare class SessionManager { private readonly deps; private readonly options; private readonly sessions; private shuttingDown; private counter; private exitCounter; constructor(deps: SessionManagerOptions); private now; private newId; private newUuid; private log; private running; start(spec: StartSpec): Promise; private refuseStart; private isDirectory; list(): SessionSummary[]; attach(sessionId: string, sinceSeq: number, listener: SessionListener): { ok: true; detach: () => void; } | { ok: false; code: "unknown-session"; }; input(sessionId: string, frame: Record): { ok: true; } | { ok: false; code: "unknown-session" | "session-exited"; }; /** Close claude's input, then insist if it has not finished in time. */ end(sessionId: string): { ok: true; } | { ok: false; code: "unknown-session"; }; shutdown(): Promise; private onStdout; private onExit; private forgetOldExited; private emit; private deliver; private summarize; } //# sourceMappingURL=session-host-sessions.d.ts.map