import { type SessionWarmup } from "../../core/lsp-session.js";
import { type LspChildProcess } from "./stdio-lsp-transport.js";
import { type LspServerManager, type LspSession, type LspReadText } from "../../core/lsp.js";
import type { ExecutionEnv } from "../../internal/harness-types.js";
import type { EngineNotice } from "../../core/engine-notice.js";
/**
 * Default language → server command table (the DETERMINED supported set). Each value is a `command [args…]`
 * whose binary must be on the host `PATH`; absent ⇒ that language degrades. Mirrors the servers service bakes
 * into its sandbox templates (so TOC & TOB expose the SAME languages when installed), plus the common
 * rust/c/cpp servers a local dev box usually has. Override/extend via {@link NodeLspManagerOptions.servers}
 * (a `null` value disables a language). All of these speak LSP over stdio by default.
 */
export declare const DEFAULT_LSP_SERVERS: Readonly<Record<string, string>>;
/** Spawn a child (default). Resolves the child on a successful `spawn`, or `undefined` on `error` (ENOENT =
 *  server not installed → the manager degrades that language). Waiting for the 'spawn'/'error' race before use
 *  is CRITICAL: `spawn()` returns synchronously but ENOENT fires 'error' asynchronously (CC's LSPClient §1.5). */
export type LspSpawn = (command: string, args: string[], cwd: string, signal?: AbortSignal) => Promise<LspChildProcess | undefined>;
export interface NodeLspManagerOptions {
    /** Override/extend {@link DEFAULT_LSP_SERVERS} (merged over it; a `null` value disables that language). */
    servers?: Record<string, string | null>;
    /** Injectable spawn (tests supply a fake server; default = {@link defaultLspSpawn} over `child_process.spawn`). */
    spawn?: LspSpawn;
    /** The language server's environment for the default spawn, verbatim. Omitted ⇒ the host environment scrubbed of
     *  credential-shaped names (`scrubSecretEnv`, a tool child's scrub). Unread when {@link spawn} is supplied. */
    env?: Record<string, string>;
    /** #1081 — the withheld-keys notice sink, SAME shared once-per-process latch/code as the shell-exec
     *  site's `NodeExecutionEnv.onNotice`. Absent ⇒ console only. Unread when {@link spawn} is supplied. */
    onNotice?: (notice: EngineNotice) => void;
    /** Read a file's CURRENT text for the session's didOpen/didChange re-sync (default = local `fs.readFile` utf8). */
    readText?: LspReadText;
    /** Resolve the server `cwd`/`rootUri` for a file (default = env.cwd ?? nearest project marker ?? file dir). */
    resolveRoot?: (filePath: string, env?: ExecutionEnv) => string;
    /** Observability hook (open/heal/degrade/evict events). */
    log?: (event: string, fields: Record<string, unknown>) => void;
    /** Warm-up window override (see {@link TransportLspSession}); default 10s/1s. */
    warmup?: SessionWarmup;
    /** Max concurrently-cached language servers before LRU eviction (default 16, min 1). A safety net bounding
     *  the cache when a deployment can't call {@link NodeLspManager.evict} at task-end (e.g. ephemeral/worktree
     *  roots). Each cached server is a real process (jdtls = a JVM), so keep this modest. */
    maxSessions?: number;
    /** Idle-TTL (ms) after which an unused server is auto-reclaimed by a background sweep (default 600_000 = 10
     *  min; 0 disables). Self-healing complement to {@link NodeLspManager.evict}: a deployment that can't call
     *  `evict` at task-end still won't leak — an abandoned server dies on its own. An actively reused server never
     *  goes idle (its last-used stamp refreshes on every lookup), so a modest TTL doesn't churn the happy path. */
    idleTtlMs?: number;
    /** design/121: the workspace diagnostics registry every session feeds from publishDiagnostics.
     *  WORKSPACE-scoped, not server-scoped — it survives heal/evict (diagnostics belong to files, not to a
     *  server instance). Absent ⇒ notifications stay dropped (pre-121 behavior byte-for-byte). */
    diagnostics?: import("../../core/lsp-diagnostics.js").LspDiagnosticsRegistry;
}
export declare class NodeLspManager implements LspServerManager {
    private readonly servers;
    private readonly spawnFn;
    private readonly readTextFn;
    private readonly resolveRootFn;
    private readonly log;
    private readonly warmup?;
    /** design/121: the workspace diagnostics registry (see {@link NodeLspManagerOptions.diagnostics}) —
     *  public so the Runner drains it at turn boundaries via {@link LspServerManager.diagnostics}. */
    readonly diagnostics: import("../../core/lsp-diagnostics.js").LspDiagnosticsRegistry;
    private readonly maxSessions;
    private readonly idleTtlMs;
    private sweepTimer?;
    /** `<language> <root>` → the live session + its root + last-used stamp (reused across ops; healed when its
     *  transport dies; idle-TTL swept; LRU-evicted over {@link maxSessions}; `evict(root)` drops a task's servers
     *  at task-end). Space-separated key is unambiguous — a languageId never contains a space. */
    private readonly cache;
    /** In-flight opens (concurrent ops on the same language+root share ONE spawn/initialize). Tracks each open's
     *  `root` + a `cancelled` flag so {@link evict}/{@link dispose} can reclaim a server whose open resolves AFTER
     *  the teardown already ran (else its `.then` caches a live child that nothing reclaims — permanently after
     *  dispose, since the sweep timer is cleared). */
    private readonly opening;
    /** RB-376 — the NEGATIVE spawn cache, the missing twin of {@link cache}: a `(language, root)` key
     *  whose real `spawn()` already failed (binary not on PATH). Without it, every successful
     *  Edit/Write/NotebookEdit re-triggered a full spawn attempt for the same missing server — a
     *  production leg recorded 205 `lsp_spawn_degraded` events with the model never calling LSP once.
     *  Same key construction as the positive cache. NO TTL by design: a missing binary does not appear
     *  mid-task, and a time-based retry would just resume the re-spawn storm — {@link clearFailed} is
     *  the explicit recovery valve. `attempts` counts suppressed hits (first failure logs once via
     *  `lsp_spawn_degraded`; cache hits stay silent; the count surfaces on the cleared log line). */
    private readonly failed;
    /** HRD-LSP-14: consecutive `initialize` failures per key, BEFORE the latch (see {@link INIT_FAILURE_LATCH}). */
    private readonly initFailures;
    /** Set by {@link dispose}: an in-flight open that resolves after dispose self-closes instead of caching. */
    private disposed;
    constructor(opts?: NodeLspManagerOptions);
    /** Close + drop every server idle past {@link idleTtlMs}. Runs on a timer; best-effort. */
    private sweepIdle;
    sessionFor(filePath: string, signal?: AbortSignal, env?: ExecutionEnv): Promise<LspSession | undefined>;
    /** Spawn + initialize a fresh session for `key`, deduping concurrent opens. `carry` = files to re-open
     *  before the session becomes visible (heal). */
    private open;
    /** LRU-evict (close) the least-recently-used server(s) until there is room for one more under {@link maxSessions}.
     *  A safety net: without an explicit {@link evict}, a long-lived process spawning tasks with EPHEMERAL roots
     *  (worktree / per-task cwd) would otherwise leak one heavyweight language server per (task × language). */
    private evictOverCap;
    /**
     * Evict + kill every cached language server rooted at `root`. Call this at TASK-END for an ephemeral/worktree
     * cwd so a heavyweight server (e.g. a jdtls JVM) doesn't outlive the task that spawned it — the deterministic
     * complement to the LRU cap. `root` must match what `resolveRoot` produced for the task (the host lane uses the
     * task's `env.cwd`). Best-effort + idempotent (an unknown/already-evicted root is a no-op).
     */
    evict(root: string): Promise<void>;
    /**
     * RB-376 — drop negative spawn-cache entries so the NEXT op may retry the real spawn: the explicit
     * recovery valve for "I just installed that language server" (the entries deliberately have no TTL —
     * see the `failed` field doc). No arg ⇒ clear everything; `root` ⇒ only that root's entries (the
     * {@link evict} shape). Each cleared entry logs its suppressed-attempt count (evict-log parity).
     * Idempotent; an unknown root is a no-op.
     */
    clearFailed(root?: string): void;
    /**
     * HRD-LSP-14 (optional {@link LspServerManager} member): WHY the last `sessionFor` for this file degraded.
     * `openSession` collapses three very different situations into one `undefined` — the language has no
     * server configured, the binary is not installed, or the server IS installed and failed to initialize —
     * and the tool rendered the same "no language server for its language in this environment" for all of
     * them. The first is a fact about the deployment; the third is an incident the operator can fix, and
     * telling the model (and the transcript) they are the same thing is how "we have no LSP for Go" survives
     * for months in an environment where gopls is installed and crashing.
     */
    unavailableReason(filePath: string, env?: ExecutionEnv): string | undefined;
    private openSession;
    private serverCmd;
    /** Kill every running server + stop the idle sweep (call on runner/deployment shutdown). Best-effort. */
    dispose(): Promise<void>;
}
/** Resolve a file path to a languageId via its extension (undefined = no mapping → degrade). */
export declare function languageFor(filePath: string): string | undefined;
/** CC 2.1.220's LSP file cap — single-sourced in core (`src/core/lsp.ts`), where the tool-layer check
 *  enforces it model-visibly; re-exported here so engine-path importers keep working. */
