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