/**
 * RateLimitSentinel — Owns the full lifecycle of riding out Anthropic's
 * server-side capacity throttle without dropping the session or burning quota.
 *
 * Scenario: Claude Code surfaces "Server is temporarily limiting requests (not
 * your usage limit) · Rate limited" (or "Repeated 529 Overloaded errors") only
 * AFTER its own internal retries (CLAUDE_CODE_MAX_RETRIES, exp backoff) are
 * exhausted. The session then sits idle with no reply relayed to the user.
 *
 * Prior behavior: SessionManager's idle-error path fired a single immediate
 * nudge — which re-hits the live throttle and burns quota — then went silent
 * until the zombie-killer reaped the session.
 *
 * What this class adds (mirrors CompactionSentinel's own-the-lifecycle pattern):
 *   1. Dedupe across the two signal triggers (watchdog poll + SessionManager
 *      idle-error emit).
 *   2. Immediate user notice — "throttled, backing off, you're not dropped".
 *   3. Backoff BEFORE re-engaging (escalating schedule) — the core quota-burn
 *      mitigation; we sit on top of Claude's already-exhausted retries.
 *   4. Neutral "continue" re-engagement (NOT the compaction-resume payload,
 *      which would falsely tell the agent its memory was reset).
 *   5. Verification — jsonl size/mtime growth = Claude processed the nudge.
 *   6. Periodic user check-ins at verify-fail transitions (min-spacing gated).
 *   7. Escalation after a capped attempts/window envelope.
 *   8. Zombie-kill veto while a recovery is in flight (isRecoveryActive).
 *   9. Bidirectional deferral with CompactionSentinel via deferIf, so the two
 *      never inject into one pane concurrently.
 *
 * The lifecycle is strictly SEQUENTIAL — at any instant a session is either
 * waiting out a backoff or waiting out a verify window, never both — so a
 * single timer slot per session is sufficient (no concurrent backoff/verify/
 * check-in timers). Check-ins are emitted synchronously at the transition.
 *
 * Decoupling: takes resumeFn + notifyFn in deps rather than importing server.ts,
 * so topic/channel routing stays out of this file (same pattern as
 * CompactionSentinel's recoverFn).
 */
import { EventEmitter } from 'node:events';
export type RateLimitTrigger = 'watchdog-poll' | 'idle-error' | string;
/**
 * The CLASS of transient API error this recovery is riding out. The lifecycle
 * (backoff → re-engage → verify → escalate) is identical across classes — only the
 * backoff SCHEDULE and the user-facing wording differ:
 *  - 'throttle'      — Anthropic capacity throttle / 529 Overloaded / rate limit.
 *                      Re-hitting it immediately burns quota, so the backoff is LONG.
 *  - 'transient-api' — a generic transient API error (500/502/503, timeout, connection
 *                      drop). These usually clear in seconds, so the first retry is
 *                      FAST and the schedule escalates more gently. Generalizing the
 *                      proven throttle-recovery lifecycle to this whole class is the
 *                      2026-05-29 future-proofing ask (topic 13481).
 */
