la-machina-engine 0.3.0

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.
@@ -0,0 +1,3587 @@
1
+ import * as zod from 'zod';
2
+ import { z, ZodTypeAny } from 'zod';
3
+ import { ContentBlockParam, MessageParam } from '@anthropic-ai/sdk/resources/messages';
4
+ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
5
+ import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
6
+
7
+ /**
8
+ * StorageAdapter — abstract filesystem interface for la-machina-engine.
9
+ *
10
+ * All paths are relative to the adapter's root. The adapter enforces
11
+ * path safety (no traversal, no absolute paths, no null bytes) and
12
+ * provides a filesystem-shaped API that subsystems can use without
13
+ * knowing whether the backend is local disk, R2, or any other
14
+ * S3-compatible object store.
15
+ *
16
+ * Implementations ship in this package:
17
+ * - `LocalStorageAdapter` (src/storage/localAdapter.ts) — fs-backed
18
+ * - `R2StorageAdapter` (src/storage/r2Adapter.ts) — S3-compatible
19
+ *
20
+ * Users may supply custom adapters implementing this interface.
21
+ */
22
+ interface FileStat {
23
+ readonly size: number;
24
+ readonly mtime: Date;
25
+ }
26
+ interface StorageAdapter {
27
+ /** Read a file as UTF-8 text. Returns null if file doesn't exist. */
28
+ readFile(relativePath: string): Promise<string | null>;
29
+ /** Write a file atomically. Creates parent directories if needed. */
30
+ writeFile(relativePath: string, content: string): Promise<void>;
31
+ /** Append to a file. Creates it if it doesn't exist. */
32
+ appendFile(relativePath: string, content: string): Promise<void>;
33
+ /** Delete a file. No-op if it doesn't exist. */
34
+ deleteFile(relativePath: string): Promise<void>;
35
+ /** Check if a file or directory exists. */
36
+ exists(relativePath: string): Promise<boolean>;
37
+ /**
38
+ * List entries in a directory. Returns just the names (not full paths).
39
+ * Returns an empty array if the directory doesn't exist.
40
+ */
41
+ listDir(relativePath: string): Promise<string[]>;
42
+ /** Create a directory (recursive). No-op on S3-like backends. */
43
+ mkdir(relativePath: string): Promise<void>;
44
+ /** Check if a path is a directory. */
45
+ isDirectory(relativePath: string): Promise<boolean>;
46
+ /** Get file metadata. Returns null if file doesn't exist. */
47
+ stat(relativePath: string): Promise<FileStat | null>;
48
+ }
49
+ /**
50
+ * Two-root storage model:
51
+ *
52
+ * - `global`: rooted at `{rootPath}/.claude/` — identity, universal rules
53
+ * - `workspace`: rooted at `{rootPath}/workspaces/{workspaceId}/.claude/` —
54
+ * project-specific memory, skills, sessions, episodes
55
+ *
56
+ * Every consumer gets a pair of adapters via `createEngineStorage()`.
57
+ */
58
+ interface EngineStorage {
59
+ readonly global: StorageAdapter;
60
+ readonly workspace: StorageAdapter;
61
+ }
62
+ /**
63
+ * Error thrown by all storage operations. `cause` is preserved as-is
64
+ * (usually an `ErrnoException` or AWS SDK error) so callers can inspect
65
+ * the underlying failure.
66
+ */
67
+ declare class StorageError extends Error {
68
+ readonly cause?: unknown;
69
+ constructor(message: string, cause?: unknown);
70
+ }
71
+
72
+ /**
73
+ * R2BindingStorageAdapter — Cloudflare Workers R2 binding (NOT S3 SDK).
74
+ *
75
+ * Unlike `R2StorageAdapter` which talks to R2 over the S3 REST protocol,
76
+ * this adapter uses the native R2 binding exposed to Workers runtime:
77
+ *
78
+ * [[r2_buckets]]
79
+ * binding = "BUCKET"
80
+ * bucket_name = "my-bucket"
81
+ *
82
+ * // In code:
83
+ * new R2BindingStorageAdapter(env.BUCKET, 'tenant/acme')
84
+ *
85
+ * Why use this over `R2StorageAdapter` on Workers?
86
+ * - `@aws-sdk/client-s3` is heavy (~500 KB) and the Workers-compatibility
87
+ * story for ListObjectsV2 XML parsing is brittle; CommonPrefixes come
88
+ * back empty in some runtimes, silently breaking `listDir`.
89
+ * - The R2 binding API (`bucket.list`, `bucket.get`, `bucket.put`,
90
+ * `bucket.delete`) is native, typed, and uses native JSON responses.
91
+ * - No AWS credentials, no endpoint URL — the binding handles auth.
92
+ *
93
+ * Use `R2StorageAdapter` on Node or anywhere you don't have a Workers
94
+ * binding (local dev with Miniflare's S3 endpoint, backfills, etc).
95
+ * Use this adapter inside Workers for reliability and bundle size.
96
+ *
97
+ * All pure JS — no `node:` imports, Workers-compatible.
98
+ */
99
+
100
+ interface R2Object {
101
+ key: string;
102
+ size: number;
103
+ uploaded: Date;
104
+ httpMetadata?: R2HTTPMetadata;
105
+ customMetadata?: Record<string, string>;
106
+ }
107
+ interface R2ObjectBody extends R2Object {
108
+ text(): Promise<string>;
109
+ arrayBuffer(): Promise<ArrayBuffer>;
110
+ blob?(): Promise<Blob>;
111
+ }
112
+ interface R2Objects {
113
+ objects: R2Object[];
114
+ truncated: boolean;
115
+ cursor?: string;
116
+ delimitedPrefixes?: string[];
117
+ }
118
+ interface R2HTTPMetadata {
119
+ contentType?: string;
120
+ contentLanguage?: string;
121
+ contentDisposition?: string;
122
+ contentEncoding?: string;
123
+ cacheControl?: string;
124
+ cacheExpiry?: Date;
125
+ }
126
+ interface R2ListOptions {
127
+ prefix?: string;
128
+ delimiter?: string;
129
+ cursor?: string;
130
+ limit?: number;
131
+ include?: Array<'httpMetadata' | 'customMetadata'>;
132
+ }
133
+ interface R2PutOptions {
134
+ httpMetadata?: R2HTTPMetadata;
135
+ customMetadata?: Record<string, string>;
136
+ }
137
+ /**
138
+ * Structural type for `env.BUCKET` — intentionally not imported from
139
+ * `@cloudflare/workers-types` so this adapter has no extra peer deps.
140
+ */
141
+ interface R2BucketBinding {
142
+ head(key: string): Promise<R2Object | null>;
143
+ get(key: string): Promise<R2ObjectBody | null>;
144
+ put(key: string, value: string | ArrayBuffer | ArrayBufferView | ReadableStream | Blob | null, options?: R2PutOptions): Promise<R2Object>;
145
+ delete(keys: string | string[]): Promise<void>;
146
+ list(options?: R2ListOptions): Promise<R2Objects>;
147
+ }
148
+ declare class R2BindingStorageAdapter implements StorageAdapter {
149
+ private readonly bucket;
150
+ private readonly rootPrefix;
151
+ constructor(bucket: R2BucketBinding, rootPrefix: string);
152
+ private key;
153
+ readFile(rel: string): Promise<string | null>;
154
+ writeFile(rel: string, content: string): Promise<void>;
155
+ appendFile(rel: string, content: string): Promise<void>;
156
+ deleteFile(rel: string): Promise<void>;
157
+ exists(rel: string): Promise<boolean>;
158
+ /**
159
+ * List direct children of `rel` (files + subdirectory names). Returns
160
+ * just the names (not full keys). Empty array if path doesn't exist.
161
+ *
162
+ * Uses the native R2 list API with `delimiter: '/'` which returns
163
+ * `delimitedPrefixes` for "subdirectories" and `objects` for files —
164
+ * equivalent to S3's CommonPrefixes + Contents but parsed by the
165
+ * binding itself (no XML, no SDK).
166
+ */
167
+ listDir(rel: string): Promise<string[]>;
168
+ mkdir(rel: string): Promise<void>;
169
+ isDirectory(rel: string): Promise<boolean>;
170
+ stat(rel: string): Promise<FileStat | null>;
171
+ }
172
+
173
+ /**
174
+ * Compaction config + result types.
175
+ */
176
+
177
+ type CompactionStrategy = 'drop-middle' | 'summarize' | 'session-memory' | 'auto';
178
+ interface ResolvedCompactionConfig {
179
+ /** Which strategy to use. 'auto' picks the best one at runtime. */
180
+ readonly strategy: CompactionStrategy;
181
+ /** Fraction (0-1) of context window that triggers compaction. */
182
+ readonly threshold: number;
183
+ /** How many recent messages to keep after compaction. */
184
+ readonly keepLast: number;
185
+ /** Max tokens for the LLM-generated summary. */
186
+ readonly summaryMaxTokens: number;
187
+ /** Enable time-based microcompaction of old tool results. */
188
+ readonly microcompact: boolean;
189
+ /** Age in ms after which tool results are eligible for microcompaction. */
190
+ readonly microcompactAgeMs: number;
191
+ }
192
+
193
+ /**
194
+ * Coordinator config types.
195
+ */
196
+ interface ResolvedCoordinatorConfig {
197
+ /** Whether coordinator mode is active. Default: false. */
198
+ readonly enabled: boolean;
199
+ /**
200
+ * Tools available to worker agents. Workers deliberately lack Agent
201
+ * (no sub-delegation) and typically lack MCP (coordinator controls
202
+ * external access). Default: core file/search tools only.
203
+ */
204
+ readonly workerTools: readonly string[];
205
+ /** Max concurrent workers the coordinator can spawn. Default: 5. */
206
+ readonly maxConcurrentWorkers: number;
207
+ }
208
+
209
+ /**
210
+ * streaming — normalize raw Anthropic stream events into a small,
211
+ * well-shaped union that the engine's agent loop can consume.
212
+ *
213
+ * The Anthropic SDK emits a discriminated union of six raw events per
214
+ * stream:
215
+ * message_start — metadata and initial usage
216
+ * content_block_start — begin a text or tool_use block
217
+ * content_block_delta — partial text or input_json chunk
218
+ * content_block_stop — end of a block
219
+ * message_delta — stop_reason + final output_tokens
220
+ * message_stop — terminal event
221
+ *
222
+ * This module collects deltas into complete blocks and emits a cleaner
223
+ * union: `text` / `tool_use` / `message_stop`. Tool_use input JSON is
224
+ * assembled and parsed once at content_block_stop so downstream code
225
+ * sees a typed object rather than raw partial JSON.
226
+ *
227
+ * Errors:
228
+ * - Malformed tool_use JSON → `StreamParseError`
229
+ * - Stream ends without `message_stop` → `StreamIncompleteError`
230
+ */
231
+
232
+ interface TokenUsage$1 {
233
+ readonly input: number;
234
+ readonly output: number;
235
+ readonly cacheCreationInput?: number | undefined;
236
+ readonly cacheReadInput?: number | undefined;
237
+ }
238
+ /**
239
+ * Common Anthropic stop reasons. The `(string & {})` pattern preserves
240
+ * autocomplete for the documented values while still allowing arbitrary
241
+ * strings from third-party proxies that may use other vocabularies.
242
+ */
243
+ type StopReason = 'end_turn' | 'tool_use' | 'max_tokens' | 'stop_sequence' | 'refusal' | 'pause_turn' | (string & Record<never, never>);
244
+ type NormalizedEvent = {
245
+ readonly type: 'text';
246
+ readonly index: number;
247
+ readonly text: string;
248
+ } | {
249
+ readonly type: 'tool_use';
250
+ readonly index: number;
251
+ readonly id: string;
252
+ readonly name: string;
253
+ readonly input: unknown;
254
+ } | {
255
+ readonly type: 'thinking';
256
+ readonly index: number;
257
+ readonly thinking: string;
258
+ } | {
259
+ readonly type: 'message_stop';
260
+ readonly stopReason: StopReason;
261
+ readonly usage: TokenUsage$1;
262
+ };
263
+
264
+ /**
265
+ * Orchestrator types — plan schema, step results, retry policy,
266
+ * orchestrator config, and the public OrchestratorResult.
267
+ */
268
+
269
+ declare const PlanStepSchema: z.ZodObject<{
270
+ id: z.ZodString;
271
+ description: z.ZodString;
272
+ action: z.ZodEnum<["research", "implement", "verify", "review", "custom"]>;
273
+ files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
274
+ spec: z.ZodOptional<z.ZodString>;
275
+ dependsOn: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
276
+ }, "strip", z.ZodTypeAny, {
277
+ id: string;
278
+ description: string;
279
+ action: "custom" | "research" | "implement" | "verify" | "review";
280
+ files?: string[] | undefined;
281
+ spec?: string | undefined;
282
+ dependsOn?: string[] | undefined;
283
+ }, {
284
+ id: string;
285
+ description: string;
286
+ action: "custom" | "research" | "implement" | "verify" | "review";
287
+ files?: string[] | undefined;
288
+ spec?: string | undefined;
289
+ dependsOn?: string[] | undefined;
290
+ }>;
291
+ declare const PlanSchema: z.ZodObject<{
292
+ summary: z.ZodString;
293
+ steps: z.ZodArray<z.ZodObject<{
294
+ id: z.ZodString;
295
+ description: z.ZodString;
296
+ action: z.ZodEnum<["research", "implement", "verify", "review", "custom"]>;
297
+ files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
298
+ spec: z.ZodOptional<z.ZodString>;
299
+ dependsOn: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
300
+ }, "strip", z.ZodTypeAny, {
301
+ id: string;
302
+ description: string;
303
+ action: "custom" | "research" | "implement" | "verify" | "review";
304
+ files?: string[] | undefined;
305
+ spec?: string | undefined;
306
+ dependsOn?: string[] | undefined;
307
+ }, {
308
+ id: string;
309
+ description: string;
310
+ action: "custom" | "research" | "implement" | "verify" | "review";
311
+ files?: string[] | undefined;
312
+ spec?: string | undefined;
313
+ dependsOn?: string[] | undefined;
314
+ }>, "many">;
315
+ }, "strip", z.ZodTypeAny, {
316
+ summary: string;
317
+ steps: {
318
+ id: string;
319
+ description: string;
320
+ action: "custom" | "research" | "implement" | "verify" | "review";
321
+ files?: string[] | undefined;
322
+ spec?: string | undefined;
323
+ dependsOn?: string[] | undefined;
324
+ }[];
325
+ }, {
326
+ summary: string;
327
+ steps: {
328
+ id: string;
329
+ description: string;
330
+ action: "custom" | "research" | "implement" | "verify" | "review";
331
+ files?: string[] | undefined;
332
+ spec?: string | undefined;
333
+ dependsOn?: string[] | undefined;
334
+ }[];
335
+ }>;
336
+ type PlanStep = z.infer<typeof PlanStepSchema>;
337
+ type Plan = z.infer<typeof PlanSchema>;
338
+ type StepStatus = 'done' | 'failed' | 'skipped' | 'reverted';
339
+ interface StepResult {
340
+ readonly stepId: string;
341
+ readonly action: string;
342
+ readonly status: StepStatus;
343
+ readonly output?: string | undefined;
344
+ readonly error?: string | undefined;
345
+ readonly attempts: number;
346
+ readonly agentId?: string | undefined;
347
+ }
348
+ interface FileSnapshot {
349
+ readonly path: string;
350
+ /** null = file didn't exist before (delete on revert). */
351
+ readonly content: string | null;
352
+ }
353
+ interface RetryPolicy {
354
+ readonly maxAttempts: number;
355
+ readonly backoffMs: number;
356
+ readonly onExhausted: 'revert' | 'skip' | 'fail';
357
+ }
358
+ interface ResolvedRetryPolicies {
359
+ readonly plan: RetryPolicy;
360
+ readonly research: RetryPolicy;
361
+ readonly implement: RetryPolicy;
362
+ readonly verify: RetryPolicy;
363
+ readonly review: RetryPolicy;
364
+ }
365
+ interface ResolvedOrchestratorConfig {
366
+ readonly enabled: boolean;
367
+ readonly retries: ResolvedRetryPolicies;
368
+ readonly maxParallelResearchers: number;
369
+ readonly enableReview: boolean;
370
+ readonly enableRollback: boolean;
371
+ readonly maxPlanSteps: number;
372
+ /** Max turns per internal agent run (planner, researcher, implementer, verifier). Default: 15. */
373
+ readonly agentMaxTurns: number;
374
+ }
375
+ interface OrchestratorResult {
376
+ readonly status: 'done' | 'partial' | 'failed';
377
+ readonly output: string;
378
+ readonly plan: Plan | null;
379
+ readonly steps: readonly StepResult[];
380
+ readonly tokensUsed: TokenUsage$1;
381
+ readonly agentRuns: number;
382
+ readonly reverted: readonly string[];
383
+ readonly durationMs: number;
384
+ }
385
+ interface OrchestrateOptions {
386
+ readonly runId: string;
387
+ readonly nodeId: string;
388
+ readonly task: string;
389
+ /** Optional pre-built plan — skip the Planner agent. */
390
+ readonly plan?: Plan | undefined;
391
+ }
392
+
393
+ /**
394
+ * Tool contract — the shape every tool implements, plus a registry to
395
+ * collect them by name.
396
+ *
397
+ * The contract is intentionally minimal:
398
+ *
399
+ * 1. A tool has a `name`, a `description`, and a Zod `inputSchema`.
400
+ * Zod gives both runtime validation and a TypeScript-inferred
401
+ * input type for `execute()`.
402
+ * 2. `execute(input, context)` returns a `ToolResult` (or rejects).
403
+ * The runtime catches rejections and turns them into error
404
+ * results — a misbehaving tool can never crash the agent loop.
405
+ * 3. The runtime supplies a `ToolContext` carrying at least an
406
+ * `AbortSignal` for cancellation.
407
+ *
408
+ * Phase 5 ships the types and the registry. Phase 6 fills the registry
409
+ * with the core tool set (Bash, Read, Write, Edit, Glob, Grep, WebFetch).
410
+ * Custom tools come from `config.tools.custom` at engine init time.
411
+ */
412
+
413
+ /**
414
+ * The execution context passed to every tool. Phase 5 keeps this
415
+ * minimal — only an AbortSignal — so the contract stays portable.
416
+ * Later phases extend with storage, run/node ids, hook handles, etc.
417
+ */
418
+ interface ToolContext {
419
+ readonly signal: AbortSignal;
420
+ }
421
+ /**
422
+ * The result of a single tool invocation. The runtime wraps thrown
423
+ * errors in this shape so the agent loop only ever sees success/error
424
+ * as data, not exceptions.
425
+ */
426
+ interface ToolResult {
427
+ /** Human- and model-readable output. The agent's next turn sees this. */
428
+ readonly content: string;
429
+ /** True when the tool failed. */
430
+ readonly isError?: boolean;
431
+ /** Optional structured metadata for hooks / observability. */
432
+ readonly metadata?: Readonly<Record<string, unknown>>;
433
+ }
434
+ /**
435
+ * A tool definition. Generic on a Zod schema so that `execute()`'s
436
+ * input parameter is statically typed via `z.infer<TSchema>`.
437
+ *
438
+ * Tool authors normally use `defineTool()` to get the inference
439
+ * without writing the generic by hand.
440
+ */
441
+ interface Tool$1<TSchema extends z.ZodTypeAny = z.ZodTypeAny> {
442
+ readonly name: string;
443
+ readonly description: string;
444
+ readonly inputSchema: TSchema;
445
+ /**
446
+ * Optional raw JSON Schema to send to the Anthropic API as
447
+ * `input_schema`, bypassing Zod-to-JSON-Schema conversion. Used by
448
+ * MCP-sourced tools whose schema is authored in JSON Schema directly
449
+ * and whose Zod schema is a `z.unknown()` passthrough.
450
+ *
451
+ * When set, `toAnthropicTool()` in the agent loop prefers this over
452
+ * running `zodToJsonSchema(inputSchema)`. Normal in-process tools
453
+ * defined via `defineTool()` should leave this undefined.
454
+ */
455
+ readonly anthropicSchemaOverride?: Record<string, unknown>;
456
+ /**
457
+ * Returns true if this tool is safe to run concurrently with other
458
+ * concurrency-safe tools. Read-only tools (Read, Glob, Grep, etc.)
459
+ * should return true; mutating tools (Write, Edit, Bash) should
460
+ * return false. Default: false (fail-closed).
461
+ *
462
+ * The agent loop uses this to batch consecutive safe tool calls
463
+ * into a single `Promise.all()` for parallel execution, while
464
+ * running unsafe tools serially.
465
+ *
466
+ * Input is typed as `unknown` (not `z.infer<TSchema>`) so that
467
+ * `Tool<ZodObject<...>>` remains assignable to `Tool<ZodTypeAny>`
468
+ * under `exactOptionalPropertyTypes`.
469
+ */
470
+ isConcurrencySafe?: ((input: unknown) => boolean) | undefined;
471
+ execute(input: z.infer<TSchema>, context: ToolContext): Promise<ToolResult>;
472
+ }
473
+ /**
474
+ * Identity helper that locks in the schema generic so callers don't have
475
+ * to write it. Use this whenever defining a tool — it costs nothing at
476
+ * runtime and gives you full inference inside `execute()`.
477
+ *
478
+ * @example
479
+ * ```ts
480
+ * const Echo = defineTool({
481
+ * name: 'Echo',
482
+ * description: 'Echoes its input back',
483
+ * inputSchema: z.object({ msg: z.string() }),
484
+ * execute: async ({ msg }) => ({ content: msg }),
485
+ * })
486
+ * ```
487
+ */
488
+ declare function defineTool<TSchema extends z.ZodTypeAny>(tool: Tool$1<TSchema>): Tool$1<TSchema>;
489
+ /**
490
+ * In-memory registry of tools by name. Used by the engine's tool runtime
491
+ * to dispatch tool calls. Re-registering the same name throws so typos
492
+ * surface immediately rather than silently shadowing.
493
+ */
494
+ declare class ToolRegistry {
495
+ private readonly tools;
496
+ register(tool: Tool$1): void;
497
+ registerAll(tools: ReadonlyArray<Tool$1>): void;
498
+ unregister(name: string): void;
499
+ get(name: string): Tool$1 | undefined;
500
+ has(name: string): boolean;
501
+ list(): Tool$1[];
502
+ count(): number;
503
+ }
504
+
505
+ /**
506
+ * Hook event payloads + handler types.
507
+ *
508
+ * Six lifecycle slots match the `config.hooks` shape from Phase 1:
509
+ *
510
+ * preRun — fires once before the agent loop starts
511
+ * postRun — fires once when the run exits (done | paused | failed)
512
+ * preTurn — fires before each turn's API call
513
+ * postTurn — fires after each turn's tool dispatches complete
514
+ * preToolCall — fires before each tool execution
515
+ * postToolCall — fires after each tool execution (always — even on error)
516
+ *
517
+ * Hooks are functions: `(event) => void | Promise<void>`. They run in
518
+ * registration order, sequentially. Errors thrown by a hook are
519
+ * isolated — they never abort the run.
520
+ */
521
+
522
+ interface PreRunEvent {
523
+ readonly runId: string;
524
+ readonly nodeId: string;
525
+ readonly task: string;
526
+ }
527
+ interface PostRunEvent {
528
+ readonly runId: string;
529
+ readonly nodeId: string;
530
+ readonly status: 'done' | 'paused' | 'failed';
531
+ readonly tokensUsed: TokenUsage$1;
532
+ readonly turnsUsed: number;
533
+ readonly output?: string;
534
+ readonly error?: Error;
535
+ /** Number of tool calls dispatched during the run. */
536
+ readonly toolCallCount?: number;
537
+ /** Transcript path for this run (e.g. "projects/{runId}/nodes/{nodeId}"). */
538
+ readonly transcriptPath?: string;
539
+ }
540
+ interface PreTurnEvent {
541
+ readonly runId: string;
542
+ readonly nodeId: string;
543
+ readonly turnIndex: number;
544
+ readonly messageCount: number;
545
+ }
546
+ interface PostTurnEvent {
547
+ readonly runId: string;
548
+ readonly nodeId: string;
549
+ readonly turnIndex: number;
550
+ readonly tokensUsed: TokenUsage$1;
551
+ }
552
+ /**
553
+ * Stop hook event — fired after each turn completes (tool dispatch or
554
+ * terminal). Ported from La-Machina's stopHooks.ts. The hook can
555
+ * inspect the full turn state and optionally prevent continuation.
556
+ */
557
+ interface StopHookEvent {
558
+ readonly runId: string;
559
+ readonly nodeId: string;
560
+ readonly turnIndex: number;
561
+ readonly tokensUsed: TokenUsage$1;
562
+ readonly lastAssistantText: string;
563
+ readonly toolsCalled: readonly string[];
564
+ readonly status: 'tool_use' | 'end_turn';
565
+ }
566
+ /**
567
+ * Stop hook result. Return `{ preventContinuation: true }` to stop
568
+ * the run gracefully after this turn. The run returns status 'done'
569
+ * with the last assistant output. If multiple stop hooks run, any
570
+ * one returning preventContinuation stops the run.
571
+ */
572
+ interface StopHookResult {
573
+ readonly preventContinuation?: boolean;
574
+ readonly reason?: string;
575
+ }
576
+ interface PreToolCallEvent$1 {
577
+ readonly toolName: string;
578
+ readonly input: unknown;
579
+ }
580
+ interface PostToolCallEvent$1 {
581
+ readonly toolName: string;
582
+ readonly input: unknown;
583
+ readonly result: ToolResult;
584
+ readonly durationMs: number;
585
+ }
586
+ /**
587
+ * Gate decision returned by `gateBeforeTool`. `allow: true` lets the
588
+ * dispatch proceed; `allow: false` pauses the run with `reason` captured
589
+ * in the transcript snapshot.
590
+ */
591
+ interface GateDecision$1 {
592
+ readonly allow: boolean;
593
+ readonly reason?: string;
594
+ }
595
+ type PreRunHook = (event: PreRunEvent) => void | Promise<void>;
596
+ type PostRunHook = (event: PostRunEvent) => void | Promise<void>;
597
+ type PreTurnHook = (event: PreTurnEvent) => void | Promise<void>;
598
+ type PostTurnHook = (event: PostTurnEvent) => void | Promise<void>;
599
+ type PreToolCallHook = (event: PreToolCallEvent$1) => void | Promise<void>;
600
+ type PostToolCallHook = (event: PostToolCallEvent$1) => void | Promise<void>;
601
+ /**
602
+ * Stop hook — fires after each turn. Can return `{ preventContinuation: true }`
603
+ * to stop the run gracefully. Ported from La-Machina's executeStopHooks().
604
+ */
605
+ type StopHook = (event: StopHookEvent) => StopHookResult | void | Promise<StopHookResult | void>;
606
+ /**
607
+ * Gate hook signature — synchronous or async. Receives the tool name and
608
+ * the validated input, returns a `GateDecision`. Distinct from the other
609
+ * hook slots because its return value drives pause/resume behavior.
610
+ */
611
+ type GateBeforeToolHook = (toolName: string, input: unknown) => GateDecision$1 | Promise<GateDecision$1>;
612
+
613
+ /**
614
+ * MCP config types.
615
+ *
616
+ * Three transport variants (discriminated on `type`):
617
+ * - `stdio` — subprocess spawning, most common
618
+ * - `http` — Streamable HTTP (modern MCP spec, preferred for cloud)
619
+ * - `sse` — Server-Sent Events (legacy, still in use by many servers)
620
+ */
621
+ interface McpToolDef {
622
+ readonly name: string;
623
+ readonly description: string;
624
+ readonly inputSchema: Record<string, unknown>;
625
+ }
626
+ interface McpCallResult {
627
+ readonly content: string;
628
+ readonly isError?: boolean;
629
+ }
630
+ type McpServerState = 'uninitialized' | 'connecting' | 'connected' | 'failed' | 'disconnected';
631
+ interface ResolvedMcpStdioServerConfig {
632
+ readonly type: 'stdio';
633
+ readonly command: string;
634
+ readonly args: readonly string[];
635
+ readonly env: Readonly<Record<string, string>> | undefined;
636
+ readonly cwd: string | undefined;
637
+ readonly isolateEnv: boolean;
638
+ /** See `ResolvedMcpHttpServerConfig.allowSampling`. */
639
+ readonly allowSampling?: boolean | undefined;
640
+ }
641
+ interface ResolvedMcpHttpServerConfig {
642
+ readonly type: 'http';
643
+ /** Full URL of the MCP server endpoint, e.g. `https://mcp.example.com/v1`. */
644
+ readonly url: string;
645
+ /** Optional headers sent on every request (auth tokens, API keys). */
646
+ readonly headers: Readonly<Record<string, string>> | undefined;
647
+ /**
648
+ * Use the Workers-friendly plain-POST transport (`BindingHttpTransport`)
649
+ * instead of the default `StreamableHTTPClientTransport`. Enable when
650
+ * running on Cloudflare Workers — the SDK's streaming transport can
651
+ * hang on Workers after `initialize`. Default: false.
652
+ */
653
+ readonly preferBindingTransport?: boolean | undefined;
654
+ /**
655
+ * Per-request dynamic header provider. When set, called before every
656
+ * MCP send and its result is merged OVER the static `headers`. Use
657
+ * for OAuth tokens that need refresh. On HTTP 401 the engine invokes
658
+ * the provider a second time and retries the request once.
659
+ *
660
+ * Must be idempotent — may be called many times per run. Not called
661
+ * for stdio servers (their env is immutable post-spawn).
662
+ */
663
+ readonly headersProvider?: (() => Promise<Readonly<Record<string, string>>>) | undefined;
664
+ /**
665
+ * Allow this server to request LLM completions via MCP's
666
+ * `sampling/createMessage`. OFF by default — sampling consumes the
667
+ * engine's LLM budget, so opt-in is explicit per server. See Plan 018 §2.
668
+ */
669
+ readonly allowSampling?: boolean | undefined;
670
+ }
671
+ interface ResolvedMcpSseServerConfig {
672
+ readonly type: 'sse';
673
+ /** Full URL of the SSE endpoint, e.g. `https://mcp.example.com/sse`. */
674
+ readonly url: string;
675
+ /** Optional headers sent on every request. */
676
+ readonly headers: Readonly<Record<string, string>> | undefined;
677
+ /** See `ResolvedMcpHttpServerConfig.headersProvider`. */
678
+ readonly headersProvider?: (() => Promise<Readonly<Record<string, string>>>) | undefined;
679
+ /** See `ResolvedMcpHttpServerConfig.allowSampling`. */
680
+ readonly allowSampling?: boolean | undefined;
681
+ }
682
+ type ResolvedMcpServerConfig = ResolvedMcpStdioServerConfig | ResolvedMcpHttpServerConfig | ResolvedMcpSseServerConfig;
683
+ interface ResolvedMcpConfig {
684
+ readonly servers: Readonly<Record<string, ResolvedMcpServerConfig>>;
685
+ readonly connectTimeoutMs: number;
686
+ readonly callTimeoutMs: number;
687
+ /** Timeout for shutdownAll() before force-killing servers. Default: 5000. */
688
+ readonly shutdownTimeoutMs: number;
689
+ }
690
+
691
+ /**
692
+ * Permission system types.
693
+ *
694
+ * Three modes:
695
+ * - `'open'` — every tool is allowed (v0.1 behavior, default)
696
+ * - `'rules'` — evaluate rule strings in order, first match wins
697
+ * - `'locked'` — only safe-allowlist tools pass, everything else denied
698
+ *
699
+ * Rule syntax:
700
+ * - `'allow:Read'` — allow the Read tool
701
+ * - `'deny:Bash'` — deny the Bash tool
702
+ * - `'allow:mcp__fs__*'` — glob: allow all tools from the fs MCP server
703
+ * - `'deny:*'` — deny everything not explicitly allowed above
704
+ *
705
+ * Rules are evaluated top-to-bottom. The first matching rule wins. If no
706
+ * rule matches, the decision falls to the mode's default:
707
+ * - `'rules'` mode → deny (fall-closed)
708
+ * - `'open'` mode → allow (rules are informational only)
709
+ * - `'locked'` mode → rules field is ignored entirely
710
+ */
711
+ type PermissionMode = 'open' | 'rules' | 'locked';
712
+ type PermissionAction = 'allow' | 'deny';
713
+ interface PermissionRule {
714
+ readonly action: PermissionAction;
715
+ readonly pattern: string;
716
+ }
717
+ type PermissionDecision = {
718
+ readonly allowed: true;
719
+ } | {
720
+ readonly allowed: false;
721
+ readonly reason: string;
722
+ };
723
+ interface ResolvedPermissionsConfig {
724
+ readonly mode: PermissionMode;
725
+ readonly rules: readonly string[];
726
+ }
727
+
728
+ /** Placeholder for the Phase 5 tool contract. */
729
+ type Tool = unknown;
730
+ /**
731
+ * Structured log record emitted by the engine to `config.logging.sink`.
732
+ * `ts` is an ISO-8601 timestamp. `meta` is any caller-supplied context.
733
+ */
734
+ interface LogEntry {
735
+ readonly level: LogLevel;
736
+ readonly msg: string;
737
+ readonly ts: string;
738
+ readonly meta?: Record<string, unknown>;
739
+ }
740
+ type ModelProvider = 'anthropic' | 'openai' | 'google' | 'openai-compatible' | 'bedrock' | 'vertex' | 'proxy';
741
+ type StorageProvider = 'local' | 'r2' | 'r2-binding';
742
+ type MemoryMode = 'off' | 'read-only' | 'read-write';
743
+ type MemoryScope = 'workspace' | 'global';
744
+ type FlushPolicy = 'turn-end' | 'entry' | 'manual';
745
+ type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'debug';
746
+ type LogSink = 'stderr' | 'none' | ((entry: LogEntry) => void);
747
+ interface ResolvedR2Config {
748
+ readonly bucket: string;
749
+ readonly region: string;
750
+ readonly accessKeyId: string;
751
+ readonly secretAccessKey: string;
752
+ readonly endpoint?: string | undefined;
753
+ }
754
+ interface ResolvedModelConfig {
755
+ readonly provider: ModelProvider;
756
+ readonly modelId: string;
757
+ readonly apiKey: string;
758
+ readonly baseURL?: string | undefined;
759
+ readonly maxTokens: number;
760
+ readonly temperature: number;
761
+ /** Max retries on transient API errors (429, 500). 0 = no retry. Default: 2. */
762
+ readonly maxRetries: number;
763
+ }
764
+ interface ResolvedStorageConfig {
765
+ readonly provider: StorageProvider;
766
+ readonly rootPath: string;
767
+ readonly workspaceId: string;
768
+ readonly r2?: ResolvedR2Config | undefined;
769
+ /**
770
+ * Cloudflare R2 binding object (env.BUCKET) — required when
771
+ * provider === 'r2-binding'. Structurally: { head, get, put, delete, list }.
772
+ * Not serializable — kept as-is and used only inside the factory.
773
+ */
774
+ readonly r2Binding?: R2BucketBinding | undefined;
775
+ }
776
+ interface ResolvedMemoryConfig {
777
+ readonly mode: MemoryMode;
778
+ readonly scope: MemoryScope;
779
+ }
780
+ interface ResolvedToolsConfig {
781
+ readonly enabled: readonly string[];
782
+ readonly disabled: readonly string[];
783
+ readonly custom: readonly Tool[];
784
+ }
785
+ interface ResolvedAgentsConfig {
786
+ readonly builtins: readonly string[];
787
+ readonly customPath?: string | undefined;
788
+ }
789
+ interface ResolvedSkillsConfig {
790
+ readonly path?: string | undefined;
791
+ readonly autoload: boolean;
792
+ /**
793
+ * SSRF allowlist for per-run `InlineSkillSource` URL fetches.
794
+ * When undefined / empty, any URL is allowed (development default).
795
+ * Production deployments should set this to their CDN / R2 host(s).
796
+ */
797
+ readonly allowedHosts?: readonly string[] | undefined;
798
+ }
799
+ interface ResolvedExecutionConfig {
800
+ readonly maxTurns: number;
801
+ readonly maxSubagentDepth: number;
802
+ readonly turnTimeoutMs: number;
803
+ readonly runTimeoutMs: number;
804
+ /** Context window size in tokens. Used by compaction to decide when to compact. Default: 200_000. */
805
+ readonly contextLimit: number;
806
+ /** Max number of concurrency-safe tools to execute in parallel. Default: 10. */
807
+ readonly maxToolConcurrency: number;
808
+ }
809
+ interface ResolvedTranscriptConfig {
810
+ readonly enabled: boolean;
811
+ readonly flushPolicy: FlushPolicy;
812
+ readonly idleFlushMs: number;
813
+ }
814
+ interface ResolvedHooksConfig {
815
+ readonly preRun: readonly PreRunHook[];
816
+ readonly postRun: readonly PostRunHook[];
817
+ readonly preTurn: readonly PreTurnHook[];
818
+ readonly postTurn: readonly PostTurnHook[];
819
+ readonly preToolCall: readonly PreToolCallHook[];
820
+ readonly postToolCall: readonly PostToolCallHook[];
821
+ /**
822
+ * Optional gate called before every tool dispatch. Returning
823
+ * `{ allow: false, reason? }` pauses the run with the pending tool
824
+ * captured in the snapshot. `undefined` means no gate — the default.
825
+ *
826
+ * This is the only real pause pathway in v0.1. Setting this enables
827
+ * human-in-the-loop workflows without bypassing `initEngine()`.
828
+ */
829
+ readonly gateBeforeTool: GateBeforeToolHook | undefined;
830
+ /**
831
+ * When true, the parent's gate propagates into every spawned
832
+ * subagent's inner loop. A gate denial inside the child pauses the
833
+ * parent run with `RunSnapshot.pendingSubagent` populated so
834
+ * observers can see which subagent tool was blocked. Default: false.
835
+ *
836
+ * Only the parent engine reads this flag; subagents don't re-read it.
837
+ * The `Agent` tool factory receives the effective gate callback at
838
+ * construction time.
839
+ */
840
+ readonly propagateGateToSubagents?: boolean | undefined;
841
+ /**
842
+ * Stop hooks — fire after each turn. Can return `{ preventContinuation: true }`
843
+ * to stop the run gracefully. Ported from La-Machina's executeStopHooks().
844
+ */
845
+ readonly stopHooks: readonly StopHook[];
846
+ }
847
+ interface ResolvedLoggingConfig {
848
+ readonly level: LogLevel;
849
+ readonly sink: LogSink;
850
+ }
851
+ interface ResolvedConfig {
852
+ readonly model: ResolvedModelConfig;
853
+ readonly storage: ResolvedStorageConfig;
854
+ readonly memory: ResolvedMemoryConfig;
855
+ readonly tools: ResolvedToolsConfig;
856
+ readonly agents: ResolvedAgentsConfig;
857
+ readonly skills: ResolvedSkillsConfig;
858
+ readonly execution: ResolvedExecutionConfig;
859
+ readonly transcript: ResolvedTranscriptConfig;
860
+ readonly hooks: ResolvedHooksConfig;
861
+ readonly logging: ResolvedLoggingConfig;
862
+ readonly mcp: ResolvedMcpConfig;
863
+ readonly permissions: ResolvedPermissionsConfig;
864
+ readonly compaction: ResolvedCompactionConfig;
865
+ readonly coordinator: ResolvedCoordinatorConfig;
866
+ readonly orchestrator: ResolvedOrchestratorConfig;
867
+ }
868
+
869
+ type DeepPartial<T> = T extends readonly unknown[] ? T : T extends object ? {
870
+ -readonly [K in keyof T]?: DeepPartial<T[K]> | undefined;
871
+ } : T;
872
+ type UserConfig = DeepPartial<ResolvedConfig>;
873
+
874
+ /**
875
+ * ModelAdapter — the interface every LLM provider must implement.
876
+ *
877
+ * Same pattern as StorageAdapter. The engine calls `streamMessage()`
878
+ * and gets back a normalized async generator of events. The adapter
879
+ * handles provider-specific HTTP translation behind the scenes.
880
+ *
881
+ * Two implementations ship:
882
+ * - AnthropicAdapter → @anthropic-ai/sdk (existing, default)
883
+ * - AISdkAdapter → Vercel AI SDK (75+ providers)
884
+ *
885
+ * NormalizedEvent and TokenUsage types come from api/streaming.ts —
886
+ * the canonical definitions. Adapters produce these; the agent loop
887
+ * consumes them. No duplicate type definitions.
888
+ */
889
+
890
+ interface ModelToolDefinition {
891
+ readonly name: string;
892
+ readonly description: string;
893
+ readonly inputSchema: Record<string, unknown>;
894
+ }
895
+ interface ModelMessage {
896
+ readonly role: 'user' | 'assistant';
897
+ readonly content: unknown;
898
+ }
899
+ interface StreamRequest {
900
+ readonly messages: readonly ModelMessage[];
901
+ readonly system?: string | undefined;
902
+ readonly tools?: readonly ModelToolDefinition[] | undefined;
903
+ readonly maxTokens?: number | undefined;
904
+ readonly temperature?: number | undefined;
905
+ }
906
+
907
+ interface ModelAdapter {
908
+ streamMessage(request: StreamRequest): AsyncGenerator<NormalizedEvent, void, void>;
909
+ }
910
+
911
+ /**
912
+ * MCP sampling — Plan 018 §2.
913
+ *
914
+ * MCP servers can issue `sampling/createMessage` requests asking the
915
+ * *client* to run an LLM completion on their behalf. The canonical use
916
+ * case is a tool that needs to summarize / classify / structure some
917
+ * text internally without carrying its own API key.
918
+ *
919
+ * This module provides:
920
+ *
921
+ * - `SamplingHandler` — the function callers can install via
922
+ * `EngineInternals.samplingHandler` to customize routing.
923
+ * - `defaultSamplingHandler(modelAdapter)` — a ready-to-use default
924
+ * that routes every request to the engine's own model adapter.
925
+ * Same model, same API key, same billing.
926
+ * - Request/response shapes mirroring MCP's schema closely enough to
927
+ * translate both directions without leaking SDK internals into the
928
+ * rest of the engine.
929
+ *
930
+ * Safety: calling sampling is OFF by default per MCP server
931
+ * (`allowSampling: false`). When a server attempts sampling and the
932
+ * flag is off, the McpClient responds with a protocol error instead
933
+ * of invoking the handler. The handler itself is only called after
934
+ * the gate passes.
935
+ */
936
+
937
+ /** Content block shape accepted from / sent to the MCP server. */
938
+ interface SamplingTextBlock {
939
+ readonly type: 'text';
940
+ readonly text: string;
941
+ }
942
+ interface SamplingMessage {
943
+ readonly role: 'user' | 'assistant';
944
+ readonly content: SamplingTextBlock | ReadonlyArray<SamplingTextBlock>;
945
+ }
946
+ interface SamplingModelPreferences {
947
+ readonly hints?: ReadonlyArray<{
948
+ readonly name?: string;
949
+ }>;
950
+ readonly costPriority?: number;
951
+ readonly speedPriority?: number;
952
+ readonly intelligencePriority?: number;
953
+ }
954
+ interface SamplingRequest {
955
+ readonly messages: ReadonlyArray<SamplingMessage>;
956
+ readonly systemPrompt?: string;
957
+ readonly maxTokens: number;
958
+ readonly temperature?: number;
959
+ readonly stopSequences?: ReadonlyArray<string>;
960
+ readonly modelPreferences?: SamplingModelPreferences;
961
+ readonly includeContext?: 'none' | 'thisServer' | 'allServers';
962
+ readonly metadata?: Readonly<Record<string, unknown>>;
963
+ }
964
+ interface SamplingResponse {
965
+ readonly role: 'assistant';
966
+ readonly model: string;
967
+ /** The assistant text. */
968
+ readonly content: SamplingTextBlock;
969
+ readonly stopReason?: 'endTurn' | 'stopSequence' | 'maxTokens' | 'error';
970
+ }
971
+ interface SamplingContext {
972
+ /** MCP server that issued the request. */
973
+ readonly serverName: string;
974
+ /** Engine runId for the run this handler was invoked inside. */
975
+ readonly runId?: string;
976
+ /** Engine nodeId for the run. */
977
+ readonly nodeId?: string;
978
+ /**
979
+ * Nesting depth — incremented when sampling itself triggers another
980
+ * sampling request. Guards against loops: handlers MAY refuse when
981
+ * `depth` exceeds a threshold (default implementation refuses at 3).
982
+ */
983
+ readonly depth: number;
984
+ }
985
+ type SamplingHandler = (request: SamplingRequest, context: SamplingContext) => Promise<SamplingResponse>;
986
+ /** Max depth the default handler will recurse through. */
987
+ declare const DEFAULT_SAMPLING_MAX_DEPTH = 3;
988
+ /**
989
+ * Build a default handler that routes every sampling request to the
990
+ * provided `ModelAdapter`. Consumes the stream, concatenates the text
991
+ * blocks, and maps the stop reason into the MCP vocabulary.
992
+ *
993
+ * If you want a different model per server, provide a custom
994
+ * handler via `EngineInternals.samplingHandler`.
995
+ */
996
+ declare function defaultSamplingHandler(modelAdapter: ModelAdapter): SamplingHandler;
997
+
998
+ /**
999
+ * SubagentRegistry — tracks parent→child relationships across a single
1000
+ * `engine.run()`. Mints unique `agentId`s, computes spawn depth, and
1001
+ * enforces `maxDepth` so a runaway loop can't recursively spawn forever.
1002
+ *
1003
+ * The registry's lifetime is one engine run. A fresh instance is built
1004
+ * inside `engine.run()` and shared with every Agent tool invocation
1005
+ * during that run, including children that themselves spawn grandchildren.
1006
+ *
1007
+ * Tracking is in-memory only. We don't persist parent→child relationships
1008
+ * to the transcript here — that's the runner's job, which writes
1009
+ * `subagent_spawn` and `subagent_done` entries to the parent's transcript.
1010
+ */
1011
+ interface SubagentRegistryOptions {
1012
+ /** Maximum spawn depth. Depth 1 = a direct child of the root run. */
1013
+ readonly maxDepth: number;
1014
+ }
1015
+ interface SpawnResult {
1016
+ readonly agentId: string;
1017
+ readonly depth: number;
1018
+ }
1019
+ /** Result from a background agent, stored on completion. */
1020
+ interface BackgroundAgentResult {
1021
+ readonly agentId: string;
1022
+ readonly output: string;
1023
+ readonly isError: boolean;
1024
+ readonly description?: string;
1025
+ }
1026
+ /** Serialized form for persistence alongside transcripts. */
1027
+ interface SerializedAgent {
1028
+ readonly agentId: string;
1029
+ readonly parentId: string | null;
1030
+ readonly depth: number;
1031
+ readonly name?: string;
1032
+ readonly status: 'running' | 'stopped' | 'completed';
1033
+ }
1034
+ declare class SubagentRegistry {
1035
+ private readonly maxDepth;
1036
+ private readonly agents;
1037
+ constructor(options: SubagentRegistryOptions);
1038
+ /**
1039
+ * Mint a new subagent under `parentAgentId` (null = direct child of
1040
+ * the root run). Throws if the resulting depth would exceed `maxDepth`.
1041
+ */
1042
+ spawn(parentAgentId: string | null): SpawnResult;
1043
+ getDepth(agentId: string): number;
1044
+ getParent(agentId: string): string | null;
1045
+ count(): number;
1046
+ /** Set a human-readable name for routing (e.g. subagent_type or task description). */
1047
+ setName(agentId: string, name: string): void;
1048
+ /** Find an agent by its human-readable name. Returns the first match. */
1049
+ findByName(name: string): string | undefined;
1050
+ setStatus(agentId: string, status: 'running' | 'stopped' | 'completed'): void;
1051
+ getStatus(agentId: string): 'running' | 'stopped' | 'completed';
1052
+ /** Queue a message for a running agent. */
1053
+ queueMessage(agentId: string, message: string): void;
1054
+ /** Drain all pending messages. Returns empty array if none. */
1055
+ drainMessages(agentId: string): string[];
1056
+ /** Mark an agent as background (fire-and-forget). */
1057
+ setBackground(agentId: string): void;
1058
+ /** Track a background agent's Promise for drain-before-pause. */
1059
+ setBackgroundPromise(agentId: string, promise: Promise<void>): void;
1060
+ /** Store a background agent's result on completion. */
1061
+ setBackgroundResult(agentId: string, result: BackgroundAgentResult): void;
1062
+ /**
1063
+ * Drain all completed background agent results. Returns them and
1064
+ * clears the stored results so they're only delivered once.
1065
+ * Called at the top of each turn in agentLoop.
1066
+ */
1067
+ drainCompletedBackgroundResults(): BackgroundAgentResult[];
1068
+ /**
1069
+ * Wait for all running background agents to complete (with timeout).
1070
+ * Called before pause to drain in-flight work. Returns results from
1071
+ * agents that finished within the timeout.
1072
+ */
1073
+ awaitBackgroundAgents(timeoutMs?: number): Promise<BackgroundAgentResult[]>;
1074
+ /** Serialize all agents for sidecar persistence. */
1075
+ toJSON(): SerializedAgent[];
1076
+ /** Rebuild registry from serialized data (e.g. on resume). */
1077
+ static fromJSON(data: SerializedAgent[], maxDepth: number): SubagentRegistry;
1078
+ private requireAgent;
1079
+ }
1080
+
1081
+ /**
1082
+ * Smart memory type definitions.
1083
+ *
1084
+ * The Engram is the fundamental unit of memory — named for Karl Lashley's
1085
+ * concept of the physical substrate of a memory trace. Each engram has a
1086
+ * kind (always / never / when / lesson / profile), a scope (global vs.
1087
+ * workspace), a confidence level, and a topic.
1088
+ */
1089
+ type EngramKind = 'always' | 'never' | 'when' | 'lesson' | 'profile';
1090
+ type EngramScope = 'global' | 'workspace';
1091
+ type EngramConfidence = 'high' | 'medium' | 'low';
1092
+ type EngramSource = 'user' | 'consolidation' | 'llm';
1093
+ interface Engram {
1094
+ readonly text: string;
1095
+ readonly kind: EngramKind;
1096
+ readonly scope: EngramScope;
1097
+ readonly confidence: EngramConfidence;
1098
+ readonly topic: string;
1099
+ readonly source: EngramSource;
1100
+ }
1101
+ /**
1102
+ * Episodic memory entry — a single turn snapshot. JSONL-encoded for
1103
+ * append-only logging via the storage adapter.
1104
+ */
1105
+ interface Episode {
1106
+ readonly ts: string;
1107
+ readonly session: string;
1108
+ readonly turn: number;
1109
+ readonly role: 'user' | 'assistant' | 'tool_call' | 'tool_result';
1110
+ readonly content: string;
1111
+ readonly meta: Readonly<Record<string, unknown>>;
1112
+ }
1113
+
1114
+ /**
1115
+ * EpisodicMemory — append-only JSONL log of every turn in a run.
1116
+ *
1117
+ * Ported from La-Machina with TS-strict cleanup. The episodic log
1118
+ * captures the raw timeline of conversation turns (user input,
1119
+ * assistant text, tool calls, tool results) for later inspection,
1120
+ * search, or training data extraction.
1121
+ *
1122
+ * Design rules:
1123
+ * - **Fire-and-forget**: `logTurn()` returns synchronously and never
1124
+ * throws. Storage errors are swallowed so logging cannot break the
1125
+ * agent loop.
1126
+ * - **Disabled = total no-op**: when `enabled` is false, no file is
1127
+ * created and `logTurn()` does nothing. Used to implement
1128
+ * `memory.mode === 'off'` at the engine config layer.
1129
+ * - **Per-session file**: each `startSession()` mints a fresh JSONL
1130
+ * under `{subdir}/{sessionId}.jsonl`. Format `YYYYMMDD_HHMMSS`.
1131
+ * - **Truncation**: tool_call and tool_result content is capped at
1132
+ * 2000 characters to keep individual entries small.
1133
+ */
1134
+
1135
+ declare class EpisodicMemory {
1136
+ private readonly storage;
1137
+ private readonly subdir;
1138
+ private _enabled;
1139
+ private _sessionId;
1140
+ private _filePath;
1141
+ /**
1142
+ * Serialization chain — every log() chains its appendFile onto the
1143
+ * previous one so on-disk order matches call order. Without this,
1144
+ * two concurrent unawaited appendFile calls can land in arbitrary
1145
+ * order even though each individual O_APPEND is atomic.
1146
+ */
1147
+ private writeChain;
1148
+ constructor(storage: StorageAdapter, subdir?: string, enabled?: boolean);
1149
+ get enabled(): boolean;
1150
+ set enabled(value: boolean);
1151
+ get currentSessionId(): string | null;
1152
+ /**
1153
+ * Start a new session — mints a timestamp-based id and creates an
1154
+ * empty JSONL file under `{subdir}/`. Returns the session id, or
1155
+ * empty string if disabled.
1156
+ */
1157
+ startSession(): Promise<string>;
1158
+ resumeSession(sessionId: string): Promise<string>;
1159
+ /**
1160
+ * Log a single episode entry. Fire-and-forget — never blocks, never
1161
+ * throws. Disabled mode and missing session both produce silent
1162
+ * no-ops so callers don't have to guard.
1163
+ */
1164
+ log(episode: Episode): void;
1165
+ /** Convenience wrapper that builds an Episode and logs it. */
1166
+ logTurn(turn: number, role: Episode['role'], content: string, meta?: Readonly<Record<string, unknown>>): void;
1167
+ }
1168
+
1169
+ /**
1170
+ * Transcript entry types and schemas.
1171
+ *
1172
+ * A transcript is a sequence of `Entry` records persisted as newline-
1173
+ * delimited JSON (NDJSON) sharded across numbered `.jsonl` files. This
1174
+ * module defines the entry type union, their Zod schemas, and the
1175
+ * serialize/parse helpers used by the writer and reader.
1176
+ *
1177
+ * The entry vocabulary is deliberately minimal — seven types. Everything
1178
+ * CLI-UX-specific in La-Machina's 19-type union has been dropped. Only
1179
+ * what a workflow engine needs to replay, audit, and learn from:
1180
+ *
1181
+ * user — user message (prompt, tool result from the client)
1182
+ * assistant — assistant message (text + tool_use blocks)
1183
+ * tool_result — result of a tool call dispatched by the engine
1184
+ * subagent_spawn — a child agent was created
1185
+ * subagent_done — a child agent returned its final output
1186
+ * meta — session-scoped metadata (title, tags, workflow ctx)
1187
+ * error — engine-level failure (tool crash, stream break, etc.)
1188
+ */
1189
+
1190
+ declare const EntrySchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
1191
+ message: z.ZodObject<{
1192
+ role: z.ZodEnum<["user", "assistant"]>;
1193
+ content: z.ZodArray<z.ZodUnknown, "many">;
1194
+ }, "strict", z.ZodTypeAny, {
1195
+ role: "user" | "assistant";
1196
+ content: unknown[];
1197
+ }, {
1198
+ role: "user" | "assistant";
1199
+ content: unknown[];
1200
+ }>;
1201
+ uuid: z.ZodString;
1202
+ parentUuid: z.ZodNullable<z.ZodString>;
1203
+ ts: z.ZodString;
1204
+ type: z.ZodLiteral<"user">;
1205
+ }, "strict", z.ZodTypeAny, {
1206
+ type: "user";
1207
+ message: {
1208
+ role: "user" | "assistant";
1209
+ content: unknown[];
1210
+ };
1211
+ uuid: string;
1212
+ parentUuid: string | null;
1213
+ ts: string;
1214
+ }, {
1215
+ type: "user";
1216
+ message: {
1217
+ role: "user" | "assistant";
1218
+ content: unknown[];
1219
+ };
1220
+ uuid: string;
1221
+ parentUuid: string | null;
1222
+ ts: string;
1223
+ }>, z.ZodObject<{
1224
+ message: z.ZodObject<{
1225
+ role: z.ZodEnum<["user", "assistant"]>;
1226
+ content: z.ZodArray<z.ZodUnknown, "many">;
1227
+ }, "strict", z.ZodTypeAny, {
1228
+ role: "user" | "assistant";
1229
+ content: unknown[];
1230
+ }, {
1231
+ role: "user" | "assistant";
1232
+ content: unknown[];
1233
+ }>;
1234
+ uuid: z.ZodString;
1235
+ parentUuid: z.ZodNullable<z.ZodString>;
1236
+ ts: z.ZodString;
1237
+ type: z.ZodLiteral<"assistant">;
1238
+ }, "strict", z.ZodTypeAny, {
1239
+ type: "assistant";
1240
+ message: {
1241
+ role: "user" | "assistant";
1242
+ content: unknown[];
1243
+ };
1244
+ uuid: string;
1245
+ parentUuid: string | null;
1246
+ ts: string;
1247
+ }, {
1248
+ type: "assistant";
1249
+ message: {
1250
+ role: "user" | "assistant";
1251
+ content: unknown[];
1252
+ };
1253
+ uuid: string;
1254
+ parentUuid: string | null;
1255
+ ts: string;
1256
+ }>, z.ZodObject<{
1257
+ toolUseId: z.ZodString;
1258
+ content: z.ZodUnknown;
1259
+ isError: z.ZodOptional<z.ZodBoolean>;
1260
+ uuid: z.ZodString;
1261
+ parentUuid: z.ZodNullable<z.ZodString>;
1262
+ ts: z.ZodString;
1263
+ type: z.ZodLiteral<"tool_result">;
1264
+ }, "strict", z.ZodTypeAny, {
1265
+ type: "tool_result";
1266
+ uuid: string;
1267
+ toolUseId: string;
1268
+ parentUuid: string | null;
1269
+ ts: string;
1270
+ content?: unknown;
1271
+ isError?: boolean | undefined;
1272
+ }, {
1273
+ type: "tool_result";
1274
+ uuid: string;
1275
+ toolUseId: string;
1276
+ parentUuid: string | null;
1277
+ ts: string;
1278
+ content?: unknown;
1279
+ isError?: boolean | undefined;
1280
+ }>, z.ZodObject<{
1281
+ agentId: z.ZodString;
1282
+ agentType: z.ZodString;
1283
+ uuid: z.ZodString;
1284
+ parentUuid: z.ZodNullable<z.ZodString>;
1285
+ ts: z.ZodString;
1286
+ type: z.ZodLiteral<"subagent_spawn">;
1287
+ }, "strict", z.ZodTypeAny, {
1288
+ type: "subagent_spawn";
1289
+ uuid: string;
1290
+ agentId: string;
1291
+ parentUuid: string | null;
1292
+ ts: string;
1293
+ agentType: string;
1294
+ }, {
1295
+ type: "subagent_spawn";
1296
+ uuid: string;
1297
+ agentId: string;
1298
+ parentUuid: string | null;
1299
+ ts: string;
1300
+ agentType: string;
1301
+ }>, z.ZodObject<{
1302
+ agentId: z.ZodString;
1303
+ output: z.ZodString;
1304
+ uuid: z.ZodString;
1305
+ parentUuid: z.ZodNullable<z.ZodString>;
1306
+ ts: z.ZodString;
1307
+ type: z.ZodLiteral<"subagent_done">;
1308
+ }, "strict", z.ZodTypeAny, {
1309
+ type: "subagent_done";
1310
+ uuid: string;
1311
+ output: string;
1312
+ agentId: string;
1313
+ parentUuid: string | null;
1314
+ ts: string;
1315
+ }, {
1316
+ type: "subagent_done";
1317
+ uuid: string;
1318
+ output: string;
1319
+ agentId: string;
1320
+ parentUuid: string | null;
1321
+ ts: string;
1322
+ }>, z.ZodObject<{
1323
+ key: z.ZodString;
1324
+ value: z.ZodUnknown;
1325
+ uuid: z.ZodString;
1326
+ ts: z.ZodString;
1327
+ type: z.ZodLiteral<"meta">;
1328
+ }, "strict", z.ZodTypeAny, {
1329
+ type: "meta";
1330
+ uuid: string;
1331
+ ts: string;
1332
+ key: string;
1333
+ value?: unknown;
1334
+ }, {
1335
+ type: "meta";
1336
+ uuid: string;
1337
+ ts: string;
1338
+ key: string;
1339
+ value?: unknown;
1340
+ }>, z.ZodObject<{
1341
+ error: z.ZodObject<{
1342
+ code: z.ZodString;
1343
+ message: z.ZodString;
1344
+ stack: z.ZodOptional<z.ZodString>;
1345
+ }, "strict", z.ZodTypeAny, {
1346
+ code: string;
1347
+ message: string;
1348
+ stack?: string | undefined;
1349
+ }, {
1350
+ code: string;
1351
+ message: string;
1352
+ stack?: string | undefined;
1353
+ }>;
1354
+ uuid: z.ZodString;
1355
+ ts: z.ZodString;
1356
+ type: z.ZodLiteral<"error">;
1357
+ }, "strict", z.ZodTypeAny, {
1358
+ type: "error";
1359
+ error: {
1360
+ code: string;
1361
+ message: string;
1362
+ stack?: string | undefined;
1363
+ };
1364
+ uuid: string;
1365
+ ts: string;
1366
+ }, {
1367
+ type: "error";
1368
+ error: {
1369
+ code: string;
1370
+ message: string;
1371
+ stack?: string | undefined;
1372
+ };
1373
+ uuid: string;
1374
+ ts: string;
1375
+ }>]>;
1376
+ type Entry = z.infer<typeof EntrySchema>;
1377
+
1378
+ /**
1379
+ * TranscriptMeta — the small sidecar at `{logPath}/meta.json` that the
1380
+ * writer updates on every flush.
1381
+ *
1382
+ * The meta document serves two purposes:
1383
+ * 1. **Fast session listing**: upstream callers (Nikaido, dashboard)
1384
+ * can stat this file to know turn count, status, and last update
1385
+ * without reading the transcript shards.
1386
+ * 2. **Resume anchor**: on pause/resume, the snapshot points to the
1387
+ * last shard index recorded here, letting the reader skip straight
1388
+ * to where it left off.
1389
+ *
1390
+ * The document is tiny (<1KB) and written frequently, so the cost of
1391
+ * overwriting on every flush is negligible. Writes go through the
1392
+ * storage adapter's atomic `writeFile`.
1393
+ */
1394
+
1395
+ type TranscriptStatus = 'pending' | 'running' | 'paused' | 'done' | 'failed';
1396
+ declare const TranscriptMetaSchema: z.ZodObject<{
1397
+ version: z.ZodLiteral<1>;
1398
+ status: z.ZodEnum<["pending", "running", "paused", "done", "failed"]>;
1399
+ startedAt: z.ZodString;
1400
+ updatedAt: z.ZodString;
1401
+ turnCount: z.ZodNumber;
1402
+ messageCount: z.ZodNumber;
1403
+ lastShardIndex: z.ZodNullable<z.ZodNumber>;
1404
+ shardCount: z.ZodNumber;
1405
+ }, "strict", z.ZodTypeAny, {
1406
+ status: "paused" | "done" | "failed" | "running" | "pending";
1407
+ version: 1;
1408
+ messageCount: number;
1409
+ lastShardIndex: number | null;
1410
+ startedAt: string;
1411
+ updatedAt: string;
1412
+ turnCount: number;
1413
+ shardCount: number;
1414
+ }, {
1415
+ status: "paused" | "done" | "failed" | "running" | "pending";
1416
+ version: 1;
1417
+ messageCount: number;
1418
+ lastShardIndex: number | null;
1419
+ startedAt: string;
1420
+ updatedAt: string;
1421
+ turnCount: number;
1422
+ shardCount: number;
1423
+ }>;
1424
+ type TranscriptMeta = z.infer<typeof TranscriptMetaSchema>;
1425
+
1426
+ /**
1427
+ * Permission evaluator — the decision pipeline.
1428
+ *
1429
+ * Pipeline (evaluated in order, first match wins):
1430
+ *
1431
+ * 1. Mode check:
1432
+ * - `'open'` → allow immediately (no further evaluation)
1433
+ * - `'locked'` → check allowlist only (skip rules entirely)
1434
+ * - `'rules'` → proceed to step 2
1435
+ *
1436
+ * 2. Safe-tool allowlist fast-path:
1437
+ * - If the tool is on the safe allowlist → allow (skip rules)
1438
+ *
1439
+ * 3. Rule evaluation (top-to-bottom):
1440
+ * - First matching rule wins
1441
+ * - `allow:X` → allow
1442
+ * - `deny:X` → deny with reason
1443
+ *
1444
+ * 4. Fall-closed:
1445
+ * - If no rule matched → deny ("no matching rule, mode is rules")
1446
+ *
1447
+ * The evaluator is a pure function: no side effects, no LLM calls.
1448
+ * The gate hook (`hooks.gateBeforeTool`) runs AFTER this evaluation in
1449
+ * the engine's tool dispatch — it can override a permission allow with
1450
+ * a pause, but it cannot override a permission deny.
1451
+ */
1452
+
1453
+ /**
1454
+ * Compiled permission policy — call `check(toolName)` to get a decision.
1455
+ * Built once at engine construction time from the resolved config.
1456
+ */
1457
+ interface PermissionPolicy {
1458
+ check(toolName: string): PermissionDecision;
1459
+ }
1460
+ /**
1461
+ * Build a `PermissionPolicy` from a resolved config. The returned
1462
+ * object is reusable across all runs on the same engine instance.
1463
+ */
1464
+ declare function buildPermissionPolicy(config: ResolvedPermissionsConfig): PermissionPolicy;
1465
+
1466
+ /**
1467
+ * ToolExecutor — runs tools by name with input validation, timeouts,
1468
+ * cancellation, error isolation, and lifecycle hooks.
1469
+ *
1470
+ * Responsibilities:
1471
+ * 1. Resolve a tool by name from a `ToolRegistry`. Unknown name →
1472
+ * typed error result, never an exception.
1473
+ * 2. Validate the caller-supplied input against the tool's Zod schema.
1474
+ * Invalid input → typed error result.
1475
+ * 3. Build a `ToolContext` with an `AbortSignal` that fires on either
1476
+ * timeout or external cancellation.
1477
+ * 4. Race the tool's `execute()` against `timeoutMs` (when set). On
1478
+ * timeout, return an error result and abort the context signal.
1479
+ * 5. Catch any synchronous or asynchronous throw from the tool and
1480
+ * wrap it as `{ content, isError: true }`. **A misbehaving tool
1481
+ * must never crash the agent loop.**
1482
+ * 6. Fire `preToolCall` and `postToolCall` hooks. `postToolCall` runs
1483
+ * in a finally semantic — even on error, even on timeout. Hook
1484
+ * failures are isolated; they never propagate.
1485
+ *
1486
+ * The executor is stateless except for the registry reference; a
1487
+ * single instance per engine run is fine.
1488
+ */
1489
+
1490
+ interface PreToolCallEvent {
1491
+ readonly toolName: string;
1492
+ readonly input: unknown;
1493
+ }
1494
+ interface PostToolCallEvent {
1495
+ readonly toolName: string;
1496
+ readonly input: unknown;
1497
+ readonly result: ToolResult;
1498
+ readonly durationMs: number;
1499
+ }
1500
+ interface ToolExecutorHooks {
1501
+ readonly preToolCall?: (event: PreToolCallEvent) => void | Promise<void>;
1502
+ readonly postToolCall?: (event: PostToolCallEvent) => void | Promise<void>;
1503
+ }
1504
+ interface ToolExecutorOptions {
1505
+ readonly registry: ToolRegistry;
1506
+ /** Per-call timeout in milliseconds. Omit or 0 to disable. */
1507
+ readonly timeoutMs?: number;
1508
+ readonly hooks?: ToolExecutorHooks;
1509
+ /** Permission policy. When set, every tool dispatch is checked before
1510
+ * execution. Denied tools get an isError result without running. */
1511
+ readonly permissions?: PermissionPolicy;
1512
+ }
1513
+ interface ExecuteOptions {
1514
+ /** Optional external AbortSignal that can cancel the tool early. */
1515
+ readonly signal?: AbortSignal;
1516
+ }
1517
+ declare class ToolExecutor {
1518
+ private readonly registry;
1519
+ private readonly timeoutMs;
1520
+ private readonly hooks;
1521
+ private readonly permissions;
1522
+ constructor(options: ToolExecutorOptions);
1523
+ execute(toolName: string, rawInput: unknown, options?: ExecuteOptions): Promise<ToolResult>;
1524
+ private executeInner;
1525
+ private runWithTimeout;
1526
+ private firePreHook;
1527
+ private firePostHook;
1528
+ }
1529
+
1530
+ /**
1531
+ * SkillSource — abstraction over skill discovery and lazy body loading.
1532
+ *
1533
+ * Two implementations live alongside this file:
1534
+ * - `StorageSkillSource` (storageSkillSource.ts) — reads `SKILL.md`
1535
+ * files from a storage adapter (the current default behavior).
1536
+ * - `InlineSkillSource` (inlineSkillSource.ts) — takes an explicit
1537
+ * list of overrides passed per-run via `RunOptions.skills`.
1538
+ *
1539
+ * Downstream code (prompt builder, SkillPage tool) accepts `SkillSource`
1540
+ * rather than reaching into storage directly, so the two paths share
1541
+ * every contract: model sees the same catalogue, `SkillPage` returns
1542
+ * bodies the same way, and errors surface the same error shape.
1543
+ */
1544
+ /**
1545
+ * Minimal descriptor the system prompt needs to list a skill. The full
1546
+ * body stays unloaded until `getSkillFile` / `getPage` is invoked.
1547
+ */
1548
+ interface ResolvedSkill {
1549
+ /** Unique identifier — `[a-zA-Z0-9_-]+`. */
1550
+ readonly name: string;
1551
+ /** One-line human description. Rendered in the system prompt catalogue. */
1552
+ readonly description: string;
1553
+ /** True when supplemental `pages/*.md` are available. */
1554
+ readonly hasPages: boolean;
1555
+ }
1556
+ /**
1557
+ * Pluggable skill source used by the engine at run time.
1558
+ *
1559
+ * - `list()` runs once at run start. Cheap — names + descriptions only.
1560
+ * - `getSkillFile()` / `getPage()` are invoked by the `SkillPage` tool
1561
+ * when the model decides a skill is relevant. They MAY fetch from the
1562
+ * network; they SHOULD cache within a run.
1563
+ *
1564
+ * Implementations MUST return `null` for unknown skills / pages (never
1565
+ * throw for "not found"). They MAY throw for infrastructure failures
1566
+ * (fetch errors, disallowed hosts) — the `SkillPage` tool converts
1567
+ * those to tool errors.
1568
+ */
1569
+ interface SkillSource {
1570
+ /** Enumerate available skills. Safe to call multiple times per run. */
1571
+ list(): Promise<ReadonlyArray<ResolvedSkill>>;
1572
+ /** Load the main `SKILL.md` body for a skill. `null` if unknown. */
1573
+ getSkillFile(skill: string): Promise<string | null>;
1574
+ /** Load a named page for a skill. `null` if skill or page is unknown. */
1575
+ getPage(skill: string, page: string): Promise<string | null>;
1576
+ /**
1577
+ * Return the list of page names available for a skill. Used by the
1578
+ * `SkillPage` tool to render a "did you mean?" suggestion when a page
1579
+ * name misses. Returns `[]` for unknown skills or skills with no pages.
1580
+ */
1581
+ listPages(skill: string): Promise<ReadonlyArray<string>>;
1582
+ }
1583
+ /**
1584
+ * Shape the caller passes to `RunOptions.skills` / `ResumeOptions.skills`
1585
+ * to override disk-based discovery for exactly this run.
1586
+ *
1587
+ * Rules:
1588
+ * - `name` must match `/^[a-zA-Z0-9_-]+$/` — rejected at construction.
1589
+ * - `description` is required (needed for the prompt catalogue without fetch).
1590
+ * - Either `body` OR `url` for the main file. `body` wins when both set.
1591
+ * Omitting both is valid — the skill is listed but reading it errors.
1592
+ * - `pages` follow the same `body`/`url` rules per-page.
1593
+ * - `headers` travel with both the main fetch and every page fetch.
1594
+ */
1595
+ interface SkillOverride {
1596
+ readonly name: string;
1597
+ readonly description: string;
1598
+ readonly body?: string;
1599
+ readonly url?: string;
1600
+ readonly pages?: Readonly<Record<string, SkillPageOverride>>;
1601
+ readonly headers?: Readonly<Record<string, string>>;
1602
+ }
1603
+ interface SkillPageOverride {
1604
+ readonly body?: string;
1605
+ readonly url?: string;
1606
+ }
1607
+
1608
+ /**
1609
+ * Engine-facing types for `run()` and `resume()`.
1610
+ *
1611
+ * Phase 1 defines the interface shapes. Actual behavior (streaming, tool
1612
+ * calls, pause/resume logic) arrives in Phases 7–10. These types are
1613
+ * exported from the public API so callers can compile against them.
1614
+ */
1615
+ interface RunOptions {
1616
+ /** Unique run identifier. Optional — auto-generated as `run_<uuid>` if omitted. */
1617
+ readonly runId?: string;
1618
+ readonly nodeId: string;
1619
+ readonly task: string;
1620
+ readonly tools?: readonly string[] | undefined;
1621
+ readonly context?: Readonly<Record<string, unknown>> | undefined;
1622
+ /** Override maxTurns for this run only. Used by the orchestrator for per-phase budgets. */
1623
+ readonly maxTurns?: number | undefined;
1624
+ /**
1625
+ * Token budget for this run. When total tokens used (input + output)
1626
+ * exceeds this value, the run stops gracefully with the last assistant
1627
+ * output. Ported from La-Machina's task_budget enforcement.
1628
+ */
1629
+ readonly tokenBudget?: number | undefined;
1630
+ /**
1631
+ * Output format. Default: 'text'.
1632
+ * - 'text': model responds freely, result.output is raw string
1633
+ * - 'json': engine injects schema instructions into system prompt,
1634
+ * parses the model's final response as JSON, validates against
1635
+ * outputSchema if provided, retries once on failure.
1636
+ * result.data contains the parsed+validated value.
1637
+ */
1638
+ readonly outputFormat?: 'text' | 'json' | undefined;
1639
+ /**
1640
+ * Zod schema for structured output. Only used when outputFormat is 'json'.
1641
+ * Injected into system prompt as JSON Schema description, used to
1642
+ * validate the model's response. If validation fails, retries once.
1643
+ */
1644
+ readonly outputSchema?: zod.ZodTypeAny | undefined;
1645
+ /**
1646
+ * Per-run skill override (Plan 017). When present, the engine IGNORES
1647
+ * `config.skills.autoload` + `config.skills.path` and uses this list
1648
+ * instead. Each override carries a description (always required) plus
1649
+ * either an inline `body` or an HTTPS `url` that is fetched on demand
1650
+ * by the `SkillPage` tool.
1651
+ */
1652
+ readonly skills?: ReadonlyArray<SkillOverride> | undefined;
1653
+ }
1654
+ interface TokenUsage {
1655
+ readonly input: number;
1656
+ readonly output: number;
1657
+ readonly cacheCreationInput?: number | undefined;
1658
+ readonly cacheReadInput?: number | undefined;
1659
+ }
1660
+ /**
1661
+ * Why a run paused.
1662
+ *
1663
+ * v0.1 only ever produces `'gate_required'` — that's the one path the agent
1664
+ * loop uses to pause (`gateBeforeTool` callback returning `{ allow: false }`).
1665
+ *
1666
+ * The other three values are reserved for v0.2:
1667
+ * - `'max_turns'` — currently returns `{ status: 'failed', error: ERR_MAX_TURNS }`
1668
+ * instead. See `agentLoop.ts` + tests at
1669
+ * `test/unit/engine/agentLoop.test.ts` and
1670
+ * `test/integration/engine/run-with-mockapi.test.ts`.
1671
+ * - `'timeout'` — `execution.runTimeoutMs` is declared but not enforced.
1672
+ * - `'explicit'` — no explicit-pause API exists yet.
1673
+ *
1674
+ * Kept in the union so the v0.2 change is backwards-compatible (no type
1675
+ * narrowing in callers that already switch on the full set).
1676
+ */
1677
+ type PauseReason = 'gate_required' | 'subagent_gate_required' | 'max_turns' | 'explicit' | 'timeout';
1678
+ interface RunSnapshot {
1679
+ readonly version: 1;
1680
+ readonly status: 'paused';
1681
+ readonly runId: string;
1682
+ readonly nodeId: string;
1683
+ readonly pausedAt: string;
1684
+ readonly pauseReason: PauseReason;
1685
+ readonly messageCount: number;
1686
+ readonly lastShardIndex: number;
1687
+ readonly lastMessageUuid: string;
1688
+ readonly pendingToolCall?: {
1689
+ toolName: string;
1690
+ toolUseId: string;
1691
+ input: unknown;
1692
+ calledAt: string;
1693
+ } | undefined;
1694
+ /**
1695
+ * Nested-pause pointer set when a subagent's own gate fired. The parent
1696
+ * is paused on its Agent tool call; this field records the child's
1697
+ * snapshot so observers can inspect which inner tool was blocked.
1698
+ * Populated only when `hooks.propagateGateToSubagents: true`.
1699
+ */
1700
+ readonly pendingSubagent?: {
1701
+ subagentType: string;
1702
+ parentToolUseId: string;
1703
+ childSnapshot: RunSnapshot;
1704
+ } | undefined;
1705
+ readonly tokensUsedSoFar: TokenUsage;
1706
+ readonly turnsUsed: number;
1707
+ }
1708
+ interface TranscriptLocation {
1709
+ readonly path: string;
1710
+ readonly lastShardIndex: number;
1711
+ }
1712
+ type RunResult = {
1713
+ readonly status: 'done';
1714
+ readonly output: string;
1715
+ /** Parsed + validated data when outputFormat is 'json'. Undefined for 'text' or if parsing failed. */
1716
+ readonly data?: unknown;
1717
+ readonly tokensUsed: TokenUsage;
1718
+ readonly turns: number;
1719
+ } | {
1720
+ readonly status: 'paused';
1721
+ readonly snapshot: RunSnapshot;
1722
+ readonly reason: PauseReason;
1723
+ } | {
1724
+ readonly status: 'failed';
1725
+ readonly error: Error;
1726
+ readonly transcript: TranscriptLocation;
1727
+ };
1728
+ interface ResumeOptions {
1729
+ /** The runId of the paused run. Engine loads snapshot from storage. */
1730
+ readonly runId: string;
1731
+ /** Optional nodeId to disambiguate if multiple nodes have paused under this runId. */
1732
+ readonly nodeId?: string;
1733
+ readonly gateAnswer?: unknown;
1734
+ readonly outputFormat?: 'text' | 'json' | undefined;
1735
+ readonly outputSchema?: zod.ZodTypeAny | undefined;
1736
+ /**
1737
+ * Optional: pass snapshot directly instead of loading from storage.
1738
+ * Used internally and for advanced cases. Most clients should just pass runId.
1739
+ */
1740
+ readonly snapshot?: RunSnapshot;
1741
+ /**
1742
+ * Per-resume skill override (Plan 017). Mirrors `RunOptions.skills` —
1743
+ * pass the same list you used on start() to keep tool availability
1744
+ * consistent. Overrides are not persisted in the snapshot.
1745
+ */
1746
+ readonly skills?: ReadonlyArray<SkillOverride> | undefined;
1747
+ }
1748
+
1749
+ /**
1750
+ * Result of `gateBeforeTool` callback. `allow: true` runs the tool;
1751
+ * `allow: false` pauses the run with the given reason. The pending
1752
+ * tool call is captured in the snapshot.
1753
+ */
1754
+ interface GateDecision {
1755
+ readonly allow: boolean;
1756
+ readonly reason?: string;
1757
+ }
1758
+ type GateBeforeTool = (toolName: string, input: unknown) => GateDecision | Promise<GateDecision>;
1759
+ /**
1760
+ * Progress payload fired by `onProgress` at turn boundaries. Maps
1761
+ * directly to `RunStateManager.RunProgress` on the engine side.
1762
+ */
1763
+ interface LoopProgress {
1764
+ /** Current turn index (1-based). */
1765
+ readonly turns: number;
1766
+ /** Cumulative token usage across all turns so far. */
1767
+ readonly tokensUsed: TokenUsage$1;
1768
+ /** What the loop is doing right now. */
1769
+ readonly currentActivity: 'idle' | 'streaming' | 'tool_dispatch' | 'compacting';
1770
+ /** Most recent tool name (when `currentActivity === 'tool_dispatch'`). */
1771
+ readonly lastTool?: string;
1772
+ }
1773
+ interface ToolBatch {
1774
+ concurrent: boolean;
1775
+ calls: Array<{
1776
+ id: string;
1777
+ name: string;
1778
+ input: unknown;
1779
+ }>;
1780
+ }
1781
+ /**
1782
+ * Partition tool calls into batches: consecutive concurrency-safe tools
1783
+ * are grouped together for parallel execution; each unsafe tool becomes
1784
+ * its own serial batch. Mirrors La-Machina's toolOrchestration.ts pattern.
1785
+ */
1786
+ declare function partitionToolCalls(calls: ReadonlyArray<{
1787
+ id: string;
1788
+ name: string;
1789
+ input: unknown;
1790
+ }>, registry?: ToolRegistry): ToolBatch[];
1791
+
1792
+ /**
1793
+ * EngineResponse — unified response shape for the public API.
1794
+ *
1795
+ * Every engine.run() and engine.resume() returns this single flat shape.
1796
+ * Internal code uses RunResult (the discriminated union); this module
1797
+ * converts it to the client-facing format.
1798
+ *
1799
+ * {
1800
+ * status: 'done' | 'paused' | 'failed',
1801
+ * data: any, // user-defined — text or JSON from outputSchema
1802
+ * meta: { ... }, // tokens, turns, timing, snapshot, transcript
1803
+ * errors: [], // structured error array
1804
+ * timestamp: number, // unix ms
1805
+ * }
1806
+ */
1807
+
1808
+ interface ResponseError {
1809
+ readonly code: string;
1810
+ readonly message: string;
1811
+ readonly details?: unknown;
1812
+ }
1813
+ interface ResponseMeta {
1814
+ readonly nodeId: string;
1815
+ readonly turns?: number;
1816
+ readonly tokensUsed?: TokenUsage;
1817
+ readonly durationMs?: number;
1818
+ readonly output?: string;
1819
+ readonly snapshot?: RunSnapshot;
1820
+ readonly pendingToolCall?: RunSnapshot['pendingToolCall'];
1821
+ readonly pauseReason?: PauseReason;
1822
+ readonly transcript?: TranscriptLocation;
1823
+ /** Additional context-specific fields (e.g., activity, lastTool, cancelled). */
1824
+ readonly [key: string]: unknown;
1825
+ }
1826
+ interface EngineResponse {
1827
+ /** Unique run identifier — client's primary handle. Use this for resume. */
1828
+ readonly runId: string;
1829
+ readonly status: 'done' | 'paused' | 'failed' | 'queued' | 'running' | 'cancelled' | 'not_found';
1830
+ readonly data: unknown;
1831
+ readonly meta: ResponseMeta;
1832
+ readonly errors: readonly ResponseError[];
1833
+ readonly timestamp: number;
1834
+ }
1835
+ /**
1836
+ * Convert internal RunResult to the unified EngineResponse shape.
1837
+ */
1838
+ declare function toResponse(result: RunResult, extra: {
1839
+ runId: string;
1840
+ nodeId: string;
1841
+ durationMs: number;
1842
+ logPath?: string;
1843
+ }): EngineResponse;
1844
+
1845
+ /**
1846
+ * RunState — durable per-run state stored in state.json.
1847
+ *
1848
+ * Lives alongside transcript shards and snapshot.json:
1849
+ * projects/{runId}/nodes/{nodeId}/state.json
1850
+ *
1851
+ * Enables:
1852
+ * - Async run tracking (getStatus, waitFor)
1853
+ * - Crash recovery (detect orphaned running runs via stale heartbeat)
1854
+ * - Webhook delivery history (retry tracking)
1855
+ * - Full response persistence (data + meta + errors) — getStatus returns
1856
+ * the same shape as sync engine.run()
1857
+ *
1858
+ * All pure JS — no `node:` imports, Workers-compatible.
1859
+ */
1860
+
1861
+ type RunStatus = 'queued' | 'running' | 'paused' | 'done' | 'failed' | 'cancelled';
1862
+ interface RunProgress {
1863
+ readonly turns: number;
1864
+ readonly tokensUsed: TokenUsage;
1865
+ readonly currentActivity: 'idle' | 'streaming' | 'tool_dispatch' | 'compacting';
1866
+ readonly lastTool?: string;
1867
+ }
1868
+ type WebhookEvent = 'paused' | 'done' | 'failed';
1869
+ interface WebhookDelivery {
1870
+ readonly id: string;
1871
+ readonly event: WebhookEvent;
1872
+ readonly attempt: number;
1873
+ readonly scheduledAt: number;
1874
+ readonly deliveredAt?: number;
1875
+ readonly status: 'pending' | 'delivered' | 'failed';
1876
+ readonly httpCode?: number;
1877
+ readonly error?: string;
1878
+ }
1879
+ interface WebhookConfig {
1880
+ readonly url: string;
1881
+ readonly events: readonly WebhookEvent[];
1882
+ readonly secret?: string;
1883
+ readonly headers?: Readonly<Record<string, string>>;
1884
+ readonly deliveries: readonly WebhookDelivery[];
1885
+ }
1886
+ interface RunState {
1887
+ readonly version: 1;
1888
+ readonly runId: string;
1889
+ readonly nodeId: string;
1890
+ readonly status: RunStatus;
1891
+ readonly startedAt: number;
1892
+ readonly lastHeartbeat: number;
1893
+ readonly progress: RunProgress;
1894
+ readonly response: EngineResponse | null;
1895
+ readonly webhook?: WebhookConfig;
1896
+ }
1897
+ /**
1898
+ * Reads, writes, and updates state.json files via the storage adapter.
1899
+ * Concurrent writes are NOT synchronized — callers are responsible for
1900
+ * serializing updates (in practice: only one process writes per run).
1901
+ */
1902
+ declare class RunStateManager {
1903
+ private readonly storage;
1904
+ constructor(storage: StorageAdapter);
1905
+ private path;
1906
+ write(state: RunState): Promise<void>;
1907
+ read(runId: string, nodeId: string): Promise<RunState | null>;
1908
+ /**
1909
+ * Merge patch into current state and write back. Returns the new state.
1910
+ * If no state exists, throws — use `write()` for initial creation.
1911
+ */
1912
+ update(runId: string, nodeId: string, patch: Partial<RunState>): Promise<RunState>;
1913
+ /**
1914
+ * Update just the heartbeat + progress (cheap, called every turn).
1915
+ */
1916
+ heartbeat(runId: string, nodeId: string, progress: Partial<RunProgress>): Promise<void>;
1917
+ /**
1918
+ * Mark terminal state with response. Used at end of run/resume.
1919
+ */
1920
+ finalize(runId: string, nodeId: string, response: EngineResponse): Promise<RunState>;
1921
+ /**
1922
+ * List all state files under a runId (one per node). Returns empty array
1923
+ * if the runId directory doesn't exist.
1924
+ */
1925
+ scanRun(runId: string): Promise<RunState[]>;
1926
+ /**
1927
+ * Scan all state files and return those with stale heartbeats.
1928
+ * Used by recoverOrphanedRuns().
1929
+ */
1930
+ findOrphaned(staleThresholdMs: number): Promise<RunState[]>;
1931
+ /** Helper to build a fresh state for a new run. */
1932
+ static initial(runId: string, nodeId: string, webhook?: WebhookConfig): RunState;
1933
+ }
1934
+
1935
+ /**
1936
+ * BackgroundExecutor — abstracts how async runs are dispatched.
1937
+ *
1938
+ * Node.js: NodeBackgroundExecutor uses `void Promise` fire-and-forget.
1939
+ * Cloudflare Workers: user provides a DurableObjectExecutor (Worker-side
1940
+ * code that dispatches to a DO with a longer execution budget).
1941
+ *
1942
+ * All pure JS — no `node:` imports, Workers-compatible.
1943
+ */
1944
+ /**
1945
+ * Contract for async run dispatch. Engine calls `schedule()` with a work
1946
+ * function; executor is responsible for running it eventually.
1947
+ */
1948
+ interface BackgroundExecutor {
1949
+ /**
1950
+ * Schedule a work function to run in the background. Must return
1951
+ * immediately — the function will be invoked asynchronously.
1952
+ *
1953
+ * The returned AbortSignal fires if `cancel(runId)` is called.
1954
+ * Work functions should pass this to agentLoop for early termination.
1955
+ */
1956
+ schedule(runId: string, work: (signal: AbortSignal) => Promise<void>): void;
1957
+ /**
1958
+ * Cancel a scheduled/running execution. Fires the AbortSignal so the
1959
+ * work function can exit gracefully.
1960
+ */
1961
+ cancel(runId: string): Promise<void>;
1962
+ /** True if this executor is currently tracking a run. */
1963
+ isActive(runId: string): boolean;
1964
+ }
1965
+ /**
1966
+ * Default Node.js implementation — fire-and-forget via `void` Promise.
1967
+ * Works in Node, Bun, Deno. Does NOT work on Cloudflare Workers because
1968
+ * the Worker instance terminates when the request handler returns.
1969
+ */
1970
+ declare class NodeBackgroundExecutor implements BackgroundExecutor {
1971
+ private readonly running;
1972
+ schedule(runId: string, work: (signal: AbortSignal) => Promise<void>): void;
1973
+ cancel(runId: string): Promise<void>;
1974
+ isActive(runId: string): boolean;
1975
+ /** For tests/observability — current count of running tasks. */
1976
+ size(): number;
1977
+ }
1978
+
1979
+ /** Config for webhook delivery when using async APIs. */
1980
+ interface WebhookOptions {
1981
+ readonly url: string;
1982
+ readonly secret?: string;
1983
+ readonly events?: readonly WebhookEvent[];
1984
+ readonly headers?: Record<string, string>;
1985
+ }
1986
+ /** Options for engine.start() — extends RunOptions with webhook. */
1987
+ interface StartOptions extends RunOptions {
1988
+ readonly webhook?: WebhookOptions;
1989
+ }
1990
+ /** Options for engine.resumeAsync() — extends ResumeOptions with webhook. */
1991
+ interface ResumeAsyncOptions extends ResumeOptions {
1992
+ readonly webhook?: WebhookOptions;
1993
+ }
1994
+ /** Options for engine.waitFor() — blocks until terminal state. */
1995
+ interface WaitForOptions {
1996
+ readonly nodeId?: string;
1997
+ readonly timeoutMs?: number;
1998
+ readonly pollIntervalMs?: number;
1999
+ }
2000
+ interface EngineInternals {
2001
+ /** Override the fetch implementation used by the API client. Tests only. */
2002
+ readonly fetch?: typeof globalThis.fetch;
2003
+ /**
2004
+ * Optional pre-tool gate. When set, every tool dispatch is checked
2005
+ * by this callback. Returning `{ allow: false }` pauses the run.
2006
+ */
2007
+ readonly gateBeforeTool?: GateBeforeTool;
2008
+ /**
2009
+ * Background executor for async runs (engine.start, engine.resumeAsync).
2010
+ * Defaults to NodeBackgroundExecutor. On Cloudflare Workers, provide a
2011
+ * DurableObjectExecutor that dispatches to a DO.
2012
+ */
2013
+ readonly backgroundExecutor?: BackgroundExecutor;
2014
+ /**
2015
+ * Override the storage factory. When set, the engine calls this
2016
+ * instead of `createEngineStorage(config.storage)` for every run.
2017
+ *
2018
+ * Use this on Cloudflare Workers to build an `EngineStorage` pair
2019
+ * backed by `R2BindingStorageAdapter` (which uses the native R2
2020
+ * binding, not the S3 SDK). Example:
2021
+ *
2022
+ * new Engine(config, {
2023
+ * buildStorage: async () => ({
2024
+ * global: new R2BindingStorageAdapter(env.BUCKET, 'root/.claude'),
2025
+ * workspace: new R2BindingStorageAdapter(env.BUCKET, `root/workspaces/${ws}/.claude`),
2026
+ * }),
2027
+ * })
2028
+ */
2029
+ readonly buildStorage?: () => Promise<EngineStorage>;
2030
+ /**
2031
+ * Handler invoked when an MCP server with `allowSampling: true`
2032
+ * requests an LLM completion via `sampling/createMessage`. Defaults
2033
+ * to a handler that routes through the engine's own ModelAdapter
2034
+ * (same API key, same billing). Override to route to a cheaper
2035
+ * model, enforce budget limits, or refuse specific servers.
2036
+ */
2037
+ readonly samplingHandler?: SamplingHandler;
2038
+ }
2039
+ declare class Engine {
2040
+ readonly config: ResolvedConfig;
2041
+ private readonly internals;
2042
+ private readonly mcpManager;
2043
+ private readonly permissionPolicy;
2044
+ private readonly backgroundExecutor;
2045
+ private readonly webhookDispatcher;
2046
+ constructor(config: ResolvedConfig, internals?: EngineInternals);
2047
+ run(options: RunOptions): Promise<EngineResponse>;
2048
+ resume(options: ResumeOptions): Promise<EngineResponse>;
2049
+ /**
2050
+ * Load a paused snapshot from storage using runId (and optional nodeId).
2051
+ * If nodeId is not provided, finds the most recently paused node.
2052
+ */
2053
+ private loadSnapshot;
2054
+ /**
2055
+ * Start a run asynchronously. Writes initial state, schedules the run
2056
+ * in the background, and returns immediately. Clients should track the
2057
+ * returned runId and poll via `getStatus(runId)` or wait for a webhook.
2058
+ *
2059
+ * Workers compatibility: provide a DurableObjectExecutor via
2060
+ * EngineInternals.backgroundExecutor — the default NodeBackgroundExecutor
2061
+ * uses fire-and-forget Promises which won't survive Worker request exit.
2062
+ */
2063
+ start(options: StartOptions): Promise<{
2064
+ runId: string;
2065
+ nodeId: string;
2066
+ status: RunStatus;
2067
+ }>;
2068
+ /**
2069
+ * Resume a paused run asynchronously. Equivalent to engine.resume() but
2070
+ * dispatched via the background executor. Returns immediately.
2071
+ */
2072
+ resumeAsync(options: ResumeAsyncOptions): Promise<{
2073
+ runId: string;
2074
+ nodeId: string;
2075
+ status: RunStatus;
2076
+ }>;
2077
+ /**
2078
+ * Read the current status of a run. Returns the full EngineResponse once
2079
+ * terminal; returns a provisional response with status='running'|'queued'
2080
+ * otherwise. If nodeId is omitted, picks the most recently updated node.
2081
+ */
2082
+ getStatus(runId: string, nodeId?: string): Promise<EngineResponse>;
2083
+ /**
2084
+ * Poll until the run reaches a terminal state (done | failed | paused |
2085
+ * cancelled) or the timeout expires. Returns the final EngineResponse.
2086
+ */
2087
+ waitFor(runId: string, opts?: WaitForOptions): Promise<EngineResponse>;
2088
+ /**
2089
+ * Cancel an async run. Aborts the background executor and marks the
2090
+ * state as cancelled. Idempotent — safe to call on already-terminal runs.
2091
+ */
2092
+ cancelRun(runId: string, nodeId?: string): Promise<void>;
2093
+ /**
2094
+ * Retry a specific webhook delivery. Useful when the client acknowledges
2095
+ * they missed an event (e.g., after downtime).
2096
+ */
2097
+ retryWebhook(runId: string, deliveryId: string, nodeId?: string): Promise<void>;
2098
+ /**
2099
+ * Scan all runs for stale heartbeats and mark them as failed. Clients
2100
+ * should call this on startup to recover from crashes.
2101
+ *
2102
+ * @param opts.staleThresholdMs - Heartbeat age after which a running run
2103
+ * is considered orphaned. Default: 5 minutes.
2104
+ */
2105
+ recoverOrphanedRuns(opts?: {
2106
+ staleThresholdMs?: number;
2107
+ }): Promise<readonly RunState[]>;
2108
+ private maybeFireWebhook;
2109
+ private dispatchWebhookWithRetries;
2110
+ /**
2111
+ * Shut down engine-owned background resources — currently just the
2112
+ * `McpManager`'s connection pool. Long-running hosts (servers,
2113
+ * daemons) SHOULD call this before dropping their engine reference,
2114
+ * otherwise MCP subprocesses leak until the parent process exits.
2115
+ *
2116
+ * Idempotent. Safe to call multiple times.
2117
+ */
2118
+ shutdown(): Promise<void>;
2119
+ /**
2120
+ * Structured orchestration — decomposes a task into a plan, executes
2121
+ * each step with specialized agents (planner, researcher, implementer,
2122
+ * verifier, reviewer, finalizer), enforces retry policies, and reverts
2123
+ * failed implementations.
2124
+ *
2125
+ * Unlike `run()` (single agent, best effort), `orchestrate()` guarantees
2126
+ * a result: done (all steps passed), partial (some failed + reverted),
2127
+ * or failed (planning failed entirely).
2128
+ *
2129
+ * Requires `config.orchestrator.enabled = true` or the method throws.
2130
+ * The orchestrator calls `engine.run()` internally for each phase —
2131
+ * it's a state machine layer above the existing agent loop.
2132
+ *
2133
+ * Pure JS — works on both Node.js and Cloudflare Workers.
2134
+ */
2135
+ orchestrate(options: OrchestrateOptions): Promise<OrchestratorResult>;
2136
+ /**
2137
+ * Emit a terminal log line per run. `done` is info; `paused` is info;
2138
+ * `failed` is error (with the error message in `meta.error`).
2139
+ */
2140
+ private logRunEnd;
2141
+ /**
2142
+ * Collect the names of all tools that will be registered — used to
2143
+ * populate the system prompt's tool-specific instructions BEFORE
2144
+ * building the registry itself (the prompt goes into the registry's
2145
+ * Agent tool as the child system prompt, so the prompt must exist
2146
+ * first).
2147
+ */
2148
+ private collectToolNames;
2149
+ /**
2150
+ * Resolve the subagent catalogue the Agent tool will dispatch against.
2151
+ *
2152
+ * Merge semantics:
2153
+ * 1. Start with `config.agents.builtins` — each name becomes a
2154
+ * placeholder definition that inherits the parent's system prompt.
2155
+ * (The only builtin v0.1 knows about is `'general-purpose'`.)
2156
+ * 2. If `config.agents.customPath` is set, load any `.md` files from
2157
+ * that directory (relative to the workspace adapter) and append as
2158
+ * full `AgentDefinition` records with their own system prompts.
2159
+ * 3. Custom-loaded agents with the same name as a builtin REPLACE the
2160
+ * builtin — last-wins dedup happens inside `createAgentTool`.
2161
+ *
2162
+ * Throws `ConfigError` if the merged list is empty.
2163
+ */
2164
+ private resolveAgents;
2165
+ /**
2166
+ * Pick the effective gate callback:
2167
+ * 1. `EngineInternals.gateBeforeTool` (test seam) — highest priority
2168
+ * 2. `config.hooks.gateBeforeTool` (user config) — the public path
2169
+ * 3. `undefined` (no gate) — default
2170
+ */
2171
+ private resolveGate;
2172
+ /**
2173
+ * Start the run-level timeout. Returns an AbortController signal if
2174
+ * `runTimeoutMs > 0`, otherwise returns an empty handle. The caller
2175
+ * MUST invoke `clear()` in a finally block to cancel the timer.
2176
+ */
2177
+ private startRunTimeout;
2178
+ private firePostRunHook;
2179
+ private finalizeResult;
2180
+ /**
2181
+ * Pick the effective `SkillSource` for a run.
2182
+ *
2183
+ * - Per-run override (`options.skills`) → `InlineSkillSource`
2184
+ * - else `config.skills.autoload === true` → `StorageSkillSource`
2185
+ * - else → undefined (SkillPage tool not registered)
2186
+ */
2187
+ private resolveSkillSource;
2188
+ /**
2189
+ * Build a throttled heartbeat callback for agentLoop's `onProgress` hook.
2190
+ *
2191
+ * Only writes to `state.json` if one already exists for this run
2192
+ * (i.e., the run was started via `engine.start()` or `resumeAsync()`).
2193
+ * Sync `engine.run()` callers without a pre-existing state file get a
2194
+ * no-op heartbeat.
2195
+ *
2196
+ * Throttled to at most one write per 500ms AND only when activity
2197
+ * changes, so R2 writes stay cheap even for long runs.
2198
+ */
2199
+ private buildHeartbeat;
2200
+ /**
2201
+ * Build the engine's storage pair. Uses the `EngineInternals.buildStorage`
2202
+ * override when provided (for Workers / custom adapters), otherwise
2203
+ * defers to the standard `createEngineStorage()` factory.
2204
+ */
2205
+ private buildStorage;
2206
+ private buildClient;
2207
+ }
2208
+
2209
+ /**
2210
+ * providerSelector — translate a `ResolvedModelConfig` into a typed
2211
+ * `ProviderPlan` describing what API client to construct and how.
2212
+ *
2213
+ * The plan is a thin, validated intermediate between user config and the
2214
+ * actual SDK construction so that:
2215
+ * 1. Invalid combinations (proxy provider without baseURL, anthropic
2216
+ * without apiKey) are rejected at this layer, not at runtime inside
2217
+ * the SDK's message send
2218
+ * 2. Deferred backends (Bedrock, Vertex, Foundry) can be flagged here
2219
+ * with a clear `NotImplementedError` pointing at the v0.2 milestone
2220
+ * 3. Tests can exercise selection logic independently of actual client
2221
+ * construction
2222
+ */
2223
+
2224
+ interface AnthropicProviderPlan {
2225
+ readonly kind: 'anthropic';
2226
+ readonly modelId: string;
2227
+ readonly apiKey: string;
2228
+ readonly baseURL: string | undefined;
2229
+ readonly maxTokens: number;
2230
+ readonly temperature: number;
2231
+ readonly maxRetries: number;
2232
+ }
2233
+ interface ProxyProviderPlan {
2234
+ readonly kind: 'proxy';
2235
+ readonly modelId: string;
2236
+ readonly apiKey: string;
2237
+ readonly baseURL: string;
2238
+ readonly maxTokens: number;
2239
+ readonly temperature: number;
2240
+ readonly maxRetries: number;
2241
+ }
2242
+ type ProviderPlan = AnthropicProviderPlan | ProxyProviderPlan;
2243
+
2244
+ /**
2245
+ * AnthropicClient — thin wrapper around `@anthropic-ai/sdk`.
2246
+ *
2247
+ * Responsibilities:
2248
+ * 1. Construct an `Anthropic` instance from a `ProviderPlan` (anthropic
2249
+ * first-party or proxy). Passes a custom `fetch` when supplied so
2250
+ * tests can drive the client with a scripted fetch without any real
2251
+ * network I/O.
2252
+ * 2. Expose `streamMessage(request)` as an async generator of
2253
+ * `NormalizedEvent`, with Anthropic SDK stream events passed through
2254
+ * `normalizeStream` on the way out.
2255
+ * 3. Translate SDK-level errors into the engine's typed error taxonomy
2256
+ * (`AuthError`, `RateLimitError`, `ApiError`, etc.) so callers can
2257
+ * discriminate without instanceof-checking third-party classes.
2258
+ *
2259
+ * The client is stateless — each `streamMessage` call is independent.
2260
+ * Callers should reuse a single `AnthropicClient` per run; the
2261
+ * underlying SDK already pools HTTP connections internally.
2262
+ */
2263
+
2264
+ interface AnthropicClientOptions {
2265
+ readonly plan: ProviderPlan;
2266
+ /** Custom fetch for tests. Defaults to globalThis.fetch. */
2267
+ readonly fetch?: typeof globalThis.fetch;
2268
+ }
2269
+
2270
+ /**
2271
+ * AnthropicAdapter — wraps the existing @anthropic-ai/sdk client as a
2272
+ * ModelAdapter. Default provider.
2273
+ */
2274
+
2275
+ declare class AnthropicAdapter implements ModelAdapter {
2276
+ private readonly client;
2277
+ constructor(options: AnthropicClientOptions);
2278
+ streamMessage(request: StreamRequest): AsyncGenerator<NormalizedEvent, void, void>;
2279
+ }
2280
+
2281
+ /**
2282
+ * AISdkAdapter — ModelAdapter using the Vercel AI SDK for 75+ providers.
2283
+ *
2284
+ * Properly converts Anthropic-format messages to AI SDK v4 ModelMessage
2285
+ * format, including tool_use/tool_result round-trips. The key is that
2286
+ * AI SDK v4 requires tool result output as { type: 'text', value: '...' }
2287
+ * (not a plain string or generic object).
2288
+ */
2289
+
2290
+ interface AISdkAdapterOptions {
2291
+ readonly provider: string;
2292
+ readonly modelId: string;
2293
+ readonly apiKey: string;
2294
+ readonly baseURL?: string | undefined;
2295
+ readonly maxRetries?: number | undefined;
2296
+ }
2297
+ declare class AISdkAdapter implements ModelAdapter {
2298
+ private readonly options;
2299
+ private model;
2300
+ constructor(options: AISdkAdapterOptions);
2301
+ streamMessage(request: StreamRequest): AsyncGenerator<NormalizedEvent, void, void>;
2302
+ private getModel;
2303
+ }
2304
+
2305
+ /**
2306
+ * Model adapter factory — routes config to the right adapter.
2307
+ *
2308
+ * Same pattern as storage/factory.ts:
2309
+ * createEngineStorage(config.storage) → StorageAdapter
2310
+ * createModelAdapter(config.model) → ModelAdapter
2311
+ */
2312
+
2313
+ interface CreateModelAdapterOptions {
2314
+ /** Custom fetch for the Anthropic adapter (OpenRouter auth override). */
2315
+ readonly fetch?: typeof globalThis.fetch | undefined;
2316
+ }
2317
+ /**
2318
+ * Create a ModelAdapter from the resolved model config.
2319
+ *
2320
+ * Routing:
2321
+ * - 'anthropic' (no sdk override) → AnthropicAdapter (@anthropic-ai/sdk)
2322
+ * - 'proxy' → AnthropicAdapter with baseURL
2323
+ * - 'openai', 'google', 'openai-compatible' → AISdkAdapter
2324
+ * - 'anthropic' + sdk: 'ai-sdk' → AISdkAdapter with @ai-sdk/anthropic
2325
+ */
2326
+ declare function createModelAdapter(config: ResolvedModelConfig, options?: CreateModelAdapterOptions): ModelAdapter;
2327
+
2328
+ /**
2329
+ * WebhookDispatcher — delivers status changes to client URLs.
2330
+ *
2331
+ * Features:
2332
+ * - HMAC-SHA256 signing via Web Crypto API (works everywhere)
2333
+ * - Exponential backoff retries: 10s → 60s → 5min → 30min → give up
2334
+ * - HTTP 4xx → no retry (client bug); 5xx/network → retry
2335
+ * - HTTP 410 Gone → give up immediately (resource deliberately removed)
2336
+ * - Delivery tracking: each attempt recorded in state.json
2337
+ *
2338
+ * All pure JS — no `node:` imports, Workers-compatible.
2339
+ */
2340
+
2341
+ /** Milliseconds to wait before attempt N (1-indexed). */
2342
+ declare const RETRY_DELAYS_MS: readonly [0, 10000, 60000, number, number];
2343
+ declare const MAX_ATTEMPTS: 5;
2344
+ /**
2345
+ * Sign a webhook payload with HMAC-SHA256 using Web Crypto API.
2346
+ * Returns the full header value: `sha256=<hex>`.
2347
+ */
2348
+ declare function signPayload(secret: string, timestamp: number, body: string): Promise<string>;
2349
+ interface DeliverOptions {
2350
+ readonly url: string;
2351
+ readonly event: WebhookEvent;
2352
+ readonly payload: EngineResponse;
2353
+ readonly secret?: string;
2354
+ readonly headers?: Readonly<Record<string, string>>;
2355
+ readonly attempt?: number;
2356
+ readonly deliveryId?: string;
2357
+ readonly timeoutMs?: number;
2358
+ readonly fetch?: typeof globalThis.fetch;
2359
+ }
2360
+ interface DeliverResult {
2361
+ readonly delivery: WebhookDelivery;
2362
+ readonly shouldRetry: boolean;
2363
+ readonly nextRetryDelayMs?: number;
2364
+ }
2365
+ declare class WebhookDispatcher {
2366
+ private readonly fetchImpl;
2367
+ constructor(fetchImpl?: typeof globalThis.fetch);
2368
+ /**
2369
+ * Attempt one delivery. Returns a delivery record and whether to retry.
2370
+ */
2371
+ deliver(options: DeliverOptions): Promise<DeliverResult>;
2372
+ }
2373
+
2374
+ /**
2375
+ * Error taxonomy for la-machina-engine.
2376
+ *
2377
+ * Every thrown error must be a subclass of `EngineError`, so callers can
2378
+ * `instanceof` discriminate at the library boundary. Phase 1 defines only
2379
+ * the errors needed for config resolution and unimplemented methods.
2380
+ * Later phases extend this hierarchy (see plan 002 §4 Phase 4).
2381
+ */
2382
+ declare class EngineError extends Error {
2383
+ readonly code: string;
2384
+ constructor(code: string, message: string);
2385
+ }
2386
+ /**
2387
+ * Thrown when config is invalid or incomplete. Raised by `initEngine()` when
2388
+ * `apiKey` cannot be resolved, or when schema validation fails.
2389
+ */
2390
+ declare class ConfigError extends EngineError {
2391
+ constructor(message: string);
2392
+ }
2393
+ /**
2394
+ * Thrown when a method is called in a phase where its implementation has
2395
+ * not yet landed. Used by `engine.run()` and `engine.resume()` during
2396
+ * Phases 1–6 before the loop is wired, and by provider selectors for
2397
+ * backends deferred to v0.2 (Bedrock, Vertex, Foundry).
2398
+ */
2399
+ declare class NotImplementedError extends EngineError {
2400
+ constructor(feature: string, target: string);
2401
+ }
2402
+ /**
2403
+ * Thrown when the provider rejects a request with HTTP 401/403 (missing
2404
+ * or invalid API key). Callers should not retry.
2405
+ */
2406
+ declare class AuthError extends EngineError {
2407
+ constructor(message: string);
2408
+ }
2409
+ /**
2410
+ * Thrown when the provider returns HTTP 429 (rate limited). Callers
2411
+ * should back off and retry. `retryAfterMs` reflects the provider's
2412
+ * hint when available, otherwise null.
2413
+ */
2414
+ declare class RateLimitError extends EngineError {
2415
+ readonly retryAfterMs: number | null;
2416
+ constructor(message: string, retryAfterMs?: number | null);
2417
+ }
2418
+ /**
2419
+ * Thrown when a streaming response can't be parsed into a normalized
2420
+ * event — typically malformed SSE or a JSON parse failure in a delta.
2421
+ * This is a hard error; the stream is not recoverable.
2422
+ */
2423
+ declare class StreamParseError extends EngineError {
2424
+ constructor(message: string);
2425
+ }
2426
+ /**
2427
+ * Thrown when a streaming response ends without a terminal event
2428
+ * (`message_stop`). The caller cannot know whether the assistant
2429
+ * actually finished — almost always safer to fail the run and retry.
2430
+ */
2431
+ declare class StreamIncompleteError extends EngineError {
2432
+ constructor(message: string);
2433
+ }
2434
+ /**
2435
+ * Generic provider-side API error — non-auth, non-rate-limit failures
2436
+ * like 500s, network drops, and unrecognized shapes. `status` is the
2437
+ * HTTP status code when available, otherwise null.
2438
+ */
2439
+ declare class ApiError extends EngineError {
2440
+ readonly status: number | null;
2441
+ constructor(message: string, status?: number | null);
2442
+ }
2443
+ /**
2444
+ * Thrown when a run exceeds `execution.runTimeoutMs`. The agent loop is
2445
+ * aborted at the next checkpoint (top of the turn loop or after a tool
2446
+ * dispatch). The transcript so far is preserved.
2447
+ */
2448
+ declare class RunTimeoutError extends EngineError {
2449
+ readonly timeoutMs: number;
2450
+ constructor(timeoutMs: number);
2451
+ }
2452
+
2453
+ /**
2454
+ * Structured JSON output — schema injection, parsing, validation.
2455
+ *
2456
+ * When `outputFormat: 'json'` is set on RunOptions:
2457
+ * 1. buildSchemaPrompt() injects output instructions into the system prompt
2458
+ * 2. tryParseJSON() extracts JSON from the model's response (handles fences, preamble)
2459
+ * 3. validateOutput() checks against the Zod schema
2460
+ *
2461
+ * All pure JS — no `node:` imports, Workers-compatible.
2462
+ */
2463
+
2464
+ /**
2465
+ * Build the output format section to append to the system prompt.
2466
+ * Called when outputFormat is 'json'.
2467
+ */
2468
+ declare function buildSchemaPrompt(schema?: ZodTypeAny): string;
2469
+ interface ParseResult {
2470
+ readonly ok: boolean;
2471
+ readonly value?: unknown;
2472
+ readonly error?: string;
2473
+ }
2474
+ /**
2475
+ * Extract JSON from model output. Tries multiple strategies:
2476
+ * 1. Raw parse (entire text is JSON)
2477
+ * 2. Strip markdown fences (```json ... ```)
2478
+ * 3. Find first { ... } or [ ... ] block
2479
+ */
2480
+ declare function tryParseJSON(text: string): ParseResult;
2481
+ /**
2482
+ * Validate parsed JSON against a Zod schema.
2483
+ * Returns the validated data or an error message.
2484
+ */
2485
+ declare function validateOutput(value: unknown, schema: ZodTypeAny): {
2486
+ ok: true;
2487
+ data: unknown;
2488
+ } | {
2489
+ ok: false;
2490
+ error: string;
2491
+ };
2492
+
2493
+ /**
2494
+ * StreamingToolExecutor — stateful tool queue with ordered result yielding,
2495
+ * concurrency control, and sibling error cascading.
2496
+ *
2497
+ * Ported 1:1 from La-Machina's StreamingToolExecutor.ts. Key behaviors:
2498
+ *
2499
+ * - Tools are enqueued via `addTool()` and executed as they become eligible
2500
+ * - `canExecute()` enforces: concurrent-safe tools run in parallel; unsafe
2501
+ * tools run alone (exclusive access)
2502
+ * - Results are yielded in **original enqueue order** regardless of
2503
+ * completion order (preserves tool_result ordering for the API)
2504
+ * - On **Bash error**, `siblingAbortController.abort()` cancels all queued
2505
+ * siblings with synthetic error results. Only Bash cascades — Read/Grep/
2506
+ * WebFetch errors are independent.
2507
+ * - All pure JS — no `node:` imports, Workers-compatible.
2508
+ */
2509
+
2510
+ declare class StreamingToolExecutor {
2511
+ private readonly tools;
2512
+ private readonly executor;
2513
+ private readonly siblingAbort;
2514
+ private bashErrorDesc;
2515
+ constructor(executor: ToolExecutor);
2516
+ /**
2517
+ * Enqueue a tool call. Eligible tools start executing immediately.
2518
+ */
2519
+ addTool(id: string, name: string, input: unknown, isConcurrencySafe: boolean): void;
2520
+ /**
2521
+ * Yield results in original enqueue order. Waits for each tool to
2522
+ * complete before yielding — guarantees the API sees tool_result
2523
+ * blocks in the same order as the tool_use blocks.
2524
+ */
2525
+ results(): AsyncGenerator<{
2526
+ id: string;
2527
+ result: ToolResult;
2528
+ }>;
2529
+ /**
2530
+ * Can this tool start now? Mirrors La-Machina's canExecuteTool():
2531
+ * - If nothing is executing → yes
2532
+ * - If only concurrent-safe tools are executing AND this tool is safe → yes
2533
+ * - Otherwise → no (wait for exclusive access)
2534
+ */
2535
+ private canExecute;
2536
+ /**
2537
+ * Walk the queue and start any eligible tools. Called after enqueue
2538
+ * and after each tool completes (to unblock the next batch).
2539
+ */
2540
+ private processQueue;
2541
+ private startTool;
2542
+ private executeTool;
2543
+ }
2544
+
2545
+ /**
2546
+ * SendMessage tool — inter-agent communication within a single run.
2547
+ *
2548
+ * Routes messages to agents tracked in the SubagentRegistry:
2549
+ *
2550
+ * - **Running agent**: Message is queued via `registry.queueMessage()`.
2551
+ * The child's agentLoop drains queued messages at the start of
2552
+ * each turn (injected as a user message).
2553
+ *
2554
+ * - **Stopped agent**: Returns an error — auto-resume is deferred to v0.5.
2555
+ *
2556
+ * - **Completed agent**: Returns an error — can't send to finished agents.
2557
+ *
2558
+ * Ported from La-Machina's SendMessageTool.ts (918 lines). The engine
2559
+ * version omits mailbox/UDS/bridge routing (CLI-only) and structured
2560
+ * messages (shutdown_request, plan_approval) — headless mode doesn't
2561
+ * need them.
2562
+ */
2563
+
2564
+ declare const inputSchema$1: z.ZodObject<{
2565
+ to: z.ZodString;
2566
+ message: z.ZodString;
2567
+ }, "strip", z.ZodTypeAny, {
2568
+ message: string;
2569
+ to: string;
2570
+ }, {
2571
+ message: string;
2572
+ to: string;
2573
+ }>;
2574
+ interface SendMessageToolOptions {
2575
+ readonly registry: SubagentRegistry;
2576
+ }
2577
+ declare function createSendMessageTool(options: SendMessageToolOptions): Tool$1<typeof inputSchema$1>;
2578
+
2579
+ /**
2580
+ * Fork subagent — child inherits parent's full message context.
2581
+ *
2582
+ * Ported 1:1 from La-Machina's forkSubagent.ts. When `subagent_type`
2583
+ * is omitted, the Agent tool forks a child that sees the parent's
2584
+ * entire conversation history + a directive. This enables "continue
2585
+ * this but focus on X" delegation without starting from scratch.
2586
+ *
2587
+ * Key behaviors:
2588
+ * - Placeholder tool_results are byte-identical across all forks
2589
+ * (enables prompt cache sharing)
2590
+ * - Recursion guard via `<fork-boilerplate>` tag detection
2591
+ * - Fork children are instructed NOT to spawn sub-agents
2592
+ *
2593
+ * All pure JS — no `node:` imports, Workers-compatible.
2594
+ */
2595
+
2596
+ /**
2597
+ * Detect whether the current message history contains a fork boilerplate
2598
+ * tag — prevents recursive forking (child can't re-fork).
2599
+ */
2600
+ declare function isInForkChild(messages: readonly MessageParam[]): boolean;
2601
+ /**
2602
+ * Build the messages a forked child sees. The child gets:
2603
+ * 1. All of the parent's existing messages (passed separately)
2604
+ * 2. A cloned assistant message with tool_use blocks
2605
+ * 3. Placeholder tool_results + fork directive
2606
+ *
2607
+ * The placeholder text is identical across all forks so the LLM
2608
+ * provider can cache-share the prefix.
2609
+ */
2610
+ declare function buildForkedMessages(directive: string, assistantContent: readonly ContentBlockParam[]): MessageParam[];
2611
+
2612
+ /**
2613
+ * LocalStorageAdapter — filesystem-backed `StorageAdapter`.
2614
+ *
2615
+ * Ported from La-Machina's production implementation. Wraps Node
2616
+ * `fs/promises` with:
2617
+ *
2618
+ * - **Atomic writes** (temp file + rename pattern)
2619
+ * - **Path safety** via `validateRelativePath` — blocks traversal, absolute
2620
+ * paths, null bytes at every entrypoint
2621
+ * - **Null-on-missing semantics** — `readFile`/`stat` return `null` instead
2622
+ * of throwing `ENOENT`; `listDir` returns `[]` for missing directories
2623
+ * - **Recursive directory creation** on all write operations
2624
+ *
2625
+ * Errors from the underlying `fs` module are wrapped in `StorageError` with
2626
+ * the original error preserved as `.cause`.
2627
+ */
2628
+
2629
+ declare class LocalStorageAdapter implements StorageAdapter {
2630
+ private readonly root;
2631
+ constructor(root: string);
2632
+ private resolve;
2633
+ readFile(rel: string): Promise<string | null>;
2634
+ writeFile(rel: string, content: string): Promise<void>;
2635
+ appendFile(rel: string, content: string): Promise<void>;
2636
+ deleteFile(rel: string): Promise<void>;
2637
+ exists(rel: string): Promise<boolean>;
2638
+ listDir(rel: string): Promise<string[]>;
2639
+ mkdir(rel: string): Promise<void>;
2640
+ isDirectory(rel: string): Promise<boolean>;
2641
+ stat(rel: string): Promise<FileStat | null>;
2642
+ getRoot(): string;
2643
+ rootExists(): Promise<boolean>;
2644
+ }
2645
+
2646
+ /**
2647
+ * R2StorageAdapter — Cloudflare R2 (S3-compatible) implementation.
2648
+ *
2649
+ * Ported from La-Machina. Uses `@aws-sdk/client-s3` since R2 speaks the S3
2650
+ * protocol. Path semantics:
2651
+ *
2652
+ * - Files become objects keyed at `${rootPrefix}/${relativePath}`
2653
+ * - Directories are synthetic — S3 has no real directory concept. `mkdir`
2654
+ * is a no-op; `listDir` uses `Delimiter: '/'` to separate files from
2655
+ * "subdirectories" (common prefixes).
2656
+ * - `appendFile` is read-modify-write (S3 has no native append primitive).
2657
+ * OK for small files (JSONL episode logs); prohibitively expensive for
2658
+ * large files.
2659
+ * - `writeFile` is a single atomic PutObject — no temp+rename dance.
2660
+ *
2661
+ * The backing S3Client is constructed from config. Tests use
2662
+ * `aws-sdk-client-mock` to intercept `.send()` without needing DI.
2663
+ */
2664
+
2665
+ interface R2ClientConfig {
2666
+ readonly bucket: string;
2667
+ readonly region: string;
2668
+ readonly accessKeyId: string;
2669
+ readonly secretAccessKey: string;
2670
+ readonly endpoint?: string | undefined;
2671
+ }
2672
+ declare class R2StorageAdapter implements StorageAdapter {
2673
+ private readonly client;
2674
+ private readonly bucket;
2675
+ private readonly rootPrefix;
2676
+ constructor(config: R2ClientConfig, rootPrefix: string);
2677
+ private key;
2678
+ readFile(rel: string): Promise<string | null>;
2679
+ writeFile(rel: string, content: string): Promise<void>;
2680
+ appendFile(rel: string, content: string): Promise<void>;
2681
+ deleteFile(rel: string): Promise<void>;
2682
+ exists(rel: string): Promise<boolean>;
2683
+ listDir(rel: string): Promise<string[]>;
2684
+ mkdir(rel: string): Promise<void>;
2685
+ isDirectory(rel: string): Promise<boolean>;
2686
+ stat(rel: string): Promise<FileStat | null>;
2687
+ }
2688
+
2689
+ /**
2690
+ * Hippocampus — memory encoding and retrieval engine.
2691
+ *
2692
+ * Ported from La-Machina. Named for the brain's hippocampus, which
2693
+ * encodes new traces (writing) and pattern-completes partial cues into
2694
+ * full memories (reading).
2695
+ *
2696
+ * Each Hippocampus instance manages one scope's files via a
2697
+ * StorageAdapter:
2698
+ * - profile.md → identity (full rewrite, not append)
2699
+ * - rules.md → behavioral gates (always / never / when sections)
2700
+ * - lessons.md → semantic facts (append + dedup, token-budgeted recall)
2701
+ * - topics/*.md → per-topic deep dives
2702
+ *
2703
+ * The storage backend is fully abstracted — the same instance works
2704
+ * against local fs or R2. Read-modify-write paths use the adapter's
2705
+ * atomic `writeFile` so concurrent writers see one or the other, never
2706
+ * a partial.
2707
+ */
2708
+
2709
+ declare class Hippocampus {
2710
+ private readonly storage;
2711
+ private readonly subdir;
2712
+ private readonly profilePath;
2713
+ private readonly rulesPath;
2714
+ private readonly lessonsPath;
2715
+ private readonly topicsDir;
2716
+ constructor(storage: StorageAdapter, subdir?: string);
2717
+ recallIdentity(): Promise<string>;
2718
+ recallRules(): Promise<string>;
2719
+ /**
2720
+ * Load semantic knowledge most recent first, within a token budget
2721
+ * (rough heuristic: 4 chars per token). The header lines (# Lessons,
2722
+ * blank lines) always survive; entry lines fill the remaining budget
2723
+ * starting from the most recent.
2724
+ */
2725
+ recallLessons(tokenBudget?: number): Promise<string>;
2726
+ recallTopic(slug: string): Promise<string>;
2727
+ encodeRule(text: string, kind: 'always' | 'never' | 'when', confidence?: EngramConfidence, source?: EngramSource): Promise<void>;
2728
+ encodeLesson(text: string, topic?: string, source?: EngramSource): Promise<void>;
2729
+ /**
2730
+ * Replace the identity snapshot — full rewrite, not append. Identity
2731
+ * is a coherent snapshot, not an append log.
2732
+ */
2733
+ rewriteIdentity(entries: ReadonlyArray<string>): Promise<void>;
2734
+ entryCount(): Promise<number>;
2735
+ getStorage(): StorageAdapter;
2736
+ get rulesPathRelative(): string;
2737
+ get lessonsPathRelative(): string;
2738
+ get subdirPath(): string;
2739
+ /**
2740
+ * Extract normalized entry texts from a markdown memory file.
2741
+ * Strips the "- " prefix and trailing `<!-- ... -->` metadata so
2742
+ * dedup works regardless of the comment payload.
2743
+ */
2744
+ static extractEntryTexts(content: string): Set<string>;
2745
+ /** Sanitize a topic name into a safe file slug. */
2746
+ static sanitizeSlug(name: string): string;
2747
+ }
2748
+
2749
+ /**
2750
+ * memoryConfig — applies the engine's `memory.mode` and `memory.scope`
2751
+ * gates to a wrapped Hippocampus + EpisodicMemory pair.
2752
+ *
2753
+ * Three modes from `ResolvedMemoryConfig`:
2754
+ *
2755
+ * - `'off'` — All reads return empty, all writes are no-ops.
2756
+ * The wrapped Hippocampus is built but never touched.
2757
+ * - `'read-only'` — Reads pass through to the wrapped Hippocampus,
2758
+ * writes are silent no-ops.
2759
+ * - `'read-write'` — Both reads and writes pass through.
2760
+ *
2761
+ * Two scopes:
2762
+ * - `'workspace'` — Hippocampus reads/writes the workspace adapter.
2763
+ * - `'global'` — Hippocampus reads/writes the global adapter.
2764
+ *
2765
+ * The factory returns a thin façade that exposes Hippocampus's read +
2766
+ * write surface plus the EpisodicMemory instance. The agent loop and
2767
+ * tools talk to this façade, never the raw Hippocampus.
2768
+ */
2769
+
2770
+ interface SmartMemoryConfig {
2771
+ readonly mode: MemoryMode;
2772
+ readonly scope: MemoryScope;
2773
+ }
2774
+ interface SmartMemoryOptions {
2775
+ readonly storage: EngineStorage;
2776
+ readonly config: SmartMemoryConfig;
2777
+ }
2778
+ interface SmartMemory {
2779
+ readonly mode: MemoryMode;
2780
+ readonly scope: MemoryScope;
2781
+ readonly episodes: EpisodicMemory;
2782
+ recallIdentity(): Promise<string>;
2783
+ recallRules(): Promise<string>;
2784
+ recallLessons(tokenBudget?: number): Promise<string>;
2785
+ recallTopic(slug: string): Promise<string>;
2786
+ encodeRule(text: string, kind: 'always' | 'never' | 'when', confidence?: EngramConfidence, source?: EngramSource): Promise<void>;
2787
+ encodeLesson(text: string, topic?: string, source?: EngramSource): Promise<void>;
2788
+ rewriteIdentity(entries: ReadonlyArray<string>): Promise<void>;
2789
+ }
2790
+ declare function createSmartMemory(options: SmartMemoryOptions): SmartMemory;
2791
+
2792
+ /**
2793
+ * Skills loader — discovers `SKILL.md` files under a base directory.
2794
+ *
2795
+ * The skill format is intentionally minimal:
2796
+ *
2797
+ * skills/
2798
+ * ├── alpha/
2799
+ * │ ├── SKILL.md ← required (name + description)
2800
+ * │ └── pages/ ← optional (multi-page skill)
2801
+ * │ ├── intro.md
2802
+ * │ └── advanced.md
2803
+ * └── beta/
2804
+ * └── SKILL.md
2805
+ *
2806
+ * The loader returns one `LoadedSkill` per discovered `SKILL.md`. The
2807
+ * description is extracted from the first non-empty body line (anything
2808
+ * after the title heading) — falling back to the heading text itself if
2809
+ * the file has no body.
2810
+ *
2811
+ * The result feeds into:
2812
+ * - the system prompt builder (Phase 12+) which injects skill names +
2813
+ * descriptions so the model knows what's available
2814
+ * - the SkillPage tool, which lets the model load specific pages on
2815
+ * demand for multi-page skills
2816
+ */
2817
+
2818
+ interface LoadedSkill {
2819
+ /** Folder name (also used as the lookup key in SkillPage). */
2820
+ readonly name: string;
2821
+ /** Human-readable description, extracted from SKILL.md. */
2822
+ readonly description: string;
2823
+ /** Whether the skill has a pages/ subdirectory with content. */
2824
+ readonly hasPages: boolean;
2825
+ /** Full relative path to the skill folder, e.g. "skills/alpha". */
2826
+ readonly path: string;
2827
+ /** Full relative path to the SKILL.md file. */
2828
+ readonly skillFilePath: string;
2829
+ }
2830
+ /**
2831
+ * Walk the given base directory and return one entry per discovered
2832
+ * skill. Returns an empty array when the base directory does not exist
2833
+ * or contains no skills.
2834
+ */
2835
+ declare function loadSkills(storage: StorageAdapter, baseDir: string): Promise<LoadedSkill[]>;
2836
+
2837
+ /**
2838
+ * SkillPage tool — loads skill content on demand via a `SkillSource`.
2839
+ *
2840
+ * The tool is pluggable: the engine constructs it with either
2841
+ * `StorageSkillSource` (disk-backed, default) or `InlineSkillSource`
2842
+ * (per-run override). The tool itself only knows the interface.
2843
+ *
2844
+ * Input:
2845
+ * { skill: string, page?: string }
2846
+ *
2847
+ * - When `page` is omitted, the main `SKILL.md` body is returned.
2848
+ * - When `page` is set, the supplemental page body is returned.
2849
+ *
2850
+ * Errors surface as `{ isError: true, content }`:
2851
+ * - invalid skill / page name (path traversal shape)
2852
+ * - unknown skill (returned null from the source)
2853
+ * - unknown page
2854
+ * - upstream source throws (network error, SSRF block, etc.)
2855
+ */
2856
+
2857
+ declare const inputSchema: z.ZodObject<{
2858
+ skill: z.ZodString;
2859
+ page: z.ZodOptional<z.ZodString>;
2860
+ }, "strip", z.ZodTypeAny, {
2861
+ skill: string;
2862
+ page?: string | undefined;
2863
+ }, {
2864
+ skill: string;
2865
+ page?: string | undefined;
2866
+ }>;
2867
+ interface SkillPageToolOptions {
2868
+ /** The skill source the tool reads through. */
2869
+ readonly source: SkillSource;
2870
+ }
2871
+ declare function createSkillPageTool(options: SkillPageToolOptions): Tool$1<typeof inputSchema>;
2872
+
2873
+ /**
2874
+ * StorageSkillSource — the default `SkillSource`.
2875
+ *
2876
+ * Reads `SKILL.md` files (and optional `pages/*.md`) from a storage
2877
+ * adapter pair. Lookup precedence is workspace-first, global-fallback —
2878
+ * matching the pre-017 behavior so existing deployments see no change.
2879
+ *
2880
+ * Empty/missing `baseDir` → `list()` returns `[]` (equivalent to "no
2881
+ * skills configured"). `autoload: false` at the config layer is handled
2882
+ * upstream — this source does not read that flag.
2883
+ */
2884
+
2885
+ interface StorageSkillSourceOptions {
2886
+ readonly storage: EngineStorage;
2887
+ /** Directory under each scope's adapter root. Default: `'skills'`. */
2888
+ readonly baseDir?: string;
2889
+ }
2890
+ declare class StorageSkillSource implements SkillSource {
2891
+ private readonly storage;
2892
+ private readonly baseDir;
2893
+ constructor(options: StorageSkillSourceOptions);
2894
+ list(): Promise<ReadonlyArray<ResolvedSkill>>;
2895
+ getSkillFile(skill: string): Promise<string | null>;
2896
+ getPage(skill: string, page: string): Promise<string | null>;
2897
+ listPages(skill: string): Promise<ReadonlyArray<string>>;
2898
+ private listPagesIn;
2899
+ private findFile;
2900
+ private readIfExists;
2901
+ }
2902
+
2903
+ /**
2904
+ * InlineSkillSource — the per-run override `SkillSource`.
2905
+ *
2906
+ * Takes a list of `SkillOverride` records and resolves bodies from
2907
+ * inline `body` (fast path, no network) or `url` (HTTPS fetch on
2908
+ * demand, cached per instance). Designed for Cloudflare Workers —
2909
+ * zero `node:*` imports, plain `fetch`.
2910
+ *
2911
+ * Security:
2912
+ * - Optional `allowedHosts` gate. When set, URL fetches outside the
2913
+ * allowlist throw before making the request. When unset (default),
2914
+ * any URL is allowed — fine for local dev, risky for production.
2915
+ * - `timeoutMs` aborts stuck fetches (default: 15s).
2916
+ *
2917
+ * Caching:
2918
+ * - Bodies (main + each page) are memoized within the instance, so
2919
+ * repeated `SkillPage` calls within one run never refetch.
2920
+ * - No cross-run cache. Each run constructs a fresh InlineSkillSource.
2921
+ */
2922
+
2923
+ interface InlineSkillSourceOptions {
2924
+ /** Fetch implementation. Defaults to `globalThis.fetch`. */
2925
+ readonly fetch?: typeof globalThis.fetch;
2926
+ /**
2927
+ * Host allowlist for URL fetches. When empty/undefined, any URL is
2928
+ * allowed. When set, each URL's host must exactly match an entry or
2929
+ * be a subdomain of one (`api.example.com` matches `example.com`).
2930
+ */
2931
+ readonly allowedHosts?: ReadonlyArray<string>;
2932
+ /** Per-request fetch timeout. Default 15_000 ms. */
2933
+ readonly timeoutMs?: number;
2934
+ }
2935
+ declare class InlineSkillSource implements SkillSource {
2936
+ private readonly byName;
2937
+ private readonly cache;
2938
+ private readonly fetchImpl;
2939
+ private readonly allowedHosts;
2940
+ private readonly timeoutMs;
2941
+ constructor(overrides: ReadonlyArray<SkillOverride>, options?: InlineSkillSourceOptions);
2942
+ list(): Promise<ReadonlyArray<ResolvedSkill>>;
2943
+ getSkillFile(skill: string): Promise<string | null>;
2944
+ getPage(skill: string, page: string): Promise<string | null>;
2945
+ listPages(skill: string): Promise<ReadonlyArray<string>>;
2946
+ private resolve;
2947
+ private assertUrlAllowed;
2948
+ }
2949
+
2950
+ /**
2951
+ * Runtime detection — determines whether the engine is running in
2952
+ * Node.js or a restricted runtime (Cloudflare Workers, etc.)
2953
+ *
2954
+ * Detection strategy:
2955
+ * - Node.js has `process.versions.node` set to a version string
2956
+ * - Workers have `process` (polyfilled) but `process.versions` is
2957
+ * undefined or `process.versions.node` is undefined
2958
+ * - As a fallback, probe whether child_process.spawn is functional
2959
+ */
2960
+ type RuntimeKind = 'node' | 'worker';
2961
+ /**
2962
+ * Detect the runtime. Result is cached after first call.
2963
+ */
2964
+ declare function detectRuntime(): RuntimeKind;
2965
+ /**
2966
+ * Whether subprocess spawning (Bash, ripgrep, MCP stdio) is available.
2967
+ */
2968
+ declare function canSpawnProcesses(): boolean;
2969
+ /**
2970
+ * Whether process lifecycle hooks (process.on('exit')) are available.
2971
+ */
2972
+ declare function hasProcessLifecycle(): boolean;
2973
+
2974
+ /**
2975
+ * Logger — structured log emission gated by `config.logging.level` and
2976
+ * routed to `config.logging.sink`.
2977
+ *
2978
+ * Levels (ascending verbosity):
2979
+ *
2980
+ * silent < error < warn < info < debug
2981
+ *
2982
+ * `silent` suppresses everything (no-op Logger). Any other level passes
2983
+ * records at that rank OR ANY RANK ABOVE IT through to the sink.
2984
+ *
2985
+ * Sinks:
2986
+ * - `'stderr'` — `console.error(JSON.stringify(entry))` per record.
2987
+ * Structured output that's cheap to grep/pipe through `jq`.
2988
+ * - `'none'` — true no-op. Use when embedding the engine inside a
2989
+ * host that has its own observability stack and doesn't want the
2990
+ * engine yelling into stderr.
2991
+ * - `(entry) => void` — callable sink. The engine hands the full
2992
+ * `LogEntry` to the caller on every emission. Use this to route into
2993
+ * OpenTelemetry, Pino, Winston, Sentry, a worker queue, etc.
2994
+ *
2995
+ * The returned logger is intentionally small: `{ error, warn, info, debug }`.
2996
+ * Each method takes a short message and an optional structured `meta`
2997
+ * object. The engine calls these at known checkpoints (run start/end,
2998
+ * turn boundaries, tool dispatch, pauses, failures) — see engine.ts for
2999
+ * the full emission schedule.
3000
+ */
3001
+
3002
+ interface Logger {
3003
+ error(msg: string, meta?: Record<string, unknown>): void;
3004
+ warn(msg: string, meta?: Record<string, unknown>): void;
3005
+ info(msg: string, meta?: Record<string, unknown>): void;
3006
+ debug(msg: string, meta?: Record<string, unknown>): void;
3007
+ }
3008
+ /**
3009
+ * Build a Logger from a resolved logging config.
3010
+ *
3011
+ * Silent-level shortcuts to a true no-op so hot paths don't even
3012
+ * allocate the meta object. Non-silent loggers filter per-call against
3013
+ * the level rank before dispatching to the sink.
3014
+ */
3015
+ declare function createLogger(config: ResolvedLoggingConfig): Logger;
3016
+
3017
+ /**
3018
+ * Orchestrate — the main state machine.
3019
+ *
3020
+ * Pure JS. No platform APIs. Calls engine.run() internally for each
3021
+ * agent phase. Enforces retry policies, file snapshots for rollback,
3022
+ * dependency ordering, and parallel research execution.
3023
+ *
3024
+ * Flow:
3025
+ * 1. Plan — Planner agent produces a JSON plan (or use caller-supplied plan)
3026
+ * 2. Execute — for each step in dependency order:
3027
+ * - research: spawn N researchers in parallel
3028
+ * - implement: synthesize spec from research → run implementer with retry
3029
+ * - verify: run verifier → on fail, retry implementer or revert
3030
+ * - review: optional code review
3031
+ * 3. Finalize — Finalizer agent assembles the report
3032
+ * 4. Return — OrchestratorResult with full provenance
3033
+ */
3034
+
3035
+ /**
3036
+ * Execute a structured orchestration. This is the internal implementation
3037
+ * called by engine.orchestrate().
3038
+ */
3039
+ declare function orchestrate(engine: Engine, options: OrchestrateOptions, config: ResolvedOrchestratorConfig, storage: StorageAdapter, logger: Logger): Promise<OrchestratorResult>;
3040
+
3041
+ /**
3042
+ * Plan parser — extract and validate a JSON plan from LLM output.
3043
+ *
3044
+ * The Planner agent may wrap JSON in markdown fences, include preamble
3045
+ * text, or produce slightly malformed JSON. This parser is tolerant:
3046
+ * 1. Strips markdown ```json ... ``` fences
3047
+ * 2. Finds the first { ... } block in the text
3048
+ * 3. Parses as JSON
3049
+ * 4. Validates against PlanSchema with Zod
3050
+ */
3051
+
3052
+ /**
3053
+ * Extract a valid Plan from raw LLM output. Returns null if the output
3054
+ * can't be parsed into a valid plan.
3055
+ */
3056
+ declare function parsePlan(raw: string, maxSteps: number): Plan | null;
3057
+
3058
+ /**
3059
+ * Generic retry-with-backoff wrapper. Pure JS — no platform APIs
3060
+ * beyond setTimeout.
3061
+ */
3062
+
3063
+ interface RetryResult<T> {
3064
+ readonly success: boolean;
3065
+ readonly value?: T;
3066
+ readonly error?: string;
3067
+ readonly attempts: number;
3068
+ }
3069
+ /**
3070
+ * Execute `fn` up to `policy.maxAttempts` times with exponential backoff.
3071
+ * Returns the first successful result or the last error.
3072
+ */
3073
+ declare function runWithRetry<T>(fn: () => Promise<T>, policy: RetryPolicy): Promise<RetryResult<T>>;
3074
+
3075
+ /**
3076
+ * File snapshot + restore — rollback mechanism for implementation failures.
3077
+ * Operates through the StorageAdapter abstraction — works on both
3078
+ * local filesystem and R2.
3079
+ */
3080
+
3081
+ /**
3082
+ * Read and store the current content of a list of files. Files that
3083
+ * don't exist get `content: null` — restoring them will delete the file
3084
+ * (reversing a create).
3085
+ */
3086
+ declare function snapshotFiles(storage: StorageAdapter, paths: readonly string[]): Promise<FileSnapshot[]>;
3087
+ /**
3088
+ * Restore files to their snapshotted state. Files that didn't exist
3089
+ * (content === null) are deleted. Files that existed are overwritten
3090
+ * with their original content.
3091
+ */
3092
+ declare function restoreSnapshot(storage: StorageAdapter, snapshots: readonly FileSnapshot[]): Promise<string[]>;
3093
+
3094
+ /**
3095
+ * Synthesize — combine research findings into an implementation spec.
3096
+ *
3097
+ * This is CODE, not an LLM call. It takes the research step results
3098
+ * for a given implementation step and produces a combined spec string
3099
+ * that the Implementer agent receives as its task.
3100
+ *
3101
+ * The synthesis is deliberately simple: concatenate research findings
3102
+ * with the step's description and any file paths. The Implementer
3103
+ * LLM does the actual reasoning — the synthesizer just assembles context.
3104
+ */
3105
+
3106
+ /**
3107
+ * Build an implementation spec from the step's description, prior
3108
+ * research results, and file list.
3109
+ */
3110
+ declare function synthesizeSpec(step: PlanStep, allResults: readonly StepResult[]): string;
3111
+
3112
+ /**
3113
+ * Agent tool — lets the parent assistant spawn a child agent.
3114
+ *
3115
+ * The Agent tool is a closure factory that captures the parent's
3116
+ * subsystems (storage, API client, registry, subagent registry,
3117
+ * config) at engine.run() time. When the parent calls it via the
3118
+ * usual tool dispatch mechanism, the closure runs `runAgent` to drive
3119
+ * a child loop and returns the child's final answer as the
3120
+ * tool_result content.
3121
+ *
3122
+ * Depth and uniqueness are enforced via `SubagentRegistry`. If the
3123
+ * spawn would exceed `maxSubagentDepth`, the tool returns an error
3124
+ * result instead of throwing — the parent assistant can read the
3125
+ * error and adapt.
3126
+ *
3127
+ * The factory's `parentAgentId` parameter is null at the top level
3128
+ * (root run) and set to the parent's agentId for nested invocations.
3129
+ */
3130
+
3131
+ /**
3132
+ * A subagent definition the parent can dispatch to via `Agent({ subagent_type })`.
3133
+ *
3134
+ * `systemPrompt` overrides the parent's system prompt when the child runs.
3135
+ * Leave it undefined to inherit the parent's prompt — which is what the
3136
+ * `'general-purpose'` builtin does.
3137
+ */
3138
+ interface AgentDefinition {
3139
+ readonly name: string;
3140
+ readonly description: string;
3141
+ readonly systemPrompt?: string;
3142
+ /**
3143
+ * When true, marks this agent as safe for parallel execution
3144
+ * alongside other readOnly agents. The Agent tool uses this via
3145
+ * `isConcurrencySafe` so the batch dispatcher can run multiple
3146
+ * research agents concurrently. Default: false.
3147
+ */
3148
+ readonly readOnly?: boolean;
3149
+ }
3150
+
3151
+ /**
3152
+ * Agents loader — discovers custom subagent definitions under a directory.
3153
+ *
3154
+ * Format (intentionally minimal, no YAML dependency):
3155
+ *
3156
+ * {customPath}/
3157
+ * ├── researcher.md
3158
+ * └── reviewer.md
3159
+ *
3160
+ * Each `.md` file defines one agent:
3161
+ *
3162
+ * # researcher
3163
+ *
3164
+ * One-line description. This is what the parent model sees in the Agent
3165
+ * tool's description catalogue.
3166
+ *
3167
+ * ---
3168
+ *
3169
+ * You are a research subagent. You have access to the web and filesystem...
3170
+ *
3171
+ * Parsing rules:
3172
+ *
3173
+ * - The **filename** (sans `.md`) is the agent's `name`. It must match
3174
+ * `^[a-zA-Z0-9_-]+$` or the file is skipped.
3175
+ * - The **first non-empty line** (after stripping leading `# `) is the
3176
+ * agent's `description`. One liner.
3177
+ * - The **rest of the file after the `---` separator** is the agent's
3178
+ * `systemPrompt`. If no `---` separator exists, the entire body after
3179
+ * the description is the system prompt. If there is no body at all,
3180
+ * `systemPrompt` is undefined and the agent inherits the parent's prompt.
3181
+ */
3182
+
3183
+ /**
3184
+ * Walk `baseDir` under the given storage adapter and return one
3185
+ * `AgentDefinition` per valid `.md` file. Returns an empty array when
3186
+ * the directory is missing or empty.
3187
+ */
3188
+ declare function loadAgents(storage: StorageAdapter, baseDir: string): Promise<AgentDefinition[]>;
3189
+
3190
+ /**
3191
+ * Coordinator mode — resolves config, builds the worker agent definition,
3192
+ * and determines the effective system prompt and tool restrictions.
3193
+ */
3194
+
3195
+ /**
3196
+ * Check whether the engine is running in coordinator mode.
3197
+ */
3198
+ declare function isCoordinatorMode(config: ResolvedConfig): boolean;
3199
+ /**
3200
+ * Build the worker agent definition. Workers get a restricted tool set
3201
+ * and a simple system prompt. The coordinator dispatches to them via
3202
+ * `Agent({ subagent_type: 'worker', ... })`.
3203
+ */
3204
+ declare function buildWorkerAgent(config: ResolvedCoordinatorConfig): AgentDefinition;
3205
+ /**
3206
+ * Get the coordinator's system prompt override. Used by the engine's
3207
+ * prompt builder when coordinator mode is active.
3208
+ */
3209
+ declare function getCoordinatorBasePrompt(): string;
3210
+
3211
+ /**
3212
+ * Coordinator system prompt — ported from La-Machina's coordinatorMode.ts.
3213
+ *
3214
+ * 370+ lines of behavioral instructions that transform the agent from
3215
+ * an implementer into an orchestrator. The prompt enforces:
3216
+ * - Research → Synthesis → Implementation → Verification phases
3217
+ * - Anti-lazy-delegation (must synthesize before directing workers)
3218
+ * - Parallel reads, serial writes
3219
+ * - Worker prompt quality standards
3220
+ *
3221
+ * Adapted for headless: no slash commands, no terminal UI, no interactive
3222
+ * approval dialogs. Tool names are parameterized.
3223
+ */
3224
+ declare function getCoordinatorSystemPrompt(): string;
3225
+
3226
+ interface McpClientOptions {
3227
+ readonly serverName: string;
3228
+ readonly config: ResolvedMcpServerConfig;
3229
+ readonly connectTimeoutMs: number;
3230
+ readonly callTimeoutMs: number;
3231
+ readonly logger: Logger;
3232
+ readonly transportOverride?: Transport;
3233
+ /**
3234
+ * Handler invoked when this server issues a `sampling/createMessage`
3235
+ * request. Only called when `config.allowSampling === true`. When
3236
+ * absent, sampling requests are refused with a protocol error.
3237
+ */
3238
+ readonly samplingHandler?: SamplingHandler;
3239
+ }
3240
+ declare class McpClient {
3241
+ readonly serverName: string;
3242
+ private readonly options;
3243
+ private sdkClient;
3244
+ private toolCache;
3245
+ private connected;
3246
+ /** PID of the child process (stdio only). Used for synchronous orphan kill. */
3247
+ private childPid;
3248
+ constructor(options: McpClientOptions);
3249
+ get isConnected(): boolean;
3250
+ /** PID of the spawned child process (stdio only). Null for HTTP/SSE. */
3251
+ get pid(): number | null;
3252
+ connect(): Promise<void>;
3253
+ listTools(): readonly McpToolDef[];
3254
+ callTool(toolName: string, input: unknown): Promise<McpCallResult>;
3255
+ close(): Promise<void>;
3256
+ /**
3257
+ * Synchronously kill the child process (stdio only). Used by
3258
+ * McpManager's process.on('exit') handler where async work is not
3259
+ * possible. No-op for HTTP/SSE transports.
3260
+ */
3261
+ killSync(): void;
3262
+ /**
3263
+ * Register the engine-side handler for MCP `sampling/createMessage`.
3264
+ * Runs user-supplied SamplingHandler, converts result to the MCP
3265
+ * `CreateMessageResult` shape.
3266
+ */
3267
+ private installSamplingHandler;
3268
+ private buildTransport;
3269
+ }
3270
+
3271
+ /**
3272
+ * BindingHttpTransport — plain-POST JSON-RPC MCP transport for Cloudflare
3273
+ * Workers.
3274
+ *
3275
+ * The upstream `StreamableHTTPClientTransport` opens a long-lived `fetch()`
3276
+ * with a streaming SSE response for server-push notifications. On the
3277
+ * Workers runtime that stream can hang unpredictably after `initialize`,
3278
+ * causing `tools/list` and `tools/call` to stall. This transport sidesteps
3279
+ * that entirely: every message is a short, stateless POST; the response
3280
+ * is parsed once and handed to `onmessage`. No persistent reader, no SSE.
3281
+ *
3282
+ * Trade-off: server-push notifications (logs, progress) don't work. That's
3283
+ * fine for the tool-calling use case — `tools/list` and `tools/call` are
3284
+ * request/response only.
3285
+ *
3286
+ * Implements just enough of the MCP transport contract for the engine's
3287
+ * `McpClient` to use. All pure JS — no `node:*` imports, Workers-safe.
3288
+ */
3289
+
3290
+ interface BindingHttpTransportOptions {
3291
+ /** Full URL of the MCP Streamable-HTTP endpoint (e.g. https://mcp.example.com/mcp). */
3292
+ readonly url: string;
3293
+ /** Extra headers sent on every POST (auth tokens, API keys). */
3294
+ readonly headers?: Readonly<Record<string, string>>;
3295
+ /**
3296
+ * Per-request dynamic header factory. When set, called before every
3297
+ * send; its result is merged OVER the static `headers`. On HTTP 401
3298
+ * the transport calls the factory again (invalidating any token
3299
+ * cache inside) and retries the request once.
3300
+ */
3301
+ readonly resolveHeaders?: () => Promise<Readonly<Record<string, string>>>;
3302
+ /** Override fetch (tests only). Defaults to globalThis.fetch. */
3303
+ readonly fetch?: typeof globalThis.fetch;
3304
+ /** Per-request timeout in milliseconds. Default: 30_000. */
3305
+ readonly timeoutMs?: number;
3306
+ }
3307
+ /**
3308
+ * MCP transport that speaks JSON-RPC 2.0 over bare POST. No persistent
3309
+ * connection. Not suitable for servers that rely on async notifications.
3310
+ */
3311
+ declare class BindingHttpTransport implements Transport {
3312
+ readonly url: string;
3313
+ private readonly headers;
3314
+ private readonly resolveHeaders;
3315
+ private readonly fetchImpl;
3316
+ private readonly timeoutMs;
3317
+ private started;
3318
+ private closed;
3319
+ sessionId?: string;
3320
+ onclose?: () => void;
3321
+ onerror?: (error: Error) => void;
3322
+ onmessage?: (message: JSONRPCMessage) => void;
3323
+ constructor(options: BindingHttpTransportOptions);
3324
+ start(): Promise<void>;
3325
+ send(message: JSONRPCMessage): Promise<void>;
3326
+ close(): Promise<void>;
3327
+ /**
3328
+ * Perform a single POST, applying current headers (static + dynamic).
3329
+ * Does not retry — the 401-refresh loop lives in `send()`.
3330
+ */
3331
+ private doFetch;
3332
+ }
3333
+
3334
+ interface McpInstructionDelta {
3335
+ readonly connected: string[];
3336
+ readonly disconnected: string[];
3337
+ }
3338
+ declare class McpManager {
3339
+ private readonly logger;
3340
+ private readonly servers;
3341
+ private readonly shutdownTimeoutMs;
3342
+ private exitHandler;
3343
+ /** Tracks which servers connected/disconnected since last delta retrieval. */
3344
+ private pendingConnected;
3345
+ private pendingDisconnected;
3346
+ constructor(config: ResolvedMcpConfig, logger: Logger, options?: {
3347
+ samplingHandler?: SamplingHandler;
3348
+ });
3349
+ get connectedCount(): number;
3350
+ /**
3351
+ * Lazily connect every configured server and return the full set of
3352
+ * tools. Detects dead connections and retries them. Per-server failures
3353
+ * are isolated.
3354
+ */
3355
+ getTools(): Promise<ReadonlyArray<Tool$1>>;
3356
+ /**
3357
+ * Retrieve and clear pending instruction deltas (connected/disconnected
3358
+ * servers since last call). Used by agentLoop to announce MCP changes
3359
+ * to the model. Ported from La-Machina's mcpInstructionsDelta.ts.
3360
+ */
3361
+ getInstructionDeltas(): McpInstructionDelta;
3362
+ /**
3363
+ * Graceful shutdown with a timeout. Attempts `client.close()` for
3364
+ * every active server within `SHUTDOWN_TIMEOUT_MS`. Servers that don't
3365
+ * respond in time are force-killed (SIGKILL for stdio, no-op for HTTP/SSE).
3366
+ *
3367
+ * Removes the process.on('exit') handler after cleanup.
3368
+ */
3369
+ shutdownAll(): Promise<void>;
3370
+ /**
3371
+ * Synchronously SIGKILL every known child PID. Used by the
3372
+ * process.on('exit') handler (where async is not possible) and by the
3373
+ * shutdown timeout fallback.
3374
+ */
3375
+ private killAllSync;
3376
+ private removeExitHandler;
3377
+ private connectServer;
3378
+ private doConnect;
3379
+ }
3380
+
3381
+ /**
3382
+ * MCP error taxonomy.
3383
+ *
3384
+ * Three error classes, all subclasses of `EngineError`:
3385
+ *
3386
+ * - `McpConnectionError` — a server failed to spawn, initialize, or list
3387
+ * tools. Swallowed by `McpManager`: logged as warn, server's tools are
3388
+ * absent from the registry, run proceeds.
3389
+ *
3390
+ * - `McpProtocolError` — a server returned a malformed JSON-RPC response
3391
+ * or an unexpected capability mismatch. Usually a server bug. Surfaces
3392
+ * in `tool_result` as `isError: true` when it happens during a call.
3393
+ *
3394
+ * - `McpTimeoutError` — a tool call exceeded `mcp.callTimeoutMs`.
3395
+ * Surfaces in `tool_result` as `isError: true` so the agent loop can
3396
+ * recover (retry, use a different tool, report back).
3397
+ *
3398
+ * The principle: MCP is best-effort. A run must never fail because one
3399
+ * of its MCP servers went wrong.
3400
+ */
3401
+
3402
+ declare class McpConnectionError extends EngineError {
3403
+ readonly serverName: string;
3404
+ constructor(serverName: string, cause: string);
3405
+ }
3406
+ declare class McpProtocolError extends EngineError {
3407
+ readonly serverName: string;
3408
+ constructor(serverName: string, cause: string);
3409
+ }
3410
+ declare class McpTimeoutError extends EngineError {
3411
+ readonly serverName: string;
3412
+ readonly toolName: string;
3413
+ readonly timeoutMs: number;
3414
+ constructor(serverName: string, toolName: string, timeoutMs: number);
3415
+ }
3416
+
3417
+ /**
3418
+ * adaptMcpTool — convert an `McpToolDef` into an engine `Tool`.
3419
+ *
3420
+ * The returned tool:
3421
+ *
3422
+ * - Has its name prefixed with `mcp__{serverName}__` so it cannot
3423
+ * collide with built-in or custom tools.
3424
+ * - Carries the server's description verbatim (the model sees this
3425
+ * as the tool's "what does it do" text).
3426
+ * - Uses `z.unknown()` as the engine-side input schema. MCP tools
3427
+ * author their validation in JSON Schema, not Zod, and the server
3428
+ * itself validates on receipt. Rather than run a lossy
3429
+ * JSON-Schema → Zod conversion at adapter time, we let the server
3430
+ * be the source of truth and pass input through unchanged.
3431
+ * - Sets `anthropicSchemaOverride` to the raw MCP JSON Schema — this
3432
+ * is what the engine ships to the Anthropic API so the model sees
3433
+ * a proper `input_schema` with typed parameters.
3434
+ * - Dispatches to `McpClient.callTool()`, turning `McpCallResult`
3435
+ * into the engine's `ToolResult` and catching any thrown MCP errors
3436
+ * into `{ isError: true }` results so the agent loop can recover.
3437
+ *
3438
+ * Why not use `z.any().passthrough()` instead of `z.unknown()`?
3439
+ * `z.unknown()` validates nothing (matches any value, including
3440
+ * `undefined`). `z.any()` is semantically equivalent but linters
3441
+ * frown on it. Either works; we pick `unknown` for stricter types.
3442
+ */
3443
+
3444
+ /** Build the engine-side tool name for an MCP tool. */
3445
+ declare function mcpToolName(serverName: string, toolName: string): string;
3446
+ declare function adaptMcpTool(client: McpClient, serverName: string, def: McpToolDef): Tool$1<z.ZodUnknown>;
3447
+
3448
+ /**
3449
+ * buildSystemPrompt — assemble the full system prompt from static
3450
+ * sections, dynamic per-run sections, and optional memory/skill recall.
3451
+ *
3452
+ * Section order (matches La-Machina's cacheable/dynamic split):
3453
+ *
3454
+ * ── STATIC (cacheable prefix — identical across runs) ──
3455
+ * 1. Base identity + capabilities
3456
+ * 2. Doing tasks (behavioral rules)
3457
+ * 3. Executing actions with care (reversibility, blast radius)
3458
+ * 4. Using your tools (tool-specific instructions)
3459
+ * 5. Tone and style + output efficiency
3460
+ *
3461
+ * ── DYNAMIC (changes per-run) ──
3462
+ * 6. Environment (OS, CWD, model, date)
3463
+ * 7. MCP tools (when config.mcp.servers is populated)
3464
+ * 8. Identity (smart memory profile.md)
3465
+ * 9. Rules (smart memory rules.md)
3466
+ * 10. Lessons (smart memory lessons.md, token-budgeted)
3467
+ * 11. Skills (loadSkills() output as name/description list)
3468
+ * 12. Custom base (caller-supplied override, appended last)
3469
+ *
3470
+ * The function is async because memory recall and skill discovery both
3471
+ * involve storage adapter reads. It's pure-ish — no writes, no side
3472
+ * effects beyond the reads themselves.
3473
+ */
3474
+
3475
+ interface BuildSystemPromptOptions {
3476
+ /** Custom base instruction appended to the system prompt. */
3477
+ readonly base?: string;
3478
+ /** SmartMemory façade — reads are gated by memory mode. */
3479
+ readonly memory: SmartMemory;
3480
+ /** Storage adapter pair — used to enumerate skills. */
3481
+ readonly storage: EngineStorage;
3482
+ /** Whether to scan and inject the skill index. */
3483
+ readonly skillsAutoload: boolean;
3484
+ /** Directory to scan for skills (relative to each scope's adapter root). */
3485
+ readonly skillsDir?: string;
3486
+ /**
3487
+ * Pre-resolved skill catalogue. When set, takes precedence over the
3488
+ * disk scan (`skillsAutoload` + `skillsDir`). Used by the engine when
3489
+ * `RunOptions.skills` supplies an inline list via `InlineSkillSource`.
3490
+ */
3491
+ readonly skillList?: ReadonlyArray<{
3492
+ name: string;
3493
+ description: string;
3494
+ }>;
3495
+ /** Token budget for the lessons section (default: 1000). */
3496
+ readonly lessonsTokenBudget?: number;
3497
+ /** Model ID for the environment section. */
3498
+ readonly modelId?: string;
3499
+ /** Provider name for the environment section. */
3500
+ readonly provider?: string;
3501
+ /** Working directory override for the environment section. */
3502
+ readonly cwd?: string;
3503
+ /** Registered tool names — drives tool-specific prompt instructions. */
3504
+ readonly registeredToolNames?: ReadonlySet<string>;
3505
+ /** MCP-sourced tools — listed in a dedicated prompt section. */
3506
+ readonly mcpTools?: ReadonlyArray<Tool$1>;
3507
+ /** When true, skip normal static sections (base/doingTasks/etc.) —
3508
+ * the coordinator prompt replaces them via `base`. */
3509
+ readonly coordinatorMode?: boolean;
3510
+ }
3511
+ declare function buildSystemPrompt(options: BuildSystemPromptOptions): Promise<string>;
3512
+
3513
+ /**
3514
+ * Safe tool allowlist — tools that are always safe to run without
3515
+ * human approval or rule evaluation.
3516
+ *
3517
+ * Ported from La-Machina's `SAFE_YOLO_ALLOWLISTED_TOOLS`. These tools
3518
+ * are read-only or purely informational — they cannot modify the
3519
+ * filesystem, send messages, or cause side effects beyond reading state.
3520
+ *
3521
+ * Used by `'locked'` mode: only these tools pass, everything else is
3522
+ * denied. Also used as a fast-path in `'rules'` mode: allowlisted tools
3523
+ * skip rule evaluation entirely.
3524
+ */
3525
+ declare const SAFE_TOOL_ALLOWLIST: ReadonlySet<string>;
3526
+ /**
3527
+ * Check if a tool name is on the safe allowlist.
3528
+ */
3529
+ declare function isSafeTool(toolName: string): boolean;
3530
+
3531
+ /**
3532
+ * Rule parser + matcher.
3533
+ *
3534
+ * Rule string format: `"action:pattern"`
3535
+ * - action: `allow` or `deny`
3536
+ * - pattern: exact tool name or glob with `*` wildcard
3537
+ *
3538
+ * Examples:
3539
+ * - `"allow:Read"` → allow the Read tool
3540
+ * - `"deny:Bash"` → deny the Bash tool
3541
+ * - `"allow:mcp__fs__*"` → allow all tools from the fs MCP server
3542
+ * - `"deny:mcp__*"` → deny all MCP tools
3543
+ * - `"deny:*"` → deny everything (catch-all)
3544
+ * - `"allow:*"` → allow everything (catch-all)
3545
+ *
3546
+ * Invalid rule strings are silently skipped (logged at warn level by
3547
+ * the evaluator).
3548
+ */
3549
+
3550
+ /**
3551
+ * Parse a rule string into a structured `PermissionRule`. Returns null
3552
+ * if the string is malformed.
3553
+ */
3554
+ declare function parseRule(raw: string): PermissionRule | null;
3555
+ /**
3556
+ * Parse an array of rule strings into structured rules, skipping
3557
+ * malformed entries.
3558
+ */
3559
+ declare function parseRules(raw: readonly string[]): readonly PermissionRule[];
3560
+ /**
3561
+ * Check if a tool name matches a rule's pattern. Uses picomatch for
3562
+ * glob support (same library the engine uses for Glob/Grep tools).
3563
+ */
3564
+ declare function matchesRule(toolName: string, rule: PermissionRule): boolean;
3565
+
3566
+ /**
3567
+ * la-machina-engine — public API.
3568
+ *
3569
+ * The single entry point is `initEngine()`. Every config option has a
3570
+ * default; `initEngine()` with no arguments must work, so long as either
3571
+ * `config.model.apiKey` is set OR `ANTHROPIC_API_KEY` is in the environment.
3572
+ *
3573
+ * See plans/002-la-machina-engine.md for the full spec.
3574
+ */
3575
+
3576
+ declare const ENGINE_VERSION = "0.0.0";
3577
+ /**
3578
+ * Initialize an Engine with (optional) user config merged over defaults.
3579
+ *
3580
+ * @param user Partial config. Every field is optional.
3581
+ * @returns A fully-configured Engine ready to `run()` (once Phase 7 lands).
3582
+ * @throws {ConfigError} If config validation fails or `apiKey` cannot be
3583
+ * resolved from either `config.model.apiKey` or `ANTHROPIC_API_KEY` env.
3584
+ */
3585
+ declare function initEngine(user?: UserConfig): Engine;
3586
+
3587
+ export { AISdkAdapter, type AgentDefinition, AnthropicAdapter, ApiError, AuthError, type BackgroundAgentResult, type BackgroundExecutor, BindingHttpTransport, type BuildSystemPromptOptions, ConfigError, DEFAULT_SAMPLING_MAX_DEPTH, ENGINE_VERSION, Engine, EngineError, type EngineInternals, type EngineResponse, type EngineStorage, type Engram, type EngramConfidence, type EngramKind, type EngramScope, type EngramSource, type Entry, type Episode, EpisodicMemory, type FileSnapshot, type FileStat, type FlushPolicy, type GateBeforeToolHook, type GateDecision$1 as GateDecision, Hippocampus, InlineSkillSource, type LoadedSkill, LocalStorageAdapter, type LogEntry, type LogLevel, type LogSink, type Logger, type LoopProgress, MAX_ATTEMPTS, type McpCallResult, McpClient, McpConnectionError, type McpInstructionDelta, McpManager, McpProtocolError, type McpServerState, McpTimeoutError, type McpToolDef, type MemoryMode, type MemoryScope, type ModelAdapter, type ModelMessage, type ModelProvider, type ModelToolDefinition, NodeBackgroundExecutor, type NormalizedEvent, NotImplementedError, type OrchestrateOptions, type OrchestratorResult, type PauseReason, type PermissionAction, type PermissionDecision, type PermissionMode, type PermissionPolicy, type PermissionRule, type Plan, PlanSchema, type PlanStep, PlanStepSchema, type PostRunEvent, type PostRunHook, type PostToolCallEvent$1 as PostToolCallEvent, type PostToolCallHook, type PostTurnEvent, type PostTurnHook, type PreRunEvent, type PreRunHook, type PreToolCallEvent$1 as PreToolCallEvent, type PreToolCallHook, type PreTurnEvent, type PreTurnHook, R2BindingStorageAdapter, type R2BucketBinding, type R2ClientConfig, R2StorageAdapter, RETRY_DELAYS_MS, RateLimitError, type ResolvedAgentsConfig, type ResolvedConfig, type ResolvedCoordinatorConfig, type ResolvedExecutionConfig, type ResolvedHooksConfig, type ResolvedLoggingConfig, type ResolvedMcpConfig, type ResolvedMcpHttpServerConfig, type ResolvedMcpServerConfig, type ResolvedMcpSseServerConfig, type ResolvedMcpStdioServerConfig, type ResolvedMemoryConfig, type ResolvedModelConfig, type ResolvedOrchestratorConfig, type ResolvedPermissionsConfig, type ResolvedR2Config, type ResolvedSkill, type ResolvedSkillsConfig, type ResolvedStorageConfig, type ResolvedToolsConfig, type ResolvedTranscriptConfig, type ResponseError, type ResponseMeta, type ResumeAsyncOptions, type ResumeOptions, type RetryPolicy, type RunOptions, type RunProgress, type RunResult, type RunSnapshot, type RunState, RunStateManager, type RunStatus, RunTimeoutError, type RuntimeKind, SAFE_TOOL_ALLOWLIST, type SamplingContext, type SamplingHandler, type SamplingMessage, type SamplingModelPreferences, type SamplingRequest, type SamplingResponse, type SamplingTextBlock, type SerializedAgent, type SkillOverride, type SkillPageOverride, type SkillSource, type SmartMemory, type SmartMemoryConfig, type SpawnResult, type StartOptions, type StepResult, type StepStatus, type StopHook, type StopHookEvent, type StopHookResult, type StorageAdapter, StorageError, type StorageProvider, StorageSkillSource, StreamIncompleteError, StreamParseError, type StreamRequest, StreamingToolExecutor, SubagentRegistry, type SubagentRegistryOptions, type TokenUsage, type Tool$1 as Tool, type ToolContext, ToolRegistry, type ToolResult, type TranscriptLocation, type TranscriptMeta, type TranscriptStatus, type UserConfig, type WaitForOptions, type WebhookConfig, type WebhookDelivery, WebhookDispatcher, type WebhookEvent, type WebhookOptions, adaptMcpTool, buildForkedMessages, buildPermissionPolicy, buildSchemaPrompt, buildSystemPrompt, buildWorkerAgent, canSpawnProcesses, createLogger, createModelAdapter, createSendMessageTool, createSkillPageTool, createSmartMemory, defaultSamplingHandler, defineTool, detectRuntime, getCoordinatorBasePrompt, getCoordinatorSystemPrompt, hasProcessLifecycle, initEngine, isCoordinatorMode, isInForkChild, isSafeTool, loadAgents, loadSkills, matchesRule, mcpToolName, orchestrate, parsePlan, parseRule, parseRules, partitionToolCalls, restoreSnapshot, runWithRetry, signPayload, snapshotFiles, synthesizeSpec, toResponse, tryParseJSON, validateOutput };