import type { LspClient, ServerConfig, WorkspaceEdit } from "./types.js"; /** * A JSON-RPC error response to one of our requests. The message keeps the code, so * substring checks such as `isMethodNotFoundError` still match. */ export declare class LspResponseError extends Error { readonly code: number | undefined; readonly data: unknown; constructor(code: number | undefined, data: unknown, message: string); } /** A request the server did not answer within its timeout; it may still be computing the answer. */ export declare class LspRequestTimeoutError extends Error { readonly method: string; readonly timeoutMs: number; constructor(method: string, timeoutMs: number); } /** Whether a client cold start attaches to a broker-shared language server. */ export declare function isSharedLspEnabled(): boolean; /** Hold the shared-LSP mode at `enabled` until released; `set` follows later setting changes. */ export declare function holdSharedLsp(enabled: boolean): { set(enabled: boolean): void; release(): void; }; /** * Call `listener` with the new value whenever {@link isSharedLspEnabled} changes * (subagent worker threads follow the main thread's mode); returns the unsubscribe. */ export declare function onSharedLspChange(listener: (enabled: boolean) => void): () => void; /** * Configure the global fallback idle timeout for LSP clients (used in tests/overrides). * When unset, each client evaluates against its workspace config (`getConfig(client.cwd).idleTimeoutMs`). * @param ms - Timeout in milliseconds, or null/undefined to disable global override */ export declare function setIdleTimeout(ms: number | null | undefined): void; /** * Whether a client may be reaped by the idle checker. * * A client with in-flight requests is *busy*, never idle. `lastActivity` is * stamped when a request is written, not while it is outstanding, so a single * request that runs longer than the idle timeout used to look like silence: * the checker tore the client down mid-flight and `shutdownClientInstance` * rejected the caller's still-pending promise with "LSP client shutdown" * (issue #8390). Requests that settle refresh `lastActivity`, so a client * becomes eligible again only after the final one lands and the full idle * window then elapses. * * Exported for tests; the idle checker is the only production caller. */ export declare function isIdleClient(client: LspClient, now: number, timeoutMs: number): boolean; /** * Whether the background idle checker interval is currently active. * Exported for tests. */ export declare function isIdleCheckerRunning(): boolean; /** * Reconcile the background idle checker interval against currently configured timeouts. * Starts the checker if any registered client or workspace has a positive timeout, * or stops it if none do. */ export declare function reconcileIdleChecker(): void; /** * Sweeps all registered LSP clients against their workspace idle timeout. * Exported for tests. */ export declare function checkIdleClients(): Promise; /** LSP `FileChangeType` values for workspace/didChangeWatchedFiles notifications. */ export declare enum FileChangeType { Created = 1, Changed = 2, Deleted = 3 } /** Filesystem change authored by the harness and announced to active LSP clients. */ export interface WatchedFileChange { filePath: string; type: FileChangeType; } /** * Add the checkouts holding `filePaths` to `client`'s workspace where the server would * otherwise not load them ({@link workspaceFolderToAdd}), in one notification, and return * once the server has them. Concurrent callers for one checkout share its delivery, which * a caller's abort does not cancel. The server re-indexes, so the client's project-load * wait covers that load too. */ export declare function ensureWorkspaceFolders(client: LspClient, filePaths: readonly string[], signal?: AbortSignal): Promise; /** {@link ensureWorkspaceFolders} for one file. */ export declare function ensureWorkspaceFolder(client: LspClient, filePath: string, signal?: AbortSignal): Promise; /** * Apply a server-provided workspace edit and reconcile every affected open LSP document. * Runtime callers use this wrapper so later semantic requests observe the committed files. * Reconciliation is derived from the ops that actually ran — an op skipped via * `ignoreIfExists`/`ignoreIfNotExists` neither closes overlays nor notifies watchers, and * when the edit fails partway the already-executed prefix is still reconciled before the * error propagates so mutated files never keep stale overlays. */ export declare function applyWorkspaceEditWithLsp(edit: WorkspaceEdit, cwd: string, signal?: AbortSignal): Promise; /** Whether the server advertised LSP 3.17 document diagnostic pulls statically or through registration. */ export declare function supportsDocumentDiagnostics(client: LspClient): boolean; /** Timeout for warmup initialize requests (5 seconds) */ export declare const WARMUP_TIMEOUT_MS = 5000; /** * True when this client speaks the rust-analyzer protocol, detected by the * command basename (`rust-analyzer[.exe]`) of the configured or resolved * binary. Callers use it to gate rust-analyzer-only requests such as * `rust-analyzer/reloadWorkspace` (see {@link reloadServer}). */ /** * The `Cargo.toml` files rust-analyzer can discover under `root`, sorted and joined, so a reload * can tell whether the crate set changed since initialize (gitignored dirs such as `target/` skipped). */ export declare function listCargoManifests(root: string, signal?: AbortSignal): Promise; export declare function isRustAnalyzerClient(client: LspClient): boolean; /** * Shut down clients for `cwd` whose identity is absent from `configs`, and * return the server commands torn down. * * `reload *` re-reads config from disk. Identity-aware keys make a changed * server resolve to a fresh client, but the process spawned from the old * config would stay registered and running — the idle checker that would * eventually reap it is opt-in and off by default. */ export declare function shutdownStaleClients(cwd: string, configs: readonly ServerConfig[], signal?: AbortSignal): Promise; /** Allow an explicit user reload to retry a matching initialization failure immediately. */ export declare function clearInitializationFailure(config: ServerConfig, cwd: string): void; /** * Get or create an LSP client for the given server configuration and working directory. * @param config - Server configuration * @param cwd - Working directory * @param initTimeoutMs - Optional hard deadline for the initialize handshake (warmup / other * short-lived callers). When set it takes precedence over `signal` inside `sendRequest`. * @param signal - Optional caller abort signal. Threaded into the initialize `sendRequest` * and the `initialized` notification so a wedged server surfaces the caller's * timeout/cancel instead of falling back to the internal 30s default. */ export declare function getOrCreateClient(config: ServerConfig, cwd: string, initTimeoutMs?: number, signal?: AbortSignal): Promise; /** Return an active or already-starting client without starting a language server. */ export declare function getActiveOrPendingClient(config: ServerConfig, cwd: string, signal?: AbortSignal): Promise; /** * Mark a file whose server overlay OMP has advanced ahead of disk for an in-flight * write. While marked, {@link reconcileFileFromDisk} skips reading disk back into * the server, because the on-disk file is the *stale* side until the write commits. * Every call MUST be balanced by {@link endPendingDiskWrite}. */ export declare function beginPendingDiskWrite(filePath: string): void; /** Release a mark set by {@link beginPendingDiskWrite}; the overlay is authoritative until then. */ export declare function endPendingDiskWrite(filePath: string): void; /** * Ensure a file is opened in the LSP client. * Sends didOpen notification if the file is not already tracked. */ export declare function ensureFileOpen(client: LspClient, filePath: string, signal?: AbortSignal): Promise; /** * Reconcile an already-open document with current disk contents before a semantic query. * * {@link ensureFileOpen} opens an untracked file but no-ops when the URI is already * open, so an external edit — one not routed through OMP's write/edit tools, which * announce their changes via {@link notifyWorkspaceWatchedFiles}/{@link refreshFile} — * leaves the server holding the pre-edit document while callers compute query * positions from disk. This reads the file and, when its contents diverge from the * text last sent to the server, pushes a `didChange` so the server's copy matches * the disk text the position was derived from. Untracked files fall through to * {@link ensureFileOpen}; unchanged files send nothing; a tracked file gone from * disk is closed so the server stops answering for it. * Returns `true` when the caller should wait for fresh diagnostics because the stale * ones were dropped: a `didChange` was pushed for a reconciled overlay, or a document * closed for idleness was reopened. * A file with an in-flight OMP write ({@link beginPendingDiskWrite}) is skipped * entirely: its overlay leads disk, so reading disk back would revert the server * to pre-write content. */ export declare function reconcileFileFromDisk(client: LspClient, filePath: string, signal?: AbortSignal): Promise; /** What a sync does with an open document whose file can no longer be read as text. */ export type UnreadableOpenFile = "throw" | "close"; /** * Reconciles every document the client holds open against disk. * * A project-wide request answers with ranges for every file it touches. An * already-open consumer that was edited outside OMP keeps a stale overlay, so * the server's ranges still point at the old text — applying them to the * current disk text writes the new identifier into the wrong lines while the * request reports success (#15772). Reconciling only the requested target * leaves those consumers stale, so sync all open documents first. * * Files with an in-flight OMP write are skipped (their overlay leads disk), * unchanged files send nothing, and `excludeUri` is left for the caller to * sync. The sweep is not a use of each document, so idle ones still close. * `unreadable: "close"` closes a document whose file can no longer be read, * so one broken file does not fail a query about another. * Returns the number of documents that were re-synced. */ export declare function reconcileOpenFilesFromDisk(client: LspClient, signal?: AbortSignal, excludeUri?: string, unreadable?: UnreadableOpenFile): Promise; /** * Wait for the server's initial project loading to complete. * Races the server's $/progress tracking against the abort signal. * Returns immediately if loading already completed or timed out. */ export declare function waitForProjectLoaded(client: LspClient, signal?: AbortSignal): Promise; /** * Sync in-memory content to the LSP client without reading from disk. * Use this to provide instant feedback during edits before the file is saved. */ export declare function syncContent(client: LspClient, filePath: string, content: string, signal?: AbortSignal): Promise; /** * Notify LSP that a file was saved. * Assumes content was already synced via syncContent; the saved text is read * back only for servers that asked for it. */ export declare function notifySaved(client: LspClient, filePath: string, signal?: AbortSignal): Promise; /** * Announce harness-authored filesystem changes to active LSP clients for `cwd`. * * Created or deleted files can change module resolution for otherwise untouched * open documents, so those overlays are refreshed after the watcher notification. * This also covers sibling files that are not open text documents, such as generated * CSS modules or type files that another edited document imports immediately. * * The underlying stdin write drain is self-bounded by * {@link WATCHED_FILES_NOTIFY_TIMEOUT_MS}; only an abort of the caller's * `signal` rejects. */ export declare function notifyWorkspaceWatchedFiles(cwd: string, changes: readonly WatchedFileChange[], signal?: AbortSignal): Promise; /** * Refresh a file in the LSP client. * Increments version, sends didChange and didSave notifications; closes a tracked * document whose file is gone from disk. */ export declare function refreshFile(client: LspClient, filePath: string, signal?: AbortSignal): Promise; /** * Close and reopen a document from disk. A push server need not republish diagnostics * for an identical-text didChange (clangd does not), but recomputes them on open, picking * up changed dependencies whether or not OMP has them open. A save follows the open for * servers that validate on save. */ export declare function reopenFile(client: LspClient, filePath: string, signal?: AbortSignal): Promise; /** * Make clangd rebuild an open document from disk and publish its diagnostics. Resolves * `true` when it sent the rebuild, so the report for the document's current version is * final only once clangd's build status settles (see `waitForRebuiltDiagnostics`); `false` * when it opened the document instead, or left it to an in-flight OMP write. */ export declare function rebuildFile(client: LspClient, filePath: string, signal?: AbortSignal): Promise; /** * Re-send every open document after a change that can alter how it resolves. This is * not a use: idle documents keep aging, and documents closed meanwhile stay closed. */ export declare function refreshOpenFiles(client: LspClient, signal?: AbortSignal): Promise; /** * Tear down a specific client instance using the LSP shutdown/exit handshake. * * Removes the client from the registry by identity first (never evicting a * newer client already republished under the same key), then performs a bounded * graceful shutdown, force-killing and awaiting confirmed process exit. * * @returns `true` once the process is confirmed exited, `false` if it outlived * the shutdown budget — callers reporting a restart must treat `false` as a * failed teardown, not a completed restart. */ export declare function shutdownClientInstance(client: LspClient): Promise; /** * Tear down a client together with the language-server process behind it. * * A broker-shared link's own `shutdown`/`exit` only detaches it: the mux answers * both and keeps the server idle for reuse. When the server must not survive — * a restart, or a superseded configuration that no reconnect will ask for — * ask the mux to kill the link's server first (best-effort: the link may be the * wedged part), then confirm the link exits like any other client. */ export declare function shutdownClientServer(client: LspClient): Promise; /** * Shutdown a specific client by key. * * @returns `true` when the client is gone (already absent or confirmed exited), * `false` if a live process outlived the shutdown budget. */ export declare function shutdownClient(key: string): Promise; /** * Send an LSP request and wait for response. * * Timeout policy: * - If `timeoutMs` is explicitly provided, that value is used. * - Else, if `signal` is provided, no internal timer is installed (the caller * owns the deadline via the signal — typically a wall-clock `AbortSignal.timeout` * from the LSP tool). Installing a second hard-coded 30s timer here used to * cause "timed out after 30000ms" errors even when the caller had requested * `timeout: 60`. * - Else (no signal, no explicit timeout), fall back to `DEFAULT_REQUEST_TIMEOUT_MS` * to avoid leaking pending requests forever. */ export declare function sendRequest(client: LspClient, method: string, params: unknown, signal?: AbortSignal, timeoutMs?: number): Promise; /** * Send an LSP notification (no response expected). * `signal` bounds the underlying `sink.flush()` — without it a server that * stops draining stdin blocks every future write on the client's write queue. */ export declare function sendNotification(client: LspClient, method: string, params: unknown, signal?: AbortSignal): Promise; /** * Shutdown all LSP clients. */ export declare function shutdownAll(): Promise; /** Status of an LSP server */ export interface LspServerStatus { name: string; status: "connecting" | "ready" | "error"; fileTypes: string[]; error?: string; } /** * Get status of all active LSP clients. */ export declare function getActiveClients(): LspServerStatus[];