/** * session-registry.ts — live interactive-terminal session management. * * Backs the `interactive_terminal` tool. Keeps child processes alive across * multiple tool calls so the model can drive REPLs, interactive prompts, and * (when a real PTY is available) full-screen TUI apps. * * Backend selection is a HYBRID: * 1. Prefer `node-pty` (a real pseudo-terminal → correct isatty, resize, * full-screen TUIs). It is an OPTIONAL dependency — absent on platforms * where it cannot be built. * 2. Fall back to `node:child_process` pipes. Works for line-oriented * interactive programs; does NOT satisfy programs that require a TTY. * * PTY sessions additionally feed a VT100 screen emulator (screen-buffer.ts) * so reads can return the CURRENT RENDERED SCREEN (like `tmux capture-pane`) * instead of the raw repaint stream — essential for full-screen TUIs that * redraw in place. The emulator also auto-answers terminal queries (cursor * position, device attributes, window size) that TUIs block on. * * This module MUST NOT import any platform SDK. */ import { TerminalScreen } from "./screen-buffer.ts"; export type TerminalBackendKind = "pty" | "pipe"; /** Backend selection: auto prefers a real PTY, falling back to pipes. */ export type TerminalBackendPref = "auto" | "pty" | "pipe"; interface Backend { readonly kind: TerminalBackendKind; write(data: string): void; resize(cols: number, rows: number): void; kill(signal?: string): void; } export interface OpenOptions { command?: string; args?: string[]; shell?: boolean; cwd?: string; env?: Record; cols?: number; rows?: number; idleTimeoutMs?: number; /** Caller-requested session id (sanitized; suffixed on collision). */ requestedId?: string; /** * Backend preference. Default "auto" (prefer node-pty, fall back to pipes). * Env `ROLEBOX_TERMINAL_BACKEND` overrides the default when this is unset. * NOTE: node-pty is unreliable under the Bun runtime (input written after a * short delay can be dropped) — force "pipe" there for line-oriented programs. */ backend?: TerminalBackendPref; } export type ReadMode = "auto" | "stream" | "screen"; export interface ReadOptions { waitMs?: number; until?: string; timeoutMs?: number; fromStart?: boolean; stripAnsi?: boolean; /** * What to read: * - "stream": raw output stream since the last read (append-only view). * - "screen": the current rendered screen snapshot (pty only) — the right * view for full-screen TUIs that repaint in place. * - "auto" (default): "screen" when the pty session behaves like a * full-screen TUI (alt-screen or heavy cursor addressing), else "stream". */ mode?: ReadMode; abort?: AbortSignal; } export interface ReadResult { text: string; /** Which view produced `text`. */ mode: "stream" | "screen"; /** For `until` reads: whether the pattern matched. null when no `until`. */ matched: boolean | null; /** True when the wait budget expired before the wait condition was met. */ timedOut: boolean; /** True when the read was cancelled via the abort signal. */ aborted: boolean; /** True when the read text was truncated to the last MAX_READ_CHARS chars. */ truncated: boolean; /** For screen reads: whether the snapshot differs from the previous one. */ screenChanged: boolean | null; } export interface TerminalSession { id: string; owner: string; backend: TerminalBackendKind; command: string; cols: number; rows: number; createdAt: number; lastActivityAt: number; lastDataAt: number; alive: boolean; exitCode: number | null; exitSignal: string | null; /** Spawn-time failure message (e.g. ENOENT), if any. */ spawnError: string | null; /** Retained output chunks (rolling, capped). */ chunks: string[]; /** Read cursor: index into the logical concatenation of `chunks`. */ readCursor: number; /** Total chars currently retained across `chunks`. */ retained: number; /** Total chars ever produced (for cursor math after trimming). */ produced: number; /** VT100 screen emulator (pty backend only). */ screen: TerminalScreen | null; /** Last screen snapshot returned to a reader (for change detection). */ lastScreenSnapshot: string | null; handle: Backend; idleTimer: ReturnType | null; idleTimeoutMs: number; } /** * Remove ANSI escape sequences (CSI, OSC and two-character escapes). * * One-line delegation to the canonical `stripAnsi` in `src/utils/text-format.ts`; * that module adopted this module's proven `ANSI_RE` verbatim, so PTY snapshot * behaviour is unchanged. */ export declare function stripAnsi(s: string): string; /** Whether the pty session looks like a full-screen TUI. */ export declare function isTuiSession(sess: TerminalSession): boolean; export declare function openSession(owner: string, opts: OpenOptions): Promise; /** Resolve a session for an owner. Throws if missing or owned by another session. */ export declare function getOwned(owner: string, id: string): TerminalSession; export declare function writeSession(sess: TerminalSession, data: string): void; export declare function resizeSession(sess: TerminalSession, cols: number, rows: number): void; /** * Close a session gracefully: send the signal (default SIGTERM), wait a grace * period, then escalate to SIGKILL if the process is still alive. */ export declare function closeSession(owner: string, id: string, signal?: string): Promise; export declare function listSessions(owner: string): TerminalSession[]; /** * Read output from a session. * * Views (see ReadOptions.mode): raw output stream, or (pty) the rendered * screen snapshot. "auto" picks the screen view for full-screen TUIs. * * Wait behaviour: * - `until` (regex): block until the pattern appears (stream: in new output, * ANSI-stripped; screen: in the snapshot), the process exits, the read * aborts, or the wait budget expires. * - `waitMs` (quiet period): block until no new output for waitMs (i.e. the * output has settled), exit, abort, or budget expiry. If the session is * already quiet, this returns after ~waitMs. * - neither: settle briefly, then return. * * The stream read-cursor is always consumed (unless `fromStart`), regardless * of view, so consecutive reads never replay old output. */ export declare function readSession(sess: TerminalSession, opts: ReadOptions): Promise; /** Test-only: reset all sessions. */ export declare function __resetForTests(): void; export {}; //# sourceMappingURL=session-registry.d.ts.map