import type { SessionBackend, SpawnOpts, SessionProbe } from './types.js'; /** * ZellijBackend — session backend using zellij for process persistence. * * Architecture: pty-under-zellij (the "B route"), the zellij analogue of the * legacy TmuxBackend (pty-under-tmux): * - A node-pty process runs `zellij --session … --layout-string …` (fresh) * or `zellij attach …` (reattach). The node-pty is the only zellij client. * - All output flows through the pty (onData/onExit work unchanged) — we get * the raw rendered byte stream for free, sidestepping zellij `subscribe`'s * "whole-viewport-snapshot" model that doesn't fit botmux's xterm pipeline. * - Input goes through pty.write(). We start zellij in **locked mode with * keybindings cleared** (generated config), so every byte we write — * including Ctrl-C, arrows, bracketed-paste markers — passes straight to * the focused CLI pane with zero keybinding interception (the moral * equivalent of tmux's single prefix key, but with nothing reserved). * - resize() is pty.resize(): the attached client's size drives the pane, so * the headless-default 25-column problem never bites (the pty is the size). * - kill() only detaches (kills the pty client); the zellij server keeps the * CLI running, so a daemon restart re-attaches with `zellij attach`. * - destroySession() runs `zellij delete-session -f` (kill + purge the * resurrectable corpse) on explicit /close. * * Naming: zellij sessions are named `bmx-`, same as tmux. */ export declare class ZellijBackend implements SessionBackend { private process; private readonly sessionName; private readonly ownsSession; private reattaching; private configPath; private tmpConfigDir; /** Set by kill()/destroySession() so the pty-client exit they cause (an * intentional detach/teardown — the zellij session survives) is NOT * reported as a CLI exit. A real CLI exit leaves this false. */ private intentionalExit; /** Cached CLI pid. The CLI subprocess starts asynchronously after spawn(), * so the first getChildPid() may be null; once resolved it's stable for the * session lifetime (single CLI pane → pane exit ends the session). */ private resolvedCliPid; /** Explicit pane target for adopt mode (e.g. "terminal_2"). When null, * zellij `action` commands address the focused pane — correct for managed * mode where the single CLI pane is always focused. */ private readonly paneId; claudeJsonlPath?: string; cliPid?: number; cliCwd?: string; constructor(sessionName: string, opts?: { ownsSession?: boolean; isReattach?: boolean; paneId?: string; }); static isAvailable(): boolean; static sessionName(sessionId: string): string; /** Names of LIVE (non-exited) zellij sessions. A killed-but-serialised * session lingers in `list-sessions` as "(EXITED - attach to resurrect)"; * we must not treat those as reattachable, so filter them out. */ static liveSessions(): string[]; /** Like liveSessions(), but distinguishes "command failed/timed out" ({ok:false}) * from "command succeeded, zero live sessions" ({ok:true, sessions:[]}). The * tri-state probe builds on this so a transient `list-sessions` failure isn't * read as "session gone". */ static probeLiveSessions(): { ok: true; sessions: string[]; } | { ok: false; }; static hasSession(name: string): boolean; static probeSession(name: string): SessionProbe; /** Kill + purge a session (so no resurrectable corpse accumulates). */ static killSession(name: string): void; static listBotmuxSessions(): string[]; spawn(bin: string, args: string[], opts: SpawnOpts): void; /** Write the per-session config (locked mode + cleared keybinds so pty.write * passes through untouched, no startup tips / pane frames) and, for a fresh * spawn, the single-pane layout file. Both live in one temp dir cleaned on * kill(). On reattach the layout file is unused but harmless. */ private writeRuntimeFiles; get isReattach(): boolean; write(data: string): boolean; /** Literal text, no Enter. */ sendText(text: string): boolean; /** Special keys by tmux-style name (Enter, Escape, C-c, M-Enter, …). */ sendSpecialKeys(...keys: string[]): boolean; /** Bracketed paste: wrap with \e[200~ … \e[201~ so TUIs (CoCo/Ink/Codex) * detect the paste boundary and don't treat embedded \n as Enter. Mirrors * `tmux paste-buffer -p`. */ pasteText(text: string): void; resize(cols: number, rows: number): void; /** Must be called AFTER spawn(). */ onData(cb: (data: string) => void): void; /** Must be called AFTER spawn(). */ onExit(cb: (code: number | null, signal: string | null) => void): void; /** CLI pid. May be null immediately after spawn() (the CLI subprocess starts * asynchronously); the worker retries. Cached once resolved. */ getChildPid(): number | null; /** Detach only — kills the pty client, leaves the zellij session running. */ kill(): void; /** Kill the zellij session permanently (explicit /close). */ destroySession(): void; private cleanupConfig; } /** * Locked mode + cleared keybinds => the client pty forwards every byte to the * focused pane with no zellij interception. No startup tips / pane frames so * the captured stream is just the CLI (we don't need to hide chrome, but a * clean single pane keeps screenshots faithful and the renderer simple). */ export declare const ZELLIJ_CONFIG_KDL = "// botmux-generated \u2014 do not edit\nshow_startup_tips false\npane_frames false\ndefault_mode \"locked\"\nkeybinds clear-defaults=true {\n}\n"; /** Escape a string for a KDL double-quoted value. */ export declare function kdlString(s: string): string; /** * Build the `--layout-string` KDL: a single full-screen pane that execs the CLI * through the user's shell (same wrapper TmuxBackend uses, so PATH / NVM / pnpm * / mise shims load from rcfiles). Command + args go in via execvp semantics — * no shell-quoting needed (KDL strings carry spaces/quotes), only KDL escaping. * * POSIX shell layouts keep the `-c