klyro 1.0.0 → 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.
Files changed (96) hide show
  1. package/dist/agent/anthropic-adapter.d.ts +36 -0
  2. package/dist/agent/anthropic-adapter.js +68 -16
  3. package/dist/agent/capabilities.d.ts +23 -0
  4. package/dist/agent/capabilities.js +46 -5
  5. package/dist/agent/child-worker.d.ts +104 -0
  6. package/dist/agent/child-worker.js +250 -0
  7. package/dist/agent/orchestrator.d.ts +106 -5
  8. package/dist/agent/orchestrator.js +378 -58
  9. package/dist/agent/provider-adapter.d.ts +8 -0
  10. package/dist/agent/provider-adapter.js +12 -3
  11. package/dist/agent/retry.d.ts +1 -1
  12. package/dist/agent/retry.js +2 -2
  13. package/dist/agent/runtime.d.ts +50 -8
  14. package/dist/agent/runtime.js +196 -33
  15. package/dist/agent/worktree-manager.d.ts +74 -0
  16. package/dist/agent/worktree-manager.js +189 -0
  17. package/dist/checkpoints/store.js +30 -5
  18. package/dist/cli/auth.js +16 -1
  19. package/dist/cli/config.d.ts +9 -3
  20. package/dist/cli/config.js +64 -3
  21. package/dist/cli/eval.d.ts +6 -1
  22. package/dist/cli/eval.js +9 -0
  23. package/dist/cli/repl.js +155 -28
  24. package/dist/cli/run.d.ts +7 -11
  25. package/dist/cli/run.js +68 -17
  26. package/dist/cli/update.d.ts +5 -0
  27. package/dist/cli/update.js +62 -10
  28. package/dist/context/import-graph.d.ts +2 -0
  29. package/dist/context/import-graph.js +31 -3
  30. package/dist/context/klyro-md.js +4 -1
  31. package/dist/context/memory.d.ts +8 -0
  32. package/dist/context/memory.js +50 -2
  33. package/dist/context/project-map.d.ts +6 -0
  34. package/dist/context/project-map.js +50 -2
  35. package/dist/context/repo-map.d.ts +2 -0
  36. package/dist/context/repo-map.js +31 -1
  37. package/dist/events/catalog.d.ts +28 -0
  38. package/dist/index.js +89 -4
  39. package/dist/mcp/client.d.ts +6 -4
  40. package/dist/mcp/client.js +83 -14
  41. package/dist/mcp/config.d.ts +10 -0
  42. package/dist/mcp/config.js +18 -1
  43. package/dist/mcp/registry.d.ts +23 -1
  44. package/dist/mcp/registry.js +78 -6
  45. package/dist/mcp/schema.d.ts +11 -4
  46. package/dist/mcp/schema.js +27 -16
  47. package/dist/mcp/trust.d.ts +20 -0
  48. package/dist/mcp/trust.js +74 -0
  49. package/dist/persistence/audit.d.ts +28 -0
  50. package/dist/persistence/audit.js +101 -1
  51. package/dist/persistence/store.d.ts +26 -2
  52. package/dist/persistence/store.js +140 -13
  53. package/dist/policy/approval.d.ts +14 -0
  54. package/dist/policy/approval.js +44 -2
  55. package/dist/policy/engine.d.ts +1 -0
  56. package/dist/policy/engine.js +88 -8
  57. package/dist/policy/secret-redactor.js +4 -0
  58. package/dist/providers/model-info.d.ts +17 -0
  59. package/dist/providers/model-info.js +35 -2
  60. package/dist/repl.d.ts +6 -0
  61. package/dist/repl.js +12 -7
  62. package/dist/tools/agent/spawn-agent.js +5 -5
  63. package/dist/tools/agent/task-apply.d.ts +4 -0
  64. package/dist/tools/agent/task-apply.js +44 -0
  65. package/dist/tools/agent/task-stop.d.ts +6 -0
  66. package/dist/tools/agent/task-stop.js +39 -0
  67. package/dist/tools/agent/task-wait.d.ts +17 -0
  68. package/dist/tools/agent/task-wait.js +79 -0
  69. package/dist/tools/fs/apply-patch.js +71 -0
  70. package/dist/tools/fs/edit-file.js +65 -0
  71. package/dist/tools/fs/multi-edit.d.ts +4 -0
  72. package/dist/tools/fs/multi-edit.js +66 -0
  73. package/dist/tools/fs/write-file.js +67 -0
  74. package/dist/tools/registry.js +6 -0
  75. package/dist/tools/shell/background.js +6 -3
  76. package/dist/tools/shell/sandbox.d.ts +51 -0
  77. package/dist/tools/shell/sandbox.js +143 -0
  78. package/dist/tools/shell/shell-exec.d.ts +1 -0
  79. package/dist/tools/shell/shell-exec.js +83 -11
  80. package/dist/tools/shell/worker-entry.d.ts +12 -0
  81. package/dist/tools/shell/worker-entry.js +43 -0
  82. package/dist/tools/types.d.ts +6 -0
  83. package/dist/tools/verify/run-verify.js +3 -1
  84. package/dist/trace/writer.d.ts +13 -0
  85. package/dist/trace/writer.js +55 -4
  86. package/dist/tui/app.js +1 -1
  87. package/dist/tui/approval.js +20 -21
  88. package/dist/util.d.ts +1 -0
  89. package/dist/util.js +1 -0
  90. package/dist/verification/baseline.js +17 -3
  91. package/dist/verification/classify.js +23 -12
  92. package/dist/verification/engine.js +3 -1
  93. package/dist/verification/registry.d.ts +2 -0
  94. package/dist/verification/registry.js +33 -0
  95. package/dist/verification/scoped.js +28 -6
  96. 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 {};
