import type Database from "better-sqlite3"; import type { ManagedSession, SessionStatus } from "../../types"; /** * Durable registry of managed sessions (C1 Phase 2). * See docs/architecture/2026-07-24-durable-session-runtime.md. * * Distinct from SessionsRepository, which wraps the in-memory SessionStore for * the live request path. This one exists so session identity and provenance * outlive the streamer process: without it, a restart loses startedAt, * promptCount, sessionName, the Codex rollout binding, and failureReason, and * the boot reconciler has nothing to reconcile against. * * Deliberately not stored here: the PTY output ring buffer and xterm screen. * Their authoritative copy is the provider's JSONL, and writing 64KiB of ANSI * on every chunk to duplicate it would be a write storm for no gain. */ /** How a persisted status was obtained — never inferred, always recorded. */ export type StatusSource = "spawn" | "transition" | "exit" | "shutdown" | "probe" | "reconcile"; export type { SessionLifecycle } from "../../types"; export interface ManagedSessionRow { is_subagent?: number; parent_conversation_id?: string | null; session_id: string; provider: string; pid: number | null; cmdline: string | null; project_path: string; project_name: string; branch: string; status: string; status_source: string; status_updated_at: number; started_at: number; completed_at: number | null; last_activity_at: number | null; prompt_count: number; session_name: string | null; project_id: string | null; bound_conversation_id: string | null; resumed_from_conversation_id: string | null; failure_reason: string | null; streamer_instance_id: string; /** * Which machine boot `pid` was recorded during (migration 002). Optional * because rows written before it exists read back as null/absent, which the * reconciler treats exactly like a mismatch — never like a match. */ boot_token?: string | null; } /** Most rows the boot reconciler will probe in one pass. */ export declare const PROBE_SET_MAX = 200; /** Most rows one diagnostics report will carry. */ export declare const DIAGNOSTICS_MAX = 200; /** How long a finished session stays in the registry as history. */ export declare const TERMINAL_RETENTION_MS: number; export interface RecordSpawnInput { session: ManagedSession; pid: number | null; cmdline: string | null; streamerInstanceId: string; } export declare class ManagedSessionsRepository { private upsertStmt; private updateStatusStmt; private bindStmt; private getStmt; private listNonTerminalStmt; private listAllStmt; private pruneTerminalStmt; private listRecoverableStmt; private deleteStmt; constructor(db: Database.Database); /** Record a session at spawn, or refresh every field of an existing row. */ recordSpawn({ session, pid, cmdline, streamerInstanceId }: RecordSpawnInput): void; /** * Persist a status transition. `source` is required rather than defaulted: * a status whose provenance is unknown is the thing this table exists to * prevent, and the reconciler reads it to decide how much to trust the value. */ recordStatus(sessionId: string, status: SessionStatus, source: StatusSource, fields?: { completedAt?: Date | null; lastActivityAt?: Date | null; promptCount?: number; failureReason?: string | null; /** Null/omitted keeps whatever is stored — it never clears a known name. */ sessionName?: string | null; }): void; /** * Persist the Codex rollout id discovered after spawn. * * Its own statement rather than a `recordSpawn` re-run: the binding arrives * while the session is live, and re-upserting would also rewrite `cmdline` * with an id that is *not* in a fresh Codex process's argv, turning the * reconciler's identity check into a false `orphaned`. Without this write the * binding lives only in memory and dies with the streamer — which is the * whole reason a restarted Codex session could not be resumed (G6). */ recordBinding(sessionId: string, boundConversationId: string): void; get(sessionId: string): ManagedSessionRow | null; /** * Rows with no recorded completion — the reconciler's probe set. * * Capped. Callers must compare the result length against the limit and say so * when it clips: a silently truncated probe set reads as "we checked * everything" when it did not. */ listNonTerminal(limit?: number): ManagedSessionRow[]; /** Every row, most recently touched first, for the diagnostics surface. */ listAll(limit?: number): ManagedSessionRow[]; /** * Delete terminal rows older than `olderThanMs`, returning how many went. * * Only rows carrying a `completed_at` are eligible, so nothing the reconciler * or rehydrator might still want is reachable from here — a row without one * is by definition unfinished business, however old it looks. */ pruneTerminal(olderThanMs?: number): number; /** * Rows a restart could bring back: still open, or closed by our own shutdown, * and touched no longer ago than `sinceMs`. Newest first, capped — the caller * decides which of these actually deserve rehydrating (`shouldRehydrate`). */ listRecoverable({ sinceMs, limit, includeSubagents, }: { sinceMs: number; limit: number; includeSubagents?: boolean; }): ManagedSessionRow[]; delete(sessionId: string): void; } //# sourceMappingURL=managed-sessions.repository.d.ts.map