@themoltnet/pi-runtime 0.10.0 → 0.11.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 (3) hide show
  1. package/dist/index.d.ts +159 -221
  2. package/dist/index.js +270 -2047
  3. package/package.json +7 -5
package/dist/index.js CHANGED
@@ -1,45 +1,23 @@
1
1
  import { execFileSync } from "node:child_process";
2
- import { cpSync, createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
2
+ import { cpSync, createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync } from "node:fs";
3
3
  import { mkdir, realpath, stat } from "node:fs/promises";
4
4
  import path, { isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { pipeline } from "node:stream/promises";
6
6
  import { Type } from "@earendil-works/pi-ai";
7
7
  import { AuthStorage, DEFAULT_MAX_BYTES, DefaultResourceLoader, ModelRegistry, SessionManager, createAgentSession, createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createSyntheticSourceInfo, createWriteToolDefinition, defineTool, formatSize, parseFrontmatter, truncateHead, truncateLine } from "@earendil-works/pi-coding-agent";
8
+ import { BrokeredHttpSecretBoundaryError, GONDOLIN_BASE_EXECUTABLES, GUEST_TASK_CONTEXT_MOUNT, GuestEnvironmentBoundaryError, activateAgentEnv, activateAgentEnv as activateAgentEnv$1, assertGuestEnvironmentBoundary, assertHostAuthenticatedGuestEnvironment, canonicalizeBrokeredHttpSecretDescriptor, ensureSnapshot, ensureSnapshot as ensureSnapshot$1, findMainWorktree, findMainWorktree as findMainWorktree$1, isResolvedPathInsideRoot, isResolvedPathInsideRoot as isResolvedPathInsideRoot$1, loadCredentials, prepareBrokeredHttpSecrets, resolveVfsShadowConfig, resumeVm as resumeVm$1 } from "@themoltnet/sandbox-gondolin";
8
9
  import { SpanStatusCode, context, metrics, trace } from "@opentelemetry/api";
9
10
  import { CID } from "multiformats/cid";
10
- import "multiformats/codecs/raw";
11
- import "multiformats/hashes/digest";
12
- import { sha256 } from "multiformats/hashes/sha2";
13
- import "@noble/hashes/sha2";
14
- import "multiformats/bases/base32";
15
- import "@noble/curves/ed25519.js";
16
- import * as ed from "@noble/ed25519";
17
- import { createHash } from "crypto";
18
- import { createHash as createHash$1 } from "node:crypto";
19
11
  import * as json from "multiformats/codecs/json";
20
- import "@ipld/dag-cbor";
21
- import { FREEFORM_TYPE, SUBMIT_OUTPUT_GATE_ID, TaskContext, buildTaskUserPrompt, getSubmitOutputContract, materializeTaskOutput, mergeRuntimeProfileContext, resolveTaskContext, taskTypeUsesSubagents, traceRuntimePhase, validateTaskOutput, validateTaskSubmission } from "@themoltnet/agent-runtime";
12
+ import { sha256 } from "multiformats/hashes/sha2";
13
+ import { FREEFORM_TYPE, SUBMIT_OUTPUT_GATE_ID, buildTaskUserPrompt, getSubmitOutputContract, materializeTaskOutput, mergeRuntimeProfileContext, resolveTaskContext, taskTypeUsesSubagents, traceRuntimePhase, validateTaskOutput, validateTaskSubmission } from "@themoltnet/agent-runtime";
22
14
  import { connect } from "@themoltnet/sdk/node";
23
15
  import { ShellCommandAnalyzer } from "@themoltnet/shell-command-analyzer";
24
16
  import { homedir } from "node:os";
25
- import { MemoryProvider, RealFSProvider, ShadowProvider, VM, VmCheckpoint, createHttpHooks, createShadowPathPredicate, ensureImageSelector, isWriteFlag, loadGuestAssets } from "@earendil-works/gondolin";
17
+ import { VmCheckpoint } from "@earendil-works/gondolin";
26
18
  import { Type as Type$1 } from "typebox";
19
+ import { createHash } from "node:crypto";
27
20
  import { Value } from "typebox/value";
28
- import { parseEnv } from "node:util";
29
- //#region src/path-containment.ts
30
- /**
31
- * Check containment for already-resolved lexical or real paths.
32
- *
33
- * Callers that accept untrusted paths must resolve/realpath at their I/O
34
- * boundary first; keeping the platform-specific relative-path rule here avoids
35
- * subtly different `..` and absolute-path handling across runtime cleanup,
36
- * session sync, and artifact staging.
37
- */
38
- function isResolvedPathInsideRoot(path, root) {
39
- const rel = relative(root, path);
40
- return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
41
- }
42
- //#endregion
43
21
  //#region src/moltnet/render-phase6.ts
44
22
  function slugToTitle(value) {
45
23
  return value.split(/[:/_-]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" ");
@@ -276,7 +254,7 @@ async function findExistingAncestor(candidate) {
276
254
  }
277
255
  }
278
256
  function assertPathInsideWorkspace(realCwd, realPath, displayPath) {
279
- if (!isResolvedPathInsideRoot(realPath, realCwd)) throw new Error(`task artifact output path escapes workspace: ${displayPath}`);
257
+ if (!isResolvedPathInsideRoot$1(realPath, realCwd)) throw new Error(`task artifact output path escapes workspace: ${displayPath}`);
280
258
  }
281
259
  /**
282
260
  * Expand the `taskFilter` shorthand on the diary list/search tools into
@@ -964,11 +942,11 @@ function createMoltNetTools(config) {
964
942
  defineTool({
965
943
  name: "moltnet_host_exec",
966
944
  label: "Run command on host (escape hatch — requires user approval)",
967
- description: "Runs a command on the HOST machine, outside the sandbox VM. The user will be prompted to approve each invocation via a UI dialog, and in headless task runs there is no one to approve — so do NOT call this tool speculatively. Routine git and gh work — pushing branches, opening pull requests, etc. — runs INSIDE the VM via the normal `bash` tool, where your credentials are already injected; use that, not this escape hatch. Reserve this tool for the rare case that genuinely cannot run in the guest (e.g. reaching a host-only resource the VM has no path to).\n\nAllowed executables: git, gh, moltnet. Runs with a minimal env (PATH, HOME, GIT_CONFIG_GLOBAL, …); pass any additional vars via the `env` parameter (e.g. GH_TOKEN). Every invocation is logged as an auditable host execution.",
945
+ description: "Runs a command on the HOST machine, outside the sandbox VM. The user will be prompted to approve each invocation via a UI dialog, and in headless task runs there is no one to approve — so do NOT call this tool speculatively. Routine git and gh work — pushing branches, opening pull requests, etc. — runs INSIDE the VM via the normal `bash` tool; use that, not this escape hatch. Credentials are not generally injected into the guest. A runtime may expose an opaque HTTP placeholder that the host proxy can use only for declared destinations; otherwise authenticated operations are unavailable. Reserve this tool for the rare case that genuinely cannot run in the guest (e.g. reaching a host-only resource the VM has no path to).\n\nAllowed executables: git, gh, moltnet. Runs with a minimal env (PATH, HOME, GIT_CONFIG_GLOBAL, …); pass only non-secret additional vars via the `env` parameter. Every invocation is logged as an auditable host execution.",
968
946
  parameters: Type.Object({
969
947
  executable: Type.String({ description: "Executable to run (git | gh | moltnet)" }),
970
948
  args: Type.Array(Type.String(), { description: "Arguments to pass to the executable" }),
971
- env: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "Additional environment variables for this invocation (e.g. { \"GH_TOKEN\": \"...\" }). Merged on top of the minimal base env." }))
949
+ env: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "Additional non-secret environment variables for this invocation. Merged on top of the minimal base env." }))
972
950
  }),
973
951
  async execute(_id, params, _signal, _onUpdate, ctx) {
974
952
  if (!HOST_EXEC_ALLOWED.has(params.executable)) throw new Error(`host_exec: '${params.executable}' is not in the allowed list (${[...HOST_EXEC_ALLOWED].join(", ")}). Extend HOST_EXEC_ALLOWED only after explicit security review.`);
@@ -1310,21 +1288,6 @@ async function resolvePersistentSessionManager(args) {
1310
1288
  await SessionManager.list(args.cwd, args.sessionDir);
1311
1289
  return SessionManager.continueRecent(args.cwd, args.sessionDir);
1312
1290
  }
1313
- new TextEncoder();
1314
- //#endregion
1315
- //#region ../crypto-service/src/crypto.service.ts
1316
- ed.etc.sha512Sync = (...m) => {
1317
- const hash = createHash("sha512");
1318
- m.forEach((msg) => hash.update(msg));
1319
- return hash.digest();
1320
- };
1321
- //#endregion
1322
- //#region ../crypto-service/src/executor-attestation.ts
1323
- ed.etc.sha512Sync = (...m) => {
1324
- const hash = createHash$1("sha512");
1325
- m.forEach((msg) => hash.update(msg));
1326
- return hash.digest();
1327
- };
1328
1291
  //#endregion
1329
1292
  //#region ../crypto-service/src/json-cid.ts
1330
1293
  /**
@@ -1340,26 +1303,13 @@ async function computeJsonCid(value) {
1340
1303
  return CID.create(1, json.code, hash).toString();
1341
1304
  }
1342
1305
  //#endregion
1343
- //#region ../crypto-service/src/ssh.ts
1344
- /**
1345
- * SSH key format conversion for MoltNet Ed25519 keys
1346
- *
1347
- * Converts MoltNet agent keys (ed25519:<base64>) to OpenSSH format
1348
- * for use with git commit signing and SSH authentication.
1349
- */
1350
- if (!ed.etc.sha512Sync) ed.etc.sha512Sync = (...m) => {
1351
- const hash = createHash("sha512");
1352
- m.forEach((msg) => hash.update(msg));
1353
- return hash.digest();
1354
- };
1355
- //#endregion
1356
1306
  //#region src/config.ts
1357
1307
  /** Resolve Pi's host-side auth/config directory from process configuration. */
1358
1308
  function resolvePiCodingAgentDir() {
1359
1309
  return process.env["PI_CODING_AGENT_DIR"] ?? path.join(homedir(), ".pi", "agent");
1360
1310
  }
1361
1311
  //#endregion
1362
- //#region ../tasks/src/context.ts
1312
+ //#region ../runtime-profiles/src/context.ts
1363
1313
  /**
1364
1314
  * How an executor delivers a context entry to its underlying LLM.
1365
1315
  * V1 bindings only; Tier-2 (reference_file, mcp_resource, imported_file,
@@ -1412,99 +1362,12 @@ var ContextRef = Type$1.Object({
1412
1362
  additionalProperties: false
1413
1363
  });
1414
1364
  /** Reusable input fragment for any task type. Soft cap at 5 items. */
1415
- var TaskContext$1 = Type$1.Array(ContextRef, {
1365
+ var TaskContext = Type$1.Array(ContextRef, {
1416
1366
  $id: "TaskContext",
1417
1367
  maxItems: 5
1418
1368
  });
1419
1369
  //#endregion
1420
- //#region ../tasks/src/rubric.ts
1421
- /**
1422
- * Rubric — structured acceptance criteria used by judgment tasks.
1423
- *
1424
- * Phase 1 (this PR): rubrics are embedded in task inputs. Their integrity
1425
- * is pinned via the task's `input_cid` (which covers the whole input,
1426
- * including the inline rubric). No separate storage, no CRUD.
1427
- *
1428
- * Phase 2 (see #881): rubrics become a first-class resource with their
1429
- * own signed rows and CIDv1 lookup. The schema below is designed to
1430
- * carry forward unchanged — only storage and addressing differ.
1431
- *
1432
- * Until Phase 2 lands, `rubricId` + `version` + `contentHash` are
1433
- * informational fields the author fills in; no uniqueness is enforced.
1434
- * `contentHash` is optional in Phase 1 because the *task*'s input_cid
1435
- * is the authoritative commitment.
1436
- */
1437
- /**
1438
- * How a judge must score a single criterion.
1439
- *
1440
- * - `llm_score`: 0..1 continuous, `rationale` required. Smooths failures
1441
- * into the gradient — use `llm_checklist` instead for properties where
1442
- * a single failure is a real failure (grounding, faithfulness).
1443
- * - `llm_checklist`: judge enumerates per-claim assertions with
1444
- * `{passed, evidence}`. The criterion's numeric `score` is derived:
1445
- * `1` iff every assertion passes, else `0`. Per-claim evidence is the
1446
- * dataset for cluster-analysis of failure modes. See #999.
1447
- * - `boolean`: 0 or 1, `rationale` optional.
1448
- * - `deterministic_signature_check`: judge runs a signature check;
1449
- * result is 0 or 1. No LLM discretion.
1450
- * - `deterministic_coverage_check`: every referenced source entry
1451
- * appears in the rendered output; 0 or 1.
1452
- */
1453
- var RubricScoringMode = Type$1.Union([
1454
- Type$1.Literal("llm_score"),
1455
- Type$1.Literal("llm_checklist"),
1456
- Type$1.Literal("boolean"),
1457
- Type$1.Literal("deterministic_signature_check"),
1458
- Type$1.Literal("deterministic_coverage_check")
1459
- ], { $id: "RubricScoringMode" });
1460
- /**
1461
- * One binary check produced by an `llm_checklist`-mode criterion.
1462
- *
1463
- * `evidence` is REQUIRED for both PASS and FAIL — agentskills.io grading
1464
- * principle: \"Don't give the benefit of the doubt.\" A PASS without
1465
- * concrete evidence (a quoted span, an entry id, a source location)
1466
- * cannot be audited. A FAIL without evidence cannot be clustered into
1467
- * structural fixes. The same shape is reused by `judge-eval-variant`
1468
- * (#943) so tooling, dashboards, and analysis stay uniform.
1469
- */
1470
- var AssertionResult = Type$1.Object({
1471
- id: Type$1.String({ minLength: 1 }),
1472
- text: Type$1.String({ minLength: 1 }),
1473
- passed: Type$1.Boolean(),
1474
- evidence: Type$1.String({ minLength: 1 })
1475
- }, {
1476
- $id: "AssertionResult",
1477
- additionalProperties: false
1478
- });
1479
- var RubricCriterion = Type$1.Object({
1480
- id: Type$1.String({ minLength: 1 }),
1481
- description: Type$1.String({ minLength: 1 }),
1482
- weight: Type$1.Number({
1483
- minimum: 0,
1484
- maximum: 1
1485
- }),
1486
- scoring: RubricScoringMode
1487
- }, {
1488
- $id: "RubricCriterion",
1489
- additionalProperties: false
1490
- });
1491
- /**
1492
- * A complete rubric. Same shape used in Phase 1 (inline) and Phase 2
1493
- * (stored row `body`); only the addressing mechanism differs.
1494
- */
1495
- var Rubric = Type$1.Object({
1496
- rubricId: Type$1.String({ minLength: 1 }),
1497
- version: Type$1.String({ minLength: 1 }),
1498
- preamble: Type$1.Optional(Type$1.String()),
1499
- criteria: Type$1.Array(RubricCriterion, { minItems: 1 }),
1500
- scope: Type$1.Optional(Type$1.String()),
1501
- contentHash: Type$1.Optional(Type$1.String())
1502
- }, {
1503
- $id: "Rubric",
1504
- additionalProperties: false
1505
- });
1506
- //#endregion
1507
- //#region ../tasks/src/runtime-models.ts
1370
+ //#region ../runtime-profiles/src/runtime-models.ts
1508
1371
  /**
1509
1372
  * Runtime model catalog: a list of supported provider/model couples that
1510
1373
  * MoltNet daemons can target. Backed by the `runtime_models` table.
@@ -1552,7 +1415,7 @@ Type$1.Object({
1552
1415
  additionalProperties: false
1553
1416
  });
1554
1417
  //#endregion
1555
- //#region ../tasks/src/runtime-profile-context-recipes.ts
1418
+ //#region ../runtime-profiles/src/runtime-profile-context-recipes.ts
1556
1419
  var RUNTIME_PROFILE_CONTEXT_CATALOGUE = {
1557
1420
  version: 1,
1558
1421
  fragments: {
@@ -1563,12 +1426,12 @@ var RUNTIME_PROFILE_CONTEXT_CATALOGUE = {
1563
1426
  },
1564
1427
  "accountable-delivery-v1": {
1565
1428
  binding: "prompt_prefix",
1566
- content: "# Accountable delivery\n\n- Pair every commit made during this task with a signed diary entry created by the `moltnet_create_entry` custom tool. Put the returned id in a `MoltNet-Diary: <id>` commit trailer.\n- Keep commit signing enabled; do not bypass the agent git configuration.\n- Push a branch and open or update a pull request only when the task asks for it. For GitHub mutations, use the credential-bound `GH_TOKEN` command form required by the runtime kernel.\n- Keep changes, commits, and any requested pull request coherent enough to review independently.",
1429
+ content: "# Accountable delivery\n\n- Pair every commit made during this task with a task-provenance diary entry created by the `moltnet_create_entry` custom tool. Put the returned id in a `MoltNet-Diary: <id>` commit trailer. The tool does not currently promise a content signature; never describe an entry as signed unless the active runtime exposes and verifies a signing capability.\n- Do not disable commit signing when the active runtime provides it. A host-authenticated guest has no injected signing key, so do not recover one from host configuration or claim an unsigned commit is signed.\n- Push a branch and open or update a pull request only when the task asks for it. Use a host-brokered GitHub placeholder only when the runtime kernel declares one; if no GitHub credential is active, the authenticated operation is unavailable.\n- Keep changes, commits, and any requested pull request coherent enough to review independently.",
1567
1430
  slug: "accountable-delivery-v1"
1568
1431
  },
1569
1432
  "judgment-diary-v1": {
1570
1433
  binding: "prompt_prefix",
1571
- content: "# Judgment diary discipline\n\n- For an `assess_brief`, `judge_pack`, or `pr_review` task, create a signed diary entry with the `moltnet_create_entry` custom tool before submitting the structured judgment. Capture the rationale and evidence that support the verdict.\n- Add the `judgment` tag and the active task type tag (`assess_brief`, `judge_pack`, or `pr_review`). For `judge_pack`, also add `rubric:<rubricId>` from the task facts.\n- Do not use a shell `moltnet entry` command: task provenance is injected only by the custom tool.",
1434
+ content: "# Judgment diary discipline\n\n- For an `assess_brief`, `judge_pack`, or `pr_review` task, create a diary entry with the `moltnet_create_entry` custom tool before submitting the structured judgment. Capture the rationale and evidence that support the verdict. Do not claim a content signature unless the active runtime exposes and verifies a signing capability.\n- Add the `judgment` tag and the active task type tag (`assess_brief`, `judge_pack`, or `pr_review`). For `judge_pack`, also add `rubric:<rubricId>` from the task facts.\n- Do not use a shell `moltnet entry` command: task provenance is injected only by the custom tool.",
1572
1435
  slug: "judgment-diary-v1"
1573
1436
  },
1574
1437
  "proactive-memory-v1": {
@@ -1967,10 +1830,10 @@ Type$1.Object({
1967
1830
  Type$1.Literal("completed"),
1968
1831
  Type$1.Literal("failed")
1969
1832
  ]),
1970
- githubCode: Type$1.Optional(Type$1.String()),
1833
+ githubCode: Type$1.Optional(Type$1.String({ description: "GitHub manifest code sealed to the onboarding agent public key." })),
1971
1834
  identityId: Type$1.Optional(Type$1.String()),
1972
1835
  clientId: Type$1.Optional(Type$1.String()),
1973
- clientSecret: Type$1.Optional(Type$1.String()),
1836
+ clientSecret: Type$1.Optional(Type$1.String({ description: "OAuth2 client secret sealed to the onboarding agent public key." })),
1974
1837
  installationId: Type$1.Optional(Type$1.String())
1975
1838
  });
1976
1839
  Type$1.Object({
@@ -2568,7 +2431,7 @@ var toolEnforcementLiterals = [
2568
2431
  ];
2569
2432
  var ToolEnforcementSchema = Type$1.Union(toolEnforcementLiterals, { description: "Runtime tool-policy enforcement mode: off (inert), watch (audit only), enforce (block disallowed tools, fail-closed)." });
2570
2433
  //#endregion
2571
- //#region ../tasks/src/runtime-profiles.ts
2434
+ //#region ../runtime-profiles/src/runtime-profiles.ts
2572
2435
  var RuntimeProfileName = Type$1.String({
2573
2436
  minLength: 1,
2574
2437
  maxLength: 100,
@@ -2687,7 +2550,7 @@ var RuntimeProfileContext = Type$1.Object({
2687
2550
  $id: "RuntimeProfileContext",
2688
2551
  additionalProperties: false
2689
2552
  });
2690
- var RuntimeProfileRef = Type$1.Object({ profileId: Type$1.String({ format: "uuid" }) }, {
2553
+ Type$1.Object({ profileId: Type$1.String({ format: "uuid" }) }, {
2691
2554
  $id: "RuntimeProfileRef",
2692
2555
  additionalProperties: false
2693
2556
  });
@@ -2767,7 +2630,7 @@ Type$1.Object({
2767
2630
  additionalProperties: false
2768
2631
  });
2769
2632
  //#endregion
2770
- //#region ../tasks/src/runtime-sessions.ts
2633
+ //#region ../runtime-profiles/src/runtime-sessions.ts
2771
2634
  var RuntimeSessionKind = Type$1.Union([
2772
2635
  Type$1.Literal("root"),
2773
2636
  Type$1.Literal("extend"),
@@ -2825,7 +2688,7 @@ Type$1.Object({
2825
2688
  additionalProperties: false
2826
2689
  });
2827
2690
  //#endregion
2828
- //#region ../tasks/src/runtime-slots.ts
2691
+ //#region ../runtime-profiles/src/runtime-slots.ts
2829
2692
  var RuntimeWorkspaceKind = Type$1.Union([
2830
2693
  Type$1.Literal("origin"),
2831
2694
  Type$1.Literal("fork"),
@@ -2954,1248 +2817,37 @@ Type$1.Object({
2954
2817
  additionalProperties: false
2955
2818
  });
2956
2819
  //#endregion
2957
- //#region ../tasks/src/success-criteria.ts
2958
- /**
2959
- * SuccessCriteria proposer-stated acceptance criteria, evaluated in two
2960
- * complementary places.
2961
- *
2962
- * Before this envelope existed, criteria were scattered: a vestigial
2963
- * `criteriaCid` column nobody resolved, free-form prose on
2964
- * `fulfill_brief.input`, and inline `rubric` / `criteria[]` fields on
2965
- * judgment-task inputs. None of those were machine-verifiable
2966
- * end-to-end.
2967
- *
2968
- * This module defines a single, content-addressable envelope a proposer
2969
- * attaches to any task type. It has four orthogonal sections — pick
2970
- * whichever apply per task type:
2971
- *
2972
- * - `gates` Promise-level structural/process checks
2973
- * - `assertions` Declarative claims about output JSON
2974
- * - `rubric` Weighted-criteria scoring instrument, reused
2975
- * verbatim from `./rubric.ts`.
2976
- * - `sideEffects` Required process side-effects (e.g. diary entry)
2977
- *
2978
- * ## Two roles, two task types
2979
- *
2980
- * **Producer self-assessment** (fulfillment tasks: `fulfill_brief`,
2981
- * `curate_pack`, `render_pack`). The producer **LLM** evaluates the
2982
- * criteria against its own output and emits a `VerificationRecord`
2983
- * inside `output.verification`. The daemon is pure passthrough — it
2984
- * does not run `evaluateAssertions`, does not inspect the verification
2985
- * record. The REST API is dumb storage; it never re-runs assertions and
2986
- * never runs LLMs. The cross-field rule
2987
- * `requireVerificationWhenCriteriaPresent` enforces "verification
2988
- * required iff successCriteria present" at task-output validation time
2989
- * (server-side schema check). Self-assessment is a truthful self-rating,
2990
- * NOT enforcement — `verification.passed=false` does not block /complete
2991
- * and does not affect `acceptedAttemptN`. See
2992
- * `docs/use/tasks-and-runtime.md` for the full producer/judge flow.
2993
- *
2994
- * **Binding evaluation** (judgment tasks: `assess_brief`, `judge_pack`).
2995
- * A separate task whose IS the application of `successCriteria` to
2996
- * someone else's output. Different agent (enforced at claim time), same
2997
- * envelope. The judge's verdict is binding: this is the *gate* in the
2998
- * MoltNet model. The rubric inside `successCriteria.rubric` IS the job
2999
- * spec for the judge.
3000
- *
3001
- * The clean chain: producer task with `successCriteria` → producer
3002
- * self-assesses honestly → proposer (or automation) creates a downstream
3003
- * judgment task that references the same `successCriteria` (or a
3004
- * stricter rubric) → judgment task delivers the binding verdict.
3005
- *
3006
- * Storage: SuccessCriteria lives inline at `task.input.successCriteria`,
3007
- * pinned via the task's `inputCid`. No separate column or hash. When
3008
- * #881 lands, the `rubric` field can graduate to `{ rubricCid }` lookup
3009
- * without changing this envelope, and producer + judge tasks can pin
3010
- * the SAME rubric across the chain for end-to-end auditability.
3011
- */
3012
- var SchemaCheckSpec = Type$1.Object({ schemaCid: Type$1.String({ minLength: 1 }) }, { additionalProperties: false });
3013
- var CidEqualsSpec = Type$1.Object({
3014
- path: Type$1.String({ minLength: 1 }),
3015
- expected: Type$1.String({ minLength: 1 })
3016
- }, { additionalProperties: false });
3017
- var SubmitToolCallGate = Type$1.Object({
3018
- id: Type$1.String({ minLength: 1 }),
3019
- kind: Type$1.Literal("submit-tool-call"),
3020
- description: Type$1.String({ minLength: 1 }),
3021
- required: Type$1.Boolean()
3022
- }, { additionalProperties: false });
3023
- var Gate = Type$1.Union([
3024
- SubmitToolCallGate,
3025
- Type$1.Object({
3026
- id: Type$1.String({ minLength: 1 }),
3027
- kind: Type$1.Literal("schema-check"),
3028
- spec: SchemaCheckSpec,
3029
- required: Type$1.Boolean()
3030
- }, { additionalProperties: false }),
3031
- Type$1.Object({
3032
- id: Type$1.String({ minLength: 1 }),
3033
- kind: Type$1.Literal("cid-equals"),
3034
- spec: CidEqualsSpec,
3035
- required: Type$1.Boolean()
3036
- }, { additionalProperties: false })
3037
- ], { $id: "Gate" });
3038
- var AssertionOp = Type$1.Union([
3039
- Type$1.Literal("exists"),
3040
- Type$1.Literal("equals"),
3041
- Type$1.Literal("matches"),
3042
- Type$1.Literal("in-range"),
3043
- Type$1.Literal("min-length")
3044
- ], { $id: "AssertionOp" });
3045
- var Assertion = Type$1.Object({
3046
- id: Type$1.String({ minLength: 1 }),
3047
- path: Type$1.String({ minLength: 1 }),
3048
- op: AssertionOp,
3049
- value: Type$1.Optional(Type$1.Unknown())
3050
- }, {
3051
- $id: "Assertion",
3052
- additionalProperties: false
3053
- });
3054
- var SideEffectsSpec = Type$1.Object({
3055
- diaryEntryRequired: Type$1.Optional(Type$1.Boolean()),
3056
- diaryEntryTags: Type$1.Optional(Type$1.Array(Type$1.String({ minLength: 1 }))),
3057
- referencedEntries: Type$1.Optional(Type$1.Integer({ minimum: 0 }))
3058
- }, {
3059
- $id: "SideEffectsSpec",
3060
- additionalProperties: false
3061
- });
3062
- var SuccessCriteria = Type$1.Object({
3063
- version: Type$1.Literal(1),
3064
- gates: Type$1.Optional(Type$1.Array(Gate)),
3065
- assertions: Type$1.Optional(Type$1.Array(Assertion)),
3066
- rubric: Type$1.Optional(Rubric),
3067
- minComposite: Type$1.Optional(Type$1.Number({
3068
- minimum: 0,
3069
- maximum: 1
3070
- })),
3071
- sideEffects: Type$1.Optional(SideEffectsSpec)
3072
- }, {
3073
- $id: "SuccessCriteria",
3074
- additionalProperties: false
3075
- });
3076
- var VerificationResultStatus = Type$1.Union([
3077
- Type$1.Literal("pass"),
3078
- Type$1.Literal("fail"),
3079
- Type$1.Literal("skip")
3080
- ], { $id: "VerificationResultStatus" });
3081
- var VerificationResultKind = Type$1.Union([
3082
- Type$1.Literal("gate"),
3083
- Type$1.Literal("assertion"),
3084
- Type$1.Literal("rubric"),
3085
- Type$1.Literal("sideEffect")
3086
- ], { $id: "VerificationResultKind" });
3087
- var VerificationResult = Type$1.Object({
3088
- id: Type$1.String({ minLength: 1 }),
3089
- kind: VerificationResultKind,
3090
- status: VerificationResultStatus,
3091
- detail: Type$1.Optional(Type$1.String())
3092
- }, {
3093
- $id: "VerificationResult",
3094
- additionalProperties: false
3095
- });
3096
- var VerificationRecord = Type$1.Object({
3097
- inputCid: Type$1.String({ minLength: 1 }),
3098
- results: Type$1.Array(VerificationResult),
3099
- passed: Type$1.Boolean({ description: "True iff every verification result has status \"pass\" or \"skip\"; false when any result has status \"fail\"." })
3100
- }, {
3101
- $id: "VerificationRecord",
3102
- additionalProperties: false
3103
- });
3104
- //#endregion
3105
- //#region ../tasks/src/task-artifacts.ts
3106
- var TaskArtifact = Type$1.Object({
3107
- id: Type$1.String({ format: "uuid" }),
3108
- teamId: Type$1.String({ format: "uuid" }),
3109
- taskId: Type$1.String({ format: "uuid" }),
3110
- attemptN: Type$1.Union([Type$1.Integer({ minimum: 1 }), Type$1.Null()]),
3111
- kind: Type$1.String({
3112
- minLength: 1,
3113
- maxLength: 100
3114
- }),
3115
- title: Type$1.String({
3116
- minLength: 1,
3117
- maxLength: 255
3118
- }),
3119
- contentType: Type$1.String({
3120
- minLength: 1,
3121
- maxLength: 200
3122
- }),
3123
- contentEncoding: Type$1.Union([Type$1.String({
3124
- minLength: 1,
3125
- maxLength: 100
3126
- }), Type$1.Null()]),
3127
- sizeBytes: Type$1.Integer({ minimum: 0 }),
3128
- cid: Type$1.String({
3129
- minLength: 1,
3130
- maxLength: 100
3131
- }),
3132
- createdByAgentId: Type$1.Union([Type$1.String({ format: "uuid" }), Type$1.Null()]),
3133
- expiresAt: Type$1.Union([Type$1.String({ format: "date-time" }), Type$1.Null()]),
3134
- createdAt: Type$1.String({ format: "date-time" })
3135
- }, { $id: "TaskArtifact" });
3136
- Type$1.Object({
3137
- artifacts: Type$1.Array(TaskArtifact),
3138
- nextCursor: Type$1.Union([Type$1.String({ minLength: 1 }), Type$1.Null()])
3139
- }, { $id: "TaskArtifactList" });
3140
- Type$1.Object({
3141
- limit: Type$1.Optional(Type$1.Integer({
3142
- minimum: 1,
3143
- maximum: 100
3144
- })),
3145
- cursor: Type$1.Optional(Type$1.String({ minLength: 1 }))
3146
- }, {
3147
- $id: "ListTaskArtifactsQuery",
3148
- additionalProperties: false
3149
- });
3150
- var HeaderSafeContentType = Type$1.String({
3151
- minLength: 1,
3152
- maxLength: 200,
3153
- pattern: "^[\\x21-\\x7e][\\x20-\\x7e]*$"
3154
- });
3155
- var HeaderSafeContentEncoding = Type$1.String({
3156
- minLength: 1,
3157
- maxLength: 100,
3158
- pattern: "^[\\x21-\\x7e][\\x20-\\x7e]*$"
3159
- });
3160
- Type$1.Object({
3161
- kind: Type$1.String({
3162
- minLength: 1,
3163
- maxLength: 100
3164
- }),
3165
- title: Type$1.String({
3166
- minLength: 1,
3167
- maxLength: 255
3168
- }),
3169
- contentType: Type$1.Optional(HeaderSafeContentType),
3170
- contentEncoding: Type$1.Optional(HeaderSafeContentEncoding)
3171
- }, {
3172
- $id: "UploadTaskArtifactQuery",
3173
- additionalProperties: false
3174
- });
3175
- Type$1.String({
3176
- $id: "TaskArtifactContent",
3177
- description: "Task artifact content stream.",
3178
- format: "binary"
3179
- });
3180
- Type$1.Object({ taskId: Type$1.String({ format: "uuid" }) }, {
3181
- $id: "TaskArtifactTaskParams",
3182
- additionalProperties: false
3183
- });
3184
- Type$1.Object({
3185
- taskId: Type$1.String({ format: "uuid" }),
3186
- attemptN: Type$1.Integer({ minimum: 1 })
3187
- }, {
3188
- $id: "TaskArtifactAttemptParams",
3189
- additionalProperties: false
3190
- });
3191
- Type$1.Object({
3192
- taskId: Type$1.String({ format: "uuid" }),
3193
- attemptN: Type$1.Integer({ minimum: 1 }),
3194
- cid: Type$1.String({
3195
- minLength: 1,
3196
- maxLength: 100
3197
- })
3198
- }, {
3199
- $id: "TaskArtifactContentParams",
3200
- additionalProperties: false
3201
- });
3202
- Type$1.Object({
3203
- contentType: Type$1.Optional(HeaderSafeContentType),
3204
- contentEncoding: Type$1.Optional(HeaderSafeContentEncoding)
3205
- }, {
3206
- $id: "StageTaskArtifactQuery",
3207
- additionalProperties: false
3208
- });
3209
- Type$1.Object({
3210
- cid: Type$1.String({
3211
- minLength: 1,
3212
- maxLength: 100
3213
- }),
3214
- sizeBytes: Type$1.Integer({ minimum: 0 }),
3215
- contentType: Type$1.String({
3216
- minLength: 1,
3217
- maxLength: 200
3218
- })
3219
- }, { $id: "StagedTaskArtifact" });
3220
- Type$1.Object({
3221
- taskId: Type$1.String({ format: "uuid" }),
3222
- cid: Type$1.String({
3223
- minLength: 1,
3224
- maxLength: 100
3225
- })
3226
- }, {
3227
- $id: "TaskArtifactTaskContentParams",
3228
- additionalProperties: false
3229
- });
3230
- Type$1.Object({
3231
- targetTaskId: Type$1.String({ format: "uuid" }),
3232
- successCriteria: SuccessCriteria
3233
- }, {
3234
- $id: "AssessBriefInput",
3235
- additionalProperties: false
3236
- });
3237
- /** One score line. */
3238
- var AssessBriefScore = Type$1.Object({
3239
- criterionId: Type$1.String({ minLength: 1 }),
3240
- score: Type$1.Number({
3241
- minimum: 0,
3242
- maximum: 1
3243
- }),
3244
- rationale: Type$1.Optional(Type$1.String()),
3245
- evidence: Type$1.Optional(Type$1.Object({
3246
- commitsVerified: Type$1.Number(),
3247
- commitsTotal: Type$1.Number(),
3248
- signatureFailures: Type$1.Array(Type$1.String())
3249
- }, { additionalProperties: false }))
3250
- }, {
3251
- $id: "AssessBriefScore",
3252
- additionalProperties: false
3253
- });
3254
- Type$1.Object({
3255
- scores: Type$1.Array(AssessBriefScore, { minItems: 1 }),
3256
- composite: Type$1.Number({
3257
- minimum: 0,
3258
- maximum: 1
3259
- }),
3260
- verdict: Type$1.String({ minLength: 1 }),
3261
- judgeModel: Type$1.Optional(Type$1.String())
3262
- }, {
3263
- $id: "AssessBriefOutput",
3264
- additionalProperties: false
3265
- });
3266
- //#endregion
3267
- //#region ../tasks/src/task-types/curate-pack.ts
2820
+ //#region src/runtime-definition.ts
2821
+ var PI_RUNTIME_DEFINITION_VERSION = "moltnet:pi-runtime:v1";
2822
+ var PI_EXECUTOR_MANIFEST_VERSION = "moltnet:executor-manifest:v1";
2823
+ var DEFAULT_BROKERED_HTTP_SECRET_RESOLUTION_TIMEOUT_MS = 3e4;
2824
+ var MAX_BROKERED_HTTP_SECRET_RESOLUTION_TIMEOUT_MS = 2147483647;
2825
+ var PiBrokeredHttpSecretResolutionError = class extends Error {
2826
+ constructor(requirementId, message, retryable) {
2827
+ super(message);
2828
+ this.requirementId = requirementId;
2829
+ this.retryable = retryable;
2830
+ this.name = "PiBrokeredHttpSecretResolutionError";
2831
+ }
2832
+ };
2833
+ function brokeredHttpSecretAbortError() {
2834
+ const error = /* @__PURE__ */ new Error("Brokered HTTP secret resolution aborted");
2835
+ error.name = "AbortError";
2836
+ return error;
2837
+ }
3268
2838
  /**
3269
- * `curate_pack` select and rank diary entries into a context pack.
3270
- *
3271
- * output_kind: artifact
3272
- * criteria: not required (rubric-less curation recipe)
3273
- * references: optional (e.g. a prior rendered pack being re-curated)
3274
- *
3275
- * This is step 1 of the three-session attribution loop (#875). The agent
3276
- * runs a structured exploration over a diary — tag inventory, hybrid
3277
- * search, type/tag narrowing — and emits a ranked entry list via
3278
- * `moltnet_pack_create`. The prompt is deterministic given the input
3279
- * (no operator interaction), so two runs with the same input should
3280
- * converge on similar packs.
3281
- *
3282
- * Related: `render_pack`, `judge_pack`.
2839
+ * Declare a value-free HTTP credential requirement in trusted runtime code.
2840
+ * The resolver runs per attempt on the daemon host; its return value is never
2841
+ * added to the runtime definition or executor manifest.
3283
2842
  */
3284
- var EntryTypeFilter = Type$1.Union([
3285
- Type$1.Literal("episodic"),
3286
- Type$1.Literal("semantic"),
3287
- Type$1.Literal("procedural"),
3288
- Type$1.Literal("reflection")
3289
- ]);
3290
- Type$1.Object({
3291
- diaryId: Type$1.String({ format: "uuid" }),
3292
- taskPrompt: Type$1.String({ minLength: 1 }),
3293
- entryTypes: Type$1.Optional(Type$1.Array(EntryTypeFilter, { minItems: 1 })),
3294
- tagFilters: Type$1.Optional(Type$1.Object({
3295
- include: Type$1.Optional(Type$1.Array(Type$1.String())),
3296
- exclude: Type$1.Optional(Type$1.Array(Type$1.String())),
3297
- prefix: Type$1.Optional(Type$1.String())
3298
- }, { additionalProperties: false })),
3299
- tokenBudget: Type$1.Optional(Type$1.Number({ minimum: 500 })),
3300
- recipe: Type$1.Optional(Type$1.Union([Type$1.Literal("topic-focused-v1"), Type$1.Literal("scope-inventory-v1")])),
3301
- successCriteria: Type$1.Optional(SuccessCriteria)
3302
- }, {
3303
- $id: "CuratePackInput",
3304
- additionalProperties: false
3305
- });
3306
- Type$1.Object({
3307
- packId: Type$1.String({ format: "uuid" }),
3308
- packCid: Type$1.String({ minLength: 1 }),
3309
- entries: Type$1.Array(Type$1.Object({
3310
- entryId: Type$1.String({ format: "uuid" }),
3311
- rank: Type$1.Number({ minimum: 1 }),
3312
- rationale: Type$1.String({ minLength: 1 })
3313
- }, { additionalProperties: false }), { minItems: 1 }),
3314
- recipeParams: Type$1.Record(Type$1.String(), Type$1.Unknown()),
3315
- checkpoints: Type$1.Optional(Type$1.Array(Type$1.Object({
3316
- phase: Type$1.String({ minLength: 1 }),
3317
- candidateIds: Type$1.Array(Type$1.String({ format: "uuid" })),
3318
- droppedIds: Type$1.Optional(Type$1.Array(Type$1.String({ format: "uuid" }))),
3319
- notes: Type$1.String({ minLength: 1 })
3320
- }, { additionalProperties: false }))),
3321
- summary: Type$1.String({ minLength: 1 }),
3322
- verification: Type$1.Optional(VerificationRecord)
3323
- }, {
3324
- $id: "CuratePackOutput",
3325
- additionalProperties: false
3326
- });
3327
- //#endregion
3328
- //#region ../tasks/src/task-types/freeform.ts
3329
- var FreeformExecutionOptions = Type$1.Object({
3330
- workspace: Type$1.Optional(Type$1.Union([
3331
- Type$1.Literal("none"),
3332
- Type$1.Literal("shared_mount"),
3333
- Type$1.Literal("dedicated_worktree")
3334
- ])),
3335
- revision: Type$1.Optional(Type$1.String({ pattern: "^[0-9a-fA-F]{40}$" }))
3336
- }, {
3337
- $id: "FreeformExecutionOptions",
3338
- additionalProperties: false
3339
- });
3340
- var FreeformContinueFrom = Type$1.Object({
3341
- taskId: Type$1.String({ format: "uuid" }),
3342
- attemptN: Type$1.Integer({ minimum: 1 }),
3343
- mode: Type$1.Optional(Type$1.Union([Type$1.Literal("extend"), Type$1.Literal("fork")]))
3344
- }, {
3345
- $id: "FreeformContinueFrom",
3346
- additionalProperties: false
3347
- });
3348
- var FreeformTaskTypeProposal = Type$1.Object({
3349
- name: Type$1.String({ minLength: 1 }),
3350
- rationale: Type$1.String({ minLength: 1 }),
3351
- inputShape: Type$1.Optional(Type$1.Record(Type$1.String(), Type$1.Unknown())),
3352
- outputShape: Type$1.Optional(Type$1.Record(Type$1.String(), Type$1.Unknown()))
3353
- }, {
3354
- $id: "FreeformTaskTypeProposal",
3355
- additionalProperties: false
3356
- });
3357
- Type$1.Object({
3358
- brief: Type$1.String({ minLength: 1 }),
3359
- expectedOutput: Type$1.Optional(Type$1.String({ minLength: 1 })),
3360
- constraints: Type$1.Optional(Type$1.Array(Type$1.String({ minLength: 1 }), { maxItems: 20 })),
3361
- suggestedTaskType: Type$1.Optional(Type$1.String({ minLength: 1 })),
3362
- successCriteria: Type$1.Optional(SuccessCriteria),
3363
- context: Type$1.Optional(TaskContext$1),
3364
- execution: Type$1.Optional(FreeformExecutionOptions),
3365
- continueFrom: Type$1.Optional(FreeformContinueFrom)
3366
- }, {
3367
- $id: "FreeformInput",
3368
- additionalProperties: false
3369
- });
3370
- var FreeformArtifact = Type$1.Object({
3371
- kind: Type$1.String({ minLength: 1 }),
3372
- title: Type$1.String({ minLength: 1 }),
3373
- description: Type$1.Optional(Type$1.String({ minLength: 1 })),
3374
- url: Type$1.Optional(Type$1.String({ minLength: 1 })),
3375
- path: Type$1.Optional(Type$1.String({ minLength: 1 })),
3376
- cid: Type$1.Optional(Type$1.String({ minLength: 1 })),
3377
- contentType: Type$1.Optional(Type$1.String({ minLength: 1 })),
3378
- contentEncoding: Type$1.Optional(Type$1.String({ minLength: 1 })),
3379
- sizeBytes: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
3380
- body: Type$1.Optional(Type$1.String({ maxLength: 65536 }))
3381
- }, {
3382
- $id: "FreeformArtifact",
3383
- additionalProperties: false
3384
- });
3385
- Type$1.Object({
3386
- summary: Type$1.String({ minLength: 1 }),
3387
- branch: Type$1.Optional(Type$1.String({ minLength: 1 })),
3388
- artifacts: Type$1.Optional(Type$1.Array(FreeformArtifact, { maxItems: 20 })),
3389
- proposedTaskType: Type$1.Optional(FreeformTaskTypeProposal),
3390
- diaryEntryIds: Type$1.Optional(Type$1.Array(Type$1.String({ format: "uuid" }))),
3391
- verification: Type$1.Optional(VerificationRecord)
3392
- }, {
3393
- $id: "FreeformOutput",
3394
- additionalProperties: false
3395
- });
3396
- Type$1.Object({
3397
- brief: Type$1.String({ minLength: 1 }),
3398
- successCriteria: Type$1.Optional(SuccessCriteria),
3399
- seedFiles: Type$1.Optional(Type$1.Array(Type$1.String())),
3400
- scopeHint: Type$1.Optional(Type$1.String())
3401
- }, {
3402
- $id: "FulfillBriefInput",
3403
- additionalProperties: false
3404
- });
3405
- Type$1.Object({
3406
- branch: Type$1.String({ minLength: 1 }),
3407
- commits: Type$1.Array(Type$1.Object({
3408
- sha: Type$1.String({ minLength: 7 }),
3409
- message: Type$1.String(),
3410
- diaryEntryId: Type$1.Union([Type$1.String({ format: "uuid" }), Type$1.Null()])
3411
- }, { additionalProperties: false })),
3412
- pullRequestUrl: Type$1.Union([Type$1.String(), Type$1.Null()]),
3413
- diaryEntryIds: Type$1.Array(Type$1.String({ format: "uuid" })),
3414
- summary: Type$1.String({ minLength: 1 }),
3415
- verification: Type$1.Optional(VerificationRecord)
3416
- }, {
3417
- $id: "FulfillBriefOutput",
3418
- additionalProperties: false
3419
- });
3420
- Type$1.Object({
3421
- renderedPackId: Type$1.String({ format: "uuid" }),
3422
- sourcePackId: Type$1.String({ format: "uuid" }),
3423
- successCriteria: SuccessCriteria
3424
- }, {
3425
- $id: "JudgePackInput",
3426
- additionalProperties: false
3427
- });
3428
- /** One scored criterion. Mirrors `AssessBriefScore`. */
3429
- var JudgePackScore = Type$1.Object({
3430
- criterionId: Type$1.String({ minLength: 1 }),
3431
- score: Type$1.Number({
3432
- minimum: 0,
3433
- maximum: 1
3434
- }),
3435
- rationale: Type$1.Optional(Type$1.String()),
3436
- assertions: Type$1.Optional(Type$1.Array(AssertionResult, { minItems: 1 })),
3437
- evidence: Type$1.Optional(Type$1.Record(Type$1.String(), Type$1.Unknown()))
3438
- }, {
3439
- $id: "JudgePackScore",
3440
- additionalProperties: false
3441
- });
3442
- Type$1.Object({
3443
- scores: Type$1.Array(JudgePackScore, { minItems: 1 }),
3444
- composite: Type$1.Number({
3445
- minimum: 0,
3446
- maximum: 1
3447
- }),
3448
- verdict: Type$1.String({ minLength: 1 }),
3449
- judgeModel: Type$1.Optional(Type$1.String()),
3450
- rendererBinaryCid: Type$1.Optional(Type$1.Union([Type$1.String(), Type$1.Null()]))
3451
- }, {
3452
- $id: "JudgePackOutput",
3453
- additionalProperties: false
3454
- });
3455
- Type$1.Object({
3456
- targetTaskId: Type$1.String({ format: "uuid" }),
3457
- targetAttemptN: Type$1.Integer({ minimum: 1 }),
3458
- successCriteria: SuccessCriteria
3459
- }, {
3460
- $id: "JudgeEvalAttemptInput",
3461
- additionalProperties: false
3462
- });
3463
- Type$1.Object({
3464
- targetTaskId: Type$1.String({ format: "uuid" }),
3465
- targetAttemptN: Type$1.Integer({ minimum: 1 }),
3466
- variantLabel: Type$1.String({
3467
- minLength: 1,
3468
- maxLength: 64,
3469
- pattern: "^(?!.* - ).*$"
3470
- }),
3471
- scores: Type$1.Array(JudgePackScore, { minItems: 1 }),
3472
- composite: Type$1.Number({
3473
- minimum: 0,
3474
- maximum: 1
3475
- }),
3476
- verdict: Type$1.String({ minLength: 1 }),
3477
- judgeModel: Type$1.Optional(Type$1.String({ minLength: 1 }))
3478
- }, {
3479
- $id: "JudgeEvalAttemptSubmission",
3480
- additionalProperties: false
3481
- });
3482
- Type$1.Object({
3483
- targetTaskId: Type$1.String({ format: "uuid" }),
3484
- targetAttemptN: Type$1.Integer({ minimum: 1 }),
3485
- variantLabel: Type$1.String({
3486
- minLength: 1,
3487
- maxLength: 64,
3488
- pattern: "^(?!.* - ).*$"
3489
- }),
3490
- scores: Type$1.Array(JudgePackScore, { minItems: 1 }),
3491
- composite: Type$1.Number({
3492
- minimum: 0,
3493
- maximum: 1
3494
- }),
3495
- verdict: Type$1.String({ minLength: 1 }),
3496
- judgeModel: Type$1.Optional(Type$1.String({ minLength: 1 })),
3497
- traceparent: Type$1.Optional(Type$1.String({ minLength: 1 }))
3498
- }, {
3499
- $id: "JudgeEvalAttemptOutput",
3500
- additionalProperties: false
3501
- });
3502
- //#endregion
3503
- //#region ../tasks/src/task-types/pr-review.ts
3504
- var PrReviewSubject = Type$1.Object({
3505
- title: Type$1.String({ minLength: 1 }),
3506
- summary: Type$1.String({ minLength: 1 }),
3507
- resourceUrls: Type$1.Optional(Type$1.Array(Type$1.String({ minLength: 1 }))),
3508
- inspectionHints: Type$1.Optional(Type$1.Array(Type$1.String({ minLength: 1 })))
3509
- }, {
3510
- $id: "PrReviewSubject",
3511
- additionalProperties: false
3512
- });
3513
- Type$1.Object({
3514
- subject: PrReviewSubject,
3515
- taskPrompt: Type$1.Optional(Type$1.String({ minLength: 1 })),
3516
- successCriteria: SuccessCriteria,
3517
- context: Type$1.Optional(TaskContext$1)
3518
- }, {
3519
- $id: "PrReviewInput",
3520
- additionalProperties: false
3521
- });
3522
- var PrReviewScore = Type$1.Object({
3523
- criterionId: Type$1.String({ minLength: 1 }),
3524
- score: Type$1.Union([Type$1.Literal(0), Type$1.Literal(1)]),
3525
- rationale: Type$1.String({ minLength: 1 })
3526
- }, {
3527
- $id: "PrReviewScore",
3528
- additionalProperties: false
3529
- });
3530
- Type$1.Object({
3531
- scores: Type$1.Array(PrReviewScore, { minItems: 1 }),
3532
- composite: Type$1.Number({
3533
- minimum: 0,
3534
- maximum: 1
3535
- }),
3536
- verdict: Type$1.String({ minLength: 1 })
3537
- }, {
3538
- $id: "PrReviewOutput",
3539
- additionalProperties: false
3540
- });
3541
- Type$1.Object({
3542
- packId: Type$1.String({ format: "uuid" }),
3543
- persist: Type$1.Optional(Type$1.Boolean()),
3544
- pinned: Type$1.Optional(Type$1.Boolean()),
3545
- successCriteria: Type$1.Optional(SuccessCriteria)
3546
- }, {
3547
- $id: "RenderPackInput",
3548
- additionalProperties: false
3549
- });
3550
- Type$1.Object({
3551
- renderedPackId: Type$1.Union([Type$1.String({ format: "uuid" }), Type$1.Null()]),
3552
- renderedCid: Type$1.String({ minLength: 1 }),
3553
- renderMethod: Type$1.String({ minLength: 1 }),
3554
- byteSize: Type$1.Number({ minimum: 0 }),
3555
- entriesRendered: Type$1.Number({ minimum: 0 }),
3556
- summary: Type$1.String({ minLength: 1 }),
3557
- verification: Type$1.Optional(VerificationRecord)
3558
- }, {
3559
- $id: "RenderPackOutput",
3560
- additionalProperties: false
3561
- });
3562
- //#endregion
3563
- //#region ../tasks/src/task-types/run-eval.ts
3564
- /**
3565
- * `run_eval` — execute a scenario prompt under a named variant for
3566
- * later per-attempt grading by `judge_eval_attempt` tasks.
3567
- *
3568
- * output_kind: artifact
3569
- * criteria: optional producer-only checks (when set,
3570
- * output.verification is required — the judge rubric remains hidden
3571
- * on downstream `judge_eval_attempt` tasks)
3572
- * references: not required (scenario lives entirely in input)
3573
- */
3574
- var RunEvalMode = Type$1.Union([Type$1.Literal("vitro"), Type$1.Literal("vivo")], { $id: "RunEvalMode" });
3575
- var RunEvalWorkspace = Type$1.Union([
3576
- Type$1.Literal("none"),
3577
- Type$1.Literal("shared_mount"),
3578
- Type$1.Literal("dedicated_worktree")
3579
- ], { $id: "RunEvalWorkspace" });
3580
- var RunEvalExecution = Type$1.Object({
3581
- mode: RunEvalMode,
3582
- workspace: RunEvalWorkspace
3583
- }, {
3584
- $id: "RunEvalExecution",
3585
- additionalProperties: false
3586
- });
3587
- /**
3588
- * Producer-visible checks for `run_eval`. Deliberately forbids `rubric`
3589
- * so the variant runner cannot see the downstream judge's answer key.
3590
- * Keep the rest of the SuccessCriteria envelope available for generic
3591
- * process / structure checks (`gates`, `assertions`, `sideEffects`).
3592
- */
3593
- var RunEvalSuccessCriteria = Type$1.Object({
3594
- version: Type$1.Literal(1),
3595
- gates: Type$1.Optional(SuccessCriteria.properties.gates),
3596
- assertions: Type$1.Optional(SuccessCriteria.properties.assertions),
3597
- sideEffects: Type$1.Optional(SuccessCriteria.properties.sideEffects)
3598
- }, {
3599
- $id: "RunEvalSuccessCriteria",
3600
- additionalProperties: false
3601
- });
3602
- Type$1.Object({
3603
- scenario: Type$1.Object({
3604
- prompt: Type$1.String({ minLength: 1 }),
3605
- inputFiles: Type$1.Optional(Type$1.Array(Type$1.String({ minLength: 1 })))
3606
- }, { additionalProperties: false }),
3607
- variantLabel: Type$1.String({
3608
- minLength: 1,
3609
- maxLength: 64
3610
- }),
3611
- execution: RunEvalExecution,
3612
- context: TaskContext$1,
3613
- successCriteria: Type$1.Optional(RunEvalSuccessCriteria)
3614
- }, {
3615
- $id: "RunEvalInput",
3616
- additionalProperties: false
3617
- });
3618
- var RunEvalArtifact = Type$1.Object({
3619
- path: Type$1.String({ minLength: 1 }),
3620
- cid: Type$1.String({ minLength: 1 })
3621
- }, { additionalProperties: false });
3622
- Type$1.Object({
3623
- response: Type$1.String({ minLength: 1 }),
3624
- artifacts: Type$1.Optional(Type$1.Array(RunEvalArtifact)),
3625
- verification: Type$1.Optional(VerificationRecord)
3626
- }, {
3627
- $id: "RunEvalSubmission",
3628
- additionalProperties: false
3629
- });
3630
- Type$1.Object({
3631
- response: Type$1.String({ minLength: 1 }),
3632
- artifacts: Type$1.Optional(Type$1.Array(RunEvalArtifact)),
3633
- totalTokens: Type$1.Integer({ minimum: 0 }),
3634
- durationMs: Type$1.Integer({ minimum: 0 }),
3635
- traceparent: Type$1.Optional(Type$1.String({ minLength: 1 })),
3636
- verification: Type$1.Optional(VerificationRecord)
3637
- }, {
3638
- $id: "RunEvalOutput",
3639
- additionalProperties: false
3640
- });
3641
- //#endregion
3642
- //#region ../tasks/src/task-type-registry.ts
3643
- var schemaCids = null;
3644
- function getTaskTypeRegistry() {
3645
- if (!schemaCids) throw new Error("Task type registry not initialized. Call initTaskTypeRegistry() first.");
3646
- return schemaCids;
3647
- }
3648
- new Proxy({}, { get(_, prop) {
3649
- if (typeof prop !== "string") return void 0;
3650
- return getTaskTypeRegistry().get(prop);
3651
- } });
3652
- //#endregion
3653
- //#region ../tasks/src/wire.ts
3654
- /**
3655
- * Wire-format types for the MoltNet Task model.
3656
- *
3657
- * These schemas are the single source of truth for:
3658
- * - `tasks`, `task_attempts`, `task_messages` DB columns (PR 1's Drizzle
3659
- * schema must match these verbatim)
3660
- * - REST request/response bodies (PR 4)
3661
- * - `TaskReporter` output records (PR 0)
3662
- *
3663
- * Invariant: every property on `Task` is type-neutral (applies to all
3664
- * `taskType`s). Type-specific payloads live inside `input` / `output`
3665
- * JSONB, validated against schemas registered under `task_types`.
3666
- *
3667
- * Identity rule:
3668
- * - claim/execute/sign → agent-only (`task_attempts.claimed_by_agent_id`)
3669
- * - propose/cancel → agent XOR human (dual nullable FK + XOR check)
3670
- *
3671
- * See GH issue #852 for the full design snapshot.
3672
- */
3673
- var TaskStatus = Type$1.Union([
3674
- Type$1.Literal("waiting"),
3675
- Type$1.Literal("queued"),
3676
- Type$1.Literal("dispatched"),
3677
- Type$1.Literal("running"),
3678
- Type$1.Literal("completed"),
3679
- Type$1.Literal("failed"),
3680
- Type$1.Literal("cancelled"),
3681
- Type$1.Literal("expired")
3682
- ], { $id: "TaskStatus" });
3683
- var TaskAttemptStatus = Type$1.Union([
3684
- Type$1.Literal("claimed"),
3685
- Type$1.Literal("running"),
3686
- Type$1.Literal("completed"),
3687
- Type$1.Literal("failed"),
3688
- Type$1.Literal("cancelled"),
3689
- Type$1.Literal("aborted"),
3690
- Type$1.Literal("timed_out")
3691
- ], { $id: "TaskAttemptStatus" });
3692
- var ExecutorTrustLevel = Type$1.Union([
3693
- Type$1.Literal("selfDeclared"),
3694
- Type$1.Literal("agentSigned"),
3695
- Type$1.Literal("releaseVerifiedTool"),
3696
- Type$1.Literal("sandboxAttested")
3697
- ], { $id: "ExecutorTrustLevel" });
3698
- var OutputKind = Type$1.Union([Type$1.Literal("artifact"), Type$1.Literal("judgment")], { $id: "OutputKind" });
3699
- var TaskMessageKind = Type$1.Union([
3700
- Type$1.Literal("text_delta"),
3701
- Type$1.Literal("tool_call_start"),
3702
- Type$1.Literal("tool_call_end"),
3703
- Type$1.Literal("turn_end"),
3704
- Type$1.Literal("error"),
3705
- Type$1.Literal("info")
3706
- ], { $id: "TaskMessageKind" });
3707
- var Uuid = Type$1.String({ format: "uuid" });
3708
- var Cid = Type$1.String({ minLength: 1 });
3709
- var IsoTimestamp = Type$1.String({ format: "date-time" });
3710
- /**
3711
- * Daemon-asserted runtime state stamped onto a `TaskAttemptSummary` at
3712
- * attempt-completion time. The server persists this block verbatim and
3713
- * exposes `slotResumableUntil` as a legacy/local warm-slot hint; task
3714
- * continuation eligibility is based on the completed source attempt and
3715
- * daemon-side claim-affinity/runtime-session recovery. The block carries
3716
- * its own `reportedAt` so consumers can reason about staleness without
3717
- * reading documentation. All daemon-asserted state lives here —
3718
- * top-level attempt fields stay server-authoritative.
3719
- *
3720
- * Adding new fields requires explicit design review (intentional
3721
- * boundary; see docs/superpowers/specs/2026-06-04-tasks-continue-design.md).
3722
- */
3723
- var DaemonState = Type$1.Object({
3724
- reportedAt: IsoTimestamp,
3725
- slotResumableUntil: Type$1.Union([IsoTimestamp, Type$1.Null()])
3726
- }, {
3727
- $id: "DaemonState",
3728
- additionalProperties: false
3729
- });
3730
- var ClaimConditionSchema = Type$1.Union([
3731
- Type$1.Object({
3732
- op: Type$1.Literal("all"),
3733
- conditions: Type$1.Array(Type$1.Ref("ClaimCondition"), {
3734
- minItems: 1,
3735
- maxItems: 8
3736
- })
3737
- }, { additionalProperties: false }),
3738
- Type$1.Object({
3739
- op: Type$1.Literal("any"),
3740
- conditions: Type$1.Array(Type$1.Ref("ClaimCondition"), {
3741
- minItems: 1,
3742
- maxItems: 8
3743
- })
3744
- }, { additionalProperties: false }),
3745
- Type$1.Object({
3746
- op: Type$1.Literal("task_status"),
3747
- taskId: Uuid,
3748
- statuses: Type$1.Array(Type$1.Ref("TaskStatus"), {
3749
- minItems: 1,
3750
- maxItems: 8
3751
- })
3752
- }, { additionalProperties: false }),
3753
- Type$1.Object({
3754
- op: Type$1.Literal("task_accepted"),
3755
- taskId: Uuid
3756
- }, { additionalProperties: false })
3757
- ], { $id: "ClaimCondition" });
3758
- var ClaimConditionDefinition = Type$1.Unsafe(ClaimConditionSchema);
3759
- Type$1.Unsafe(Type$1.Cyclic({ ClaimCondition: ClaimConditionDefinition }, "ClaimCondition", { $id: "ClaimCondition" }));
3760
- /**
3761
- * Reference to another task's output or an external artifact.
3762
- * Embedded in `tasks.references` JSONB array.
3763
- */
3764
- var TaskRef = Type$1.Object({
3765
- taskId: Type$1.Union([Uuid, Type$1.Null()]),
3766
- outputCid: Type$1.Optional(Cid),
3767
- role: Type$1.Union([
3768
- Type$1.Literal("judged_work"),
3769
- Type$1.Literal("reviewed_diff"),
3770
- Type$1.Literal("target_source"),
3771
- Type$1.Literal("context")
3772
- ]),
3773
- external: Type$1.Optional(Type$1.Object({
3774
- kind: Type$1.Union([
3775
- Type$1.Literal("github_pr"),
3776
- Type$1.Literal("github_issue"),
3777
- Type$1.Literal("http_url")
3778
- ]),
3779
- pr: Type$1.Optional(Type$1.Number()),
3780
- issue: Type$1.Optional(Type$1.Number()),
3781
- url: Type$1.Optional(Type$1.String()),
3782
- commit_sha: Type$1.Optional(Type$1.String()),
3783
- snapshot_cid: Type$1.Optional(Cid)
3784
- })),
3785
- artifact: Type$1.Optional(Type$1.Object({
3786
- cid: Cid,
3787
- attemptN: Type$1.Optional(Type$1.Integer({ minimum: 1 })),
3788
- kind: Type$1.Optional(Type$1.String({
3789
- minLength: 1,
3790
- maxLength: 100
3791
- })),
3792
- title: Type$1.Optional(Type$1.String({
3793
- minLength: 1,
3794
- maxLength: 255
3795
- })),
3796
- contentType: Type$1.Optional(Type$1.String({
3797
- minLength: 1,
3798
- maxLength: 200
3799
- }))
3800
- }, { additionalProperties: false }))
3801
- }, {
3802
- $id: "TaskRef",
3803
- additionalProperties: false
3804
- });
3805
- /**
3806
- * Token / cost accounting for one attempt.
3807
- * Reported by the runtime; persisted per-attempt, also rolled up into
3808
- * `TaskOutput.usage` for convenience.
3809
- */
3810
- var TaskUsage = Type$1.Object({
3811
- inputTokens: Type$1.Integer({ minimum: 0 }),
3812
- outputTokens: Type$1.Integer({ minimum: 0 }),
3813
- cacheReadTokens: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
3814
- cacheWriteTokens: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
3815
- toolCalls: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
3816
- model: Type$1.Optional(Type$1.String()),
3817
- provider: Type$1.Optional(Type$1.String())
3818
- }, {
3819
- $id: "TaskUsage",
3820
- additionalProperties: false
3821
- });
3822
- var TaskRetryDecision = Type$1.Union([Type$1.Literal("retry"), Type$1.Literal("do_not_retry")]);
3823
- var TaskRetryConfidence = Type$1.Union([
3824
- Type$1.Literal("low"),
3825
- Type$1.Literal("medium"),
3826
- Type$1.Literal("high")
3827
- ]);
3828
- var TaskRetrySource = Type$1.Union([
3829
- Type$1.Literal("explicit"),
3830
- Type$1.Literal("deterministic"),
3831
- Type$1.Literal("attempts_exhausted"),
3832
- Type$1.Literal("triage"),
3833
- Type$1.Literal("triage_failed")
3834
- ]);
3835
- var TaskRetryInfo = Type$1.Object({
3836
- source: TaskRetrySource,
3837
- decision: Type$1.Optional(TaskRetryDecision),
3838
- confidence: Type$1.Optional(TaskRetryConfidence),
3839
- reason: Type$1.Optional(Type$1.String())
3840
- }, {
3841
- $id: "TaskRetryInfo",
3842
- additionalProperties: false
3843
- });
3844
- /**
3845
- * Structured error returned from a failed attempt.
3846
- */
3847
- var TaskError = Type$1.Object({
3848
- code: Type$1.String(),
3849
- message: Type$1.String(),
3850
- stack: Type$1.Optional(Type$1.String()),
3851
- retryable: Type$1.Optional(Type$1.Boolean()),
3852
- retry: Type$1.Optional(TaskRetryInfo)
3853
- }, {
3854
- $id: "TaskError",
3855
- additionalProperties: false
3856
- });
3857
- Type$1.Object({
3858
- agentId: Type$1.Union([Uuid, Type$1.Null()]),
3859
- humanId: Type$1.Union([Uuid, Type$1.Null()])
3860
- }, {
3861
- $id: "ActorPair",
3862
- additionalProperties: false
3863
- });
3864
- Type$1.Object({
3865
- id: Uuid,
3866
- taskType: Type$1.String({ minLength: 1 }),
3867
- title: Type$1.Union([Type$1.String(), Type$1.Null()]),
3868
- tags: Type$1.Array(Type$1.String()),
3869
- teamId: Uuid,
3870
- diaryId: Type$1.Union([Uuid, Type$1.Null()]),
3871
- outputKind: OutputKind,
3872
- input: Type$1.Record(Type$1.String(), Type$1.Unknown()),
3873
- inputSchemaCid: Cid,
3874
- inputCid: Cid,
3875
- references: Type$1.Array(TaskRef),
3876
- correlationId: Type$1.Union([Uuid, Type$1.Null()]),
3877
- proposedByAgentId: Type$1.Union([Uuid, Type$1.Null()]),
3878
- proposedByHumanId: Type$1.Union([Uuid, Type$1.Null()]),
3879
- acceptedAttemptN: Type$1.Union([Type$1.Number(), Type$1.Null()]),
3880
- claimCondition: Type$1.Union([Type$1.Unsafe(Type$1.Ref("ClaimCondition")), Type$1.Null()]),
3881
- requiredExecutorTrustLevel: ExecutorTrustLevel,
3882
- allowedProfiles: Type$1.Array(RuntimeProfileRef, { maxItems: 16 }),
3883
- status: TaskStatus,
3884
- queuedAt: IsoTimestamp,
3885
- completedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
3886
- expiresAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
3887
- cancelledByAgentId: Type$1.Union([Uuid, Type$1.Null()]),
3888
- cancelledByHumanId: Type$1.Union([Uuid, Type$1.Null()]),
3889
- cancelReason: Type$1.Union([Type$1.String(), Type$1.Null()]),
3890
- maxAttempts: Type$1.Number({ minimum: 1 }),
3891
- dispatchTimeoutSec: Type$1.Union([Type$1.Integer({
3892
- minimum: 1,
3893
- maximum: 86400
3894
- }), Type$1.Null()]),
3895
- runningTimeoutSec: Type$1.Union([Type$1.Integer({
3896
- minimum: 1,
3897
- maximum: 86400
3898
- }), Type$1.Null()])
3899
- }, {
3900
- $id: "Task",
3901
- additionalProperties: false
3902
- });
3903
- Type$1.Object({
3904
- taskId: Uuid,
3905
- attemptN: Type$1.Number({ minimum: 1 }),
3906
- claimedByAgentId: Uuid,
3907
- leaseId: Type$1.Union([Uuid, Type$1.Null()]),
3908
- runtimeProfileId: Type$1.Union([Uuid, Type$1.Null()]),
3909
- runtimeProfileRevision: Type$1.Union([Type$1.Integer({ minimum: 1 }), Type$1.Null()]),
3910
- policySnapshotHash: Type$1.Union([Type$1.String({ pattern: "^sha256:[0-9a-f]{64}$" }), Type$1.Null()]),
3911
- runtimeId: Type$1.Union([Uuid, Type$1.Null()]),
3912
- claimedAt: IsoTimestamp,
3913
- startedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
3914
- completedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
3915
- status: TaskAttemptStatus,
3916
- output: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
3917
- outputCid: Type$1.Union([Cid, Type$1.Null()]),
3918
- claimedExecutorFingerprint: Type$1.Union([Cid, Type$1.Null()]),
3919
- claimedExecutorManifest: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
3920
- completedExecutorFingerprint: Type$1.Union([Cid, Type$1.Null()]),
3921
- completedExecutorManifest: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
3922
- error: Type$1.Union([TaskError, Type$1.Null()]),
3923
- usage: Type$1.Union([TaskUsage, Type$1.Null()]),
3924
- contentSignature: Type$1.Union([Type$1.String(), Type$1.Null()]),
3925
- signedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
3926
- daemonState: Type$1.Union([DaemonState, Type$1.Null()])
3927
- }, {
3928
- $id: "TaskAttempt",
3929
- additionalProperties: true
3930
- });
3931
- Type$1.Object({
3932
- taskId: Uuid,
3933
- attemptN: Type$1.Number({ minimum: 1 }),
3934
- seq: Type$1.Number({
3935
- minimum: 0,
3936
- description: "Monotonically increasing integer assigned by the server. Use as the afterSeq cursor on the list-messages endpoint to poll for new messages without re-fetching earlier ones."
3937
- }),
3938
- timestamp: IsoTimestamp,
3939
- kind: TaskMessageKind,
3940
- payload: Type$1.Record(Type$1.String(), Type$1.Unknown())
3941
- }, {
3942
- $id: "TaskMessage",
3943
- additionalProperties: false
3944
- });
3945
- Type$1.Object({
3946
- taskId: Uuid,
3947
- attemptN: Type$1.Number({ minimum: 1 }),
3948
- status: Type$1.Union([
3949
- Type$1.Literal("completed"),
3950
- Type$1.Literal("failed"),
3951
- Type$1.Literal("cancelled")
3952
- ]),
3953
- output: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
3954
- outputCid: Type$1.Union([Cid, Type$1.Null()]),
3955
- usage: TaskUsage,
3956
- durationMs: Type$1.Number({ minimum: 0 }),
3957
- error: Type$1.Optional(TaskError),
3958
- contentSignature: Type$1.Optional(Type$1.String())
3959
- }, {
3960
- $id: "TaskOutput",
3961
- additionalProperties: false
3962
- });
3963
- Type$1.Object({
3964
- runtimeId: Uuid,
3965
- agentId: Uuid,
3966
- timestamp: IsoTimestamp,
3967
- status: Type$1.Union([
3968
- Type$1.Literal("idle"),
3969
- Type$1.Literal("busy"),
3970
- Type$1.Literal("draining")
3971
- ]),
3972
- activeTaskIds: Type$1.Array(Uuid),
3973
- supportedTaskTypes: Type$1.Array(Type$1.String())
3974
- }, {
3975
- $id: "RuntimeHeartbeat",
3976
- additionalProperties: false
3977
- });
3978
- //#endregion
3979
- //#region src/snapshot.ts
3980
- /**
3981
- * Snapshot builder with auto-build and caching.
3982
- *
3983
- * Builds a Gondolin VM snapshot in two layers:
3984
- * 1. Base (always): Alpine essentials, git, gh CLI, MoltNet CLI, agent user
3985
- * 2. User setup commands (optional): arbitrary shell commands on top of the base
3986
- *
3987
- * Consumers provide raw shell commands — no abstraction over package managers
3988
- * or runtimes. The base provides curl, git, tar, jq; everything else is up to
3989
- * the setup commands.
3990
- *
3991
- * Caches in a platform-appropriate directory:
3992
- * - macOS: ~/Library/Caches/moltnet/gondolin/
3993
- * - Linux: ~/.cache/moltnet/gondolin/
3994
- *
3995
- * The cache key is a hash of the full config. When any input changes,
3996
- * a new snapshot is built automatically.
3997
- */
3998
- /** Alpine packages whose commands are guaranteed in every snapshot. */
3999
- var BASE_ALPINE_PACKAGE_EXECUTABLES = {
4000
- curl: "curl",
4001
- file: "file",
4002
- git: "git",
4003
- jq: "jq",
4004
- ripgrep: "rg",
4005
- tar: "tar",
4006
- xz: "xz"
4007
- };
4008
- /** Alpine packages required by every snapshot. */
4009
- var BASE_ALPINE_PACKAGES = ["ca-certificates", ...Object.keys(BASE_ALPINE_PACKAGE_EXECUTABLES)];
4010
- /** Commands guaranteed by the base Gondolin snapshot. */
4011
- var GONDOLIN_BASE_EXECUTABLES = Object.freeze([
4012
- ...Object.values(BASE_ALPINE_PACKAGE_EXECUTABLES),
4013
- "gh",
4014
- "moltnet"
4015
- ].sort());
4016
- /** gh CLI version installed in every snapshot. */
4017
- var GH_VERSION = "2.74.0";
4018
- /** MoltNet CLI version — downloaded as a binary, no Node needed. */
4019
- var MOLTNET_CLI_VERSION = "1.37.0";
4020
- /**
4021
- * Resolve guest architecture from host (Gondolin VMs match host arch).
4022
- *
4023
- * The two naming conventions are NOT interchangeable:
4024
- * - `gh` — GitHub release-asset suffix (gh CLI ships `linux_amd64.tar.gz`,
4025
- * `linux_arm64.tar.gz`).
4026
- * - `npm` — npm optionalDependencies naming, which mirrors Node's
4027
- * `process.arch` values (`x64`, `arm64`). The MoltNet CLI is
4028
- * published as `@themoltnet/cli-linux-x64` and
4029
- * `@themoltnet/cli-linux-arm64`, NOT `cli-linux-amd64`.
4030
- */
4031
- function getGuestArch() {
4032
- if (process.arch === "arm64") return {
4033
- gh: "linux_arm64",
4034
- npm: "linux-arm64"
4035
- };
4036
- return {
4037
- gh: "linux_amd64",
4038
- npm: "linux-x64"
4039
- };
4040
- }
4041
- /** Hosts reachable during snapshot build. */
4042
- var SETUP_ALLOWED_HOSTS = [
4043
- "dl-cdn.alpinelinux.org",
4044
- "*.alpinelinux.org",
4045
- "registry.npmjs.org",
4046
- "*.npmjs.org",
4047
- "nodejs.org",
4048
- "*.nodejs.org",
4049
- "unofficial-builds.nodejs.org",
4050
- "github.com",
4051
- "*.github.com",
4052
- "*.githubusercontent.com",
4053
- "objects.githubusercontent.com"
4054
- ];
4055
- var DEFAULT_CONFIG = {};
4056
- function getCacheDir() {
4057
- if (process.platform === "darwin") return path.join(process.env.HOME ?? "/tmp", "Library", "Caches", "moltnet", "gondolin");
4058
- const base = process.env.XDG_CACHE_HOME ?? path.join(process.env.HOME ?? "/tmp", ".cache");
4059
- return path.join(base, "moltnet", "gondolin");
4060
- }
4061
- function computeConfigHash(config) {
4062
- const h = createHash$1("sha256");
4063
- h.update(JSON.stringify({
4064
- baseAlpine: BASE_ALPINE_PACKAGES,
4065
- ghVersion: GH_VERSION,
4066
- cliVersion: MOLTNET_CLI_VERSION,
4067
- config
4068
- }));
4069
- return h.digest("hex").slice(0, 12);
4070
- }
4071
- function getSnapshotPath(config) {
4072
- const hash = computeConfigHash(config);
4073
- const dir = path.join(getCacheDir(), `v2-${hash}`);
4074
- return path.join(dir, "snapshot.qcow2");
4075
- }
4076
- /**
4077
- * Ensure a cached snapshot exists, building one if needed.
4078
- * Returns the absolute path to the qcow2 checkpoint file.
4079
- */
4080
- async function ensureSnapshot(options = {}) {
4081
- const config = options.config ?? DEFAULT_CONFIG;
4082
- const log = options.onProgress ?? (() => {});
4083
- const maxCached = options.maxCached ?? 1;
4084
- const snapshotPath = getSnapshotPath(config);
4085
- const snapshotDir = path.dirname(snapshotPath);
4086
- if (existsSync(snapshotPath)) {
4087
- log(`snapshot cache hit: ${snapshotPath}`);
4088
- return snapshotPath;
4089
- }
4090
- log("snapshot cache miss — building (this takes 1-3 minutes)...");
4091
- mkdirSync(snapshotDir, { recursive: true });
4092
- const overlayPath = path.join(snapshotDir, "build.overlay.qcow2");
4093
- if (existsSync(overlayPath)) rmSync(overlayPath);
4094
- if (existsSync(snapshotPath)) rmSync(snapshotPath);
4095
- log("resolving alpine-base image...");
4096
- const assets = loadGuestAssets((await ensureImageSelector("alpine-base")).assetDir);
4097
- const overlaySize = config.overlaySize ?? "3G";
4098
- log(`creating qcow2 overlay (${overlaySize})...`);
4099
- execFileSync("qemu-img", [
4100
- "create",
4101
- "-f",
4102
- "qcow2",
4103
- "-F",
4104
- "raw",
4105
- "-b",
4106
- assets.rootfsPath,
4107
- overlayPath,
4108
- overlaySize
4109
- ], { stdio: "pipe" });
4110
- const { httpHooks } = createHttpHooks({ allowedHosts: [...SETUP_ALLOWED_HOSTS, ...config.allowedHosts ?? []] });
4111
- log("booting VM for setup...");
4112
- const vm = await VM.create({
4113
- httpHooks,
4114
- env: {
4115
- PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/go/bin",
4116
- HOME: "/root",
4117
- GOROOT: "/usr/lib/go",
4118
- GOPATH: "/root/go"
4119
- },
4120
- sandbox: {
4121
- rootDiskPath: overlayPath,
4122
- rootDiskFormat: "qcow2",
4123
- rootDiskReadOnly: false,
4124
- rootDiskDeleteOnClose: false
4125
- }
4126
- });
4127
- try {
4128
- await buildSnapshot(vm, config, log);
4129
- log("creating checkpoint...");
4130
- await vm.checkpoint(snapshotPath);
4131
- log(`snapshot saved: ${snapshotPath}`);
4132
- } finally {
4133
- try {
4134
- await vm.close();
4135
- } catch {}
4136
- if (existsSync(overlayPath)) rmSync(overlayPath, { force: true });
4137
- }
4138
- pruneOldSnapshots(maxCached, snapshotDir);
4139
- return snapshotPath;
4140
- }
4141
- /** Helper: run a command in the VM, throw on failure. */
4142
- async function run(vm, log, label, cmd) {
4143
- log(label);
4144
- const r = await vm.exec(cmd);
4145
- if (r.exitCode !== 0) {
4146
- const output = [r.stderr, r.stdout].filter(Boolean).join("\n").slice(0, 800);
4147
- throw new Error(`snapshot build "${label}" failed (exit ${r.exitCode}):\n${output}`);
4148
- }
4149
- }
4150
- async function buildSnapshot(vm, config, log) {
4151
- await run(vm, log, "resizing rootfs...", "apk add --no-cache e2fsprogs-extra >/dev/null 2>&1 && resize2fs /dev/vda 2>/dev/null");
4152
- await run(vm, log, `installing base packages: ${BASE_ALPINE_PACKAGES.join(" ")}`, `apk add --no-cache ${BASE_ALPINE_PACKAGES.join(" ")}`);
4153
- const arch = getGuestArch();
4154
- await run(vm, log, `installing gh ${GH_VERSION} (${arch.gh})...`, `sh -eu -c '
4155
- curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_${arch.gh}.tar.gz" -o /tmp/gh.tar.gz
4156
- tar -xzf /tmp/gh.tar.gz -C /tmp
4157
- mv /tmp/gh_${GH_VERSION}_${arch.gh}/bin/gh /usr/local/bin/gh
4158
- chmod +x /usr/local/bin/gh
4159
- rm -rf /tmp/gh.tar.gz /tmp/gh_${GH_VERSION}_${arch.gh}
4160
- gh --version
4161
- '`);
4162
- await run(vm, log, `installing moltnet CLI ${MOLTNET_CLI_VERSION} (${arch.npm})...`, `sh -eu -c '
4163
- curl -fsSL "https://registry.npmjs.org/@themoltnet/cli-${arch.npm}/-/cli-${arch.npm}-${MOLTNET_CLI_VERSION}.tgz" -o /tmp/moltnet.tgz
4164
- tar -xzf /tmp/moltnet.tgz -C /tmp
4165
- mv /tmp/package/bin/moltnet /usr/local/bin/moltnet
4166
- chmod +x /usr/local/bin/moltnet
4167
- rm -rf /tmp/moltnet.tgz /tmp/package
4168
- '`);
4169
- await run(vm, log, "creating agent user...", `sh -eu -c '
4170
- addgroup -g 501 agent 2>/dev/null || true
4171
- adduser -D -u 501 -G agent -h /home/agent -s /bin/sh agent 2>/dev/null || true
4172
- mkdir -p /home/agent/.moltnet /home/agent/.cache
4173
- chown -R agent:agent /home/agent
4174
- chmod 644 /etc/gondolin/mitm/ca.crt 2>/dev/null || true
4175
- '`);
4176
- await run(vm, log, "configuring DNS resolvers...", `sh -c 'echo "nameserver 8.8.8.8
4177
- nameserver 1.1.1.1" > /etc/resolv.conf'`);
4178
- if (config.setupCommands?.length) for (let i = 0; i < config.setupCommands.length; i++) await run(vm, log, `setup [${i + 1}/${config.setupCommands.length}]...`, config.setupCommands[i]);
4179
- }
4180
- function pruneOldSnapshots(maxCached, currentDir) {
4181
- const cacheRoot = getCacheDir();
4182
- if (!existsSync(cacheRoot)) return;
4183
- const entries = readdirSync(cacheRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("v")).map((e) => {
4184
- const fullPath = path.join(cacheRoot, e.name);
4185
- return {
4186
- path: fullPath,
4187
- mtime: statSync(fullPath).mtimeMs
4188
- };
4189
- }).sort((a, b) => b.mtime - a.mtime);
4190
- for (const entry of entries.slice(maxCached + 1)) if (entry.path !== currentDir) rmSync(entry.path, {
4191
- recursive: true,
4192
- force: true
2843
+ function definePiBrokeredHttpSecret(options) {
2844
+ const descriptor = canonicalizeBrokeredHttpSecretDescriptor(options);
2845
+ return Object.freeze({
2846
+ kind: "brokered_http_secret",
2847
+ descriptor,
2848
+ resolve: options.resolve
4193
2849
  });
4194
2850
  }
4195
- //#endregion
4196
- //#region src/runtime-definition.ts
4197
- var PI_RUNTIME_DEFINITION_VERSION = "moltnet:pi-runtime:v1";
4198
- var PI_EXECUTOR_MANIFEST_VERSION = "moltnet:executor-manifest:v1";
4199
2851
  function definePiTool(input, options = {}) {
4200
2852
  if ("descriptor" in input) {
4201
2853
  assertToolName(input.descriptor.name);
@@ -4252,7 +2904,7 @@ function defineGondolinTemplate(options) {
4252
2904
  executables,
4253
2905
  resumeCommands,
4254
2906
  async resolve(context = {}) {
4255
- const checkpointPath = options.resolveCheckpoint ? await options.resolveCheckpoint(context) : options.checkpointPath ?? await ensureSnapshot({
2907
+ const checkpointPath = options.resolveCheckpoint ? await options.resolveCheckpoint(context) : options.checkpointPath ?? await ensureSnapshot$1({
4256
2908
  config: options.snapshot,
4257
2909
  onProgress: context.onProgress
4258
2910
  });
@@ -4285,17 +2937,28 @@ function definePiRuntime(options) {
4285
2937
  const names = /* @__PURE__ */ new Map();
4286
2938
  for (const tool of options.tools ?? []) claimToolName(names, tool.descriptor.name, "tool contribution");
4287
2939
  for (const extension of options.extensions ?? []) for (const name of extension.declaredTools) claimToolName(names, name, `extension "${extension.id}"`);
2940
+ const secretIds = /* @__PURE__ */ new Set();
2941
+ const secretEnvNames = /* @__PURE__ */ new Set();
2942
+ for (const secret of options.brokeredHttpSecrets ?? []) {
2943
+ const { id, guestEnv } = secret.descriptor;
2944
+ if (secretIds.has(id)) throw new Error(`Duplicate brokered HTTP secret id "${id}"`);
2945
+ if (secretEnvNames.has(guestEnv)) throw new Error(`Duplicate brokered HTTP secret guest env "${guestEnv}"`);
2946
+ secretIds.add(id);
2947
+ secretEnvNames.add(guestEnv);
2948
+ }
4288
2949
  return Object.freeze({
4289
2950
  schemaVersion: PI_RUNTIME_DEFINITION_VERSION,
4290
2951
  id: options.id,
4291
2952
  version: options.version,
4292
2953
  runtimeKind: options.runtimeKind ?? "gondolin_pi",
4293
2954
  vm: options.vm,
2955
+ brokeredHttpSecrets: Object.freeze([...options.brokeredHttpSecrets ?? []]),
4294
2956
  tools: Object.freeze([...options.tools ?? []]),
4295
2957
  extensions: Object.freeze([...options.extensions ?? []])
4296
2958
  });
4297
2959
  }
4298
2960
  async function buildPiExecutorManifest(input) {
2961
+ const brokeredHttpSecrets = input.runtime.brokeredHttpSecrets ?? [];
4299
2962
  const descriptors = [...(input.builtInTools ?? []).map((descriptor) => ({
4300
2963
  descriptor,
4301
2964
  scope: "parent_and_subagents"
@@ -4335,6 +2998,14 @@ async function buildPiExecutorManifest(input) {
4335
2998
  templateFingerprint: input.template.fingerprint,
4336
2999
  guestAssetBuildId: input.template.guestAssetBuildId
4337
3000
  },
3001
+ ...brokeredHttpSecrets.length > 0 && { brokeredHttpSecrets: brokeredHttpSecrets.map(({ descriptor }) => ({
3002
+ id: descriptor.id,
3003
+ guestEnv: descriptor.guestEnv,
3004
+ hosts: [...descriptor.hosts],
3005
+ protocol: descriptor.protocol ?? "https",
3006
+ ports: [...descriptor.ports ?? [descriptor.protocol === "http" ? 80 : 443]],
3007
+ required: descriptor.required !== false
3008
+ })).sort((left, right) => left.id.localeCompare(right.id)) },
4338
3009
  tools,
4339
3010
  extensions: input.runtime.extensions.map((extension) => ({
4340
3011
  id: extension.id,
@@ -4344,6 +3015,109 @@ async function buildPiExecutorManifest(input) {
4344
3015
  executables: input.template.executables
4345
3016
  };
4346
3017
  }
3018
+ async function materializePiBrokeredHttpSecrets(input) {
3019
+ const timeoutMs = input.timeoutMs ?? 3e4;
3020
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_BROKERED_HTTP_SECRET_RESOLUTION_TIMEOUT_MS) throw new Error(`Brokered HTTP secret resolution timeout must be an integer between 1 and ${MAX_BROKERED_HTTP_SECRET_RESOLUTION_TIMEOUT_MS}`);
3021
+ if (input.signal?.aborted) throw brokeredHttpSecretAbortError();
3022
+ const contributions = input.runtime.brokeredHttpSecrets ?? [];
3023
+ const batchController = new AbortController();
3024
+ const abortBatchFromAttempt = () => batchController.abort(input.signal?.reason);
3025
+ input.signal?.addEventListener("abort", abortBatchFromAttempt, { once: true });
3026
+ try {
3027
+ const outcomes = await Promise.all(contributions.map((contribution, index) => materializeSinglePiBrokeredHttpSecret({
3028
+ contribution,
3029
+ context: input.context,
3030
+ batchSignal: batchController.signal,
3031
+ timeoutMs,
3032
+ index
3033
+ }).then((outcome) => {
3034
+ if (outcome.kind === "failure" && !batchController.signal.aborted) batchController.abort(outcome.error);
3035
+ return outcome;
3036
+ })));
3037
+ if (input.signal?.aborted) throw brokeredHttpSecretAbortError();
3038
+ const failures = outcomes.filter((outcome) => outcome.kind === "failure").sort((left, right) => Number(left.error.retryable) - Number(right.error.retryable) || left.index - right.index);
3039
+ if (failures[0]) throw failures[0].error;
3040
+ return outcomes.filter((outcome) => outcome.kind === "success").sort((left, right) => left.index - right.index).map(({ binding }) => binding);
3041
+ } finally {
3042
+ input.signal?.removeEventListener("abort", abortBatchFromAttempt);
3043
+ }
3044
+ }
3045
+ /** One auditable resolver lifecycle; the batch coordinator owns precedence. */
3046
+ async function materializeSinglePiBrokeredHttpSecret(input) {
3047
+ const { descriptor, resolve } = input.contribution;
3048
+ if (input.batchSignal.aborted) return {
3049
+ kind: "batch-abort",
3050
+ index: input.index
3051
+ };
3052
+ const controller = new AbortController();
3053
+ let timedOut = false;
3054
+ const abortFromBatch = () => controller.abort(input.batchSignal.reason);
3055
+ input.batchSignal.addEventListener("abort", abortFromBatch, { once: true });
3056
+ const timeout = setTimeout(() => {
3057
+ timedOut = true;
3058
+ controller.abort();
3059
+ }, input.timeoutMs);
3060
+ const abortOutcome = new Promise((resolveAbort) => {
3061
+ const resolveFromAbort = () => resolveAbort({ kind: "abort" });
3062
+ if (controller.signal.aborted) resolveFromAbort();
3063
+ else controller.signal.addEventListener("abort", resolveFromAbort, { once: true });
3064
+ });
3065
+ try {
3066
+ let resolverOutcome;
3067
+ try {
3068
+ resolverOutcome = Promise.resolve(resolve({
3069
+ ...input.context,
3070
+ signal: controller.signal
3071
+ })).then((value) => ({
3072
+ kind: "value",
3073
+ value
3074
+ }), (error) => ({
3075
+ kind: "error",
3076
+ error
3077
+ }));
3078
+ } catch (error) {
3079
+ resolverOutcome = Promise.resolve({
3080
+ kind: "error",
3081
+ error
3082
+ });
3083
+ }
3084
+ const outcome = await Promise.race([resolverOutcome, abortOutcome]);
3085
+ if (outcome.kind === "abort") {
3086
+ if (!timedOut) return {
3087
+ kind: "batch-abort",
3088
+ index: input.index
3089
+ };
3090
+ return {
3091
+ kind: "failure",
3092
+ index: input.index,
3093
+ error: new PiBrokeredHttpSecretResolutionError(descriptor.id, `Brokered HTTP secret "${descriptor.id}" resolution timed out`, true)
3094
+ };
3095
+ }
3096
+ if (outcome.kind === "error") return {
3097
+ kind: "failure",
3098
+ index: input.index,
3099
+ error: outcome.error instanceof PiBrokeredHttpSecretResolutionError ? outcome.error : new PiBrokeredHttpSecretResolutionError(descriptor.id, `Brokered HTTP secret "${descriptor.id}" resolution failed`, true)
3100
+ };
3101
+ if ((outcome.value === void 0 || outcome.value === "") && descriptor.required !== false) return {
3102
+ kind: "failure",
3103
+ index: input.index,
3104
+ error: new PiBrokeredHttpSecretResolutionError(descriptor.id, `Required brokered HTTP secret "${descriptor.id}" is unavailable`, false)
3105
+ };
3106
+ return {
3107
+ kind: "success",
3108
+ index: input.index,
3109
+ binding: {
3110
+ ...descriptor,
3111
+ hosts: [...descriptor.hosts],
3112
+ ports: descriptor.ports ? [...descriptor.ports] : void 0,
3113
+ value: outcome.value
3114
+ }
3115
+ };
3116
+ } finally {
3117
+ clearTimeout(timeout);
3118
+ input.batchSignal.removeEventListener("abort", abortFromBatch);
3119
+ }
3120
+ }
4347
3121
  async function materializePiTools(input) {
4348
3122
  const contributions = input.runtime.tools.filter((tool) => input.target === "parent" || tool.scope === "parent_and_subagents");
4349
3123
  return (await Promise.all(contributions.map(async (contribution) => {
@@ -4408,633 +3182,6 @@ function assertRuntimeKind(value) {
4408
3182
  if (!RUNTIME_PROFILE_RUNTIME_KIND_REGEXP.test(value)) throw new Error(`Invalid runtime kind "${value}"`);
4409
3183
  }
4410
3184
  //#endregion
4411
- //#region src/abort-utils.ts
4412
- function throwIfAborted(signal, label) {
4413
- if (!signal?.aborted) return;
4414
- throw abortError(label, signal);
4415
- }
4416
- function abortError(label, signal) {
4417
- const reason = signal.reason;
4418
- const suffix = reason instanceof Error ? reason.message : reason === void 0 ? "aborted" : String(reason);
4419
- const err = /* @__PURE__ */ new Error(`${label} aborted: ${suffix}`);
4420
- err.name = "AbortError";
4421
- return err;
4422
- }
4423
- function cleanupLateResource(resourcePromise, opts) {
4424
- resourcePromise.then(async (resource) => {
4425
- try {
4426
- await opts.cleanup(resource);
4427
- } catch (err) {
4428
- opts.onCleanupError?.(err);
4429
- }
4430
- }, () => {});
4431
- }
4432
- async function abortableResource(opts) {
4433
- const { signal } = opts;
4434
- if (!signal) return opts.promise;
4435
- throwIfAborted(signal, opts.label);
4436
- const resourcePromise = Promise.resolve(opts.promise);
4437
- const abortPromise = new Promise((_, reject) => {
4438
- const abort = () => {
4439
- cleanupLateResource(resourcePromise, opts);
4440
- reject(abortError(opts.label, signal));
4441
- };
4442
- signal.addEventListener("abort", abort, { once: true });
4443
- resourcePromise.then(() => signal.removeEventListener("abort", abort), () => signal.removeEventListener("abort", abort));
4444
- });
4445
- return Promise.race([resourcePromise, abortPromise]);
4446
- }
4447
- async function delay(ms, signal, label) {
4448
- if (!signal) {
4449
- await new Promise((resolve) => {
4450
- setTimeout(resolve, ms);
4451
- });
4452
- return;
4453
- }
4454
- throwIfAborted(signal, label);
4455
- await new Promise((resolve, reject) => {
4456
- const listener = () => {
4457
- clearTimeout(timeout);
4458
- reject(abortError(label, signal));
4459
- };
4460
- const timeout = setTimeout(() => {
4461
- signal.removeEventListener("abort", listener);
4462
- resolve();
4463
- }, ms);
4464
- signal.addEventListener("abort", listener, { once: true });
4465
- });
4466
- }
4467
- //#endregion
4468
- //#region src/vm-manager.ts
4469
- /**
4470
- * Memory-backed VFS mount used by the daemon to inject task context
4471
- * (#943 slice 1.5). This is a separate top-level mount because Gondolin
4472
- * mounts can't nest. The agent's Gondolin-bound Read tool accepts paths
4473
- * under this prefix (see toGuestPath in tool-operations.ts).
4474
- *
4475
- * Why MemoryProvider rather than a path under the workspace mount:
4476
- * - Injected task context is ephemeral by intent: per-task-attempt input
4477
- * scoped to the VM lifetime. MemoryProvider models that exactly —
4478
- * in-memory, per-VM-instance, zero host artefacts, automatic
4479
- * cleanup on VM close.
4480
- * - Writing under the workspace mount fails in worktrees because we symlink
4481
- * `.moltnet/` to the main repo (so credentials are reachable from
4482
- * worktrees), and Gondolin's RealFSProvider correctly refuses to
4483
- * create paths whose ancestors' realpath escapes the mount root.
4484
- * That refusal is a deliberate sandbox-escape protection, not a
4485
- * bug. See diary semantic entry cd27d9d3-efdc-4aec-ac0d-5fd8ce258d1f
4486
- * and episodic 7affbfeb-18a2-4963-aeac-c177eb2afa2d for the full
4487
- * investigation and the alternatives we rejected.
4488
- */
4489
- var GUEST_TASK_CONTEXT_MOUNT = "/moltnet-task-context";
4490
- function resolveVfsShadowConfig(config) {
4491
- const patterns = config?.vfs?.shadow ?? [];
4492
- if (patterns.length === 0) return {
4493
- mode: "none",
4494
- patterns: []
4495
- };
4496
- return {
4497
- mode: config?.vfs?.shadowMode ?? "tmpfs",
4498
- patterns
4499
- };
4500
- }
4501
- function shouldRunResumeCommand(entry, ctx) {
4502
- if (typeof entry === "string") return true;
4503
- const workspaceModes = entry.when?.workspaceMode;
4504
- if (workspaceModes && !workspaceModes.includes(ctx.workspaceMode)) return false;
4505
- return true;
4506
- }
4507
- function shouldShadowNodeModulesPath(pathname) {
4508
- const normalized = path.posix.normalize(pathname);
4509
- return normalized === "/node_modules" || normalized.startsWith("/node_modules/") || normalized.endsWith("/node_modules") || normalized.includes("/node_modules/");
4510
- }
4511
- function isNodeModulesBinPath(pathname) {
4512
- const normalized = path.posix.normalize(pathname);
4513
- return normalized.includes("/node_modules/.bin/") || normalized.startsWith("/node_modules/.bin/");
4514
- }
4515
- var AutoParentMemoryProvider = class extends MemoryProvider {
4516
- ensureParentDir(pathname) {
4517
- const parent = path.posix.dirname(path.posix.normalize(pathname));
4518
- if (!parent || parent === "/" || parent === ".") return;
4519
- this.mkdirSync(parent, { recursive: true });
4520
- }
4521
- async mkdir(pathname, options) {
4522
- this.ensureParentDir(pathname);
4523
- return super.mkdir(pathname, options);
4524
- }
4525
- mkdirSync(pathname, options) {
4526
- this.ensureParentDir(pathname);
4527
- return super.mkdirSync(pathname, options);
4528
- }
4529
- async open(pathname, flags, mode) {
4530
- if (isWriteFlag(flags)) this.ensureParentDir(pathname);
4531
- return super.open(pathname, flags, isWriteFlag(flags) && isNodeModulesBinPath(pathname) ? (mode ?? 493) | 73 : mode);
4532
- }
4533
- openSync(pathname, flags, mode) {
4534
- if (isWriteFlag(flags)) this.ensureParentDir(pathname);
4535
- return super.openSync(pathname, flags, isWriteFlag(flags) && isNodeModulesBinPath(pathname) ? (mode ?? 493) | 73 : mode);
4536
- }
4537
- };
4538
- /**
4539
- * Resolve the main worktree root (where .moltnet/ lives — it's untracked,
4540
- * only exists in the main worktree, not in git worktrees).
4541
- */
4542
- function findMainWorktree(startPath = process.cwd()) {
4543
- let output;
4544
- try {
4545
- output = execFileSync("git", [
4546
- "-C",
4547
- startPath,
4548
- "worktree",
4549
- "list",
4550
- "--porcelain"
4551
- ], {
4552
- encoding: "utf8",
4553
- stdio: "pipe"
4554
- });
4555
- } catch (err) {
4556
- const message = err instanceof Error ? err.message : String(err);
4557
- throw new Error(`Git worktree discovery requires a git repository: ${message}`);
4558
- }
4559
- for (const block of output.split("\n\n")) {
4560
- const lines = block.split("\n");
4561
- const wt = lines.find((l) => l.startsWith("worktree "));
4562
- if (wt && !lines.some((l) => l === "bare")) return wt.replace("worktree ", "");
4563
- }
4564
- throw new Error("Could not find main git worktree");
4565
- }
4566
- function resolveVmAgentDir(config) {
4567
- const rootDir = config.agentRootDir ?? findMainWorktree();
4568
- return path.join(rootDir, ".moltnet", config.agentName);
4569
- }
4570
- function loadCredentials(agentDir, mode = "guest-config", onDiagnostic) {
4571
- const moltnetPath = path.join(agentDir, "moltnet.json");
4572
- const agentEnvPath = path.join(agentDir, "env");
4573
- const piAgentDir = resolvePiCodingAgentDir();
4574
- const piAuthPath = path.join(piAgentDir, "auth.json");
4575
- const piAuthJson = existsSync(piAuthPath) ? readFileSync(piAuthPath, "utf8") : null;
4576
- if (mode === "host-authenticated") return {
4577
- moltnetJson: "",
4578
- agentEnvRaw: "",
4579
- piAuthJson,
4580
- agentEnv: {},
4581
- gitconfig: null,
4582
- sshPrivateKey: null,
4583
- sshPublicKey: null,
4584
- allowedSigners: null,
4585
- githubAppPem: null,
4586
- githubAppPemFilename: null
4587
- };
4588
- const hasMoltnetJson = existsSync(moltnetPath);
4589
- const hasAgentEnv = existsSync(agentEnvPath);
4590
- if (!hasMoltnetJson || !hasAgentEnv) throw new Error(`Guest credential mode requires both ${moltnetPath} and ${agentEnvPath}`);
4591
- const moltnetJson = readFileSync(moltnetPath, "utf8");
4592
- const agentEnvRaw = readFileSync(agentEnvPath, "utf8");
4593
- if (moltnetJson.trim() === "") throw new Error(`Agent configuration is empty: ${moltnetPath}`);
4594
- const gitconfigPath = path.join(agentDir, "gitconfig");
4595
- const gitconfig = existsSync(gitconfigPath) ? readFileSync(gitconfigPath, "utf8") : null;
4596
- const sshDir = path.join(agentDir, "ssh");
4597
- const sshPrivateKey = existsSync(path.join(sshDir, "id_ed25519")) ? readFileSync(path.join(sshDir, "id_ed25519"), "utf8") : null;
4598
- const sshPublicKey = existsSync(path.join(sshDir, "id_ed25519.pub")) ? readFileSync(path.join(sshDir, "id_ed25519.pub"), "utf8") : null;
4599
- const allowedSigners = existsSync(path.join(sshDir, "allowed_signers")) ? readFileSync(path.join(sshDir, "allowed_signers"), "utf8") : null;
4600
- let githubAppPem = null;
4601
- let githubAppPemFilename = null;
4602
- const pemPath = (moltnetJson ? JSON.parse(moltnetJson) : null)?.github?.private_key_path;
4603
- if (pemPath) if (!existsSync(pemPath)) onDiagnostic?.({
4604
- event: "vm.credentials.github_key_missing",
4605
- level: "warning",
4606
- credentialMode: mode,
4607
- message: `github.private_key_path not found at ${pemPath}; moltnet github token will fail inside the guest`
4608
- });
4609
- else {
4610
- githubAppPem = readFileSync(pemPath, "utf8");
4611
- githubAppPemFilename = path.basename(pemPath);
4612
- }
4613
- return {
4614
- moltnetJson,
4615
- agentEnvRaw,
4616
- piAuthJson,
4617
- agentEnv: parseEnv(agentEnvRaw),
4618
- gitconfig,
4619
- sshPrivateKey,
4620
- sshPublicKey,
4621
- allowedSigners,
4622
- githubAppPem,
4623
- githubAppPemFilename
4624
- };
4625
- }
4626
- /**
4627
- * Apply agent env vars to the host process, mirroring `moltnet start`.
4628
- * Resolves relative paths (e.g. GIT_CONFIG_GLOBAL) against the repo root.
4629
- */
4630
- function activateAgentEnv(agentEnv, repoRoot) {
4631
- for (const [k, v] of Object.entries(agentEnv)) {
4632
- if (v === void 0 || v === null || v === "") continue;
4633
- let resolved = v;
4634
- if (k === "GIT_CONFIG_GLOBAL" && !path.isAbsolute(v)) resolved = path.join(repoRoot, v);
4635
- process.env[k] = resolved;
4636
- }
4637
- }
4638
- var BASE_ALLOWED_HOSTS = [
4639
- "api.openai.com",
4640
- "*.openai.com",
4641
- "chat.openai.com",
4642
- "chatgpt.com",
4643
- "*.chatgpt.com",
4644
- "registry.npmjs.org",
4645
- "github.com",
4646
- "*.github.com",
4647
- "*.githubusercontent.com",
4648
- "proxy.golang.org",
4649
- "sum.golang.org",
4650
- "golang.org",
4651
- "storage.googleapis.com",
4652
- "*.googlesource.com"
4653
- ];
4654
- var DEFAULT_MOLTNET_API_URL = "https://api.themolt.net";
4655
- /**
4656
- * Host environment names that may intentionally cross into a
4657
- * host-authenticated guest. This local list is the authority boundary;
4658
- * server-supplied runtime-profile `requiredEnv` cannot widen it.
4659
- */
4660
- var HOST_AUTHENTICATED_GUEST_ENV_ALLOWLIST = new Set([
4661
- "ANTHROPIC_API_KEY",
4662
- "OPENAI_API_KEY",
4663
- "OPENAI_BASE_URL",
4664
- "AZURE_OPENAI_API_KEY",
4665
- "AZURE_OPENAI_ENDPOINT",
4666
- "AZURE_OPENAI_API_VERSION",
4667
- "GOOGLE_API_KEY",
4668
- "GEMINI_API_KEY",
4669
- "MISTRAL_API_KEY",
4670
- "GROQ_API_KEY",
4671
- "OPENROUTER_API_KEY",
4672
- "XAI_API_KEY",
4673
- "CEREBRAS_API_KEY",
4674
- "DEEPSEEK_API_KEY",
4675
- "OLLAMA_API_KEY",
4676
- "OLLAMA_BASE_URL",
4677
- "AWS_ACCESS_KEY_ID",
4678
- "AWS_SECRET_ACCESS_KEY",
4679
- "AWS_SESSION_TOKEN",
4680
- "AWS_REGION",
4681
- "AWS_DEFAULT_REGION",
4682
- "GITHUB_TOKEN",
4683
- "GH_TOKEN",
4684
- "LINEAR_API_KEY"
4685
- ]);
4686
- var RESERVED_GUEST_ENVIRONMENT_NAMES = new Set([
4687
- "PATH",
4688
- "HOME",
4689
- "NODE_EXTRA_CA_CERTS",
4690
- "MOLTNET_GUEST_WORKSPACE",
4691
- "GIT_SSH",
4692
- "GIT_SSH_COMMAND",
4693
- "SSH_AUTH_SOCK"
4694
- ]);
4695
- function isReservedGuestEnvironmentName(name) {
4696
- return name.startsWith("MOLTNET_") || name.startsWith("GIT_CONFIG_") || RESERVED_GUEST_ENVIRONMENT_NAMES.has(name);
4697
- }
4698
- var GuestEnvironmentBoundaryError = class extends Error {
4699
- constructor(refusedNames) {
4700
- super(`Guest credential boundary refuses runtime-controlled environment variables: ${refusedNames.join(", ")}. Remove them from the runtime profile; MoltNet operations use the trusted host-side Agent.`);
4701
- this.refusedNames = refusedNames;
4702
- this.name = "GuestEnvironmentBoundaryError";
4703
- }
4704
- };
4705
- function assertGuestEnvironmentBoundary(options) {
4706
- const refusedForwardEnv = (options.forwardEnv ?? []).filter((name) => isReservedGuestEnvironmentName(name) || options.guestCredentialMode === "host-authenticated" && !HOST_AUTHENTICATED_GUEST_ENV_ALLOWLIST.has(name));
4707
- const refusedSandboxEnv = Object.keys(options.sandboxEnv ?? {}).filter(isReservedGuestEnvironmentName);
4708
- const refused = [...new Set([...refusedForwardEnv, ...refusedSandboxEnv])].sort();
4709
- if (refused.length > 0) throw new GuestEnvironmentBoundaryError(refused);
4710
- }
4711
- /** @deprecated Prefer assertGuestEnvironmentBoundary for mode-aware checks. */
4712
- function assertHostAuthenticatedGuestEnvironment(options) {
4713
- assertGuestEnvironmentBoundary({
4714
- guestCredentialMode: "host-authenticated",
4715
- ...options
4716
- });
4717
- }
4718
- /**
4719
- * Return whether two Gondolin hostname globs can match at least one common
4720
- * string. Each `*` is an arbitrary substring, so this walks the product of the
4721
- * two small glob automata instead of relying on exact-string comparisons.
4722
- */
4723
- function hostnamePatternsOverlap(left, right) {
4724
- const a = left.trim().toLowerCase();
4725
- const b = right.trim().toLowerCase();
4726
- if (!a || !b) return false;
4727
- const pending = [[0, 0]];
4728
- const visited = /* @__PURE__ */ new Set();
4729
- while (pending.length > 0) {
4730
- const next = pending.pop();
4731
- if (!next) continue;
4732
- const [aIndex, bIndex] = next;
4733
- const state = `${aIndex}:${bIndex}`;
4734
- if (visited.has(state)) continue;
4735
- visited.add(state);
4736
- if (aIndex === a.length && bIndex === b.length) return true;
4737
- const aChar = a[aIndex];
4738
- const bChar = b[bIndex];
4739
- if (aChar === "*") pending.push([aIndex + 1, bIndex]);
4740
- if (bChar === "*") pending.push([aIndex, bIndex + 1]);
4741
- if (aChar !== void 0 && bChar !== void 0 && (aChar === "*" || bChar === "*" || aChar === bChar)) pending.push([aChar === "*" ? aIndex : aIndex + 1, bChar === "*" ? bIndex : bIndex + 1]);
4742
- }
4743
- return false;
4744
- }
4745
- function assertInternalHostsDoNotOverlapProtectedHosts(internalHosts, protectedHosts) {
4746
- for (const internalHost of internalHosts) {
4747
- const protectedHost = protectedHosts.find((candidate) => hostnamePatternsOverlap(internalHost, candidate));
4748
- if (protectedHost) throw new Error(`sandbox.network.allowedInternalHosts pattern "${internalHost}" overlaps external-only host pattern "${protectedHost}"`);
4749
- }
4750
- }
4751
- /**
4752
- * Run a shell command in the guest and throw if it fails. Mirror of
4753
- * `run()` in `snapshot.ts` for the resume-side hook chain — every
4754
- * setup step is essential to a healthy session, so a silent non-zero
4755
- * exit (e.g. a mount that fails into the FUSE write path, or a
4756
- * consumer-provided resume command that fails to install pnpm) must
4757
- * surface immediately rather than fall through to cryptic agent
4758
- * errors later.
4759
- */
4760
- async function vmRun(vm, label, command, signal) {
4761
- const wrapped = `set -eu\nset -o pipefail\n${command}`;
4762
- throwIfAborted(signal, `resume step "${label}"`);
4763
- const r = await vm.exec([
4764
- "sh",
4765
- "-c",
4766
- wrapped
4767
- ], { signal });
4768
- if (r.exitCode !== 0) {
4769
- const tail = [r.stderr, r.stdout].filter(Boolean).join("\n").slice(-800);
4770
- throw new Error(`resume step "${label}" failed (exit ${r.exitCode}):\n${tail}`);
4771
- }
4772
- }
4773
- function nonErrorMessage(err) {
4774
- if (typeof err === "string") return err;
4775
- try {
4776
- return JSON.stringify(err) ?? "unknown error";
4777
- } catch {
4778
- return "unknown error";
4779
- }
4780
- }
4781
- /**
4782
- * Resume a VM from a checkpoint, inject credentials, configure egress +
4783
- * TLS. Returns the managed VM handle.
4784
- */
4785
- async function resumeVm(config) {
4786
- throwIfAborted(config.signal, "VM resume");
4787
- const agentDir = resolveVmAgentDir(config);
4788
- const guestWorkspace = path.resolve(config.mountPath);
4789
- const guestCredentialMode = config.guestCredentialMode ?? "guest-config";
4790
- if (guestCredentialMode === "guest-config" && !existsSync(agentDir)) throw new Error(`Agent directory not found: ${agentDir}. Run: moltnet register --name ${config.agentName}`);
4791
- assertGuestEnvironmentBoundary({
4792
- guestCredentialMode,
4793
- forwardEnv: config.forwardEnv,
4794
- sandboxEnv: config.sandboxConfig?.env
4795
- });
4796
- config.onDiagnostic?.({
4797
- event: "vm.credentials.mode",
4798
- level: "info",
4799
- credentialMode: guestCredentialMode,
4800
- message: guestCredentialMode === "host-authenticated" ? "MoltNet agent files and non-allowlisted host environment variables are withheld from the guest" : "The complete MoltNet agent configuration is available to the guest"
4801
- });
4802
- const creds = loadCredentials(agentDir, guestCredentialMode, config.onDiagnostic);
4803
- const configuredApiUrl = creds.moltnetJson ? JSON.parse(creds.moltnetJson).endpoints.api : void 0;
4804
- const apiHost = new URL(configuredApiUrl ?? process.env.MOLTNET_API_URL ?? DEFAULT_MOLTNET_API_URL).hostname;
4805
- const runtimeAllowedHosts = config.sandboxConfig?.network?.allowedHosts ?? [];
4806
- const runtimeAllowedInternalHosts = config.sandboxConfig?.network?.allowedInternalHosts ?? [];
4807
- const protectedExternalHosts = [...new Set([
4808
- ...BASE_ALLOWED_HOSTS,
4809
- apiHost,
4810
- ...config.extraAllowedHosts ?? []
4811
- ])];
4812
- assertInternalHostsDoNotOverlapProtectedHosts(runtimeAllowedInternalHosts, protectedExternalHosts);
4813
- const { httpHooks, env: secretEnv } = createHttpHooks({
4814
- allowedHosts: [...new Set([...protectedExternalHosts, ...runtimeAllowedHosts])],
4815
- allowedInternalHosts: runtimeAllowedInternalHosts
4816
- });
4817
- const vmAgentDir = `/home/agent/.moltnet/${config.agentName}`;
4818
- const vmAgentEnv = {};
4819
- for (const [k, v] of Object.entries(creds.agentEnv)) {
4820
- if (v === void 0 || v === "") continue;
4821
- if (k === "GIT_CONFIG_GLOBAL") vmAgentEnv[k] = `${vmAgentDir}/gitconfig`;
4822
- else if (k.endsWith("_PRIVATE_KEY_PATH")) vmAgentEnv[k] = `${vmAgentDir}/${path.basename(v)}`;
4823
- else vmAgentEnv[k] = v;
4824
- }
4825
- if (creds.moltnetJson) vmAgentEnv.MOLTNET_CREDENTIALS_PATH = `${vmAgentDir}/moltnet.json`;
4826
- const vfsConfig = resolveVfsShadowConfig(config.sandboxConfig);
4827
- let workspaceProvider = new RealFSProvider(config.mountPath);
4828
- workspaceProvider = new ShadowProvider(workspaceProvider, {
4829
- shouldShadow: ({ path: shadowPath }) => shouldShadowNodeModulesPath(shadowPath),
4830
- denySymlinkBypass: false,
4831
- tmpfs: new AutoParentMemoryProvider(),
4832
- writeMode: "tmpfs"
4833
- });
4834
- if (vfsConfig.mode !== "none") {
4835
- const predicate = createShadowPathPredicate(vfsConfig.patterns);
4836
- workspaceProvider = new ShadowProvider(workspaceProvider, {
4837
- shouldShadow: predicate,
4838
- writeMode: vfsConfig.mode
4839
- });
4840
- }
4841
- if (guestCredentialMode === "host-authenticated") workspaceProvider = new ShadowProvider(workspaceProvider, {
4842
- shouldShadow: ({ path: shadowPath }) => shadowPath.split("/").includes(".moltnet"),
4843
- denySymlinkBypass: true,
4844
- writeMode: "deny"
4845
- });
4846
- const forwardedEnv = {};
4847
- for (const name of config.forwardEnv ?? []) {
4848
- const value = process.env[name];
4849
- if (value === void 0 || value === "") continue;
4850
- forwardedEnv[name] = value;
4851
- }
4852
- const envOverrides = config.sandboxConfig?.env ?? {};
4853
- const vmEnv = {
4854
- ...secretEnv,
4855
- ...vmAgentEnv,
4856
- ...forwardedEnv,
4857
- PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/go/bin",
4858
- HOME: "/home/agent",
4859
- NODE_NO_WARNINGS: "1",
4860
- NODE_EXTRA_CA_CERTS: "/etc/ssl/certs/ca-certificates.crt",
4861
- ...envOverrides,
4862
- MOLTNET_GUEST_WORKSPACE: guestWorkspace
4863
- };
4864
- const resources = config.sandboxConfig?.resources;
4865
- const workspaceMode = config.workspaceMode ?? "shared_mount";
4866
- const vm = await abortableResource({
4867
- promise: VmCheckpoint.load(config.checkpointPath).resume({
4868
- httpHooks,
4869
- env: vmEnv,
4870
- ...resources?.memory && { memory: resources.memory },
4871
- ...resources?.cpus && { cpus: resources.cpus },
4872
- vfs: { mounts: {
4873
- [guestWorkspace]: workspaceProvider,
4874
- [GUEST_TASK_CONTEXT_MOUNT]: new MemoryProvider()
4875
- } }
4876
- }),
4877
- signal: config.signal,
4878
- label: "VM resume",
4879
- cleanup: (resumedVm) => resumedVm.close(),
4880
- onCleanupError: (err) => {
4881
- const message = err instanceof Error ? err.message : String(err);
4882
- process.stderr.write(`[vm] aborted resume late vm.close() failed: ${message}\n`);
4883
- }
4884
- });
4885
- try {
4886
- await vmRun(vm, "TLS certificates", `
4887
- cp /etc/gondolin/mitm/ca.crt /usr/local/share/ca-certificates/gondolin-mitm.crt
4888
- update-ca-certificates 2>/dev/null
4889
- cat /etc/gondolin/mitm/ca.crt >> /etc/ssl/certs/ca-certificates.crt
4890
- `, config.signal);
4891
- await vmRun(vm, "DNS resolvers", `printf 'nameserver 8.8.8.8\\nnameserver 1.1.1.1\\n' > /etc/resolv.conf`, config.signal);
4892
- await vmRun(vm, "git safe.directory", `git config --system --add safe.directory '*'`, config.signal);
4893
- for (const [i, entry] of (config.sandboxConfig?.resumeCommands ?? []).entries()) {
4894
- if (!shouldRunResumeCommand(entry, { workspaceMode })) continue;
4895
- const { run, retries, backoffMs } = typeof entry === "string" ? {
4896
- run: entry,
4897
- retries: 0,
4898
- backoffMs: 2e3
4899
- } : {
4900
- run: entry.run,
4901
- retries: entry.retries ?? 0,
4902
- backoffMs: entry.retryBackoffMs ?? 2e3
4903
- };
4904
- const label = `resumeCommands[${i}]`;
4905
- let lastErr;
4906
- for (let attempt = 0; attempt <= retries; attempt++) try {
4907
- await vmRun(vm, label, run, config.signal);
4908
- lastErr = void 0;
4909
- break;
4910
- } catch (err) {
4911
- lastErr = err;
4912
- if (attempt === retries) break;
4913
- await delay((attempt + 1) * backoffMs, config.signal, label);
4914
- }
4915
- if (lastErr) throw lastErr instanceof Error ? lastErr : new Error(nonErrorMessage(lastErr));
4916
- }
4917
- const vmSshDir = `${vmAgentDir}/ssh`;
4918
- const hasAgentFiles = guestCredentialMode === "guest-config";
4919
- await vm.exec(hasAgentFiles ? `mkdir -p ${vmAgentDir}/ssh /home/agent/.pi/agent` : "mkdir -p /home/agent/.pi/agent", { signal: config.signal });
4920
- if (creds.piAuthJson !== null) await vm.fs.writeFile("/home/agent/.pi/agent/auth.json", creds.piAuthJson, {
4921
- mode: 384,
4922
- signal: config.signal
4923
- });
4924
- if (hasAgentFiles) {
4925
- const vmMoltnetJson = rewriteMoltnetJsonPaths(creds.moltnetJson, vmAgentDir, vmSshDir, creds.githubAppPemFilename);
4926
- await vm.fs.writeFile(`${vmAgentDir}/moltnet.json`, vmMoltnetJson, {
4927
- mode: 384,
4928
- signal: config.signal
4929
- });
4930
- await vm.fs.writeFile(`${vmAgentDir}/env`, creds.agentEnvRaw, {
4931
- mode: 384,
4932
- signal: config.signal
4933
- });
4934
- if (creds.gitconfig) {
4935
- const vmGitconfig = rewriteGitconfigPaths(creds.gitconfig, vmSshDir, vmAgentDir);
4936
- await vm.fs.writeFile(`${vmAgentDir}/gitconfig`, vmGitconfig, {
4937
- mode: 420,
4938
- signal: config.signal
4939
- });
4940
- }
4941
- if (creds.sshPrivateKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519`, creds.sshPrivateKey, {
4942
- mode: 384,
4943
- signal: config.signal
4944
- });
4945
- if (creds.sshPublicKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519.pub`, creds.sshPublicKey, {
4946
- mode: 420,
4947
- signal: config.signal
4948
- });
4949
- if (creds.allowedSigners) await vm.fs.writeFile(`${vmSshDir}/allowed_signers`, creds.allowedSigners, {
4950
- mode: 420,
4951
- signal: config.signal
4952
- });
4953
- if (creds.githubAppPem && creds.githubAppPemFilename) await vm.fs.writeFile(`${vmAgentDir}/${creds.githubAppPemFilename}`, creds.githubAppPem, {
4954
- mode: 384,
4955
- signal: config.signal
4956
- });
4957
- }
4958
- await vm.exec(hasAgentFiles ? "chown -R agent:agent /home/agent/.pi /home/agent/.moltnet" : "chown -R agent:agent /home/agent/.pi", { signal: config.signal });
4959
- return {
4960
- vm,
4961
- credentials: creds,
4962
- mountPath: config.mountPath,
4963
- guestWorkspace,
4964
- agentDir
4965
- };
4966
- } catch (err) {
4967
- try {
4968
- await vm.close();
4969
- } catch (closeErr) {
4970
- const m = closeErr instanceof Error ? closeErr.message : String(closeErr);
4971
- process.stderr.write(`[vm] post-throw vm.close() failed: ${m}\n`);
4972
- }
4973
- throw err;
4974
- }
4975
- }
4976
- /**
4977
- * Rewrite host-absolute paths inside an agent gitconfig to VM-local
4978
- * equivalents before injecting it into the guest.
4979
- *
4980
- * Two rewrites:
4981
- * - `signingKey = <host path>` → `<vmSshDir>/id_ed25519`
4982
- * - `... credential-helper --credentials <host moltnet.json>`
4983
- * → `<vmAgentDir>/moltnet.json`
4984
- *
4985
- * The credential-helper line is generated host-side by `moltnet github setup`
4986
- * with a host-absolute `--credentials` path; inside the guest that path is
4987
- * invalid, so it must point at the VM-side moltnet.json. The `insteadOf`
4988
- * rewrite rule and every other line are workspace-independent and pass through
4989
- * unchanged. A gitconfig without a credential helper is rewritten only for
4990
- * `signingKey`.
4991
- *
4992
- * This is the single source of truth for git push auth in the guest: the
4993
- * injected gitconfig carries the tokenless mint-on-demand helper, so the VM
4994
- * no longer hand-rolls a credential-helper script or runs an imperative
4995
- * `git config --global ... insteadOf` against the guest $HOME.
4996
- */
4997
- function rewriteGitconfigPaths(gitconfig, vmSshDir, vmAgentDir) {
4998
- return gitconfig.replace(/signingKey\s*=\s*.+/g, `signingKey = ${vmSshDir}/id_ed25519`).replace(/(moltnet github credential-helper --credentials )\S+/g, `$1${vmAgentDir}/moltnet.json`);
4999
- }
5000
- /**
5001
- * Rewrite host-absolute paths inside moltnet.json to VM-local equivalents.
5002
- *
5003
- * Fields rewritten:
5004
- * ssh.private_key_path → <vmSshDir>/<basename of original>
5005
- * ssh.public_key_path → <vmSshDir>/<basename of original>
5006
- * git.config_path → <vmAgentDir>/gitconfig
5007
- * github.private_key_path → <vmAgentDir>/<pemFilename> (if present)
5008
- *
5009
- * All other fields are passed through unchanged.
5010
- * Throws if moltnetJson is not valid JSON — callers must not inject a broken
5011
- * moltnet.json into the guest.
5012
- */
5013
- function rewriteMoltnetJsonPaths(moltnetJson, vmAgentDir, vmSshDir, githubAppPemFilename) {
5014
- const config = JSON.parse(moltnetJson);
5015
- if (config.ssh && typeof config.ssh === "object") {
5016
- const ssh = config.ssh;
5017
- const origPrivate = typeof ssh.private_key_path === "string" ? ssh.private_key_path : null;
5018
- const origPublic = typeof ssh.public_key_path === "string" ? ssh.public_key_path : null;
5019
- config.ssh = {
5020
- ...ssh,
5021
- ...origPrivate !== null && { private_key_path: `${vmSshDir}/${path.basename(origPrivate)}` },
5022
- ...origPublic !== null && { public_key_path: `${vmSshDir}/${path.basename(origPublic)}` }
5023
- };
5024
- }
5025
- if (config.git && typeof config.git === "object") {
5026
- const git = { ...config.git };
5027
- git.config_path = `${vmAgentDir}/gitconfig`;
5028
- config.git = git;
5029
- }
5030
- if (githubAppPemFilename && config.github && typeof config.github === "object") {
5031
- const github = { ...config.github };
5032
- github.private_key_path = `${vmAgentDir}/${githubAppPemFilename}`;
5033
- config.github = github;
5034
- }
5035
- return JSON.stringify(config);
5036
- }
5037
- //#endregion
5038
3185
  //#region src/tool-operations.ts
5039
3186
  /**
5040
3187
  * Gondolin tool operations: redirect pi's built-in tool operations
@@ -5501,7 +3648,7 @@ function decideToolCall(input) {
5501
3648
  return fenced(input.enforcement, "tool_not_permitted", `not permitted by tool policy: ${missing.join(", ")}`, `would block (watch): ${missing.join(", ")}`, missing, missingShellCommands);
5502
3649
  }
5503
3650
  function fingerprintArgv(argv) {
5504
- return `sha256:${createHash$1("sha256").update(JSON.stringify(argv.map((token) => token === null ? { dynamic: true } : token))).digest("hex").slice(0, 16)}`;
3651
+ return `sha256:${createHash("sha256").update(JSON.stringify(argv.map((token) => token === null ? { dynamic: true } : token))).digest("hex").slice(0, 16)}`;
5505
3652
  }
5506
3653
  function toMatchedShellCommand(executable, argvPrefix) {
5507
3654
  return {
@@ -5737,6 +3884,34 @@ function decisionContext(deps) {
5737
3884
  };
5738
3885
  }
5739
3886
  //#endregion
3887
+ //#region src/vm.ts
3888
+ /** Guest path where Pi expects its auth blob. */
3889
+ var PI_GUEST_AUTH_PATH = "/home/agent/.pi/agent/auth.json";
3890
+ /**
3891
+ * Pi's provider authentication as a sandbox `ProviderAuthSource`. CI writes
3892
+ * `auth.json` under `PI_CODING_AGENT_DIR`; local runs fall back to the
3893
+ * canonical `~/.pi/agent` dir when the override is unset.
3894
+ */
3895
+ function piProviderAuth() {
3896
+ return {
3897
+ guestPath: PI_GUEST_AUTH_PATH,
3898
+ load: () => {
3899
+ const authPath = path.join(resolvePiCodingAgentDir(), "auth.json");
3900
+ return existsSync(authPath) ? readFileSync(authPath, "utf8") : null;
3901
+ }
3902
+ };
3903
+ }
3904
+ /**
3905
+ * Resume a Gondolin VM for a Pi session. Identical to the sandbox package's
3906
+ * `resumeVm`, with Pi's provider auth supplied unless the caller overrides it.
3907
+ */
3908
+ function resumeVm(config) {
3909
+ return resumeVm$1({
3910
+ providerAuth: piProviderAuth(),
3911
+ ...config
3912
+ });
3913
+ }
3914
+ //#endregion
5740
3915
  //#region src/runtime/capability-discovery.ts
5741
3916
  var GuestExecutableProbeError = class extends Error {
5742
3917
  code;
@@ -6219,7 +4394,12 @@ function buildSandboxCapabilityInstructions(sandbox, policy) {
6219
4394
  ];
6220
4395
  const externalHosts = [...sandbox.allowedHosts].sort();
6221
4396
  const internalHosts = [...sandbox.allowedInternalHosts].sort();
6222
- lines.push(...externalHosts.length > 0 ? [`- Additional external egress hosts: ${externalHosts.map((host) => `\`${host}\``).join(", ")}.`] : [], ...internalHosts.length > 0 ? [`- Additional internal egress hosts: ${internalHosts.map((host) => `\`${host}\``).join(", ")}.`] : [], "- Runtime service endpoints required for task execution may be available", " in addition to the operator-configured hosts above.");
4397
+ const brokeredSecretEnvNames = [...sandbox.brokeredSecretEnvNames ?? []].sort();
4398
+ lines.push(...externalHosts.length > 0 ? [`- Additional external egress hosts: ${externalHosts.map((host) => `\`${host}\``).join(", ")}.`] : [], ...internalHosts.length > 0 ? [`- Additional internal egress hosts: ${internalHosts.map((host) => `\`${host}\``).join(", ")}.`] : [], ...brokeredSecretEnvNames.length > 0 ? [
4399
+ "- Host-brokered HTTP credentials are available only as opaque",
4400
+ ` placeholders in: ${brokeredSecretEnvNames.map((name) => `\`${name}\``).join(", ")}. The host proxy may substitute them only for their`,
4401
+ " declared destination hosts. Do not print, persist, or move them."
4402
+ ] : [], "- Runtime service endpoints required for task execution may be available", " in addition to the operator-configured hosts above.");
6223
4403
  return lines.join("\n");
6224
4404
  }
6225
4405
  function shellExecutableIsAvailable(policy, sandbox, executable) {
@@ -6232,20 +4412,27 @@ function buildCredentialInstructions(policy, sandbox) {
6232
4412
  const lines = [
6233
4413
  "## Identity & credentials",
6234
4414
  "",
6235
- "- Your credentials live at `/home/agent/.moltnet/<agent>/moltnet.json`",
6236
- " with the gitconfig and SSH key alongside. Do not move, copy, or expose",
6237
- " these files outside the VM."
4415
+ "- Long-lived MoltNet identity and signing credentials remain on the",
4416
+ " trusted daemon host. Guest credential files, SSH signing keys, GitHub",
4417
+ " App private keys, and credential helpers are not supported guest",
4418
+ " capabilities. Do not inspect or use them even if transitional",
4419
+ " compatibility plumbing makes one visible.",
4420
+ "- Use structured `moltnet_*` tools for authenticated MoltNet operations.",
4421
+ " Do not try to recover host configuration through shell commands."
6238
4422
  ];
6239
4423
  const moltnetAvailable = shellExecutableIsAvailable(policy, sandbox, "moltnet");
6240
4424
  const ghAvailable = shellExecutableIsAvailable(policy, sandbox, "gh");
6241
4425
  const gitAvailable = shellExecutableIsAvailable(policy, sandbox, "git");
6242
- if (moltnetAvailable) lines.push("- When authorized by the effective shell policy, use the installed", " `moltnet` binary on `PATH`; never invoke a cached or `npx` copy.");
4426
+ if (moltnetAvailable) lines.push("- The installed `moltnet` binary has no guest identity credentials;", " do not use it for authenticated operations.");
6243
4427
  if (ghAvailable) {
6244
- lines.push("- This headless VM has no human GitHub token fallback. Every authorized", " `gh` write must use an inline App token.");
6245
- if (moltnetAvailable) lines.push("", " ```bash", " CREDS=\"$(cd \"$(dirname \"$GIT_CONFIG_GLOBAL\")\" && pwd)/moltnet.json\"", " GH_TOKEN=$(moltnet github token --credentials \"$CREDS\") gh <command>", " ```");
6246
- else lines.push("- The effective policy does not authorize the `moltnet` token-minting", " command, so do not attempt a GitHub write.");
4428
+ const githubPlaceholder = (sandbox?.brokeredSecretEnvNames ?? []).find((name) => name === "GH_TOKEN" || name === "GITHUB_TOKEN");
4429
+ lines.push(...githubPlaceholder ? [
4430
+ `- GitHub CLI authentication uses the host-brokered \`${githubPlaceholder}\``,
4431
+ " placeholder. Use it normally for policy-authorized HTTPS",
4432
+ " requests; it is not a reusable or inspectable token."
4433
+ ] : ["- No brokered GitHub credential is active. Authenticated `gh`", " operations are unavailable; do not mint or recover a host token."]);
6247
4434
  }
6248
- if (gitAvailable) lines.push("- An authorized `git push` uses the injected credential helper and does", " not need `GH_TOKEN`.");
4435
+ if (gitAvailable) lines.push("- Local Git commands run inside the guest. No signing key or Git", " credential helper is injected; signing and authenticated push", " require an explicitly provided capability.");
6249
4436
  return lines.join("\n");
6250
4437
  }
6251
4438
  /**
@@ -7183,7 +5370,7 @@ function gitRefExists(mainRepo, ref) {
7183
5370
  }
7184
5371
  function findMainWorktreeForDedicatedTask(startPath) {
7185
5372
  try {
7186
- return findMainWorktree(startPath);
5373
+ return findMainWorktree$1(startPath);
7187
5374
  } catch (err) {
7188
5375
  const message = err instanceof Error ? err.message : String(err);
7189
5376
  throw new Error(`Dedicated worktree tasks require a git repository: ${message}`);
@@ -7322,6 +5509,20 @@ function createGondolinToolDefinitions(config) {
7322
5509
  }
7323
5510
  ];
7324
5511
  }
5512
+ /** Resolve one attempt's host-only HTTP credentials before VM resume. */
5513
+ async function resolveAttemptBrokeredHttpSecrets(input) {
5514
+ if (!input.runtimeDefinition) return void 0;
5515
+ return materializePiBrokeredHttpSecrets({
5516
+ runtime: input.runtimeDefinition,
5517
+ context: {
5518
+ agentName: input.agentName,
5519
+ claimedTask: input.claimedTask,
5520
+ cwdPath: input.cwdPath
5521
+ },
5522
+ signal: input.signal,
5523
+ timeoutMs: input.timeoutMs
5524
+ });
5525
+ }
7325
5526
  function createMoltNetAgentResolver(input) {
7326
5527
  let resolved;
7327
5528
  return () => {
@@ -7353,7 +5554,7 @@ function createPiTaskExecutor(opts) {
7353
5554
  if (!cachedCheckpoint) if (opts.runtimeDefinition) {
7354
5555
  cachedTemplate = await opts.runtimeDefinition.vm.resolve({ onProgress: opts.onSnapshotProgress });
7355
5556
  cachedCheckpoint = cachedTemplate.checkpointPath;
7356
- } else cachedCheckpoint = await ensureSnapshot({
5557
+ } else cachedCheckpoint = await ensureSnapshot$1({
7357
5558
  config: opts.sandboxConfig?.snapshot,
7358
5559
  onProgress: opts.onSnapshotProgress ?? ((m) => {
7359
5560
  process.stderr.write(`[snapshot] ${m}\n`);
@@ -7477,10 +5678,10 @@ async function executePiTask(claimedTask, reporter, opts) {
7477
5678
  reporterOpen = true;
7478
5679
  let checkpointPath;
7479
5680
  let resolvedVmTemplate = opts.resolvedVmTemplate;
7480
- let effectiveSandboxConfig;
5681
+ let brokeredSecretEnvNames = [];
7481
5682
  try {
7482
5683
  if (!resolvedVmTemplate && opts.runtimeDefinition) resolvedVmTemplate = opts.resolveVmTemplate ? await opts.resolveVmTemplate() : await opts.runtimeDefinition.vm.resolve({ onProgress: opts.onSnapshotProgress });
7483
- checkpointPath = await traceRuntimePhase("moltnet.execution.snapshot.prepare", { "moltnet.snapshot.source": resolvedVmTemplate ? "resolved_template" : opts.checkpointPath ? "configured_checkpoint" : opts.resolveCheckpointPath ? "resolver" : "build_or_cache" }, async () => resolvedVmTemplate?.checkpointPath ?? opts.checkpointPath ?? (opts.resolveCheckpointPath ? await opts.resolveCheckpointPath() : await ensureSnapshot({
5684
+ checkpointPath = await traceRuntimePhase("moltnet.execution.snapshot.prepare", { "moltnet.snapshot.source": resolvedVmTemplate ? "resolved_template" : opts.checkpointPath ? "configured_checkpoint" : opts.resolveCheckpointPath ? "resolver" : "build_or_cache" }, async () => resolvedVmTemplate?.checkpointPath ?? opts.checkpointPath ?? (opts.resolveCheckpointPath ? await opts.resolveCheckpointPath() : await ensureSnapshot$1({
7484
5685
  config: opts.sandboxConfig?.snapshot,
7485
5686
  onProgress: opts.onSnapshotProgress ?? ((m) => {
7486
5687
  process.stderr.write(`[snapshot] ${m}\n`);
@@ -7505,13 +5706,33 @@ async function executePiTask(claimedTask, reporter, opts) {
7505
5706
  }
7506
5707
  if (!workspace) throw new Error("task workspace not prepared");
7507
5708
  const preparedWorkspace = workspace;
5709
+ const effectiveSandboxConfig = applyExecutionPlanSandboxOverrides(resolvedVmTemplate ? {
5710
+ ...opts.sandboxConfig,
5711
+ snapshot: void 0,
5712
+ resumeCommands: [...resolvedVmTemplate.resumeCommands]
5713
+ } : opts.sandboxConfig, executionPlan);
5714
+ let brokeredSecrets;
5715
+ try {
5716
+ const runtimeDefinition = opts.runtimeDefinition;
5717
+ brokeredSecrets = runtimeDefinition ? await traceRuntimePhase("moltnet.execution.credentials.resolve", { "moltnet.credentials.requirement_count": runtimeDefinition.brokeredHttpSecrets?.length ?? 0 }, () => resolveAttemptBrokeredHttpSecrets({
5718
+ runtimeDefinition,
5719
+ agentName: opts.agentName,
5720
+ claimedTask,
5721
+ cwdPath,
5722
+ signal: reporter.cancelSignal
5723
+ })) : void 0;
5724
+ } catch (err) {
5725
+ const message = err instanceof Error ? err.message : String(err);
5726
+ if (reporter.cancelSignal.aborted || err?.name === "AbortError") {
5727
+ await emitError("credential_resolution", message, { cancelled: true });
5728
+ return makeCancelledOutput(reporter.cancelReason ?? "Task cancelled during credential resolution.");
5729
+ }
5730
+ await emitError("credential_resolution", message);
5731
+ return makeFailedOutput("credential_resolution_failed", message, finalUsage, err instanceof PiBrokeredHttpSecretResolutionError ? err.retryable : false);
5732
+ }
7508
5733
  try {
7509
- effectiveSandboxConfig = applyExecutionPlanSandboxOverrides(resolvedVmTemplate ? {
7510
- ...opts.sandboxConfig,
7511
- snapshot: void 0,
7512
- resumeCommands: [...resolvedVmTemplate.resumeCommands]
7513
- } : opts.sandboxConfig, executionPlan);
7514
- managed = await traceRuntimePhase("moltnet.execution.vm.resume", { "moltnet.workspace.mode": preparedWorkspace.mode }, () => resumeVm({
5734
+ brokeredSecretEnvNames = (brokeredSecrets ?? []).filter(({ value }) => value !== void 0 && value !== "").map(({ guestEnv }) => guestEnv).sort();
5735
+ managed = await traceRuntimePhase("moltnet.execution.vm.resume", { "moltnet.workspace.mode": preparedWorkspace.mode }, () => (opts.resumeVm ?? resumeVm)({
7515
5736
  checkpointPath,
7516
5737
  agentName: opts.agentName,
7517
5738
  agentRootDir,
@@ -7521,6 +5742,7 @@ async function executePiTask(claimedTask, reporter, opts) {
7521
5742
  extraAllowedHosts: opts.extraAllowedHosts,
7522
5743
  sandboxConfig: effectiveSandboxConfig,
7523
5744
  forwardEnv: opts.forwardEnv,
5745
+ brokeredSecrets,
7524
5746
  onDiagnostic: opts.onVmDiagnostic,
7525
5747
  signal: reporter.cancelSignal
7526
5748
  }));
@@ -7535,7 +5757,7 @@ async function executePiTask(claimedTask, reporter, opts) {
7535
5757
  }
7536
5758
  const diaryId = task.diaryId ?? "";
7537
5759
  const taskTeamId = task.teamId ?? "";
7538
- activateAgentEnv(managed.credentials.agentEnv, agentRootDir);
5760
+ activateAgentEnv$1(managed.credentials.agentEnv, agentRootDir);
7539
5761
  const activeWorkspace = preparedWorkspace;
7540
5762
  const activeManaged = managed;
7541
5763
  const getMoltNetAgent = createMoltNetAgentResolver({
@@ -7828,7 +6050,8 @@ async function executePiTask(claimedTask, reporter, opts) {
7828
6050
  nodeModulesWriteMode: "tmpfs",
7829
6051
  verifiedExecutables: verifiedGuestExecutables,
7830
6052
  allowedHosts: [...effectiveSandboxConfig?.network?.allowedHosts ?? [], ...opts.extraAllowedHosts ?? []],
7831
- allowedInternalHosts: effectiveSandboxConfig?.network?.allowedInternalHosts ?? []
6053
+ allowedInternalHosts: effectiveSandboxConfig?.network?.allowedInternalHosts ?? [],
6054
+ brokeredSecretEnvNames
7832
6055
  },
7833
6056
  toolPolicy: capabilityProjection.instructorPolicy
7834
6057
  });
@@ -8801,4 +7024,4 @@ function describeToolErrorMessage(result) {
8801
7024
  }
8802
7025
  }
8803
7026
  //#endregion
8804
- export { GONDOLIN_BASE_EXECUTABLES, GONDOLIN_TOOL_NAMES, GuestEnvironmentBoundaryError, HOST_EXEC_DEFAULT_BASE_ENV, MOLTNET_TOOL_NAMES, PI_EXECUTOR_MANIFEST_VERSION, PI_RUNTIME_DEFINITION_VERSION, activateAgentEnv, assertGuestEnvironmentBoundary, assertHostAuthenticatedGuestEnvironment, buildAgentSession, buildPiExecutorManifest, buildRuntimeKernel, buildWorkspaceMountInstructions, createGondolinBashOps, createGondolinEditOps, createGondolinFindOps, createGondolinLsOps, createGondolinReadOps, createGondolinToolDefinitions, createGondolinWriteOps, createMoltNetTools, createPiOtelExtension, createPiRetryTriage, createPiTaskExecutor, createSubagentTool, createToolPolicyExtension, decideForEvent, decideToolCall, defineGondolinTemplate, definePiExtension, definePiRuntime, definePiTool, enabledPiToolNames, ensureSnapshot, executeGondolinGrep, executePiTask, filterModelVisibleTools, findMainWorktree, injectRuntimeContext as injectTaskContext, isKernelTool, isResolvedPathInsideRoot, isToolVisible, loadCredentials, materializePiExtensions, materializePiTools, normalizeRetryTriageResult, redactRetryTriageSecrets, resolveHostExecBaseEnv, resolveSessionToolPolicy, resolveTaskWorktreePath, resumeVm, toGuestPath };
7027
+ export { BrokeredHttpSecretBoundaryError, DEFAULT_BROKERED_HTTP_SECRET_RESOLUTION_TIMEOUT_MS, GONDOLIN_BASE_EXECUTABLES, GONDOLIN_TOOL_NAMES, GuestEnvironmentBoundaryError, HOST_EXEC_DEFAULT_BASE_ENV, MOLTNET_TOOL_NAMES, PI_EXECUTOR_MANIFEST_VERSION, PI_GUEST_AUTH_PATH, PI_RUNTIME_DEFINITION_VERSION, PiBrokeredHttpSecretResolutionError, activateAgentEnv, assertGuestEnvironmentBoundary, assertHostAuthenticatedGuestEnvironment, buildAgentSession, buildPiExecutorManifest, buildRuntimeKernel, buildWorkspaceMountInstructions, createGondolinBashOps, createGondolinEditOps, createGondolinFindOps, createGondolinLsOps, createGondolinReadOps, createGondolinToolDefinitions, createGondolinWriteOps, createMoltNetTools, createPiOtelExtension, createPiRetryTriage, createPiTaskExecutor, createSubagentTool, createToolPolicyExtension, decideForEvent, decideToolCall, defineGondolinTemplate, definePiBrokeredHttpSecret, definePiExtension, definePiRuntime, definePiTool, enabledPiToolNames, ensureSnapshot, executeGondolinGrep, executePiTask, filterModelVisibleTools, findMainWorktree, injectRuntimeContext as injectTaskContext, isKernelTool, isResolvedPathInsideRoot, isToolVisible, loadCredentials, materializePiBrokeredHttpSecrets, materializePiExtensions, materializePiTools, normalizeRetryTriageResult, piProviderAuth, prepareBrokeredHttpSecrets, redactRetryTriageSecrets, resolveHostExecBaseEnv, resolveSessionToolPolicy, resolveTaskWorktreePath, resumeVm, toGuestPath };