/**
 * Watchdog rotate — in-place rotation of a rate-limited session onto a healthy
 * account/harness, inside the SAME terminal tab (one-watchdog; follow-up to
 * RUSH-2132 / PR #1875).
 *
 * When a stalled session's transcript tail shows a hard limit ("You've hit your
 * weekly limit · resets …"), the daemon watchdog rotates it instead of nudging:
 *
 *   1. DETECT  — classifyTailForRotate() matches the tail against the limit
 *      patterns (ported from apps/ext/src/core/autoRotate.ts) and parses the
 *      `resets <time>` clause when present.
 *   2. GATE    — defaultRotateGate() runs the SAME first-party selection
 *      `agents run auto` would (collectHarnessCandidates + pickHarnessWeighted,
 *      ../rotate.ts). Zero healthy → ONE `rotate` skip event per cooldown window
 *      and the terminal is left untouched. No `agents view` subprocess anywhere.
 *   3. RELAUNCH — the per-harness exit sequence (ported from apps/ext
 *      prewarm.ts PREWARM_CONFIGS) is injected, then
 *      `agents run auto --interactive --session-id <uuid>`.
 *   4. REPLAY   — when the new session's TUI is live (bounded wait, default
 *      60s), the resume replay is injected. On timeout the session is flagged
 *      and the machine stops — never blind-type into a dead shell.
 *
 * The machine spans ticks (the exit sequence kills the old session, so it drops
 * out of the active-session list before the new TUI is live): state persists at
 * <watchdog-state>/rotate/<sessionId>.json as
 * exiting → launching → awaiting-tui → replaying → done | failed.
 *
 * Config: `watchdog.rotate: on|off` in agents.yaml (default on), read per tick.
 */
import type { InjectTarget } from '../terminal/index.js';
import type { ActiveSession } from '../session/active.js';
/**
 * Agent-reported hard-limit texts, matched against a session transcript tail.
 * Ported verbatim from apps/ext/src/core/autoRotate.ts RATE_LIMIT_PATTERNS —
 * kept specific on purpose: a transcript carries prose, so a loose "rate limit"
 * match would rotate terminals whose agent merely DISCUSSED limits. The first
 * two patterns cover the weekly/session variants, including claude's
 * "You've hit your weekly limit · resets <time>" form.
 */
export declare const ROTATE_LIMIT_PATTERNS: RegExp[];
export type RotateTailVerdict = {
    kind: 'none';
} | {
    kind: 'rate_limited';
    resetsAtMs?: number;
};
/**
 * Classify a transcript tail for the rotate decision: does it show a hard
 * account limit (rotate this session) or not (leave it to the nudge path)?
 * Unlike the retired extension path there is NO `no healthy` tail parsing here —
 * the health gate is a first-party function call (defaultRotateGate), not a
 * cross-package string contract.
 */
export declare function classifyTailForRotate(tailLines: string[], nowMs: number): RotateTailVerdict;
/**
 * Parse the `resets <time>` clause of a limit line into an epoch-ms horizon.
 * Ported from apps/ext/src/core/autoRotate.ts parseResetTimeMs (behavior
 * verbatim): the ISO form (milliseconds + Z) is matched EXPLICITLY and first —
 * a generic capture stops at the milliseconds dot and drops the Z, which makes
 * Date.parse read LOCAL time (the suppression would end hours off). Time-of-day
 * forms like `7am` / `7:30pm` with an optional `(Area/City)` IANA zone cover
 * claude's own limit text. Returns undefined when no usable reset is present or
 * the parsed time is already past (caller falls back to its default cooldown).
 */
export declare function parseRotateResetMs(text: string, nowMs: number): number | undefined;
/**
 * Clean-exit key sequences per harness, ported verbatim from apps/ext
 * prewarm.ts PREWARM_CONFIGS. Injected as RAW BYTES with no trailing Enter — a
 * literal \x03 written to the pty IS Ctrl+C (SIGINT), \x1b IS Esc. claude's Ink
 * TUI needs the Esc first to leave any open mode before the interrupt pair.
 */
