/**
 * tmux session lifecycle.
 *
 * Public surface used by both `agents tmux` commands and external consumers
 * (swarmify, `agents teams` with a future multiplexer mode). Every state
 * change goes through here so the CLI surface stays a thin parser.
 *
 * Liveness model: tmux itself owns liveness. The `<name>.json` meta files are
 * pure provenance — `listSessions()` always reconciles them against
 * `tmux list-sessions` and prunes stale entries on the fly.
 */
/**
 * Ten times tmux's 2,000-line default, while keeping per-pane memory bounded:
 * history storage scales with pane width, so a 200-column pane retains at most
 * four million grid cells rather than an unbounded agent transcript.
 */
export declare const AGENTS_TMUX_HISTORY_LIMIT = 20000;
/**
 * Bump when the generated startup config changes, so an ALREADY-RUNNING server
 * picks the new settings up. tmux reads `-f` only when it actually starts a
 * server: a second `new-session -f other.conf` against a live server exits 0
 * and silently applies nothing (verified against tmux 3.4). Since agents-cli
 * uses one long-lived shared socket, the normal state at upgrade is a running
 * server — so without this reconcile every existing machine would get none of
 * these defaults, silently. Same shape as AGENT_HOOK_SCHEMA below.
 */
export declare const AGENTS_TMUX_CONFIG_SCHEMA = 1;
/** Source one user config without tmux's quiet flag, preserving path bytes. */
export declare function userConfigSourceLine(value: string): string;
/** Provenance written alongside each live tmux session. */
export interface SessionMeta {
    name: string;
    socket: string;
    createdAt: number;
    /** Initial command launched in the first pane (informational — tmux owns the actual process). */
    cmd?: string;
    /** Working directory the session was created in. */
    cwd?: string;
    /** Who created this session — useful for `agents sessions --active` attribution. */
    source: 'cli' | 'extension' | 'teams' | 'external';
    /** Free-form labels callers can stamp (e.g. `{ agent: 'claude', vscodePid: 1234 }`). */
    labels?: Record<string, string>;
    /**
     * The first pane's id (`%N`) captured at creation. The exact send-keys /
     * attach handle for the agent that runs in this session — recorded so
     * `agents sessions --active` and the spawn-wrap path (src/lib/exec.ts) don't
     * have to re-query it. Absent for pre-existing/`attach-existing` sessions.
     */
    pane?: string;
}
export interface CreateSessionOptions {
    name: string;
    cmd?: string;
    /**
     * Redacted copy of `cmd` to persist in SessionMeta.cmd instead of the real
     * command. When set, `cmd` is still executed but only `metaCmd` is written to
     * disk — used to keep resolved secret values out of the informational cmd
     * field (see exec.ts buildTmuxAgentCommand, RUSH-1758). Defaults to `cmd`.
     */
    metaCmd?: string;
    cwd?: string;
    env?: NodeJS.ProcessEnv;
    socket?: string;
    source?: SessionMeta['source'];
    labels?: Record<string, string>;
    /** When the named session already exists, kill it before creating. */
    replace?: boolean;
    /** When the named session already exists, return it instead of failing. */
    attachExisting?: boolean;
    /** Initial window dimensions for the detached session. tmux clamps to client size on attach. */
    width?: number;
    height?: number;
}
export interface ListedSession {
    name: string;
    socket: string;
    /** Unix epoch seconds reported by tmux (`session_created`). */
    createdAtTmux: number;
    windows: number;
    attached: boolean;
    meta?: SessionMeta;
}
/** Build the atomic server-configuration + session-creation command. */
export declare function buildCreateSessionArgs(opts: CreateSessionOptions, startupConfig: string): string[];
export declare class TmuxSessionError extends Error {
    readonly cause?: unknown | undefined;
    constructor(message: string, cause?: unknown | undefined);
}
/** Reject invalid session names early — tmux's error for `.`/`:` is cryptic. */
export declare function assertValidSessionName(name: string): void;
/** Slugify an arbitrary string into a valid session name. Useful for swarmify auto-generated names. */
export declare function slugifyName(input: string): string;
/** True when a session by this name currently exists on the given socket. */
export declare function hasSession(name: string, socket?: string): Promise<boolean>;
/**
 * Create a new detached session. Throws when the name is already taken unless
 * `replace` or `attachExisting` is set.
 */
export declare function createSession(opts: CreateSessionOptions): Promise<SessionMeta>;
/**
 * Kill one named session. Idempotent — killing a non-existent session is a no-op.
 *
 * Destroying the tmux session only SIGHUPs each pane's FOREGROUND process group,
 * so a helper the agent put in its own process group (an MCP server, a harness
 * background daemon) survives and reparents to init. `reapOrphans` therefore
 * defaults ON: the session's leftover helpers are terminated with it rather than
 * waiting for the daemon's next sweep (RUSH-2521). {@link reapDeadTmuxPanes}
 * passes `false` because it sweeps every session's leftovers in one pass instead
 * of re-snapshotting the process table per session.
 */
