/**
 * ProactiveSwapMonitor — the PRE-LIMIT half of the Subscription & Auth Standard's
 * continuity guarantee (P1.3).
 *
 * The reactive path (`autoSwapOnRateLimit`) only fires AFTER a session hits its
 * account's wall: a RateLimitSentinel escalation on an ACTUAL throttle signal.
 * So a session blips at the wall before recovering — and an UNTAGGED session
 * (one with no `subscriptionAccountId`, e.g. the primary interactive session
 * running on the default config) has nothing for the swap engine to grab, so it
 * just wedges until an operator manually swaps the default login. That is the
 * exact 2026-06-09 failure that motivated this monitor.
 *
 * This monitor moves a session OFF an account BEFORE it walls, at a lag-aware
 * measured threshold below the real limit.
 *
 * ── Why a separate, lower threshold (lag-aware) ──
 * The QuotaPoller reads utilization periodically, so our reading TRAILS the real
 * usage (measured live: our ~90% == Anthropic's ~95% on the same account). A
 * pre-emptive swap therefore triggers at a LOWER measured threshold (default 80)
 * to leave margin for the lag — by the time we read 80%, real is higher, and the
 * swap completes before the wall.
 *
 * ── Effective-account resolution (covers the session you actually use) ──
 * A session carries `subscriptionAccountId` only if it was pinned at spawn. The
 * primary interactive session usually runs on the DEFAULT config (untagged), so
 * the monitor resolves an untagged session's effective account from the default
 * config's live login (InUseAccountResolver). Without this, the session a user
 * is actively in would be invisible to the swap engine and wedge at the wall.
 *
 * ── Bounded, non-storming ──
 * Per evaluation: only accounts AT pressure that have a sub-threshold ALTERNATE
 * are sources; candidates are sorted newest-first (the just-(re)started
 * interactive session ranks first) and capped per cycle; each swapped session
 * enters a cooldown so a slow restart isn't double-swapped. Near the wall the
 * monitor triggers a fresh poll so a fast burn isn't missed between the
 * low-frequency baseline polls.
 *
 * Gated OFF by default (moving live sessions is real authority — same authority
 * as `autoSwapOnRateLimit`, just an earlier trigger). The decision core is pure
 * (injected deps) so it tests with zero sessions and zero network.
 */
import type { SubscriptionAccount } from './SubscriptionPool.js';
/** A running, swap-eligible session as the monitor sees it. */
export interface ProactiveSwapSession {
    /** tmux session name (what the swap path keys on). */
    sessionName: string;
    /** The pool account this session is tagged with, or null if untagged
     *  (untagged ⇒ running on the default config ⇒ resolved via the default login). */
    accountId: string | null;
    /** ISO start time — newest-first ordering proxy for "most recently active". */
    startedAt?: string;
}
/** The shape returned by the injected swap (a subset of QuotaAwareScheduler.SwapResult). */
export interface ProactiveSwapOutcome {
    swapped: boolean;
    toAccountId: string | null;
}
export interface ProactiveSwapMonitorConfig {
    /** Current pool accounts (e.g. () => pool.list()). */
    listAccounts: () => SubscriptionAccount[];
    /** Currently-running, swap-eligible (claude-code) sessions. */
    listRunningSessions: () => ProactiveSwapSession[];
    /** The pool account the DEFAULT config is logged into right now (or null).
     *  Untagged sessions run here; from InUseAccountResolver in production. */
    resolveDefaultAccountId: () => Promise<string | null>;
    /** Performs the actual swap (wraps QuotaAwareScheduler.onQuotaPressure). */
    swap: (args: {
        sessionName: string;
        exhaustedAccountId: string;
        nowMs: number;
    }) => Promise<ProactiveSwapOutcome>;
    /** Optional fresh-poll trigger, awaited when an account is in the watch zone. */
    triggerPoll?: () => Promise<unknown>;
    /** Measured binding-window utilization % that triggers a pre-emptive swap. Default 80. */
    thresholdPct?: number;
    /** When an at-risk account is within this many points of the threshold, the
     *  monitor refreshes the poll before deciding (so a fast burn isn't missed
     *  between baseline polls). Default 15 (i.e. watch zone starts at 65%). */
    watchMarginPct?: number;
    /** Max sessions swapped per evaluation cycle (storm guard). Default 3. */
    maxSwapsPerCycle?: number;
    /** Per-session cooldown after a successful swap before it's eligible again.
     *  Default 600000 (10m) — must exceed the swap+restart time. */
    cooldownMs?: number;
    /** Monitor tick cadence. Default 180000 (3m). */
    tickMs?: number;
    /** Injected for tests. */
    now?: () => number;
    logger?: {
        log: (m: string) => void;
        warn: (m: string) => void;
    };
}
export interface ProactiveSwapTickResult {
    /** Session names that were actually swapped this pass. */
    swapped: string[];
    /** How many sessions were eligible (at-pressure, has-alternate, off-cooldown). */
    considered: number;
    /** Whether a fresh poll was triggered before the decision (watch zone). */
    refreshed: boolean;
}
export declare class ProactiveSwapMonitor {
    private readonly cfg;
    private readonly thresholdPct;
    private readonly watchMarginPct;
    private readonly maxSwapsPerCycle;
    private readonly cooldownMs;
    private readonly tickMs;
    private readonly now;
    private readonly logger;
    /** Last successful-swap timestamp per session (cooldown bookkeeping). */
    private readonly lastSwapAt;
    private timer;
    private ticking;
    private lastResult;
    constructor(cfg: ProactiveSwapMonitorConfig);
    start(): void;
    stop(): void;
    /** Status for the read route (never throws). */
    status(): {
        thresholdPct: number;
        watchPct: number;
        maxSwapsPerCycle: number;
        cooldownMs: number;
        tickMs: number;
        running: boolean;
        lastResult: ProactiveSwapTickResult | null;
    };
    /**
     * One monitor pass: if any at-risk account is in the watch zone, refresh the
     * quota poll first (fresh data near the wall), then evaluate + swap.
     */
    tick(): Promise<ProactiveSwapTickResult>;
    /**
     * Pure-ish decision + swap on the CURRENT snapshots (no poll refresh). Exposed
     * for tests and the on-demand check route.
     */
    evaluate(): Promise<{
        swapped: string[];
        considered: number;
    }>;
    /**
     * Map running sessions to candidates whose EFFECTIVE account is at/over minPct.
     * Effective account = the session's tag, else the default-config login (so the
     * untagged interactive session is visible). Resolves the default login once.
     */
    private mapCandidates;
}
//# sourceMappingURL=ProactiveSwapMonitor.d.ts.map