/**
 * SessionWatchdog — Auto-remediation for stuck Claude sessions (Instar port).
 *
 * Detects when a Claude session has a long-running bash command and escalates
 * from gentle (Ctrl+C) to forceful (SIGKILL + session kill). Adapted from
 * Dawn Server's SessionWatchdog for Instar's self-contained architecture.
 *
 * Escalation pipeline:
 *   Level 0: Monitoring (default)
 *   Level 1: Ctrl+C via tmux send-keys
 *   Level 2: SIGTERM the stuck child PID
 *   Level 3: SIGKILL the stuck child PID
 *   Level 4: Kill tmux session
 */
import { EventEmitter } from 'node:events';
import type { SessionManager } from '../core/SessionManager.js';
import type { StateManager } from '../core/StateManager.js';
import type { InstarConfig, IntelligenceProvider } from '../core/types.js';
export declare enum EscalationLevel {
    Monitoring = 0,
    CtrlC = 1,
    SigTerm = 2,
    SigKill = 3,
    KillSession = 4
}
interface EscalationState {
    level: EscalationLevel;
    levelEnteredAt: number;
    stuckChildPid: number;
    stuckCommand: string;
    retryCount: number;
}
export interface InterventionEvent {
    sessionName: string;
    level: EscalationLevel;
    action: string;
    stuckCommand: string;
    stuckPid: number;
    timestamp: number;
    /** Outcome tracking — filled in after a delay */
    outcome?: 'recovered' | 'died' | 'unknown';
    /** Time in ms between intervention and outcome determination */
    outcomeDelayMs?: number;
}
/** Aggregated watchdog stats for telemetry */
export interface WatchdogStats {
    interventionsTotal: number;
    interventionsByLevel: Record<string, number>;
    recoveries: number;
    sessionDeaths: number;
    outcomeUnknown: number;
    llmGateOverrides: number;
}
export interface WatchdogEvents {
    intervention: [event: InterventionEvent];
    recovery: [sessionName: string, fromLevel: EscalationLevel];
    'compaction-idle': [sessionName: string];
    'rate-limited': [sessionName: string];
}
export declare class SessionWatchdog extends EventEmitter {
    private config;
    private sessionManager;
    private state;
    private interval;
    private escalationState;
    private interventionHistory;
    private enabled;
    /** Liveness of the poll loop (GUARD-POSTURE-ENDPOINT-SPEC §2.2): 0 = never polled. */
    private lastPollAt;
    private running;
    private stuckThresholdMs;
    private pollIntervalMs;
    /** Deterministic ceiling (ms) past which a command is killed even when the LLM
     *  stuck-judge is unavailable/errors. Below it, the watchdog fails CLOSED (no
     *  interrupt) rather than fail-open. 0 disables the ceiling (pure fail-closed). */
    private hardCeilingMs;
    private logPath;
    /** Intelligence provider — gates escalation entry with LLM command analysis */
    intelligence: IntelligenceProvider | null;
    /** Temporarily exempted commands (LLM confirmed as legitimate long-running) */
    private temporaryExclusions;
    /** Counter for LLM gate overrides (said "legitimate") — for telemetry */
    private llmGateOverrides;
    /** Pending outcome checks — maps sessionName to intervention event */
    private pendingOutcomeChecks;
    /** Cooldowns for compaction-idle detection — prevents repeated emissions */
    private compactionIdleCooldowns;
    private static readonly COMPACTION_IDLE_COOLDOWN_MS;
    /** Cooldowns for rate-limited detection — prevents repeated emissions */
    private rateLimitedCooldowns;
    private static readonly RATE_LIMITED_COOLDOWN_MS;
    /**
     * Settle tracking for the throttle backstop — per session, the fingerprint of
     * the last poll's pane and when it first appeared unchanged. A throttled pane
     * that stays byte-identical across polls means the turn ended (no spinner
     * animating), i.e. the session is genuinely stuck and safe to recover.
     */
    private throttleSettle;
    /** How long a throttled pane must be byte-identical before recovery (configurable for tests/tuning). */
    private readonly rateLimitSettleMs;
    constructor(config: InstarConfig, sessionManager: SessionManager, state: StateManager);
    start(): void;
    stop(): void;
    setEnabled(enabled: boolean): void;
    isEnabled(): boolean;
    isManaging(sessionName: string): boolean;
    getStatus(): {
        enabled: boolean;
        sessions: Array<{
            name: string;
            escalation: EscalationState | null;
        }>;
        interventionHistory: InterventionEvent[];
    };
    private poll;
    private checkSession;
    /**
     * Detects sessions stuck on a server-side capacity throttle (NOT the user's
     * usage quota, NOT mid-retry) and hands recovery to the RateLimitSentinel via
     * the 'rate-limited' event.
     *
     * Detection uses the SETTLED-OUTPUT signal: the throttle string is present in
     * the (widened) recent pane AND that pane has not changed since the previous
     * poll. An actively-working Claude session animates its spinner + elapsed
     * timer every tick, so byte-identical output across polls proves no work is
     * being produced — the turn ended on the throttle.
     *
     * This deliberately REPLACES the old gates that made this sentinel never fire
     * in the wild (2026-05-30 incident):
     *   - the active-child-process check (a lingering background shell masked the
     *     throttle on busy dev sessions), and
     *   - the at-prompt / 20-line-window check (Claude's input box + footer push
     *     the "API Error" line out of view).
     * The settle guard + the wider window (RATE_LIMIT_SETTLED_CAPTURE_LINES)
     * cover both failure modes while staying false-positive-safe.
     */
    checkRateLimited(tmuxSession: string): void;
    /**
     * Detects sessions that just went through context compaction and are idle
     * at a prompt. Emits 'compaction-idle' so server.ts can activate triage
     * to reinject pending user messages.
     *
     * Defense-in-depth against false positives:
     *   1. Compaction marker must appear in last 10 lines (recency — avoids stale buffer)
     *   2. Prompt must be in last 3 lines (structural — avoids > in content)
     *   3. No active child processes (process-level — Claude is truly idle, not mid-tool)
     *   4. Cooldown per session (prevents re-triggering on same compaction)
     *   5. server.ts checks message history before activating triage (data-level guard)
     *   6. TriageOrchestrator Pattern 2b re-validates before reinjecting (redundant check)
     */
    checkCompactionIdle(tmuxSession: string): Promise<void>;
    private handleEscalation;
    /**
     * LLM gate: Before entering escalation, ask whether the command is
     * legitimately long-running or actually stuck. This prevents the watchdog
     * from killing legitimate builds, installs, or data processing.
     *
     * Returns true if the command appears stuck and should be escalated.
     * Returns false if the LLM thinks it's a legitimate long-running task.
     *
     * When the LLM judge is UNAVAILABLE (no provider) or ERRORS (rate-limited /
     * circuit-open / timeout — common under load), this FAILS CLOSED: it does NOT
     * interrupt below the deterministic hard ceiling, and only returns "stuck"
     * once the command has run past `hardCeilingMs`. This is the "No Silent
     * Degradation to Brittle Fallback" rule applied to a DESTRUCTIVE action
     * (Ctrl+C kills the running command): a false interrupt of a legitimate long
     * build/test is frequent and costly, so the safe direction when the judge
     * can't run is to leave the command alone — while the hard ceiling still
     * deterministically recovers a genuinely hung command (e.g. `crontab -`
     * waiting on stdin) without any LLM. (Previously this fail-opened, which under
     * load Ctrl+C'd every command running past the 3-min threshold.)
     */
    private isCommandStuck;
    /**
     * Deterministic backstop for the fail-closed paths: true once a command has
     * run past `hardCeilingMs`. A ceiling of 0 disables it (pure fail-closed —
     * never interrupt without a positive LLM "stuck" verdict).
     */
    private hardCeilingExceeded;
    /**
     * Resolve the framework-CLI PID inside the given tmux pane. Was
     * Claude-only — generalized for v1.0.0 provider portability so the
     * watchdog also finds Codex sessions (and future frameworks) running
     * inside Instar-managed panes.
     *
     * @deprecated method name retained for v0.x compat — internal callers
     * use the framework-generic shape.
     */
    private getClaudePid;
    private getFrameworkPid;
    private getChildProcesses;
    /**
     * Detects whether the given process is a pipeline stdin consumer (tail,
     * grep, sort, etc.) whose process group still contains an active sibling
     * producer. Such cases are false positives for the stuck-command detector:
     * `zsh -c "python ... | tail -40"` execs tail into place, so the child
     * of claude shows up as `tail -40` even though the real work is the
     * python producer still running in the same pgid.
     *
     * Returns true if this PID looks like a waiting consumer in an active
     * pipeline — in which case escalation should be skipped.
     */
    private hasActivePipelineSibling;
    /**
     * Cheap check whether a command is a pure stdin consumer (tail, grep,
     * sort, etc.) without needing pgid lookups. Used to apply extended
     * grace periods and fail-closed escalation for this class of commands.
     */
    private isStdinConsumerCommand;
    /**
     * Heuristic: does a stdin-consumer command have a file argument? If so,
     * it's reading from the file (not a pipe) and the pipeline guard doesn't
     * apply. We strip flags (tokens starting with `-`) and anything that
     * looks like a flag value, then check if a non-flag token remains past
     * the executable name.
     */
    private hasFileArgument;
    private isExcluded;
    private parseElapsed;
    private sendSignal;
    private isProcessAlive;
    private recordIntervention;
    /**
     * Check session health 60s after an intervention.
     * Did the session recover (still producing output) or die?
     */
    private checkOutcome;
    /**
     * Append an event to the persistent JSONL log.
     * 30-day retention, auto-rotated.
     */
    private persistEvent;
    /**
     * Read persistent intervention log entries since a given time.
     */
    readLog(sinceMs?: number): InterventionEvent[];
    /**
     * Get aggregated watchdog stats for a time window.
     * Used by TelemetryCollector for Baseline submissions.
     */
    getStats(sinceMs?: number): WatchdogStats;
    /**
     * Rotate the persistent log — remove entries older than 30 days.
     */
    rotateLog(): void;
    /** Sync in-memory runtime read for the GuardRegistry (GET /guards).
     *  Cheap property read ONLY — getStatus() lists sessions. */
    guardStatus(): {
        enabled: boolean;
        lastTickAt: number;
    };
}
export {};
//# sourceMappingURL=SessionWatchdog.d.ts.map