@tangle-network/sdk-cli-runner 0.0.0-stage → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ Copyright (c) 2025 Tangle Network
2
+
3
+ All rights reserved.
4
+
5
+ This software and associated documentation files (the "Software") are proprietary
6
+ and confidential. No part of this Software may be reproduced, distributed, or
7
+ transmitted in any form or by any means, including photocopying, recording, or
8
+ other electronic or mechanical methods, without the prior written permission of
9
+ Tangle Network.
10
+
11
+ For licensing inquiries, contact: hello@tangle.tools
@@ -0,0 +1,584 @@
1
+ import { ChildProcess, SpawnOptions } from "node:child_process";
2
+
3
+ //#region src/attachment-error.d.ts
4
+ /** An attachment cannot be delivered to a native CLI. */
5
+ declare class InvalidAttachmentError extends Error {
6
+ readonly code = "invalid_attachment";
7
+ readonly statusCode = 400;
8
+ constructor(message: string);
9
+ }
10
+ //#endregion
11
+ //#region src/types.d.ts
12
+ /**
13
+ * Configuration for spawning a CLI process.
14
+ */
15
+ interface CliSpawnConfig {
16
+ /** The command to execute */
17
+ command: string;
18
+ /** Arguments to pass to the command */
19
+ args: string[];
20
+ /** Working directory for the process */
21
+ cwd: string;
22
+ /** Additional environment variables. `undefined` values are an explicit
23
+ * scrub signal — the runner deletes the corresponding key from
24
+ * process.env before spawn. Use this to unset inherited auth vars
25
+ * (e.g. ANTHROPIC_API_KEY in OAuth mode). */
26
+ env?: Record<string, string | undefined>;
27
+ /** Start from an empty environment instead of inheriting process.env. */
28
+ inheritEnv?: boolean;
29
+ /** Abort signal for cancellation */
30
+ signal?: AbortSignal;
31
+ /** Wall-clock timeout in milliseconds (default: no timeout). Callers
32
+ * usually bound a turn through `signal` instead. */
33
+ timeoutMs?: number;
34
+ /** Additional spawn options */
35
+ spawnOptions?: SpawnOptions;
36
+ /** Root executor only: drop supplementary groups and privilege before exec. */
37
+ isolatedIdentity?: {
38
+ uid: number;
39
+ gid: number;
40
+ };
41
+ /** Sidecar-owned append-only raw process stream, outside the child environment. */
42
+ rawCapturePath?: string;
43
+ /**
44
+ * With `rawCapturePath`: open the capture at `beginRawCapture`, not at spawn.
45
+ * A process started before the turn it serves (a held spare) opens it inside
46
+ * that turn, so its frames carry the turn's attempt identity and retention.
47
+ * What it prints before then is kept, in order, and written first; a process
48
+ * that prints more than 1 MiB before then is stopped.
49
+ */
50
+ rawCaptureDeferred?: boolean;
51
+ /**
52
+ * Start the child yourself (an executor slot, a jail) instead of `spawn`.
53
+ * Only `launchCliProcess` and `execCli` take it, because a launch may wait
54
+ * for capacity; the runner still owns capture, deadlines, abort and exit.
55
+ */
56
+ launcher?: CliLauncher;
57
+ /**
58
+ * Treat the CLI's whole process group as the run: members still alive when
59
+ * the leader exits are stopped before the exit is reported, and one that
60
+ * outlives SIGKILL fails the run. Off by default, because an agent's turn
61
+ * may start a process meant to outlive it (a dev server a preview serves).
62
+ */
63
+ ownProcessGroup?: boolean;
64
+ /**
65
+ * What a child that rejects its stdin (closes the pipe early) gets. "stop"
66
+ * (default) stops it, because an adapter's turn cannot continue without its
67
+ * input. "report" lets it exit on its own and reports `stdinError`, for a
68
+ * caller that judges the exit itself.
69
+ */
70
+ stdinFailure?: "stop" | "report";
71
+ }
72
+ /** What a consumer-owned launch is asked to start, after env assembly. */
73
+ interface CliLaunchSpec {
74
+ command: string;
75
+ args: string[];
76
+ cwd: string;
77
+ env: Record<string, string | undefined>;
78
+ stdio: Exclude<SpawnOptions["stdio"], undefined>;
79
+ detached: boolean;
80
+ signal?: AbortSignal;
81
+ }
82
+ /** A child a consumer started, and how its whole extent ends. */
83
+ interface CliLaunch {
84
+ child: ChildProcess;
85
+ /** Stop everything the launch started (a container, a scope). Called once, on the first kill. */
86
+ terminate?(): Promise<void>;
87
+ /** Give back what the launch held (an executor slot). Called once, after exit. */
88
+ release?(): void;
89
+ }
90
+ /**
91
+ * Starts one CLI child. Resolve as soon as the child is spawned, with no
92
+ * further await: the runner attaches its exit and error listeners when the
93
+ * launch settles, and a spawn failure is emitted on the next tick.
94
+ */
95
+ type CliLauncher = (spec: CliLaunchSpec) => CliLaunch | Promise<CliLaunch>;
96
+ /**
97
+ * The bound that killed a process the runner timed out. The runner has one:
98
+ * the fixed `timeoutMs` wall clock.
99
+ */
100
+ type CliTimeoutReason = "fixed";
101
+ /**
102
+ * Result from a CLI process exit.
103
+ */
104
+ interface CliExitResult {
105
+ /** Exit code (null if killed by signal) */
106
+ code: number | null;
107
+ /** Signal that killed the process (null if exited normally) */
108
+ signal: NodeJS.Signals | null;
109
+ /** Whether the process exited successfully (code 0) */
110
+ success: boolean;
111
+ /** Whether the process was killed by timeout */
112
+ timedOut: boolean;
113
+ /** The bound that killed the process. Set only when `timedOut` is true, so
114
+ * a caller can branch on the cause instead of on a bare boolean. */
115
+ timeoutReason?: CliTimeoutReason;
116
+ /** What that bound observed: the command, the budget it exceeded and the
117
+ * time it ran. Set only when `timedOut` is true. Callers put it in the
118
+ * failure they raise, so the operator reads the cause and not "timed out".
119
+ */
120
+ timeoutDiagnostic?: string;
121
+ /** Message from the spawn-level `error` event (e.g. ENOENT when the
122
+ * binary is not on PATH). Set only when the process failed to spawn or
123
+ * was lost at the OS level — callers must surface this instead of
124
+ * reporting a bare `code: null`, which is indistinguishable from a
125
+ * signal kill. */
126
+ spawnError?: string;
127
+ /** The native child closed or rejected stdin before its input was delivered. */
128
+ stdinError?: string;
129
+ /** The errno code of `stdinError` (EPIPE for a pipe the child closed). */
130
+ stdinErrorCode?: string;
131
+ /** Raw evidence could not be written or flushed; the process is not a complete trace. */
132
+ captureError?: string;
133
+ /** The caller's abort signal fired before the process exited. */
134
+ aborted?: boolean;
135
+ /** Members of the process group outlived SIGKILL after the leader exited. */
136
+ processGroupError?: string;
137
+ /** A consumer launch's `terminate` failed. */
138
+ terminateError?: string;
139
+ }
140
+ /**
141
+ * Handle to a running CLI process.
142
+ */
143
+ interface CliProcess {
144
+ /** Process ID */
145
+ pid: number | undefined;
146
+ /** The underlying child process */
147
+ process: ChildProcess;
148
+ /** Write data to stdin */
149
+ write(data: CliStdinValue): void;
150
+ /** Close stdin (signals end of input) */
151
+ closeInput(): void;
152
+ /** Kill the process */
153
+ kill(signal?: NodeJS.Signals): boolean;
154
+ /** Promise that resolves when the process exits */
155
+ exitPromise: Promise<CliExitResult>;
156
+ /**
157
+ * Open a deferred raw capture (`rawCaptureDeferred`) in the caller's attempt.
158
+ * Throws when the capture already failed (the process was told to stop); a
159
+ * no-op for any other process, or once the process has exited.
160
+ */
161
+ beginRawCapture?(): void;
162
+ }
163
+ /**
164
+ * Configuration for stream parsing.
165
+ *
166
+ * Three modes cover every CLI output shape we've encountered:
167
+ *
168
+ * - **"json"** (default): newline-delimited JSON. Each line is parsed via
169
+ * `JSON.parse`, then passed to `transform`. Used by claude-code, codex.
170
+ * - **"lines"**: plaintext with one logical event per line. Each line is
171
+ * passed to `transform` as a raw trimmed string with no JSON parsing.
172
+ * Used by hermes quiet mode plaintext response lines.
173
+ * - **"raw"**: stdout chunks delivered verbatim with no per-line splitting.
174
+ * `transform` is NOT called; instead the parser maintains a single
175
+ * accumulating buffer that the consumer reads in `onComplete`. Used by
176
+ * openclaw (`agent --json` emits one buffered JSON object across the
177
+ * entire stdout stream — pretty-printed envelopes with embedded newlines
178
+ * would corrupt under "lines" reassembly).
179
+ *
180
+ * Picking the right mode matters: "lines" reassembled via `+ "\n"` looks
181
+ * like raw mode but silently drops any internal blank lines (the line
182
+ * splitter trims them before transform). Use "raw" whenever the unit is
183
+ * the whole stream, not the line.
184
+ */
185
+ interface StreamParserConfig<T> {
186
+ /**
187
+ * Parse mode. See the interface docstring for the contract per mode.
188
+ * Defaults to "json" for backwards compatibility with the original
189
+ * JSONL-only API.
190
+ */
191
+ parseAs?: "json" | "lines" | "raw";
192
+ /**
193
+ * Transform a parsed event to the desired type (return null to skip).
194
+ * In "json" mode the input is the JSON.parse result; in "lines" mode it
195
+ * is the raw trimmed line string. **Ignored in "raw" mode** — the
196
+ * consumer reads the full buffer via `onRawComplete`.
197
+ */
198
+ transform?: (raw: unknown) => T | null;
199
+ /** Called for each parsed event ("json" and "lines" modes only). */
200
+ onEvent: (event: T) => void;
201
+ /** Called on parse errors (only fires in "json" mode) */
202
+ onError?: (error: Error, rawLine: string) => void;
203
+ /**
204
+ * Called when the stream completes.
205
+ *
206
+ * In "raw" mode, the full untouched stdout buffer is passed as the
207
+ * argument so consumers can parse it however they want (single
208
+ * `JSON.parse`, regex extraction, etc). In "json" and "lines" modes the
209
+ * argument is undefined — events were already delivered via `onEvent`.
210
+ */
211
+ onComplete?: (rawBuffer?: string) => void;
212
+ }
213
+ /**
214
+ * A stream parser instance that can process chunks of data.
215
+ */
216
+ interface StreamParser<_T> {
217
+ /** Process a chunk of data */
218
+ push(chunk: string): void;
219
+ /** Signal end of stream */
220
+ end(): void;
221
+ }
222
+ /**
223
+ * Result from execCli (one-shot execution).
224
+ */
225
+ interface CliExecResult<T> {
226
+ /** Exit result from the process */
227
+ exit: CliExitResult;
228
+ /** Collected stdout (when no parser was given) */
229
+ stdout?: string;
230
+ /** Collected stderr */
231
+ stderr: string;
232
+ /** Collected events (if streaming parser was used) */
233
+ events?: T[];
234
+ /** Wall clock from launch to exit. */
235
+ durationMs: number;
236
+ }
237
+ /** Bounds of what `execCli` keeps in memory. */
238
+ interface CliExecOptions {
239
+ /** Newest stdout and stderr bytes retained per stream. Default 64 MiB. */
240
+ maxOutputBytes?: number;
241
+ /** Written to the child's stdin, which then closes. Omitted: stdin stays as the config leaves it. */
242
+ stdin?: string;
243
+ }
244
+ /**
245
+ * One value the runner writes to a child process's standard input. A string
246
+ * goes through unchanged; any other value is serialized as JSON. The type is
247
+ * named so a caller cannot hand in an arbitrary object whose shape the runner
248
+ * cannot serialize.
249
+ */
250
+ type CliStdinValue = string | Record<string, unknown>;
251
+ //#endregion
252
+ //#region src/cli-process.d.ts
253
+ /**
254
+ * Spawns a CLI process with streaming support.
255
+ *
256
+ * @param config - Configuration for the process
257
+ * @param parserConfig - Optional streaming parser for stdout
258
+ * @returns Handle to the running process
259
+ */
260
+ declare function spawnCliProcess<T = unknown>(config: CliSpawnConfig, parserConfig?: StreamParserConfig<T>): CliProcess;
261
+ /**
262
+ * Start a CLI through the config's `launcher` (or `spawn` without one) and
263
+ * attach the same capture, deadlines, abort and exit handling as
264
+ * `spawnCliProcess`. The launcher receives the fully assembled environment.
265
+ */
266
+ declare function launchCliProcess<T = unknown>(config: CliSpawnConfig, parserConfig?: StreamParserConfig<T>): Promise<CliProcess>;
267
+ /**
268
+ * Executes a CLI command and waits for completion. Without a parser it
269
+ * returns stdout; stdout and stderr keep their newest `maxOutputBytes`.
270
+ */
271
+ declare function execCli<T = unknown>(config: CliSpawnConfig, parserConfig?: StreamParserConfig<T>, options?: CliExecOptions): Promise<CliExecResult<T>>;
272
+ //#endregion
273
+ //#region src/held-spare.d.ts
274
+ /** The process handle a spare keeps and a turn takes over. */
275
+ type HeldSpareProcess = Pick<CliProcess, "pid" | "write" | "closeInput" | "kill" | "exitPromise" | "beginRawCapture"> & {
276
+ stderr?: NodeJS.ReadableStream;
277
+ };
278
+ /**
279
+ * A parser that holds the spare's output until a turn adopts the process, then
280
+ * hands the turn's own parser everything the CLI printed so far, in order.
281
+ */
282
+ declare function createDeferredParser<T>(): {
283
+ config: StreamParserConfig<T>;
284
+ attach(target: StreamParserConfig<T>): void;
285
+ };
286
+ /** How long a spare no turn adopted keeps its process. */
287
+ declare const HELD_SPARE_IDLE_MS = 120000;
288
+ /** A spare is idle until a turn reserves or claims it, or it is stopped. */
289
+ type HeldSpareState = "idle" | "reserved" | "claimed" | "stopped";
290
+ interface HeldSpareFields<TEvent> {
291
+ /** Names the spare to whoever must tell its requests from a turn's. */
292
+ readonly id: string;
293
+ readonly home: string;
294
+ readonly workspaceRoot: string;
295
+ readonly args: readonly string[];
296
+ /** The spare's environment as a turn compares it (see launchDifferences). */
297
+ readonly env: Readonly<Record<string, string | undefined>>;
298
+ readonly process: HeldSpareProcess;
299
+ readonly parser: ReturnType<typeof createDeferredParser<TEvent>>;
300
+ readonly controller: AbortController;
301
+ readonly startedAt: number;
302
+ state: HeldSpareState;
303
+ idleTimer?: ReturnType<typeof setTimeout>;
304
+ }
305
+ /** A spare, plus what the harness records about its launch (`TExtra`). */
306
+ type HeldSpare<TEvent, TExtra extends object = object> = HeldSpareFields<TEvent> & TExtra;
307
+ interface HeldSpareRegistry<TEvent, TExtra extends object = object> {
308
+ /** The spare running or stopping in `home`, if any. */
309
+ spareFor(home: string | undefined): HeldSpare<TEvent, TExtra> | undefined;
310
+ register(spare: HeldSpare<TEvent, TExtra>): void;
311
+ /** True while a spare in `home` runs or is stopping: its HOME is not free. */
312
+ has(home: string): boolean;
313
+ /** Stop a spare no turn runs on and forget it. */
314
+ stop(spare: HeldSpare<TEvent, TExtra>): void;
315
+ /** A turn runs on the spare: it is the turn's process now, not a spare. */
316
+ claim(spare: HeldSpare<TEvent, TExtra>): void;
317
+ /**
318
+ * The turn takes the spare's process: the spare is claimed, its raw capture
319
+ * opens in the turn's attempt (what the process printed before is the
320
+ * turn's too), its output so far and from now on goes to the turn's parser,
321
+ * and the turn's abort signal stops it. Returns the handle the turn drives.
322
+ * Throws for a spare that is stopped or already claimed, and stops the spare
323
+ * and throws when its capture cannot open or already failed while held; the
324
+ * turn then starts its own process.
325
+ */
326
+ adopt(spare: HeldSpare<TEvent, TExtra>, turn: {
327
+ parser: StreamParserConfig<TEvent>;
328
+ signal: AbortSignal;
329
+ }): HeldSpareProcess;
330
+ /** Stop every spare no turn claimed, and wait for the processes to exit. */
331
+ discard(): Promise<void>;
332
+ }
333
+ declare function createHeldSpareRegistry<TEvent, TExtra extends object = object>(): HeldSpareRegistry<TEvent, TExtra>;
334
+ /** The argument after `flag`, if any. */
335
+ declare function argAfter(args: readonly string[], flag: string): string | undefined;
336
+ /**
337
+ * Whether two launches are the same process. `comparable` maps an environment
338
+ * value to what must match (a harness drops the lines it allows to differ);
339
+ * the default compares values as they are. Returns the names of what differs
340
+ * (never values: environments hold credentials), empty when equal.
341
+ */
342
+ declare function launchDifferences(spare: Pick<HeldSpareFields<unknown>, "args" | "env">, turn: {
343
+ args: readonly string[];
344
+ env: Readonly<Record<string, string | undefined>>;
345
+ }, comparable?: (key: string, value: string | undefined) => string | undefined): string[];
346
+ //#endregion
347
+ //#region src/raw-capture.d.ts
348
+ /** Hard encoded-byte bound across canonical JSON spool files, including live writers.
349
+ * Temporary trim copies and filesystem allocation are separate from this budget.
350
+ */
351
+ declare const RAW_CAPTURE_TOTAL_CAP_BYTES: number;
352
+ type RawCaptureMode = "none" | "tail" | "full";
353
+ /** The root-private spool: one `<sha256(HOME)>/process-io.frames` per runtime HOME. */
354
+ declare const RAW_CAPTURE_ROOT = "/root/.sidecar/raw-evidence";
355
+ type RawCaptureAttempt = {
356
+ executionId: string;
357
+ ordinal: number;
358
+ providerSessionId: string;
359
+ };
360
+ /** Bind Sidecar-owned attempt identity before entering a provider adapter. */
361
+ declare function withRawCaptureAttempt<T>(attempt: RawCaptureAttempt, run: () => Promise<T>): Promise<T>;
362
+ /** The sidecar owns this directory; the CLI's writable HOME cannot contain its evidence. */
363
+ declare function resolveRawCapturePath(runtimeHome: string): string;
364
+ /**
365
+ * Opt one runtime HOME in to full capture, inside the shared byte budget, when
366
+ * the platform default is off. The Sidecar calls this for a session created with retention
367
+ * "workspace", whose retained evidence must include process I/O. An explicit
368
+ * SIDECAR_RAW_CAPTURE_MODE=none or PHI redaction still disables it.
369
+ */
370
+ declare function allowRetainedRawCapture(runtimeHome: string): void;
371
+ /** Delete a session's private spool only after its evidence has been archived. */
372
+ declare function removeRawCapture(runtimeHome: string): void;
373
+ type Stream = "stdin" | "stdout" | "stderr" | "protocol";
374
+ /** One process appends exact byte frames to a sidecar-owned session file. */
375
+ declare function openRawCapture(file: string, options?: {
376
+ unbound?: boolean;
377
+ mode?: RawCaptureMode;
378
+ }): {
379
+ frame(_stream: Stream, _bytes: Buffer, _metadata?: Record<string, unknown>): void;
380
+ close(_result: Record<string, unknown>): void;
381
+ };
382
+ interface RawCaptureProcess {
383
+ processId: string;
384
+ executionId?: string;
385
+ ordinal?: number;
386
+ /** Bytes of this process's lines in the file. */
387
+ bytes: number;
388
+ /** `at` of its first frame, and its position in the file. */
389
+ firstAt: string;
390
+ firstOffset: number;
391
+ /** Its terminal record is in the file. */
392
+ closed: boolean;
393
+ /** This sidecar process still holds its writer; it is never trimmed. */
394
+ live: boolean;
395
+ }
396
+ /** The processes in one capture file, in file order, with their sizes. */
397
+ declare function summarizeRawCapture(file: string): Promise<{
398
+ sizeBytes: number;
399
+ processes: RawCaptureProcess[];
400
+ }>;
401
+ /**
402
+ * Remove whole processes from a capture file. A process is dropped with all
403
+ * of its lines or not at all, so every remaining process still reads as a
404
+ * complete sequence. A process this sidecar is still writing is never
405
+ * dropped. Frames written while the file is rewritten are carried into the
406
+ * replacement before it takes the path.
407
+ */
408
+ declare function trimRawCapture(file: string, drop: ReadonlySet<string>): Promise<{
409
+ beforeBytes: number;
410
+ afterBytes: number;
411
+ droppedBytes: number;
412
+ }>;
413
+ //#endregion
414
+ //#region src/rpc-bridge-protocol.d.ts
415
+ /**
416
+ * Long-running stdio JSON-RPC 2.0 client over NDJSON framing.
417
+ *
418
+ * Built for the Agent Client Protocol (ACP) — every ACP-compliant agent
419
+ * binary speaks JSON-RPC 2.0 over stdin/stdout with one envelope per line
420
+ * (`JSON.stringify(msg) + "\n"`, no Content-Length headers). Reusable for
421
+ * any future stdio-bridge CLI with the same wire format (LSP variants,
422
+ * MCP-over-stdio, custom protocols).
423
+ *
424
+ * Design contract:
425
+ *
426
+ * - **One bridge = one child process.** Callers own session isolation and
427
+ * turn serialization. ACP uses one bridge per execute; protocols such as
428
+ * Codex app-server can retain the bridge across serialized native threads.
429
+ * A retained process binds capture separately for each admitted turn.
430
+ *
431
+ * - **Bounded.** Pending request map is capped (default 1024) to prevent
432
+ * runaway memory if the agent hangs without responding. Stderr is
433
+ * captured into a ring buffer (default 64 KiB) so a noisy agent can't
434
+ * exhaust memory.
435
+ *
436
+ * - **Fail-loud on close.** Pending requests reject with `BridgeClosedError`
437
+ * when the child exits — no silent hangs.
438
+ *
439
+ * - **Server-style request handlers.** ACP agents make reverse calls
440
+ * (`fs/read_text_file`, `terminal/*`, `session/request_permission`).
441
+ * `onRequest()` registers a handler that returns the JSON-RPC `result`
442
+ * field; throws map to JSON-RPC errors. One handler per method (last
443
+ * write wins is rejected — registration throws on duplicate).
444
+ */
445
+ interface RpcBridgeLogger {
446
+ debug: (msg: string, meta?: Record<string, unknown>) => void;
447
+ warn: (msg: string, meta?: Record<string, unknown>) => void;
448
+ error: (msg: string, meta?: Record<string, unknown>) => void;
449
+ }
450
+ interface RpcBridgeConfig {
451
+ command: string;
452
+ args: string[];
453
+ cwd: string;
454
+ /** `undefined` scrubs an inherited variable; see `mergeSpawnEnv`. */
455
+ env?: Record<string, string | undefined>;
456
+ /** AbortSignal — closes the bridge when aborted. */
457
+ signal?: AbortSignal;
458
+ /** Cap on outstanding requests. Default 1024. */
459
+ maxPendingRequests?: number;
460
+ /** Stderr ring-buffer size in bytes. Default 65536. */
461
+ maxStderrBytes?: number;
462
+ /**
463
+ * Default per-request timeout (ms). Each `request()` rejects after this
464
+ * many ms if no response — protects callers from hanging forever when the
465
+ * agent silently drops a request. Per-call override available on
466
+ * `request(method, params, { timeoutMs })`. Default: 60_000.
467
+ */
468
+ defaultRequestTimeoutMs?: number;
469
+ /** Default grace window for `close()` if not specified at the call site. */
470
+ defaultCloseGraceMs?: number;
471
+ /**
472
+ * Hard wall-clock cap on `close()` — if the drain → SIGTERM → SIGKILL
473
+ * sequence has not completed, a logical close never claims physical exit.
474
+ * Unowned transports may retry escalation on a later close. Owned-process
475
+ * cleanup rejects on a live survivor or observation fault and retains that
476
+ * failure as its resource fence. Default: 10_000 ms.
477
+ */
478
+ hardCloseDeadlineMs?: number;
479
+ logger?: RpcBridgeLogger;
480
+ /** Trusted session-local file for exact process I/O frames. */
481
+ rawCapturePath?: string;
482
+ /** Keep lifetime bytes unbound; captureTurn binds each admitted retained-process turn. */
483
+ rawCapturePerTurn?: boolean;
484
+ uid?: number;
485
+ gid?: number;
486
+ /** Trusted root launch: use the maintained group scrub and privilege boundary. */
487
+ isolatedIdentity?: CliSpawnConfig["isolatedIdentity"];
488
+ /** Own a POSIX process group; on Linux, also track observed descendants that detach. Default false. */
489
+ detached?: boolean;
490
+ /** Wait for native EOF cleanup before forced termination. Default 0; included in the hard cap. */
491
+ stdinCloseGraceMs?: number;
492
+ }
493
+ interface RpcRequestOptions {
494
+ /** Override the bridge's default per-request timeout (ms). */
495
+ timeoutMs?: number;
496
+ }
497
+ type RpcNotificationHandler<TParams = unknown> = (params: TParams) => void | Promise<void>;
498
+ type RpcRequestHandler<TParams = unknown, TResult = unknown> = (params: TParams) => TResult | Promise<TResult>;
499
+ interface RpcCloseReason {
500
+ code: number | null;
501
+ signal: NodeJS.Signals | null;
502
+ error?: Error;
503
+ reason: "graceful" | "process-exit" | "process-error" | "aborted";
504
+ }
505
+ interface RpcBridge {
506
+ /** Capture one serialized turn in its caller's trusted execution context. */
507
+ captureTurn(path: string, metadata?: Record<string, unknown>): (result: Record<string, unknown>) => void;
508
+ request<TResult = unknown>(method: string, params?: unknown, options?: RpcRequestOptions): Promise<TResult>;
509
+ notify(method: string, params?: unknown): void;
510
+ /** Returns an unsubscribe function. Multiple handlers per method allowed. */
511
+ onNotification<TParams = unknown>(method: string, handler: RpcNotificationHandler<TParams>): () => void;
512
+ /** ONE handler per method. Throws on duplicate. Returns unsubscribe. */
513
+ onRequest<TParams = unknown, TResult = unknown>(method: string, handler: RpcRequestHandler<TParams, TResult>): () => void;
514
+ /** Fires once after closure and any owned cleanup attempt; failures carry process-error. */
515
+ onClose(handler: (reason: RpcCloseReason) => void): () => void;
516
+ /** Graceful close: stop accepting new requests, drain in-flight, then SIGTERM, then SIGKILL. */
517
+ close(graceMs?: number): Promise<void>;
518
+ /** Tail of stderr captured into the ring buffer. */
519
+ getStderrTail(): string;
520
+ readonly pid: number | undefined;
521
+ readonly closed: boolean;
522
+ readonly process: ChildProcess;
523
+ }
524
+ //#endregion
525
+ //#region src/rpc-bridge-errors.d.ts
526
+ declare class JsonRpcError extends Error {
527
+ readonly code: number;
528
+ readonly data?: unknown | undefined;
529
+ constructor(code: number, message: string, data?: unknown | undefined);
530
+ }
531
+ declare class BridgeClosedError extends Error {
532
+ /** The child's last stderr, bounded; kept off the message, which callers log. */
533
+ readonly stderrTail?: string;
534
+ constructor(reason: string, options?: {
535
+ cause?: Error;
536
+ stderrTail?: string;
537
+ });
538
+ }
539
+ declare class RpcRequestTimeoutError extends Error {
540
+ readonly method: string;
541
+ readonly timeoutMs: number;
542
+ constructor(method: string, timeoutMs: number);
543
+ }
544
+ //#endregion
545
+ //#region src/rpc-stdio-process.d.ts
546
+ /**
547
+ * Spawn an RpcBridge over a long-running stdio child process.
548
+ *
549
+ * The bridge is usable immediately after this call — outbound writes
550
+ * before the child has booted are buffered by Node's stdin pipe. Inbound
551
+ * dispatch starts as soon as the child writes its first line.
552
+ */
553
+ declare function spawnRpcBridge(config: RpcBridgeConfig): RpcBridge;
554
+ //#endregion
555
+ //#region src/shell-quote.d.ts
556
+ declare function shellQuote(value: string): string;
557
+ //#endregion
558
+ //#region src/spawn-env.d.ts
559
+ /**
560
+ * Merge adapter environment overrides onto a base environment for `spawn`.
561
+ *
562
+ * An adapter scrubs an inherited variable by setting it to `undefined` (the
563
+ * claude-code and codex adapters strip a router key under OAuth so the CLI
564
+ * keeps its stored tokens). A plain spread keeps such a key in the object,
565
+ * and what the child then sees depends on the spawner. Deleting the key makes
566
+ * the scrub unconditional.
567
+ */
568
+ declare function mergeSpawnEnv(base: NodeJS.ProcessEnv, overrides: Record<string, string | undefined> | undefined): Record<string, string | undefined>;
569
+ //#endregion
570
+ //#region src/stream-parser.d.ts
571
+ /**
572
+ * Creates a stdout stream parser. Three modes:
573
+ *
574
+ * - "json" (default): newline-delimited JSON, JSON.parse per line.
575
+ * - "lines": plaintext, raw trimmed line per `transform` call.
576
+ * - "raw": no per-line splitting — accumulate the entire stream into a
577
+ * single buffer and deliver it via `onComplete(buffer)`.
578
+ *
579
+ * See StreamParserConfig docstring for the full contract per mode.
580
+ */
581
+ declare function createStreamParser<T>(config: StreamParserConfig<T>): StreamParser<T>;
582
+ //#endregion
583
+ export { BridgeClosedError, type CliExecOptions, type CliExecResult, type CliExitResult, type CliLaunch, type CliLaunchSpec, type CliLauncher, type CliProcess, type CliSpawnConfig, type CliStdinValue, HELD_SPARE_IDLE_MS, type HeldSpare, type HeldSpareRegistry, InvalidAttachmentError, JsonRpcError, RAW_CAPTURE_ROOT, RAW_CAPTURE_TOTAL_CAP_BYTES, type RawCaptureProcess, type RpcBridge, type RpcBridgeConfig, type RpcCloseReason, type RpcNotificationHandler, type RpcRequestHandler, type RpcRequestOptions, RpcRequestTimeoutError, type StreamParserConfig, allowRetainedRawCapture, argAfter, createDeferredParser, createHeldSpareRegistry, createStreamParser, execCli, launchCliProcess, launchDifferences, mergeSpawnEnv, openRawCapture, removeRawCapture, resolveRawCapturePath, shellQuote, spawnCliProcess, spawnRpcBridge, summarizeRawCapture, trimRawCapture, withRawCaptureAttempt };
584
+ //# sourceMappingURL=index.d.mts.map