@themoltnet/pi-runtime 0.10.1 → 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 +266 -2007
  3. package/package.json +8 -6
package/dist/index.js CHANGED
@@ -1,37 +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
11
  import * as json from "multiformats/codecs/json";
11
12
  import { sha256 } from "multiformats/hashes/sha2";
12
- import { FREEFORM_TYPE, SUBMIT_OUTPUT_GATE_ID, TaskContext, buildTaskUserPrompt, getSubmitOutputContract, materializeTaskOutput, mergeRuntimeProfileContext, resolveTaskContext, taskTypeUsesSubagents, traceRuntimePhase, validateTaskOutput, validateTaskSubmission } from "@themoltnet/agent-runtime";
13
+ import { FREEFORM_TYPE, SUBMIT_OUTPUT_GATE_ID, buildTaskUserPrompt, getSubmitOutputContract, materializeTaskOutput, mergeRuntimeProfileContext, resolveTaskContext, taskTypeUsesSubagents, traceRuntimePhase, validateTaskOutput, validateTaskSubmission } from "@themoltnet/agent-runtime";
13
14
  import { connect } from "@themoltnet/sdk/node";
14
15
  import { ShellCommandAnalyzer } from "@themoltnet/shell-command-analyzer";
15
16
  import { homedir } from "node:os";
16
- import { MemoryProvider, RealFSProvider, ShadowProvider, VM, VmCheckpoint, createHttpHooks, createShadowPathPredicate, ensureImageSelector, isWriteFlag, loadGuestAssets } from "@earendil-works/gondolin";
17
+ import { VmCheckpoint } from "@earendil-works/gondolin";
17
18
  import { Type as Type$1 } from "typebox";
18
- import { Value } from "typebox/value";
19
19
  import { createHash } from "node:crypto";
20
- import { parseEnv } from "node:util";
21
- //#region src/path-containment.ts
22
- /**
23
- * Check containment for already-resolved lexical or real paths.
24
- *
25
- * Callers that accept untrusted paths must resolve/realpath at their I/O
26
- * boundary first; keeping the platform-specific relative-path rule here avoids
27
- * subtly different `..` and absolute-path handling across runtime cleanup,
28
- * session sync, and artifact staging.
29
- */
30
- function isResolvedPathInsideRoot(path, root) {
31
- const rel = relative(root, path);
32
- return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
33
- }
34
- //#endregion
20
+ import { Value } from "typebox/value";
35
21
  //#region src/moltnet/render-phase6.ts
36
22
  function slugToTitle(value) {
37
23
  return value.split(/[:/_-]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" ");
@@ -268,7 +254,7 @@ async function findExistingAncestor(candidate) {
268
254
  }
269
255
  }
270
256
  function assertPathInsideWorkspace(realCwd, realPath, displayPath) {
271
- 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}`);
272
258
  }
273
259
  /**
274
260
  * Expand the `taskFilter` shorthand on the diary list/search tools into
@@ -956,11 +942,11 @@ function createMoltNetTools(config) {
956
942
  defineTool({
957
943
  name: "moltnet_host_exec",
958
944
  label: "Run command on host (escape hatch — requires user approval)",
959
- 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.",
960
946
  parameters: Type.Object({
961
947
  executable: Type.String({ description: "Executable to run (git | gh | moltnet)" }),
962
948
  args: Type.Array(Type.String(), { description: "Arguments to pass to the executable" }),
963
- 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." }))
964
950
  }),
965
951
  async execute(_id, params, _signal, _onUpdate, ctx) {
966
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.`);
@@ -1323,7 +1309,7 @@ function resolvePiCodingAgentDir() {
1323
1309
  return process.env["PI_CODING_AGENT_DIR"] ?? path.join(homedir(), ".pi", "agent");
1324
1310
  }
1325
1311
  //#endregion
1326
- //#region ../tasks/src/context.ts
1312
+ //#region ../runtime-profiles/src/context.ts
1327
1313
  /**
1328
1314
  * How an executor delivers a context entry to its underlying LLM.
1329
1315
  * V1 bindings only; Tier-2 (reference_file, mcp_resource, imported_file,
@@ -1376,99 +1362,12 @@ var ContextRef = Type$1.Object({
1376
1362
  additionalProperties: false
1377
1363
  });
1378
1364
  /** Reusable input fragment for any task type. Soft cap at 5 items. */
