import { BinaryBridge, type BridgeOptions } from "./bridge.js"; import type { Logger } from "./logger.js"; import type { AftTransportPool, ToolCallArguments, ToolCallOptions, ToolCallResult } from "./transport.js"; /** * Historical error class — kept for backwards-compatible imports. * * **No longer thrown by `BridgePool.getBridge()`.** Prior versions refused * to spawn a bridge when `project_root` resolved to `$HOME`, but that was * too restrictive: legitimate migration tasks (e.g. shell config sweeps, * dotfile maintenance) need to operate from `$HOME` directly. The Rust * `handle_configure` now auto-disables heavy subsystems * (`search_index`, `semantic_search`) and records `degraded_reasons: * ["home_root"]` on the status snapshot, so the bridge spawns fast, * `read`/`write`/`edit`/`bash` work, and the sidebar / `/aft-status` * surfaces the degraded state. See `crates/aft/src/commands/configure.rs` * for the full reasoning. * * Plugins still skip *eager* configure on `$HOME` (Desktop launches from * `~` shouldn't auto-warm a bridge no one asked for), but lazy configure * on the first real tool call works in degraded mode. */ export declare class HomeProjectRootError extends Error { readonly projectRoot: string; constructor(projectRoot: string); } /** * Test whether the given normalized project root matches the user's home * directory exactly. Subdirectories of `$HOME` are valid project roots and * pass through. */ export declare function isHomeDirectoryRoot(normalizedKey: string): boolean; export interface BridgeToolCallRuntime { sessionID?: string; } export interface PoolOptions extends BridgeOptions { maxPoolSize?: number; idleTimeoutMs?: number; logger?: Logger; /** * Optional per-project configure override loader. Called exactly once when * a new bridge is spawned for `projectRoot`, with the canonical (already * normalized) project root. Returned overrides are deep-merged on top of * the pool's global `configOverrides` and shallow-merged into the bridge's * configure payload (per-project values win). * * Use this when one plugin instance serves many projects (OpenCode Desktop / * `opencode serve`) and each project has its own `.opencode/aft.jsonc` whose * fields differ from the user-level config. Without this loader, only the * project config visible at plugin init reaches the Rust side; later * sessions opened in other projects inherit the wrong project's overrides. * * Caveats: * - The loader runs synchronously inside `getBridge()`. Keep it cheap. * - Existing bridges keep the overrides they were spawned with — this is * intentional so reloads don't blow away warm trigram/LSP/semantic state. * - The loader should ONLY return per-project-overridable fields. Truly * global fields (storage_dir, _ort_dylib_dir, harness, lsp_paths_extra) * belong in the pool's static `configOverrides` constructor argument. * * If the loader throws, the bridge falls back to global overrides only and * the error is logged via the pool logger. */ projectConfigLoader?: (projectRoot: string) => Record; } /** * Manages a pool of BinaryBridge instances, keyed by **canonical project root**. * * Prior to issue #14, the pool spawned one binary process per OpenCode session, * which duplicated every heavy in-memory structure (ONNX runtime, trigram and * semantic indexes, LSP state, symbol caches) N times for N sessions in the * same project. That produced an effective "leak" the user saw as many aft * processes consuming gigabytes of RAM on large repositories. * * The current design spawns **one bridge per project** and relies on the Rust * side to partition the small amount of truly session-scoped state (undo * history, named checkpoints) via the `session_id` envelope field attached by * the `callBridge()` helper. Sessions sharing a bridge still share the * latency of a single request pipeline; the trade-off is acceptable because * it removes the real RAM multiplier. */ export declare class BridgePool implements AftTransportPool { /** Project-root → bridge. Key is a normalized canonical path. */ private readonly bridges; private readonly staleBridges; private binaryPath; private readonly maxPoolSize; private readonly idleTimeoutMs; private readonly bridgeOptions; private readonly configOverrides; private editSlotSurvives; private editSlotSurvivesCaptured; private readonly projectConfigLoader; private readonly logger; private cleanupTimer; private shutdownCalled; constructor(binaryPath: string, options?: PoolOptions, configOverrides?: Record); /** * Get an alive bridge only when it belongs to the requested project root. * * Used by read-only paths (e.g. `/aft-status`, background-bash drains) that * want to reuse a warm bridge with loaded indexes/LSP state. Returns `null` * when no live bridge exists for `projectRoot`; callers typically fall back * to {@link BridgePool.getBridge} which will create one. Cross-project bridge * sharing is intentionally **not** supported — draining bg-completions or * status from another project's bridge mixes session-isolated state. */ getActiveBridgeForRoot(projectRoot: string): BinaryBridge | null; /** All live bridges, for session-scoped signals that must not depend on * exact root-key resolution (the command itself carries the session ID). */ activeBridges(): BinaryBridge[]; /** * Get or create the bridge for `projectRoot`. * * Callers should always pass a **canonical** project root (see * `projectRootFor()` in `tools/_shared.ts`). All sessions operating on the * same project share one bridge; their undo/checkpoint state is still * isolated by `session_id` on the Rust side. */ getBridge(projectRoot: string): BinaryBridge; toolCall(projectRoot: string, runtime: BridgeToolCallRuntime, name: string, rawArgs?: ToolCallArguments, options?: ToolCallOptions): Promise; /** Periodic pool maintenance: retire updated binaries and evict idle bridges. */ private cleanup; /** Evict the least recently used bridge to make room. */ private evictLRU; /** * No-op for the standalone transport: bridges are per-project (shared across a * project's sessions), and per-session state (undo/checkpoint/bash) lives * Rust-side keyed by session_id — there is nothing session-scoped to tear down * on the plugin side. Present to satisfy {@link AftTransportPool} so the subc * pool's per-session route teardown is callable transport-agnostically. */ closeSession(_projectRoot: string, _session: string): Promise; /** Shut down all bridges and stop the cleanup timer. */ shutdown(): Promise; /** A standalone pool can be restarted by its owner after shutdown. */ isShutdown(): boolean; /** * Replace the binary path and restart all bridges. * Used after downloading a newer binary version. */ replaceBinary(newPath: string): Promise; private startCleanupTimer; private log; private error; /** * Update a runtime configure override for future bridge spawns. Existing bridges * keep those mutable runtime values so their warm state is not discarded. * * `edit_slot_survives` is host registration state instead: its first boolean * value is captured separately and forwarded to every existing and future bridge. * Any later write is a lifecycle error rather than a mutable config update. */ setConfigureOverride(key: string, value: unknown): void; /** * Reapply configure overrides to a live bridge without replacing it. * * Async resources such as the LSP install cache can become available after a * bridge has configured. Sending a normal configure request keeps the bridge's * warm Rust state while allowing those process-state paths to take effect. */ reconfigure(projectRoot: string, overrides: Record): Promise; private loadProjectOverrides; /** Number of active bridges in the pool. */ get size(): number; /** * Test-only: read the current configure-override map. * * NEVER call this from production code. The override map is intentionally * private because the contract is "applied at next spawn" — exposing the * live map invites callers to mutate it directly and bypass the lifecycle. * This getter is here so tests for `setConfigureOverride` can verify the * mutation result without spawning real binaries. */ _testGetConfigOverrides(): Readonly>; /** * Test-only view of the per-bridge options forwarded to every spawned * `BinaryBridge`. Lets tests assert that documented `BridgeOptions` fields * (e.g. `childEnv`) are actually propagated through the pool rather than * silently dropped. */ _testGetBridgeOptions(): Readonly; } //# sourceMappingURL=pool.d.ts.map