export declare function killSession(name: string, socket?: string, opts?: {
    reapOrphans?: boolean;
}): Promise<boolean>;
/**
 * After an attach client returns: destroy the session if every pane is dead
 * (the agent exited); leave it if any pane is still alive (Ctrl-b d).
 *
 * `runInTmux` already does this via `resolveAfterAttach`. The attach verbs
 * (`agents tmux attach`, `sessions focus`/`resume --attach-only`, `jumpTo`)
 * used to `process.exit` the tmux client status and leave a `remain-on-exit`
 * husk — the session "came back" in `tmux ls` after the user exited the agent.
 */
export declare function teardownIfAgentExited(name: string, socket?: string): Promise<'killed' | 'kept' | 'absent'>;
/**
 * Kill every session on the shared server AND the server itself, then prune
 * meta files. Wipes the socket so the next `new` starts from a clean slate.
 */
export declare function killAll(socket?: string): Promise<number>;
/** Result returned by {@link reapDeadTmuxPanes}. */
export interface ReapDeadPanesResult {
    /** Number of tmux sessions killed. */
    reaped: number;
    /** Names of reaped sessions. */
    sessions: string[];
    /** Human-readable lines describing each reap, for log output. */
    details: string[];
    /** Orphaned helper processes terminated (MCP servers, harness background daemons). */
    processes: number;
    /** One human-readable line per terminated helper process. */
    processDetails: string[];
    /** Non-fatal observations worth logging — e.g. a tier-1 sweep skipped this tick because a tmux query failed. */
    warnings: string[];
}
/**
 * Kill every tmux session whose panes are ALL dead (`pane_dead=1`), and
 * terminate the helper processes whose owning agent has exited.
 *
 * Dead panes accumulate because `remain-on-exit on` is intentionally set
 * per-pane so the harness can inspect exit status. Without a periodic sweep
 * they pile up indefinitely — e.g. 48 of 127 sessions dead on yosemite-s0
 * (RUSH-2501).
 *
 * Killing the pane is not enough on its own: it SIGHUPs only the pane's
 * FOREGROUND process group, so an MCP server or harness background daemon that
 * moved itself out of that group survives the session that spawned it and keeps
 * its memory forever (RUSH-2521). The process sweep runs FIRST, while the dead
 * sessions still exist, so every leftover is attributed to a named session
 * rather than to a session tmux has already forgotten.
 *
 * Safety invariant: a session with ANY live pane (`pane_dead=0`) is never
 * touched, and neither are the processes belonging to a session whose agent is
 * still running or that has a client attached.
 *
 * `dryRun` reports both halves without killing anything.
 */
export declare function reapDeadTmuxPanes(socket?: string, opts?: {
    dryRun?: boolean;
    pids?: number[];
}): Promise<ReapDeadPanesResult>;
/**
 * Map every pane id (`%N`) on a socket to its `session:window.pane` attach
 * target, in one batched `tmux list-panes -a` call. `%116 -> main:2.0` is a
 * valid `tmux attach -t main:2` / `tmux select-window -t main:2` target — a
 * human jump target, unlike the bare `%pane` send-keys id. Because it walks
 * every pane (not just one-per-session), it also surfaces multiple agents that
 * share a session across windows. Best-effort: returns an empty map on any
 * failure (tmux gone, foreign socket) so callers fall back to the raw pane id.
 */
export declare function mapPanesToTargets(socket?: string): Promise<Map<string, string>>;
/** One tmux client attached to the shared server. */
export interface TmuxClient {
    /** Controlling TTY of the terminal running `tmux attach` (e.g. '/dev/ttys004'). */
    tty: string;
    /** PID of the `tmux attach` client process — the leaf whose ancestry names the host app. */
    pid: number;
    /** The `session:window.pane` the client is currently displaying. */
    target: string;
}
/**
 * List every client attached to the shared server, with the terminal PID and
 * the session/window/pane it's viewing. This is how "viewing in <app> tab N"
 * resolves: a client's `pid` walks the process ancestry to name the host app,
 * and its `target` says which session it's attached to. Best-effort — returns
 * an empty list on any failure (no server, foreign socket) so the renderer
 * degrades to "detached".
 */