@@ -19,6 +19,7 @@ import { assertSafeBaseURL } from '../chat.js';
19
19
  import { parseRetryAfterMs } from './provider-adapter.js';
20
20
  const DEFAULT_VERSION = '2023-06-01';
21
21
  const DEFAULT_TIMEOUT_MS = 120_000;
22
+ export const PROMPT_CACHING_BETA = 'prompt-caching-2024-07-31';
22
23
  export class AnthropicApiError extends Error {
23
24
  status;
24
25
  body;
@@ -35,7 +36,10 @@ export function anthropicAdapter(opts) {
35
36
  const baseURL = rawBase.replace(/\/+$/, '');
36
37
  const version = opts.anthropicVersion ?? DEFAULT_VERSION;
37
38
  const authHeader = opts.authHeader ?? 'x-api-key';
38
- const betas = opts.betas ?? [];
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);
39
43
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
40
44
  if (!fetchImpl) {
41
45
  throw new Error('anthropicAdapter: no fetch available — pass opts.fetchImpl or run on Node 18+');
@@ -45,15 +49,36 @@ export function anthropicAdapter(opts) {
45
49
  stream(req) {
46
50
  return streamAnthropic(req, {
47
51
  baseURL, apiKey: opts.apiKey, timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
48
- fetchImpl, version, authHeader, betas,
52
+ fetchImpl, version, authHeader, betas, promptCache,
49
53
  });
50
54
  },
51
55
  };
52
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
+ }
53
78
  async function* streamAnthropic(req, opts) {
54
79
  const body = {
55
80
  model: req.model,
56
- system: req.system,
81
+ system: buildAnthropicSystem(req.system, req.systemSuffix, opts.promptCache),
57
82
  messages: toAnthropicMessages(req.messages),
58
83
  tools: req.tools.length > 0 ? req.tools.map(toAnthropicTool) : undefined,
59
84
  max_tokens: req.maxTokens ?? 4096,
@@ -98,11 +123,15 @@ async function* streamAnthropic(req, opts) {
98
123
  clearTimeout(timer);
99
124
  if (!resp.ok || !resp.body) {
100
125
  const text = await resp.text().catch(() => '<unreadable>');
101
- const retryable = resp.status >= 500 || resp.status === 429;
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);
102
131
  const retryAfterMs = retryable ? parseRetryAfterMs(resp.headers?.get('retry-after')) : undefined;
103
132
  yield {
104
133
  kind: 'error',
105
- code: `http_${resp.status}`,
134
+ code: isOverflow ? 'REQUEST_TOO_LARGE' : `http_${resp.status}`,
106
135
  message: `Anthropic API returned ${resp.status}: ${text.slice(0, 500)}`,
107
136
  retryable,
108
137
  status: String(resp.status),
@@ -208,25 +237,45 @@ async function* streamAnthropic(req, opts) {
208
237
  kind: 'message_end',
209
238
  finishReason: 'stop',
210
239
  ...(state.usage.input !== undefined || state.usage.output !== undefined
211
- ? { usage: { input: state.usage.input ?? 0, output: state.usage.output ?? 0 } }
240
+ ? { usage: withCache(state.usage) }
212
241
  : {}),
213
242
  };
214
243
  }
215
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
+ }
216
269
  function translateSse(event, parsed, state) {
217
270
  const out = [];
218
271
  switch (event) {
219
272
  case 'message_start': {
220
273
  const usage = parsed.message?.usage;
221
- if (typeof usage?.input_tokens === 'number')
222
- state.usage.input = usage.input_tokens;
274
+ recordAnthropicUsage(state, usage);
223
275
  return out;
224
276
  }
225
277
  case 'message_delta': {
226
- const usage = parsed.usage;
227
- if (typeof usage?.output_tokens === 'number') {
228
- state.usage.output = (state.usage.output ?? 0) + usage.output_tokens;
229
- }
278
+ recordAnthropicUsage(state, parsed.usage);
230
279
  return out;
231
280
  }
232
281
  case 'content_block_start': {
@@ -297,17 +346,20 @@ function translateSse(event, parsed, state) {
297
346
  kind: 'message_end',
298
347
  finishReason: 'stop',
299
348
  ...(state.usage.input !== undefined || state.usage.output !== undefined
300
- ? { usage: { input: state.usage.input ?? 0, output: state.usage.output ?? 0 } }
349
+ ? { usage: withCache(state.usage) }
301
350
  : {}),
302
351
  });
303
352
  return out;
304
353
  }
305
354
  case 'error': {
306
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}`);
307
359
  out.push({
308
360
  kind: 'error',
309
- code: err?.type ?? 'anthropic_error',
310
- message: err?.message ?? 'unknown Anthropic error',
361
+ code: isOverflow ? 'REQUEST_TOO_LARGE' : rawType,
362
+ message: rawMsg,
311
363
  retryable: false,
312
364
  });
313
365
  return out;
@@ -378,4 +430,4 @@ function toAnthropicTool(t) {
378
430
  };
379
431
  }
380
432
  // Re-export for testability.
381
- export const _internal = { toAnthropicMessages, toAnthropicTool, translateSse };
433
+ export const _internal = { toAnthropicMessages, toAnthropicTool, translateSse, buildAnthropicSystem };
@@ -29,6 +29,11 @@ export interface AgentCapabilities {
29
29
  model?: string;
30
30
  /** Recursion depth cap. Beyond this, spawn attempts are blocked. */
31
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[];
32
37
  }
33
38
  export interface ResolveToolsInput {
34
39
  /** Tools the parent is allowed to use (or `null` for the root agent = all registered tools). */
@@ -93,6 +98,11 @@ export interface ResolvedCapabilities {
93
98
  maxDepth: number;
94
99
  readonly: boolean;
95
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[];
96
106
  }
97
107
  /** Extra knobs consumed by `resolveCapabilities` on top of `ResolveToolsInput`. */
98
108
  export interface ResolveCapabilitiesExtra {
@@ -100,10 +110,23 @@ export interface ResolveCapabilitiesExtra {
100
110
  maxDepth: number;
101
111
  /** If true, the resolved `allowed` set may include tools that themselves spawn agents. */
102
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[];
103
118
  }
104
119
  /** Combined input for the one-shot resolver used by the orchestrator. */
105
120
  export interface ResolveCapabilitiesInput extends ResolveToolsInput, ResolveCapabilitiesExtra {
106
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;
107
130
  /**
108
131
  * One-shot capability resolver: tool intersection + per-agent model/maxDepth.
109
132
  *
@@ -71,10 +71,11 @@ export function resolveAgentTools(input) {
71
71
  }
72
72
  }
73
73
  }
74
- // 4. readonly → strip write tools.
74
+ // 4. readonly → strip write tools (plus run_verify: verification can
75
+ // execute arbitrary commands, so it is not read-only).
75
76
  if (agent.readonly) {
76
77
  for (const name of [...allowed]) {
77
- if (writeTools.has(name)) {
78
+ if (writeTools.has(name) || name === 'run_verify') {
78
79
  allowed.delete(name);
79
80
  dropped.push({ tool: name, reason: 'readonly' });
80
81
  }
@@ -104,6 +105,44 @@ export function resolveAgentTools(input) {
104
105
  dropped: dropped.sort((a, b) => a.tool.localeCompare(b.tool)),
105
106
  };
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
+ }
107
146
  /**
108
147
  * One-shot capability resolver: tool intersection + per-agent model/maxDepth.
109
148
  *
@@ -118,7 +157,10 @@ export function resolveCapabilities(input) {
118
157
  const allowed = new Set(resolved.allowed);
119
158
  const model = input.agent.model;
120
159
  const maxDepth = input.agent.maxDepth ?? input.maxDepth;
121
- const canSpawn = input.canSpawnOverride ?? input.agent.canSpawn ?? false;
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);
122
164
  return {
123
165
  allowed,
124
166
  dropped: resolved.dropped,
@@ -126,6 +168,7 @@ export function resolveCapabilities(input) {
126
168
  maxDepth,
127
169
  readonly: input.agent.readonly ?? false,
128
170
  canSpawn,
171
+ ...(allowedPaths !== undefined ? { allowedPaths } : {}),
129
172
  };
130
173
  }
131
174
  /** Sensible defaults for "what counts as a write tool" if a caller doesn't override. */
@@ -136,13 +179,11 @@ export const DEFAULT_WRITE_TOOLS = new Set([
136
179
  'apply_patch',
137
180
  'shell_exec',
138
181
  'memory_write',
139
- 'background_shell',
140
182
  'todo_write',
141
183
  ]);
142
184
  /** Default spawn tools — removed from children by default. */
143
185
  export const DEFAULT_SPAWN_TOOLS = new Set([
144
186
  'spawn_agent',
145
- 'subtask',
146
187
  ]);
147
188
  /** Default deny-list — these are NEVER allowed, even if explicitly requested. */
148
189
  export const DEFAULT_DENIED_TOOLS = new Set([
@@ -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>;