1379
- var TaskContext$1 = Type$1.Array(ContextRef, {
1365
+ var TaskContext = Type$1.Array(ContextRef, {
1380
1366
  $id: "TaskContext",
1381
1367
  maxItems: 5
1382
1368
  });
1383
1369
  //#endregion
1384
- //#region ../tasks/src/rubric.ts
1385
- /**
1386
- * Rubric — structured acceptance criteria used by judgment tasks.
1387
- *
1388
- * Phase 1 (this PR): rubrics are embedded in task inputs. Their integrity
1389
- * is pinned via the task's `input_cid` (which covers the whole input,
1390
- * including the inline rubric). No separate storage, no CRUD.
1391
- *
1392
- * Phase 2 (see #881): rubrics become a first-class resource with their
1393
- * own signed rows and CIDv1 lookup. The schema below is designed to
1394
- * carry forward unchanged — only storage and addressing differ.
1395
- *
1396
- * Until Phase 2 lands, `rubricId` + `version` + `contentHash` are
1397
- * informational fields the author fills in; no uniqueness is enforced.
1398
- * `contentHash` is optional in Phase 1 because the *task*'s input_cid
1399
- * is the authoritative commitment.
1400
- */
1401
- /**
1402
- * How a judge must score a single criterion.
1403
- *
1404
- * - `llm_score`: 0..1 continuous, `rationale` required. Smooths failures
1405
- * into the gradient — use `llm_checklist` instead for properties where
1406
- * a single failure is a real failure (grounding, faithfulness).
1407
- * - `llm_checklist`: judge enumerates per-claim assertions with
1408
- * `{passed, evidence}`. The criterion's numeric `score` is derived:
1409
- * `1` iff every assertion passes, else `0`. Per-claim evidence is the
1410
- * dataset for cluster-analysis of failure modes. See #999.
1411
- * - `boolean`: 0 or 1, `rationale` optional.
1412
- * - `deterministic_signature_check`: judge runs a signature check;
1413
- * result is 0 or 1. No LLM discretion.
1414
- * - `deterministic_coverage_check`: every referenced source entry
1415
- * appears in the rendered output; 0 or 1.
1416
- */
1417
- var RubricScoringMode = Type$1.Union([
1418
- Type$1.Literal("llm_score"),
1419
- Type$1.Literal("llm_checklist"),
1420
- Type$1.Literal("boolean"),
1421
- Type$1.Literal("deterministic_signature_check"),
1422
- Type$1.Literal("deterministic_coverage_check")
1423
- ], { $id: "RubricScoringMode" });
1424
- /**
1425
- * One binary check produced by an `llm_checklist`-mode criterion.
1426
- *
1427
- * `evidence` is REQUIRED for both PASS and FAIL — agentskills.io grading
1428
- * principle: \"Don't give the benefit of the doubt.\" A PASS without
1429
- * concrete evidence (a quoted span, an entry id, a source location)
1430
- * cannot be audited. A FAIL without evidence cannot be clustered into
1431
- * structural fixes. The same shape is reused by `judge-eval-variant`
1432
- * (#943) so tooling, dashboards, and analysis stay uniform.
1433
- */
1434
- var AssertionResult = Type$1.Object({
1435
- id: Type$1.String({ minLength: 1 }),
1436
- text: Type$1.String({ minLength: 1 }),
1437
- passed: Type$1.Boolean(),
1438
- evidence: Type$1.String({ minLength: 1 })
1439
- }, {
1440
- $id: "AssertionResult",
1441
- additionalProperties: false
1442
- });
1443
- var RubricCriterion = Type$1.Object({
1444
- id: Type$1.String({ minLength: 1 }),
1445
- description: Type$1.String({ minLength: 1 }),
1446
- weight: Type$1.Number({
1447
- minimum: 0,
1448
- maximum: 1
1449
- }),
1450
- scoring: RubricScoringMode
1451
- }, {
1452
- $id: "RubricCriterion",
1453
- additionalProperties: false
1454
- });
1455
- /**
1456
- * A complete rubric. Same shape used in Phase 1 (inline) and Phase 2
1457
- * (stored row `body`); only the addressing mechanism differs.
1458
- */
1459
- var Rubric = Type$1.Object({
1460
- rubricId: Type$1.String({ minLength: 1 }),
1461
- version: Type$1.String({ minLength: 1 }),
1462
- preamble: Type$1.Optional(Type$1.String()),
1463
- criteria: Type$1.Array(RubricCriterion, { minItems: 1 }),
1464
- scope: Type$1.Optional(Type$1.String()),
1465
- contentHash: Type$1.Optional(Type$1.String())
1466
- }, {
1467
- $id: "Rubric",
1468
- additionalProperties: false
1469
- });
1470
- //#endregion
1471
- //#region ../tasks/src/runtime-models.ts
1370
+ //#region ../runtime-profiles/src/runtime-models.ts
1472
1371
  /**
1473
1372
  * Runtime model catalog: a list of supported provider/model couples that
1474
1373
  * MoltNet daemons can target. Backed by the `runtime_models` table.
@@ -1516,7 +1415,7 @@ Type$1.Object({
1516
1415
  additionalProperties: false
1517
1416
  });
1518
1417
  //#endregion
1519
- //#region ../tasks/src/runtime-profile-context-recipes.ts
1418
+ //#region ../runtime-profiles/src/runtime-profile-context-recipes.ts
1520
1419
  var RUNTIME_PROFILE_CONTEXT_CATALOGUE = {
1521
1420
  version: 1,
1522
1421
  fragments: {
@@ -1527,12 +1426,12 @@ var RUNTIME_PROFILE_CONTEXT_CATALOGUE = {
1527
1426
  },
1528
1427
  "accountable-delivery-v1": {
1529
1428
  binding: "prompt_prefix",
1530
- 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.",
1531
1430
  slug: "accountable-delivery-v1"
1532
1431
  },
1533
1432
  "judgment-diary-v1": {
1534
1433
  binding: "prompt_prefix",
1535
- 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.",
1536
1435
  slug: "judgment-diary-v1"
1537
1436
  },
1538
1437
  "proactive-memory-v1": {
@@ -2532,7 +2431,7 @@ var toolEnforcementLiterals = [
2532
2431
  ];
2533
2432
  var ToolEnforcementSchema = Type$1.Union(toolEnforcementLiterals, { description: "Runtime tool-policy enforcement mode: off (inert), watch (audit only), enforce (block disallowed tools, fail-closed)." });
2534
2433
  //#endregion
2535
- //#region ../tasks/src/runtime-profiles.ts
2434
+ //#region ../runtime-profiles/src/runtime-profiles.ts
2536
2435
  var RuntimeProfileName = Type$1.String({
2537
2436
  minLength: 1,
2538
2437
  maxLength: 100,
@@ -2651,7 +2550,7 @@ var RuntimeProfileContext = Type$1.Object({
2651
2550
  $id: "RuntimeProfileContext",
2652
2551
  additionalProperties: false
2653
2552
  });
2654
- var RuntimeProfileRef = Type$1.Object({ profileId: Type$1.String({ format: "uuid" }) }, {
2553
+ Type$1.Object({ profileId: Type$1.String({ format: "uuid" }) }, {
2655
2554
  $id: "RuntimeProfileRef",
2656
2555
  additionalProperties: false
2657
2556
  });
@@ -2731,7 +2630,7 @@ Type$1.Object({
2731
2630
  additionalProperties: false
2732
2631
  });
2733
2632
  //#endregion
2734
- //#region ../tasks/src/runtime-sessions.ts
2633
+ //#region ../runtime-profiles/src/runtime-sessions.ts
2735
2634
  var RuntimeSessionKind = Type$1.Union([
2736
2635
  Type$1.Literal("root"),
2737
2636
  Type$1.Literal("extend"),
@@ -2789,7 +2688,7 @@ Type$1.Object({
2789
2688
  additionalProperties: false
2790
2689
  });
2791
2690
  //#endregion
2792
- //#region ../tasks/src/runtime-slots.ts
2691
+ //#region ../runtime-profiles/src/runtime-slots.ts
2793
2692
  var RuntimeWorkspaceKind = Type$1.Union([
2794
2693
  Type$1.Literal("origin"),
2795
2694
  Type$1.Literal("fork"),
@@ -2918,1248 +2817,37 @@ Type$1.Object({
2918
2817
  additionalProperties: false
2919
2818
  });
2920
2819
  //#endregion
2921
- //#region ../tasks/src/success-criteria.ts
2922
- /**
2923
- * SuccessCriteria proposer-stated acceptance criteria, evaluated in two
2924
- * complementary places.
2925
- *
2926
- * Before this envelope existed, criteria were scattered: a vestigial
2927
- * `criteriaCid` column nobody resolved, free-form prose on
2928
- * `fulfill_brief.input`, and inline `rubric` / `criteria[]` fields on
2929
- * judgment-task inputs. None of those were machine-verifiable
2930
- * end-to-end.
2931
- *
2932
- * This module defines a single, content-addressable envelope a proposer
2933
- * attaches to any task type. It has four orthogonal sections — pick
2934
- * whichever apply per task type:
2935
- *
2936
- * - `gates` Promise-level structural/process checks
2937
- * - `assertions` Declarative claims about output JSON
2938
- * - `rubric` Weighted-criteria scoring instrument, reused
2939
- * verbatim from `./rubric.ts`.
2940
- * - `sideEffects` Required process side-effects (e.g. diary entry)
2941
- *
2942
- * ## Two roles, two task types
2943
- *
2944
- * **Producer self-assessment** (fulfillment tasks: `fulfill_brief`,
2945
- * `curate_pack`, `render_pack`). The producer **LLM** evaluates the
2946
- * criteria against its own output and emits a `VerificationRecord`
2947
- * inside `output.verification`. The daemon is pure passthrough — it
2948
- * does not run `evaluateAssertions`, does not inspect the verification
2949
- * record. The REST API is dumb storage; it never re-runs assertions and
2950
- * never runs LLMs. The cross-field rule
2951
- * `requireVerificationWhenCriteriaPresent` enforces "verification
2952
- * required iff successCriteria present" at task-output validation time
2953
- * (server-side schema check). Self-assessment is a truthful self-rating,
2954
- * NOT enforcement — `verification.passed=false` does not block /complete
2955
- * and does not affect `acceptedAttemptN`. See
2956
- * `docs/use/tasks-and-runtime.md` for the full producer/judge flow.
2957
- *
2958
- * **Binding evaluation** (judgment tasks: `assess_brief`, `judge_pack`).
2959
- * A separate task whose IS the application of `successCriteria` to
2960
- * someone else's output. Different agent (enforced at claim time), same
2961
- * envelope. The judge's verdict is binding: this is the *gate* in the
2962
- * MoltNet model. The rubric inside `successCriteria.rubric` IS the job
2963
- * spec for the judge.
2964
- *
2965
- * The clean chain: producer task with `successCriteria` → producer
2966
- * self-assesses honestly → proposer (or automation) creates a downstream
2967
- * judgment task that references the same `successCriteria` (or a
2968
- * stricter rubric) → judgment task delivers the binding verdict.
2969
- *
2970
- * Storage: SuccessCriteria lives inline at `task.input.successCriteria`,
2971
- * pinned via the task's `inputCid`. No separate column or hash. When
2972
- * #881 lands, the `rubric` field can graduate to `{ rubricCid }` lookup
2973
- * without changing this envelope, and producer + judge tasks can pin
2974
- * the SAME rubric across the chain for end-to-end auditability.
2975
- */
2976
- var SchemaCheckSpec = Type$1.Object({ schemaCid: Type$1.String({ minLength: 1 }) }, { additionalProperties: false });
2977
- var CidEqualsSpec = Type$1.Object({
2978
- path: Type$1.String({ minLength: 1 }),
2979
- expected: Type$1.String({ minLength: 1 })
2980
- }, { additionalProperties: false });
2981
- var SubmitToolCallGate = Type$1.Object({
2982
- id: Type$1.String({ minLength: 1 }),
2983
- kind: Type$1.Literal("submit-tool-call"),
2984
- description: Type$1.String({ minLength: 1 }),
2985
- required: Type$1.Boolean()
2986
- }, { additionalProperties: false });
2987
- var Gate = Type$1.Union([
2988
- SubmitToolCallGate,
2989
- Type$1.Object({
2990
- id: Type$1.String({ minLength: 1 }),
2991
- kind: Type$1.Literal("schema-check"),
2992
- spec: SchemaCheckSpec,
2993
- required: Type$1.Boolean()
2994
- }, { additionalProperties: false }),
2995
- Type$1.Object({
2996
- id: Type$1.String({ minLength: 1 }),
2997
- kind: Type$1.Literal("cid-equals"),
2998
- spec: CidEqualsSpec,
2999
- required: Type$1.Boolean()
3000
- }, { additionalProperties: false })
3001
- ], { $id: "Gate" });
3002
- var AssertionOp = Type$1.Union([
3003
- Type$1.Literal("exists"),
3004
- Type$1.Literal("equals"),
3005
- Type$1.Literal("matches"),
3006
- Type$1.Literal("in-range"),
3007
- Type$1.Literal("min-length")
3008
- ], { $id: "AssertionOp" });
3009
- var Assertion = Type$1.Object({
3010
- id: Type$1.String({ minLength: 1 }),
3011
- path: Type$1.String({ minLength: 1 }),
3012
- op: AssertionOp,
3013
- value: Type$1.Optional(Type$1.Unknown())
3014
- }, {
3015
- $id: "Assertion",
3016
- additionalProperties: false
3017
- });
3018
- var SideEffectsSpec = Type$1.Object({
3019
- diaryEntryRequired: Type$1.Optional(Type$1.Boolean()),
3020
- diaryEntryTags: Type$1.Optional(Type$1.Array(Type$1.String({ minLength: 1 }))),
3021
- referencedEntries: Type$1.Optional(Type$1.Integer({ minimum: 0 }))
3022
- }, {
3023
- $id: "SideEffectsSpec",
3024
- additionalProperties: false
3025
- });
3026
- var SuccessCriteria = Type$1.Object({
3027
- version: Type$1.Literal(1),
3028
- gates: Type$1.Optional(Type$1.Array(Gate)),
3029
- assertions: Type$1.Optional(Type$1.Array(Assertion)),
3030
- rubric: Type$1.Optional(Rubric),
3031
- minComposite: Type$1.Optional(Type$1.Number({
3032
- minimum: 0,
3033
- maximum: 1
3034
- })),
3035
- sideEffects: Type$1.Optional(SideEffectsSpec)
3036
- }, {
3037
- $id: "SuccessCriteria",
3038
- additionalProperties: false
3039
- });
3040
- var VerificationResultStatus = Type$1.Union([
3041
- Type$1.Literal("pass"),
3042
- Type$1.Literal("fail"),
3043
- Type$1.Literal("skip")
3044
- ], { $id: "VerificationResultStatus" });
3045
- var VerificationResultKind = Type$1.Union([
3046
- Type$1.Literal("gate"),
3047
- Type$1.Literal("assertion"),
3048
- Type$1.Literal("rubric"),
3049
- Type$1.Literal("sideEffect")
3050
- ], { $id: "VerificationResultKind" });
3051
- var VerificationResult = Type$1.Object({
3052
- id: Type$1.String({ minLength: 1 }),
3053
- kind: VerificationResultKind,
3054
- status: VerificationResultStatus,
3055
- detail: Type$1.Optional(Type$1.String())
3056
- }, {
3057
- $id: "VerificationResult",
3058
- additionalProperties: false
3059
- });
3060
- var VerificationRecord = Type$1.Object({
3061
- inputCid: Type$1.String({ minLength: 1 }),
3062
- results: Type$1.Array(VerificationResult),
3063
- passed: Type$1.Boolean({ description: "True iff every verification result has status \"pass\" or \"skip\"; false when any result has status \"fail\"." })
3064
- }, {
3065
- $id: "VerificationRecord",
3066
- additionalProperties: false
3067
- });
3068
- //#endregion
3069
- //#region ../tasks/src/task-artifacts.ts
3070
- var TaskArtifact = Type$1.Object({
3071
- id: Type$1.String({ format: "uuid" }),
3072
- teamId: Type$1.String({ format: "uuid" }),
3073
- taskId: Type$1.String({ format: "uuid" }),
3074
- attemptN: Type$1.Union([Type$1.Integer({ minimum: 1 }), Type$1.Null()]),
3075
- kind: Type$1.String({
3076
- minLength: 1,
3077
- maxLength: 100
3078
- }),
3079
- title: Type$1.String({
3080
- minLength: 1,
3081
- maxLength: 255
3082
- }),
3083
- contentType: Type$1.String({
3084
- minLength: 1,
3085
- maxLength: 200
3086
- }),
3087
- contentEncoding: Type$1.Union([Type$1.String({
3088
- minLength: 1,
3089
- maxLength: 100
3090
- }), Type$1.Null()]),
3091
- sizeBytes: Type$1.Integer({ minimum: 0 }),
3092
- cid: Type$1.String({
3093
- minLength: 1,
3094
- maxLength: 100
3095
- }),
3096
- createdByAgentId: Type$1.Union([Type$1.String({ format: "uuid" }), Type$1.Null()]),
3097
- expiresAt: Type$1.Union([Type$1.String({ format: "date-time" }), Type$1.Null()]),
3098
- createdAt: Type$1.String({ format: "date-time" })
3099
- }, { $id: "TaskArtifact" });
3100
- Type$1.Object({
3101
- artifacts: Type$1.Array(TaskArtifact),
3102
- nextCursor: Type$1.Union([Type$1.String({ minLength: 1 }), Type$1.Null()])
3103
- }, { $id: "TaskArtifactList" });
3104
- Type$1.Object({
3105
- limit: Type$1.Optional(Type$1.Integer({
3106
- minimum: 1,
3107
- maximum: 100
3108
- })),
3109
- cursor: Type$1.Optional(Type$1.String({ minLength: 1 }))
3110
- }, {
3111
- $id: "ListTaskArtifactsQuery",
3112
- additionalProperties: false
3113
- });
3114
- var HeaderSafeContentType = Type$1.String({
3115
- minLength: 1,
3116
- maxLength: 200,
3117
- pattern: "^[\\x21-\\x7e][\\x20-\\x7e]*$"
3118
- });
3119
- var HeaderSafeContentEncoding = Type$1.String({
3120
- minLength: 1,
3121
- maxLength: 100,
3122
- pattern: "^[\\x21-\\x7e][\\x20-\\x7e]*$"
3123
- });
3124
- Type$1.Object({
3125
- kind: Type$1.String({
3126
- minLength: 1,
3127
- maxLength: 100
3128
- }),
3129
- title: Type$1.String({
3130
- minLength: 1,
3131
- maxLength: 255
3132
- }),
3133
- contentType: Type$1.Optional(HeaderSafeContentType),
3134
- contentEncoding: Type$1.Optional(HeaderSafeContentEncoding)
3135
- }, {
3136
- $id: "UploadTaskArtifactQuery",
3137
- additionalProperties: false
3138
- });
3139
- Type$1.String({
3140
- $id: "TaskArtifactContent",
3141
- description: "Task artifact content stream.",
3142
- format: "binary"
3143
- });
3144
- Type$1.Object({ taskId: Type$1.String({ format: "uuid" }) }, {
3145
- $id: "TaskArtifactTaskParams",
3146
- additionalProperties: false
3147
- });
3148
- Type$1.Object({
3149
- taskId: Type$1.String({ format: "uuid" }),
3150
- attemptN: Type$1.Integer({ minimum: 1 })
3151
- }, {
3152
- $id: "TaskArtifactAttemptParams",
3153
- additionalProperties: false
3154
- });
3155
- Type$1.Object({
3156
- taskId: Type$1.String({ format: "uuid" }),
3157
- attemptN: Type$1.Integer({ minimum: 1 }),
3158
- cid: Type$1.String({
3159
- minLength: 1,
3160
- maxLength: 100
3161
- })
3162
- }, {
3163
- $id: "TaskArtifactContentParams",
3164
- additionalProperties: false
3165
- });
3166
- Type$1.Object({
3167
- contentType: Type$1.Optional(HeaderSafeContentType),
3168
- contentEncoding: Type$1.Optional(HeaderSafeContentEncoding)
3169
- }, {
3170
- $id: "StageTaskArtifactQuery",
3171
- additionalProperties: false
3172
- });
3173
- Type$1.Object({
3174
- cid: Type$1.String({
3175
- minLength: 1,
3176
- maxLength: 100
3177
- }),
3178
- sizeBytes: Type$1.Integer({ minimum: 0 }),
3179
- contentType: Type$1.String({
3180
- minLength: 1,
3181
- maxLength: 200
3182
- })
3183
- }, { $id: "StagedTaskArtifact" });
3184
- Type$1.Object({
3185
- taskId: Type$1.String({ format: "uuid" }),
3186
- cid: Type$1.String({
3187
- minLength: 1,
3188
- maxLength: 100
3189
- })
3190
- }, {
3191
- $id: "TaskArtifactTaskContentParams",
3192
- additionalProperties: false
3193
- });
3194
- Type$1.Object({
3195
- targetTaskId: Type$1.String({ format: "uuid" }),
3196
- successCriteria: SuccessCriteria
3197
- }, {
3198
- $id: "AssessBriefInput",
3199
- additionalProperties: false
3200
- });
3201
- /** One score line. */
3202
- var AssessBriefScore = Type$1.Object({
3203
- criterionId: Type$1.String({ minLength: 1 }),
3204
- score: Type$1.Number({
3205
- minimum: 0,
3206
- maximum: 1
3207
- }),
3208
- rationale: Type$1.Optional(Type$1.String()),
3209
- evidence: Type$1.Optional(Type$1.Object({
3210
- commitsVerified: Type$1.Number(),
3211
- commitsTotal: Type$1.Number(),
3212
- signatureFailures: Type$1.Array(Type$1.String())
3213
- }, { additionalProperties: false }))
3214
- }, {
3215
- $id: "AssessBriefScore",
3216
- additionalProperties: false
3217
- });
3218
- Type$1.Object({
3219
- scores: Type$1.Array(AssessBriefScore, { minItems: 1 }),
3220
- composite: Type$1.Number({
3221
- minimum: 0,
3222
- maximum: 1
3223
- }),
3224
- verdict: Type$1.String({ minLength: 1 }),
3225
- judgeModel: Type$1.Optional(Type$1.String())
3226
- }, {
3227
- $id: "AssessBriefOutput",
3228
- additionalProperties: false
3229
- });
3230
- //#endregion
3231
- //#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
+ }
3232
2838
  /**
3233
- * `curate_pack` select and rank diary entries into a context pack.
3234
- *
3235
- * output_kind: artifact
3236
- * criteria: not required (rubric-less curation recipe)
3237
- * references: optional (e.g. a prior rendered pack being re-curated)
3238
- *
3239
- * This is step 1 of the three-session attribution loop (#875). The agent
3240
- * runs a structured exploration over a diary — tag inventory, hybrid
3241
- * search, type/tag narrowing — and emits a ranked entry list via
3242
- * `moltnet_pack_create`. The prompt is deterministic given the input
3243
- * (no operator interaction), so two runs with the same input should
3244
- * converge on similar packs.
3245
- *
3246
- * 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.
3247
2842
  */
3248
- var EntryTypeFilter = Type$1.Union([
3249
- Type$1.Literal("episodic"),
3250
- Type$1.Literal("semantic"),
3251
- Type$1.Literal("procedural"),
3252
- Type$1.Literal("reflection")
3253
- ]);
3254
- Type$1.Object({
3255
- diaryId: Type$1.String({ format: "uuid" }),
3256
- taskPrompt: Type$1.String({ minLength: 1 }),
3257
- entryTypes: Type$1.Optional(Type$1.Array(EntryTypeFilter, { minItems: 1 })),
3258
- tagFilters: Type$1.Optional(Type$1.Object({
3259
- include: Type$1.Optional(Type$1.Array(Type$1.String())),
3260
- exclude: Type$1.Optional(Type$1.Array(Type$1.String())),
3261
- prefix: Type$1.Optional(Type$1.String())
3262
- }, { additionalProperties: false })),
3263
- tokenBudget: Type$1.Optional(Type$1.Number({ minimum: 500 })),
3264
- recipe: Type$1.Optional(Type$1.Union([Type$1.Literal("topic-focused-v1"), Type$1.Literal("scope-inventory-v1")])),
3265
- successCriteria: Type$1.Optional(SuccessCriteria)
3266
- }, {
3267
- $id: "CuratePackInput",
3268
- additionalProperties: false
3269
- });
3270
- Type$1.Object({
3271
- packId: Type$1.String({ format: "uuid" }),
3272
- packCid: Type$1.String({ minLength: 1 }),
3273
- entries: Type$1.Array(Type$1.Object({
3274
- entryId: Type$1.String({ format: "uuid" }),
3275
- rank: Type$1.Number({ minimum: 1 }),
3276
- rationale: Type$1.String({ minLength: 1 })
3277
- }, { additionalProperties: false }), { minItems: 1 }),
3278
- recipeParams: Type$1.Record(Type$1.String(), Type$1.Unknown()),
3279
- checkpoints: Type$1.Optional(Type$1.Array(Type$1.Object({
3280
- phase: Type$1.String({ minLength: 1 }),
3281
- candidateIds: Type$1.Array(Type$1.String({ format: "uuid" })),
3282
- droppedIds: Type$1.Optional(Type$1.Array(Type$1.String({ format: "uuid" }))),
3283
- notes: Type$1.String({ minLength: 1 })
3284
- }, { additionalProperties: false }))),
3285
- summary: Type$1.String({ minLength: 1 }),
3286
- verification: Type$1.Optional(VerificationRecord)
3287
- }, {
3288
- $id: "CuratePackOutput",
3289
- additionalProperties: false
3290
- });
3291
- //#endregion
3292
- //#region ../tasks/src/task-types/freeform.ts
3293
- var FreeformExecutionOptions = Type$1.Object({
3294
- workspace: Type$1.Optional(Type$1.Union([
3295
- Type$1.Literal("none"),
3296
- Type$1.Literal("shared_mount"),
3297
- Type$1.Literal("dedicated_worktree")
3298
- ])),
3299
- revision: Type$1.Optional(Type$1.String({ pattern: "^[0-9a-fA-F]{40}$" }))
3300
- }, {
3301
- $id: "FreeformExecutionOptions",
3302
- additionalProperties: false
3303
- });
3304
- var FreeformContinueFrom = Type$1.Object({
3305
- taskId: Type$1.String({ format: "uuid" }),
3306
- attemptN: Type$1.Integer({ minimum: 1 }),
3307
- mode: Type$1.Optional(Type$1.Union([Type$1.Literal("extend"), Type$1.Literal("fork")]))
3308
- }, {
3309
- $id: "FreeformContinueFrom",
3310
- additionalProperties: false
3311
- });
3312
- var FreeformTaskTypeProposal = Type$1.Object({
3313
- name: Type$1.String({ minLength: 1 }),
3314
- rationale: Type$1.String({ minLength: 1 }),
3315
- inputShape: Type$1.Optional(Type$1.Record(Type$1.String(), Type$1.Unknown())),
3316
- outputShape: Type$1.Optional(Type$1.Record(Type$1.String(), Type$1.Unknown()))
3317
- }, {
3318
- $id: "FreeformTaskTypeProposal",
3319
- additionalProperties: false
3320
- });
3321
- Type$1.Object({
3322
- brief: Type$1.String({ minLength: 1 }),
3323
- expectedOutput: Type$1.Optional(Type$1.String({ minLength: 1 })),
3324
- constraints: Type$1.Optional(Type$1.Array(Type$1.String({ minLength: 1 }), { maxItems: 20 })),
3325
- suggestedTaskType: Type$1.Optional(Type$1.String({ minLength: 1 })),
3326
- successCriteria: Type$1.Optional(SuccessCriteria),
3327
- context: Type$1.Optional(TaskContext$1),
3328
- execution: Type$1.Optional(FreeformExecutionOptions),
3329
- continueFrom: Type$1.Optional(FreeformContinueFrom)
3330
- }, {
3331
- $id: "FreeformInput",
3332
- additionalProperties: false
3333
- });
3334
- var FreeformArtifact = Type$1.Object({
3335
- kind: Type$1.String({ minLength: 1 }),
3336
- title: Type$1.String({ minLength: 1 }),
3337
- description: Type$1.Optional(Type$1.String({ minLength: 1 })),
3338
- url: Type$1.Optional(Type$1.String({ minLength: 1 })),
3339
- path: Type$1.Optional(Type$1.String({ minLength: 1 })),
3340
- cid: Type$1.Optional(Type$1.String({ minLength: 1 })),
3341
- contentType: Type$1.Optional(Type$1.String({ minLength: 1 })),
3342
- contentEncoding: Type$1.Optional(Type$1.String({ minLength: 1 })),
3343
- sizeBytes: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
3344
- body: Type$1.Optional(Type$1.String({ maxLength: 65536 }))
3345
- }, {
3346
- $id: "FreeformArtifact",
3347
- additionalProperties: false
3348
- });
3349
- Type$1.Object({
3350
- summary: Type$1.String({ minLength: 1 }),
3351
- branch: Type$1.Optional(Type$1.String({ minLength: 1 })),
3352
- artifacts: Type$1.Optional(Type$1.Array(FreeformArtifact, { maxItems: 20 })),
3353
- proposedTaskType: Type$1.Optional(FreeformTaskTypeProposal),
3354
- diaryEntryIds: Type$1.Optional(Type$1.Array(Type$1.String({ format: "uuid" }))),
3355
- verification: Type$1.Optional(VerificationRecord)
3356
- }, {
3357
- $id: "FreeformOutput",
3358
- additionalProperties: false
3359
- });
3360
- Type$1.Object({
3361
- brief: Type$1.String({ minLength: 1 }),
3362
- successCriteria: Type$1.Optional(SuccessCriteria),
3363
- seedFiles: Type$1.Optional(Type$1.Array(Type$1.String())),
3364
- scopeHint: Type$1.Optional(Type$1.String())
3365
- }, {
3366
- $id: "FulfillBriefInput",
3367
- additionalProperties: false
3368
- });
3369
- Type$1.Object({
3370
- branch: Type$1.String({ minLength: 1 }),
3371
- commits: Type$1.Array(Type$1.Object({
3372
- sha: Type$1.String({ minLength: 7 }),
3373
- message: Type$1.String(),
3374
- diaryEntryId: Type$1.Union([Type$1.String({ format: "uuid" }), Type$1.Null()])
3375
- }, { additionalProperties: false })),
3376
- pullRequestUrl: Type$1.Union([Type$1.String(), Type$1.Null()]),
3377
- diaryEntryIds: Type$1.Array(Type$1.String({ format: "uuid" })),
3378
- summary: Type$1.String({ minLength: 1 }),
3379
- verification: Type$1.Optional(VerificationRecord)
3380
- }, {
3381
- $id: "FulfillBriefOutput",
3382
- additionalProperties: false
3383
- });
3384
- Type$1.Object({
3385
- renderedPackId: Type$1.String({ format: "uuid" }),
3386
- sourcePackId: Type$1.String({ format: "uuid" }),
3387
- successCriteria: SuccessCriteria
3388
- }, {
3389
- $id: "JudgePackInput",
3390
- additionalProperties: false
3391
- });
3392
- /** One scored criterion. Mirrors `AssessBriefScore`. */
3393
- var JudgePackScore = Type$1.Object({
3394
- criterionId: Type$1.String({ minLength: 1 }),
3395
- score: Type$1.Number({
3396
- minimum: 0,
3397
- maximum: 1
3398
- }),
3399
- rationale: Type$1.Optional(Type$1.String()),
3400
- assertions: Type$1.Optional(Type$1.Array(AssertionResult, { minItems: 1 })),
3401
- evidence: Type$1.Optional(Type$1.Record(Type$1.String(), Type$1.Unknown()))
3402
- }, {
3403
- $id: "JudgePackScore",
3404
- additionalProperties: false
3405
- });
3406
- Type$1.Object({
3407
- scores: Type$1.Array(JudgePackScore, { minItems: 1 }),
3408
- composite: Type$1.Number({
3409
- minimum: 0,
3410
- maximum: 1
3411
- }),
3412
- verdict: Type$1.String({ minLength: 1 }),
3413
- judgeModel: Type$1.Optional(Type$1.String()),
3414
- rendererBinaryCid: Type$1.Optional(Type$1.Union([Type$1.String(), Type$1.Null()]))
3415
- }, {
3416
- $id: "JudgePackOutput",
3417
- additionalProperties: false
3418
- });
3419
- Type$1.Object({
3420
- targetTaskId: Type$1.String({ format: "uuid" }),
3421
- targetAttemptN: Type$1.Integer({ minimum: 1 }),
3422
- successCriteria: SuccessCriteria
3423
- }, {
3424
- $id: "JudgeEvalAttemptInput",
3425
- additionalProperties: false
3426
- });
3427
- Type$1.Object({
3428
- targetTaskId: Type$1.String({ format: "uuid" }),
3429
- targetAttemptN: Type$1.Integer({ minimum: 1 }),
3430
- variantLabel: Type$1.String({
3431
- minLength: 1,
3432
- maxLength: 64,
3433
- pattern: "^(?!.* - ).*$"
3434
- }),
3435
- scores: Type$1.Array(JudgePackScore, { minItems: 1 }),
3436
- composite: Type$1.Number({
3437
- minimum: 0,
3438
- maximum: 1
3439
- }),
3440
- verdict: Type$1.String({ minLength: 1 }),
3441
- judgeModel: Type$1.Optional(Type$1.String({ minLength: 1 }))
3442
- }, {
3443
- $id: "JudgeEvalAttemptSubmission",
3444
- additionalProperties: false
3445
- });
3446
- Type$1.Object({
3447
- targetTaskId: Type$1.String({ format: "uuid" }),
3448
- targetAttemptN: Type$1.Integer({ minimum: 1 }),
3449
- variantLabel: Type$1.String({
3450
- minLength: 1,
3451
- maxLength: 64,
3452
- pattern: "^(?!.* - ).*$"
3453
- }),
3454
- scores: Type$1.Array(JudgePackScore, { minItems: 1 }),
3455
- composite: Type$1.Number({
3456
- minimum: 0,
3457
- maximum: 1
3458
- }),
3459
- verdict: Type$1.String({ minLength: 1 }),
3460
- judgeModel: Type$1.Optional(Type$1.String({ minLength: 1 })),
3461
- traceparent: Type$1.Optional(Type$1.String({ minLength: 1 }))
3462
- }, {
3463
- $id: "JudgeEvalAttemptOutput",
3464
- additionalProperties: false
3465
- });
3466
- //#endregion
3467
- //#region ../tasks/src/task-types/pr-review.ts
3468
- var PrReviewSubject = Type$1.Object({
3469
- title: Type$1.String({ minLength: 1 }),
3470
- summary: Type$1.String({ minLength: 1 }),
3471
- resourceUrls: Type$1.Optional(Type$1.Array(Type$1.String({ minLength: 1 }))),
3472
- inspectionHints: Type$1.Optional(Type$1.Array(Type$1.String({ minLength: 1 })))
3473
- }, {
3474
- $id: "PrReviewSubject",
3475
- additionalProperties: false
3476
- });
3477
- Type$1.Object({
3478
- subject: PrReviewSubject,
3479
- taskPrompt: Type$1.Optional(Type$1.String({ minLength: 1 })),
3480
- successCriteria: SuccessCriteria,
3481
- context: Type$1.Optional(TaskContext$1)
3482
- }, {
3483
- $id: "PrReviewInput",
3484
- additionalProperties: false
3485
- });
3486
- var PrReviewScore = Type$1.Object({
3487
- criterionId: Type$1.String({ minLength: 1 }),
3488
- score: Type$1.Union([Type$1.Literal(0), Type$1.Literal(1)]),
3489
- rationale: Type$1.String({ minLength: 1 })
3490
- }, {
3491
- $id: "PrReviewScore",
3492
- additionalProperties: false
3493
- });
3494
- Type$1.Object({
3495
- scores: Type$1.Array(PrReviewScore, { minItems: 1 }),
3496
- composite: Type$1.Number({
3497
- minimum: 0,
3498
- maximum: 1
3499
- }),
3500
- verdict: Type$1.String({ minLength: 1 })
3501
- }, {
3502
- $id: "PrReviewOutput",
3503
- additionalProperties: false
3504
- });
3505
- Type$1.Object({
3506
- packId: Type$1.String({ format: "uuid" }),
3507
- persist: Type$1.Optional(Type$1.Boolean()),
3508
- pinned: Type$1.Optional(Type$1.Boolean()),
3509
- successCriteria: Type$1.Optional(SuccessCriteria)
3510
- }, {
3511
- $id: "RenderPackInput",
3512
- additionalProperties: false
3513
- });
3514
- Type$1.Object({
3515
- renderedPackId: Type$1.Union([Type$1.String({ format: "uuid" }), Type$1.Null()]),
3516
- renderedCid: Type$1.String({ minLength: 1 }),
3517
- renderMethod: Type$1.String({ minLength: 1 }),
3518
- byteSize: Type$1.Number({ minimum: 0 }),
3519
- entriesRendered: Type$1.Number({ minimum: 0 }),
3520
- summary: Type$1.String({ minLength: 1 }),
3521
- verification: Type$1.Optional(VerificationRecord)
3522
- }, {
3523
- $id: "RenderPackOutput",
3524
- additionalProperties: false
3525
- });
3526
- //#endregion
3527
- //#region ../tasks/src/task-types/run-eval.ts
3528
- /**
3529
- * `run_eval` — execute a scenario prompt under a named variant for
3530
- * later per-attempt grading by `judge_eval_attempt` tasks.
3531
- *
3532
- * output_kind: artifact
3533
- * criteria: optional producer-only checks (when set,
3534
- * output.verification is required — the judge rubric remains hidden
3535
- * on downstream `judge_eval_attempt` tasks)
3536
- * references: not required (scenario lives entirely in input)
3537
- */
3538
- var RunEvalMode = Type$1.Union([Type$1.Literal("vitro"), Type$1.Literal("vivo")], { $id: "RunEvalMode" });
3539
- var RunEvalWorkspace = Type$1.Union([
3540
- Type$1.Literal("none"),
3541
- Type$1.Literal("shared_mount"),
3542
- Type$1.Literal("dedicated_worktree")
3543
- ], { $id: "RunEvalWorkspace" });
3544
- var RunEvalExecution = Type$1.Object({
3545
- mode: RunEvalMode,
3546
- workspace: RunEvalWorkspace
3547
- }, {
3548
- $id: "RunEvalExecution",
3549
- additionalProperties: false
3550
- });
3551
- /**
3552
- * Producer-visible checks for `run_eval`. Deliberately forbids `rubric`
3553
- * so the variant runner cannot see the downstream judge's answer key.
3554
- * Keep the rest of the SuccessCriteria envelope available for generic
3555
- * process / structure checks (`gates`, `assertions`, `sideEffects`).
3556
- */
3557
- var RunEvalSuccessCriteria = Type$1.Object({
3558
- version: Type$1.Literal(1),
3559
- gates: Type$1.Optional(SuccessCriteria.properties.gates),
3560
- assertions: Type$1.Optional(SuccessCriteria.properties.assertions),
3561
- sideEffects: Type$1.Optional(SuccessCriteria.properties.sideEffects)
3562
- }, {
3563
- $id: "RunEvalSuccessCriteria",
3564
- additionalProperties: false
3565
- });
3566
- Type$1.Object({
3567
- scenario: Type$1.Object({
3568
- prompt: Type$1.String({ minLength: 1 }),
3569
- inputFiles: Type$1.Optional(Type$1.Array(Type$1.String({ minLength: 1 })))
3570
- }, { additionalProperties: false }),
3571
- variantLabel: Type$1.String({
3572
- minLength: 1,
3573
- maxLength: 64
3574
- }),
3575
- execution: RunEvalExecution,
3576
- context: TaskContext$1,
3577
- successCriteria: Type$1.Optional(RunEvalSuccessCriteria)
3578
- }, {
3579
- $id: "RunEvalInput",
3580
- additionalProperties: false
3581
- });
3582
- var RunEvalArtifact = Type$1.Object({
3583
- path: Type$1.String({ minLength: 1 }),
3584
- cid: Type$1.String({ minLength: 1 })
3585
- }, { additionalProperties: false });
3586
- Type$1.Object({
3587
- response: Type$1.String({ minLength: 1 }),
3588
- artifacts: Type$1.Optional(Type$1.Array(RunEvalArtifact)),
3589
- verification: Type$1.Optional(VerificationRecord)
3590
- }, {
3591
- $id: "RunEvalSubmission",
3592
- additionalProperties: false
3593
- });
3594
- Type$1.Object({
3595
- response: Type$1.String({ minLength: 1 }),
3596
- artifacts: Type$1.Optional(Type$1.Array(RunEvalArtifact)),
3597
- totalTokens: Type$1.Integer({ minimum: 0 }),
3598
- durationMs: Type$1.Integer({ minimum: 0 }),
3599
- traceparent: Type$1.Optional(Type$1.String({ minLength: 1 })),
3600
- verification: Type$1.Optional(VerificationRecord)
3601
- }, {
3602
- $id: "RunEvalOutput",
3603
- additionalProperties: false
3604
- });
3605
- //#endregion
3606
- //#region ../tasks/src/task-type-registry.ts
3607
- var schemaCids = null;
3608
- function getTaskTypeRegistry() {
3609
- if (!schemaCids) throw new Error("Task type registry not initialized. Call initTaskTypeRegistry() first.");
3610
- return schemaCids;
3611
- }
3612
- new Proxy({}, { get(_, prop) {
3613
- if (typeof prop !== "string") return void 0;
3614
- return getTaskTypeRegistry().get(prop);
3615
- } });
3616
- //#endregion
3617
- //#region ../tasks/src/wire.ts
3618
- /**
3619
- * Wire-format types for the MoltNet Task model.
3620
- *
3621
- * These schemas are the single source of truth for:
3622
- * - `tasks`, `task_attempts`, `task_messages` DB columns (PR 1's Drizzle
3623
- * schema must match these verbatim)
3624
- * - REST request/response bodies (PR 4)
3625
- * - `TaskReporter` output records (PR 0)
3626
- *
3627
- * Invariant: every property on `Task` is type-neutral (applies to all
3628
- * `taskType`s). Type-specific payloads live inside `input` / `output`
3629
- * JSONB, validated against schemas registered under `task_types`.
3630
- *
3631
- * Identity rule:
3632
- * - claim/execute/sign → agent-only (`task_attempts.claimed_by_agent_id`)
3633
- * - propose/cancel → agent XOR human (dual nullable FK + XOR check)
3634
- *
3635
- * See GH issue #852 for the full design snapshot.
3636
- */
3637
- var TaskStatus = Type$1.Union([
3638
- Type$1.Literal("waiting"),
3639
- Type$1.Literal("queued"),
3640
- Type$1.Literal("dispatched"),
3641
- Type$1.Literal("running"),
3642
- Type$1.Literal("completed"),
3643
- Type$1.Literal("failed"),
3644
- Type$1.Literal("cancelled"),
3645
- Type$1.Literal("expired")
3646
- ], { $id: "TaskStatus" });
3647
- var TaskAttemptStatus = Type$1.Union([
3648
- Type$1.Literal("claimed"),
3649
- Type$1.Literal("running"),
3650
- Type$1.Literal("completed"),
3651
- Type$1.Literal("failed"),
3652
- Type$1.Literal("cancelled"),
3653
- Type$1.Literal("aborted"),
3654
- Type$1.Literal("timed_out")
3655
- ], { $id: "TaskAttemptStatus" });
3656
- var ExecutorTrustLevel = Type$1.Union([
3657
- Type$1.Literal("selfDeclared"),
3658
- Type$1.Literal("agentSigned"),
3659
- Type$1.Literal("releaseVerifiedTool"),
3660
- Type$1.Literal("sandboxAttested")
3661
- ], { $id: "ExecutorTrustLevel" });
3662
- var OutputKind = Type$1.Union([Type$1.Literal("artifact"), Type$1.Literal("judgment")], { $id: "OutputKind" });
3663
- var TaskMessageKind = Type$1.Union([
3664
- Type$1.Literal("text_delta"),
3665
- Type$1.Literal("tool_call_start"),
3666
- Type$1.Literal("tool_call_end"),
3667
- Type$1.Literal("turn_end"),
3668
- Type$1.Literal("error"),
3669
- Type$1.Literal("info")
3670
- ], { $id: "TaskMessageKind" });
3671
- var Uuid = Type$1.String({ format: "uuid" });
3672
- var Cid = Type$1.String({ minLength: 1 });
3673
- var IsoTimestamp = Type$1.String({ format: "date-time" });
3674
- /**
3675
- * Daemon-asserted runtime state stamped onto a `TaskAttemptSummary` at
3676
- * attempt-completion time. The server persists this block verbatim and
3677
- * exposes `slotResumableUntil` as a legacy/local warm-slot hint; task
3678
- * continuation eligibility is based on the completed source attempt and
3679
- * daemon-side claim-affinity/runtime-session recovery. The block carries
3680
- * its own `reportedAt` so consumers can reason about staleness without
3681
- * reading documentation. All daemon-asserted state lives here —
3682
- * top-level attempt fields stay server-authoritative.
3683
- *
3684
- * Adding new fields requires explicit design review (intentional
3685
- * boundary; see docs/superpowers/specs/2026-06-04-tasks-continue-design.md).
3686
- */
3687
- var DaemonState = Type$1.Object({
3688
- reportedAt: IsoTimestamp,
3689
- slotResumableUntil: Type$1.Union([IsoTimestamp, Type$1.Null()])
3690
- }, {
3691
- $id: "DaemonState",
3692
- additionalProperties: false
3693
- });
3694
- var ClaimConditionSchema = Type$1.Union([
3695
- Type$1.Object({
3696
- op: Type$1.Literal("all"),
3697
- conditions: Type$1.Array(Type$1.Ref("ClaimCondition"), {
3698
- minItems: 1,
3699
- maxItems: 8
3700
- })
3701
- }, { additionalProperties: false }),
3702
- Type$1.Object({
3703
- op: Type$1.Literal("any"),
3704
- conditions: Type$1.Array(Type$1.Ref("ClaimCondition"), {
3705
- minItems: 1,
3706
- maxItems: 8
3707
- })
3708
- }, { additionalProperties: false }),
3709
- Type$1.Object({
3710
- op: Type$1.Literal("task_status"),
3711
- taskId: Uuid,
3712
- statuses: Type$1.Array(Type$1.Ref("TaskStatus"), {
3713
- minItems: 1,
3714
- maxItems: 8
3715
- })
3716
- }, { additionalProperties: false }),
3717
- Type$1.Object({
3718
- op: Type$1.Literal("task_accepted"),
3719
- taskId: Uuid
3720
- }, { additionalProperties: false })
3721
- ], { $id: "ClaimCondition" });
3722
- var ClaimConditionDefinition = Type$1.Unsafe(ClaimConditionSchema);
3723
- Type$1.Unsafe(Type$1.Cyclic({ ClaimCondition: ClaimConditionDefinition }, "ClaimCondition", { $id: "ClaimCondition" }));
3724
- /**
3725
- * Reference to another task's output or an external artifact.
3726
- * Embedded in `tasks.references` JSONB array.
3727
- */
3728
- var TaskRef = Type$1.Object({
3729
- taskId: Type$1.Union([Uuid, Type$1.Null()]),
3730
- outputCid: Type$1.Optional(Cid),
3731
- role: Type$1.Union([
3732
- Type$1.Literal("judged_work"),
3733
- Type$1.Literal("reviewed_diff"),
3734
- Type$1.Literal("target_source"),
3735
- Type$1.Literal("context")
3736
- ]),
3737
- external: Type$1.Optional(Type$1.Object({
3738
- kind: Type$1.Union([
3739
- Type$1.Literal("github_pr"),
3740
- Type$1.Literal("github_issue"),
3741
- Type$1.Literal("http_url")
3742
- ]),
3743
- pr: Type$1.Optional(Type$1.Number()),
3744
- issue: Type$1.Optional(Type$1.Number()),
3745
- url: Type$1.Optional(Type$1.String()),
3746
- commit_sha: Type$1.Optional(Type$1.String()),
3747
- snapshot_cid: Type$1.Optional(Cid)
3748
- })),
3749
- artifact: Type$1.Optional(Type$1.Object({
3750
- cid: Cid,
3751
- attemptN: Type$1.Optional(Type$1.Integer({ minimum: 1 })),
3752
- kind: Type$1.Optional(Type$1.String({
3753
- minLength: 1,
3754
- maxLength: 100
3755
- })),
3756
- title: Type$1.Optional(Type$1.String({
3757
- minLength: 1,
3758
- maxLength: 255
3759
- })),
3760
- contentType: Type$1.Optional(Type$1.String({
3761
- minLength: 1,
3762
- maxLength: 200
3763
- }))
3764
- }, { additionalProperties: false }))
3765
- }, {
3766
- $id: "TaskRef",
3767
- additionalProperties: false
3768
- });
3769
- /**
3770
- * Token / cost accounting for one attempt.
3771
- * Reported by the runtime; persisted per-attempt, also rolled up into
3772
- * `TaskOutput.usage` for convenience.
3773
- */
3774
- var TaskUsage = Type$1.Object({
3775
- inputTokens: Type$1.Integer({ minimum: 0 }),
3776
- outputTokens: Type$1.Integer({ minimum: 0 }),
3777
- cacheReadTokens: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
3778
- cacheWriteTokens: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
3779
- toolCalls: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
3780
- model: Type$1.Optional(Type$1.String()),
3781
- provider: Type$1.Optional(Type$1.String())
3782
- }, {
3783
- $id: "TaskUsage",
3784
- additionalProperties: false
3785
- });
3786
- var TaskRetryDecision = Type$1.Union([Type$1.Literal("retry"), Type$1.Literal("do_not_retry")]);
3787
- var TaskRetryConfidence = Type$1.Union([
3788
- Type$1.Literal("low"),
3789
- Type$1.Literal("medium"),
3790
- Type$1.Literal("high")
3791
- ]);
3792
- var TaskRetrySource = Type$1.Union([
3793
- Type$1.Literal("explicit"),
3794
- Type$1.Literal("deterministic"),
3795
- Type$1.Literal("attempts_exhausted"),
3796
- Type$1.Literal("triage"),
3797
- Type$1.Literal("triage_failed")
3798
- ]);
3799
- var TaskRetryInfo = Type$1.Object({
3800
- source: TaskRetrySource,
3801
- decision: Type$1.Optional(TaskRetryDecision),
3802
- confidence: Type$1.Optional(TaskRetryConfidence),
3803
- reason: Type$1.Optional(Type$1.String())
3804
- }, {
3805
- $id: "TaskRetryInfo",
3806
- additionalProperties: false
3807
- });
3808
- /**
3809
- * Structured error returned from a failed attempt.
3810
- */
3811
- var TaskError = Type$1.Object({
3812
- code: Type$1.String(),
3813
- message: Type$1.String(),
3814
- stack: Type$1.Optional(Type$1.String()),
3815
- retryable: Type$1.Optional(Type$1.Boolean()),
3816
- retry: Type$1.Optional(TaskRetryInfo)
3817
- }, {
3818
- $id: "TaskError",
3819
- additionalProperties: false
3820
- });
3821
- Type$1.Object({
3822
- agentId: Type$1.Union([Uuid, Type$1.Null()]),
3823
- humanId: Type$1.Union([Uuid, Type$1.Null()])
3824
- }, {
3825
- $id: "ActorPair",
3826
- additionalProperties: false
3827
- });
3828
- Type$1.Object({
3829
- id: Uuid,
3830
- taskType: Type$1.String({ minLength: 1 }),
3831
- title: Type$1.Union([Type$1.String(), Type$1.Null()]),
3832
- tags: Type$1.Array(Type$1.String()),
3833
- teamId: Uuid,
3834
- diaryId: Type$1.Union([Uuid, Type$1.Null()]),
3835
- outputKind: OutputKind,
3836
- input: Type$1.Record(Type$1.String(), Type$1.Unknown()),
3837
- inputSchemaCid: Cid,
3838
- inputCid: Cid,
3839
- references: Type$1.Array(TaskRef),
3840
- correlationId: Type$1.Union([Uuid, Type$1.Null()]),
3841
- proposedByAgentId: Type$1.Union([Uuid, Type$1.Null()]),
3842
- proposedByHumanId: Type$1.Union([Uuid, Type$1.Null()]),
3843
- acceptedAttemptN: Type$1.Union([Type$1.Number(), Type$1.Null()]),
3844
- claimCondition: Type$1.Union([Type$1.Unsafe(Type$1.Ref("ClaimCondition")), Type$1.Null()]),
3845
- requiredExecutorTrustLevel: ExecutorTrustLevel,
3846
- allowedProfiles: Type$1.Array(RuntimeProfileRef, { maxItems: 16 }),
3847
- status: TaskStatus,
3848
- queuedAt: IsoTimestamp,
3849
- completedAt: Type$1.Union([IsoTimestamp, Type$1.Null()], { description: "First time the task entered completed, failed, cancelled, or expired; null until terminal." }),
3850
- expiresAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
3851
- cancelledByAgentId: Type$1.Union([Uuid, Type$1.Null()]),
3852
- cancelledByHumanId: Type$1.Union([Uuid, Type$1.Null()]),
3853
- cancelReason: Type$1.Union([Type$1.String(), Type$1.Null()]),
3854
- maxAttempts: Type$1.Number({ minimum: 1 }),
3855
- dispatchTimeoutSec: Type$1.Union([Type$1.Integer({
3856
- minimum: 1,
3857
- maximum: 86400
3858
- }), Type$1.Null()]),
3859
- runningTimeoutSec: Type$1.Union([Type$1.Integer({
3860
- minimum: 1,
3861
- maximum: 86400
3862
- }), Type$1.Null()])
3863
- }, {
3864
- $id: "Task",
3865
- additionalProperties: false
3866
- });
3867
- Type$1.Object({
3868
- taskId: Uuid,
3869
- attemptN: Type$1.Number({ minimum: 1 }),
3870
- claimedByAgentId: Uuid,
3871
- leaseId: Type$1.Union([Uuid, Type$1.Null()]),
3872
- runtimeProfileId: Type$1.Union([Uuid, Type$1.Null()]),
3873
- runtimeProfileRevision: Type$1.Union([Type$1.Integer({ minimum: 1 }), Type$1.Null()]),
3874
- policySnapshotHash: Type$1.Union([Type$1.String({ pattern: "^sha256:[0-9a-f]{64}$" }), Type$1.Null()]),
3875
- runtimeId: Type$1.Union([Uuid, Type$1.Null()]),
3876
- claimedAt: IsoTimestamp,
3877
- startedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
3878
- completedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
3879
- status: TaskAttemptStatus,
3880
- output: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
3881
- outputCid: Type$1.Union([Cid, Type$1.Null()]),
3882
- claimedExecutorFingerprint: Type$1.Union([Cid, Type$1.Null()]),
3883
- claimedExecutorManifest: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
3884
- completedExecutorFingerprint: Type$1.Union([Cid, Type$1.Null()]),
3885
- completedExecutorManifest: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
3886
- error: Type$1.Union([TaskError, Type$1.Null()]),
3887
- usage: Type$1.Union([TaskUsage, Type$1.Null()]),
3888
- contentSignature: Type$1.Union([Type$1.String(), Type$1.Null()]),
3889
- signedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
3890
- daemonState: Type$1.Union([DaemonState, Type$1.Null()])
3891
- }, {
3892
- $id: "TaskAttempt",
3893
- additionalProperties: true
3894
- });
3895
- Type$1.Object({
3896
- taskId: Uuid,
3897
- attemptN: Type$1.Number({ minimum: 1 }),
3898
- seq: Type$1.Number({
3899
- minimum: 0,
3900
- 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."
3901
- }),
3902
- timestamp: IsoTimestamp,
3903
- kind: TaskMessageKind,
3904
- payload: Type$1.Record(Type$1.String(), Type$1.Unknown())
3905
- }, {
3906
- $id: "TaskMessage",
3907
- additionalProperties: false
3908
- });
3909
- Type$1.Object({
3910
- taskId: Uuid,
3911
- attemptN: Type$1.Number({ minimum: 1 }),
3912
- status: Type$1.Union([
3913
- Type$1.Literal("completed"),
3914
- Type$1.Literal("failed"),
3915
- Type$1.Literal("cancelled")
3916
- ]),
3917
- output: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
3918
- outputCid: Type$1.Union([Cid, Type$1.Null()]),
3919
- usage: TaskUsage,
3920
- durationMs: Type$1.Number({ minimum: 0 }),
3921
- error: Type$1.Optional(TaskError),
3922
- contentSignature: Type$1.Optional(Type$1.String())
3923
- }, {
3924
- $id: "TaskOutput",
3925
- additionalProperties: false
3926
- });
3927
- Type$1.Object({
3928
- runtimeId: Uuid,
3929
- agentId: Uuid,
3930
- timestamp: IsoTimestamp,
3931
- status: Type$1.Union([
3932
- Type$1.Literal("idle"),
3933
- Type$1.Literal("busy"),
3934
- Type$1.Literal("draining")
3935
- ]),
3936
- activeTaskIds: Type$1.Array(Uuid),
3937
- supportedTaskTypes: Type$1.Array(Type$1.String())
3938
- }, {
3939
- $id: "RuntimeHeartbeat",
3940
- additionalProperties: false
3941
- });
3942
- //#endregion
3943
- //#region src/snapshot.ts
3944
- /**
3945
- * Snapshot builder with auto-build and caching.
3946
- *
3947
- * Builds a Gondolin VM snapshot in two layers:
3948
- * 1. Base (always): Alpine essentials, git, gh CLI, MoltNet CLI, agent user
3949
- * 2. User setup commands (optional): arbitrary shell commands on top of the base
3950
- *
3951
- * Consumers provide raw shell commands — no abstraction over package managers
3952
- * or runtimes. The base provides curl, git, tar, jq; everything else is up to
3953
- * the setup commands.
3954
- *
3955
- * Caches in a platform-appropriate directory:
3956
- * - macOS: ~/Library/Caches/moltnet/gondolin/
3957
- * - Linux: ~/.cache/moltnet/gondolin/
3958
- *
3959
- * The cache key is a hash of the full config. When any input changes,
3960
- * a new snapshot is built automatically.
3961
- */
3962
- /** Alpine packages whose commands are guaranteed in every snapshot. */
3963
- var BASE_ALPINE_PACKAGE_EXECUTABLES = {
3964
- curl: "curl",
3965
- file: "file",
3966
- git: "git",
3967
- jq: "jq",
3968
- ripgrep: "rg",
3969
- tar: "tar",
3970
- xz: "xz"
3971
- };
3972
- /** Alpine packages required by every snapshot. */
3973
- var BASE_ALPINE_PACKAGES = ["ca-certificates", ...Object.keys(BASE_ALPINE_PACKAGE_EXECUTABLES)];
3974
- /** Commands guaranteed by the base Gondolin snapshot. */
3975
- var GONDOLIN_BASE_EXECUTABLES = Object.freeze([
3976
- ...Object.values(BASE_ALPINE_PACKAGE_EXECUTABLES),
3977
- "gh",
3978
- "moltnet"
3979
- ].sort());
3980
- /** gh CLI version installed in every snapshot. */
3981
- var GH_VERSION = "2.74.0";
3982
- /** MoltNet CLI version — downloaded as a binary, no Node needed. */
3983
- var MOLTNET_CLI_VERSION = "1.37.0";
3984
- /**
3985
- * Resolve guest architecture from host (Gondolin VMs match host arch).
3986
- *
3987
- * The two naming conventions are NOT interchangeable:
3988
- * - `gh` — GitHub release-asset suffix (gh CLI ships `linux_amd64.tar.gz`,
3989
- * `linux_arm64.tar.gz`).
3990
- * - `npm` — npm optionalDependencies naming, which mirrors Node's
3991
- * `process.arch` values (`x64`, `arm64`). The MoltNet CLI is
3992
- * published as `@themoltnet/cli-linux-x64` and
3993
- * `@themoltnet/cli-linux-arm64`, NOT `cli-linux-amd64`.
3994
- */
3995
- function getGuestArch() {
3996
- if (process.arch === "arm64") return {
3997
- gh: "linux_arm64",
3998
- npm: "linux-arm64"
3999
- };
4000
- return {
4001
- gh: "linux_amd64",
4002
- npm: "linux-x64"
4003
- };
4004
- }
4005
- /** Hosts reachable during snapshot build. */
4006
- var SETUP_ALLOWED_HOSTS = [
4007
- "dl-cdn.alpinelinux.org",
4008
- "*.alpinelinux.org",
4009
- "registry.npmjs.org",
4010
- "*.npmjs.org",
4011
- "nodejs.org",
4012
- "*.nodejs.org",
4013
- "unofficial-builds.nodejs.org",
4014
- "github.com",
4015
- "*.github.com",
4016
- "*.githubusercontent.com",
4017
- "objects.githubusercontent.com"
4018
- ];
4019
- var DEFAULT_CONFIG = {};
4020
- function getCacheDir() {
4021
- if (process.platform === "darwin") return path.join(process.env.HOME ?? "/tmp", "Library", "Caches", "moltnet", "gondolin");
4022
- const base = process.env.XDG_CACHE_HOME ?? path.join(process.env.HOME ?? "/tmp", ".cache");
4023
- return path.join(base, "moltnet", "gondolin");
4024
- }
4025
- function computeConfigHash(config) {
4026
- const h = createHash("sha256");
4027
- h.update(JSON.stringify({
4028
- baseAlpine: BASE_ALPINE_PACKAGES,
4029
- ghVersion: GH_VERSION,
4030
- cliVersion: MOLTNET_CLI_VERSION,
4031
- config
4032
- }));
4033
- return h.digest("hex").slice(0, 12);
4034
- }
4035
- function getSnapshotPath(config) {
4036
- const hash = computeConfigHash(config);
4037
- const dir = path.join(getCacheDir(), `v2-${hash}`);
4038
- return path.join(dir, "snapshot.qcow2");
4039
- }
4040
- /**
4041
- * Ensure a cached snapshot exists, building one if needed.
4042
- * Returns the absolute path to the qcow2 checkpoint file.
4043
- */
4044
- async function ensureSnapshot(options = {}) {
4045
- const config = options.config ?? DEFAULT_CONFIG;
4046
- const log = options.onProgress ?? (() => {});
4047
- const maxCached = options.maxCached ?? 1;
4048
- const snapshotPath = getSnapshotPath(config);
4049
- const snapshotDir = path.dirname(snapshotPath);
4050
- if (existsSync(snapshotPath)) {
4051
- log(`snapshot cache hit: ${snapshotPath}`);
4052
- return snapshotPath;
4053
- }
4054
- log("snapshot cache miss — building (this takes 1-3 minutes)...");
4055
- mkdirSync(snapshotDir, { recursive: true });
4056
- const overlayPath = path.join(snapshotDir, "build.overlay.qcow2");
4057
- if (existsSync(overlayPath)) rmSync(overlayPath);
4058
- if (existsSync(snapshotPath)) rmSync(snapshotPath);
4059
- log("resolving alpine-base image...");
4060
- const assets = loadGuestAssets((await ensureImageSelector("alpine-base")).assetDir);
4061
- const overlaySize = config.overlaySize ?? "3G";
4062
- log(`creating qcow2 overlay (${overlaySize})...`);
4063
- execFileSync("qemu-img", [
4064
- "create",
4065
- "-f",
4066
- "qcow2",
4067
- "-F",
4068
- "raw",
4069
- "-b",
4070
- assets.rootfsPath,
4071
- overlayPath,
4072
- overlaySize
4073
- ], { stdio: "pipe" });
4074
- const { httpHooks } = createHttpHooks({ allowedHosts: [...SETUP_ALLOWED_HOSTS, ...config.allowedHosts ?? []] });
4075
- log("booting VM for setup...");
4076
- const vm = await VM.create({
4077
- httpHooks,
4078
- env: {
4079
- PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/go/bin",
4080
- HOME: "/root",
4081
- GOROOT: "/usr/lib/go",
4082
- GOPATH: "/root/go"
4083
- },
4084
- sandbox: {
4085
- rootDiskPath: overlayPath,
4086
- rootDiskFormat: "qcow2",
4087
- rootDiskReadOnly: false,
4088
- rootDiskDeleteOnClose: false
4089
- }
4090
- });
4091
- try {
4092
- await buildSnapshot(vm, config, log);
4093
- log("creating checkpoint...");
4094
- await vm.checkpoint(snapshotPath);
4095
- log(`snapshot saved: ${snapshotPath}`);
4096
- } finally {
4097
- try {
4098
- await vm.close();
4099
- } catch {}
4100
- if (existsSync(overlayPath)) rmSync(overlayPath, { force: true });
4101
- }
4102
- pruneOldSnapshots(maxCached, snapshotDir);
4103
- return snapshotPath;
4104
- }
4105
- /** Helper: run a command in the VM, throw on failure. */
4106
- async function run(vm, log, label, cmd) {
4107
- log(label);
4108
- const r = await vm.exec(cmd);
4109
- if (r.exitCode !== 0) {
4110
- const output = [r.stderr, r.stdout].filter(Boolean).join("\n").slice(0, 800);
4111
- throw new Error(`snapshot build "${label}" failed (exit ${r.exitCode}):\n${output}`);
4112
- }
4113
- }
4114
- async function buildSnapshot(vm, config, log) {
4115
- await run(vm, log, "resizing rootfs...", "apk add --no-cache e2fsprogs-extra >/dev/null 2>&1 && resize2fs /dev/vda 2>/dev/null");
4116
- await run(vm, log, `installing base packages: ${BASE_ALPINE_PACKAGES.join(" ")}`, `apk add --no-cache ${BASE_ALPINE_PACKAGES.join(" ")}`);
4117
- const arch = getGuestArch();
4118
- await run(vm, log, `installing gh ${GH_VERSION} (${arch.gh})...`, `sh -eu -c '
4119
- curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_${arch.gh}.tar.gz" -o /tmp/gh.tar.gz
4120
- tar -xzf /tmp/gh.tar.gz -C /tmp
4121
- mv /tmp/gh_${GH_VERSION}_${arch.gh}/bin/gh /usr/local/bin/gh
4122
- chmod +x /usr/local/bin/gh
4123
- rm -rf /tmp/gh.tar.gz /tmp/gh_${GH_VERSION}_${arch.gh}
4124
- gh --version
4125
- '`);
4126
- await run(vm, log, `installing moltnet CLI ${MOLTNET_CLI_VERSION} (${arch.npm})...`, `sh -eu -c '
4127
- curl -fsSL "https://registry.npmjs.org/@themoltnet/cli-${arch.npm}/-/cli-${arch.npm}-${MOLTNET_CLI_VERSION}.tgz" -o /tmp/moltnet.tgz
4128
- tar -xzf /tmp/moltnet.tgz -C /tmp
4129
- mv /tmp/package/bin/moltnet /usr/local/bin/moltnet
4130
- chmod +x /usr/local/bin/moltnet
4131
- rm -rf /tmp/moltnet.tgz /tmp/package
4132
- '`);
4133
- await run(vm, log, "creating agent user...", `sh -eu -c '
4134
- addgroup -g 501 agent 2>/dev/null || true
4135
- adduser -D -u 501 -G agent -h /home/agent -s /bin/sh agent 2>/dev/null || true
4136
- mkdir -p /home/agent/.moltnet /home/agent/.cache
4137
- chown -R agent:agent /home/agent
4138
- chmod 644 /etc/gondolin/mitm/ca.crt 2>/dev/null || true
4139
- '`);
4140
- await run(vm, log, "configuring DNS resolvers...", `sh -c 'echo "nameserver 8.8.8.8
4141
- nameserver 1.1.1.1" > /etc/resolv.conf'`);
4142
- 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]);
4143
- }
4144
- function pruneOldSnapshots(maxCached, currentDir) {
4145
- const cacheRoot = getCacheDir();
4146
- if (!existsSync(cacheRoot)) return;
4147
- const entries = readdirSync(cacheRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("v")).map((e) => {
4148
- const fullPath = path.join(cacheRoot, e.name);
4149
- return {
4150
- path: fullPath,
4151
- mtime: statSync(fullPath).mtimeMs
4152
- };
4153
- }).sort((a, b) => b.mtime - a.mtime);
4154
- for (const entry of entries.slice(maxCached + 1)) if (entry.path !== currentDir) rmSync(entry.path, {
4155
- recursive: true,
4156
- 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
4157
2849
  });
4158
2850
  }