export declare function listClients(socket?: string): Promise<TmuxClient[]>;
/** A dead pane's exit status, read from tmux while the pane lingers under remain-on-exit. */
export interface PaneExit {
    /** False when the pane/socket no longer exists or tmux could not query it. */
    found: boolean;
    /** True once the process that ran in the pane has exited (pane is dead). */
    dead: boolean;
    /** Exit status of the dead pane's process, when tmux reports it. */
    status?: number;
}
/**
 * Read whether a pane's process has exited and, if so, its exit status. Used by
 * the spawn-wrap path to recover the wrapped agent's exit code after the attach
 * client returns. `found: false` distinguishes a missing pane from a living one;
 * focus must recover the former instead of attaching a retained/stale target.
 */
export declare function paneExitStatus(pane: string, socket?: string): Promise<PaneExit>;
/**
 * Bind a per-session hook. Used by the spawn-wrap path to install a `pane-died`
 * hook that detaches the attach client the instant the wrapped agent exits (the
 * global `remain-on-exit on` otherwise leaves the client staring at a dead pane).
 * Best-effort — returns false when tmux rejects the hook so callers do not
 * stamp a schema marker and daemon reconciliation can retry later.
 */
export declare function setSessionHook(name: string, hook: string, command: string, socket?: string, timeoutMs?: number): Promise<boolean>;
/**
 * Schema version of the `pane-died` hook installed on managed `agents run`
 * sessions. Bump whenever the hook's SHAPE changes so the daemon reconcile
 * (reconcileSessionHooks) knows to re-stamp live sessions a prior binary left on
 * an older shape.
 *   v1 — the original unconditional `detach-client`: ANY pane death (including a
 *        user exiting a split they opened) tore down the whole client.
 *   v2 — `#{hook_pane}`-guarded: only the AGENT pane dying detaches; a user
 *        split's death runs `kill-pane`, closing just that split.
 *   v3 — the else-branch pins its target via
 *        `run-shell "tmux -S <socket> kill-pane -t #{hook_pane}"`. Untargeted
 *        kill-pane resolves "current pane" inside the hook context, which goes
 *        nondeterministic on a loaded detached server (CI flake #965: the dead
 *        split survived as a husk); run-shell format-expands its command at
 *        fire time, so the event pane is always the target.
 *   v4 — `run-shell -C "kill-pane -t #{hook_pane}"` executes the targeted command
 *        in the tmux server instead of launching a second tmux client against
 *        the same socket from inside the hook. That self-client could race the
 *        server under load and leave the dead split behind.
 *   v5 — `run-shell -b -C "kill-pane -t #{hook_pane}"` runs the targeted kill-pane
 *        in the background inside the server command queue. The synchronous
 *        variant could stall the hook on a loaded CI runner, letting the dead
 *        split survive until the test's wait timeout expired (flake in CI shard
 *        3: pane-guarded pane-died hook / user split exit).
 *   v6 — the agent-pane branch is now `session_attached`-aware: with a client
 *        attached it `detach-client`s (the interactive path, so runInTmux's
 *        attach returns and reads the exit status, EXEC-23b unchanged); with NO
 *        client it `kill-session`s outright. A `remain-on-exit` pane with no
 *        attach client to reap it otherwise lingers as a dead husk until the
 *        daemon's periodic `reapDeadTmuxPanes` sweep — so a run whose terminal
 *        was closed, or a `/exit` after the user detached, left an idle-looking
 *        dead session on the socket. Killing it the instant the agent exits is
 *        the "one close tears down every layer" contract (#5a).
 */
export declare const AGENT_HOOK_SCHEMA = 6;
/**
 * The guarded `pane-died` hook. When the AGENT pane dies, tear the whole session
 * down — but HOW depends on whether a client is attached:
 *   - attached (`#{session_attached}`): `detach-client` so the blocking attach in
 *     runInTmux returns and `resolveAfterAttach` reads the exit status off the
 *     still-`remain-on-exit` dead pane, then kills the session (EXEC-23b, unchanged);
 *   - no client: `kill-session` outright. Without this the dead pane sits under
 *     `remain-on-exit` as an idle-looking husk until the daemon's periodic
 *     `reapDeadTmuxPanes` sweep — the "second exit" / orphaned-idle session bug
 *     when a wrapped agent exits with nobody attached (a closed terminal, or a
 *     `/exit` after the user detached). `detach-client` on an unattached session
 *     is a no-op, so the old hook left the husk; `kill-session` ends it at once.
 * A user split's death still runs the else-branch, closing just that split via
 * `run-shell -b -C` with an explicit `-t #{hook_pane}` target: tmux format-expands
 * the command at fire time and executes it inside the server command queue in the
 * background, so the event pane is always the one killed without launching a second
 * tmux client against the same socket and without stalling the hook on a loaded
 * runner. Single source of truth: both the spawn-wrap (exec.ts) and the daemon
 * reconcile build the hook here, so the two can never drift.
 */
