@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 +11 -0
- package/dist/index.d.mts +584 -0
- package/dist/index.mjs +2285 -0
- package/package.json +35 -3
- package/README.md +0 -3
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
|
package/dist/index.d.mts
ADDED
|
@@ -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
|