import type { CliAdapter, CliAdapterStatus } from '../../cli-adapter-types.js';
import type { PtyTransportFactory } from '../../cli-adapters/pty-transport.js';
import { type MeshSendKeyItem, type MeshSendKeyName } from '../../cli-adapters/provider-cli-shared.js';
import { type InteractivePromptResponse } from '../types/interactive-prompt.js';
export declare class SpecCliAdapter implements CliAdapter {
    readonly cliType: string;
    readonly cliName: string;
    readonly workingDir: string;
    /**
     * Marker the daemon's finalization gate checks: `getStatus()` returns
     * `messages: []` by design here (chat history lives in the daemon's
     * native-history pipeline, not the adapter). Without this flag,
     * cli-provider-instance's `missing_final_assistant` gate would stall
     * every turn until the 30s safety timeout because it expects the
     * adapter to surface the final assistant message.
     */
    readonly chatMessagesOwnedExternally: true;
    private driver;
    /** Common spec fields the adapter reads, present in both v3 and v4. */
    private spec;
    private lastEvent;
    private latestState;
    private latestModal;
    private statusCallback;
    private ptyDataCallback;
    private partialResponse;
    private activeInteractivePrompt;
    private interactivePromptTransport;
    private claudeTuiPromptCaptureInFlight;
    /**
     * Wall clock of the first frame on which a held interactive prompt was
     * observed to have left the screen. Mirrors the approval FSM's
     * `modalLostAt` hysteresis (see cli-state-engine.ts): claude-cli's TUI
     * repaints the choice picker as several PTY chunks, so a single frame
     * with no "Enter to select" footer is not proof the prompt is gone — it
     * may just be mid-repaint. We only clear the held prompt once it has
     * been absent across a short grace window. Reset to null the moment the
     * prompt footer reappears.
     *
     * Without this, a choice prompt resolved *directly in the terminal* (the
     * user picked an option without going through ADHDev's
     * setInteractivePromptResponse) was never cleared from
     * `activeInteractivePrompt`, so getStatus() re-emitted the same prompt
     * forever — the choice-resolve-stuck bug.
     */
    private interactivePromptLostAt;
    private jsonLineTail;
    private exited;
    private spawned;
    private providerSessionId;
    /** Wall clock at the moment spawn() ran. Used as the cutoff for
     *  native-history file selection so a prior session's transcript
     *  can't leak into this session before the agent has written its
     *  own records. */
    private spawnedAtMs;
    /** Env vars the daemon set on the spawned child. Mesh coordinator
     *  points hermes at a per-coordinator HERMES_HOME so the dashboard's
     *  native-history reader needs that override to find the right
     *  state.db; without it the reader sees ~/.hermes/state.db which
     *  the coordinator-launched hermes never writes to. The choice to
     *  redirect HERMES_HOME is a workaround for an unresolved hermes
     *  upstream feature gap (see hermes-agent#23130 — runtime-supplied
     *  MCP config), so this routing keeps the dashboard honest until
     *  hermes ships a runtime MCP override. */
    private spawnedEnv;
    constructor(specPath: string, workingDir: string, cliArgs: string[], extraEnv: Record<string, string>, transportFactory?: PtyTransportFactory);
    spawn(): Promise<void>;
    sendMessage(text: string): Promise<void>;
    getStatus(): CliAdapterStatus;
    private maybeRefreshNativeHistory;
    getScriptParsedStatus(): unknown;
    private detectBackgroundTask;
    getPartialResponse(): string;
    shutdown(): void;
    cancel(): void;
    isProcessing(): boolean;
    isReady(): boolean;
    isAlive(): boolean;
    private static readonly TERMINAL_SNAPSHOT_DEFAULT_MAX_BYTES;
    private static readonly TERMINAL_SNAPSHOT_ABSOLUTE_MAX_BYTES;
    /**
     * MESH-READ-TERMINAL (feature 2: RAW terminal read). Least-privilege read
     * of the CURRENT rendered viewport for mesh_read_terminal on the spec path
     * (claude-cli / antigravity / codex-cli — the native-source providers that
     * route through SpecCliAdapter). Mirrors ProviderCliAdapter.getTerminalScreenSnapshot:
     *  - returns ONLY the driver's current viewport snapshot, the cursor
     *    position and the terminal geometry — NO scrollback, NO parser/FSM
     *    state, NO debug buffers;
     *  - the payload is byte-bounded (UTF-8) with bottom-tail preservation so a
     *    screen of multi-byte glyphs can never exceed the MCP payload cap;
     *  - `hash` is over the FULL untruncated viewport so a caller can detect a
     *    screen change across polls even when the returned text was truncated.
     *
     * SECURITY: the raw viewport can carry tokens / command args / env / user
     * data. Callers MUST gate this on mesh ownership and MUST NOT log the text.
     */
    getTerminalScreenSnapshot(maxBytes?: number): {
        text: string;
        cursor: {
            col: number;
            row: number;
        };
        cols: number;
        rows: number;
        truncated: boolean;
        originalBytes: number;
        returnedBytes: number;
        hash: string;
    };
    /**
     * MESH-SEND-KEYS (feature 3: key injection). Inject a STRUCTURED key
     * sequence into the spec-driven PTY for mesh_send_keys. Mirrors
     * ProviderCliAdapter.injectKeys' modal fail-closed guard, then writes the
     * whole encoded sequence in ONE pty_write dispatch (text+ENTER is a single
     * contiguous string, so a submit key can never be separated from the text
     * it submits).
     *
     * The spec path drives the child through the FsmDriver, not a directly-held
     * ptyProcess — there is no adapter-level echo-gate/submit-retry FIFO to race
     * against here (the driver serializes its own writes), so the only guard is
     * the modal fail-closed: a NON-destructive injection into an actionable
     * approval modal is refused (use mesh_approve) unless explicitly overridden.
     * A destructive ESC/CTRL_C dismisses rather than confirms, so it is allowed
     * past this gate (the tool layer owns the destructive double-gate + audit).
     * This method NEVER logs the literal text — only key enums / byte length.
     */
    injectKeys(items: MeshSendKeyItem[], opts?: {
        allowModalOverride?: boolean;
    }): Promise<{
        ok: true;
        keys: MeshSendKeyName[];
        hasDestructive: boolean;
        submits: boolean;
        bytes: number;
    } | {
        ok: false;
        refused: 'submit_race' | 'actionable_modal';
        keys: MeshSendKeyName[];
        hasDestructive: boolean;
    }>;
    setOnStatusChange(cb: () => void): void;
    setOnPtyData(cb: (data: string) => void): void;
    writeRaw(data: string): void;
    resize(cols: number, rows: number): void;
    resolveModal(buttonIndex: number): void;
    resolveModalMatched(buttonIndex: number): boolean;
    resolveAction(data: unknown): Promise<void>;
    setInteractivePromptResponse(response: InteractivePromptResponse): Promise<void>;
    isApprovalRecentlyResolved(): boolean;
    clearHistory(): void;
    updateRuntimeSettings(): void;
    setServerConn(): void;
    /**
     * Map an invokeScript(name, args) call onto a control_bar entry.
     *
     * scriptName is matched against control.id. The control's action.type
     * drives the dispatch:
     *
     *   send_keys     → click_control                   (e.g. stop)
     *   open_picker   → two roles, driven by the screen, not a hardcoded list:
     *                   - LIST  (no choice arg): open the picker, wait for it
     *                     to render, parse the on-screen options via
     *                     `extract_choices`, and return them as
     *                     `controlResult.options` (+ `currentValue`). This is
     *                     how the dashboard's Model/Mode controls learn what is
     *                     actually selectable in this CLI right now.
     *                   - SELECT (args.choiceIndex / args.choiceLabel): drive
     *                     the picker to that option using `submit_key`.
     *   attach_image  → attach_image dispatch; expects args.blob (data url
     *                   or base64) and args.mime
     *
     * Callers that pass an unknown control id get a { not_found } response.
     * No control matched, no driver call — keeps the surface honest.
     */
    invokeScript(scriptName: string, args?: Record<string, unknown>): Promise<unknown>;
    /**
     * Open an `open_picker` control and return the options the CLI is showing,
     * parsed live from the screen via `extract_choices`. Nothing is selected —
     * the picker is left open so a follow-up SELECT invoke can commit a choice.
     */
    private openPickerAndListChoices;
    /**
     * Drive an already-listable picker to a specific option. The option can be
     * named (choiceLabel — matched against the parsed on-screen labels) or
     * positional (choiceIndex — the on-screen number). The actual keystrokes
     * come from the spec's `submit_key` with `{index}` substituted, so the spec
     * — not this code — decides how a selection is keyed for each CLI.
     */
    private selectPickerChoice;
    /** Parse the picker choices only if the picker already appears rendered on
     *  the live screen (its `wait_for` condition currently matches and at least
     *  one choice parses). Returns the parsed choices when open, else null so
     *  the caller knows it must send the trigger to open it. Used to de-dup the
     *  picker open in {@link selectPickerChoice}. */
    private extractPickerChoicesIfRendered;
    /** Poll the live screen until the picker's `wait_for` condition matches,
     *  up to a short budget. Returns true if it rendered, false on timeout. */
    private waitForPickerRendered;
    /** Parse the picker's `extract_choices` pattern against the live screen.
     *  Each match yields { index, label, current }. `current` is true for the
     *  line the CLI marks with its cursor glyph (❯ ›). Purely screen-driven —
     *  no model/mode names are baked in. */
    private extractPickerChoices;
    /** Live text of a named screen section (or the whole screen when no
     *  section is named), resolved from the driver's current sections. */
    private readScreenSectionText;
    getDebugSnapshot(): unknown;
    getRuntimeMetadata(): unknown;
    updateRuntimeMeta(meta?: Record<string, unknown>): void;
    refreshProviderDefinition(): void;
    private handleEvent;
    private detectInteractivePromptFromPtyChunk;
    private readCurrentScreenSections;
    private extractProviderSessionIdFromScreen;
    private readClaudeScreenAssistantMessages;
    /**
     * Grace window a held interactive prompt must be absent from the screen
     * before we treat it as resolved-in-terminal and clear it. claude-cli
     * repaints the picker across multiple PTY chunks, so a single
     * footer-less frame is not proof the prompt is gone. Sized in the same
     * spirit as the approval FSM's `approvalCooldown` modal-lost hysteresis.
     */
    private static readonly INTERACTIVE_PROMPT_LOST_GRACE_MS;
    /**
     * Multi-question TUI capture: after Tabbing to a page, re-snapshot at this
     * interval until its checkbox glyph column has settled, up to the timeout.
     * The interval matches the legacy single fixed wait (120ms); the timeout
     * bounds total capture time so a single-select page (which never shows
     * glyphs) doesn't stall the capture indefinitely.
     */
    private static readonly CLAUDE_TUI_PAGE_POLL_INTERVAL_MS;
    private static readonly CLAUDE_TUI_PAGE_SETTLE_TIMEOUT_MS;
    /**
     * Clear a held interactive prompt once the user has resolved it directly
     * in the terminal (the choice picker leaves the screen without going
     * through setInteractivePromptResponse). The approval path already does
     * this via the FSM's modal-lost hysteresis; the interactive-prompt path
     * had no equivalent, so a terminal-side answer left activeInteractivePrompt
     * set and getStatus() re-emitted the same choice modal forever.
     *
     * Detection mirrors capture: the claude TUI picker is on-screen exactly
     * while its "Enter to select" footer is rendered. When the footer is gone
     * for INTERACTIVE_PROMPT_LOST_GRACE_MS the prompt is genuinely resolved.
     */
    private maybeClearResolvedClaudeTuiPrompt;
    private maybeCaptureClaudeTuiPrompt;
    /**
     * The TUI prompt is captured on the FIRST frame that renders the
     * "Enter to select" footer. At that instant the option rows' checkbox
     * column may not have drawn yet, so `detectClaudeTuiMultiSelect` returns
     * false and the prompt is frozen as single-select — the dashboard then
     * renders radio buttons even though the picker is multi-select.
     *
     * While the same TUI prompt is still on screen, re-check the live snapshot:
     * if checkbox glyphs have since appeared, promote any single-select
     * question to multi-select and re-emit status. Promotion is one-way
     * (false→true only) — once a question is known multi-select we never demote
     * it, since the glyph column can scroll out of view on later frames.
     *
     * For MULTI-question prompts the per-page Tab capture is the actual source
     * of the bug: pages 2..N are snapshotted ~120ms after the Tab keypress,
     * before their option-row glyph column has redrawn, so those questions
     * freeze as single-select while page 1 (already settled) is correct. We
     * cannot upgrade blindly — the live snapshot shows only ONE focused page —
     * but we CAN read that page's question text/header and upgrade the matching
     * question. As the user navigates the picker (or it settles), each page is
     * eventually re-read and repaired.
     */
    private maybeUpgradeClaudeTuiMultiSelect;
    private readClaudeTuiHeaders;
    /**
     * Snapshot the currently-focused claude TUI page, polling until its
     * option-row checkbox glyph column has settled (or a bounded timeout).
     *
     * Why poll: right after a Tab keypress the newly-focused page's glyph
     * column has not redrawn yet, so an immediate snapshot shows the question +
     * option labels but NO per-option checkbox markers — freezing that page as
     * single-select. A fixed delay either races (too short) or is wasteful (too
     * long). Instead we re-snapshot at a fixed interval and stop as soon as the
     * frame shows multi-select glyphs, falling back to the last frame at the
     * timeout. Single-select pages never show glyphs, so they always poll to the
     * timeout — bounded small to keep capture snappy.
     */
    private snapshotSettledClaudeTuiPage;
    private captureClaudeTuiPrompt;
    getDebugState(): Record<string, any>;
    getTraceState(limit?: number): Record<string, any>;
    getProviderResolutionMeta(): Record<string, any>;
}
