import type { TerminalCapability } from "@opengeni/sdk"; import { type ReactNode } from "react"; import { type TerminalStreamStatus } from "../hooks/use-terminal-stream.js"; import type { UseSandboxTerminalResult } from "../hooks/use-sandbox-terminal.js"; import { type RendererTier } from "../lib/xterm-renderer.js"; /** * A COMPLETE xterm `ITheme` (subset by intent, but every field xterm colors a * cell with is present so it never falls back to its stock VGA palette). Built * from the og tokens via `xtermThemeFromTokens`. */ export type XtermTheme = { background?: string; foreground?: string; cursor?: string; cursorAccent?: string; selectionBackground?: string; selectionForeground?: string; selectionInactiveBackground?: string; black?: string; red?: string; green?: string; yellow?: string; blue?: string; magenta?: string; cyan?: string; white?: string; brightBlack?: string; brightRed?: string; brightGreen?: string; brightYellow?: string; brightBlue?: string; brightMagenta?: string; brightCyan?: string; brightWhite?: string; }; export type SandboxTerminalProps = { /** From `useSandboxTerminal(...)`. */ result: UseSandboxTerminalResult; /** * The negotiated Terminal capability cell. When it advertises `transport: * "pty-ws"` + a live `url` (a warm box with a viewer attached), the terminal is * driven by a REAL bidirectional PTY over the Modal tunnel (ttyd-over-websocket) * INSTEAD of the broken ptyWrite-over-HTTP path: xterm input → the socket, the * socket's output → xterm, xterm resize → the socket. On a cold box / no url * (`transport: "sse-events"`) it stays on the read-only Channel-A firehose * (`result.chunks`). Omit to force the legacy firehose-only behavior. */ terminalCapability?: TerminalCapability | null | undefined; /** * Full xterm theme. When omitted the terminal self-derives it from the `--og-*` * tokens of its own container (correct even inside a nested `data-og-theme` * subtree). Passing it lets the host drive re-theme on a theme flip. */ theme?: XtermTheme | undefined; fontFamily?: string | undefined; fontSize?: number | undefined; /** * Lease liveness (`cold` | `warming` | `warm` | `draining`). Drives the * boot-in-terminal status lines: once the user has engaged the terminal and * the box is not yet warm, a styled "● waking machine — Ns…" line is rendered * INSIDE xterm (no overlay/spinner) until the live PTY connects. */ liveness?: string | undefined; /** * Force read-only even when the PTY accepts stdin. Default: interactive * whenever a live pty-ws stream is connected OR `result.write !== null` (the box * advertises an interactive PTY); otherwise the read-only agent firehose. */ readOnly?: boolean | undefined; /** Shown on the server / before xterm hydrates (SSR-safe placeholder). */ placeholder?: ReactNode | undefined; /** Render the small status header (pty/shell + running dot + read-only pill). */ showHeader?: boolean | undefined; /** Shell label for the header (e.g. `/bin/bash`). */ shell?: string | undefined; /** * Fired the first time the user engages the terminal surface (focus or click). * The host wires this to warm the box for the REAL pty-ws terminal (the viewer * attach), so a cold box upgrades from the read-only firehose to a live PTY ON * INTERACT — never on mere mount (which would force a box spin-up and regress * the firehose-only default). */ onActivate?: (() => void) | undefined; /** Re-negotiate the terminal capability after an unexpected socket failure. */ onReconnectNeeded?: (() => void) | undefined; className?: string | undefined; }; type XtermLike = { open: (el: HTMLElement) => void; focus: () => void; write: (data: string) => void; clear: () => void; /** Full reset (screen, scrollback and modes); optional for test doubles. */ reset?: () => void; onData: (cb: (data: string) => void) => { dispose: () => void; }; onResize: (cb: (size: { cols: number; rows: number; }) => void) => { dispose: () => void; }; loadAddon: (addon: unknown) => void; dispose: () => void; cols: number; rows: number; options: { theme?: XtermTheme; disableStdin?: boolean; cursorBlink?: boolean; fontFamily?: string; fontSize?: number; }; }; export type TerminalSurfaceState = "loading" | "connecting" | "error" | "waking" | "output-only" | "interactive"; /** * Stable semantic state for hosts and browser acceptance. The xterm helper * textarea exists before the live PTY socket is ready, so its presence alone * must never be treated as proof that keystrokes can reach the sandbox. */ export declare function terminalSurfaceState(input: { ready: boolean; acceptsInput: boolean; inputPending: boolean; ptyStatus: TerminalStreamStatus; booting: boolean; }): TerminalSurfaceState; export declare function subscribeTerminalInput(term: Pick | null, sink: ((data: string) => void) | null, enabled: boolean): { dispose: () => void; } | null; /** Clear the transient wake line and put the first live prompt at cell 1,1. * `Terminal.clear()` removes scrollback but deliberately preserves the cursor, * which lets the first PTY frame append to the old wake message. */ export declare function clearTerminalBootStatus(term: Pick): void; /** Debug seam (dev/evidence only, never a typed public API): a global hook the * screenshot harness reads to probe the settled renderer tier + resolved font * without mounting a real WebGL context in unit tests. */ type TerminalDebugInfo = { renderer: RendererTier | null; fontFamily: string | undefined; fontSize: number | undefined; hasVarInFont: boolean; theme: XtermTheme | undefined; /** The live xterm instance (opaque) — the evidence harness reads its buffer to * prove screen preservation (E5) and drive the burst-responsiveness probe. */ term: unknown; }; declare global { var __OG_TERMINAL_DEBUG__: ((info: TerminalDebugInfo) => void) | undefined; var __OG_FORCE_RENDERER_FAIL__: string | undefined; } /** * An xterm.js terminal fed by the Channel-A event projection * (`useSandboxTerminal`) or a live ttyd PTY. xterm + its addons are lazy-imported * inside an effect, so SSR renders the placeholder and the terminal mounts on * hydration. * * Renderer: WebGL (GPU-composited cells) with a canvas → DOM fallback ladder * (`attachRenderer`), so bursty output stays smooth but a lost/blocklisted * context degrades gracefully. Font + theme are resolved to CONCRETE values * from the og tokens BEFORE construction (xterm measures glyphs in a canvas * where `var()` never resolves, and colors every cell from a full ANSI palette). * * The terminal is mounted ONCE and its data source is swapped in place: the * read-only firehose (`result.chunks`) → the live PTY. Stdin is toggled via * `term.options.disableStdin` at runtime (not a remount), so scrollback and the * visible screen survive the projection→PTY handoff. * * Resizes are tracked with a `ResizeObserver` on the container (not just * `window.resize`) so dragging the dock handle refits the grid. */ export declare function SandboxTerminal({ result, terminalCapability, theme, fontFamily, fontSize, liveness, readOnly, placeholder, showHeader, shell, onActivate, onReconnectNeeded, className, }: SandboxTerminalProps): import("react").JSX.Element; export {};