export declare const ROTATE_EXIT_SEQUENCES: Record<string, string[]>;
/** Unknown harnesses get the common denominator: Ctrl+C twice. */
export declare const DEFAULT_ROTATE_EXIT_SEQUENCE: string[];
export declare function exitSequenceFor(agent: string): string[];
/**
 * The rotate relaunch, typed into the same tab: full auto — the CLI resolves
 * host (affinity) → harness (cross-harness headroom) → account (balanced) and
 * exits nonzero when every layer is exhausted. Ported from apps/ext
 * autoRotate.ts buildAutoRotateLaunchCommand. A terminal on a REMOTE device
 * rotates ON that device (`--device`); a local terminal omits it. `--session-id`
 * is honored only when the CLI picks claude (existing claude-only semantics)
 * and ignored otherwise — passing it unconditionally keeps the terminal's
 * AGENT_SESSION_ID aligned with the session Claude actually creates.
 */
export declare function buildRotateLaunchCommand(opts: {
    host?: string;
    sessionId: string;
}): string;
/**
 * The harness-agnostic replay injected once the new TUI is live: load the OLD
 * session's transcript, assess, continue. This is the same instruction shape
 * the CLI's own `continue` flow uses.
 */
export declare function buildRotateReplayText(oldSessionId: string): string;
export type RotatePhase = 'exiting' | 'launching' | 'awaiting-tui' | 'replaying' | 'done' | 'failed';
/** Persisted at <watchdog-state>/rotate/<sessionId>.json — keyed by the OLD session id. */
export interface RotateState {
    /** The OLD (rate-limited) session id — the file key and the replay target. */
    sessionId: string;
    /** The id passed to `--session-id` on the relaunch. */
    newSessionId: string;
    /** The harness that was rate-limited (drives the exit-sequence table). */
    agent: string;
    phase: RotatePhase;
    /** The resolved inject target — serializable, so the sweep can replay without re-resolving. */
    target: InjectTarget;
    /** Remote device the terminal lives on, when provenance says ssh. */
    host?: string;
    /**
     * The old session's cwd — correlates the readiness fallback: a fresh active
     * session only counts as the relaunched TUI when it runs in the SAME project.
     */
    cwd?: string;
    /**
     * The machine the old session runs on (provenance host = os.hostname()) —
     * the second half of the readiness-fallback correlation, so a fresh session
     * on ANOTHER box never satisfies it.
     */
    machineHost?: string;
    startedAtMs: number;
    updatedAtMs: number;
    /** awaiting-tui deadline: startedAtMs + readiness budget. */
    deadlineMs: number;
    error?: string;
    /**
     * Set on the transition to `failed`: the tick will not re-begin a rotate for
     * this session until then (default +15m). Without it a session whose old TUI
     * ignored the exit sequence re-enters begin → deadline → failed every tick.
     */
    suppressUntilMs?: number;
}
/** Bounded wait for the relaunched TUI to come live (readiness). */
export declare const DEFAULT_ROTATE_READINESS_MS = 60000;
/** Zero-healthy skip cooldown when neither the gate nor the tail carries a reset. */
export declare const DEFAULT_ROTATE_SKIP_COOLDOWN_MS: number;
/** Retry cooldown after a FAILED rotate — honored at begin via the state file. */
export declare const DEFAULT_ROTATE_FAILED_COOLDOWN_MS: number;
export declare function rotateStatePath(dir: string, sessionId: string): string;
export declare function readRotateState(dir: string, sessionId: string): RotateState | null;
export declare function writeRotateState(dir: string, state: RotateState): void;
export declare function clearRotateState(dir: string, sessionId: string): void;
/** A phase the machine still has work to do in (done/failed are terminal). */
export declare function isInflightPhase(phase: RotatePhase): boolean;
/** Every persisted rotate state (any phase) — for `watchdog status`. */
export declare function listRotateStates(dir: string): RotateState[];
/** In-flight rotates only — the set a tick's sweep must advance. */
export declare function listInflightRotates(dir: string): RotateState[];
/**
 * One `rotate` skip event per cooldown window, tracked as
 * <watchdog-state>/rotate-skips.json: { [sessionId]: suppressUntilMs }. A skip
 * inside the window logs nothing and touches nothing.
 */
