/**
 * CliStateEngine — CLI provider status state machine
 *
 * Owns all status-transition logic, timer management, and script-driven
 * evaluation. Reads buffer state from the transport and writes to PTY via
 * the transport interface — adapter stays as pure I/O layer.
 */
import { type TurnParseScope } from './provider-cli-parse.js';
import { type CliProviderModule, type CliSessionStatus, type CliTraceEntry, type ParsedSession } from './provider-cli-shared.js';
import type { CliScriptRunner } from './cli-script-runner.js';
export interface CliBufferSnapshot {
    accumulatedBuffer: string;
    accumulatedRawBuffer: string;
    recentOutputBuffer: string;
    responseBuffer: string;
    screenText: string;
    parseScreenText: string;
    workingDir: string;
    providerSessionId: string | null;
    runtimeSettings: Record<string, any>;
    isWaitingForResponse: boolean;
    currentTurnScope: TurnParseScope | null;
    lastOutputAt: number;
    lastNonEmptyOutputAt: number;
    lastScreenChangeAt: number;
    lastScreenSnapshot: string;
}
/** What the engine needs from the transport layer */
export interface CliTransportAccess {
    getSnapshot(): CliBufferSnapshot;
    writeRaw(data: string | Buffer): void;
    getApprovalKeyForIndex(buttonIndex: number): string | undefined;
    flushOutboundQueue(): void;
    isAlive(): boolean;
    /** Optional: override script dispatch (used by tests to mock detection) */
    runDetectStatus?(text: string): string | null;
    /** Optional: override script dispatch (used by tests to mock approval) */
    runParseApproval?(tail: string): {
        message: string;
        buttons: string[];
    } | null;
    /** Optional: override full session parse (used by tests to mock parsing) */
    runParseSession?(): ParsedSession | null;
    /** Optional: provider type override — used when tests patch the adapter's cliType */
    cliType?: string;
}
export interface CliStateEngineCallbacks {
    onStatusChange(): void;
    onApplyParsedSession(session: ParsedSession): void;
    onTurnCompleted(): void;
    /**
     * FALSE-IDLE (Fix 2): true when the owning session is inside the post-approval
     * resume grace — an autonomous auto-approving mesh session that resolved a modal
     * within the resume-grace window. The engine cannot decide this on its own (it has
     * no mesh/auto-approve awareness); the instance answers using the SAME
     * `inApprovalResumeGrace` judgment Fix 1 uses, so the two fixes stay in lockstep.
     * Optional: absent ⇒ treated as false (no hysteresis) so any non-instance embedder
     * keeps the pre-fix behavior.
     */
    isInApprovalResumeGrace?(): boolean;
}
export declare class CliStateEngine {
    private readonly provider;
    private readonly runner;
    private readonly transport;
    private readonly callbacks;
    private readonly timeouts;
    currentStatus: CliSessionStatus['status'];
    isWaitingForResponse: boolean;
    currentTurnScope: TurnParseScope | null;
    currentTurnTaskId: string | null;
    currentTurnStartedAt: number;
    activeModal: {
        message: string;
        buttons: string[];
    } | null;
    lastApprovalResolvedAt: number;
    lastResolvedModalMessage: string;
    /**
     * Monotonic counter bumped every time the FSM *enters* waiting_approval
     * with a freshly captured modal (see `applyWaitingApproval`). It is the
     * single discriminator between "the same approval re-observed across TUI
     * paint flaps" and "a genuinely new, distinct approval".
     *
     * The message-equality cooldown below (`lastResolvedModalMessage`) cannot
     * tell these apart on its own: claude-cli routinely presents consecutive
     * approvals whose modal message text is identical (e.g. two back-to-back
     * Bash-command prompts). When that second approval arrived inside
     * `approvalCooldown`, the message-equality guard silently swallowed the
     * key write and the approval stuck forever — fatal under auto-approval.
     *
     * `approvalEntrySeq` increments on every fresh entry; `lastResolvedEntrySeq`
     * records which entry the cooldown belongs to. We only short-circuit the
     * write when we are still resolving *that same* entry — a new entry (new
     * seq) is always a real, distinct approval and must be written.
     */
    approvalEntrySeq: number;
    lastResolvedEntrySeq: number;
    /**
     * When the engine previously held a modal but the latest parse failed
     * to extract one, we record the timestamp here and only drop the modal
     * after the configured `approvalCooldown` to avoid flapping between
     * waiting_approval and generating on every Claude TUI redraw — that
     * flapping is what fed auto-approve a fresh modal signature on each
     * paint and made the engine type "1" repeatedly into the prompt.
     */
    modalLostAt: number;
    private modalLostRecheckTimer;
    private approvalExitTimeout;
    responseEpoch: number;
    submitPendingUntil: number;
    responseSettleIgnoreUntil: number;
    submitRetryUsed: boolean;
    submitRetryPromptSnippet: string;
    finishRetryCount: number;
    providerErrorMessage: string | null;
    providerErrorReason: string | null;
    private settleTimer;
    private idleTimeout;
    private finishRetryTimer;
    private providerErrorRetryTimer;
    private providerErrorRetryKey;
    pendingScriptStatus: 'generating' | 'waiting_approval' | null;
    pendingScriptStatusSince: number;
    private pendingScriptStatusTimer;
    private idleFinishCandidate;
    private approvalResumeDeferSince;
    private approvalResumeDeferEpoch;
    /**
     * `finishResponse` produces the `generating → idle` transition that
     * coordinators interpret as "task complete". Some providers (antigravity-
     * cli observed in the wild) briefly paint a screen that looks like an
     * idle prompt between tool result frames while still actively running,
     * which fired `response_finished` and broke completion semantics.
     * We defer the actual idle transition by IDLE_CONFIRMATION_GRACE_MS and
     * cancel it if the scripted detection re-detects generating during that
     * window — a true completion stays idle for many seconds, so a 2-second
     * grace is sufficient to filter the paint blip.
     */
    private pendingIdleFinishTimer;
    private pendingIdleFinishAt;
    private statusHistory;
    private traceEntries;
    private traceSeq;
    private traceSessionId;
    constructor(provider: CliProviderModule, runner: CliScriptRunner, transport: CliTransportAccess, callbacks: CliStateEngineCallbacks, timeouts: Required<NonNullable<CliProviderModule['timeouts']>>);
    setStatus(status: CliSessionStatus['status'], trigger?: string): void;
    scheduleSettle(): void;
    /** Called from sendMessage in transport once a turn scope is established. */
    onTurnStarted(turnScope: TurnParseScope): void;
    /** Called when PTY exits */
    onPtyExit(): void;
    /** Called when adapter starts up successfully */
    onSpawnReady(): void;
    resolveModal(buttonIndex: number): void;
    isApprovalRecentlyResolved(): boolean;
    /**
     * Called from sendMessage before starting a new turn.
     * Clears stale idle response state when the terminal looks idle and no modal is active.
     */
    clearStaleIdleResponseGuard(reason: string, snap: CliBufferSnapshot): boolean;
    /**
     * Called from sendMessage before starting a new turn.
     * Clears stale idle response state when the parsed session confirms idle with a final assistant message.
     */
    clearParsedIdleResponseGuard(reason: string, parsedStatus: any, snap: CliBufferSnapshot): boolean;
    clearAllTimers(): void;
    resetActiveTurnState(): void;
    clearIdleFinishCandidate(reason: string): void;
    /**
     * Poll-driven static-idle confirm (D4). A hosted CLI session (e.g. a fresh
     * antigravity coordinator) whose boot banner drove the FSM into 'generating'
     * can then sit at a STATIC ready prompt emitting no further PTY output. Every
     * output-driven busy→idle re-eval (handleOutput/resolveStartupState/settle)
     * is starved because there is no new output, and the startup-settle loop has
     * hard-stopped past spawnAt+10s — so currentStatus stays frozen at generating
     * and the dashboard disables Send. This is the ONE path that can release that
     * wedge from the read-only status poll.
     *
     * Safety: this must NEVER flip a real generating turn to idle. The gate is
     * done by the caller (getStatus) reusing resolveStartupState's proven
     * predicates: no recent PTY output for a grace window, runDetectStatus of the
     * current screen === 'idle', and no active/parsed modal. Here we add the
     * final structural guard: there must be NO active turn scope. A live user
     * turn always carries a currentTurnScope (set in onTurnStarted), so this only
     * releases the boot-banner wedge and the post-turn static-idle case, both of
     * which have already had their scope nulled. Returns true when it transitioned.
     */
    confirmPollStaticIdle(reason: string): boolean;
    hasActionableApproval(startupModal?: {
        message: string;
        buttons: string[];
    } | null): boolean;
    getTraceEntries(): CliTraceEntry[];
    getStatusHistory(): {
        status: string;
        at: number;
        trigger?: string;
    }[];
    getTraceSessionId(): string;
    /** Record a trace entry from the transport layer (e.g. output events in debug mode). */
    recordExternalTrace(type: string, payload?: Record<string, any>): void;
    runDetectStatus(snap: CliBufferSnapshot): string | null;
    runParseApproval(snap: CliBufferSnapshot): {
        message: string;
        buttons: string[];
    } | null;
    runParseSession(snap: CliBufferSnapshot): ParsedSession | null;
    evaluateSettled(snap: CliBufferSnapshot): void;
    private applyPendingScriptStatusDebounce;
    private applyHoldGenerating;
    private applyWaitingApproval;
    private applyGenerating;
    private applyError;
    private maybeScheduleProviderErrorRetry;
    private applyIdle;
    /**
     * FALSE-IDLE (screen-quiet gate): has the visible terminal screen content been
     * byte-identical for at least SCREEN_QUIET_IDLE_MS continuously?
     *
     * `lastScreenChangeAt` is bumped by the adapter every time the normalized screen
     * snapshot changes (spinner frame, streaming command output, etc.), so
     * `now - lastScreenChangeAt` is the real screen-diff quiet age. Reads the LIVE
     * transport snapshot so the deferred idleFinish timeout re-checks current screen
     * state, not the stale snapshot from when the timer was armed. A never-changed
     * screen (lastScreenChangeAt === 0) is treated as quiet.
     */
    private hasScreenBeenQuietForIdle;
    /**
     * FALSE-IDLE (Fix 2): should applyIdle suppress the idle/finish for the current
     * turn because we are inside the post-approval resume grace?
     *
     * True only when: (a) the owning instance reports it is an autonomous auto-approving
     * mesh session that resolved a modal within the resume-grace window
     * (isInApprovalResumeGrace callback — the SAME judgment Fix 1 uses), AND (b) the defer
     * for THIS response epoch has not yet exceeded APPROVAL_RESUME_IDLE_DEFER_CAP_MS.
     * The cap guarantees no infinite defer: a turn that genuinely ended right after an
     * approval and stays silent past the cap stops being suppressed and finishes normally.
     * The per-epoch anchor means a fresh turn/response restarts the clock.
     */
    private shouldDeferIdleForApprovalResume;
    private clearApprovalResumeDefer;
    finishResponse(): void;
    private scheduleIdleFinish;
    private cancelPendingIdleFinish;
    /**
     * Schedule one more settled evaluation while pinned to `waiting_approval`
     * with no actionable modal. The settled FSM normally only re-runs on new
     * PTY output; a provider whose modal cue lingers in a form detectStatus
     * still matches (e.g. kimi's questionPattern hitting the user echo) but
     * whose PTY has gone quiet would never get another evaluation, latching
     * `waiting_approval` forever. This timer guarantees the modal-lost recovery
     * in `applyWaitingApproval` is reached even against a silent PTY. It is a
     * no-op once the FSM leaves `waiting_approval` (the re-evaluation itself
     * takes the recovery branch and clears the state).
     */
    private armModalLostRecheck;
    private armApprovalExitTimeout;
    private armIdleFinishCandidate;
    private shouldDeferIdleTimeoutFinish;
    private hasRecentInteractiveActivity;
    private hasMeaningfulResponseBuffer;
    private shouldDeferFinishForTranscript;
    private rescheduleTranscriptFinishCheck;
    private shouldRetryFinishResponse;
    private commitCurrentTranscript;
    private maybeCommitVisibleIdleTranscript;
    private parsedStatusHasFinalAssistantMessage;
    private parsedStatusHasFinalStandardAssistantMessage;
    private recordTrace;
}