export { MAX_LSP_FILE_BYTES } from "../../core/lsp.js";
/**
 * RB-232: the local default {@link LspReadText}, hardened with a stat-first precondition (CC
 * 2.1.220 validateInput does the same before ever reading — "Path is not a file", (pretty220.js:474700, historical; not relocated in 250 — see cc-250 anchors)).
 * The stat check is the LOAD-BEARING part, do not "simplify" it away: a plain `readFile` on a FIFO/socket
 * blocks forever on the FIRST read and PINS a libuv threadpool thread (default pool = 4) for the process
 * lifetime — measured: `readFile(fifo, { signal })` with an abort fired at 2s was still unsettled at 10s,
 * because Node only checks the signal between chunk reads. `{ signal }` is kept as a bonus for regular
 * files; it cannot rescue the FIFO case on its own. (The tool layer additionally rejects such paths via
 * `env.fileInfo` before any read — this is defense in depth for direct/embedder callers.)
 */
export declare const defaultLspReadText: LspReadText;
/** ask ②: on Windows, npm-global language servers materialize as `.cmd` shims and
 *  Node 20+ `spawn(cmd, args)` without a shell rejects them (EINVAL, CVE-2024-27980 hardening) —
 *  every DEFAULT_LSP_SERVERS entry would silently degrade to "no LSP". `shell:true` routes through
 *  cmd.exe so PATHEXT/.cmd resolution works. Injection surface: `command`/`args` come from
 *  LspServerSpec (deployment configuration, trusted) or DEFAULT_LSP_SERVERS constants — never from
 *  the model. Exported as a pure function so the win32 branch is testable off-platform. */
