/** * Ownership of the operator's keyboard for the lifetime of a `ccx run`. * * A run can host several claude sessions back to back (an account swap ends one * and starts the next). Each session used to take the terminal into raw mode, * resume stdin, attach a listener, then undo all of it on exit, so a swap * toggled global terminal state twice while a pseudo-terminal was being torn * down and another built. On Windows, where the terminal handle may itself be a * pseudo-terminal (VS Code / Cursor integrated terminals are), that churn is * exactly the kind of thing that destabilises the native layer. * * So the terminal is claimed ONCE per run and handed to whichever session is * current: sessions only swap the destination of the keystrokes, they never * touch the terminal's mode. */ import { type EscapeBufferOptions } from './escape-buffer.js'; import { type MouseGate } from './mouse-gate.js'; type Writer = (data: string) => void; export interface TerminalInput { /** Send keystrokes to `write` until the returned detach function is called. */ attach(write: Writer): () => void; /** * Tell the relay what the child just wrote, so it knows which mouse reports * that child has actually asked for. Everything the child prints already * passes through the caller, so this costs a scan and nothing else. */ observeChildOutput(text: string): void; /** Restore the terminal and stop reading. Safe to call more than once. */ close(): void; } export interface TerminalInputDeps { /** Where to write terminal-correcting sequences; defaults to stdout. */ out?: (text: string) => void; /** Injected in tests. */ gate?: MouseGate; /** * Timing for the escape buffer. Injected so tests can drive the clock rather * than sleep: the windows here are measured in a few hundred milliseconds, * and a test that waits out a real one is deciding by how busy the machine * is, not by whether the code is right. */ escapeBuffer?: EscapeBufferOptions; /** * Told once when reports are being dropped that the session never asked for. * * Silence here would be a mistake repeated. This bug survived two fixes * partly because nothing recorded what was happening, so each attempt began * by guessing again. If it ever comes back, the log should already say that * the terminal was reporting mouse activity nobody requested, and whether * telling it to stop worked. */ onUnrequestedReports?: (detail: { dropped: number; toldTerminalToStop: boolean; }) => void; } /** Claim the terminal for this run. */ export declare function openTerminalInput(stream?: NodeJS.ReadStream, deps?: TerminalInputDeps): TerminalInput; export {};