/**
 * Live-session lease (Track T-DEL, DEL.1) — the cross-session arbitration
 * primitive. While `opensquid chat watch` runs for a session, it writes +
 * heartbeats a lease file; the always-on agent-bridge daemon reads it (DEL.2)
 * and stays silent while a FRESH lease exists (a live interactive session is
 * handling the work), so the two delivery paths never double-respond.
 *
 * Freshness — not mere existence — is the signal: a crashed `chat watch` leaves
 * a stale lease, and the daemon must resume once it goes stale. So `removeLease`
 * is best-effort cleanup; `isLeaseFresh` is the authority.
 *
 * KEY-AGNOSTIC (T-CHAT-AS-TERMINAL CAT.1c): the lease functions take the
 * lease-file PATH directly, not a project_uuid. This decouples the lease
 * envelope/freshness logic from how the key resolves to a path, so the chat
 * surfaces can lease per-UMBRELLA (`umbrellaLiveSessionLease(id)`) while the
 * agent-bridge daemon keeps leasing per-project (`liveSessionLease(uuid)`).
 * The caller resolves the path; this module owns only the envelope + I/O.
 *
 * Imports from: node:fs/promises.
 * Imported by: src/runtime/chat/watch_cli.ts (writer/heartbeat, umbrella path),
 *   src/runtime/chat/session_routing.ts (reader, umbrella path),
 *   src/runtime/agent_bridge/dispatcher.ts (reader, project path, DEL.2).
 */
/** A lease older than this is stale → the daemon may respond. */
export declare const STALE_MS = 90000;
/** Heartbeat cadence — comfortably under STALE_MS so a live lease never lapses. */
export declare const HEARTBEAT_MS = 30000;
export interface LiveSessionLease {
    session_id: string;
    pid: number;
    refreshed_at: string;
}
/** Resolve a stable session id for the lease (informational; freshness rules). */
export declare function resolveSessionId(env?: NodeJS.ProcessEnv): string;
export declare function writeLease(leasePath: string, sessionId: string, now?: Date): Promise<void>;
/** Refresh just the timestamp (keeps session_id/pid). */
export declare function refreshLease(leasePath: string, now?: Date): Promise<void>;
export declare function readLease(leasePath: string): Promise<LiveSessionLease | null>;
/** True iff a lease exists AND was refreshed within STALE_MS (clock-forward). */
export declare function isLeaseFresh(lease: LiveSessionLease | null, now?: Date): boolean;
/**
 * Ownership-aware freshness (T-CHAT-AS-TERMINAL CAT.5). True iff the lease is
 * fresh AND owned by `expectedSessionId`. This is the double-holder guard: a
 * headless daemon answers an inbound ONLY when it both holds a fresh lease and
 * that lease carries ITS OWN session id — never while a human (or any other)
 * session holds a fresh lease, which would double-answer (409 / mingling).
 *
 * `isLeaseFresh` alone answers "is SOMEONE live?"; this answers "are WE live?".
 */
export declare function isLeaseFreshAndOwnedBy(lease: LiveSessionLease | null, expectedSessionId: string, now?: Date): boolean;
/**
 * Acquire the lease for `sessionId` IFF it is free for us — there is no FRESH
 * lease owned by a DIFFERENT session. Returns true ⇒ we now hold it (it was
 * absent / stale / already-ours and we (re)wrote it); false ⇒ a different
 * session holds a fresh lease, so the caller stands down LOCAL-ONLY
 * (invariant #6: a second same-umbrella session gets no chat connection).
 *
 * This is the ONE arbitration primitive shared by the live interactive session
 * (claims the umbrella lease with its Claude Code session id — so its Stop-hook
 * drive owns the turn and the headless stands down) and the headless daemon
 * (claims with `headless-<umbrella>`). Fail-quiet: a write error ⇒ false.
 */
export declare function acquireLeaseIfFree(leasePath: string, sessionId: string, now?: Date): Promise<boolean>;
/** Best-effort removal on `chat watch` exit. Never throws. */
export declare function removeLease(leasePath: string): Promise<void>;
//# sourceMappingURL=live_session_lease.d.ts.map