klyro 0.1.63 → 1.0.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/dist/agent/anthropic-adapter.d.ts +36 -0
- package/dist/agent/anthropic-adapter.js +73 -16
- package/dist/agent/capabilities.d.ts +145 -0
- package/dist/agent/capabilities.js +191 -0
- package/dist/agent/child-worker.d.ts +104 -0
- package/dist/agent/child-worker.js +250 -0
- package/dist/agent/orchestrator.d.ts +232 -0
- package/dist/agent/orchestrator.js +589 -0
- package/dist/agent/provider-adapter.d.ts +17 -0
- package/dist/agent/provider-adapter.js +35 -3
- package/dist/agent/registry.d.ts +1 -0
- package/dist/agent/registry.js +1 -0
- package/dist/agent/retry.d.ts +13 -2
- package/dist/agent/retry.js +21 -3
- package/dist/agent/runtime.d.ts +70 -8
- package/dist/agent/runtime.js +205 -34
- package/dist/agent/scoped-registry.d.ts +22 -0
- package/dist/agent/scoped-registry.js +42 -0
- package/dist/agent/task-manager.d.ts +115 -0
- package/dist/agent/task-manager.js +250 -0
- package/dist/agent/worker-spawner.d.ts +17 -12
- package/dist/agent/worker-spawner.js +26 -20
- package/dist/agent/worktree-manager.d.ts +74 -0
- package/dist/agent/worktree-manager.js +189 -0
- package/dist/checkpoints/store.js +30 -5
- package/dist/cli/auth.js +16 -1
- package/dist/cli/config.d.ts +9 -3
- package/dist/cli/config.js +64 -3
- package/dist/cli/dotenv.d.ts +3 -0
- package/dist/cli/dotenv.js +57 -0
- package/dist/cli/eval.d.ts +6 -1
- package/dist/cli/eval.js +9 -0
- package/dist/cli/repl.js +183 -17
- package/dist/cli/run.d.ts +10 -11
- package/dist/cli/run.js +176 -14
- package/dist/cli/update.d.ts +5 -0
- package/dist/cli/update.js +62 -10
- package/dist/context/import-graph.d.ts +2 -0
- package/dist/context/import-graph.js +31 -3
- package/dist/context/klyro-md.d.ts +6 -0
- package/dist/context/klyro-md.js +25 -16
- package/dist/context/memory.d.ts +8 -0
- package/dist/context/memory.js +50 -2
- package/dist/context/project-map.d.ts +6 -0
- package/dist/context/project-map.js +50 -2
- package/dist/context/repo-map.d.ts +2 -0
- package/dist/context/repo-map.js +31 -1
- package/dist/context/trust.d.ts +42 -0
- package/dist/context/trust.js +111 -0
- package/dist/events/catalog.d.ts +99 -0
- package/dist/index.js +93 -4
- package/dist/mcp/client.d.ts +55 -0
- package/dist/mcp/client.js +294 -0
- package/dist/mcp/config.d.ts +40 -0
- package/dist/mcp/config.js +99 -0
- package/dist/mcp/policy.d.ts +13 -0
- package/dist/mcp/policy.js +12 -0
- package/dist/mcp/registry.d.ts +72 -0
- package/dist/mcp/registry.js +244 -0
- package/dist/mcp/schema.d.ts +18 -0
- package/dist/mcp/schema.js +57 -0
- package/dist/mcp/trust.d.ts +20 -0
- package/dist/mcp/trust.js +74 -0
- package/dist/persistence/audit.d.ts +28 -0
- package/dist/persistence/audit.js +101 -1
- package/dist/persistence/store.d.ts +26 -2
- package/dist/persistence/store.js +140 -13
- package/dist/policy/approval.d.ts +14 -0
- package/dist/policy/approval.js +44 -2
- package/dist/policy/engine.d.ts +1 -0
- package/dist/policy/engine.js +91 -10
- package/dist/policy/secret-redactor.js +4 -0
- package/dist/providers/model-info.d.ts +17 -0
- package/dist/providers/model-info.js +35 -2
- package/dist/repl.d.ts +6 -0
- package/dist/repl.js +12 -7
- package/dist/tools/agent/spawn-agent.d.ts +9 -0
- package/dist/tools/agent/spawn-agent.js +50 -0
- package/dist/tools/agent/task-apply.d.ts +4 -0
- package/dist/tools/agent/task-apply.js +44 -0
- package/dist/tools/agent/task-get.d.ts +8 -0
- package/dist/tools/agent/task-get.js +40 -0
- package/dist/tools/agent/task-list.d.ts +4 -0
- package/dist/tools/agent/task-list.js +41 -0
- package/dist/tools/agent/task-stop.d.ts +6 -0
- package/dist/tools/agent/task-stop.js +39 -0
- package/dist/tools/agent/task-wait.d.ts +17 -0
- package/dist/tools/agent/task-wait.js +79 -0
- package/dist/tools/fs/apply-patch.js +71 -0
- package/dist/tools/fs/edit-file.js +65 -0
- package/dist/tools/fs/multi-edit.d.ts +4 -0
- package/dist/tools/fs/multi-edit.js +66 -0
- package/dist/tools/fs/write-file.js +67 -0
- package/dist/tools/plan/todo-write.d.ts +1 -1
- package/dist/tools/registry.js +12 -0
- package/dist/tools/shell/background.js +6 -3
- package/dist/tools/shell/sandbox.d.ts +51 -0
- package/dist/tools/shell/sandbox.js +143 -0
- package/dist/tools/shell/shell-exec.d.ts +1 -0
- package/dist/tools/shell/shell-exec.js +83 -11
- package/dist/tools/shell/worker-entry.d.ts +12 -0
- package/dist/tools/shell/worker-entry.js +43 -0
- package/dist/tools/types.d.ts +18 -0
- package/dist/tools/verify/run-verify.js +3 -1
- package/dist/trace/writer.d.ts +13 -0
- package/dist/trace/writer.js +55 -4
- package/dist/tui/app.js +1 -1
- package/dist/tui/approval.js +20 -21
- package/dist/util.d.ts +1 -0
- package/dist/util.js +1 -0
- package/dist/verification/baseline.js +17 -3
- package/dist/verification/classify.js +23 -12
- package/dist/verification/engine.js +3 -1
- package/dist/verification/registry.d.ts +2 -0
- package/dist/verification/registry.js +33 -0
- package/dist/verification/scoped.js +28 -6
- package/package.json +1 -1
|
@@ -30,7 +30,14 @@ export interface AnthropicAdapterOptions {
|
|
|
30
30
|
authHeader?: 'x-api-key' | 'Authorization';
|
|
31
31
|
/** Beta features (e.g. ['prompt-caching-2024-07-31', 'tools-2024-04-04']). */
|
|
32
32
|
betas?: string[];
|
|
33
|
+
/**
|
|
34
|
+
* Prompt caching: put a `cache_control: {type:'ephemeral'}` breakpoint on
|
|
35
|
+
* the stable system block and send the `prompt-caching-2024-07-31` beta
|
|
36
|
+
* (merged with user betas). Default true.
|
|
37
|
+
*/
|
|
38
|
+
promptCache?: boolean;
|
|
33
39
|
}
|
|
40
|
+
export declare const PROMPT_CACHING_BETA = "prompt-caching-2024-07-31";
|
|
34
41
|
interface AnthropicMessage {
|
|
35
42
|
role: 'user' | 'assistant';
|
|
36
43
|
content: Array<{
|
|
@@ -51,6 +58,25 @@ interface AnthropicMessage {
|
|
|
51
58
|
is_error?: boolean;
|
|
52
59
|
}>;
|
|
53
60
|
}
|
|
61
|
+
interface AnthropicRequest {
|
|
62
|
+
model: string;
|
|
63
|
+
system?: string | Array<{
|
|
64
|
+
type: 'text';
|
|
65
|
+
text: string;
|
|
66
|
+
cache_control?: {
|
|
67
|
+
type: 'ephemeral';
|
|
68
|
+
};
|
|
69
|
+
}>;
|
|
70
|
+
messages: AnthropicMessage[];
|
|
71
|
+
tools?: Array<{
|
|
72
|
+
name: string;
|
|
73
|
+
description: string;
|
|
74
|
+
input_schema: unknown;
|
|
75
|
+
}>;
|
|
76
|
+
max_tokens: number;
|
|
77
|
+
temperature?: number;
|
|
78
|
+
stream: true;
|
|
79
|
+
}
|
|
54
80
|
interface AnthropicSseEvent {
|
|
55
81
|
type: string;
|
|
56
82
|
[key: string]: unknown;
|
|
@@ -61,6 +87,13 @@ export declare class AnthropicApiError extends Error {
|
|
|
61
87
|
constructor(status: number, body: string);
|
|
62
88
|
}
|
|
63
89
|
export declare function anthropicAdapter(opts: AnthropicAdapterOptions): ProviderAdapter;
|
|
90
|
+
/**
|
|
91
|
+
* Build the Anthropic `system` field. The stable system text gets the
|
|
92
|
+
* cache breakpoint; the volatile telemetry suffix (when present) rides as
|
|
93
|
+
* a second, uncached block so it never poisons the prefix cache.
|
|
94
|
+
* Exported via _internal for testing.
|
|
95
|
+
*/
|
|
96
|
+
export declare function buildAnthropicSystem(system: string | undefined, suffix: string | undefined, promptCache: boolean): AnthropicRequest['system'];
|
|
64
97
|
/**
|
|
65
98
|
* Mutable per-stream assembly state. Blocks are keyed by content_block
|
|
66
99
|
* index; the tool id is carried inside the block entry. There is no global
|
|
@@ -79,6 +112,8 @@ interface AnthropicStreamState {
|
|
|
79
112
|
usage: {
|
|
80
113
|
input?: number;
|
|
81
114
|
output?: number;
|
|
115
|
+
cacheRead?: number;
|
|
116
|
+
cacheWrite?: number;
|
|
82
117
|
};
|
|
83
118
|
}
|
|
84
119
|
declare function translateSse(event: string, parsed: AnthropicSseEvent, state: AnthropicStreamState): StreamEvent[];
|
|
@@ -92,5 +127,6 @@ export declare const _internal: {
|
|
|
92
127
|
toAnthropicMessages: typeof toAnthropicMessages;
|
|
93
128
|
toAnthropicTool: typeof toAnthropicTool;
|
|
94
129
|
translateSse: typeof translateSse;
|
|
130
|
+
buildAnthropicSystem: typeof buildAnthropicSystem;
|
|
95
131
|
};
|
|
96
132
|
export {};
|
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
* Auth can be a Bearer token (for proxies) — the adapter accepts either.
|
|
17
17
|
*/
|
|
18
18
|
import { assertSafeBaseURL } from '../chat.js';
|
|
19
|
+
import { parseRetryAfterMs } from './provider-adapter.js';
|
|
19
20
|
const DEFAULT_VERSION = '2023-06-01';
|
|
20
21
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
22
|
+
export const PROMPT_CACHING_BETA = 'prompt-caching-2024-07-31';
|
|
21
23
|
export class AnthropicApiError extends Error {
|
|
22
24
|
status;
|
|
23
25
|
body;
|
|
@@ -34,7 +36,10 @@ export function anthropicAdapter(opts) {
|
|
|
34
36
|
const baseURL = rawBase.replace(/\/+$/, '');
|
|
35
37
|
const version = opts.anthropicVersion ?? DEFAULT_VERSION;
|
|
36
38
|
const authHeader = opts.authHeader ?? 'x-api-key';
|
|
37
|
-
const
|
|
39
|
+
const promptCache = opts.promptCache ?? true;
|
|
40
|
+
const betas = [...(opts.betas ?? [])];
|
|
41
|
+
if (promptCache && !betas.includes(PROMPT_CACHING_BETA))
|
|
42
|
+
betas.push(PROMPT_CACHING_BETA);
|
|
38
43
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
39
44
|
if (!fetchImpl) {
|
|
40
45
|
throw new Error('anthropicAdapter: no fetch available — pass opts.fetchImpl or run on Node 18+');
|
|
@@ -44,15 +49,36 @@ export function anthropicAdapter(opts) {
|
|
|
44
49
|
stream(req) {
|
|
45
50
|
return streamAnthropic(req, {
|
|
46
51
|
baseURL, apiKey: opts.apiKey, timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
47
|
-
fetchImpl, version, authHeader, betas,
|
|
52
|
+
fetchImpl, version, authHeader, betas, promptCache,
|
|
48
53
|
});
|
|
49
54
|
},
|
|
50
55
|
};
|
|
51
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Build the Anthropic `system` field. The stable system text gets the
|
|
59
|
+
* cache breakpoint; the volatile telemetry suffix (when present) rides as
|
|
60
|
+
* a second, uncached block so it never poisons the prefix cache.
|
|
61
|
+
* Exported via _internal for testing.
|
|
62
|
+
*/
|
|
63
|
+
export function buildAnthropicSystem(system, suffix, promptCache) {
|
|
64
|
+
const hasSuffix = suffix !== undefined && suffix !== '';
|
|
65
|
+
const breakpoint = promptCache ? { cache_control: { type: 'ephemeral' } } : {};
|
|
66
|
+
if (hasSuffix) {
|
|
67
|
+
if (!system)
|
|
68
|
+
return promptCache ? [{ type: 'text', text: suffix, ...breakpoint }] : suffix;
|
|
69
|
+
return [
|
|
70
|
+
{ type: 'text', text: system, ...breakpoint },
|
|
71
|
+
{ type: 'text', text: suffix },
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
if (!system)
|
|
75
|
+
return undefined;
|
|
76
|
+
return promptCache ? [{ type: 'text', text: system, ...breakpoint }] : system;
|
|
77
|
+
}
|
|
52
78
|
async function* streamAnthropic(req, opts) {
|
|
53
79
|
const body = {
|
|
54
80
|
model: req.model,
|
|
55
|
-
system: req.system,
|
|
81
|
+
system: buildAnthropicSystem(req.system, req.systemSuffix, opts.promptCache),
|
|
56
82
|
messages: toAnthropicMessages(req.messages),
|
|
57
83
|
tools: req.tools.length > 0 ? req.tools.map(toAnthropicTool) : undefined,
|
|
58
84
|
max_tokens: req.maxTokens ?? 4096,
|
|
@@ -97,11 +123,19 @@ async function* streamAnthropic(req, opts) {
|
|
|
97
123
|
clearTimeout(timer);
|
|
98
124
|
if (!resp.ok || !resp.body) {
|
|
99
125
|
const text = await resp.text().catch(() => '<unreadable>');
|
|
126
|
+
// 413 / request_too_large (or a too-long-prompt message) is context
|
|
127
|
+
// overflow, not transient: dedicated code, never retryable here — the
|
|
128
|
+
// runtime owns compress-and-retry.
|
|
129
|
+
const isOverflow = resp.status === 413 || /request_too_large|too_large|prompt_too_long|context_length|maximum context length|prompt is too long/i.test(text);
|
|
130
|
+
const retryable = !isOverflow && (resp.status >= 500 || resp.status === 429);
|
|
131
|
+
const retryAfterMs = retryable ? parseRetryAfterMs(resp.headers?.get('retry-after')) : undefined;
|
|
100
132
|
yield {
|
|
101
133
|
kind: 'error',
|
|
102
|
-
code: `http_${resp.status}`,
|
|
134
|
+
code: isOverflow ? 'REQUEST_TOO_LARGE' : `http_${resp.status}`,
|
|
103
135
|
message: `Anthropic API returned ${resp.status}: ${text.slice(0, 500)}`,
|
|
104
|
-
retryable
|
|
136
|
+
retryable,
|
|
137
|
+
status: String(resp.status),
|
|
138
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
105
139
|
};
|
|
106
140
|
return;
|
|
107
141
|
}
|
|
@@ -203,25 +237,45 @@ async function* streamAnthropic(req, opts) {
|
|
|
203
237
|
kind: 'message_end',
|
|
204
238
|
finishReason: 'stop',
|
|
205
239
|
...(state.usage.input !== undefined || state.usage.output !== undefined
|
|
206
|
-
? { usage:
|
|
240
|
+
? { usage: withCache(state.usage) }
|
|
207
241
|
: {}),
|
|
208
242
|
};
|
|
209
243
|
}
|
|
210
244
|
}
|
|
245
|
+
/** Copy input/output plus any cache counters into a message_end usage payload. */
|
|
246
|
+
function withCache(u) {
|
|
247
|
+
return {
|
|
248
|
+
input: u.input ?? 0,
|
|
249
|
+
output: u.output ?? 0,
|
|
250
|
+
...(u.cacheRead !== undefined ? { cacheRead: u.cacheRead } : {}),
|
|
251
|
+
...(u.cacheWrite !== undefined ? { cacheWrite: u.cacheWrite } : {}),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/** Fold an Anthropic `usage` object (message_start or message_delta) into stream state. */
|
|
255
|
+
function recordAnthropicUsage(state, u) {
|
|
256
|
+
const rec = u;
|
|
257
|
+
if (typeof rec?.input_tokens === 'number')
|
|
258
|
+
state.usage.input = rec.input_tokens;
|
|
259
|
+
if (typeof rec?.output_tokens === 'number') {
|
|
260
|
+
state.usage.output = (state.usage.output ?? 0) + rec.output_tokens;
|
|
261
|
+
}
|
|
262
|
+
if (typeof rec?.cache_creation_input_tokens === 'number') {
|
|
263
|
+
state.usage.cacheWrite = (state.usage.cacheWrite ?? 0) + rec.cache_creation_input_tokens;
|
|
264
|
+
}
|
|
265
|
+
if (typeof rec?.cache_read_input_tokens === 'number') {
|
|
266
|
+
state.usage.cacheRead = (state.usage.cacheRead ?? 0) + rec.cache_read_input_tokens;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
211
269
|
function translateSse(event, parsed, state) {
|
|
212
270
|
const out = [];
|
|
213
271
|
switch (event) {
|
|
214
272
|
case 'message_start': {
|
|
215
273
|
const usage = parsed.message?.usage;
|
|
216
|
-
|
|
217
|
-
state.usage.input = usage.input_tokens;
|
|
274
|
+
recordAnthropicUsage(state, usage);
|
|
218
275
|
return out;
|
|
219
276
|
}
|
|
220
277
|
case 'message_delta': {
|
|
221
|
-
|
|
222
|
-
if (typeof usage?.output_tokens === 'number') {
|
|
223
|
-
state.usage.output = (state.usage.output ?? 0) + usage.output_tokens;
|
|
224
|
-
}
|
|
278
|
+
recordAnthropicUsage(state, parsed.usage);
|
|
225
279
|
return out;
|
|
226
280
|
}
|
|
227
281
|
case 'content_block_start': {
|
|
@@ -292,17 +346,20 @@ function translateSse(event, parsed, state) {
|
|
|
292
346
|
kind: 'message_end',
|
|
293
347
|
finishReason: 'stop',
|
|
294
348
|
...(state.usage.input !== undefined || state.usage.output !== undefined
|
|
295
|
-
? { usage:
|
|
349
|
+
? { usage: withCache(state.usage) }
|
|
296
350
|
: {}),
|
|
297
351
|
});
|
|
298
352
|
return out;
|
|
299
353
|
}
|
|
300
354
|
case 'error': {
|
|
301
355
|
const err = parsed.error;
|
|
356
|
+
const rawType = err?.type ?? 'anthropic_error';
|
|
357
|
+
const rawMsg = err?.message ?? 'unknown Anthropic error';
|
|
358
|
+
const isOverflow = /too_large|too_long|context_length|prompt_too_long|prompt is too long/i.test(`${rawType} ${rawMsg}`);
|
|
302
359
|
out.push({
|
|
303
360
|
kind: 'error',
|
|
304
|
-
code:
|
|
305
|
-
message:
|
|
361
|
+
code: isOverflow ? 'REQUEST_TOO_LARGE' : rawType,
|
|
362
|
+
message: rawMsg,
|
|
306
363
|
retryable: false,
|
|
307
364
|
});
|
|
308
365
|
return out;
|
|
@@ -373,4 +430,4 @@ function toAnthropicTool(t) {
|
|
|
373
430
|
};
|
|
374
431
|
}
|
|
375
432
|
// Re-export for testability.
|
|
376
|
-
export const _internal = { toAnthropicMessages, toAnthropicTool, translateSse };
|
|
433
|
+
export const _internal = { toAnthropicMessages, toAnthropicTool, translateSse, buildAnthropicSystem };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent capability enforcement (P0.2 from r-6-10.fix.md).
|
|
3
|
+
*
|
|
4
|
+
* Resolves the **effective tool set** for a (child) agent by intersecting:
|
|
5
|
+
*
|
|
6
|
+
* 1. the parent's effective tools — a child can never receive more tools than its parent
|
|
7
|
+
* 2. the agent definition's `allowedTools` (if specified)
|
|
8
|
+
* 3. the runtime policy's allowed tools (e.g. `--allow-write` flags)
|
|
9
|
+
*
|
|
10
|
+
* Then applies safety modifiers:
|
|
11
|
+
*
|
|
12
|
+
* - `readonly: true` strips every tool classified as write or mutation.
|
|
13
|
+
* - `canSpawn: false` (default for children) removes any spawn_agent-like tools.
|
|
14
|
+
* - A deny-list always wins, regardless of what the agent declares.
|
|
15
|
+
*
|
|
16
|
+
* This is a pure function — no I/O, no side effects. It exists so the runtime
|
|
17
|
+
* can decide which tools to expose to the model BEFORE the model picks one.
|
|
18
|
+
*
|
|
19
|
+
* Wiring lives in src/agent/runtime.ts (see the tool-filter hook).
|
|
20
|
+
*/
|
|
21
|
+
export interface AgentCapabilities {
|
|
22
|
+
/** Tools the agent definition explicitly allows. `undefined` means "no allow-list". */
|
|
23
|
+
allowedTools?: string[];
|
|
24
|
+
/** If true, write/mutation tools are stripped. */
|
|
25
|
+
readonly?: boolean;
|
|
26
|
+
/** If false (or unset), tools that can spawn new agents are stripped. */
|
|
27
|
+
canSpawn?: boolean;
|
|
28
|
+
/** Model override — if the provider cannot satisfy it, callers must error rather than fall back. */
|
|
29
|
+
model?: string;
|
|
30
|
+
/** Recursion depth cap. Beyond this, spawn attempts are blocked. */
|
|
31
|
+
maxDepth?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Path allow-list for filesystem scope. `undefined` means unconstrained.
|
|
34
|
+
* A child's effective list is the intersection with its parent's.
|
|
35
|
+
*/
|
|
36
|
+
allowedPaths?: string[];
|
|
37
|
+
}
|
|
38
|
+
export interface ResolveToolsInput {
|
|
39
|
+
/** Tools the parent is allowed to use (or `null` for the root agent = all registered tools). */
|
|
40
|
+
parentTools: ReadonlySet<string> | null;
|
|
41
|
+
/** The agent's declared capability profile. */
|
|
42
|
+
agent: AgentCapabilities;
|
|
43
|
+
/** Tools currently allowed by runtime policy (e.g. user-permission flags). */
|
|
44
|
+
policyAllowed: ReadonlySet<string>;
|
|
45
|
+
/** All tools known to the registry (used when parentTools is null). */
|
|
46
|
+
registryTools: ReadonlySet<string>;
|
|
47
|
+
/** Tools that mutate state — stripped under `readonly`. */
|
|
48
|
+
writeTools: ReadonlySet<string>;
|
|
49
|
+
/** Tools that spawn other agents — stripped when `canSpawn` is false. */
|
|
50
|
+
spawnTools: ReadonlySet<string>;
|
|
51
|
+
/** Tools that are NEVER allowed, even if explicitly requested. */
|
|
52
|
+
denied: ReadonlySet<string>;
|
|
53
|
+
/** If true, spawn tools are kept even when agent.canSpawn is false/unset. */
|
|
54
|
+
canSpawnOverride?: boolean;
|
|
55
|
+
}
|
|
56
|
+
export interface ResolveToolsResult {
|
|
57
|
+
/** Tools the child agent is permitted to call. */
|
|
58
|
+
allowed: string[];
|
|
59
|
+
/** Tools that were denied (debugging/observability). */
|
|
60
|
+
dropped: {
|
|
61
|
+
tool: string;
|
|
62
|
+
reason: 'denied' | 'readonly' | 'no-spawn' | 'not-in-parent' | 'not-in-policy' | 'unknown';
|
|
63
|
+
}[];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the effective tool set for an agent.
|
|
67
|
+
*
|
|
68
|
+
* Order of operations:
|
|
69
|
+
* 1. Start from `parentTools ?? registryTools`.
|
|
70
|
+
* 2. Intersect with `policyAllowed`.
|
|
71
|
+
* 3. If `allowedTools` is set, intersect again.
|
|
72
|
+
* 4. Strip `writeTools` if `readonly`.
|
|
73
|
+
* 5. Strip `spawnTools` if `!canSpawn`.
|
|
74
|
+
* 6. Remove `denied` last (always wins).
|
|
75
|
+
*/
|
|
76
|
+
export declare function resolveAgentTools(input: ResolveToolsInput): ResolveToolsResult;
|
|
77
|
+
/**
|
|
78
|
+
* Reason a tool was dropped from the resolved capability set. Mirrors
|
|
79
|
+
* `ResolveToolsResult.dropped[].reason`.
|
|
80
|
+
*/
|
|
81
|
+
export type DropReason = 'denied' | 'readonly' | 'no-spawn' | 'not-in-parent' | 'not-in-policy' | 'unknown';
|
|
82
|
+
/**
|
|
83
|
+
* Fully-resolved capability profile for a child agent — tool set plus
|
|
84
|
+
* non-tool knobs (model override, recursion cap). Computed once at spawn
|
|
85
|
+
* time and threaded through the runtime as `parentContext`.
|
|
86
|
+
*/
|
|
87
|
+
export interface ResolvedCapabilities {
|
|
88
|
+
/** Sorted tool names the child is allowed to invoke. */
|
|
89
|
+
allowed: ReadonlySet<string>;
|
|
90
|
+
/** Per-tool drop reasons — surfaced in ChildSummary for parent visibility. */
|
|
91
|
+
dropped: {
|
|
92
|
+
tool: string;
|
|
93
|
+
reason: DropReason;
|
|
94
|
+
}[];
|
|
95
|
+
/** Model override to send to the provider (or undefined to inherit parent). */
|
|
96
|
+
model?: string;
|
|
97
|
+
/** Effective recursion depth cap. Caller MUST reject spawn when depth + 1 > maxDepth. */
|
|
98
|
+
maxDepth: number;
|
|
99
|
+
readonly: boolean;
|
|
100
|
+
canSpawn: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Effective filesystem scope: parent's `allowedPaths` intersected with the
|
|
103
|
+
* agent's own. `undefined` means unconstrained (neither side restricts).
|
|
104
|
+
*/
|
|
105
|
+
allowedPaths?: string[];
|
|
106
|
+
}
|
|
107
|
+
/** Extra knobs consumed by `resolveCapabilities` on top of `ResolveToolsInput`. */
|
|
108
|
+
export interface ResolveCapabilitiesExtra {
|
|
109
|
+
/** Caller-provided maxDepth (e.g. from CLI `--max-depth` or a parent's maxDepth). Required. */
|
|
110
|
+
maxDepth: number;
|
|
111
|
+
/** If true, the resolved `allowed` set may include tools that themselves spawn agents. */
|
|
112
|
+
canSpawnOverride?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* The parent's filesystem allow-list (`undefined` = unconstrained parent —
|
|
115
|
+
* the agent's own list, if any, applies as-is).
|
|
116
|
+
*/
|
|
117
|
+
parentAllowedPaths?: string[];
|
|
118
|
+
}
|
|
119
|
+
/** Combined input for the one-shot resolver used by the orchestrator. */
|
|
120
|
+
export interface ResolveCapabilitiesInput extends ResolveToolsInput, ResolveCapabilitiesExtra {
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Intersect two filesystem allow-lists. `undefined` on either side means
|
|
124
|
+
* "unconstrained" — the other side applies as-is; both `undefined` stays
|
|
125
|
+
* `undefined`. Entries survive when they are equal to, or nested inside, an
|
|
126
|
+
* entry of the other list (so a parent `/repo` and a child `/repo/sub`
|
|
127
|
+
* intersect to `/repo/sub`). Comparison is case-insensitive on Windows.
|
|
128
|
+
*/
|
|
129
|
+
export declare function intersectAllowedPaths(parentPaths?: string[], agentPaths?: string[]): string[] | undefined;
|
|
130
|
+
/**
|
|
131
|
+
* One-shot capability resolver: tool intersection + per-agent model/maxDepth.
|
|
132
|
+
*
|
|
133
|
+
* `maxDepth` precedence (most specific wins):
|
|
134
|
+
* 1. `agent.maxDepth` (if set on the definition)
|
|
135
|
+
* 2. `extra.maxDepth` (parent / CLI cap)
|
|
136
|
+
* `model` is propagated only when the agent declares one; we do not silently
|
|
137
|
+
* override a parent's chosen model.
|
|
138
|
+
*/
|
|
139
|
+
export declare function resolveCapabilities(input: ResolveCapabilitiesInput): ResolvedCapabilities;
|
|
140
|
+
/** Sensible defaults for "what counts as a write tool" if a caller doesn't override. */
|
|
141
|
+
export declare const DEFAULT_WRITE_TOOLS: ReadonlySet<string>;
|
|
142
|
+
/** Default spawn tools — removed from children by default. */
|
|
143
|
+
export declare const DEFAULT_SPAWN_TOOLS: ReadonlySet<string>;
|
|
144
|
+
/** Default deny-list — these are NEVER allowed, even if explicitly requested. */
|
|
145
|
+
export declare const DEFAULT_DENIED_TOOLS: ReadonlySet<string>;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent capability enforcement (P0.2 from r-6-10.fix.md).
|
|
3
|
+
*
|
|
4
|
+
* Resolves the **effective tool set** for a (child) agent by intersecting:
|
|
5
|
+
*
|
|
6
|
+
* 1. the parent's effective tools — a child can never receive more tools than its parent
|
|
7
|
+
* 2. the agent definition's `allowedTools` (if specified)
|
|
8
|
+
* 3. the runtime policy's allowed tools (e.g. `--allow-write` flags)
|
|
9
|
+
*
|
|
10
|
+
* Then applies safety modifiers:
|
|
11
|
+
*
|
|
12
|
+
* - `readonly: true` strips every tool classified as write or mutation.
|
|
13
|
+
* - `canSpawn: false` (default for children) removes any spawn_agent-like tools.
|
|
14
|
+
* - A deny-list always wins, regardless of what the agent declares.
|
|
15
|
+
*
|
|
16
|
+
* This is a pure function — no I/O, no side effects. It exists so the runtime
|
|
17
|
+
* can decide which tools to expose to the model BEFORE the model picks one.
|
|
18
|
+
*
|
|
19
|
+
* Wiring lives in src/agent/runtime.ts (see the tool-filter hook).
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the effective tool set for an agent.
|
|
23
|
+
*
|
|
24
|
+
* Order of operations:
|
|
25
|
+
* 1. Start from `parentTools ?? registryTools`.
|
|
26
|
+
* 2. Intersect with `policyAllowed`.
|
|
27
|
+
* 3. If `allowedTools` is set, intersect again.
|
|
28
|
+
* 4. Strip `writeTools` if `readonly`.
|
|
29
|
+
* 5. Strip `spawnTools` if `!canSpawn`.
|
|
30
|
+
* 6. Remove `denied` last (always wins).
|
|
31
|
+
*/
|
|
32
|
+
export function resolveAgentTools(input) {
|
|
33
|
+
const { parentTools, agent, policyAllowed, registryTools, writeTools, spawnTools, denied, } = input;
|
|
34
|
+
const allowed = new Set();
|
|
35
|
+
const dropped = [];
|
|
36
|
+
// 1. Baseline: parent's tools, or all registered tools for the root agent.
|
|
37
|
+
// Record narrowing for observability: registry tools outside the parent's
|
|
38
|
+
// set are reported as 'not-in-parent' drops (child can never exceed parent).
|
|
39
|
+
const baseline = parentTools ?? registryTools;
|
|
40
|
+
if (parentTools !== null) {
|
|
41
|
+
for (const name of registryTools) {
|
|
42
|
+
if (!parentTools.has(name)) {
|
|
43
|
+
dropped.push({ tool: name, reason: 'not-in-parent' });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const name of baseline) {
|
|
48
|
+
if (!registryTools.has(name)) {
|
|
49
|
+
dropped.push({ tool: name, reason: 'unknown' });
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!policyAllowed.has(name)) {
|
|
53
|
+
dropped.push({ tool: name, reason: 'not-in-policy' });
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
allowed.add(name);
|
|
57
|
+
}
|
|
58
|
+
// 3. Apply the agent's allow-list (if any). Anything not in the list is dropped.
|
|
59
|
+
if (agent.allowedTools) {
|
|
60
|
+
const explicit = new Set(agent.allowedTools);
|
|
61
|
+
for (const name of [...allowed]) {
|
|
62
|
+
if (!explicit.has(name)) {
|
|
63
|
+
allowed.delete(name);
|
|
64
|
+
dropped.push({ tool: name, reason: 'not-in-parent' });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Tools requested in allowedTools but not in the baseline cannot be granted.
|
|
68
|
+
for (const name of agent.allowedTools) {
|
|
69
|
+
if (!allowed.has(name) && registryTools.has(name) && policyAllowed.has(name)) {
|
|
70
|
+
dropped.push({ tool: name, reason: 'not-in-parent' });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// 4. readonly → strip write tools (plus run_verify: verification can
|
|
75
|
+
// execute arbitrary commands, so it is not read-only).
|
|
76
|
+
if (agent.readonly) {
|
|
77
|
+
for (const name of [...allowed]) {
|
|
78
|
+
if (writeTools.has(name) || name === 'run_verify') {
|
|
79
|
+
allowed.delete(name);
|
|
80
|
+
dropped.push({ tool: name, reason: 'readonly' });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// 5. canSpawn=false → strip spawn tools. Default: root agent (parentTools
|
|
85
|
+
// === null) may spawn; children strip unless explicitly enabled. An explicit
|
|
86
|
+
// canSpawnOverride=true (via resolveCapabilities) also keeps them.
|
|
87
|
+
const canSpawnEffective = input.canSpawnOverride ?? input.agent.canSpawn ?? (input.parentTools === null);
|
|
88
|
+
if (!canSpawnEffective) {
|
|
89
|
+
for (const name of [...allowed]) {
|
|
90
|
+
if (spawnTools.has(name)) {
|
|
91
|
+
allowed.delete(name);
|
|
92
|
+
dropped.push({ tool: name, reason: 'no-spawn' });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// 6. Deny-list always wins.
|
|
97
|
+
for (const name of [...allowed]) {
|
|
98
|
+
if (denied.has(name)) {
|
|
99
|
+
allowed.delete(name);
|
|
100
|
+
dropped.push({ tool: name, reason: 'denied' });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
allowed: [...allowed].sort(),
|
|
105
|
+
dropped: dropped.sort((a, b) => a.tool.localeCompare(b.tool)),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Intersect two filesystem allow-lists. `undefined` on either side means
|
|
110
|
+
* "unconstrained" — the other side applies as-is; both `undefined` stays
|
|
111
|
+
* `undefined`. Entries survive when they are equal to, or nested inside, an
|
|
112
|
+
* entry of the other list (so a parent `/repo` and a child `/repo/sub`
|
|
113
|
+
* intersect to `/repo/sub`). Comparison is case-insensitive on Windows.
|
|
114
|
+
*/
|
|
115
|
+
export function intersectAllowedPaths(parentPaths, agentPaths) {
|
|
116
|
+
if (parentPaths === undefined)
|
|
117
|
+
return agentPaths;
|
|
118
|
+
if (agentPaths === undefined)
|
|
119
|
+
return [...parentPaths];
|
|
120
|
+
const out = [];
|
|
121
|
+
const seen = new Set();
|
|
122
|
+
const within = (candidate, scope) => {
|
|
123
|
+
const norm = (p) => {
|
|
124
|
+
const n = p.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
125
|
+
return process.platform === 'win32' ? n.toLowerCase() : n;
|
|
126
|
+
};
|
|
127
|
+
const c = norm(candidate);
|
|
128
|
+
const s = norm(scope);
|
|
129
|
+
return c === s || c.startsWith(s + '/');
|
|
130
|
+
};
|
|
131
|
+
for (const a of agentPaths) {
|
|
132
|
+
for (const p of parentPaths) {
|
|
133
|
+
// The overlap of two nested scopes is the narrower one.
|
|
134
|
+
const narrower = within(a, p) ? a : within(p, a) ? p : undefined;
|
|
135
|
+
if (narrower !== undefined) {
|
|
136
|
+
const key = process.platform === 'win32' ? narrower.toLowerCase() : narrower;
|
|
137
|
+
if (!seen.has(key)) {
|
|
138
|
+
seen.add(key);
|
|
139
|
+
out.push(narrower);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return out.sort();
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* One-shot capability resolver: tool intersection + per-agent model/maxDepth.
|
|
148
|
+
*
|
|
149
|
+
* `maxDepth` precedence (most specific wins):
|
|
150
|
+
* 1. `agent.maxDepth` (if set on the definition)
|
|
151
|
+
* 2. `extra.maxDepth` (parent / CLI cap)
|
|
152
|
+
* `model` is propagated only when the agent declares one; we do not silently
|
|
153
|
+
* override a parent's chosen model.
|
|
154
|
+
*/
|
|
155
|
+
export function resolveCapabilities(input) {
|
|
156
|
+
const resolved = resolveAgentTools(input);
|
|
157
|
+
const allowed = new Set(resolved.allowed);
|
|
158
|
+
const model = input.agent.model;
|
|
159
|
+
const maxDepth = input.agent.maxDepth ?? input.maxDepth;
|
|
160
|
+
// Default matches resolveAgentTools: the root agent (parentTools === null)
|
|
161
|
+
// may spawn; children strip spawn tools unless explicitly enabled.
|
|
162
|
+
const canSpawn = input.canSpawnOverride ?? input.agent.canSpawn ?? (input.parentTools === null);
|
|
163
|
+
const allowedPaths = intersectAllowedPaths(input.parentAllowedPaths, input.agent.allowedPaths);
|
|
164
|
+
return {
|
|
165
|
+
allowed,
|
|
166
|
+
dropped: resolved.dropped,
|
|
167
|
+
...(model !== undefined ? { model } : {}),
|
|
168
|
+
maxDepth,
|
|
169
|
+
readonly: input.agent.readonly ?? false,
|
|
170
|
+
canSpawn,
|
|
171
|
+
...(allowedPaths !== undefined ? { allowedPaths } : {}),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/** Sensible defaults for "what counts as a write tool" if a caller doesn't override. */
|
|
175
|
+
export const DEFAULT_WRITE_TOOLS = new Set([
|
|
176
|
+
'write_file',
|
|
177
|
+
'edit_file',
|
|
178
|
+
'multi_edit',
|
|
179
|
+
'apply_patch',
|
|
180
|
+
'shell_exec',
|
|
181
|
+
'memory_write',
|
|
182
|
+
'todo_write',
|
|
183
|
+
]);
|
|
184
|
+
/** Default spawn tools — removed from children by default. */
|
|
185
|
+
export const DEFAULT_SPAWN_TOOLS = new Set([
|
|
186
|
+
'spawn_agent',
|
|
187
|
+
]);
|
|
188
|
+
/** Default deny-list — these are NEVER allowed, even if explicitly requested. */
|
|
189
|
+
export const DEFAULT_DENIED_TOOLS = new Set([
|
|
190
|
+
// Add dangerous tools here. Empty by default — extend as policy matures.
|
|
191
|
+
]);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G2 — Process isolation for sub-agents (V1: headless/CLI children).
|
|
3
|
+
*
|
|
4
|
+
* This module is the *child-process* end of spawn_agent. The orchestrator
|
|
5
|
+
* forks this file with `node` (never shares a process), passing a
|
|
6
|
+
* JSON-serializable payload on STDIN (not argv — the system prompt + memory
|
|
7
|
+
* can exceed argv/command-line length limits, which would fail the spawn with
|
|
8
|
+
* E2BIG). The child rebuilds a minimal but self-sufficient `RuntimeDeps`,
|
|
9
|
+
* runs the task through `run()`, emits ONE JSON `ChildResult` line on stdout,
|
|
10
|
+
* and exits 0/1.
|
|
11
|
+
*
|
|
12
|
+
* Isolation contract: a child that OOMs, segfaults, or enters an infinite
|
|
13
|
+
* loop fails ONLY this process — the parent harness survives and reports
|
|
14
|
+
* `CHILD_CRASH` exactly as it does for an in-process throw.
|
|
15
|
+
*
|
|
16
|
+
* V1 scope (documented honestly):
|
|
17
|
+
* - Headless children only. TUI children stay in-process because the Ink
|
|
18
|
+
* approval bridge is inherently tied to the parent terminal.
|
|
19
|
+
* - The child runs a system-prompt STRING (already assembled by the parent,
|
|
20
|
+
* so KLYRO.md + memory + trust decisions match) with a file-system tool
|
|
21
|
+
* subset and DenyAll approval — no MCP server forwarding, no live prompt.
|
|
22
|
+
* - Grandchild spawning is not forwarded: a process-isolated child rebuilds
|
|
23
|
+
* its own agent bridge-less deps, so its tools see NO_ORCHESTRATOR (same
|
|
24
|
+
* as a pre-bridge child). V2 may thread a task handle back via a socket.
|
|
25
|
+
*
|
|
26
|
+
* Secrets never cross the process boundary via argv/env leaks: the child
|
|
27
|
+
* reads its provider key/baseURL from the SAME env the parent CLI uses
|
|
28
|
+
* (KLYRO_API_KEY, KLYRO_BASE_URL), so credentials stay in-process/env,
|
|
29
|
+
* never in the payload.
|
|
30
|
+
*/
|
|
31
|
+
import type { RunOptions, RunResult, RuntimeDeps } from './runtime.js';
|
|
32
|
+
export interface ChildWorkerPayload {
|
|
33
|
+
/** Working dir the child operates in (may be a worktree). */
|
|
34
|
+
cwd: string;
|
|
35
|
+
/** The task prompt. */
|
|
36
|
+
task: string;
|
|
37
|
+
/** Model string (drives adapter selection + rate table). */
|
|
38
|
+
model: string;
|
|
39
|
+
/** Pre-assembled system prompt (stable + volatile already spliced). */
|
|
40
|
+
systemPrompt: string;
|
|
41
|
+
/** Agent id, for the system prompt identity. */
|
|
42
|
+
agentId: string;
|
|
43
|
+
/** Cap caps. */
|
|
44
|
+
maxSteps?: number;
|
|
45
|
+
maxCost?: number;
|
|
46
|
+
maxTimeMs?: number;
|
|
47
|
+
maxTokens?: number;
|
|
48
|
+
}
|
|
49
|
+
/** The one-line result the child prints to stdout on success. */
|
|
50
|
+
export type ChildResult = Pick<RunResult, 'status' | 'steps' | 'toolCalls' | 'finalText' | 'hasEdits' | 'usage'>;
|
|
51
|
+
/**
|
|
52
|
+
* Differently-phrased crash, distinct from an in-process throw the parent
|
|
53
|
+
* would catch. The child either exited non-zero without a parseable result
|
|
54
|
+
* line, or produced a malformed result. Carries the raw stdout/stderr tail.
|
|
55
|
+
*/
|
|
56
|
+
export declare class ChildCrashError extends Error {
|
|
57
|
+
readonly likelyCause: 'exit' | 'oom' | 'timeout';
|
|
58
|
+
readonly stderrTail?: string | undefined;
|
|
59
|
+
constructor(message: string, likelyCause: 'exit' | 'oom' | 'timeout', stderrTail?: string | undefined);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the compiled child-worker entry that *this* module's neighboring
|
|
63
|
+
* build (src or dist) produced. Both dev (tsx, .ts) and packaged (dist, .js)
|
|
64
|
+
* layouts live beside this file, so we probe for whichever exists.
|
|
65
|
+
*/
|
|
66
|
+
export declare function workerEntryPath(): string;
|
|
67
|
+
export interface ForkChildOptions {
|
|
68
|
+
/** Abort the child process when this fires (wired to the parent signal). */
|
|
69
|
+
signal?: AbortSignal;
|
|
70
|
+
/** Abort+kill the child after this long (defaults to 10 min). */
|
|
71
|
+
timeoutMs?: number;
|
|
72
|
+
/** Capture point for stderr (for diagnostics, already included in error). */
|
|
73
|
+
onStderr?: (chunk: string) => void;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Fork the child-worker as a real OS process and await its single JSON
|
|
77
|
+
* ChildResult line. Secrets never reach argv: the payload rides stdin, the
|
|
78
|
+
* provider key/baseURL come from the inherited env. Non-zero exit / missing
|
|
79
|
+
* line → rejects with ChildCrashError so the orchestrator maps it to
|
|
80
|
+
* CHILD_CRASH exactly as an in-process throw does.
|
|
81
|
+
*/
|
|
82
|
+
export declare function forkChild(entry: string, payload: ChildWorkerPayload, opts?: ForkChildOptions): Promise<ChildResult>;
|
|
83
|
+
/**
|
|
84
|
+
* Rebuild a minimal RuntimeDeps from the payload + inherited env. Centralized
|
|
85
|
+
* so tests can call it directly. Errors on missing provider config.
|
|
86
|
+
*/
|
|
87
|
+
export declare function buildChildDeps(payload: ChildWorkerPayload): Promise<{
|
|
88
|
+
deps: RuntimeDeps;
|
|
89
|
+
options: RunOptions;
|
|
90
|
+
}>;
|
|
91
|
+
/**
|
|
92
|
+
* Read the JSON payload. Payloads arrive on stdin (the parent writes the JSON
|
|
93
|
+
* then ends stdin), never argv — large system prompts would otherwise fail
|
|
94
|
+
* the spawn with E2BIG on both Windows (32K cmdline) and Linux (MAX_ARG_STRLEN).
|
|
95
|
+
* Callers who must use argv (tests) can route through `runChildFromString`.
|
|
96
|
+
*/
|
|
97
|
+
export declare function readPayloadFromStdin(): Promise<string>;
|
|
98
|
+
/**
|
|
99
|
+
* Main entry. Reads payload from stdin, runs, prints ONE JSON ChildResult
|
|
100
|
+
* line, exits 0 on complete, 1 on failure/crash (so the parent knows to mark
|
|
101
|
+
* CHILD_CRASH). Intended to be the sole behavior of this file when invoked as
|
|
102
|
+
* a real process.
|
|
103
|
+
*/
|
|
104
|
+
export declare function main(): Promise<void>;
|