export declare function lspSpawnOptions(cwd: string): {
    cwd: string;
    stdio: ["pipe", "pipe", "ignore"];
    shell?: boolean;
};
/**
 * Default spawn over `child_process.spawn`, resolving on 'spawn' / `undefined` on 'error' (ENOENT = degrade)
 * / REJECTING with the errno error for any other spawn failure (see {@link NodeLspManager}'s `failed` map:
 * "not installed" is permanent and worth caching, EACCES/EMFILE/a transient cwd is not — HRD-LSP-15).
 *
 * 🔴 HRD-LSP-4 — the ABSORBER below is load-bearing, do not "simplify" it away. `ChildProcess` is an
 * `EventEmitter`, and an 'error' emitted with ZERO 'error' listeners is re-thrown by Node as an uncaught
 * exception — in a library that means TAKING THE HOST PROCESS DOWN. `spawn()` returns synchronously while
 * ENOENT arrives asynchronously (CC's LSPClient §1.5), so every path that removes our own listener before
 * that event lands is a live crash: `onAbort` (cancel a spawn that is about to fail = a routine task
 * cancellation) and `onSpawn` (the window between resolving and `StdioLspTransport`'s constructor attaching
 * its handler). A permanent no-op listener, attached before anything can fire and never removed, makes the
 * emitter unable to throw; the transport's own handler still receives every runtime error afterwards.
 */
export declare const defaultLspSpawn: LspSpawn;
