import type { Logger } from "./logger.js"; import type { BgCompletion, StatusCompression } from "./protocol.js"; import type { AftProjectTransport, ToolCallArguments, ToolCallOptions, ToolCallResult } from "./transport.js"; type BinaryFingerprintReader = (binaryPath: string) => string | null | undefined; /** Test seam: replace the on-disk binary fingerprint reader without fs module mocks. */ export declare function __setBinaryFingerprintForTests(impl: BinaryFingerprintReader | null): void; /** * Compare two semver version strings (major.minor.patch plus pre-release). * Returns: negative if a < b, 0 if equal, positive if a > b. */ /** * Re-tag a single stderr line forwarded from the `aft` child process. * * env_logger in `aft` emits each log line with an outer `[aft]` or `[aft-lsp]` * tag based on log target. The plugin logger then wraps those lines with its * own `[aft-plugin]` outer tag. We must NOT add a second `[aft]` here when * the line is already tagged, or LSP errors end up rendered as * `[aft-plugin] [aft] [aft-lsp] [aft] ...` (the v0.19.0 doubled-prefix bug). * * Rule: * - Already starts with `[aft]` or `[aft-]` → leave as-is. * - Untagged (rare child-library output, panics, etc.) → prepend `[aft]`. * * Exported for unit testing; production callers use it inside the stderr * `on("data")` handler in `BinaryBridge.spawn`. */ export declare function tagStderrLine(line: string): string; /** * Return false only for the known benign third-party cpuinfo line emitted by * ONNX Runtime's bundled pytorch/cpuinfo library in restricted Linux sandboxes. * This line is not an AFT failure; all other child stderr must still surface. */ export declare function shouldSurfaceStderrLine(line: string): boolean; export declare function compareSemver(a: string, b: string): number; /** Single configure-time warning produced by the Rust side. */ export interface ConfigureWarning { code?: string; message: string; [key: string]: unknown; } /** Project/user trust-boundary key dropped by Rust config resolution. */ export interface ConfigureDroppedKey { key: string; tier: string; reason: string; } /** Context passed to {@link BridgeOptions.onConfigureWarnings} after the first successful configure. */ export interface ConfigureWarningsContext { projectRoot: string; sessionId?: string | null; client?: unknown; warnings: ConfigureWarning[]; configDroppedKeys?: ConfigureDroppedKey[]; } export type VersionMismatchCallbackResult = string | null | undefined; export type VersionMismatchCallback = (binaryVersion: string, minVersion: string) => VersionMismatchCallbackResult | Promise; /** * Thrown when a request times out at the transport layer but the bridge is * being kept warm (passive/bash-family calls with `keepBridgeOnTimeout`). The * timeout means the bridge was *busy*, not hung — the request can be retried. * Carries a machine-readable `code` so pollers (e.g. the bash_watch poll loop) * can distinguish "bridge busy, retry" from a genuine command failure without * string-matching the message. */ export declare class BridgeTransportTimeoutError extends Error { readonly command: string; readonly timeoutMs: number; readonly code: "transport_timeout"; constructor(command: string, timeoutMs: number, message: string); } /** A standalone bridge cannot carry a request because its process transport died. */ export declare class BridgeTransportUnavailableError extends Error { readonly code: "bridge_transport_unavailable"; constructor(message: string, options?: { cause?: unknown; }); } /** The transport failed after a request may have been written to the child. */ export declare class BridgeTransportUnknownOutcomeError extends BridgeTransportUnavailableError { constructor(message: string, options?: { cause?: unknown; }); } /** Type guard for a transport-timeout rejection (bridge busy, retryable). */ export declare function isBridgeTransportTimeout(err: unknown): err is BridgeTransportTimeoutError; export interface BridgeOptions { /** Request timeout in milliseconds. Default: 30000 */ timeoutMs?: number; /** * Consecutive silent request timeouts (no id-matched response) before the * bridge is killed and respawned. Default: 2. Child stdout activity since * the request still keeps the bridge warm regardless of this counter. */ hangThreshold?: number; /** * Extra environment variables to set on the spawned `aft` child process, * applied on top of the inherited `process.env` at spawn time. Use this to * scope per-bridge child env (e.g. `AFT_CACHE_DIR` in tests) WITHOUT mutating * the shared process-global `process.env` — mutating `process.env` races * across concurrent bridges and, because spawn is lazy (first `send()`), is * easily restored before the child actually inherits it. A value of * `undefined` deletes the key from the child env. */ childEnv?: Record; /** Maximum restart attempts before giving up. Default: 3 */ maxRestarts?: number; /** Minimum binary version required (semver). If the binary is older, onVersionMismatch is called. */ minVersion?: string; /** * Called when binary version is older than minVersion. Receives (binaryVersion, minVersion). * Return a replacement binary path to coordinate a one-shot retry, null to abort, or void for * legacy fire-and-forget behavior. */ onVersionMismatch?: VersionMismatchCallback; /** Called after the first successful configure returns user-visible warnings. */ onConfigureWarnings?: (context: ConfigureWarningsContext) => void | Promise; /** Called for server-pushed background bash completions. */ onBashCompletion?: (completion: BashCompletedPayload, bridge: BinaryBridge) => void | Promise; /** Called for server-pushed long-running bash reminders. */ onBashLongRunning?: (reminder: BashLongRunningPayload, bridge: BinaryBridge) => void | Promise; /** Called when a registered bash_watch pattern matches on stdout/stderr. */ onBashPatternMatch?: (frame: BashPatternMatchFrame, bridge: BinaryBridge) => void | Promise; /** Prefix for error messages. Default: "[aft-bridge]" */ errorPrefix?: string; /** Optional structured logger; falls back to active logger / console. */ logger?: Logger; } export interface BashCompletedPayload extends BgCompletion { type: "bash_completed"; session_id: string; } export interface BashLongRunningPayload { type: "bash_long_running"; task_id: string; session_id: string; command: string; elapsed_ms: number; } export interface BashPatternMatchFrame { type: "bash_pattern_match"; task_id: string; session_id: string; watch_id: string; match_text: string; match_offset: number; context: string; once: boolean; } export interface StatusSnapshot { version?: string; project_root?: string | null; canonical_root?: string | null; cache_role?: "main" | "worktree" | "not_initialized" | string; search_index?: Record; semantic_index?: Record; disk?: Record; lsp_servers?: number; runtime?: { live_watchers?: number; live_actor_roots?: number; open_routes?: number; }; symbol_cache?: Record; storage_dir?: string | null; features?: Record; compression?: StatusCompression; [key: string]: unknown; } export interface BridgeRequestOptions { onProgress?: (chunk: { kind: "stdout" | "stderr"; text: string; }) => void; /** * Host cancellation for one Rust request. Standalone sends `cancel_request`; * supervisor-backed subc closes the scoped route so the daemon cancels it. */ abortSignal?: AbortSignal; /** Per-call transport timeout in milliseconds. Defaults to the bridge-wide timeout. */ transportTimeoutMs?: number; /** * Skip bridge-hang escalation for this request. * * The default (false) treats a transport-level timeout as a possible bridge * hang. The bridge now escalates cautiously: a single timeout while the child * is still emitting stdout, or before the hang threshold is reached, rejects * only that request and keeps warm state. Repeated silent timeouts still kill * the child so the next call gets a fresh bridge. * * Some commands enforce their own timeouts on the Rust side (notably `bash`, * which uses a watchdog thread to terminate the child shell and return a * timeout response). For those, a transport timeout means the response was * lost or queued behind something else — the bridge itself is still healthy * and should keep its warm state (LSP servers, semantic index, callers * cache, undo history). Pass `keepBridgeOnTimeout: true` to reject the * request without contributing to hang escalation. */ keepBridgeOnTimeout?: boolean; } interface SendOptions extends BridgeRequestOptions { timeoutMs?: number; configureWarningClient?: unknown; markConfiguredOnSuccess?: boolean; } /** * Manages a persistent `aft` child process, communicating via NDJSON over * stdin/stdout. Lazy-spawns on first `send()` call. Handles crash detection * with exponential backoff auto-restart. */ export declare class BinaryBridge implements AftProjectTransport { private static readonly RESTART_RESET_MS; /** How many recent stderr lines to keep for crash diagnostics. */ private static readonly STDERR_TAIL_MAX; private binaryPath; private cwd; private process; /** Fingerprint of the on-disk binary contents captured when this child spawned. */ private spawnedBinaryFingerprint; /** Last idle-window binary refresh check for this live child. */ private lastBinaryFingerprintCheckAt; /** Once true, this bridge must not accept new requests and should be drained. */ private _retiringDueToBinaryChange; private pending; private outstandingBackgroundTaskIds; private nextId; private processGeneration; private stdoutBuffer; private stdoutReadOffset; private stderrBuffer; /** Ring buffer of the last N stderr lines, cleared on every spawn. */ private stderrTail; private _restartCount; private _shuttingDown; private timeoutMs; private hangThreshold; private maxRestarts; private configured; private _configurePromise; private configOverrides; private editSlotSurvives; private editSlotSurvivesCaptured; private readonly hashlineRegistrationLogState; private minVersion; private onVersionMismatch; private onConfigureWarnings; private onBashCompletion; private onBashLongRunning; private onBashPatternMatch; private cachedStatus; private statusListeners; /** Notification clients keyed by session_id for async configure warning pushes. */ private configureWarningClients; private restartResetTimer; /** Updated after every successfully parsed stdout frame from the child. */ private lastChildActivityAt; /** Consecutive non-bash-style request timeouts without an id-matched response. */ private consecutiveRequestTimeouts; private errorPrefix; private readonly logger; private readonly childEnv; private readonly platform; constructor(binaryPath: string, cwd: string, options?: BridgeOptions, configOverrides?: Record, editSlotSurvives?: boolean, platform?: NodeJS.Platform); private logVia; private warnVia; private errorVia; private getLogFilePathVia; private sessionLogVia; private sessionWarnVia; private sessionErrorVia; /** Number of times the binary has been restarted after a crash. */ get restartCount(): number; /** Whether the child process is currently alive. */ isAlive(): boolean; private invalidateTransportProcess; hasPendingRequests(): boolean; hasOutstandingBackgroundTasks(): boolean; /** * Idle-window maintenance hook: when the on-disk binary changed since this * child spawned, stop routing new work to this bridge so the pool can let it * drain and retire it. */ maybeScheduleRespawnForUpdatedBinary(checkIntervalMs: number, now?: number): boolean; /** Project root this bridge was spawned/configured for. */ getCwd(): string; /** Returns the latest pushed or primed status snapshot, or null before the cold path completes. */ getCachedStatus(): StatusSnapshot | null; /** * Subscribe to status updates. If a snapshot is already cached, the listener * is invoked synchronously before this method returns. Listener errors are * caught and logged so one subscriber cannot break delivery to others. */ subscribeStatus(listener: (snapshot: StatusSnapshot) => void): () => void; /** Seed the plugin-side cache from the direct `status` cold path. */ cacheStatusSnapshot(snapshot: StatusSnapshot): void; /** Capture the host registration fact once without restarting a bridge created before registration. */ setEditSlotSurvives(value: boolean): void; private logHashlineRegistrationCarrier; /** * Send a command to the binary and return the parsed response. * Lazy-spawns the binary on first call. */ send(command: string, params?: Record, options?: SendOptions): Promise>; /** * Dispatch an agent tool through the server-side `tool_call` command. * * The Rust command returns the direct leaf response plus one `text` field; * status_bar/bg_completions and every other sidecar stay top-level so the * existing plugin ingest/capture path sees the same raw response shape. */ private listenForRequestAbort; toolCall(sessionId: string | undefined, name: string, rawArgs?: ToolCallArguments, options?: ToolCallOptions): Promise; private sendWithVersionMismatchRetry; private deliverConfigureWarnings; /** * Handle the `configure_warnings` push frame the Rust binary emits after * configure has returned. The frame carries the warnings produced by the * deferred file walk + missing-binary detection. Forwards to the same * `onConfigureWarnings` handler used for synchronous warnings so plugins * don't need to know about the async path. */ private handleConfigureWarningsFrame; private handleStatusChangedFrame; private deliverStatusSnapshot; /** Kill the child process and reject all pending requests. */ shutdown(): Promise; /** Query binary version and compare against minVersion. Calls onVersionMismatch if outdated. */ private checkVersion; private replaceCurrentBinary; private ensureSpawned; private spawnProcess; private pushStderrLine; private onStderrData; private flushStderrBuffer; /** * Format the current stderr tail for inclusion in error messages. Returns * empty string when nothing has been captured (e.g., silent SIGKILL from * macOS amfid) so the caller can safely concatenate unconditionally. */ private formatStderrTail; private onStdoutData; private compactStdoutBuffer; private flushStdoutBuffer; private processStdoutLine; private accountForBashTaskResponse; private handleTimeout; private handleCrash; private rejectAllPending; private scheduleRestartCountReset; private clearRestartResetTimer; } export {}; //# sourceMappingURL=bridge.d.ts.map