/** * Agent registry — the server-side ledger of agent sessions (Claude Code / * Codex CLIs running inside terminal ptys). * * Agents are project-scoped (keyed by the working directory they were * launched in) and decoupled from notebooks: switching notebooks never kills * or switches an agent; the user does, through the agent manager. Records * persist to disk so agents survive as *resumable* entities even when their * pty dies (browser gone, server restart, user hibernates them): the CLI's * own on-disk trajectory (`claude --resume ` / `codex resume`) can * reconstruct the conversation — the registry just remembers what exists, * where, and how to revive it. The client owns launch/revive command * construction (it knows about remote-agent mode); the server owns truth * about what is live. * * States: 'live' (pty exists) → 'hibernated' (pty gone, trajectory on disk). * All records load as 'hibernated' on boot — ptys never survive a restart. */ export interface AgentRecord { /** Terminal (pty) id the agent runs in — stable, derived from workdir. */ terminalId: string; kind: 'claude' | 'codex'; /** Directory the agent was launched in — the project scope. */ workdir: string; /** Where the CLI process runs: this server, or the user's machine over the reverse channel. */ location: 'server' | 'remote'; /** Claude --session-id (resume pointer). Codex resumes via its own picker. */ sessionId?: string; /** Notebook that launched it (informational only — agents are not bound to notebooks). */ launchedFrom?: string; /** * Pinned workspace-mirror dir slug (`p--` under ~/.nebula/agent). * Stored at launch so record-driven resumes keep finding the conversation * even if the slug derivation ever changes — paths in records don't drift. */ mirrorSlug?: string; state: 'live' | 'hibernated'; createdAt: number; lastLaunchAt: number; } declare class AgentRegistry { private records; private exitUnsubs; private loaded; private ensureLoaded; private persist; /** * Register (or re-register on revive) an agent launched in a terminal. * MERGE semantics: fields the caller didn't supply NEVER overwrite stored * truth — a resume relaunch that doesn't know the sessionId must not erase * the registry's copy (that id is the conversation; losing it downgrades * every later Continue to a picker). */ register(rec: Omit): AgentRecord; private static TUI_INIT_RE; private static TUI_TEARDOWN_RE; private dataUnsubs; private graceTimers; private watchLiveness; /** Feed pty output through the liveness machine (public for tests). */ observeOutput(terminalId: string, chunk: string): void; /** List all agents, reconciling 'live' against actual pty existence. */ list(): AgentRecord[]; /** * list() plus tty-foreground facts for every record whose pty exists: * - `busy`: something owns the pty's tty foreground — a TUI, an ssh hop, a * running command. Crucially reported on HIBERNATED records too: the * TUI-stream liveness machine can mis-score (an agent resumed by hand * never re-registers), and a client that trusts 'hibernated' then types a * launch command straight into the running TUI's input box (lab report: a * live codex politely declined to run the pasted ssh line). The tty * foreground is authoritative — and unlike a child-process scan it stays * false while a fresh login shell sources its rc files. * - `idleShell` on live records: the inverse — 'live' but the foreground is * a bare shell (a hung-then-dead ssh hop leaves exactly this; a dead * transport emits no teardown, so only the foreground probe can see it). * Unknown checks claim nothing. */ listEnriched(): (AgentRecord & { idleShell?: boolean; busy?: boolean; })[]; /** Hibernate: close the pty; the record (and on-disk trajectory) remain. */ hibernate(terminalId: string): boolean; /** Forget the agent entirely (pty closed; registry record removed). */ remove(terminalId: string): boolean; } export declare const agentRegistry: AgentRegistry; export {};