/**
 * SessionRecovery — Mechanical session crash/stall recovery via JSONL analysis.
 *
 * A fast, deterministic layer that runs BEFORE the LLM-powered TriageOrchestrator.
 * Detects three failure modes without any LLM calls:
 * 1. Tool call stalls (process alive but frozen mid-tool)
 * 2. Crashes (process dead with incomplete JSONL)
 * 3. Error loops (same error repeated 3+ times)
 *
 * Recovery strategy: truncate JSONL to safe point + respawn with recovery prompt.
 * Escalation ladder: last_exchange → last_successful_tool → n_exchanges_back → alert human.
 *
 * Self-contained — no Dawn dependencies. Uses only:
 * - stall-detector.ts (pure function)
 * - crash-detector.ts (pure function)
 * - jsonl-truncator.ts (pure function)
 *
 * Part of PROP-session-stall-recovery (Instar integration)
 */
import { EventEmitter } from 'events';
import { type TruncationStrategy } from './jsonl-truncator.js';
export interface SessionRecoveryConfig {
    enabled: boolean;
    /** Max recovery attempts per session before alerting human */
    maxAttempts: number;
    /** Cooldown between recovery attempts (ms) */
    cooldownMs: number;
    /** Project directory (used to find JSONL files) */
    projectDir: string;
}
export interface RecoveryAttempt {
    lastAttempt: number;
    count: number;
}
export interface RecoveryResult {
    recovered: boolean;
    failureType: 'stall' | 'crash' | 'error_loop' | 'context_exhaustion' | null;
    strategy?: TruncationStrategy;
    attemptNumber?: number;
    message: string;
    /** True when the kill was DEFERRED because the work-check found the session
     *  alive and actively producing work (active child processes). The session
     *  was NOT killed and is NOT dead — callers MUST NOT treat this as a failed
     *  recovery of a dead session, and MUST NOT notify the user (a deferral is
     *  the system deciding the session is fine; telling the user it died is a
     *  false death report — the 2026-06-06 "conversation too long" flood). */
    deferred?: boolean;
}
export interface SessionRecoveryDeps {
    /** Check if a tmux session's Claude process is alive */
    isSessionAlive: (sessionName: string) => boolean;
    /** Get the PID of the pane in a tmux session */
    getPanePid?: (sessionName: string) => number | null;
    /**
     * Kill a tmux session — for kill-to-respawn paths this should route through
     * `SessionManager.terminateSession(id, 'session-recovery', { disposition:
     * 'recovery-bounce', finalStatus: 'killed', bypassRecoveryFlag: true })` so the
     * §P3 notifier stays silent (a bounce is not a disappearance) and the kill
     * lands in the reap-log as a recovery-bounce (UNIFIED-SESSION-LIFECYCLE §P0 #8).
     * Implementations may be async; callers `await` the result.
     */
    killSession: (sessionName: string) => void | Promise<void>;
    /**
     * P1/P2 cross-check (UNIFIED-SESSION-LIFECYCLE §P0 #8): does the session's
     * tmux pane have active child processes? A JSONL stall on a process that is
     * still producing real work is almost always a false read — keep the session
     * and let the next tick re-evaluate. Undefined ⇒ check is skipped (defaults
     * to the pre-Phase-2 behavior).
     */
    hasActiveProcesses?: (sessionName: string) => boolean;
    /** Respawn a session for a topic, optionally with a recovery prompt */
    respawnSession: (topicId: number, sessionName?: string, recoveryPrompt?: string) => Promise<void>;
    /** Send a message to a topic */
    sendToTopic?: (topicId: number, message: string) => Promise<void>;
    /** Capture tmux output for a session (needed for context exhaustion detection) */
    captureSessionOutput?: (sessionName: string, lines: number) => string | null;
    /** Respawn a session fresh (no --resume) for context exhaustion recovery */
    respawnSessionFresh?: (topicId: number, sessionName?: string, recoveryPrompt?: string) => Promise<void>;
    /**
     * Non-destructive context-wall escalation: press `/compact` for a session
     * genuinely stuck at "Context limit reached · /compact or /clear to continue"
     * and verify the wall cleared. This PRESERVES the conversation (Claude
     * compacts in place) — the rung that should be tried BEFORE the destructive
     * fresh respawn. Resolves `{ cleared: true }` when the wall is gone after
     * compaction, `{ cleared: false, reason }` when compaction itself fails
     * (e.g. the conversation is too long to even compact) or times out — in
     * which case recovery falls through to the fresh respawn. Undefined ⇒ the
     * rung is skipped (pre-escalation behavior: straight to fresh respawn).
     */
    attemptCompaction?: (sessionName: string) => Promise<{
        cleared: boolean;
        reason?: string;
    }>;
    /**
     * Get recent messages for a topic (used by context-exhaustion recovery to
     * capture any in-flight agent reply that lands between detection and respawn,
     * so the fresh session can avoid duplicating it).
     */
    getRecentTopicMessages?: (topicId: number, limit: number) => Array<{
        text: string;
        fromUser: boolean;
        timestamp: string | number | Date;
    }>;
}
export declare class SessionRecovery extends EventEmitter {
    private config;
    private deps;
    private recoveryAttempts;
    private stateFilePath;
    constructor(config: Partial<SessionRecoveryConfig>, deps: SessionRecoveryDeps);
    /**
     * Check a session for mechanical failures and attempt recovery.
     * Should be called from SessionMonitor.checkSession() before LLM triage.
     *
     * @returns RecoveryResult — if recovered is true, caller should skip LLM triage
     */
    checkAndRecover(topicId: number, sessionName: string): Promise<RecoveryResult>;
    /**
     * Log a recovery event to the JSONL event log for observability.
     */
    logEvent(result: RecoveryResult, topicId: number, sessionName: string): void;
    /**
     * Aggregate recovery stats from the event log since a given timestamp.
     */
    getStats(sinceMs: number): {
        attempts: {
            stall: number;
            crash: number;
            errorLoop: number;
            contextExhaustion: number;
        };
        successes: {
            stall: number;
            crash: number;
            errorLoop: number;
            contextExhaustion: number;
        };
    };
    /**
     * Shared kill-to-respawn entry (UNIFIED-SESSION-LIFECYCLE §P0 #8).
     *  - P1/P2 cross-check first: if the tmux pane has active child processes the
     *    session is doing real work and the JSONL "stall/crash" reading is almost
     *    certainly stale — defer this recovery attempt, let the next tick re-read.
     *  - Otherwise route through the dep's killSession (which is wired in
     *    server.ts to `terminateSession(disposition:'recovery-bounce',
     *    bypassRecoveryFlag:true)` so the §P3 notifier stays silent — a bounce is
     *    not a disappearance — and the kill lands in the reap-log).
     *
     *  @returns `'killed'` on a kill we performed; `'deferred-still-working'`
     *           when the work-check vetoed the kill.
     */
    private killForRecovery;
    private recoverFromStall;
    /**
     * Recover from context exhaustion ("conversation too long").
     *
     * Unlike stall/crash recovery which truncates the JSONL and resumes,
     * context exhaustion means the ENTIRE conversation is too large.
     * Recovery strategy: kill the session and respawn FRESH with telegram
     * history as the context seed. No --resume, no JSONL reuse.
     */
    private recoverFromContextExhaustion;
    /**
     * After a context-exhausted session is killed, an in-flight reply it was
     * generating may still land in topic history a few seconds later. This
     * helper polls history for a new agent message with a timestamp after
     * detection, returning its text if found (or null if nothing landed in
     * the grace window).
     *
     * Purpose: pass the captured reply into the recovery prompt so the fresh
     * session does not re-answer a question the dying session already answered.
     * Without this, the fresh session's bootstrap snapshot is a view of history
     * that predates the dying reply, and the fresh session duplicates output.
     *
     * Grace window: 7s. Empirically, in-flight replies land 2-6s after detection;
     * 7s covers the common case without adding much latency when nothing arrives.
     * Breaks early the moment a reply is seen.
     */
    private waitForInFlightReply;
    private recoverFromCrash;
    private recoverFromErrorLoop;
    private buildStallRecoveryPrompt;
    private buildCrashRecoveryPrompt;
    private buildErrorLoopRecoveryPrompt;
    private pickTruncationStrategy;
    private shouldAttempt;
    private recordAttempt;
    /**
     * Load recovery state from disk. Prevents infinite kill-respawn loops
     * across dawn-server restarts by preserving attempt counts and cooldowns.
     */
    private loadState;
    /**
     * Persist recovery state to disk so it survives process restarts.
     */
    private saveState;
    /**
     * Find the JSONL file for a session using lsof (Strategy 1) with NO fallback.
     *
     * Previous implementation fell back to most-recently-modified JSONL which
     * could match a DIFFERENT healthy session, leading to cross-session corruption
     * during truncation. Now returns null if lsof can't identify the file —
     * better to skip recovery than corrupt the wrong session.
     */
    private findJsonlForSession;
    /**
     * Clean up old recovery tracking entries and stale .bak files.
     */
    cleanup(): void;
    /**
     * Remove .bak.* files older than maxAge from the Claude projects JSONL directory.
     * Each recovery creates a full backup — without cleanup these accumulate indefinitely,
     * consuming disk and retaining sensitive conversation data.
     */
    private cleanupBackupFiles;
}
/**
 * Sanitize text for injection into a recovery prompt.
 * Strips control characters, Unicode directional overrides, and truncates.
 */
export declare function sanitizeForPrompt(text: string, maxLength?: number): string;
//# sourceMappingURL=SessionRecovery.d.ts.map