@themoltnet/pi-runtime 0.2.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.
Files changed (4) hide show
  1. package/LICENSE +235 -0
  2. package/dist/index.d.ts +1348 -0
  3. package/dist/index.js +35146 -0
  4. package/package.json +73 -0
@@ -0,0 +1,1348 @@
1
+ import { Agent } from '@themoltnet/sdk';
2
+ import { AgentSession } from '@earendil-works/pi-coding-agent';
3
+ import { Api } from '@earendil-works/pi-ai';
4
+ import { BashOperations } from '@earendil-works/pi-coding-agent';
5
+ import { ClaimedTask } from '@themoltnet/agent-runtime';
6
+ import { CommandAnalysis } from '@themoltnet/shell-command-analyzer';
7
+ import { connect } from '@themoltnet/sdk';
8
+ import { ContextRef } from '@themoltnet/agent-runtime';
9
+ import { EditOperations } from '@earendil-works/pi-coding-agent';
10
+ import { ExtensionAPI } from '@earendil-works/pi-coding-agent';
11
+ import { FindOperations } from '@earendil-works/pi-coding-agent';
12
+ import { GrepToolDetails } from '@earendil-works/pi-coding-agent';
13
+ import { GrepToolInput } from '@earendil-works/pi-coding-agent';
14
+ import { LoadSkillsResult } from '@earendil-works/pi-coding-agent';
15
+ import { LsOperations } from '@earendil-works/pi-coding-agent';
16
+ import { Model } from '@earendil-works/pi-ai';
17
+ import { Readable } from 'node:stream';
18
+ import { ReadOperations } from '@earendil-works/pi-coding-agent';
19
+ import { ShellCommandAnalyzer } from '@themoltnet/shell-command-analyzer';
20
+ import { Skill } from '@earendil-works/pi-coding-agent';
21
+ import { Static } from 'typebox';
22
+ import { SubagentContractRegistry } from '@themoltnet/agent-runtime';
23
+ import { TaskOutput } from '@themoltnet/agent-runtime';
24
+ import { TaskReporter } from '@themoltnet/agent-runtime';
25
+ import { TObject } from 'typebox';
26
+ import { ToolCallEvent } from '@earendil-works/pi-coding-agent';
27
+ import { ToolDefinition } from '@earendil-works/pi-coding-agent';
28
+ import { Type } from 'typebox';
29
+ import { VM } from '@earendil-works/gondolin';
30
+ import { WriteOperations } from '@earendil-works/pi-coding-agent';
31
+
32
+ /**
33
+ * Apply agent env vars to the host process, mirroring `moltnet start`.
34
+ * Resolves relative paths (e.g. GIT_CONFIG_GLOBAL) against the repo root.
35
+ */
36
+ export declare function activateAgentEnv(agentEnv: Record<string, string | undefined>, repoRoot: string): void;
37
+
38
+ /** Minimal shape of the SDK method the resolver needs (keeps deps testable). */
39
+ export declare interface AllowedToolsClient {
40
+ runtimeProfiles: {
41
+ allowedTools: (profileId: string, options: {
42
+ teamId: string;
43
+ }) => Promise<{
44
+ enforcement: ToolEnforcement;
45
+ allowedTools: string[];
46
+ }>;
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Construct an `AgentSession`. By default it is in-memory; callers may opt
52
+ * parent sessions into daemon-owned file persistence via `sessionPersistence`.
53
+ * The caller is responsible for eventually invoking `session.prompt(...)` and
54
+ * for tearing down — the helper does no lifecycle management beyond
55
+ * construction.
56
+ */
57
+ export declare function buildAgentSession(args: BuildAgentSessionArgs): Promise<AgentSession>;
58
+
59
+ declare interface BuildAgentSessionArgs {
60
+ /** Host directory mounted into the VM. */
61
+ mountPath: string;
62
+ /** Host working directory where the agent session should start. */
63
+ cwdPath: string;
64
+ /** pi auth directory (resolved from `PI_CODING_AGENT_DIR` or `~/.pi/agent`). */
65
+ piAuthDir: string;
66
+ /** Resolved pi model handle (provider + model id). */
67
+ modelHandle: Model<Api>;
68
+ /** Optional runtime-profile thinking/reasoning level applied at session start. */
69
+ thinkingLevel?: PiThinkingLevel | null;
70
+ /** Optional runtime-profile sampling temperature applied to provider requests. */
71
+ temperature?: number | null;
72
+ /** Optional runtime-profile nucleus sampling probability mass. */
73
+ topP?: number | null;
74
+ /** Optional runtime-profile top-k sampling cutoff. */
75
+ topK?: number | null;
76
+ /** Optional runtime-profile generated output token cap. */
77
+ maxOutputTokens?: number | null;
78
+ /** Pre-built customTools array. Caller composes Gondolin + MoltNet + submit tools. */
79
+ customTools: ToolDefinition[];
80
+ /**
81
+ * Exact model-visible tool allowlist. Omit for Pi's default behavior.
82
+ * Enforce-mode runtimes pass this so denied built-ins are not reconstructed
83
+ * behind the custom-tool overrides.
84
+ */
85
+ tools?: string[];
86
+ /** System-prompt fragments appended after pi's defaults. Parent passes the
87
+ * runtime instructor; subagents pass their narrower variant. */
88
+ appendSystemPrompt: string[];
89
+ /** Skills to advertise in `<available_skills>`. Default: empty list. */
90
+ skillsOverride?: () => LoadSkillsResult;
91
+ /** Span attributes merged onto every OTel span the session emits. */
92
+ otelSpanAttrs: Record<string, string | number | boolean>;
93
+ /** Agent name for `gen_ai.agent.name` on the root span. */
94
+ agentName: string;
95
+ /**
96
+ * Extra pi extension factories appended after the always-on telemetry (and
97
+ * model-options) extensions — e.g. the tool-policy `tool_call` gate. Each is a
98
+ * plain `(pi) => void` registrar, same shape as the OTel extension.
99
+ */
100
+ extraExtensionFactories?: ((pi: ExtensionAPI) => void)[];
101
+ /**
102
+ * Parent sessions may persist their conversation history in a daemon-owned
103
+ * directory. Subagents should leave this unset and stay in-memory.
104
+ */
105
+ sessionPersistence?: {
106
+ sessionDir: string;
107
+ forkFromSessionPath?: string | null;
108
+ };
109
+ }
110
+
111
+ export declare function buildPiExecutorManifest(input: {
112
+ runtime: PiRuntimeDefinition;
113
+ profile: {
114
+ id: string;
115
+ definitionCid: string;
116
+ };
117
+ template: ResolvedGondolinTemplate;
118
+ builtInTools?: readonly PiToolDescriptor[];
119
+ builtInToolNames?: readonly string[];
120
+ }): Promise<PiExecutorManifest>;
121
+
122
+ /**
123
+ * Build the minimal immutable system-prompt kernel. Runtime-profile context
124
+ * carries operator-selected workflow guidance; this kernel stays last in the
125
+ * system-prompt sequence so the daemon, not injected context, owns these
126
+ * rules.
127
+ */
128
+ export declare function buildRuntimeKernel(ctx: RuntimeInstructorContext): string;
129
+
130
+ export declare function buildWorkspaceMountInstructions(guestWorkspace: string): string;
131
+
132
+ export declare function createGondolinBashOps(vm: VM, localCwd: string, guestWorkspace: string): BashOperations;
133
+
134
+ export declare function createGondolinEditOps(vm: VM, localCwd: string, guestWorkspace: string): EditOperations;
135
+
136
+ export declare function createGondolinFindOps(vm: VM, localCwd: string, guestWorkspace: string): FindOperations;
137
+
138
+ export declare function createGondolinLsOps(vm: VM, localCwd: string, guestWorkspace: string): LsOperations;
139
+
140
+ export declare function createGondolinReadOps(vm: VM, localCwd: string, guestWorkspace: string): ReadOperations;
141
+
142
+ export declare function createGondolinToolDefinitions(config: {
143
+ vm: VM;
144
+ cwdPath: string;
145
+ guestWorkspace: string;
146
+ }): ToolDefinition[];
147
+
148
+ export declare function createGondolinWriteOps(vm: VM, localCwd: string, guestWorkspace: string): WriteOperations;
149
+
150
+ /**
151
+ * Create all MoltNet tool definitions, ready to pass to `pi.registerTool()`.
152
+ */
153
+ export declare function createMoltNetTools(config: MoltNetToolsConfig): ToolDefinition<any, any>[];
154
+
155
+ export declare function createPiOtelExtension(options?: PiOtelOptions): (pi: ExtensionAPI) => void;
156
+
157
+ export declare function createPiRetryTriage(options: {
158
+ model: Model<Api>;
159
+ thinkingLevel?: PiRetryTriageThinkingLevel | null;
160
+ piAgentDir: string;
161
+ timeoutMs?: number;
162
+ cwd?: string;
163
+ }): PiRetryTriage;
164
+
165
+ /**
166
+ * Factory that builds a pi-specific `executeTask` function suitable for
167
+ * injection into `AgentRuntime`. The returned function caches the resolved
168
+ * checkpoint across tasks so the second task hits the snapshot cache.
169
+ */
170
+ export declare function createPiTaskExecutor(opts: ExecutePiTaskOptions): (claimedTask: ClaimedTask, reporter: TaskReporter) => Promise<TaskOutput>;
171
+
172
+ /**
173
+ * Build the subagent custom tool for a parent session. The handle
174
+ * exposes the call counter so executors can emit summary telemetry
175
+ * when the parent terminates.
176
+ */
177
+ export declare function createSubagentTool(args: CreateSubagentToolArgs): SubagentToolHandle;
178
+
179
+ export declare interface CreateSubagentToolArgs {
180
+ /** Host directory mounted into the VM. */
181
+ mountPath: string;
182
+ /** Host working directory the subagent should start in. Defaults to mountPath. */
183
+ cwdPath?: string;
184
+ /** pi auth directory the parent resolved. */
185
+ piAuthDir: string;
186
+ /** Resolved pi model handle — subagents share it. */
187
+ modelHandle: Model<Api>;
188
+ /** Runtime-profile thinking/reasoning level — subagents inherit it. */
189
+ thinkingLevel?: PiThinkingLevel | null;
190
+ /** Runtime-profile sampling temperature — subagents inherit it. */
191
+ temperature?: number | null;
192
+ /** Runtime-profile nucleus sampling probability mass — subagents inherit it. */
193
+ topP?: number | null;
194
+ /** Runtime-profile top-k sampling cutoff — subagents inherit it. */
195
+ topK?: number | null;
196
+ /** Runtime-profile generated output token cap — subagents inherit it. */
197
+ maxOutputTokens?: number | null;
198
+ /** Agent name for telemetry. */
199
+ agentName: string;
200
+ /**
201
+ * Custom tools every subagent inherits (Gondolin-routed
202
+ * built-ins + moltnet_* tools, etc). MUST NOT include
203
+ * the parent's submit-output tool, the parent's `subagent` tool,
204
+ * or any other parent-only artefact — the caller is responsible
205
+ * for filtering. The subagent appends its own submit tool.
206
+ */
207
+ inheritedCustomTools: ToolDefinition[];
208
+ /** Exact enforce-mode tool allowlist inherited from the parent session. */
209
+ tools?: string[];
210
+ /**
211
+ * The parent runtime instructor verbatim. Subagents prepend it to
212
+ * their own short "you are a subagent" preamble so the same
213
+ * invariants (gh auth, diary discipline, accountable commits)
214
+ * apply if the subagent takes those actions. The parent's task
215
+ * description dictates whether they should.
216
+ */
217
+ parentRuntimeInstructor: string;
218
+ parentTaskId: string;
219
+ parentTaskType: string;
220
+ parentAttemptN: number;
221
+ /**
222
+ * Parent task's cancel signal. When the daemon cancels the parent
223
+ * task (operator cancel or task-level `runningTimeoutSec` expiry),
224
+ * each in-flight subagent's inner `session.abort()` is invoked so
225
+ * it tears down promptly instead of running until its own LLM
226
+ * call resolves. Mirrors the existing `wireSessionAbort` pattern
227
+ * the parent session uses.
228
+ *
229
+ * Optional only because the test seam can omit it; production
230
+ * callers (executePiTask) pass `reporter.cancelSignal`.
231
+ */
232
+ parentCancelSignal?: AbortSignal;
233
+ /**
234
+ * Per-call fallback timeout. Defends against an inner session that
235
+ * ignores `abort()` for any reason (LLM provider stuck, tool call
236
+ * hanging on I/O, etc.). When the timeout fires, `session.abort()`
237
+ * is invoked and the tool returns `isError: true` with a
238
+ * `subagent_timed_out` reason the parent LLM can recover from.
239
+ *
240
+ * Default: 5 minutes. Set to `0` to disable (relying purely on
241
+ * parentCancelSignal). Negative values are treated as the default.
242
+ */
243
+ timeoutMs?: number;
244
+ /**
245
+ * Test seam. Production callers leave this undefined and get
246
+ * `buildAgentSession` from the factory module. Tests inject a mock
247
+ * that returns a stub session implementing only `prompt()` to
248
+ * exercise the tool's logic without booting a VM.
249
+ */
250
+ buildAgentSession?: (args: BuildAgentSessionArgs) => Promise<AgentSession>;
251
+ /**
252
+ * Contract registry for resolving output_schema names to TypeBox
253
+ * schemas at call time. The subagent tool reads ONLY via `.get()`
254
+ * and `.list()` — the registry is immutable after construction.
255
+ *
256
+ * Production callers (executePiTask) create the registry with
257
+ * built-in contracts at session-setup; tests inject a registry
258
+ * with whatever stubs they need.
259
+ */
260
+ contractRegistry: SubagentContractRegistry;
261
+ /**
262
+ * Extra pi extension factories every subagent session registers — chiefly the
263
+ * tool-policy `tool_call` gate. A subagent runs in its own `AgentSession`, so
264
+ * without re-registering the gate here it would execute tools un-checked,
265
+ * escaping the parent's enforcement (#1348 B2). Empty/undefined when tool
266
+ * enforcement is `off`, so subagents then match the parent's un-gated
267
+ * behaviour. The factories are read-only closures, safe to share across the
268
+ * parent and every subagent session.
269
+ */
270
+ extraExtensionFactories?: ((pi: ExtensionAPI) => void)[];
271
+ }
272
+
273
+ /**
274
+ * A pi extension factory that gates every `tool_call` against the resolved
275
+ * policy. Blocks in `enforce`, audits (and allows) in `watch`, and is a no-op in
276
+ * `off`. Register it in a session's `extensionFactories`.
277
+ */
278
+ export declare function createToolPolicyExtension(deps: ToolPolicyExtensionDeps): (pi: ExtensionAPI) => void;
279
+
280
+ /**
281
+ * Map a pi `tool_call` event to a gate decision, extracting the shell command
282
+ * for `bash` and delegating to {@link decideToolCall}.
283
+ */
284
+ export declare function decideForEvent(event: ToolCallEvent, policy: SessionToolPolicy, analyze: ShellCommandAnalyzer['analyze']): GateDecision;
285
+
286
+ /**
287
+ * Decide whether a tool call is permitted by the resolved policy.
288
+ *
289
+ * Fail-closed in `enforce` (audited-but-allowed in `watch`, no-op in `off`) for:
290
+ *
291
+ * 1. **Unresolvable commands** — a `bash` command whose executables cannot be
292
+ * statically resolved (command substitution, `eval`, non-literal command
293
+ * names, unparseable input).
294
+ * 2. **Arbitrary-code interpreters** — a `bash` command that invokes a shell or
295
+ * language interpreter (`bash -c`, `python`, `node`, `perl`, …; the
296
+ * analyzer's `arbitrary-code` risk tier). Being name-listed is NOT enough:
297
+ * we cannot statically see the code such an interpreter runs, so the policy's
298
+ * allow-set can't bound it. This is the interim conservative stance for
299
+ * issue #1348 — an operator who lists `bash` still cannot smuggle
300
+ * `bash -c "curl … | sh"` past `enforce`.
301
+ * 3. **Unlisted executables** — any resolved executable not in `allowedTools`.
302
+ *
303
+ * KNOWN LIMITATION (follow-up): the `escapable` risk tier (GTFOBins binaries
304
+ * like `find`, `tar`, `awk` that document shell-spawn / file-write techniques)
305
+ * is NOT blocked on the tier alone. The analyzer already re-analyzes the
306
+ * sub-commands it can see through documented escape flags (`find -exec`,
307
+ * `tar --to-command`, …), but techniques it cannot parse statically could still
308
+ * escape a name-based allow-set. Tightening `escapable` (e.g. an LLM judge or a
309
+ * capability-aware allow-set) is tracked as future work.
310
+ */
311
+ export declare function decideToolCall(input: GateInput): GateDecision;
312
+
313
+ export declare function defineGondolinTemplate(options: DefineGondolinTemplateOptions): GondolinTemplateDefinition;
314
+
315
+ export declare interface DefineGondolinTemplateOptions {
316
+ id: string;
317
+ version: string;
318
+ snapshot?: SnapshotConfig;
319
+ checkpointPath?: string;
320
+ resolveCheckpoint?(context: GondolinTemplateResolveContext): Promise<string>;
321
+ fingerprint?: string;
322
+ executables?: readonly string[];
323
+ resumeCommands?: readonly ResumeCommand[];
324
+ }
325
+
326
+ export declare function definePiExtension(options: PiExtensionOptions): PiExtensionContribution;
327
+
328
+ export declare function definePiRuntime(options: DefinePiRuntimeOptions): PiRuntimeDefinition;
329
+
330
+ export declare interface DefinePiRuntimeOptions {
331
+ id: string;
332
+ version: string;
333
+ runtimeKind?: string;
334
+ vm: GondolinTemplateDefinition;
335
+ tools?: readonly PiToolContribution[];
336
+ extensions?: readonly PiExtensionContribution[];
337
+ }
338
+
339
+ export declare function definePiTool(tool: ToolDefinition, options?: {
340
+ scope?: PiToolScope;
341
+ }): PiToolContribution;
342
+
343
+ export declare function definePiTool(options: PiToolFactoryOptions): PiToolContribution;
344
+
345
+ export declare function enabledPiToolNames(input: {
346
+ tools: readonly ToolDefinition[];
347
+ extensions?: readonly PiExtensionContribution[];
348
+ policy?: {
349
+ enforcement: ToolEnforcement;
350
+ allowedTools: ReadonlySet<string>;
351
+ };
352
+ }): string[] | undefined;
353
+
354
+ /**
355
+ * Ensure a cached snapshot exists, building one if needed.
356
+ * Returns the absolute path to the qcow2 checkpoint file.
357
+ */
358
+ export declare function ensureSnapshot(options?: EnsureSnapshotOptions): Promise<string>;
359
+
360
+ export declare interface EnsureSnapshotOptions {
361
+ config?: SnapshotConfig;
362
+ onProgress?: (message: string) => void;
363
+ /** Max number of old snapshots to keep (default 1). */
364
+ maxCached?: number;
365
+ }
366
+
367
+ export declare function executeGondolinGrep(vm: VM, localCwd: string, guestWorkspace: string, params: GrepToolInput, signal?: AbortSignal): Promise<TextToolResult<GrepToolDetails>>;
368
+
369
+ /**
370
+ * Run one attempt of `task` in a freshly-resumed Gondolin VM. Owns the full
371
+ * lifecycle: resume VM → wire tools → pi session → close VM. Always returns
372
+ * a `TaskOutput` (failures surface as `status: 'failed'`); throws only on
373
+ * unrecoverable setup errors.
374
+ */
375
+ export declare function executePiTask(claimedTask: ClaimedTask, reporter: TaskReporter, opts: ExecutePiTaskOptions): Promise<TaskOutput>;
376
+
377
+ export declare interface ExecutePiTaskOptions {
378
+ /** MoltNet agent whose credentials the VM boots with. */
379
+ agentName: string;
380
+ /**
381
+ * Host root that owns `.moltnet/<agentName>/`.
382
+ *
383
+ * Defaults to `mountPath`, but callers that mount scratch workspaces should
384
+ * pass the stable sandbox root.
385
+ */
386
+ agentRootDir?: string;
387
+ /** Host cwd that the VM mounts into the guest (defaults to `process.cwd()`). */
388
+ mountPath?: string;
389
+ /** LLM selection. */
390
+ provider: string;
391
+ model: string;
392
+ /**
393
+ * Runtime-profile reasoning/thinking level. Null/undefined means use Pi's
394
+ * configured default; explicit `off` disables provider thinking where
395
+ * supported.
396
+ */
397
+ thinkingLevel?: PiThinkingLevel | null;
398
+ /** Optional sampling temperature. Null/undefined means provider default. */
399
+ temperature?: number | null;
400
+ /** Optional nucleus-sampling probability mass. Null/undefined means provider default. */
401
+ topP?: number | null;
402
+ /** Optional top-k sampling cutoff. Null/undefined means provider default. */
403
+ topK?: number | null;
404
+ /** Optional cap on generated output tokens. Null/undefined means provider/model default. */
405
+ maxOutputTokens?: number | null;
406
+ /** Extra hosts to allow in the sandbox egress policy. */
407
+ extraAllowedHosts?: string[];
408
+ /** Sandbox overrides (env, VFS shadows, resources). */
409
+ sandboxConfig?: SandboxConfig;
410
+ /** Host environment variable names to forward into the Pi VM. */
411
+ forwardEnv?: string[];
412
+ /**
413
+ * Runtime profile context defaults. Merged with task.input.context at
414
+ * execution time because the selected runtime profile is known only after
415
+ * claim. Task entries override profile entries with the same slug.
416
+ */
417
+ runtimeProfileContext?: readonly ContextRef[];
418
+ /**
419
+ * Runtime profile id, used to resolve the tool-policy allow-set at session
420
+ * start. Required together with a non-`off` `toolEnforcement` for the
421
+ * `tool_call` gate to run.
422
+ */
423
+ runtimeProfileId?: string;
424
+ /** Tool-policy enforcement mode for the selected runtime profile. */
425
+ toolEnforcement?: ToolEnforcement;
426
+ /**
427
+ * Forwarded to `buildTaskUserPrompt` for per-type builders. Static
428
+ * across tasks. Today no built-in builder needs per-task `extras` —
429
+ * judges fetch their own dependent data via MoltNet tools
430
+ * (`moltnet_get_task`, `moltnet_list_task_attempts`, etc.) at run
431
+ * time, which keeps this layer task-type-agnostic. Field is kept
432
+ * for forward compat with custom prompt builders that might want it.
433
+ */
434
+ promptExtras?: Record<string, unknown>;
435
+ /** Snapshot progress callback; defaults to stderr logging. */
436
+ onSnapshotProgress?: (message: string) => void;
437
+ /**
438
+ * Optional pre-resolved checkpoint path. If omitted, `ensureSnapshot` is
439
+ * invoked. Useful for batch execution where the caller wants to cache
440
+ * across tasks.
441
+ */
442
+ checkpointPath?: string;
443
+ /**
444
+ * Lazy checkpoint resolver used by `createPiTaskExecutor` so snapshot
445
+ * creation can happen after the reporter has been opened and can surface
446
+ * setup failures as task messages.
447
+ */
448
+ resolveCheckpointPath?: () => Promise<string>;
449
+ /**
450
+ * Set when the caller already opened the reporter before handing control
451
+ * to `executePiTask`.
452
+ */
453
+ reporterAlreadyOpened?: boolean;
454
+ /**
455
+ * Optional callback invoked alongside every `reporter.record()` so
456
+ * the daemon can mirror task messages into its local logger.
457
+ * Bound at executor-construction time — use when one task runs per
458
+ * process (e.g. `once.ts`) and per-task context is known before
459
+ * the executor is built. For poll mode, prefer `makeOnTurnEvent`
460
+ * below. If both are set, `makeOnTurnEvent` wins.
461
+ * See `TurnEventHandler` for payload shape. Defaults to a no-op.
462
+ */
463
+ onTurnEvent?: TurnEventHandler;
464
+ /**
465
+ * Per-task factory variant for `onTurnEvent`. Invoked once per
466
+ * task with the claimed task before any emit, so the returned
467
+ * handler can bind taskId / attemptN into a pino child.
468
+ * Use in poll mode where N tasks run sequentially in the same
469
+ * process. See #1078.
470
+ */
471
+ makeOnTurnEvent?: TurnEventHandlerFactory;
472
+ /**
473
+ * Cap the number of tool-use turns per attempt. When the limit is
474
+ * reached, the pi session is aborted and the attempt finalizes with
475
+ * `error.code: max_turns_exceeded`. A tool-use turn = any `turn_end`
476
+ * whose `stopReason !== 'end_turn'` (matches the Anthropic SDK
477
+ * `max_turns` semantics: the model's final text-only response doesn't
478
+ * count). Default `0` = disabled. Recommended `30` for `fulfill_brief`.
479
+ * Closes part of #1094.
480
+ */
481
+ maxTurns?: number;
482
+ /**
483
+ * Cap the number of `bash` tool timeouts per attempt. A timeout is a
484
+ * `tool_execution_end` for `bash` whose result text contains
485
+ * "Command timed out after" (pi's stable error wrapper from
486
+ * `@earendil-works/pi-coding-agent`'s bash tool). When the limit is
487
+ * reached, the pi session is aborted and the attempt finalizes with
488
+ * `error.code: max_bash_timeouts_exceeded`. Catches the death-spiral
489
+ * pattern from task `a3762f44` where the model kept retrying
490
+ * long-blocking shell commands until the host job timeout fired.
491
+ * Default `3`. Set to `0` to disable. Closes part of #1094.
492
+ */
493
+ maxBashTimeouts?: number;
494
+ /**
495
+ * Number of correction turns allowed after the first invalid submit-output
496
+ * tool call. A value of 2 permits three invalid submit calls total before
497
+ * the attempt fails with output_validation_failed.
498
+ */
499
+ maxSubmitValidationRetries?: number;
500
+ /**
501
+ * Number of same-session re-prompts when the model ends its turn WITHOUT
502
+ * calling the submit-output tool at all (no captured payload and no
503
+ * exhausted validation budget). Distinct from
504
+ * `maxSubmitValidationRetries`, which recovers *invalid-args* submit calls.
505
+ * Each re-prompt names the submit tool and forbids a prose reply. When the
506
+ * budget is spent the attempt still fails with `submit_output_missing`.
507
+ * Only applies to task types that register a submit tool. Default `3`. Set
508
+ * to `0` to disable. See #1528.
509
+ */
510
+ maxSubmitMissingReprompts?: number;
511
+ /**
512
+ * Continuation prompt sent when a turn ends without a submit call. Defaults
513
+ * to `buildSubmitMissingPrompt(<tool name>)`.
514
+ */
515
+ submitMissingPrompt?: string;
516
+ /**
517
+ * Cap provider-error retries inside the same Pi session. A retry is attempted
518
+ * only after a Pi assistant turn ends with `stopReason: "error"` and the
519
+ * provider diagnostic is not a known credential/model/config failure. This is
520
+ * distinct from daemon attempt retry: the active session keeps its context and
521
+ * receives a short continuation prompt.
522
+ *
523
+ * Default `4`. Set to `0` to disable.
524
+ */
525
+ maxProviderErrorRetries?: number;
526
+ /** Base delay for same-session provider-error retries. Default `2000`. */
527
+ providerErrorRetryBaseDelayMs?: number;
528
+ /** Maximum delay for same-session provider-error retries. Default `30000`. */
529
+ providerErrorRetryMaxDelayMs?: number;
530
+ /** Continuation prompt sent after a retryable provider error. Default `Go on`. */
531
+ providerErrorRetryPrompt?: string;
532
+ /**
533
+ * Optional UI adapter for interactive pi/TUI callers. The daemon normally
534
+ * leaves this unset and consumes the structured `provider_error_retry` task
535
+ * message instead.
536
+ */
537
+ providerErrorRetryUi?: ProviderErrorRetryUi;
538
+ /**
539
+ * Skip per-call UI approval for matching `moltnet_host_exec` commands.
540
+ * Keep false/undefined for interactive consumers. `true` skips every dialog
541
+ * after HOST_EXEC_ALLOWED; an array limits auto-approval to matching rules.
542
+ */
543
+ hostExecAutoApprove?: HostExecAutoApproveConfig;
544
+ /**
545
+ * Optional daemon-supplied execution plan. Keeps task semantics out of
546
+ * `pi-extension` while still letting callers opt into stable worktrees and
547
+ * file-backed Pi sessions for selected task classes.
548
+ */
549
+ makeExecutionPlan?: PiTaskExecutionPlanFactory;
550
+ /**
551
+ * Immutable subagent contract registry used to resolve `output_schema`
552
+ * names at subagent tool call time. Constructed by the daemon (or
553
+ * tests) from static built-in schemas — `execute-pi-task` never hardcodes
554
+ * contracts. See #1106.
555
+ */
556
+ subagentContractRegistry?: SubagentContractRegistry;
557
+ /**
558
+ * Structured logger for tool-policy resolution/gate events. The daemon passes
559
+ * its task-bound pino child so these lines carry taskId/attemptN and join the
560
+ * run's NDJSON stream. When omitted, tool-policy events fall back to raw
561
+ * NDJSON on stderr (single-process / test callers). See #1348.
562
+ */
563
+ toolPolicyLogger?: ToolPolicyLogger;
564
+ /** Trusted, statically imported operator runtime contributions. */
565
+ runtimeDefinition?: PiRuntimeDefinition;
566
+ /**
567
+ * Pre-resolved local VM template. Daemons resolve this before polling so
568
+ * profile requirements and executor attestation can be checked before claim.
569
+ */
570
+ resolvedVmTemplate?: ResolvedGondolinTemplate;
571
+ /** Internal/lower-level lazy template resolver used by executor factories. */
572
+ resolveVmTemplate?: () => Promise<ResolvedGondolinTemplate>;
573
+ }
574
+
575
+ export declare function filterModelVisibleTools(tools: readonly ToolDefinition[], policy?: {
576
+ enforcement: ToolEnforcement;
577
+ allowedTools: ReadonlySet<string>;
578
+ }): ToolDefinition[];
579
+
580
+ /**
581
+ * Resolve the main worktree root (where .moltnet/ lives — it's untracked,
582
+ * only exists in the main worktree, not in git worktrees).
583
+ */
584
+ export declare function findMainWorktree(startPath?: string): string;
585
+
586
+ /**
587
+ * The gate's verdict:
588
+ * - `{ allow: true }` — let the tool run.
589
+ * - `{ allow: false, reason }` — block it (enforce mode).
590
+ * - `{ audit, ... }` — would-block, but proceed and record it (watch mode).
591
+ */
592
+ export declare type GateDecision = {
593
+ allow: true;
594
+ } | {
595
+ allow: false;
596
+ reason: string;
597
+ } | {
598
+ audit: string;
599
+ missing?: string[];
600
+ };
601
+
602
+ export declare interface GateInput {
603
+ /** Pi tool name (e.g. 'bash', 'read', 'write', or a custom tool id). */
604
+ toolName: string;
605
+ /** The shell command, when `toolName === 'bash'`. */
606
+ command?: string;
607
+ enforcement: ToolEnforcement;
608
+ /** Names the policy allows (structured tool names + shell executable names). */
609
+ allowedTools: ReadonlySet<string>;
610
+ /**
611
+ * Synchronous shell analyzer (`ShellCommandAnalyzer.analyze`). Injected so the
612
+ * decision stays pure and testable; the analyzer's async WASM init happens
613
+ * once at session start.
614
+ */
615
+ analyze: (command: string) => CommandAnalysis;
616
+ }
617
+
618
+ export declare const GONDOLIN_TOOL_NAMES: readonly ["read", "write", "edit", "bash", "ls", "find", "grep"];
619
+
620
+ export declare interface GondolinTemplateDefinition {
621
+ readonly kind: 'gondolin';
622
+ readonly id: string;
623
+ readonly version: string;
624
+ readonly executables: readonly string[];
625
+ readonly resumeCommands: readonly ResumeCommand[];
626
+ resolve(context?: GondolinTemplateResolveContext): Promise<ResolvedGondolinTemplate>;
627
+ }
628
+
629
+ export declare interface GondolinTemplateResolveContext {
630
+ onProgress?: (message: string) => void;
631
+ }
632
+
633
+ /**
634
+ * Baseline env keys forwarded to host-exec child processes.
635
+ * Callers can extend this set at sandbox startup via `MoltNetToolsConfig.hostExecBaseEnv`.
636
+ */
637
+ export declare const HOST_EXEC_DEFAULT_BASE_ENV: ReadonlySet<string>;
638
+
639
+ declare type HostExecAutoApproveConfig = boolean | readonly HostExecAutoApproveRule[];
640
+
641
+ declare interface HostExecAutoApproveRule {
642
+ /** Exact executable name. Must still pass HOST_EXEC_ALLOWED. */
643
+ executable: string;
644
+ /** Optional ordered argument prefix; flags after the prefix are allowed. */
645
+ argsPrefix?: readonly string[];
646
+ /** Optional unordered argument tokens that must appear somewhere. */
647
+ argsContains?: readonly string[];
648
+ /** Optional argument tokens that prevent auto-approval when present. */
649
+ argsExcludes?: readonly string[];
650
+ }
651
+
652
+ export declare interface InjectedTaskContext {
653
+ /** Refs that were delivered, in declared order, for audit. */
654
+ injected: ContextRef[];
655
+ /** Synthetic Skill objects to splice into pi's skillsOverride. */
656
+ skills: Skill[];
657
+ /** Prepend this to `appendSystemPrompt`. Empty when nothing
658
+ * contributed (omit the array entry rather than pass an empty
659
+ * string to keep pi's prompt assembly tidy). */
660
+ systemPromptPrefix: string;
661
+ /** Append this to the task user prompt BEFORE `session.prompt()`. */
662
+ userInlineSuffix: string;
663
+ }
664
+
665
+ /**
666
+ * Resolve effective runtime context and inject the side effects Pi
667
+ * needs. Safe to call with an empty array — returns an inert result.
668
+ */
669
+ export declare function injectTaskContext(args: InjectTaskContextArgs): Promise<InjectedTaskContext>;
670
+
671
+ export declare interface InjectTaskContextArgs {
672
+ /** Empty array (the default for any non-eval task) is a no-op. */
673
+ context: readonly ContextRef[];
674
+ /** Guest filesystem handle. In production this is `managed.vm.fs`. */
675
+ fs: VmFsForContext;
676
+ /** Guest path where the active host workspace is mounted. */
677
+ guestWorkspace: string;
678
+ }
679
+
680
+ export declare function isKernelTool(name: string): boolean;
681
+
682
+ export declare function isToolVisible(name: string, policy?: {
683
+ enforcement: ToolEnforcement;
684
+ allowedTools: ReadonlySet<string>;
685
+ }): boolean;
686
+
687
+ export declare function loadCredentials(agentDir: string): VmCredentials;
688
+
689
+ export declare interface ManagedVm {
690
+ vm: VM;
691
+ credentials: VmCredentials;
692
+ mountPath: string;
693
+ guestWorkspace: string;
694
+ agentDir: string;
695
+ }
696
+
697
+ export declare function materializePiExtensions(input: {
698
+ runtime: PiRuntimeDefinition;
699
+ context: PiToolContext;
700
+ target: 'parent' | 'subagent';
701
+ policy?: {
702
+ enforcement: ToolEnforcement;
703
+ allowedTools: ReadonlySet<string>;
704
+ };
705
+ }): Promise<PiExtensionFactory[]>;
706
+
707
+ export declare function materializePiTools(input: {
708
+ runtime: PiRuntimeDefinition;
709
+ context: PiToolContext;
710
+ target: 'parent' | 'subagent';
711
+ policy?: {
712
+ enforcement: ToolEnforcement;
713
+ allowedTools: ReadonlySet<string>;
714
+ };
715
+ }): Promise<ToolDefinition[]>;
716
+
717
+ export declare const MOLTNET_TOOL_NAMES: readonly ["moltnet_pack_get", "moltnet_pack_create", "moltnet_pack_provenance", "moltnet_pack_render", "moltnet_rendered_pack_list", "moltnet_rendered_pack_get", "moltnet_diary_tags", "moltnet_list_entries", "moltnet_get_entry", "moltnet_search_entries", "moltnet_create_entry", "moltnet_get_task", "moltnet_list_task_attempts", "moltnet_list_task_messages", "moltnet_upload_task_artifact", "moltnet_list_task_artifacts", "moltnet_download_task_artifact", "moltnet_review_session_errors", "moltnet_host_exec"];
718
+
719
+ declare type MoltNetAgent = Awaited<ReturnType<typeof connect>>;
720
+
721
+ /**
722
+ * Active-task context. When present, `moltnet_create_entry` is forced to
723
+ * land entries in `diaryId` (the task diary), regardless of the env-derived
724
+ * diary, and auto-injects provenance tags under the `task:*` namespace
725
+ * (`task:id:<id>`, `task:type:<type>`, `task:attempt:<n>`, and
726
+ * `task:correlation:<id>` when the task carries one). See issue #979 +
727
+ * the #986 follow-up that introduced the namespace.
728
+ */
729
+ declare interface MoltNetTaskContext {
730
+ taskId: string;
731
+ taskType: string;
732
+ attemptN: number;
733
+ diaryId: string;
734
+ /**
735
+ * Optional correlation id. When set, propagated as a
736
+ * `task:correlation:<id>` provenance tag so all entries from a
737
+ * multi-task workflow can be grouped without enumerating individual
738
+ * task ids.
739
+ */
740
+ correlationId: string | null;
741
+ }
742
+
743
+ export declare interface MoltNetToolsConfig {
744
+ getAgent(): MoltNetAgent | null;
745
+ getDiaryId(): string | null;
746
+ getTeamId(): string | null;
747
+ getSessionErrors(): readonly TrackedError[];
748
+ clearSessionErrors(): void;
749
+ /** Host working directory for host-exec commands (worktree path or cwd). */
750
+ getHostCwd?(): string;
751
+ /**
752
+ * Optional workspace-file reader. Daemon/Gondolin callers provide this so
753
+ * artifact uploads see guest overlay writes that may not exist on the host
754
+ * mount path yet.
755
+ */
756
+ openWorkspaceFileForRead?(filePath: string): Promise<{
757
+ stream: Readable;
758
+ isFile: boolean;
759
+ sizeBytes?: number;
760
+ displayPath?: string;
761
+ }>;
762
+ /**
763
+ * Set of process.env keys that are safe to forward to host-exec child
764
+ * processes. Configured at sandbox startup so the caller can include
765
+ * agent-specific vars (e.g. MOLTNET_AGENT_NAME) alongside the defaults.
766
+ * Defaults to HOST_EXEC_DEFAULT_BASE_ENV when omitted.
767
+ */
768
+ hostExecBaseEnv?: ReadonlySet<string>;
769
+ /**
770
+ * When true, `moltnet_host_exec` skips the per-call UI approval dialog.
771
+ * Intended for non-interactive daemon automation only; interactive
772
+ * consumers should keep the default false behavior.
773
+ */
774
+ autoApproveHostExec?: boolean;
775
+ /**
776
+ * Host-exec auto-approval policy. `true` skips all dialogs after the
777
+ * executable allowlist check. An array skips only commands matching one of
778
+ * the supplied executable/argument rules. Omitted/false preserves the
779
+ * interactive approval flow.
780
+ */
781
+ hostExecAutoApprove?: HostExecAutoApproveConfig;
782
+ /**
783
+ * Active-task context, populated by the agent-daemon path. When set,
784
+ * `moltnet_create_entry` enforces `diaryId === taskContext.diaryId` and
785
+ * injects provenance tags. When absent (interactive pi-extension / TUI),
786
+ * entry creation behaves as before (env-derived diary, no auto-tags).
787
+ */
788
+ getTaskContext?(): MoltNetTaskContext | null;
789
+ }
790
+
791
+ export declare function normalizeRetryTriageResult(value: unknown): PiRetryTriageResult;
792
+
793
+ export declare const PI_EXECUTOR_MANIFEST_VERSION: "moltnet:executor-manifest:v1";
794
+
795
+ export declare const PI_RUNTIME_DEFINITION_VERSION = "moltnet:pi-runtime:v1";
796
+
797
+ export declare interface PiExecutorManifest {
798
+ schemaVersion: typeof PI_EXECUTOR_MANIFEST_VERSION;
799
+ runtime: {
800
+ kind: string;
801
+ engine: 'pi';
802
+ sandbox: 'gondolin';
803
+ id: string;
804
+ version: string;
805
+ };
806
+ profile: {
807
+ id: string;
808
+ definitionCid: string;
809
+ };
810
+ vm: {
811
+ templateId: string;
812
+ templateVersion: string;
813
+ templateFingerprint: string;
814
+ guestAssetBuildId: string;
815
+ };
816
+ tools: {
817
+ name: string;
818
+ descriptorCid: string | null;
819
+ scope: PiToolScope;
820
+ }[];
821
+ extensions: {
822
+ id: string;
823
+ declaredTools: readonly string[];
824
+ scope: PiToolScope;
825
+ }[];
826
+ executables: readonly string[];
827
+ }
828
+
829
+ export declare interface PiExtensionContribution {
830
+ readonly kind: 'extension';
831
+ readonly id: string;
832
+ readonly declaredTools: readonly string[];
833
+ readonly scope: PiToolScope;
834
+ create: (context: PiToolContext) => PiExtensionFactory | Promise<PiExtensionFactory>;
835
+ }
836
+
837
+ export declare type PiExtensionFactory = (pi: ExtensionAPI) => void;
838
+
839
+ export declare interface PiExtensionOptions {
840
+ id: string;
841
+ declaredTools?: readonly string[];
842
+ scope?: PiToolScope;
843
+ factory?: PiExtensionFactory;
844
+ create?: (context: PiToolContext) => PiExtensionFactory | Promise<PiExtensionFactory>;
845
+ }
846
+
847
+ export declare interface PiOtelOptions {
848
+ /** Agent name for `gen_ai.agent.name` on the root span. */
849
+ agentName?: string;
850
+ /**
851
+ * Extra attributes merged onto every span. Use MoltNet-specific keys
852
+ * like `moltnet.task.id` — any `gen_ai.*` keys here are filtered out
853
+ * since the extension is authoritative for those.
854
+ */
855
+ spanAttributes?: Record<string, string | number | boolean>;
856
+ }
857
+
858
+ export declare type PiRetryTriage = (input: PiRetryTriageInput) => Promise<PiRetryTriageResult>;
859
+
860
+ export declare type PiRetryTriageConfidence = RetryTriageConfidence;
861
+
862
+ export declare type PiRetryTriageDecision = RetryTriageDecision;
863
+
864
+ export declare interface PiRetryTriageInput {
865
+ task: {
866
+ id: string;
867
+ taskType: string;
868
+ teamId: string;
869
+ input: unknown;
870
+ };
871
+ attemptN: number;
872
+ maxAttempts?: number | null;
873
+ remainingAttempts?: number | null;
874
+ error: unknown;
875
+ recentMessages?: {
876
+ timestamp: string;
877
+ kind: string;
878
+ payload: unknown;
879
+ }[];
880
+ }
881
+
882
+ export declare interface PiRetryTriageResult {
883
+ decision: RetryTriageDecision;
884
+ confidence: RetryTriageConfidence;
885
+ reason: string;
886
+ }
887
+
888
+ export declare type PiRetryTriageThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
889
+
890
+ export declare interface PiRuntimeDefinition {
891
+ readonly schemaVersion: typeof PI_RUNTIME_DEFINITION_VERSION;
892
+ readonly id: string;
893
+ readonly version: string;
894
+ readonly runtimeKind: string;
895
+ readonly vm: GondolinTemplateDefinition;
896
+ readonly tools: readonly PiToolContribution[];
897
+ readonly extensions: readonly PiExtensionContribution[];
898
+ }
899
+
900
+ export declare interface PiSessionPersistencePlan {
901
+ sessionDir: string;
902
+ forkFromSessionPath?: string | null;
903
+ }
904
+
905
+ export declare interface PiTaskExecutionPlan {
906
+ /**
907
+ * Effective workspace mode for this task instance.
908
+ * `scratch_mount` means mount an empty scratch directory rather than the
909
+ * daemon checkout.
910
+ */
911
+ workspaceMode: 'shared_mount' | 'dedicated_worktree' | 'scratch_mount';
912
+ /**
913
+ * Daemon-local reuse key. When set alongside `workspaceScope: 'session'`,
914
+ * dedicated worktrees may be retained and reopened across related tasks.
915
+ */
916
+ sessionKey: string | null;
917
+ /**
918
+ * Workspace identity selected by the daemon. `null` means the task should
919
+ * run against the shared mount path.
920
+ */
921
+ workspaceId: string | null;
922
+ /**
923
+ * Branch to create or reopen for the workspace. `null` means no dedicated
924
+ * worktree is required.
925
+ */
926
+ worktreeBranch: string | null;
927
+ /**
928
+ * Base ref a NEW `worktreeBranch` is cut from. Used by `fork` continuations
929
+ * to branch from the parent's tip instead of the default (main/HEAD). Ignored
930
+ * when `worktreeBranch` already exists.
931
+ */
932
+ worktreeBaseRef?: string | null;
933
+ /**
934
+ * Lifetime of the task workspace from the daemon's point of view.
935
+ * `attempt` = disposable; `session` = keep stable for the reuse key.
936
+ */
937
+ workspaceScope: 'attempt' | 'session';
938
+ /**
939
+ * Optional existing workspace root to attach instead of creating a fresh
940
+ * shared/worktree/scratch workspace. Used for read-only-ish producer
941
+ * inspection by applying VFS shadowing on top of the mounted path.
942
+ */
943
+ workspaceAttachment?: PiWorkspaceAttachmentPlan | null;
944
+ /**
945
+ * Optional seed content for a freshly created scratch workspace.
946
+ */
947
+ workspaceSeed?: PiWorkspaceSeedPlan | null;
948
+ /**
949
+ * Optional location for file-backed Pi session history. When omitted,
950
+ * the executor keeps the conversation in memory for this attempt only.
951
+ */
952
+ sessionPersistence?: PiSessionPersistencePlan | null;
953
+ }
954
+
955
+ export declare type PiTaskExecutionPlanFactory = (claimedTask: ClaimedTask) => Promise<PiTaskExecutionPlan | null> | PiTaskExecutionPlan | null;
956
+
957
+ declare type PiThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
958
+
959
+ export declare interface PiToolContext {
960
+ agent: Agent;
961
+ claimedTask: ClaimedTask;
962
+ reporter: TaskReporter;
963
+ vm: VM;
964
+ cwdPath: string;
965
+ guestWorkspace: string;
966
+ }
967
+
968
+ export declare interface PiToolContribution {
969
+ readonly kind: 'tool';
970
+ readonly descriptor: PiToolDescriptor;
971
+ readonly scope: PiToolScope;
972
+ create: (context: PiToolContext) => ToolDefinition | Promise<ToolDefinition>;
973
+ }
974
+
975
+ export declare type PiToolDescriptor = Pick<ToolDefinition, 'name' | 'label' | 'description' | 'parameters'>;
976
+
977
+ export declare interface PiToolFactoryOptions {
978
+ descriptor: PiToolDescriptor;
979
+ scope?: PiToolScope;
980
+ create: (context: PiToolContext) => ToolDefinition | Promise<ToolDefinition>;
981
+ }
982
+
983
+ export declare type PiToolScope = 'parent' | 'parent_and_subagents';
984
+
985
+ declare interface PiWorkspaceAttachmentPlan {
986
+ mountPath: string;
987
+ cwdPath: string;
988
+ shadowWrites?: 'deny' | 'tmpfs';
989
+ }
990
+
991
+ declare interface PiWorkspaceSeedPlan {
992
+ copyFromPath: string;
993
+ source: 'producer';
994
+ }
995
+
996
+ export declare interface ProviderErrorRetryEvent extends Record<string, unknown> {
997
+ event: 'provider_error_retry';
998
+ retry: number;
999
+ maxRetries: number;
1000
+ delayMs: number;
1001
+ reason: string;
1002
+ }
1003
+
1004
+ export declare type ProviderErrorRetryLevel = 'info' | 'warning' | 'error';
1005
+
1006
+ export declare interface ProviderErrorRetryUi {
1007
+ /**
1008
+ * Mirrors pi's `ctx.hasUI`. Undefined means "UI adapter is present"; false
1009
+ * lets callers pass a stable adapter from both TUI and headless contexts.
1010
+ */
1011
+ hasUI?: boolean;
1012
+ setStatus?: (key: string, message: string) => void | Promise<void>;
1013
+ notify?: (message: string, level: ProviderErrorRetryLevel) => void | Promise<void>;
1014
+ }
1015
+
1016
+ export declare function redactRetryTriageSecrets(value: string): string;
1017
+
1018
+ export declare interface ResolvedGondolinTemplate {
1019
+ id: string;
1020
+ version: string;
1021
+ checkpointPath: string;
1022
+ fingerprint: string;
1023
+ guestAssetBuildId: string;
1024
+ executables: readonly string[];
1025
+ resumeCommands: readonly ResumeCommand[];
1026
+ }
1027
+
1028
+ /**
1029
+ * Resolve the session's tool policy at start-up.
1030
+ *
1031
+ * `off` short-circuits without a network call. Otherwise the allowed-tool set
1032
+ * is fetched from the API. If that fetch fails, the mode decides the fallback:
1033
+ * `enforce` **fails closed** (empty allow-set → every non-`off` tool is
1034
+ * blocked); `watch` fails open (empty allow-set → every tool is audited but
1035
+ * allowed).
1036
+ *
1037
+ * The result is a **session-start snapshot**: it is resolved once and cached for
1038
+ * the session's lifetime. Policy edits made while a task is running do not take
1039
+ * effect until the next session — a deliberate trade-off (one resolution per
1040
+ * session, stable enforcement for the run) accepted over re-fetching per call.
1041
+ */
1042
+ export declare function resolveSessionToolPolicy(input: ResolveSessionToolPolicyInput): Promise<SessionToolPolicy>;
1043
+
1044
+ declare interface ResolveSessionToolPolicyInput {
1045
+ agent: AllowedToolsClient;
1046
+ profileId: string;
1047
+ teamId: string;
1048
+ /**
1049
+ * The profile's enforcement mode, already known to the daemon from the
1050
+ * resolved runtime profile. Used to decide fail-open vs fail-closed when the
1051
+ * allowed-tools fetch fails.
1052
+ */
1053
+ enforcement: ToolEnforcement;
1054
+ logger: ToolPolicyLogger;
1055
+ /**
1056
+ * Deadline for the allowed-tools fetch. A hung API call must not stall session
1057
+ * start-up indefinitely, so on timeout we abort and fall back to the
1058
+ * mode-appropriate degraded policy. Defaults to
1059
+ * {@link DEFAULT_RESOLVE_TIMEOUT_MS}; `0`/negative disables the deadline.
1060
+ */
1061
+ timeoutMs?: number;
1062
+ }
1063
+
1064
+ export declare function resolveTaskWorktreePath(mainRepo: string, workspaceId: string): string;
1065
+
1066
+ export declare interface ResumeCommand {
1067
+ /** Shell command, same semantics as the string form. */
1068
+ run: string;
1069
+ /** Optional generic runtime predicate for whether this step should run. */
1070
+ when?: ResumeCommandWhen;
1071
+ /** Additional attempts on non-zero exit. Default 0. */
1072
+ retries?: number;
1073
+ /** Linear backoff between attempts in ms. Delay before attempt N+1 is
1074
+ * `(N + 1) * retryBackoffMs` (so 2s, 4s, … with the default). */
1075
+ retryBackoffMs?: number;
1076
+ }
1077
+
1078
+ /** Structured form of a resume command with optional retry policy. */
1079
+ declare interface ResumeCommandWhen {
1080
+ /**
1081
+ * Effective workspace mode(s) that should run this command.
1082
+ * Evaluated by the runtime from the mounted workspace shape rather than
1083
+ * from task type semantics.
1084
+ */
1085
+ workspaceMode?: ('shared_mount' | 'dedicated_worktree' | 'scratch_mount')[];
1086
+ }
1087
+
1088
+ /**
1089
+ * Resume a VM from a checkpoint, inject credentials, configure egress +
1090
+ * TLS. Returns the managed VM handle.
1091
+ */
1092
+ export declare function resumeVm(config: VmConfig): Promise<ManagedVm>;
1093
+
1094
+ export declare type RetryTriageConfidence = 'low' | 'medium' | 'high';
1095
+
1096
+ export declare type RetryTriageDecision = 'retry' | 'do_not_retry';
1097
+
1098
+ declare interface RuntimeInstructorContext {
1099
+ taskId: string;
1100
+ taskType: string;
1101
+ attemptN: number;
1102
+ diaryId: string;
1103
+ agentName: string;
1104
+ guestWorkspace: string;
1105
+ /** Optional correlation id grouping this task with others. */
1106
+ correlationId: string | null;
1107
+ }
1108
+
1109
+ export declare interface SandboxConfig {
1110
+ /**
1111
+ * Operator-owned snapshot build settings. Runtime profiles must never
1112
+ * populate this field.
1113
+ */
1114
+ snapshot?: {
1115
+ /** Shell commands to run after the base setup. */
1116
+ setupCommands?: string[];
1117
+ /** Additional hosts to allow network access during build. */
1118
+ allowedHosts?: string[];
1119
+ /** Overlay disk size (default '3G'). */
1120
+ overlaySize?: string;
1121
+ };
1122
+ /** Runtime network egress policy. Separate from snapshot build access. */
1123
+ network?: {
1124
+ /** Additional host patterns allowed while the VM is running.
1125
+ * Internal and private address resolution remains blocked. */
1126
+ allowedHosts?: string[];
1127
+ /** Host patterns explicitly allowed to resolve to internal/private IPs. */
1128
+ allowedInternalHosts?: string[];
1129
+ };
1130
+ /** Operator-owned shell commands to run every VM resume, after platform setup
1131
+ * (TLS, DNS, git safe.directory, tmpfs node_modules) and before
1132
+ * the agent session starts. Use for per-session bootstrap that
1133
+ * doesn't belong baked into the snapshot.
1134
+ *
1135
+ * Not included in the snapshot cache key — changes here apply on
1136
+ * every resume without triggering a snapshot rebuild. Each command
1137
+ * runs in a fresh shell with `set -eu` and `set -o pipefail`; a
1138
+ * non-zero exit (including from any segment of a pipeline) aborts
1139
+ * resume with the failing command's stderr/stdout tail.
1140
+ *
1141
+ * Each entry is either a raw string (no retries) or an object
1142
+ * `{ run, when?, retries?, retryBackoffMs? }`. `when` gates the
1143
+ * command on generic runtime properties such as effective
1144
+ * `workspaceMode`; this keeps sandbox policy decoupled from task
1145
+ * types. `retries` is the number of ADDITIONAL attempts after the
1146
+ * first failure (default 0 = no retry). Use for steps that hit the
1147
+ * network and may legitimately race DHCP/registry availability on a
1148
+ * fresh resume (e.g. `pnpm install`). The wrapped command must be
1149
+ * idempotent. */
1150
+ resumeCommands?: (string | ResumeCommand)[];
1151
+ /** VFS shadow settings — hide host paths from the guest. */
1152
+ vfs?: {
1153
+ /** Paths (relative to workspace root) to shadow from the host mount. */
1154
+ shadow?: string[];
1155
+ /** What to do with writes to shadowed paths: 'deny' or 'tmpfs' (default 'tmpfs'). */
1156
+ shadowMode?: 'deny' | 'tmpfs';
1157
+ };
1158
+ /** Environment variable overrides for the guest VM (applied on top of defaults). */
1159
+ env?: Record<string, string>;
1160
+ /** Host-side escape hatch policy. Applies only to `moltnet_host_exec`. */
1161
+ hostExec?: {
1162
+ /**
1163
+ * `true` auto-approves every allowed executable. An array auto-approves
1164
+ * only commands matching one of the executable/argument rules.
1165
+ */
1166
+ autoApprove?: boolean | {
1167
+ executable: string;
1168
+ argsPrefix?: string[];
1169
+ argsContains?: string[];
1170
+ argsExcludes?: string[];
1171
+ }[];
1172
+ };
1173
+ /** VM resource allocation. */
1174
+ resources?: {
1175
+ /** Memory size in qemu syntax (default '1G'). */
1176
+ memory?: string;
1177
+ /** CPU count (default 2). */
1178
+ cpus?: number;
1179
+ };
1180
+ }
1181
+
1182
+ /** The resolved allow-set + enforcement mode for a runtime session. */
1183
+ export declare interface SessionToolPolicy {
1184
+ enforcement: ToolEnforcement;
1185
+ allowedTools: ReadonlySet<string>;
1186
+ /**
1187
+ * `true` when the allow-set is a **degraded fallback** — the allowed-tools
1188
+ * fetch failed or timed out and this policy is the fail-closed/fail-open
1189
+ * default, NOT the operator's actual configuration. An intentional
1190
+ * empty-but-resolved policy (e.g. a profile with no bound tools) has
1191
+ * `degraded: false`. Surfaced in every audit/block log so an operator can tell
1192
+ * "blocked because the policy is empty" from "blocked because we couldn't read
1193
+ * the policy". `off` and successful resolutions are never degraded.
1194
+ */
1195
+ degraded: boolean;
1196
+ }
1197
+
1198
+ /** Extract snapshot-specific config for backwards compat with ensureSnapshot. */
1199
+ export declare type SnapshotConfig = NonNullable<SandboxConfig['snapshot']>;
1200
+
1201
+ export declare interface SubagentToolHandle {
1202
+ /** ToolDefinition to register via `customTools` on the parent session. */
1203
+ readonly tool: ToolDefinition;
1204
+ /** How many times the parent LLM has called this tool. */
1205
+ getCallCount: () => number;
1206
+ }
1207
+
1208
+ /**
1209
+ * Parameters shape the parent LLM sees when calling the subagent tool.
1210
+ *
1211
+ * - `task` — natural-language instructions for the subagent.
1212
+ * The parent authors this per call. Must be
1213
+ * non-empty.
1214
+ * - `output_schema` — name of a registered SubagentOutputContract.
1215
+ * Resolved at call time; unknown names error.
1216
+ */
1217
+ export declare const SubagentToolParameters: TObject<{
1218
+ task: Type.TString;
1219
+ output_schema: Type.TString;
1220
+ }>;
1221
+
1222
+ export declare type SubagentToolParameters = Static<typeof SubagentToolParameters>;
1223
+
1224
+ declare type TextToolResult<TDetails> = {
1225
+ content: Array<{
1226
+ type: 'text';
1227
+ text: string;
1228
+ }>;
1229
+ details: TDetails | undefined;
1230
+ };
1231
+
1232
+ /**
1233
+ * Map a host-side absolute path to a guest-side workspace path.
1234
+ * Throws if the path escapes the workspace.
1235
+ */
1236
+ export declare function toGuestPath(localCwd: string, localPath: string, guestWorkspace: string): string;
1237
+
1238
+ export declare type ToolEnforcement = Static<typeof ToolEnforcementSchema>;
1239
+
1240
+ declare const ToolEnforcementSchema = Type.Union(toolEnforcementLiterals, {
1241
+ description:
1242
+ 'Runtime tool-policy enforcement mode: off (inert), watch (audit only), enforce (block disallowed tools, fail-closed).',
1243
+ });
1244
+
1245
+ export declare interface ToolPolicyExtensionDeps {
1246
+ policy: SessionToolPolicy;
1247
+ analyzer: ShellCommandAnalyzer;
1248
+ logger: ToolPolicyLogger;
1249
+ }
1250
+
1251
+ /**
1252
+ * Structured logger the resolver and gate emit to. Deliberately the pino
1253
+ * `(obj, msg)` shape so the daemon can pass a task-bound pino child directly —
1254
+ * every tool-policy line then carries the daemon's taskId/attemptN context and
1255
+ * lands in the same NDJSON stream as the rest of the run.
1256
+ */
1257
+ declare interface ToolPolicyLogger {
1258
+ debug: (obj: Record<string, unknown>, msg: string) => void;
1259
+ info: (obj: Record<string, unknown>, msg: string) => void;
1260
+ warn: (obj: Record<string, unknown>, msg: string) => void;
1261
+ }
1262
+
1263
+ export declare interface TrackedError {
1264
+ toolName: string;
1265
+ toolCallId: string;
1266
+ input: Record<string, unknown>;
1267
+ error: string;
1268
+ timestamp: number;
1269
+ }
1270
+
1271
+ export declare interface TurnEventHandler {
1272
+ (event: TurnEventKind, summary: Record<string, unknown>): void;
1273
+ }
1274
+
1275
+ export declare type TurnEventHandlerFactory = (claimedTask: ClaimedTask) => TurnEventHandler;
1276
+
1277
+ export declare type TurnEventKind = Parameters<TaskReporter['record']>[0]['kind'];
1278
+
1279
+ export declare interface VmConfig {
1280
+ /** Absolute path to the qcow2 checkpoint. */
1281
+ checkpointPath: string;
1282
+ /** MoltNet agent name (used to resolve credentials). */
1283
+ agentName: string;
1284
+ /**
1285
+ * Host root that owns `.moltnet/<agentName>/`.
1286
+ *
1287
+ * Defaults to the main git worktree for backwards compatibility. Daemon
1288
+ * callers pass the sandbox root so non-git scratch/shared tasks can boot.
1289
+ */
1290
+ agentRootDir?: string;
1291
+ /** Host directory to mount into the VM. */
1292
+ mountPath: string;
1293
+ /** Effective workspace shape selected by the caller. */
1294
+ workspaceMode?: 'shared_mount' | 'dedicated_worktree' | 'scratch_mount';
1295
+ /** Additional hosts to allow in egress policy. */
1296
+ extraAllowedHosts?: string[];
1297
+ /** Full sandbox config (vfs shadows, env overrides). */
1298
+ sandboxConfig?: SandboxConfig;
1299
+ /**
1300
+ * Host environment variable names to copy into the VM process.
1301
+ *
1302
+ * Runtime profiles use this for provider API keys: `requiredEnv` proves the
1303
+ * daemon host has the secret, and this allowlist forwards only those names
1304
+ * into the guest without storing secret values in the profile.
1305
+ */
1306
+ forwardEnv?: string[];
1307
+ /** Abort resume/setup work, closing any live VM owned by resumeVm. */
1308
+ signal?: AbortSignal;
1309
+ }
1310
+
1311
+ export declare interface VmCredentials {
1312
+ moltnetJson: string;
1313
+ agentEnvRaw: string;
1314
+ /**
1315
+ * Pi OAuth/API-key auth blob. Null when neither `~/.pi/agent/auth.json`
1316
+ * (resolved via `PI_CODING_AGENT_DIR` when set) is present — in that
1317
+ * case the daemon relies on Pi's env-var providers (`ANTHROPIC_API_KEY`,
1318
+ * etc.) carried via `agentEnv` and the host environment instead. CI uses
1319
+ * this path.
1320
+ */
1321
+ piAuthJson: string | null;
1322
+ agentEnv: Record<string, string | undefined>;
1323
+ gitconfig: string | null;
1324
+ sshPrivateKey: string | null;
1325
+ sshPublicKey: string | null;
1326
+ allowedSigners: string | null;
1327
+ /** Raw PEM content of the GitHub App private key, or null if not configured. */
1328
+ githubAppPem: string | null;
1329
+ /** VM-local filename for the GitHub App PEM (basename of host path), or null. */
1330
+ githubAppPemFilename: string | null;
1331
+ }
1332
+
1333
+ /**
1334
+ * Subset of `@earendil-works/gondolin`'s `VmFs` we actually use. We
1335
+ * narrow the dependency surface so unit tests can hand in a
1336
+ * vitest-mocked object without instantiating a real VM. We use `any`
1337
+ * for the options parameter to make this interface bivariantly
1338
+ * compatible with `VmFs` (whose options types differ between
1339
+ * `mkdir` and `writeFile`); the orchestrator only ever calls these
1340
+ * methods with the documented option shape, so the looseness is
1341
+ * confined to this seam.
1342
+ */
1343
+ export declare interface VmFsForContext {
1344
+ mkdir: (dirPath: string, options?: any) => Promise<void>;
1345
+ writeFile: (filePath: string, data: string | Uint8Array, options?: any) => Promise<void>;
1346
+ }
1347
+
1348
+ export { }