/**
 * agent_bridge — headless lease handoff (T-CHAT-AS-TERMINAL CAT.5).
 *
 * North star (invariant #7): when an umbrella has NO live human session, the
 * always-on agent-bridge daemon runs HEADLESS — it holds the umbrella's chat
 * lease, resumes the SAME session, and answers chat. When a human session's
 * lease appears, the headless daemon STANDS DOWN (stops answering) but the
 * process stays alive; when the human lease goes stale again, the daemon
 * RE-ACQUIRES. Exactly one chat-answering session per umbrella → no
 * double-answer / 409.
 *
 * State machine (one umbrella):
 *
 *        ┌─────────────── acquire ────────────────┐
 *        │  (no fresh lease, or the fresh lease    │
 *        │   is already OUR headless id)           │
 *        ▼                                          │
 *   ┌─────────┐   human lease appears (fresh,     ┌─────────────┐
 *   │  HELD   │── owned by someone else) ───────▶ │  STOOD-DOWN  │
 *   │ (ours)  │                                   │ (not ours)   │
 *   └─────────┘ ◀── that lease goes STALE ─────── └─────────────┘
 *        │ heartbeat (30s, fs touch) while HELD        │ reclaimCheck (60s) re-attempts acquire
 *        ▼                                              ▼ when the foreign lease lapses
 *
 * ZERO IDLE TOKEN COST: the heartbeat is a pure `refreshLease` (one fs write).
 * There is NO keep-warm / ping loop here and none anywhere in the bridge — the
 * only model call happens on a real inbound (the dispatcher), never on the
 * heartbeat/reclaim ticks. The process simply parks between turns.
 *
 * Holder identity: the headless session id is `headless-<umbrellaId>` — stable
 * across the daemon's lifetime so (a) the lease's `session_id` reliably tells
 * "ours vs theirs" for the double-holder guard, and (b) subscription resume
 * (`--resume headless-<umbrellaId>`) always threads the SAME claude session.
 *
 * Imports from: node:timers, ../chat/live_session_lease, ../paths.
 * Imported by: ./daemon.ts (wires acquire + start on daemon start, stop on
 *   shutdown), tests.
 */
/** Heartbeat cadence — refresh OUR lease so a live `chat watch` never sees us
 *  as stale. Matches the chat-watch HEARTBEAT_MS (well under STALE_MS=90s). */
export declare const HEADLESS_HEARTBEAT_MS = 30000;
/** Reclaim cadence — re-check whether a foreign lease has gone stale so we can
 *  re-acquire. Folded into the same ticker as the heartbeat for simplicity. */
export declare const HEADLESS_RECLAIM_MS = 60000;
/** Stable headless holder id for an umbrella. */
export declare function headlessSessionId(umbrellaId: string): string;
export interface HeadlessLeaseManagerOptions {
    umbrellaId: string;
    /** Injected timers (tests). Default node setInterval/clearInterval. */
    setIntervalFn?: (handler: () => void, ms: number) => ReturnType<typeof setInterval>;
    clearIntervalFn?: (handle: ReturnType<typeof setInterval>) => void;
    /** Injected clock (tests). Default `() => new Date()`. */
    now?: () => Date;
    /** Heartbeat/reclaim cadence override (tests). Default HEADLESS_HEARTBEAT_MS. */
    tickMs?: number;
    /** Structured warn sink for lease-io failures. Default no-op. */
    onWarn?: (message: string) => void;
}
/**
 * Owns the headless lease lifecycle for ONE umbrella. The daemon constructs
 * one, `start()`s it (acquire-if-free + begin the heartbeat ticker), and
 * `stop()`s it on shutdown (release our hold + stop the ticker). The dispatcher
 * reads the SAME lease file (ownership-aware) at flush time — this manager
 * never gates the dispatcher directly; it only keeps the on-disk lease correct.
 */
export declare class HeadlessLeaseManager {
    private readonly umbrellaId;
    private readonly leasePath;
    private readonly sessionId;
    private readonly setIntervalFn;
    private readonly clearIntervalFn;
    private readonly now;
    private readonly tickMs;
    private readonly warn;
    private timer;
    /** True while WE currently hold the lease (our id, fresh). Drives whether
     *  the heartbeat refreshes (HELD) vs. the reclaim re-attempts (STOOD-DOWN). */
    private held;
    private stopped;
    constructor(opts: HeadlessLeaseManagerOptions);
    /** This daemon's stable headless session id (`headless-<umbrellaId>`). */
    get headlessId(): string;
    /** True iff we currently hold the lease. */
    get isHolding(): boolean;
    /**
     * Try to acquire the lease IFF it is free for us. Returns true ⇒ we now hold
     * (the on-disk lease carries our headless id). Returns false ⇒ a DIFFERENT
     * fresh lease exists (human or other) → we stand down.
     *
     *   - no fresh lease (absent / stale)                → writeLease(ours) + true
     *   - fresh lease ALREADY ours (idempotent re-acq)   → refresh + true
     *   - fresh lease owned by someone else              → false (stand down)
     */
    acquireIfFree(): Promise<boolean>;
    /**
     * One heartbeat+reclaim tick. fs-only — NO model/SDK call.
     *   - HELD (our lease still fresh + ours)        → refresh the timestamp.
     *   - HELD but the lease flipped to someone else → stand down (stop holding).
     *   - NOT held + the foreign lease is now stale  → re-acquire.
     *   - NOT held + a fresh foreign lease remains   → stay stood-down.
     */
    tick(): Promise<void>;
    /** Acquire-if-free, then start the heartbeat/reclaim ticker. Idempotent. */
    start(): Promise<void>;
    /**
     * Stop the ticker and RELEASE our hold (best-effort) so a freshly-opened
     * terminal isn't blocked by a stale headless lease on clean shutdown. We
     * only remove the lease if it is still OURS — never clobber a human's lease.
     * Idempotent.
     */
    stop(): Promise<void>;
}
//# sourceMappingURL=headless_lease.d.ts.map