import { type Host } from './types.js';
/** ssh's connection-layer failure code — re-exported from ssh-exec.ts. */
export declare const SSH_CONN_FAILURE = 255;
/**
 * ssh returns 255 for BOTH "couldn't connect" and "connected then dropped", so a
 * remote-origin 255 (the focus command's own exit) is remapped to 254 before the
 * reconnect loop sees it — inside the loop, 255 therefore always means a network
 * drop, never a code the remote command chose.
 */
export declare const REMOTE_EXIT_255_REMAPPED = 254;
/**
 * Wall-clock window bounding unproductive reconnect streaks, not the total
 * session. A reattach that reaches the host and holds resets the budget.
 */
export declare const RECONNECT_WINDOW_MS: number;
/**
 * A genuine reconnection must hold the remote pane this long before refilling
 * the retry budget. It is the minimum time to clear TTY negotiation and confirm
 * a working session, distinguishing that from an attach that reconnects and
 * immediately re-drops; otherwise a flapping link would retry forever.
 */
export declare const MIN_HOLD_MS = 10000;
export interface ReconnectState {
    /** Consecutive unproductive reattaches since the last genuine reconnection. */
    attempt: number;
    /** Ms burned on the current unproductive streak; compared to the window. */
    unproductiveMs: number;
}
export interface ReconnectOutcome {
    /** Exit code of the run or last re-attach. */
    code: number;
    /** Whether the ssh handshake completed (preflight probe succeeded). */
    connected: boolean;
    /** How long the interactive attach held after the probe returned. */
    heldMs: number;
}
export type ReconnectDecision = {
    action: 'stop';
    code: number;
} | {
    action: 'retry';
    waitMs: number;
    state: ReconnectState;
    remainingMs: number;
};
export declare function initialReconnectState(): ReconnectState;
/** Exponential backoff capped at {@link MAX_BACKOFF_MS}: 2s, 4s, 8s, 16s, 30s… */
export declare function backoffMs(attempt: number): number;
/** True when the attempt both connected and held long enough to refill the budget. */
export declare function refillsBudget(outcome: ReconnectOutcome): boolean;
/**
 * Decide the next action from a run/re-attach outcome. Non-255 exits stop and
 * surface that code; 255 retries until the unproductive window is spent.
 */
export declare function reconnectStep(state: ReconnectState, outcome: ReconnectOutcome): ReconnectDecision;
/** "14m51s", "51s" — a duration a waiting human can read at a glance. */
export declare function formatDuration(ms: number): string;
/** Notice shown before each reconnect wait. */
export declare function reconnectNotice(target: ReconnectTarget, host: string, attempt: number, waitMs: number, remainingMs: number): string;
/** Notice shown once the retry budget is spent on an unreachable host. */
export declare function exhaustedNotice(target: ReconnectTarget, host: string): string;
/** Notice shown when the host reconnects but drops again within {@link MIN_HOLD_MS}. */
export declare function unstableNotice(target: ReconnectTarget, host: string): string;
/** Notice shown when a reattach ends with a remapped remote-side exit. */
export declare function remoteExitNotice(target: ReconnectTarget, host: string): string;
/** Notice shown when the user stops the wait with Ctrl-C. */
export declare function interruptedNotice(target: ReconnectTarget, host: string): string;
/**
 * Wrap `cmd` in `bash -lc` with an exit-code remap: 255 becomes
 * {@link REMOTE_EXIT_255_REMAPPED} so a remote-origin 255 cannot pass as ssh drop.
 */
export declare function wrapRemoteExitCode(cmd: string): string;
/**
 * How a dropped run is named when we go back for it.
 * `launch` ids are minted locally before the connection exists, so they survive a drop.
 */
export type ReconnectTarget = {
    kind: 'session';
    id: string;
} | {
    kind: 'launch';
    id: string;
};
/** The short form shown to a human. Both kinds are uuid-shaped, so 8 chars reads the same. */
export declare function targetLabel(target: ReconnectTarget): string;
/**
 * The command a user can run to re-enter a session after reconnect gives up.
 * The full id is printed on its own line so it remains copyable after a drop.
 */
export declare function recoveryHint(target: ReconnectTarget, host: string): string;
/**
 * Notice shown when an interactive remote connection ends and no auto-reconnect
 * follows. Prints the session id/handle so the user can get back in.
 */
export declare function connectionEndedNotice(target: ReconnectTarget, host: string, opts?: {
    dropped?: boolean;
}): string;
/**
 * Banner printed as an interactive `--device` run takes the TTY, so the id is
 * visible in scrollback while the connection exists.
 */
export declare function connectionStartedNotice(target: ReconnectTarget, host: string): string | undefined;
/**
 * The id to print as an interactive `--device` stream starts. `run auto` is
 * excluded because its forwarded id is only real when the remote picks Claude.
 */
export declare function startConnectionTarget(opts: {
    agent: string;
    hostSessionId?: string;
    resumeId?: string;
}): ReconnectTarget | undefined;
/**
 * Decide what happens after an interactive `--device` stream returns. Auto-
 * reconnect fires on 255 unless `--raw` opted out; otherwise print recovery info.
 */
export declare function afterInteractiveRemoteExit(opts: {
    target?: ReconnectTarget;
    host: string;
    exitCode: number;
    /** True when auto-reconnect will take over (tmux-hosted, not `--raw`). */
    willReconnect: boolean;
}): {
    reconnect: boolean;
    notice: string | undefined;
};
/** Inputs the launcher has once its interactive stream has returned. */
export interface ReconnectTargetInputs {
    agent: string;
    sessionId?: string;
    resolvedId?: string;
    resumeId?: string;
    launchId?: string;
}
/**
 * Choose how to name the dropped run. `launchId` is last because it is minted
 * locally before the connection exists, so it survives the drop.
 */
export declare function pickReconnectTarget(inputs: ReconnectTargetInputs): ReconnectTarget | undefined;
/**
 * The peer's recovery verb wrapped so a remote-origin 255 cannot masquerade as
 * a network drop. No `--attach-only`: focus resumes in place when no pane survived.
 */
export declare function reattachRemoteCommand(target: ReconnectTarget): string;
/**
 * Re-attach the live remote pane by driving the peer's `agents sessions focus`.
 * A fast preflight probe decides reachability; only then do we run the
 * interactive attach/resume. Returns the exit code, connected bit, and hold time.
 */
export declare function reattachRemoteSession(host: Host, target: ReconnectTarget): ReconnectOutcome;
export interface ReconnectLoopOpts {
    host: Host;
    target: ReconnectTarget;
    /** Exit code from the initial interactive run (treated as connected). */
    initialExit: number;
    /** Test seams for the re-attach and wait. */
    reattach?: (host: Host, target: ReconnectTarget) => ReconnectOutcome;
    wait?: (ms: number) => Promise<'elapsed' | 'interrupted'>;
    write?: (s: string) => void;
}
/** Drive the reconnect loop from the initial run's outcome to a terminal code. */
export declare function reconnectInteractiveSession(opts: ReconnectLoopOpts): Promise<number>;