4159
- //#endregion
4160
- //#region src/runtime-definition.ts
4161
- var PI_RUNTIME_DEFINITION_VERSION = "moltnet:pi-runtime:v1";
4162
- var PI_EXECUTOR_MANIFEST_VERSION = "moltnet:executor-manifest:v1";
4163
2851
  function definePiTool(input, options = {}) {
4164
2852
  if ("descriptor" in input) {
4165
2853
  assertToolName(input.descriptor.name);
@@ -4216,7 +2904,7 @@ function defineGondolinTemplate(options) {
4216
2904
  executables,
4217
2905
  resumeCommands,
4218
2906
  async resolve(context = {}) {
4219
- 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({
4220
2908
  config: options.snapshot,
4221
2909
  onProgress: context.onProgress
4222
2910
  });
@@ -4249,17 +2937,28 @@ function definePiRuntime(options) {
4249
2937
  const names = /* @__PURE__ */ new Map();
4250
2938
  for (const tool of options.tools ?? []) claimToolName(names, tool.descriptor.name, "tool contribution");
4251
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
+ }
4252
2949
  return Object.freeze({
4253
2950
  schemaVersion: PI_RUNTIME_DEFINITION_VERSION,
4254
2951
  id: options.id,
4255
2952
  version: options.version,
4256
2953
  runtimeKind: options.runtimeKind ?? "gondolin_pi",
4257
2954
  vm: options.vm,
2955
+ brokeredHttpSecrets: Object.freeze([...options.brokeredHttpSecrets ?? []]),
4258
2956
  tools: Object.freeze([...options.tools ?? []]),
4259
2957
  extensions: Object.freeze([...options.extensions ?? []])
4260
2958
  });
4261
2959
  }
4262
2960
  async function buildPiExecutorManifest(input) {
2961
+ const brokeredHttpSecrets = input.runtime.brokeredHttpSecrets ?? [];
4263
2962
  const descriptors = [...(input.builtInTools ?? []).map((descriptor) => ({
4264
2963
  descriptor,
4265
2964
  scope: "parent_and_subagents"
@@ -4299,6 +2998,14 @@ async function buildPiExecutorManifest(input) {
4299
2998
  templateFingerprint: input.template.fingerprint,
4300
2999
  guestAssetBuildId: input.template.guestAssetBuildId
4301
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)) },
4302
3009
  tools,
4303
3010
  extensions: input.runtime.extensions.map((extension) => ({
4304
3011
  id: extension.id,
@@ -4308,6 +3015,109 @@ async function buildPiExecutorManifest(input) {
4308
3015
  executables: input.template.executables
4309
3016
  };
4310
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
+ }
4311
3121
  async function materializePiTools(input) {
4312
3122
  const contributions = input.runtime.tools.filter((tool) => input.target === "parent" || tool.scope === "parent_and_subagents");
4313
3123
  return (await Promise.all(contributions.map(async (contribution) => {
@@ -4372,633 +3182,6 @@ function assertRuntimeKind(value) {
4372
3182
  if (!RUNTIME_PROFILE_RUNTIME_KIND_REGEXP.test(value)) throw new Error(`Invalid runtime kind "${value}"`);
4373
3183
  }
4374
3184
  //#endregion
4375
- //#region src/abort-utils.ts
4376
- function throwIfAborted(signal, label) {
4377
- if (!signal?.aborted) return;
4378
- throw abortError(label, signal);
4379
- }
4380
- function abortError(label, signal) {
4381
- const reason = signal.reason;
4382
- const suffix = reason instanceof Error ? reason.message : reason === void 0 ? "aborted" : String(reason);
4383
- const err = /* @__PURE__ */ new Error(`${label} aborted: ${suffix}`);
4384
- err.name = "AbortError";
4385
- return err;
4386
- }
4387
- function cleanupLateResource(resourcePromise, opts) {
4388
- resourcePromise.then(async (resource) => {
4389
- try {
4390
- await opts.cleanup(resource);
4391
- } catch (err) {
4392
- opts.onCleanupError?.(err);
4393
- }
4394
- }, () => {});
4395
- }
4396
- async function abortableResource(opts) {
4397
- const { signal } = opts;
4398
- if (!signal) return opts.promise;
4399
- throwIfAborted(signal, opts.label);
4400
- const resourcePromise = Promise.resolve(opts.promise);
4401
- const abortPromise = new Promise((_, reject) => {
4402
- const abort = () => {
4403
- cleanupLateResource(resourcePromise, opts);
4404
- reject(abortError(opts.label, signal));
4405
- };
4406
- signal.addEventListener("abort", abort, { once: true });
4407
- resourcePromise.then(() => signal.removeEventListener("abort", abort), () => signal.removeEventListener("abort", abort));
4408
- });
4409
- return Promise.race([resourcePromise, abortPromise]);
4410
- }
4411
- async function delay(ms, signal, label) {
4412
- if (!signal) {
4413
- await new Promise((resolve) => {
4414
- setTimeout(resolve, ms);
4415
- });
4416
- return;
4417
- }
4418
- throwIfAborted(signal, label);
4419
- await new Promise((resolve, reject) => {
4420
- const listener = () => {
4421
- clearTimeout(timeout);
4422
- reject(abortError(label, signal));
4423
- };
4424
- const timeout = setTimeout(() => {
4425
- signal.removeEventListener("abort", listener);
4426
- resolve();
4427
- }, ms);
4428
- signal.addEventListener("abort", listener, { once: true });
4429
- });
4430
- }
4431
- //#endregion
4432
- //#region src/vm-manager.ts
4433
- /**
4434
- * Memory-backed VFS mount used by the daemon to inject task context
4435
- * (#943 slice 1.5). This is a separate top-level mount because Gondolin
4436
- * mounts can't nest. The agent's Gondolin-bound Read tool accepts paths
4437
- * under this prefix (see toGuestPath in tool-operations.ts).
4438
- *
4439
- * Why MemoryProvider rather than a path under the workspace mount:
4440
- * - Injected task context is ephemeral by intent: per-task-attempt input
4441
- * scoped to the VM lifetime. MemoryProvider models that exactly —
4442
- * in-memory, per-VM-instance, zero host artefacts, automatic
4443
- * cleanup on VM close.
4444
- * - Writing under the workspace mount fails in worktrees because we symlink
4445
- * `.moltnet/` to the main repo (so credentials are reachable from
4446
- * worktrees), and Gondolin's RealFSProvider correctly refuses to
4447
- * create paths whose ancestors' realpath escapes the mount root.
4448
- * That refusal is a deliberate sandbox-escape protection, not a
4449
- * bug. See diary semantic entry cd27d9d3-efdc-4aec-ac0d-5fd8ce258d1f
4450
- * and episodic 7affbfeb-18a2-4963-aeac-c177eb2afa2d for the full
4451
- * investigation and the alternatives we rejected.
4452
- */
4453
- var GUEST_TASK_CONTEXT_MOUNT = "/moltnet-task-context";
4454
- function resolveVfsShadowConfig(config) {
4455
- const patterns = config?.vfs?.shadow ?? [];
4456
- if (patterns.length === 0) return {
4457
- mode: "none",
4458
- patterns: []
4459
- };
4460
- return {
4461
- mode: config?.vfs?.shadowMode ?? "tmpfs",
4462
- patterns
4463
- };
4464
- }
4465
- function shouldRunResumeCommand(entry, ctx) {
4466
- if (typeof entry === "string") return true;
4467
- const workspaceModes = entry.when?.workspaceMode;
4468
- if (workspaceModes && !workspaceModes.includes(ctx.workspaceMode)) return false;
4469
- return true;
4470
- }
4471
- function shouldShadowNodeModulesPath(pathname) {
4472
- const normalized = path.posix.normalize(pathname);
4473
- return normalized === "/node_modules" || normalized.startsWith("/node_modules/") || normalized.endsWith("/node_modules") || normalized.includes("/node_modules/");
4474
- }
4475
- function isNodeModulesBinPath(pathname) {
4476
- const normalized = path.posix.normalize(pathname);
4477
- return normalized.includes("/node_modules/.bin/") || normalized.startsWith("/node_modules/.bin/");
4478
- }
4479
- var AutoParentMemoryProvider = class extends MemoryProvider {
4480
- ensureParentDir(pathname) {
4481
- const parent = path.posix.dirname(path.posix.normalize(pathname));
4482
- if (!parent || parent === "/" || parent === ".") return;
4483
- this.mkdirSync(parent, { recursive: true });
4484
- }
4485
- async mkdir(pathname, options) {
4486
- this.ensureParentDir(pathname);
4487
- return super.mkdir(pathname, options);
4488
- }
4489
- mkdirSync(pathname, options) {
4490
- this.ensureParentDir(pathname);
4491
- return super.mkdirSync(pathname, options);
4492
- }
4493
- async open(pathname, flags, mode) {
4494
- if (isWriteFlag(flags)) this.ensureParentDir(pathname);
4495
- return super.open(pathname, flags, isWriteFlag(flags) && isNodeModulesBinPath(pathname) ? (mode ?? 493) | 73 : mode);
4496
- }
4497
- openSync(pathname, flags, mode) {
4498
- if (isWriteFlag(flags)) this.ensureParentDir(pathname);
4499
- return super.openSync(pathname, flags, isWriteFlag(flags) && isNodeModulesBinPath(pathname) ? (mode ?? 493) | 73 : mode);
4500
- }
4501
- };
4502
- /**
4503
- * Resolve the main worktree root (where .moltnet/ lives — it's untracked,
4504
- * only exists in the main worktree, not in git worktrees).
4505
- */
4506
- function findMainWorktree(startPath = process.cwd()) {
4507
- let output;
4508
- try {
4509
- output = execFileSync("git", [
4510
- "-C",
4511
- startPath,
4512
- "worktree",
4513
- "list",
4514
- "--porcelain"
4515
- ], {
4516
- encoding: "utf8",
4517
- stdio: "pipe"
4518
- });
4519
- } catch (err) {
4520
- const message = err instanceof Error ? err.message : String(err);
4521
- throw new Error(`Git worktree discovery requires a git repository: ${message}`);
4522
- }
4523
- for (const block of output.split("\n\n")) {
4524
- const lines = block.split("\n");
4525
- const wt = lines.find((l) => l.startsWith("worktree "));
4526
- if (wt && !lines.some((l) => l === "bare")) return wt.replace("worktree ", "");
4527
- }
4528
- throw new Error("Could not find main git worktree");
4529
- }
4530
- function resolveVmAgentDir(config) {
4531
- const rootDir = config.agentRootDir ?? findMainWorktree();
4532
- return path.join(rootDir, ".moltnet", config.agentName);
4533
- }
4534
- function loadCredentials(agentDir, mode = "guest-config", onDiagnostic) {
4535
- const moltnetPath = path.join(agentDir, "moltnet.json");
4536
- const agentEnvPath = path.join(agentDir, "env");
4537
- const piAgentDir = resolvePiCodingAgentDir();
4538
- const piAuthPath = path.join(piAgentDir, "auth.json");
4539
- const piAuthJson = existsSync(piAuthPath) ? readFileSync(piAuthPath, "utf8") : null;
4540
- if (mode === "host-authenticated") return {
4541
- moltnetJson: "",
4542
- agentEnvRaw: "",
4543
- piAuthJson,
4544
- agentEnv: {},
4545
- gitconfig: null,
4546
- sshPrivateKey: null,
4547
- sshPublicKey: null,
4548
- allowedSigners: null,
4549
- githubAppPem: null,
4550
- githubAppPemFilename: null
4551
- };
4552
- const hasMoltnetJson = existsSync(moltnetPath);
4553
- const hasAgentEnv = existsSync(agentEnvPath);
4554
- if (!hasMoltnetJson || !hasAgentEnv) throw new Error(`Guest credential mode requires both ${moltnetPath} and ${agentEnvPath}`);
4555
- const moltnetJson = readFileSync(moltnetPath, "utf8");
4556
- const agentEnvRaw = readFileSync(agentEnvPath, "utf8");
4557
- if (moltnetJson.trim() === "") throw new Error(`Agent configuration is empty: ${moltnetPath}`);
4558
- const gitconfigPath = path.join(agentDir, "gitconfig");
4559
- const gitconfig = existsSync(gitconfigPath) ? readFileSync(gitconfigPath, "utf8") : null;
4560
- const sshDir = path.join(agentDir, "ssh");
4561
- const sshPrivateKey = existsSync(path.join(sshDir, "id_ed25519")) ? readFileSync(path.join(sshDir, "id_ed25519"), "utf8") : null;
4562
- const sshPublicKey = existsSync(path.join(sshDir, "id_ed25519.pub")) ? readFileSync(path.join(sshDir, "id_ed25519.pub"), "utf8") : null;
4563
- const allowedSigners = existsSync(path.join(sshDir, "allowed_signers")) ? readFileSync(path.join(sshDir, "allowed_signers"), "utf8") : null;
4564
- let githubAppPem = null;
4565
- let githubAppPemFilename = null;
4566
- const pemPath = (moltnetJson ? JSON.parse(moltnetJson) : null)?.github?.private_key_path;
4567
- if (pemPath) if (!existsSync(pemPath)) onDiagnostic?.({
4568
- event: "vm.credentials.github_key_missing",
4569
- level: "warning",
4570
- credentialMode: mode,
4571
- message: `github.private_key_path not found at ${pemPath}; moltnet github token will fail inside the guest`
4572
- });
4573
- else {
4574
- githubAppPem = readFileSync(pemPath, "utf8");
4575
- githubAppPemFilename = path.basename(pemPath);
4576
- }
4577
- return {
4578
- moltnetJson,
4579
- agentEnvRaw,
4580
- piAuthJson,
4581
- agentEnv: parseEnv(agentEnvRaw),
4582
- gitconfig,
4583
- sshPrivateKey,
4584
- sshPublicKey,
4585
- allowedSigners,
4586
- githubAppPem,
4587
- githubAppPemFilename
4588
- };
4589
- }
4590
- /**
4591
- * Apply agent env vars to the host process, mirroring `moltnet start`.
4592
- * Resolves relative paths (e.g. GIT_CONFIG_GLOBAL) against the repo root.
4593
- */
4594
- function activateAgentEnv(agentEnv, repoRoot) {
4595
- for (const [k, v] of Object.entries(agentEnv)) {
4596
- if (v === void 0 || v === null || v === "") continue;
4597
- let resolved = v;
4598
- if (k === "GIT_CONFIG_GLOBAL" && !path.isAbsolute(v)) resolved = path.join(repoRoot, v);
4599
- process.env[k] = resolved;
4600
- }
4601
- }
4602
- var BASE_ALLOWED_HOSTS = [
4603
- "api.openai.com",
4604
- "*.openai.com",
4605
- "chat.openai.com",
4606
- "chatgpt.com",
4607
- "*.chatgpt.com",
4608
- "registry.npmjs.org",
4609
- "github.com",
4610
- "*.github.com",
4611
- "*.githubusercontent.com",
4612
- "proxy.golang.org",
4613
- "sum.golang.org",
4614
- "golang.org",
4615
- "storage.googleapis.com",
4616
- "*.googlesource.com"
4617
- ];
4618
- var DEFAULT_MOLTNET_API_URL = "https://api.themolt.net";
4619
- /**
4620
- * Host environment names that may intentionally cross into a
4621
- * host-authenticated guest. This local list is the authority boundary;
4622
- * server-supplied runtime-profile `requiredEnv` cannot widen it.
4623
- */
4624
- var HOST_AUTHENTICATED_GUEST_ENV_ALLOWLIST = new Set([
4625
- "ANTHROPIC_API_KEY",
4626
- "OPENAI_API_KEY",
4627
- "OPENAI_BASE_URL",
4628
- "AZURE_OPENAI_API_KEY",
4629
- "AZURE_OPENAI_ENDPOINT",
4630
- "AZURE_OPENAI_API_VERSION",
4631
- "GOOGLE_API_KEY",
4632
- "GEMINI_API_KEY",
4633
- "MISTRAL_API_KEY",
4634
- "GROQ_API_KEY",
4635
- "OPENROUTER_API_KEY",
4636
- "XAI_API_KEY",
4637
- "CEREBRAS_API_KEY",
4638
- "DEEPSEEK_API_KEY",
4639
- "OLLAMA_API_KEY",
4640
- "OLLAMA_BASE_URL",
4641
- "AWS_ACCESS_KEY_ID",
4642
- "AWS_SECRET_ACCESS_KEY",
4643
- "AWS_SESSION_TOKEN",
4644
- "AWS_REGION",
4645
- "AWS_DEFAULT_REGION",
4646
- "GITHUB_TOKEN",
4647
- "GH_TOKEN",
4648
- "LINEAR_API_KEY"
4649
- ]);
4650
- var RESERVED_GUEST_ENVIRONMENT_NAMES = new Set([
4651
- "PATH",
4652
- "HOME",
4653
- "NODE_EXTRA_CA_CERTS",
4654
- "MOLTNET_GUEST_WORKSPACE",
4655
- "GIT_SSH",
4656
- "GIT_SSH_COMMAND",
4657
- "SSH_AUTH_SOCK"
4658
- ]);
4659
- function isReservedGuestEnvironmentName(name) {
4660
- return name.startsWith("MOLTNET_") || name.startsWith("GIT_CONFIG_") || RESERVED_GUEST_ENVIRONMENT_NAMES.has(name);
4661
- }
4662
- var GuestEnvironmentBoundaryError = class extends Error {
4663
- constructor(refusedNames) {
4664
- 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.`);
4665
- this.refusedNames = refusedNames;
4666
- this.name = "GuestEnvironmentBoundaryError";
4667
- }
4668
- };
4669
- function assertGuestEnvironmentBoundary(options) {
4670
- const refusedForwardEnv = (options.forwardEnv ?? []).filter((name) => isReservedGuestEnvironmentName(name) || options.guestCredentialMode === "host-authenticated" && !HOST_AUTHENTICATED_GUEST_ENV_ALLOWLIST.has(name));
4671
- const refusedSandboxEnv = Object.keys(options.sandboxEnv ?? {}).filter(isReservedGuestEnvironmentName);
4672
- const refused = [...new Set([...refusedForwardEnv, ...refusedSandboxEnv])].sort();
4673
- if (refused.length > 0) throw new GuestEnvironmentBoundaryError(refused);
4674
- }
4675
- /** @deprecated Prefer assertGuestEnvironmentBoundary for mode-aware checks. */
4676
- function assertHostAuthenticatedGuestEnvironment(options) {
4677
- assertGuestEnvironmentBoundary({
4678
- guestCredentialMode: "host-authenticated",
4679
- ...options
4680
- });
4681
- }
4682
- /**
4683
- * Return whether two Gondolin hostname globs can match at least one common
4684
- * string. Each `*` is an arbitrary substring, so this walks the product of the
4685
- * two small glob automata instead of relying on exact-string comparisons.
4686
- */
4687
- function hostnamePatternsOverlap(left, right) {
4688
- const a = left.trim().toLowerCase();
4689
- const b = right.trim().toLowerCase();
4690
- if (!a || !b) return false;
4691
- const pending = [[0, 0]];
4692
- const visited = /* @__PURE__ */ new Set();
4693
- while (pending.length > 0) {
4694
- const next = pending.pop();
4695
- if (!next) continue;
4696
- const [aIndex, bIndex] = next;
4697
- const state = `${aIndex}:${bIndex}`;
4698
- if (visited.has(state)) continue;
4699
- visited.add(state);
4700
- if (aIndex === a.length && bIndex === b.length) return true;
4701
- const aChar = a[aIndex];
4702
- const bChar = b[bIndex];
4703
- if (aChar === "*") pending.push([aIndex + 1, bIndex]);
4704
- if (bChar === "*") pending.push([aIndex, bIndex + 1]);
4705
- if (aChar !== void 0 && bChar !== void 0 && (aChar === "*" || bChar === "*" || aChar === bChar)) pending.push([aChar === "*" ? aIndex : aIndex + 1, bChar === "*" ? bIndex : bIndex + 1]);
4706
- }
4707
- return false;
4708
- }
4709
- function assertInternalHostsDoNotOverlapProtectedHosts(internalHosts, protectedHosts) {
4710
- for (const internalHost of internalHosts) {
4711
- const protectedHost = protectedHosts.find((candidate) => hostnamePatternsOverlap(internalHost, candidate));
4712
- if (protectedHost) throw new Error(`sandbox.network.allowedInternalHosts pattern "${internalHost}" overlaps external-only host pattern "${protectedHost}"`);
4713
- }
4714
- }
4715
- /**
4716
- * Run a shell command in the guest and throw if it fails. Mirror of
4717
- * `run()` in `snapshot.ts` for the resume-side hook chain — every
4718
- * setup step is essential to a healthy session, so a silent non-zero
4719
- * exit (e.g. a mount that fails into the FUSE write path, or a
4720
- * consumer-provided resume command that fails to install pnpm) must
4721
- * surface immediately rather than fall through to cryptic agent
4722
- * errors later.
4723
- */
4724
- async function vmRun(vm, label, command, signal) {
4725
- const wrapped = `set -eu\nset -o pipefail\n${command}`;
4726
- throwIfAborted(signal, `resume step "${label}"`);
4727
- const r = await vm.exec([
4728
- "sh",
4729
- "-c",
4730
- wrapped
4731
- ], { signal });
4732
- if (r.exitCode !== 0) {
4733
- const tail = [r.stderr, r.stdout].filter(Boolean).join("\n").slice(-800);
4734
- throw new Error(`resume step "${label}" failed (exit ${r.exitCode}):\n${tail}`);
4735
- }
4736
- }
4737
- function nonErrorMessage(err) {
4738
- if (typeof err === "string") return err;
4739
- try {
4740
- return JSON.stringify(err) ?? "unknown error";
4741
- } catch {
4742
- return "unknown error";
4743
- }
4744
- }
4745
- /**
4746
- * Resume a VM from a checkpoint, inject credentials, configure egress +
4747
- * TLS. Returns the managed VM handle.
4748
- */
4749
- async function resumeVm(config) {
4750
- throwIfAborted(config.signal, "VM resume");
4751
- const agentDir = resolveVmAgentDir(config);
4752
- const guestWorkspace = path.resolve(config.mountPath);
4753
- const guestCredentialMode = config.guestCredentialMode ?? "guest-config";
4754
- if (guestCredentialMode === "guest-config" && !existsSync(agentDir)) throw new Error(`Agent directory not found: ${agentDir}. Run: moltnet register --name ${config.agentName}`);
4755
- assertGuestEnvironmentBoundary({
4756
- guestCredentialMode,
4757
- forwardEnv: config.forwardEnv,
4758
- sandboxEnv: config.sandboxConfig?.env
4759
- });
4760
- config.onDiagnostic?.({
4761
- event: "vm.credentials.mode",
4762
- level: "info",
4763
- credentialMode: guestCredentialMode,
4764
- 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"
4765
- });
4766
- const creds = loadCredentials(agentDir, guestCredentialMode, config.onDiagnostic);
4767
- const configuredApiUrl = creds.moltnetJson ? JSON.parse(creds.moltnetJson).endpoints.api : void 0;
4768
- const apiHost = new URL(configuredApiUrl ?? process.env.MOLTNET_API_URL ?? DEFAULT_MOLTNET_API_URL).hostname;
4769
- const runtimeAllowedHosts = config.sandboxConfig?.network?.allowedHosts ?? [];
4770
- const runtimeAllowedInternalHosts = config.sandboxConfig?.network?.allowedInternalHosts ?? [];
4771
- const protectedExternalHosts = [...new Set([
4772
- ...BASE_ALLOWED_HOSTS,
4773
- apiHost,
4774
- ...config.extraAllowedHosts ?? []
4775
- ])];
4776
- assertInternalHostsDoNotOverlapProtectedHosts(runtimeAllowedInternalHosts, protectedExternalHosts);
4777
- const { httpHooks, env: secretEnv } = createHttpHooks({
4778
- allowedHosts: [...new Set([...protectedExternalHosts, ...runtimeAllowedHosts])],
4779
- allowedInternalHosts: runtimeAllowedInternalHosts
4780
- });
4781
- const vmAgentDir = `/home/agent/.moltnet/${config.agentName}`;
4782
- const vmAgentEnv = {};
4783
- for (const [k, v] of Object.entries(creds.agentEnv)) {
4784
- if (v === void 0 || v === "") continue;
4785
- if (k === "GIT_CONFIG_GLOBAL") vmAgentEnv[k] = `${vmAgentDir}/gitconfig`;
4786
- else if (k.endsWith("_PRIVATE_KEY_PATH")) vmAgentEnv[k] = `${vmAgentDir}/${path.basename(v)}`;
4787
- else vmAgentEnv[k] = v;
4788
- }
4789
- if (creds.moltnetJson) vmAgentEnv.MOLTNET_CREDENTIALS_PATH = `${vmAgentDir}/moltnet.json`;
4790
- const vfsConfig = resolveVfsShadowConfig(config.sandboxConfig);
4791
- let workspaceProvider = new RealFSProvider(config.mountPath);
4792
- workspaceProvider = new ShadowProvider(workspaceProvider, {
4793
- shouldShadow: ({ path: shadowPath }) => shouldShadowNodeModulesPath(shadowPath),
4794
- denySymlinkBypass: false,
4795
- tmpfs: new AutoParentMemoryProvider(),
4796
- writeMode: "tmpfs"
4797
- });
4798
- if (vfsConfig.mode !== "none") {
4799
- const predicate = createShadowPathPredicate(vfsConfig.patterns);
4800
- workspaceProvider = new ShadowProvider(workspaceProvider, {
4801
- shouldShadow: predicate,
4802
- writeMode: vfsConfig.mode
4803
- });
4804
- }
4805
- if (guestCredentialMode === "host-authenticated") workspaceProvider = new ShadowProvider(workspaceProvider, {
4806
- shouldShadow: ({ path: shadowPath }) => shadowPath.split("/").includes(".moltnet"),
4807
- denySymlinkBypass: true,
4808
- writeMode: "deny"
4809
- });
4810
- const forwardedEnv = {};
4811
- for (const name of config.forwardEnv ?? []) {
4812
- const value = process.env[name];
4813
- if (value === void 0 || value === "") continue;
4814
- forwardedEnv[name] = value;
4815
- }
4816
- const envOverrides = config.sandboxConfig?.env ?? {};
4817
- const vmEnv = {
4818
- ...secretEnv,
4819
- ...vmAgentEnv,
4820
- ...forwardedEnv,
4821
- PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/go/bin",
4822
- HOME: "/home/agent",
4823
- NODE_NO_WARNINGS: "1",
4824
- NODE_EXTRA_CA_CERTS: "/etc/ssl/certs/ca-certificates.crt",
4825
- ...envOverrides,
4826
- MOLTNET_GUEST_WORKSPACE: guestWorkspace
4827
- };
4828
- const resources = config.sandboxConfig?.resources;
4829
- const workspaceMode = config.workspaceMode ?? "shared_mount";
4830
- const vm = await abortableResource({
4831
- promise: VmCheckpoint.load(config.checkpointPath).resume({
4832
- httpHooks,
4833
- env: vmEnv,
4834
- ...resources?.memory && { memory: resources.memory },
4835
- ...resources?.cpus && { cpus: resources.cpus },
4836
- vfs: { mounts: {
4837
- [guestWorkspace]: workspaceProvider,
4838
- [GUEST_TASK_CONTEXT_MOUNT]: new MemoryProvider()
4839
- } }
4840
- }),
4841
- signal: config.signal,
4842
- label: "VM resume",
4843
- cleanup: (resumedVm) => resumedVm.close(),
4844
- onCleanupError: (err) => {
4845
- const message = err instanceof Error ? err.message : String(err);
4846
- process.stderr.write(`[vm] aborted resume late vm.close() failed: ${message}\n`);
4847
- }
4848
- });
4849
- try {
4850
- await vmRun(vm, "TLS certificates", `
4851
- cp /etc/gondolin/mitm/ca.crt /usr/local/share/ca-certificates/gondolin-mitm.crt
4852
- update-ca-certificates 2>/dev/null
4853
- cat /etc/gondolin/mitm/ca.crt >> /etc/ssl/certs/ca-certificates.crt
4854
- `, config.signal);
4855
- await vmRun(vm, "DNS resolvers", `printf 'nameserver 8.8.8.8\\nnameserver 1.1.1.1\\n' > /etc/resolv.conf`, config.signal);
4856
- await vmRun(vm, "git safe.directory", `git config --system --add safe.directory '*'`, config.signal);
4857
- for (const [i, entry] of (config.sandboxConfig?.resumeCommands ?? []).entries()) {
4858
- if (!shouldRunResumeCommand(entry, { workspaceMode })) continue;
4859
- const { run, retries, backoffMs } = typeof entry === "string" ? {
4860
- run: entry,
4861
- retries: 0,
4862
- backoffMs: 2e3
4863
- } : {
4864
- run: entry.run,
4865
- retries: entry.retries ?? 0,
4866
- backoffMs: entry.retryBackoffMs ?? 2e3
4867
- };
4868
- const label = `resumeCommands[${i}]`;
4869
- let lastErr;
4870
- for (let attempt = 0; attempt <= retries; attempt++) try {
4871
- await vmRun(vm, label, run, config.signal);
4872
- lastErr = void 0;
4873
- break;
4874
- } catch (err) {
4875
- lastErr = err;
4876
- if (attempt === retries) break;
4877
- await delay((attempt + 1) * backoffMs, config.signal, label);
4878
- }
4879
- if (lastErr) throw lastErr instanceof Error ? lastErr : new Error(nonErrorMessage(lastErr));
4880
- }
4881
- const vmSshDir = `${vmAgentDir}/ssh`;
4882
- const hasAgentFiles = guestCredentialMode === "guest-config";
4883
- await vm.exec(hasAgentFiles ? `mkdir -p ${vmAgentDir}/ssh /home/agent/.pi/agent` : "mkdir -p /home/agent/.pi/agent", { signal: config.signal });
4884
- if (creds.piAuthJson !== null) await vm.fs.writeFile("/home/agent/.pi/agent/auth.json", creds.piAuthJson, {
4885
- mode: 384,
4886
- signal: config.signal
4887
- });
4888
- if (hasAgentFiles) {
4889
- const vmMoltnetJson = rewriteMoltnetJsonPaths(creds.moltnetJson, vmAgentDir, vmSshDir, creds.githubAppPemFilename);
4890
- await vm.fs.writeFile(`${vmAgentDir}/moltnet.json`, vmMoltnetJson, {
4891
- mode: 384,
4892
- signal: config.signal
4893
- });
4894
- await vm.fs.writeFile(`${vmAgentDir}/env`, creds.agentEnvRaw, {
4895
- mode: 384,
4896
- signal: config.signal
4897
- });
4898
- if (creds.gitconfig) {
4899
- const vmGitconfig = rewriteGitconfigPaths(creds.gitconfig, vmSshDir, vmAgentDir);
4900
- await vm.fs.writeFile(`${vmAgentDir}/gitconfig`, vmGitconfig, {
4901
- mode: 420,
4902
- signal: config.signal
4903
- });
4904
- }
4905
- if (creds.sshPrivateKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519`, creds.sshPrivateKey, {
4906
- mode: 384,
4907
- signal: config.signal
4908
- });
4909
- if (creds.sshPublicKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519.pub`, creds.sshPublicKey, {
4910
- mode: 420,
4911
- signal: config.signal
4912
- });
4913
- if (creds.allowedSigners) await vm.fs.writeFile(`${vmSshDir}/allowed_signers`, creds.allowedSigners, {
4914
- mode: 420,
4915
- signal: config.signal
4916
- });
4917
- if (creds.githubAppPem && creds.githubAppPemFilename) await vm.fs.writeFile(`${vmAgentDir}/${creds.githubAppPemFilename}`, creds.githubAppPem, {
4918
- mode: 384,
4919
- signal: config.signal
4920
- });
4921
- }
4922
- await vm.exec(hasAgentFiles ? "chown -R agent:agent /home/agent/.pi /home/agent/.moltnet" : "chown -R agent:agent /home/agent/.pi", { signal: config.signal });
4923
- return {
4924
- vm,
4925
- credentials: creds,
4926
- mountPath: config.mountPath,
4927
- guestWorkspace,
4928
- agentDir
4929
- };
4930
- } catch (err) {
4931
- try {
4932
- await vm.close();
4933
- } catch (closeErr) {
4934
- const m = closeErr instanceof Error ? closeErr.message : String(closeErr);
4935
- process.stderr.write(`[vm] post-throw vm.close() failed: ${m}\n`);
4936
- }
4937
- throw err;
4938
- }
4939
- }
4940
- /**
4941
- * Rewrite host-absolute paths inside an agent gitconfig to VM-local
4942
- * equivalents before injecting it into the guest.
4943
- *
4944
- * Two rewrites:
4945
- * - `signingKey = <host path>` → `<vmSshDir>/id_ed25519`
4946
- * - `... credential-helper --credentials <host moltnet.json>`
4947
- * → `<vmAgentDir>/moltnet.json`
4948
- *
4949
- * The credential-helper line is generated host-side by `moltnet github setup`
4950
- * with a host-absolute `--credentials` path; inside the guest that path is
4951
- * invalid, so it must point at the VM-side moltnet.json. The `insteadOf`
4952
- * rewrite rule and every other line are workspace-independent and pass through
4953
- * unchanged. A gitconfig without a credential helper is rewritten only for
4954
- * `signingKey`.
4955
- *
4956
- * This is the single source of truth for git push auth in the guest: the
4957
- * injected gitconfig carries the tokenless mint-on-demand helper, so the VM
4958
- * no longer hand-rolls a credential-helper script or runs an imperative
4959
- * `git config --global ... insteadOf` against the guest $HOME.
4960
- */
4961
- function rewriteGitconfigPaths(gitconfig, vmSshDir, vmAgentDir) {
4962
- return gitconfig.replace(/signingKey\s*=\s*.+/g, `signingKey = ${vmSshDir}/id_ed25519`).replace(/(moltnet github credential-helper --credentials )\S+/g, `$1${vmAgentDir}/moltnet.json`);
4963
- }
4964
- /**
4965
- * Rewrite host-absolute paths inside moltnet.json to VM-local equivalents.
4966
- *
4967
- * Fields rewritten:
4968
- * ssh.private_key_path → <vmSshDir>/<basename of original>
4969
- * ssh.public_key_path → <vmSshDir>/<basename of original>
4970
- * git.config_path → <vmAgentDir>/gitconfig
4971
- * github.private_key_path → <vmAgentDir>/<pemFilename> (if present)
4972
- *
4973
- * All other fields are passed through unchanged.
4974
- * Throws if moltnetJson is not valid JSON — callers must not inject a broken
4975
- * moltnet.json into the guest.
4976
- */
4977
- function rewriteMoltnetJsonPaths(moltnetJson, vmAgentDir, vmSshDir, githubAppPemFilename) {
4978
- const config = JSON.parse(moltnetJson);
4979
- if (config.ssh && typeof config.ssh === "object") {
4980
- const ssh = config.ssh;
4981
- const origPrivate = typeof ssh.private_key_path === "string" ? ssh.private_key_path : null;
4982
- const origPublic = typeof ssh.public_key_path === "string" ? ssh.public_key_path : null;
4983
- config.ssh = {
4984
- ...ssh,
4985
- ...origPrivate !== null && { private_key_path: `${vmSshDir}/${path.basename(origPrivate)}` },
4986
- ...origPublic !== null && { public_key_path: `${vmSshDir}/${path.basename(origPublic)}` }
4987
- };
4988
- }
4989
- if (config.git && typeof config.git === "object") {
4990
- const git = { ...config.git };
4991
- git.config_path = `${vmAgentDir}/gitconfig`;
4992
- config.git = git;
4993
- }
4994
- if (githubAppPemFilename && config.github && typeof config.github === "object") {
4995
- const github = { ...config.github };
4996
- github.private_key_path = `${vmAgentDir}/${githubAppPemFilename}`;
4997
- config.github = github;
4998
- }
4999
- return JSON.stringify(config);
5000
- }
5001
- //#endregion
5002
3185
  //#region src/tool-operations.ts
5003
3186
  /**
5004
3187
  * Gondolin tool operations: redirect pi's built-in tool operations
@@ -5701,6 +3884,34 @@ function decisionContext(deps) {
5701
3884
  };
5702
3885
  }
5703
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
5704
3915
  //#region src/runtime/capability-discovery.ts
5705
3916
  var GuestExecutableProbeError = class extends Error {
5706
3917
  code;
@@ -6183,7 +4394,12 @@ function buildSandboxCapabilityInstructions(sandbox, policy) {
6183
4394
  ];
6184
4395
  const externalHosts = [...sandbox.allowedHosts].sort();
6185
4396
  const internalHosts = [...sandbox.allowedInternalHosts].sort();
6186
- 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.");
6187
4403
  return lines.join("\n");
6188
4404
  }
6189
4405
  function shellExecutableIsAvailable(policy, sandbox, executable) {
@@ -6196,20 +4412,27 @@ function buildCredentialInstructions(policy, sandbox) {
6196
4412
  const lines = [
6197
4413
  "## Identity & credentials",
6198
4414
  "",
6199
- "- Your credentials live at `/home/agent/.moltnet/<agent>/moltnet.json`",
6200
- " with the gitconfig and SSH key alongside. Do not move, copy, or expose",
6201
- " 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."
6202
4422
  ];
6203
4423
  const moltnetAvailable = shellExecutableIsAvailable(policy, sandbox, "moltnet");
6204
4424
  const ghAvailable = shellExecutableIsAvailable(policy, sandbox, "gh");
6205
4425
  const gitAvailable = shellExecutableIsAvailable(policy, sandbox, "git");
6206
- 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.");
6207
4427
  if (ghAvailable) {
6208
- lines.push("- This headless VM has no human GitHub token fallback. Every authorized", " `gh` write must use an inline App token.");
6209
- if (moltnetAvailable) lines.push("", " ```bash", " CREDS=\"$(cd \"$(dirname \"$GIT_CONFIG_GLOBAL\")\" && pwd)/moltnet.json\"", " GH_TOKEN=$(moltnet github token --credentials \"$CREDS\") gh <command>", " ```");
6210
- 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."]);
6211
4434
  }
6212
- 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.");
6213
4436
  return lines.join("\n");
6214
4437
  }
6215
4438
  /**
@@ -7147,7 +5370,7 @@ function gitRefExists(mainRepo, ref) {
7147
5370
  }
7148
5371
  function findMainWorktreeForDedicatedTask(startPath) {
7149
5372
  try {
7150
- return findMainWorktree(startPath);
5373
+ return findMainWorktree$1(startPath);
7151
5374
  } catch (err) {
7152
5375
  const message = err instanceof Error ? err.message : String(err);
7153
5376
  throw new Error(`Dedicated worktree tasks require a git repository: ${message}`);
@@ -7286,6 +5509,20 @@ function createGondolinToolDefinitions(config) {
7286
5509
  }
7287
5510
  ];
7288
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
+ }
7289
5526
  function createMoltNetAgentResolver(input) {
7290
5527
  let resolved;
7291
5528
  return () => {
@@ -7317,7 +5554,7 @@ function createPiTaskExecutor(opts) {
7317
5554
  if (!cachedCheckpoint) if (opts.runtimeDefinition) {
7318
5555
  cachedTemplate = await opts.runtimeDefinition.vm.resolve({ onProgress: opts.onSnapshotProgress });
7319
5556
  cachedCheckpoint = cachedTemplate.checkpointPath;
7320
- } else cachedCheckpoint = await ensureSnapshot({
5557
+ } else cachedCheckpoint = await ensureSnapshot$1({
7321
5558
  config: opts.sandboxConfig?.snapshot,
7322
5559
  onProgress: opts.onSnapshotProgress ?? ((m) => {
7323
5560
  process.stderr.write(`[snapshot] ${m}\n`);
@@ -7441,10 +5678,10 @@ async function executePiTask(claimedTask, reporter, opts) {
7441
5678
  reporterOpen = true;
7442
5679
  let checkpointPath;
7443
5680
  let resolvedVmTemplate = opts.resolvedVmTemplate;
7444
- let effectiveSandboxConfig;
5681
+ let brokeredSecretEnvNames = [];
7445
5682
  try {
7446
5683
  if (!resolvedVmTemplate && opts.runtimeDefinition) resolvedVmTemplate = opts.resolveVmTemplate ? await opts.resolveVmTemplate() : await opts.runtimeDefinition.vm.resolve({ onProgress: opts.onSnapshotProgress });
7447
- 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({
7448
5685
  config: opts.sandboxConfig?.snapshot,
7449
5686
  onProgress: opts.onSnapshotProgress ?? ((m) => {
7450
5687
  process.stderr.write(`[snapshot] ${m}\n`);
@@ -7469,13 +5706,33 @@ async function executePiTask(claimedTask, reporter, opts) {
7469
5706
  }
7470
5707
  if (!workspace) throw new Error("task workspace not prepared");
7471
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
+ }
7472
5733
  try {
7473
- effectiveSandboxConfig = applyExecutionPlanSandboxOverrides(resolvedVmTemplate ? {
7474
- ...opts.sandboxConfig,
7475
- snapshot: void 0,
7476
- resumeCommands: [...resolvedVmTemplate.resumeCommands]
7477
- } : opts.sandboxConfig, executionPlan);
7478
- 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)({
7479
5736
  checkpointPath,
7480
5737
  agentName: opts.agentName,
7481
5738
  agentRootDir,
@@ -7485,6 +5742,7 @@ async function executePiTask(claimedTask, reporter, opts) {
7485
5742
  extraAllowedHosts: opts.extraAllowedHosts,
7486
5743
  sandboxConfig: effectiveSandboxConfig,
7487
5744
  forwardEnv: opts.forwardEnv,
5745
+ brokeredSecrets,
7488
5746
  onDiagnostic: opts.onVmDiagnostic,
7489
5747
  signal: reporter.cancelSignal
7490
5748
  }));
@@ -7499,7 +5757,7 @@ async function executePiTask(claimedTask, reporter, opts) {
7499
5757
  }
7500
5758
  const diaryId = task.diaryId ?? "";
7501
5759
  const taskTeamId = task.teamId ?? "";
7502
- activateAgentEnv(managed.credentials.agentEnv, agentRootDir);
5760
+ activateAgentEnv$1(managed.credentials.agentEnv, agentRootDir);
7503
5761
  const activeWorkspace = preparedWorkspace;
7504
5762
  const activeManaged = managed;
7505
5763
  const getMoltNetAgent = createMoltNetAgentResolver({
@@ -7792,7 +6050,8 @@ async function executePiTask(claimedTask, reporter, opts) {
7792
6050
  nodeModulesWriteMode: "tmpfs",
7793
6051
  verifiedExecutables: verifiedGuestExecutables,
7794
6052
  allowedHosts: [...effectiveSandboxConfig?.network?.allowedHosts ?? [], ...opts.extraAllowedHosts ?? []],
7795
- allowedInternalHosts: effectiveSandboxConfig?.network?.allowedInternalHosts ?? []
6053
+ allowedInternalHosts: effectiveSandboxConfig?.network?.allowedInternalHosts ?? [],
6054
+ brokeredSecretEnvNames
7796
6055
  },
7797
6056
  toolPolicy: capabilityProjection.instructorPolicy
7798
6057
  });
@@ -8765,4 +7024,4 @@ function describeToolErrorMessage(result) {
8765
7024
  }
8766
7025
  }
8767
7026
  //#endregion
8768
- 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 };