export type ApiErrorClass = 'throttle' | 'transient-api';
export type RateLimitStatus = 'detected' | 'backing-off' | 'resuming' | 'recovered' | 'escalated';
export interface RateLimitRecoveryState {
    sessionName: string;
    trigger: RateLimitTrigger;
    /** Which transient-error class this recovery rides out (picks backoff + wording). */
    errorClass: ApiErrorClass;
    detectedAt: number;
    attempts: number;
    lastInjectAt: number;
    lastCheckInAt: number;
    baselineJsonlPath: string | null;
    baselineJsonlSize: number | null;
    baselineJsonlMtime: number | null;
    status: RateLimitStatus;
}
export interface RateLimitSentinelDeps {
    /**
     * Inject a NEUTRAL "continue" nudge into the session via the INTERNAL
     * recovery channel — never a `[telegram:N]`-prefixed user-message path. The
     * prefix would make the agent answer the nudge as if it came from the user
     * and relay that reply, contradicting the throttle notices (incoherence
     * incident 2026-06-05). Returns whether the injection was accepted.
     * Must NOT reuse the compaction-resume payload.
     */
    resumeFn: (sessionName: string) => Promise<boolean>;
    /** Route a user-facing message for this session (server.ts owns topic lookup + relay). */
    notifyFn: (sessionName: string, text: string) => Promise<void>;
    /** Project dir, to locate Claude Code JSONL files for verification. */
    projectDir: string;
    /** Resolve a session's Claude Code session UUID for exact-file jsonl lookup. */
    getClaudeSessionId?: (sessionName: string) => string | undefined;
    /**
     * Resolve a session's framework ('codex-cli' | 'claude-code' | undefined) — the value
     * carried on a running session. When it returns 'codex-cli', recovery-verification
     * reads the newest codex ROLLOUT jsonl (account-wide growth = the account-wide OpenAI
     * throttle cleared) instead of the Claude transcript. Absent/non-codex → the unchanged
     * Claude path is used (Claude behavior byte-for-byte preserved). #33.
     */
    getSessionFramework?: (sessionName: string) => string | undefined;
    /** Override $CODEX_HOME (tests). Defaults to ~/.codex. Only used for codex sessions. */
    codexHome?: string;
    /** Override ~/.gemini (tests). Defaults to ~/.gemini. Only used for gemini-cli sessions. */
    geminiHome?: string;
    /** Defer (skip starting) recovery when this returns true — e.g. compaction recovery in flight. */
    deferIf?: (sessionName: string) => boolean;
    /** Override JSONL lookup root (tests). Defaults to $HOME/.claude/projects/<project-hash>. */
    jsonlRoot?: string;
    /** Override Date.now (tests). */
    now?: () => number;
    /** Override timer setters (tests). */
    setTimer?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;
    clearTimer?: (handle: ReturnType<typeof setTimeout>) => void;
}
export interface RateLimitSentinelConfig {
    /** Master kill switch. When false, report() is a no-op. */
    enabled?: boolean;
    /**
     * #33 codex parity: enable the codex account-usage detection poll (server-side), which
     * reports throttled codex sessions into the sentinel so its (codex-aware) recovery runs
     * for codex exactly as it does for Claude. Ships DARK (default off / undefined) — the
     * Claude detection triggers are unaffected; flip on after a live codex-throttle
     * verification. Rollback = set false.
     */
    codexUsageDetection?: boolean;
    /** Escalating wait (ms) before each re-engagement attempt for a THROTTLE/rate-limit
     *  recovery. Last value repeats. */
    backoffScheduleMs?: number[];
    /** Escalating wait (ms) for a generic 'transient-api' recovery (500/502/503/timeout/
     *  connection). Shorter than the throttle schedule — these usually clear in seconds,
     *  so the first retry is fast. Last value repeats. */
    transientApiBackoffScheduleMs?: number[];
    /** Max re-engagement attempts before escalating. */
    maxAttempts?: number;
    /** Max wall-clock window (ms) a recovery may run before escalating. */
    maxWindowMs?: number;
    /** How long to wait after a nudge before checking jsonl growth. */
    verifyWindowMs?: number;
    /** Minimum spacing (ms) between user check-in messages. */
    checkInEveryMs?: number;
    /** Ignore repeat reports for the same session within this window. */
    dedupeWindowMs?: number;
}
export interface RateLimitSentinelEvents {
    'rate-limit:detected': [RateLimitRecoveryState];
    'rate-limit:resuming': [RateLimitRecoveryState & {
        backoffMs: number;
    }];
    'rate-limit:recovered': [RateLimitRecoveryState & {
        jsonlDelta: number;
    }];
    'rate-limit:escalated': [RateLimitRecoveryState & {
        reason: string;
    }];
}
export declare class RateLimitSentinel extends EventEmitter {
    private readonly deps;
    private readonly cfg;
    private readonly active;
    private readonly timers;
    private readonly recentReports;
    private deferIf?;
    constructor(deps: RateLimitSentinelDeps, config?: RateLimitSentinelConfig);
    /** Late-bind the deferral predicate (server.ts wires the two sentinels at each other). */
    setDeferIf(fn: (sessionName: string) => boolean): void;
    private now;
    private setTimer;
    private clearTimer;
    /**
     * Report a detected throttle. Called by both signal triggers. Deduped — if a
     * recovery is already active, or one was reported within the dedupe window,
     * or deferIf says another recovery owns this session, this is a no-op.
     */
    report(sessionName: string, trigger: RateLimitTrigger, opts?: {
        errorClass?: ApiErrorClass;
    }): void;
    /** Predicate for SessionManager's zombie-killer + PresenceProxy suppression. */
    isRecoveryActive(sessionName: string): boolean;
    clear(sessionName: string): void;
    stop(): void;
    getState(sessionName: string): RateLimitRecoveryState | undefined;
    /** Snapshot of all active recoveries — backs GET /rate-limit/status. */
    listActive(): Array<RateLimitRecoveryState & {
        nextBackoffMs: number;
    }>;
    private backoffFor;
    private scheduleBackoff;
    private attemptResume;
    private verify;
    private finalize;
    private notify;
    /**
     * Per-session vendor labels for user-facing throttle messages. A codex session's
     * throttle is OpenAI's, not Anthropic's — so the wording + status URL follow the
     * session's framework (#33). Non-codex returns the exact prior Claude strings, so the
     * Claude-facing messages are byte-for-byte unchanged.
     */
    private vendor;
    private readJsonlBaseline;
}
//# sourceMappingURL=RateLimitSentinel.d.ts.map