/** * `ExtensionProcess` — one extension subprocess, its ndjson codec, and its * request/response plumbing. The TS sibling of the Rust host's * `extension/process.rs`. * * Framing is identical to MCP stdio: one JSON-RPC message per line on the child's * stdin/stdout, stderr drained to host logging. Inbound responses route to their * pending caller; inbound requests go to an {@link InboundHandler}. * * Restart is in-place ({@link ExtensionProcess.respawn}): a generation counter is * bumped so a stale reader from the dead child can't resolve a request registered * against the new child, and every in-flight request fails fast. */ /** * Backoff schedule (ms) for restart attempts. After the third failed attempt the * host marks the extension failed and stops trying. */ export declare const RESTART_BACKOFFS_MS: readonly number[]; /** Idle interval (ms) after which the host should health-probe with `ping`. */ export declare const PING_IDLE_MS = 60000; /** * Bounded depth of the per-connection observe (`event`) lane. When a slow or * stalled extension lets events pile past this, the OLDEST are shed and an * `events_lost` marker is delivered on recovery — observe events are lossy by * contract. Requests (hook/tool/ping/shutdown) are NEVER shed; they ride the * reliable control lane (a direct stdin write). */ export declare const OBSERVE_QUEUE_CAP = 1024; /** * Backoff (ms) for restart `attempt` (0-indexed). `undefined` once attempts are * exhausted — the caller transitions the extension to failed. */ export declare function backoffFor(attempt: number): number | undefined; /** * Handles ext→host requests and notifications. The default answers `ping` and * rejects everything else with MethodNotFound; the host supplies a richer impl * once ext→host methods (session/ui/kv/…) are wired. */ export interface InboundHandler { handleRequest(method: string, params: unknown): Promise; handleNotification(method: string, params: unknown): void; } /** The trivial handler: ping only. Used when the host wires nothing richer. */ export declare class DefaultInboundHandler implements InboundHandler { handleRequest(method: string, _params: unknown): Promise; handleNotification(_method: string, _params: unknown): void; } /** How to launch the subprocess. The manifest owns the full shape; this is what `spawn` needs. */ export interface SpawnSpec { command: string; args: string[]; /** * Extra env vars the extension legitimately needs (its manifest `[run] env`, * SEP-protocol vars). These are the ONLY env the child sees beyond the small * {@link ENV_PASSTHROUGH} allow-list — the host's full environment is * scrubbed so ambient secrets can't leak in. See {@link buildChildEnv}. */ env: Record; /** Working directory for the child (the extension's root). */ cwd?: string; } /** * The ONLY host environment variables passed through to an extension subprocess. * Everything else is scrubbed (see {@link buildChildEnv}) so ambient secrets — * cloud creds (`AWS_SECRET_ACCESS_KEY`), API tokens, `GITHUB_TOKEN`, … — can * never leak into an extension via inherited env (the lethal-trifecta concern). * * These are launch essentials only, and none is secret: * - `PATH` — resolve a bare-name interpreter (`node`, `python3`) and its own * subprocess lookups. Without it a non-absolute `command` won't even start. * - `HOME` — interpreters read user config/caches from it; some abort without it. * - `LANG` / `LC_ALL` / `LC_CTYPE` — locale; python3 errors on non-ASCII I/O unset. * - `TMPDIR` — where the child writes temp files (macOS/BSD). * - `TERM` — interpreters that probe for a tty degrade gracefully with it. * - `SystemRoot` — Windows: `node.exe`/`python.exe` fail to start without it. * * SEP-protocol vars and anything else an extension legitimately needs come * through its manifest `[run] env` (carried in {@link SpawnSpec.env}), NOT from * here — so adding a var is a deliberate, per-extension act, never ambient. */ export declare const ENV_PASSTHROUGH: readonly string[]; /** * Build the exact environment an extension child sees: the {@link ENV_PASSTHROUGH} * allow-list pulled from the host (via `lookup`), then `explicit` (the manifest * env) overlaid on top so an extension can still *set* — but never silently * *inherit* — any var. `lookup` is injected so this is a pure, exhaustively * testable function (the caller passes `process.env`). */ export declare function buildChildEnv(lookup: Record, explicit: Record): Record; /** One extension subprocess. */ export declare class ExtensionProcess { private readonly spec; private readonly handler; private readonly pending; private generation; private nextId; private alive; private conn; private constructor(); /** Spawn the subprocess and start its reader. Throws if it can't be spawned. */ static spawn(spec: SpawnSpec, handler: InboundHandler): ExtensionProcess; private startConnection; /** Parse and route one inbound line. */ private dispatchLine; private handleInboundRequest; /** * Send a request and await its response, bounded by `timeoutMs`. Rejects if * the connection is dead, the request times out (it also sends `$/cancel`), or * the extension replies with a JSON-RPC error. */ request(method: string, params: unknown, timeoutMs: number): Promise; /** * Best-effort `$/cancel` for an in-flight request `id`. The peer SHOULD stop * work; a cancel for an already-answered id is a harmless no-op. */ cancel(id: number): void; /** Send a fire-and-forget notification on the reliable control lane. */ notify(method: string, params: unknown): void; /** * Enqueue an observe `event` on the bounded, lossy lane. Assigns the frame a * per-connection sequence; sheds the oldest queued event (tracked for the next * `events_lost` marker) rather than block or grow unbounded when the extension * is not draining its stdin. Never throws — a shed event is the contract. */ sendEvent(event: string, context: unknown, payload: unknown): void; /** Whether the connection is currently believed alive. */ isAlive(): boolean; /** Current generation (increments on every successful respawn). */ getGeneration(): number; /** Health-probe with `ping`; resolves `true` if answered within `timeoutMs`. */ pingHealth(timeoutMs: number): Promise; /** * Kill and re-spawn the child in place. Bumps the generation (invalidating any * stale reader and failing every in-flight request), then starts a fresh * connection. `nextId` is NOT reset, so ids never collide across generations. */ respawn(): void; /** * Graceful shutdown: send `shutdown`, wait up to `graceMs` for the reply, then * force-kill. Always leaves the process dead. */ shutdown(graceMs: number): Promise; /** Serialize a frame as ndjson to the child stdin. Returns false on any error. */ private writeFrame; /** Drain the observe lane to stdin, honoring backpressure via `drain`. */ private pumpObserve; /** Fail every pending request with the same error message. */ private failAllPending; /** Tear down the current connection's child + readers. */ private abortConnection; } //# sourceMappingURL=process.d.ts.map