export declare function readRotateSkipLedger(dir: string): Record<string, number>;
/** True when a skip for this session is OUTSIDE its suppression window (log it). */
export declare function shouldLogRotateSkip(dir: string, sessionId: string, nowMs: number): boolean;
/** Suppress further skip events for this session until suppressUntilMs. */
export declare function recordRotateSkip(dir: string, sessionId: string, suppressUntilMs: number): void;
/**
 * `watchdog.rotate` in agents.yaml (default ON — it is safe now: the health gate
 * is first-party and the readiness wait is bounded). Read fresh per tick so a
 * flip mid-run is honored on the next pass.
 */
export declare function isWatchdogRotateEnabled(): boolean;
/**
 * Persist `watchdog.rotate: on|off`. Called by the `agents watchdog rotate
 * on|off` subcommand (commands/watchdog.ts) — the rotate-only switch the
 * Factory migration uses so a user who opted out of autoRotate keeps nudging
 * (rather than `agents watchdog disable`, which disables the whole watchdog here).
 */
export declare function setWatchdogRotateEnabled(on: boolean): void;
export interface RotateGateResult {
    /** True when at least one harness has a healthy account to rotate INTO. */
    healthy: boolean;
    /** Earliest future window reset across all candidates, when any snapshot carries one. */
    resetsAtMs?: number;
    /** Human detail for the skip event (the zero-healthy error text). */
    detail: string;
}
/**
 * The first-party health gate: run the SAME selection `agents run auto` would —
 * collectHarnessCandidates over every installed harness, pickHarnessWeighted.
 * Zero healthy → the caller suppresses rotation until earliestResetAcross (or
 * the parsed tail reset, or the default cooldown) and leaves the terminal alone.
 * No `agents view` subprocess, no Keychain probe: collection is cache-only
 * (collectRunCandidates reads daemon-written snapshots, readOnly).
 */
export declare function defaultRotateGate(): Promise<RotateGateResult>;
/**
 * Default TUI-liveness probe for the relaunched session: the new session's
 * transcript resolves under any known harness layout. `--session-id` is honored
 * on a claude pick; for other harnesses the runner's readiness check ALSO
 * accepts a fresh active session (started after the rotate began), so a codex
 * pick with an unknown id is still detected.
 */
export declare function defaultRotateTranscriptLive(newSessionId: string): boolean;
/**
 * The readiness FALLBACK correlation. A fresh active session counts as the
 * relaunched TUI only when ALL of these hold:
 *   - it is not the old session and started at/after the rotate began;
 *   - it runs in the SAME cwd (trailing-slash normalized); and
 *   - it runs on the SAME machine (provenance host = os.hostname(), the same
 *     field provenance.ts populates).
 * An unrelated fresh session — another project, another host, a remote
 * teammate — must NEVER satisfy readiness: on a busy fleet box an
 * uncorrelated "any new session" match fires on the first sweep regardless of
 * whether the relaunch came up, and when `agents run auto` failed loud after
 * the gate that types the replay into a bare shell. When the state lacks cwd
 * or host the fallback cannot correlate and only the transcript probe counts.
 */
export declare function isCorrelatedRelaunch(state: RotateState, s: ActiveSession): boolean;
/**
 * The default TUI-liveness probe. The new-session-id transcript is PRIMARY (a
 * claude pick honors `--session-id`); the correlated fresh-session fallback
 * (isCorrelatedRelaunch) covers non-claude picks whose id we can't know a
 * priori.
 */
export declare function defaultTuiLiveFor(state: RotateState, sessions: ActiveSession[]): boolean;