export declare function agentPaneDiedHook(sessionName: string, agentPane: string): string;
/** Stamp a session's hook-schema marker to the current version. */
export declare function markSessionHookSchema(name: string, socket?: string, timeoutMs?: number): Promise<void>;
/**
 * The outcome of {@link prepareSessionForResume}. `attach` carries the pane it
 * positively resolved so the caller can read that pane's exit status back after
 * its attach client returns — without that, a resume-attach has no handle to ask
 * tmux what happened and can only assume success (EXEC-23b).
 */
export type ResumePreparation = {
    decision: 'attach';
    pane: string;
} | {
    decision: 'create';
};
/**
 * Decide whether a native resume may attach an existing managed tmux session.
 * Only a positively resolved, living agent pane is reusable. Metadata-less
 * legacy sessions and stale metadata resolve their real first pane through
 * tmux before the decision; a dead or unreadable session is reaped so the
 * caller can create a fresh wrapper for the resumed harness.
 */
export declare function prepareSessionForResume(name: string, socket?: string): Promise<ResumePreparation>;
/**
 * Retrofit the current guarded `pane-died` hook onto every managed `agents run`
 * session whose hook predates AGENT_HOOK_SCHEMA. Idempotent and NON-DESTRUCTIVE:
 * it only `set-hook`s (never kills a pane or detaches a client), so a long-lived
 * shared server started by a pre-fix binary — whose still-running sessions carry
 * the old unconditional hook that kicked the user out of the whole view when they
 * exited a split — self-heals in place, without waiting for those agents to exit
 * or for the server to be recycled.
 *
 * NOT a daemon poll — the 5-minute `tmux-reconcile` routine that used to call this
 * was deleted (RUSH-2495). It survives as the version-skew ONE-SHOT: the daemon
 * calls it once at startup (`runDaemon` in `../daemon.js`) and once from the
 * upgrade-time migration (`runMigration` in `../migrate.js`), so a session a
 * pre-fix binary left with a stale hook still self-heals without a live poller.
 * `ensureSessionHookRepaired` covers the steady-state gap between those two
 * points by repairing a single session right before it's attached to. The
 * per-session `@ag_hook_schema` marker makes both paths a cheap no-op once a
 * session is current. Only run-wrapped sessions (`ag-` prefix) are touched — an
 * externally-created session on the socket keeps whatever hook it set.
 */
export declare function reconcileSessionHooks(socket?: string): Promise<{
    scanned: number;
    reconciled: number;
}>;
/**
 * Repair a single managed session's `pane-died` hook right before it is
 * attached to, so a legacy session left by a pre-fix binary self-heals on the
 * next touch instead of waiting for the next daemon-startup/upgrade one-shot
 * (RUSH-2435). Only `ag-`-prefixed (run-wrapped) sessions are touched; a no-op
 * for anything else. Best-effort — never throws, so a repair failure can never
 * block the attach it is protecting.
 */
export declare function ensureSessionHookRepaired(name: string, socket?: string): Promise<void>;
/**
 * List live sessions on the socket. Reconciles meta JSONs against tmux's view:
 *  - tmux session with no meta → returned without `meta` (external session)
 *  - meta file with no tmux session → meta deleted (stale)
 */
export declare function listSessions(opts?: {
    socket?: string;
    timeoutMs?: number;
}): Promise<ListedSession[]>;
export interface SplitOptions {
    name: string;
    direction: 'h' | 'v';
    cmd?: string;
    cwd?: string;
    socket?: string;
}
/**
 * Split the active pane of a session. Returns the new pane's tmux pane id
 * (e.g. `%3`) so callers can target it later via `send`/`capture`.
 */
export declare function splitPane(opts: SplitOptions): Promise<string>;
export interface SendOptions {
    name: string;
    /** Pane id (e.g. `%2`) or pane index (`0`, `1`) — appended after the session name when targeting a specific pane. */
    pane?: string;
    /** The literal characters to type into the pane. */
    keys: string;
    /** When true, do NOT append Enter at the end. */
    noEnter?: boolean;
    /**
     * When true, treat `keys` as a single literal string (-l flag). When false
     * (default), tmux interprets named keys like `C-c`, `Enter`, `Escape`.
     */
    raw?: boolean;
    socket?: string;
}
/** Send keystrokes to a session's active pane (or a specific pane via :pane). */
export declare function sendKeys(opts: SendOptions): Promise<void>;
export interface CaptureOptions {
    name: string;
    pane?: string;
    /** Number of lines from history to include (default: visible screen only). */
    lines?: number;
    /** Keep ANSI escape sequences (default strips them). */
    ansi?: boolean;
    socket?: string;
}
/** Capture pane contents as a string. The cleaned form is what humans see. */
export declare function capturePane(opts: CaptureOptions): Promise<string>;
/** Read a session's provenance JSON, if present. */
export declare function readSessionMeta(name: string): SessionMeta | null;
