import type { FastifyInstance } from 'fastify'; import { WebSocket } from 'ws'; import type { CoreAdapter } from '../core/adapter.js'; import type { CoreEvent } from '../core/types.js'; /** * Per-terminal WS fan-out (DES-TERMINAL-001 §6). The daemon's single CoreEvent * subscription is the source of truth; this hub routes the `terminalOutput` / * `terminalExited` frames for a given terminal id to the ONE browser socket that * opened it, keyed by id (a `Map`). Raw PTY output arrives * base64-encoded (`bytesB64`); we decode and send it as a BINARY frame so * xterm.js writes the exact bytes — control sequences and multi-byte UTF-8 * survive a text-frame round-trip. * * Output a terminal writes BEFORE its browser socket attaches is held (bounded) and flushed on * attach: a login container prints its sign-in link and code the moment it starts, while the * browser opens `/ws/terminals/:id` only after the POST that created it returns — seconds later on * a loaded host — so without the hold the user would never see the link (wicked-core C6). A * terminal that exited (before or after a socket attached) closes any socket that attaches to it. */ /** Per-terminal bytes held for a socket that has not attached yet (the oldest are dropped). */ export declare const EARLY_OUTPUT_MAX_BYTES: number; /** Terminals whose early output is held at once (an exited one's hold is dropped first). */ export declare const EARLY_OUTPUT_MAX_TERMINALS = 64; /** Exited terminal ids remembered (insertion order, bounded; ids are never reused). */ export declare const REAPED_MEMORY = 8192; /** How long a socket the engine has no terminal for stays open before it is closed: frames the * engine emitted before the refusal may still be on their way through the event stream, and the * terminal's own exit frame (delivered in order after its output) closes it sooner. */ export declare const GONE_GRACE_MS = 10000; export declare class TerminalHub { private readonly sockets; /** Output for ids no socket has attached to yet. */ private readonly early; /** Ids whose `terminalExited` was seen (kept past their sockets: a later attach closes at once). */ private readonly reaped; /** Ids whose socket detached: nothing is held for them again. */ private readonly retired; /** * ids whose `terminalExited` we've already routed — so the WS close handler * skips a redundant `closeTerminal` (the engine already reaped that child). */ private readonly exited; /** Bind a browser socket to a terminal id (later `terminalOutput` fans out to it), first * replaying what the terminal wrote before the socket attached; an exited terminal's socket * is closed after the replay. */ register(id: string, socket: WebSocket): void; /** Whether the hub remembers that terminal `id` exited. */ knowsExited(id: string): boolean; /** Whether `socket` is the one bound to `id` now (a newer attach replaces an older one). */ isCurrent(id: string, socket: WebSocket): boolean; /** * The engine holds no terminal `id` for `socket` (never opened, or reaped with its exit no * longer remembered). After [`GONE_GRACE_MS`] — time for frames already emitted to arrive and * be routed in order — the socket is closed if it is still open; the id's bookkeeping is * marked exited only if `socket` is still the bound one (a replaced socket owns nothing). */ gone(id: string, socket: WebSocket, graceMs?: number): void; /** True once a `terminalExited` frame has been routed for this id. */ hasExited(id: string): boolean; /** Drop the socket + exit bookkeeping for a terminal id. */ unregister(id: string): void; /** Live terminal-socket count (diagnostics / tests). */ size(): number; /** Terminals with output held for a socket that has not attached yet (diagnostics / tests). */ heldCount(): number; /** * Route one CoreEvent frame to the terminal socket it belongs to. Called for * EVERY core event; non-terminal frames are no-ops, and a terminal frame for an id no socket * has attached to yet is held for it. */ route(event: CoreEvent): void; /** Hold a terminal's output for a socket that has not attached yet (bounded both ways). */ private hold; } /** * The per-terminal WS channel: `GET /ws/terminals/:id`. Binds the browser socket * to the terminal id in the hub (so `terminalOutput` for that id fans out to it), * forwards every inbound browser frame to the PTY as raw stdin bytes, and closes * the terminal when the socket drops (unless the PTY already exited on its own). */ export declare function registerTerminalWs(app: FastifyInstance, adapter: CoreAdapter, hub: TerminalHub): void; //# sourceMappingURL=terminals.d.ts.map