import type { NatsConnection } from "@nats-io/transport-node"; import { type SessionGrant } from "@cotal-ai/core"; /** * The detach key — Ctrl-] (0x1d) by default, as in telnet/ssh escape conventions. The `detach-key` * preference rebinds it to another control key when Ctrl-] clashes with a keybinding inside the * agent's TUI: `COTAL_DETACH_KEY`, then the saved preference (`cotal config set detach-key`). Both * accept `ctrl-` or `^` (e.g. `ctrl-b`, `^_`). An unparseable `COTAL_DETACH_KEY` — * including a set-but-empty or whitespace-only one — throws (named), never silently falling back; * the caller turns that into a loud exit. A saved value was validated when it was written. */ export declare function detachKey(): { byte: number; label: string; overridden: boolean; from: string; }; /** * Whether a whole chunk `d` is exactly one press of the detach key `byte`, in any of three * encodings: the legacy single control byte; the kitty keyboard protocol's `CSI ; * u`; or xterm `modifyOtherKeys`'s `CSI 27 ; ; ~`. `` is the * decimal codepoint kitty/xterm report for the unshifted key: `byte | 0x40` for the control * punctuation (`@ [ \ ] ^ _`) and `byte | 0x60` for a letter (kitty reports the lowercase key even * though Ctrl was held). `` is 1 + the modifier bitmask and must carry the Ctrl bit * (`(mods - 1) & 4`), so plain Ctrl (5) and Ctrl+Shift (6) match but a modifier field without Ctrl * does not, and a field of 0, which neither protocol emits, is not a press either (the test needs * that floor: `(0 - 1) & 4` is 4 in ToInt32 arithmetic, so 0 would otherwise read as Ctrl). The match is on the WHOLE chunk: * a sequence with company (a paste, a second keystroke, a trailing byte) is not a press, which is * the same paste-safety property the one-byte test already had. This assumes one press arrives in * one read, as measured on a pty (issue #598); a sequence split across two reads is not buffered * and is not recognised. A kitty `CSI u` with an alternate-key `::` triple * is accepted by matching only the leading codepoint before an optional `:...` group; no other * kitty event type (release, repeat) is recognised. */ export declare function isDetachPress(d: Buffer, byte: number): boolean; /** * A transport-agnostic terminal session. {@link attachClient} drives the terminal through this * interface, so the SAME raw-mode / alt-screen / detach handling serves the mesh §13.6 session * ({@link meshSessionTransport}). (The legacy loopback `ws://.../attach/` transport was removed with * the P2 item 6 no-127.0.0.1 sweep.) */ export interface TerminalTransport { /** Fires once the transport is connected and ready to carry the terminal. */ onReady(cb: () => void): void; /** Remote → local: terminal output bytes. The transport awaits an async consumer before returning * credit, so a slow local stdout applies real backpressure instead of acknowledging unread bytes. */ onData(cb: (bytes: Buffer) => void | Promise): void; /** The session ended: `err` on a transport error (rejects), else a clean detach/close (resolves), * with the peer's distinct end `reason` when it sent one. */ onEnd(cb: (err?: Error, reason?: string) => void): void; /** Local → remote: keystroke bytes. */ send(bytes: Buffer): void; /** Local → remote: terminal resize. */ resize(cols: number, rows: number): void; /** Detach: signal the remote and stop locally. */ close(): void; } /** * The operator's terminal, held ACROSS attach attempts. A reconnect re-establishes the SESSION, * not the terminal: restoring raw mode and leaving the child's alternate screen between attempts * would flash the shell's screen back and then repaint over it, which is exactly the garble the * reconnect is supposed to spare the operator. So the two facts that outlive one session — the * mode the terminal was in before we touched it, and whether the child has us in its alternate * screen — live here, and the undo runs exactly once, when the attach really ends. * * `attachClient` creates its own when no hold is passed, which is the one-shot behaviour: enter on * ready, undo on end. */ export interface TerminalHold { /** Raw mode on, remembering what we found. Idempotent across reconnects. */ enterRaw(): void; /** Is the child in its alternate screen right now? Carried across reconnects so the final undo * leaves that buffer exactly when the child put us there. */ altScreen: boolean; /** Undo every mode we changed, leave the alternate screen if the child put us there, and give * the terminal back the raw mode it had. Idempotent. */ restore(): void; } export declare function holdTerminal(): TerminalHold; /** * Why a session ended, as {@link attachClient} resolves it. The reason is the peer's distinct `end` * reason, the transport's own (`detached`, `peer-closed`, `connection-closed`), or the broken * rail's protocol-fault name. `error` is set when the end was a fault rather than a clean close; * it is DATA here rather than a rejection, because a caller that reconnects has to read the reason * to decide, and an exception carries it only as prose. */ export interface AttachOutcome { readonly reason: string; readonly error?: Error; /** Did any seat output actually reach the terminal? The manager replays its backlog snapshot on * every open, so a session that carried nothing never got that far — which is how a caller that * reconnects tells a session that worked from one that died on the way up. */ readonly carried: boolean; } /** Did the session end because the LINK broke (so re-establishing is the honest response)? */ export declare function isTransportEnd(reason: string): boolean; /** * Drive a manager's attach session from the terminal: raw-mode stdin streams to the remote PTY, * PTY output streams to stdout, and SIGWINCH-style resizes are forwarded. The detach key (Ctrl-] * by default, {@link detachKey}) detaches without killing the agent. Transport-agnostic over the * mesh §13.6 session. */ export declare function attachClient(transport: TerminalTransport, hold?: TerminalHold, takeStdin?: () => boolean): Promise; /** * The mesh §13.6 SESSION transport: the item-6 replacement for the loopback WebSocket. Given a * connection scoped to the session's eps rails and the redeemed grant, it opens the caller rail, * asks the serving side to replay the reconstructed backlog (the `ready` handshake — PR #158 over * the mesh), and speaks the core terminal-session frame codec. A `drop` frame is surfaced INLINE * (never a silent loss); a peer `end`/close or a protocol fault ends the session with the distinct * reason. No `127.0.0.1`, no bearer URL — the grant is holder-bound. */ export declare function meshSessionTransport(nc: NatsConnection, grant: SessionGrant): TerminalTransport; //# sourceMappingURL=attach-client.d.ts.map