@tangle-network/agent-runtime 0.337.2 → 0.339.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/{activation-DRHM55Oo.d.ts → activation-CJIIl_Ey.d.ts} +90 -22
- package/dist/activation-CVSFxis9.js +876 -0
- package/dist/activation-CVSFxis9.js.map +1 -0
- package/dist/agent.d.ts +4 -4
- package/dist/agent.js +4 -7
- package/dist/agent.js.map +1 -1
- package/dist/{bundle-BPGjESee.js → bundle-DVOhBTic.js} +2 -2
- package/dist/{bundle-BPGjESee.js.map → bundle-DVOhBTic.js.map} +1 -1
- package/dist/candidate-execution/index.d.ts +4 -63
- package/dist/candidate-execution/index.js +6 -146
- package/dist/candidate-execution/index.js.map +1 -1
- package/dist/content-digest-sBTLujlK.js +9 -0
- package/dist/content-digest-sBTLujlK.js.map +1 -0
- package/dist/{delegate-B-T_ZB50.js → delegate-CCWE_AkU.js} +2 -2
- package/dist/{delegate-B-T_ZB50.js.map → delegate-CCWE_AkU.js.map} +1 -1
- package/dist/{coordination-driver-BHkFVqIq.js → driver-control-acknowledgers-C53ggdJ6.js} +4520 -4442
- package/dist/driver-control-acknowledgers-C53ggdJ6.js.map +1 -0
- package/dist/{durable-file-B_eJr6Mb.js → durable-file-gNI9ILAn.js} +1 -3
- package/dist/durable-file-gNI9ILAn.js.map +1 -0
- package/dist/durable.d.ts +3 -35
- package/dist/durable.js +225 -229
- package/dist/durable.js.map +1 -1
- package/dist/{graph-C4RFlWAJ.js → graph-CI9JXKqO.js} +65 -18
- package/dist/graph-CI9JXKqO.js.map +1 -0
- package/dist/{graph-registry-DAI6KNXB.js → graph-registry-BnO64cn8.js} +18 -17
- package/dist/graph-registry-BnO64cn8.js.map +1 -0
- package/dist/{improvement-cycle-D1I4hQvf.js → improve-BOXTLWJ3.js} +76 -1248
- package/dist/improve-BOXTLWJ3.js.map +1 -0
- package/dist/{improvement-adapter-D5gwwoXQ.d.ts → improvement-adapter-HUt_-cWA.d.ts} +2 -20
- package/dist/index.d.ts +10 -207
- package/dist/index.js +91 -305
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +10 -229
- package/dist/intelligence.js +506 -88
- package/dist/intelligence.js.map +1 -1
- package/dist/{jsonl-file-DBv31uPi.js → jsonl-file-C9Kde_0Z.js} +2 -2
- package/dist/{jsonl-file-DBv31uPi.js.map → jsonl-file-C9Kde_0Z.js.map} +1 -1
- package/dist/kernel.d.ts +4 -3
- package/dist/kernel.js +12 -13
- package/dist/loop-runner-CWzG1bH4.d.ts +32 -0
- package/dist/loop-runner-bin.d.ts +32 -2
- package/dist/loop-runner-bin.js +93 -1
- package/dist/loop-runner-bin.js.map +1 -0
- package/dist/loop-runner-pmPIHFVY.js +59 -0
- package/dist/loop-runner-pmPIHFVY.js.map +1 -0
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +101 -416
- package/dist/mcp/index.js +122 -206
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/memory-bin.js +231 -1
- package/dist/mcp/memory-bin.js.map +1 -1
- package/dist/model-policy-CyCUwHed.js.map +1 -1
- package/dist/placement.d.ts +2 -9
- package/dist/placement.js +1 -1
- package/dist/placement.js.map +1 -1
- package/dist/platform.d.ts +194 -2
- package/dist/platform.js +145 -9
- package/dist/platform.js.map +1 -1
- package/dist/{prepare-CER-ZkPp.js → prepare-bXkvPMJA.js} +1213 -710
- package/dist/prepare-bXkvPMJA.js.map +1 -0
- package/dist/{profile-gNUsHNIJ.js → profile-Dpei_Sq0.js} +89 -3
- package/dist/profile-Dpei_Sq0.js.map +1 -0
- package/dist/{profile-materialization-BnoEf1co.d.ts → profile-materialization-B1wm2SPT.d.ts} +3 -5
- package/dist/profiles.d.ts +2 -2
- package/dist/profiles.js +4 -43
- package/dist/profiles.js.map +1 -1
- package/dist/{protected-model-port-BhpQRTw-.d.ts → protected-model-port-BGgpy2r9.d.ts} +4 -30
- package/dist/protected-redaction-DFCeo4vg.js.map +1 -1
- package/dist/{provision-supervisor-DGegCUH4.js → provision-supervisor-CJ6Zz6dz.js} +13 -10
- package/dist/provision-supervisor-CJ6Zz6dz.js.map +1 -0
- package/dist/record-value-B6bwJxLa.js +12 -0
- package/dist/record-value-B6bwJxLa.js.map +1 -0
- package/dist/{run-layout-Cu8H7aeh.js → run-cancellation-C9oL62sW.js} +175 -100
- package/dist/run-cancellation-C9oL62sW.js.map +1 -0
- package/dist/{runtime-BOGpotnH.js → runtime-B2fjL9Oi.js} +2639 -3187
- package/dist/runtime-B2fjL9Oi.js.map +1 -0
- package/dist/{runtime-hooks-Bj6wJHlH.d.ts → runtime-hooks-nsqxSUFL.d.ts} +2 -6
- package/dist/{sandbox-profile-compat-QTkLe1Z6.js → sandbox-profile-compat-BzVBFqrz.js} +690 -699
- package/dist/sandbox-profile-compat-BzVBFqrz.js.map +1 -0
- package/dist/search-executor-zMumiak_.js +1916 -0
- package/dist/search-executor-zMumiak_.js.map +1 -0
- package/dist/{server-hZWM-G8w.js → server-_4voDNrl.js} +362 -67
- package/dist/server-_4voDNrl.js.map +1 -0
- package/dist/{search-executor-Cm1WZmL-.js → structural-rollout-DpLCXzaL.js} +1680 -3403
- package/dist/structural-rollout-DpLCXzaL.js.map +1 -0
- package/dist/{substrate-B0TYNrXn.js → substrate-CGDFTSRh.js} +2 -9
- package/dist/substrate-CGDFTSRh.js.map +1 -0
- package/dist/{supervise-CvAKVfue.js → supervise-qAwXmPtD.js} +7768 -5360
- package/dist/supervise-qAwXmPtD.js.map +1 -0
- package/dist/{supervisor-Rz8AGpYb.js → supervisor-DHrUJhEZ.js} +26301 -26964
- package/dist/supervisor-DHrUJhEZ.js.map +1 -0
- package/dist/testing.d.ts +227 -2
- package/dist/testing.js +13 -14
- package/dist/testing.js.map +1 -1
- package/dist/tool-loop.d.ts +1 -1
- package/dist/{tool-server-Dqer2M4_.js → tool-server-XV4aJwoQ.js} +2 -2
- package/dist/tool-server-XV4aJwoQ.js.map +1 -0
- package/dist/top-snapshot-CbZE1JsU.js +502 -0
- package/dist/top-snapshot-CbZE1JsU.js.map +1 -0
- package/dist/tui/bin.js +711 -1
- package/dist/tui/bin.js.map +1 -1
- package/dist/tui/index.d.ts +5 -71
- package/dist/tui/index.js +3 -3
- package/dist/{types-Dl5L4r8Z.d.ts → types-CaOXqRTq.d.ts} +16230 -17376
- package/dist/workspace-store/index.d.ts +2 -27
- package/dist/workspace-store/index.js +2 -2
- package/dist/workspace-store/index.js.map +1 -1
- package/dist/{workspace-streams-JLjh5DC6.d.ts → workspace-streams-BiS4ABah.d.ts} +19 -32
- package/package.json +2 -2
- package/skills/agent-graphs/SKILL.md +10 -3
- package/skills/agent-graphs/references/authoring.md +18 -0
- package/skills/profile-authoring/SKILL.md +3 -3
- package/skills/supervise/SKILL.md +2 -2
- package/dist/activation-BDP6Eivv.js +0 -234
- package/dist/activation-BDP6Eivv.js.map +0 -1
- package/dist/coder-yhVWbdWc.js +0 -40
- package/dist/coder-yhVWbdWc.js.map +0 -1
- package/dist/coordination-driver-BHkFVqIq.js.map +0 -1
- package/dist/delegation-status-CF4D0NZ_.js +0 -358
- package/dist/delegation-status-CF4D0NZ_.js.map +0 -1
- package/dist/durable-file-B_eJr6Mb.js.map +0 -1
- package/dist/graph-C4RFlWAJ.js.map +0 -1
- package/dist/graph-registry-DAI6KNXB.js.map +0 -1
- package/dist/improvement-cycle-D1I4hQvf.js.map +0 -1
- package/dist/integrations-DopfCC2a.d.ts +0 -190
- package/dist/integrations-dFi-ALk7.js +0 -132
- package/dist/integrations-dFi-ALk7.js.map +0 -1
- package/dist/kb-gate-C8z2juK8.d.ts +0 -80
- package/dist/kb-gate-DpaSwXVx.js +0 -80
- package/dist/kb-gate-DpaSwXVx.js.map +0 -1
- package/dist/loop-runner-bin-DOuBIJ2O.d.ts +0 -164
- package/dist/loop-runner-bin-Dv5QvYDH.js +0 -246
- package/dist/loop-runner-bin-Dv5QvYDH.js.map +0 -1
- package/dist/memory-server-DXQ8IxGR.js +0 -234
- package/dist/memory-server-DXQ8IxGR.js.map +0 -1
- package/dist/openai-tools-DoQ32vpe.js +0 -44
- package/dist/openai-tools-DoQ32vpe.js.map +0 -1
- package/dist/openai-tools-Oxk4KQrY.d.ts +0 -24
- package/dist/prepare-CER-ZkPp.js.map +0 -1
- package/dist/profile-gNUsHNIJ.js.map +0 -1
- package/dist/protected-model-port-BuRsviSL.js +0 -766
- package/dist/protected-model-port-BuRsviSL.js.map +0 -1
- package/dist/provision-supervisor-DGegCUH4.js.map +0 -1
- package/dist/run-layout-Cu8H7aeh.js.map +0 -1
- package/dist/runtime-BOGpotnH.js.map +0 -1
- package/dist/sandbox-profile-compat-QTkLe1Z6.js.map +0 -1
- package/dist/search-executor-Cm1WZmL-.js.map +0 -1
- package/dist/server-hZWM-G8w.js.map +0 -1
- package/dist/substrate-B0TYNrXn.js.map +0 -1
- package/dist/supervise-CvAKVfue.js.map +0 -1
- package/dist/supervisor-Rz8AGpYb.js.map +0 -1
- package/dist/tool-server-Dqer2M4_.js.map +0 -1
- package/dist/top-app-BaMw1W2z.js +0 -1212
- package/dist/top-app-BaMw1W2z.js.map +0 -1
|
@@ -1,14 +1,1185 @@
|
|
|
1
|
-
import { A as verifyMaterializedWorkspace, B as omitTopLevelDigest, E as readVerifiedArtifact, F as canonicalCandidateDigest$1, I as canonicalCandidateDocument, L as deepFreezeCandidate, O as verifyBytes, P as canonicalCandidateBytes$1, R as embeddedCandidateArtifact, S as artifactCacheKey, T as readMaterializedWorkspaceFiles, V as sha256Bytes$1,
|
|
1
|
+
import { A as verifyMaterializedWorkspace, B as omitTopLevelDigest, E as readVerifiedArtifact, F as canonicalCandidateDigest$1, I as canonicalCandidateDocument, L as deepFreezeCandidate, O as verifyBytes, P as canonicalCandidateBytes$1, R as embeddedCandidateArtifact, S as artifactCacheKey, T as readMaterializedWorkspaceFiles, U as hasControlCharacter, V as sha256Bytes$1, Z as errorMessage, b as isWellFormedUnicode, d as readCandidateGitHubResource, f as verifyCandidateCode, j as verifyWorkspaceSnapshotArtifacts, k as verifyMaterializedProfileWorkspace, m as verifyTaskOutcomePatch, p as verifyTaskCheckout, v as persistCandidateOutputArtifact, y as assertExactObjectKeys, z as immutableCandidateValue } from "./sandbox-profile-compat-BzVBFqrz.js";
|
|
2
|
+
import { _ as parseAgentCandidateProfileActivation, c as buildLaunch, f as exactProfileExecutorFiles, h as modelRoutes, l as candidateEntrypointReceipt, m as materializeAgentCandidateProfilePlan, n as PI_CANDIDATE_SESSION_DIR_ENV, r as absoluteExecutionCwd, s as assertCandidateProfileExecutionSupport, t as PI_CANDIDATE_AGENT_DIR_ENV, u as candidateProfileAgentPaths, x as requirePublicCandidateConfigValue } from "./profile-Dpei_Sq0.js";
|
|
2
3
|
import { i as redactProtectedValue, n as createProtectedRecordRedactor, r as redactProtectedReason, t as assertNoProtectedBytes } from "./protected-redaction-DFCeo4vg.js";
|
|
3
|
-
import { f as parseAgentCandidateProfileActivation, g as requirePublicCandidateConfigValue, n as PI_CANDIDATE_SESSION_DIR_ENV, o as assertCandidateProfileExecutionSupport, s as candidateProfileAgentPaths, t as PI_CANDIDATE_AGENT_DIR_ENV, u as materializeAgentCandidateProfilePlan } from "./profile-gNUsHNIJ.js";
|
|
4
4
|
import { agentCandidateArtifactRefSchema, agentCandidateBenchmarkResultEvidenceSchema, agentCandidateBenchmarkSuiteSchema, agentCandidateBenchmarkTaskSchema, agentCandidateBundleSchema, agentCandidateContainerSchema, agentCandidateExecutionLimitsSchema, agentCandidateExecutionPlanEvidenceSchema, agentCandidateExecutionPlanMaterialSchema, agentCandidateMaterializationReceiptSchema, agentCandidateModelAccessNetworkSchema, agentCandidateModelSettlementEvidenceSchema, agentCandidateProfilePlanEvidenceSchema, agentCandidateResolvedModelSchema, agentCandidateRunCellSchema, agentCandidateRunReceiptSchema, agentCandidateTaskOutcomeEvidenceSchema, agentCandidateTaskOutcomeSpecSchema, agentCandidateTerminationSchema, agentCandidateWorkspaceManifestMaterialSchema, agentCandidateWorkspaceSnapshotEvidenceSchema, sha256DigestSchema } from "@tangle-network/agent-interface";
|
|
5
5
|
import { REDACTION_VERSION, isLlmSpan } from "@tangle-network/agent-eval";
|
|
6
6
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
7
7
|
import { isAbsolute, join, posix, relative, resolve } from "node:path";
|
|
8
8
|
import { lstat, mkdtemp, readFile, readdir, rm } from "node:fs/promises";
|
|
9
9
|
import { applyAgentCandidateWorkspacePlan } from "@tangle-network/agent-profile-materialize";
|
|
10
|
+
import { isDeepStrictEqual } from "node:util";
|
|
10
11
|
import { tmpdir } from "node:os";
|
|
11
12
|
import { hashKnowledgeBase } from "@tangle-network/agent-knowledge";
|
|
13
|
+
//#region src/candidate-execution/execution-roots.ts
|
|
14
|
+
/** Validate the canonical identity and isolation boundary for execution roots. */
|
|
15
|
+
function assertAgentCandidateExecutionRoots(roots) {
|
|
16
|
+
const entries = [
|
|
17
|
+
["task", roots.taskRoot],
|
|
18
|
+
["candidate", roots.candidateRoot],
|
|
19
|
+
["profile", roots.profileRoot]
|
|
20
|
+
];
|
|
21
|
+
for (const [name, root] of entries) {
|
|
22
|
+
if (root === void 0) continue;
|
|
23
|
+
if (!posix.isAbsolute(root) || posix.normalize(root) !== root) throw new Error(`execution ${name} root must be a canonical absolute path`);
|
|
24
|
+
}
|
|
25
|
+
for (let left = 0; left < entries.length; left++) {
|
|
26
|
+
const entry = entries[left];
|
|
27
|
+
if (!entry) continue;
|
|
28
|
+
const [leftName, leftRoot] = entry;
|
|
29
|
+
if (leftRoot === void 0) continue;
|
|
30
|
+
for (let right = left + 1; right < entries.length; right++) {
|
|
31
|
+
const rightEntry = entries[right];
|
|
32
|
+
if (!rightEntry) continue;
|
|
33
|
+
const [rightName, rightRoot] = rightEntry;
|
|
34
|
+
if (rightRoot !== void 0 && executionPathsOverlap(leftRoot, rightRoot)) throw new Error(`execution ${leftName} and ${rightName} roots must be distinct and non-overlapping`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function executionPathsOverlap(left, right) {
|
|
39
|
+
const a = posix.normalize(left);
|
|
40
|
+
const b = posix.normalize(right);
|
|
41
|
+
return a === b || b.startsWith(a === "/" ? "/" : `${a}/`) || a.startsWith(b === "/" ? "/" : `${b}/`);
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/candidate-execution/types.ts
|
|
45
|
+
const verifiedCandidateBrand = Symbol("verifiedAgentCandidate");
|
|
46
|
+
const preparedCandidateBrand = Symbol("preparedAgentCandidate");
|
|
47
|
+
const verifiedTaskOutcomeBrand = Symbol("verifiedTaskOutcome");
|
|
48
|
+
/** Protected trace tags that bind a run to one prepared candidate execution. */
|
|
49
|
+
const CANDIDATE_TRACE_TAGS = {
|
|
50
|
+
executionId: "tangle.candidate.execution_id",
|
|
51
|
+
bundleDigest: "tangle.candidate.bundle_digest",
|
|
52
|
+
executionPlanDigest: "tangle.candidate.execution_plan_digest",
|
|
53
|
+
materializationReceiptDigest: "tangle.candidate.materialization_receipt_digest"
|
|
54
|
+
};
|
|
55
|
+
/** Environment keys used to propagate immutable candidate trace identity. */
|
|
56
|
+
const CANDIDATE_TRACE_ENV = {
|
|
57
|
+
executionId: "TANGLE_CANDIDATE_EXECUTION_ID",
|
|
58
|
+
bundleDigest: "TANGLE_CANDIDATE_BUNDLE_DIGEST",
|
|
59
|
+
executionPlanDigest: "TANGLE_CANDIDATE_EXECUTION_PLAN_DIGEST",
|
|
60
|
+
materializationReceiptDigest: "TANGLE_CANDIDATE_MATERIALIZATION_RECEIPT_DIGEST",
|
|
61
|
+
traceRunId: "TANGLE_TRACE_RUN_ID"
|
|
62
|
+
};
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/candidate-execution/verify.ts
|
|
65
|
+
const verifiedCandidateState = /* @__PURE__ */ new WeakMap();
|
|
66
|
+
/** Surfaces admitted by Runtime's verifier before an environment adapter is selected. */
|
|
67
|
+
const AGENT_CANDIDATE_EXECUTION_SUPPORT = Object.freeze({
|
|
68
|
+
outcomes: Object.freeze(["workspace", "output"]),
|
|
69
|
+
code: Object.freeze([
|
|
70
|
+
"disabled",
|
|
71
|
+
"no-op",
|
|
72
|
+
"git-patch"
|
|
73
|
+
]),
|
|
74
|
+
memory: Object.freeze(["disabled", "isolated"]),
|
|
75
|
+
knowledge: true,
|
|
76
|
+
profile: Object.freeze({
|
|
77
|
+
mcpTransports: Object.freeze(["stdio"]),
|
|
78
|
+
remoteMcp: false,
|
|
79
|
+
tools: false,
|
|
80
|
+
permissions: false,
|
|
81
|
+
modes: false,
|
|
82
|
+
confidential: false
|
|
83
|
+
})
|
|
84
|
+
});
|
|
85
|
+
/** Verifies every digest, resource, workspace, and Git object in a candidate bundle. */
|
|
86
|
+
async function verifyAgentCandidateBundle(input, ports) {
|
|
87
|
+
const parsed = agentCandidateBundleSchema.parse(input);
|
|
88
|
+
const withoutDigest = omitTopLevelDigest(parsed);
|
|
89
|
+
const actualDigest = canonicalCandidateDigest$1(withoutDigest);
|
|
90
|
+
if (actualDigest !== parsed.digest) throw new Error(`candidate bundle digest ${parsed.digest} does not match ${actualDigest}`);
|
|
91
|
+
const canonicalBytes = canonicalCandidateBytes$1(withoutDigest);
|
|
92
|
+
verifyBytes(canonicalBytes, parsed.digest, canonicalBytes.byteLength, "candidate bundle");
|
|
93
|
+
assertCandidateProfileExecutionSupport(parsed.profile);
|
|
94
|
+
const artifactBytes = /* @__PURE__ */ new Map();
|
|
95
|
+
const readArtifact = async (artifact) => {
|
|
96
|
+
const key = artifactCacheKey(artifact);
|
|
97
|
+
const existing = artifactBytes.get(key);
|
|
98
|
+
if (existing) return Uint8Array.from(existing);
|
|
99
|
+
const bytes = await readVerifiedArtifact(artifact, ports.artifacts);
|
|
100
|
+
artifactBytes.set(key, Uint8Array.from(bytes));
|
|
101
|
+
return bytes;
|
|
102
|
+
};
|
|
103
|
+
let patchBytes;
|
|
104
|
+
if (parsed.code.kind === "git-patch") patchBytes = await readArtifact(parsed.code.patch.artifact);
|
|
105
|
+
const materializedTree = await verifyCandidateCode(parsed.code, ports.repositories, patchBytes);
|
|
106
|
+
const resourceBytes = /* @__PURE__ */ new Map();
|
|
107
|
+
for (const resource of candidateResources(parsed)) {
|
|
108
|
+
const bytes = resource.kind === "inline" ? Buffer.from(resource.content, "utf8") : await readCandidateGitHubResource(resource, ports.repositories);
|
|
109
|
+
verifyBytes(bytes, resource.sha256, resource.byteLength, `candidate resource ${resource.name ?? (resource.kind === "github" ? resource.path : "<unnamed>")}`);
|
|
110
|
+
resourceBytes.set(resourceKey(resource), Uint8Array.from(bytes));
|
|
111
|
+
resourceBytes.set(resource.sha256, Uint8Array.from(bytes));
|
|
112
|
+
}
|
|
113
|
+
if (parsed.execution.workspace) {
|
|
114
|
+
const workspace = await verifyWorkspaceSnapshotArtifacts(parsed.execution.workspace, ports.artifacts);
|
|
115
|
+
artifactBytes.set(artifactCacheKey(parsed.execution.workspace.manifest), workspace.manifest);
|
|
116
|
+
artifactBytes.set(artifactCacheKey(parsed.execution.workspace.archive), workspace.archive);
|
|
117
|
+
}
|
|
118
|
+
if (parsed.knowledge) {
|
|
119
|
+
const knowledge = await verifyWorkspaceSnapshotArtifacts(parsed.knowledge.snapshot, ports.artifacts);
|
|
120
|
+
artifactBytes.set(artifactCacheKey(parsed.knowledge.snapshot.manifest), knowledge.manifest);
|
|
121
|
+
artifactBytes.set(artifactCacheKey(parsed.knowledge.snapshot.archive), knowledge.archive);
|
|
122
|
+
if (parsed.knowledge.retrievalConfig) await readArtifact(parsed.knowledge.retrievalConfig);
|
|
123
|
+
await readArtifact(parsed.knowledge.evaluation);
|
|
124
|
+
}
|
|
125
|
+
if (parsed.memory.mode === "isolated" && parsed.memory.seed) await readArtifact(parsed.memory.seed);
|
|
126
|
+
deepFreezeCandidate(parsed);
|
|
127
|
+
const verified = Object.freeze({
|
|
128
|
+
bundle: parsed,
|
|
129
|
+
...materializedTree === void 0 ? {} : { materializedTree },
|
|
130
|
+
[verifiedCandidateBrand]: true
|
|
131
|
+
});
|
|
132
|
+
verifiedCandidateState.set(verified, {
|
|
133
|
+
ports,
|
|
134
|
+
artifactBytes,
|
|
135
|
+
resourceBytes
|
|
136
|
+
});
|
|
137
|
+
return verified;
|
|
138
|
+
}
|
|
139
|
+
function getVerifiedCandidateState(candidate) {
|
|
140
|
+
const state = verifiedCandidateState.get(candidate);
|
|
141
|
+
if (!state || candidate[verifiedCandidateBrand] !== true) throw new Error("candidate must come from verifyAgentCandidateBundle");
|
|
142
|
+
return state;
|
|
143
|
+
}
|
|
144
|
+
async function verifiedArtifactBytes(candidate, artifact) {
|
|
145
|
+
const state = getVerifiedCandidateState(candidate);
|
|
146
|
+
const key = artifactCacheKey(artifact);
|
|
147
|
+
const existing = state.artifactBytes.get(key);
|
|
148
|
+
if (existing) return Uint8Array.from(existing);
|
|
149
|
+
const bytes = await readVerifiedArtifact(artifact, state.ports.artifacts);
|
|
150
|
+
state.artifactBytes.set(key, Uint8Array.from(bytes));
|
|
151
|
+
return bytes;
|
|
152
|
+
}
|
|
153
|
+
function verifiedResourceTextByDigest(candidate) {
|
|
154
|
+
const output = /* @__PURE__ */ new Map();
|
|
155
|
+
for (const resource of candidateResources(candidate.bundle)) {
|
|
156
|
+
const bytes = getVerifiedCandidateState(candidate).resourceBytes.get(resource.sha256);
|
|
157
|
+
if (!bytes) throw new Error(`candidate resource digest was not verified: ${resource.sha256}`);
|
|
158
|
+
output.set(resource.sha256, new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
159
|
+
}
|
|
160
|
+
return output;
|
|
161
|
+
}
|
|
162
|
+
function candidateResources(bundle) {
|
|
163
|
+
const resources = bundle.profile.resources;
|
|
164
|
+
if (!resources) return [];
|
|
165
|
+
const output = [];
|
|
166
|
+
for (const mount of resources.files ?? []) output.push(mount.resource);
|
|
167
|
+
output.push(...resources.tools ?? []);
|
|
168
|
+
output.push(...resources.skills ?? []);
|
|
169
|
+
output.push(...resources.agents ?? []);
|
|
170
|
+
output.push(...resources.commands ?? []);
|
|
171
|
+
if (typeof resources.instructions === "object") output.push(resources.instructions);
|
|
172
|
+
return output;
|
|
173
|
+
}
|
|
174
|
+
function resourceKey(resource) {
|
|
175
|
+
return canonicalCandidateDigest$1(resource);
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/candidate-execution/knowledge.ts
|
|
179
|
+
/** Environment variable containing the materialized candidate knowledge root. */
|
|
180
|
+
const CANDIDATE_KNOWLEDGE_ROOT_ENV = "TANGLE_CANDIDATE_KNOWLEDGE_ROOT";
|
|
181
|
+
/** Environment variable containing the materialized retrieval configuration path. */
|
|
182
|
+
const CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV = "TANGLE_CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG";
|
|
183
|
+
/** Deterministic, signed locations used by every candidate executor. */
|
|
184
|
+
function candidateKnowledgeExecutionPaths(taskRoot, hasRetrievalConfig) {
|
|
185
|
+
if (!posix.isAbsolute(taskRoot) || posix.normalize(taskRoot) !== taskRoot) throw new Error("candidate knowledge requires a normalized absolute task root");
|
|
186
|
+
const parent = posix.join(taskRoot, ".tangle");
|
|
187
|
+
return {
|
|
188
|
+
root: posix.join(parent, "knowledge"),
|
|
189
|
+
...hasRetrievalConfig ? { retrievalConfig: posix.join(parent, "knowledge-retrieval-config.json") } : {}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/** Verify and detach the exact file-backed knowledge admitted by the bundle. */
|
|
193
|
+
async function prepareAgentCandidateKnowledge(candidate, ports, arm) {
|
|
194
|
+
const knowledge = candidate.bundle.knowledge;
|
|
195
|
+
if (!knowledge) return void 0;
|
|
196
|
+
const archive = await verifiedArtifactBytes(candidate, knowledge.snapshot.archive);
|
|
197
|
+
const retrievalConfig = knowledge.retrievalConfig ? await verifiedArtifactBytes(candidate, knowledge.retrievalConfig) : void 0;
|
|
198
|
+
const root = await mkdtemp(join(tmpdir(), "agent-candidate-knowledge-"));
|
|
199
|
+
try {
|
|
200
|
+
await ports.workspaces.materialize({
|
|
201
|
+
role: "knowledge",
|
|
202
|
+
snapshot: knowledge.snapshot,
|
|
203
|
+
archive,
|
|
204
|
+
destination: root
|
|
205
|
+
});
|
|
206
|
+
const expectedHash = arm === "baseline" ? knowledge.candidate.baseHash : knowledge.candidate.candidateHash;
|
|
207
|
+
const actualHash = `sha256:${await hashKnowledgeBase(root, knowledge.stateScope)}`;
|
|
208
|
+
if (actualHash !== expectedHash) throw new Error(`materialized ${arm} knowledge does not match its measured content: expected ${expectedHash}, got ${actualHash}`);
|
|
209
|
+
const files = await readMaterializedWorkspaceFiles(root, knowledge.snapshot.material);
|
|
210
|
+
return Object.freeze({
|
|
211
|
+
candidate: knowledge.candidate,
|
|
212
|
+
...knowledge.stateScope ? { stateScope: Object.freeze({ ...knowledge.stateScope }) } : {},
|
|
213
|
+
snapshot: knowledge.snapshot,
|
|
214
|
+
files: Object.freeze(files.map((file) => Object.freeze({
|
|
215
|
+
path: file.path,
|
|
216
|
+
mode: file.mode,
|
|
217
|
+
bytes: Uint8Array.from(file.bytes)
|
|
218
|
+
}))),
|
|
219
|
+
...retrievalConfig ? { retrievalConfig: Uint8Array.from(retrievalConfig) } : {}
|
|
220
|
+
});
|
|
221
|
+
} finally {
|
|
222
|
+
await rm(root, {
|
|
223
|
+
recursive: true,
|
|
224
|
+
force: true
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region src/candidate-execution/exact-process-executor.ts
|
|
230
|
+
const DEFAULT_PROVISION_TIMEOUT_MS = 12e4;
|
|
231
|
+
const DEFAULT_RECOVERY_RETENTION_MS = 15 * 6e4;
|
|
232
|
+
/** Adapt one neutral exact-process provider to Runtime's trusted candidate boundary. */
|
|
233
|
+
function exactProcessProviderAsCandidateExecutor(options) {
|
|
234
|
+
return new ExactProcessAgentCandidateExecutor(options);
|
|
235
|
+
}
|
|
236
|
+
var ExactProcessAgentCandidateExecutor = class {
|
|
237
|
+
options;
|
|
238
|
+
states = /* @__PURE__ */ new Map();
|
|
239
|
+
exactProcess;
|
|
240
|
+
resources;
|
|
241
|
+
provisionTimeoutMs;
|
|
242
|
+
recoveryRetentionMs;
|
|
243
|
+
providerOptions;
|
|
244
|
+
capabilitiesPromise;
|
|
245
|
+
constructor(options) {
|
|
246
|
+
this.options = options;
|
|
247
|
+
if (!options.provider.exactProcess) throw new Error(`agent environment provider "${options.provider.name}" does not implement exact processes`);
|
|
248
|
+
this.exactProcess = options.provider.exactProcess;
|
|
249
|
+
this.resources = exactResources(options.resources);
|
|
250
|
+
this.provisionTimeoutMs = positiveInteger(options.provisionTimeoutMs ?? DEFAULT_PROVISION_TIMEOUT_MS, "exact process provision timeout");
|
|
251
|
+
this.recoveryRetentionMs = positiveInteger(options.recoveryRetentionMs ?? DEFAULT_RECOVERY_RETENTION_MS, "exact process recovery retention");
|
|
252
|
+
this.providerOptions = options.providerOptions ? immutableCandidateValue(options.providerOptions) : void 0;
|
|
253
|
+
}
|
|
254
|
+
async execute(request, context) {
|
|
255
|
+
assertSupportedRequest(request);
|
|
256
|
+
const outcome = request.benchmark.task.outcome;
|
|
257
|
+
if (outcome.kind !== "output") throw new Error("exact process executor requires an output task");
|
|
258
|
+
const network = request.executionPlan.value.material.model.access.network;
|
|
259
|
+
const egress = network.mode === "disabled" ? { mode: "blocked" } : {
|
|
260
|
+
mode: "strict",
|
|
261
|
+
allowDomains: [...network.domains]
|
|
262
|
+
};
|
|
263
|
+
await this.assertCapability(egress.mode);
|
|
264
|
+
context.signal.throwIfAborted();
|
|
265
|
+
const key = executionKey(request.executionId, request.executionPlan.value.digest);
|
|
266
|
+
if (this.states.has(key)) throw new Error("candidate execution environment is already active");
|
|
267
|
+
const image = exactImage(request.executionPlan.value.material.container.image, request.executionPlan.value.material.container.manifestDigest);
|
|
268
|
+
const maxLifetimeMs = environmentLifetimeMs(request.hardLimits.timeoutMs, this.recoveryRetentionMs);
|
|
269
|
+
const createInputDigest = canonicalCandidateDigest$1(immutableCandidateValue({
|
|
270
|
+
kind: "agent-candidate-exact-process-create",
|
|
271
|
+
provider: this.options.provider.name,
|
|
272
|
+
executionId: request.executionId,
|
|
273
|
+
executionPlanDigest: request.executionPlan.value.digest,
|
|
274
|
+
image,
|
|
275
|
+
egress,
|
|
276
|
+
maxLifetimeMs,
|
|
277
|
+
provisionTimeoutMs: this.provisionTimeoutMs,
|
|
278
|
+
resources: this.resources,
|
|
279
|
+
output: {
|
|
280
|
+
mediaType: outcome.mediaType,
|
|
281
|
+
maxBytes: outcome.maxBytes
|
|
282
|
+
},
|
|
283
|
+
...this.providerOptions ? { providerOptions: this.providerOptions } : {}
|
|
284
|
+
}));
|
|
285
|
+
const metadata = immutableCandidateValue({
|
|
286
|
+
kind: "agent-candidate-execution",
|
|
287
|
+
provider: this.options.provider.name,
|
|
288
|
+
executionId: request.executionId,
|
|
289
|
+
executionPlanDigest: request.executionPlan.value.digest,
|
|
290
|
+
createInputDigest,
|
|
291
|
+
outputMediaType: outcome.mediaType,
|
|
292
|
+
outputMaxBytes: outcome.maxBytes
|
|
293
|
+
});
|
|
294
|
+
const environment = await this.exactProcess.create({
|
|
295
|
+
image,
|
|
296
|
+
egress,
|
|
297
|
+
maxLifetimeMs,
|
|
298
|
+
provisionTimeoutMs: this.provisionTimeoutMs,
|
|
299
|
+
resources: this.resources,
|
|
300
|
+
metadata,
|
|
301
|
+
idempotencyKey: executionIdempotencyKey(request.executionId, request.executionPlan.value.digest),
|
|
302
|
+
signal: context.signal,
|
|
303
|
+
...this.providerOptions ? { providerOptions: this.providerOptions } : {}
|
|
304
|
+
});
|
|
305
|
+
try {
|
|
306
|
+
assertExecutionEnvironment(environment, metadata, this.options.provider.name);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
try {
|
|
309
|
+
await environment.destroy();
|
|
310
|
+
} catch (cleanupError) {
|
|
311
|
+
throw new AggregateError([error, cleanupError], "exact process validation and cleanup both failed");
|
|
312
|
+
}
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
const state = { environment };
|
|
316
|
+
this.states.set(key, state);
|
|
317
|
+
if ((await environment.process.list()).length !== 0) throw new Error("fresh candidate environment already contains a process");
|
|
318
|
+
await materializeRequest(environment, request, context.signal);
|
|
319
|
+
const launch = exactLaunch(request);
|
|
320
|
+
const startedAt = Date.now();
|
|
321
|
+
state.trace = {
|
|
322
|
+
runId: request.trace.runId,
|
|
323
|
+
scenarioId: request.benchmark.task.scenario.id,
|
|
324
|
+
startedAt,
|
|
325
|
+
deadlineAtMs: context.deadlineAtMs,
|
|
326
|
+
timeoutMs: request.hardLimits.timeoutMs,
|
|
327
|
+
tags: {
|
|
328
|
+
...request.trace.tags,
|
|
329
|
+
environmentId: environment.id,
|
|
330
|
+
environmentProvider: environment.provider
|
|
331
|
+
},
|
|
332
|
+
written: false
|
|
333
|
+
};
|
|
334
|
+
const process = await environment.process.spawn(launch, { signal: context.signal });
|
|
335
|
+
const outputPromise = collectOutput(process, outcome.maxBytes);
|
|
336
|
+
outputPromise.catch(() => void 0);
|
|
337
|
+
let cancellation;
|
|
338
|
+
let processExited = false;
|
|
339
|
+
const cancel = () => {
|
|
340
|
+
if (!processExited) cancellation ??= stopProcess(process);
|
|
341
|
+
};
|
|
342
|
+
context.signal.addEventListener("abort", cancel, { once: true });
|
|
343
|
+
if (context.signal.aborted) cancel();
|
|
344
|
+
try {
|
|
345
|
+
const termination = await process.wait();
|
|
346
|
+
processExited = true;
|
|
347
|
+
context.signal.removeEventListener("abort", cancel);
|
|
348
|
+
if (cancellation) await cancellation;
|
|
349
|
+
const status = await process.status();
|
|
350
|
+
assertTerminalStatus(status, termination);
|
|
351
|
+
state.output = await awaitOutput(outputPromise, context.signal, context.deadlineAtMs);
|
|
352
|
+
state.termination = termination;
|
|
353
|
+
await appendExactProcessTrace(state, context.traceStore, status);
|
|
354
|
+
return {
|
|
355
|
+
executionId: request.executionId,
|
|
356
|
+
termination
|
|
357
|
+
};
|
|
358
|
+
} finally {
|
|
359
|
+
context.signal.removeEventListener("abort", cancel);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async stop(request, context) {
|
|
363
|
+
await this.assertCapability();
|
|
364
|
+
context.signal.throwIfAborted();
|
|
365
|
+
const environment = await this.resolve(request, true);
|
|
366
|
+
if (!environment) return { stopped: true };
|
|
367
|
+
let statuses = await environment.process.list();
|
|
368
|
+
if (statuses.length > 1) throw new Error("candidate exact process environment contains more than one process");
|
|
369
|
+
const status = statuses[0];
|
|
370
|
+
if (status?.running) {
|
|
371
|
+
const process = await environment.process.get(status.pid);
|
|
372
|
+
if (!process) throw new Error("candidate environment lost its running process handle");
|
|
373
|
+
await stopProcess(process);
|
|
374
|
+
}
|
|
375
|
+
context.signal.throwIfAborted();
|
|
376
|
+
statuses = await environment.process.list();
|
|
377
|
+
if (statuses.some((entry) => entry.running)) throw new Error("candidate exact process termination is not proven");
|
|
378
|
+
const finalStatus = statuses[0];
|
|
379
|
+
if (finalStatus) assertTerminalStatus(finalStatus);
|
|
380
|
+
const state = this.states.get(executionKey(request.executionId, request.executionPlanDigest));
|
|
381
|
+
if (state) {
|
|
382
|
+
state.termination = finalStatus?.termination ?? state.termination;
|
|
383
|
+
if (context.reason === "timeout" && state.trace) state.runtimeTermination = {
|
|
384
|
+
kind: "timeout",
|
|
385
|
+
timeoutMs: state.trace.timeoutMs
|
|
386
|
+
};
|
|
387
|
+
await appendExactProcessTrace(state, context.traceStore, finalStatus);
|
|
388
|
+
}
|
|
389
|
+
return { stopped: true };
|
|
390
|
+
}
|
|
391
|
+
async capture(request, context) {
|
|
392
|
+
await this.assertCapability();
|
|
393
|
+
context.signal.throwIfAborted();
|
|
394
|
+
const environment = await this.resolve(request, false);
|
|
395
|
+
const statuses = await environment.process.list();
|
|
396
|
+
const status = statuses[0];
|
|
397
|
+
if (statuses.length !== 1 || !status || status.running) throw new Error("candidate environment must contain one stopped process before capture");
|
|
398
|
+
assertTerminalStatus(status);
|
|
399
|
+
const process = await environment.process.get(status.pid);
|
|
400
|
+
if (!process) throw new Error("candidate environment lost its captured process handle");
|
|
401
|
+
assertTerminalStatus(await process.status(), status.termination);
|
|
402
|
+
const state = this.states.get(executionKey(request.executionId, request.executionPlanDigest));
|
|
403
|
+
const output = state?.output ?? await awaitOutput(collectOutput(process, outputSpec(environment).maxBytes), context.signal, Date.now() + this.provisionTimeoutMs);
|
|
404
|
+
context.signal.throwIfAborted();
|
|
405
|
+
const evidence = canonicalCandidateBytes$1({
|
|
406
|
+
kind: "agent-candidate-exact-process-capture",
|
|
407
|
+
provider: environment.provider,
|
|
408
|
+
environmentId: environment.id,
|
|
409
|
+
executionId: request.executionId,
|
|
410
|
+
executionPlanDigest: request.executionPlanDigest,
|
|
411
|
+
createInputDigest: environment.metadata?.createInputDigest,
|
|
412
|
+
process: {
|
|
413
|
+
pid: status.pid,
|
|
414
|
+
exitCode: status.exitCode,
|
|
415
|
+
...status.exitSignal ? { exitSignal: status.exitSignal } : {},
|
|
416
|
+
termination: status.termination
|
|
417
|
+
},
|
|
418
|
+
...state?.runtimeTermination ? { runtimeTermination: state.runtimeTermination } : {},
|
|
419
|
+
output: {
|
|
420
|
+
sha256: sha256Bytes$1(output),
|
|
421
|
+
byteLength: output.byteLength
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
return {
|
|
425
|
+
taskOutcome: {
|
|
426
|
+
kind: "output",
|
|
427
|
+
bytes: output
|
|
428
|
+
},
|
|
429
|
+
evidence
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
async dispose(request, context) {
|
|
433
|
+
await this.assertCapability();
|
|
434
|
+
context.signal.throwIfAborted();
|
|
435
|
+
const environment = await this.resolve(request, true);
|
|
436
|
+
if (!environment) return { disposed: true };
|
|
437
|
+
let destroyError;
|
|
438
|
+
try {
|
|
439
|
+
await environment.destroy();
|
|
440
|
+
} catch (error) {
|
|
441
|
+
destroyError = error;
|
|
442
|
+
}
|
|
443
|
+
if (await this.exactProcess.get(environment.id)) throw new Error("exact process environment disposal is not proven", { cause: destroyError });
|
|
444
|
+
context.signal.throwIfAborted();
|
|
445
|
+
this.states.delete(executionKey(request.executionId, request.executionPlanDigest));
|
|
446
|
+
return { disposed: true };
|
|
447
|
+
}
|
|
448
|
+
async assertCapability(mode) {
|
|
449
|
+
const pending = this.capabilitiesPromise ?? Promise.resolve(this.options.provider.capabilities());
|
|
450
|
+
this.capabilitiesPromise = pending;
|
|
451
|
+
let capabilities;
|
|
452
|
+
try {
|
|
453
|
+
capabilities = await pending;
|
|
454
|
+
} catch (error) {
|
|
455
|
+
if (this.capabilitiesPromise === pending) this.capabilitiesPromise = void 0;
|
|
456
|
+
throw error;
|
|
457
|
+
}
|
|
458
|
+
const exact = capabilities.exactProcess;
|
|
459
|
+
if (!exact) throw new Error(`agent environment provider "${this.options.provider.name}" does not declare exact process support`);
|
|
460
|
+
if (mode && !exact.egress.includes(mode)) throw new Error(`agent environment provider "${this.options.provider.name}" does not declare ${mode} exact egress`);
|
|
461
|
+
}
|
|
462
|
+
async resolve(request, missingIsDeleted) {
|
|
463
|
+
const key = executionKey(request.executionId, request.executionPlanDigest);
|
|
464
|
+
const active = this.states.get(key)?.environment;
|
|
465
|
+
if (active) return active;
|
|
466
|
+
const metadata = {
|
|
467
|
+
kind: "agent-candidate-execution",
|
|
468
|
+
provider: this.options.provider.name,
|
|
469
|
+
executionId: request.executionId,
|
|
470
|
+
executionPlanDigest: request.executionPlanDigest
|
|
471
|
+
};
|
|
472
|
+
const matches = (await this.exactProcess.list({
|
|
473
|
+
metadata,
|
|
474
|
+
...this.providerOptions ? { providerOptions: this.providerOptions } : {}
|
|
475
|
+
})).filter((environment) => environment.provider === this.options.provider.name && Object.entries(metadata).every(([name, value]) => isDeepStrictEqual(environment.metadata?.[name], value)));
|
|
476
|
+
if (matches.length > 1) throw new Error("multiple exact process environments match one candidate execution");
|
|
477
|
+
const environment = matches[0];
|
|
478
|
+
if (!environment) {
|
|
479
|
+
if (missingIsDeleted) return void 0;
|
|
480
|
+
throw new Error("candidate exact process evidence is unavailable");
|
|
481
|
+
}
|
|
482
|
+
if (!isSha256Digest$1(environment.metadata?.createInputDigest)) throw new Error("candidate exact process create-input digest is unavailable");
|
|
483
|
+
this.states.set(key, { environment });
|
|
484
|
+
return environment;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
async function appendExactProcessTrace(state, traceStore, status) {
|
|
488
|
+
const trace = state.trace;
|
|
489
|
+
if (!trace || trace.written) return;
|
|
490
|
+
const endedAt = Math.max(trace.startedAt, Math.min(Date.now(), trace.deadlineAtMs));
|
|
491
|
+
const termination = state.runtimeTermination ?? state.termination ?? status?.termination;
|
|
492
|
+
await traceStore.appendRun({
|
|
493
|
+
runId: trace.runId,
|
|
494
|
+
scenarioId: trace.scenarioId,
|
|
495
|
+
startedAt: trace.startedAt,
|
|
496
|
+
endedAt,
|
|
497
|
+
status: termination?.kind === "exit" && termination.exitCode === 0 ? "completed" : "failed",
|
|
498
|
+
tags: trace.tags
|
|
499
|
+
});
|
|
500
|
+
trace.written = true;
|
|
501
|
+
}
|
|
502
|
+
function assertSupportedRequest(request) {
|
|
503
|
+
if (request.benchmark.task.outcome.kind !== "output") throw new Error("exact process candidate executor does not support workspace outcomes; use Pier");
|
|
504
|
+
if (request.executionPlan.value.material.codeKind !== "disabled" || request.inputs.candidate) throw new Error("exact process candidate executor does not support code workspaces; use Pier");
|
|
505
|
+
if (request.memory.mode !== "disabled") throw new Error("exact process candidate executor does not support isolated memory");
|
|
506
|
+
const mediaType = request.benchmark.task.outcome.mediaType.toLowerCase();
|
|
507
|
+
if (!(mediaType.startsWith("text/") || mediaType === "application/json" || mediaType.endsWith("+json"))) throw new Error("exact process candidate executor supports only UTF-8 text and JSON outputs");
|
|
508
|
+
if (!posix.isAbsolute(request.launch.executable) && !request.launch.env.PATH?.trim()) throw new Error("exact process candidate executable must be absolute or declare a signed PATH");
|
|
509
|
+
if (!request.roots) throw new Error("candidate execution roots are missing");
|
|
510
|
+
assertAgentCandidateExecutionRoots(request.roots);
|
|
511
|
+
}
|
|
512
|
+
async function materializeRequest(environment, request, signal) {
|
|
513
|
+
assertAgentCandidateExecutionRoots(request.roots);
|
|
514
|
+
const knowledgePaths = assertKnowledgeExecutionBinding(request);
|
|
515
|
+
for (const file of request.inputs.task.files) await environment.writeFile(beneath(request.roots.taskRoot, file.path), file.bytes, {
|
|
516
|
+
mode: file.mode,
|
|
517
|
+
signal
|
|
518
|
+
});
|
|
519
|
+
for (const file of request.inputs.profile.files) await environment.writeFile(profileFileExecutionPath(request, file), file.bytes, {
|
|
520
|
+
mode: file.mode,
|
|
521
|
+
signal
|
|
522
|
+
});
|
|
523
|
+
if (request.knowledge && knowledgePaths) {
|
|
524
|
+
for (const file of request.knowledge.files) await environment.writeFile(beneath(knowledgePaths.root, file.path), file.bytes, {
|
|
525
|
+
mode: file.mode,
|
|
526
|
+
signal
|
|
527
|
+
});
|
|
528
|
+
if (request.knowledge.retrievalConfig && knowledgePaths.retrievalConfig) await environment.writeFile(knowledgePaths.retrievalConfig, request.knowledge.retrievalConfig, {
|
|
529
|
+
mode: 420,
|
|
530
|
+
signal
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
if (request.instruction.delivery.kind === "utf8-file") await environment.writeFile(request.instruction.delivery.path, request.instruction.bytes, {
|
|
534
|
+
mode: 420,
|
|
535
|
+
signal
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
function assertKnowledgeExecutionBinding(request) {
|
|
539
|
+
const knowledge = request.knowledge;
|
|
540
|
+
if (!knowledge) {
|
|
541
|
+
if (request.launch.env["TANGLE_CANDIDATE_KNOWLEDGE_ROOT"] !== void 0 || request.launch.env["TANGLE_CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG"] !== void 0) throw new Error("candidate launch declares knowledge paths without verified knowledge");
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
const paths = candidateKnowledgeExecutionPaths(request.roots.taskRoot, knowledge.retrievalConfig !== void 0);
|
|
545
|
+
if (request.launch.env["TANGLE_CANDIDATE_KNOWLEDGE_ROOT"] !== paths.root || request.launch.env["TANGLE_CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG"] !== paths.retrievalConfig) throw new Error("candidate knowledge paths do not match the signed launch environment");
|
|
546
|
+
const reserved = [paths.root, paths.retrievalConfig].filter((value) => value !== void 0);
|
|
547
|
+
if ([
|
|
548
|
+
...request.inputs.task.files.map((file) => beneath(request.roots.taskRoot, file.path)),
|
|
549
|
+
...request.inputs.profile.files.map((file) => profileFileExecutionPath(request, file)),
|
|
550
|
+
...request.instruction.delivery.kind === "utf8-file" ? [request.instruction.delivery.path] : []
|
|
551
|
+
].some((path) => reserved.some((reservedPath) => path === reservedPath || path.startsWith(`${reservedPath}/`) || reservedPath.startsWith(`${path}/`)))) throw new Error("candidate knowledge paths overlap other execution inputs");
|
|
552
|
+
return paths;
|
|
553
|
+
}
|
|
554
|
+
function profileFileExecutionPath(request, file) {
|
|
555
|
+
const workspaceRoot = request.executionPlan.value.material.profile.targetWorkspace === "task" ? request.roots.taskRoot : request.roots.candidateRoot;
|
|
556
|
+
if (!workspaceRoot) throw new Error("candidate profile targets a missing workspace");
|
|
557
|
+
if (file.root !== "agent") return beneath(workspaceRoot, file.path);
|
|
558
|
+
return beneath(profileAgentDirectory(request), file.path);
|
|
559
|
+
}
|
|
560
|
+
function profileAgentDirectory(request) {
|
|
561
|
+
const paths = candidateProfileAgentPaths(request.profilePlan.value.material, request.roots.profileRoot);
|
|
562
|
+
if (!paths) throw new Error("candidate profile agent-root file is missing its private root");
|
|
563
|
+
if (request.launch.env["PI_CODING_AGENT_DIR"] !== paths.agentDir || request.launch.env["PI_CODING_AGENT_SESSION_DIR"] !== paths.sessionDir) throw new Error("candidate Pi agent-root files do not match the signed launch environment");
|
|
564
|
+
return paths.agentDir;
|
|
565
|
+
}
|
|
566
|
+
function exactLaunch(request) {
|
|
567
|
+
const instruction = new TextDecoder("utf-8", { fatal: true }).decode(request.instruction.bytes);
|
|
568
|
+
const base = {
|
|
569
|
+
executable: request.launch.executable,
|
|
570
|
+
args: request.launch.args,
|
|
571
|
+
cwd: request.launch.cwd,
|
|
572
|
+
env: request.launch.env,
|
|
573
|
+
timeoutMs: 0
|
|
574
|
+
};
|
|
575
|
+
switch (request.instruction.delivery.kind) {
|
|
576
|
+
case "argv-append": return {
|
|
577
|
+
...base,
|
|
578
|
+
args: [...base.args, instruction]
|
|
579
|
+
};
|
|
580
|
+
case "stdin-utf8": return {
|
|
581
|
+
...base,
|
|
582
|
+
stdin: instruction
|
|
583
|
+
};
|
|
584
|
+
case "utf8-file": return base;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
async function collectOutput(process, maxBytes) {
|
|
588
|
+
const chunks = [];
|
|
589
|
+
let byteLength = 0;
|
|
590
|
+
for await (const chunk of process.stdout()) {
|
|
591
|
+
const bytes = Buffer.from(chunk, "utf8");
|
|
592
|
+
byteLength += bytes.byteLength;
|
|
593
|
+
if (byteLength > maxBytes) {
|
|
594
|
+
let stopError;
|
|
595
|
+
try {
|
|
596
|
+
await stopProcess(process);
|
|
597
|
+
} catch (error) {
|
|
598
|
+
stopError = error;
|
|
599
|
+
}
|
|
600
|
+
throw new Error(`candidate output exceeds its ${maxBytes}-byte maximum`, { cause: stopError });
|
|
601
|
+
}
|
|
602
|
+
chunks.push(bytes);
|
|
603
|
+
}
|
|
604
|
+
return Uint8Array.from(Buffer.concat(chunks, byteLength));
|
|
605
|
+
}
|
|
606
|
+
async function awaitOutput(output, signal, deadlineAtMs) {
|
|
607
|
+
signal.throwIfAborted();
|
|
608
|
+
const remainingMs = deadlineAtMs - Date.now();
|
|
609
|
+
if (remainingMs <= 0) throw new Error("candidate output deadline expired");
|
|
610
|
+
let timer;
|
|
611
|
+
let onAbort;
|
|
612
|
+
try {
|
|
613
|
+
return await Promise.race([output, new Promise((_resolve, reject) => {
|
|
614
|
+
onAbort = () => reject(signal.reason ?? /* @__PURE__ */ new Error("candidate output cancelled"));
|
|
615
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
616
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error("candidate output deadline expired")), remainingMs);
|
|
617
|
+
})]);
|
|
618
|
+
} finally {
|
|
619
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
620
|
+
if (timer) clearTimeout(timer);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
async function stopProcess(process) {
|
|
624
|
+
if (!(await process.status()).running) return;
|
|
625
|
+
try {
|
|
626
|
+
await process.kill();
|
|
627
|
+
} catch (error) {
|
|
628
|
+
if ((await process.status()).running) throw error;
|
|
629
|
+
}
|
|
630
|
+
const termination = await process.wait();
|
|
631
|
+
assertTerminalStatus(await process.status(), termination);
|
|
632
|
+
}
|
|
633
|
+
function assertTerminalStatus(status, expected) {
|
|
634
|
+
if (status.running || !status.termination) throw new Error("exact process returned no terminal reason after exit");
|
|
635
|
+
if (expected && !isDeepStrictEqual(status.termination, expected)) throw new Error("exact process wait and status returned different terminal reasons");
|
|
636
|
+
}
|
|
637
|
+
function assertExecutionEnvironment(environment, metadata, providerName) {
|
|
638
|
+
if (!environment.id || environment.provider !== providerName) throw new Error("exact process provider returned the wrong environment identity");
|
|
639
|
+
if (!Object.entries(metadata).every(([name, value]) => isDeepStrictEqual(environment.metadata?.[name], value))) throw new Error("exact process provider did not persist candidate execution metadata");
|
|
640
|
+
}
|
|
641
|
+
function outputSpec(environment) {
|
|
642
|
+
const maxBytes = environment.metadata?.outputMaxBytes;
|
|
643
|
+
if (!Number.isSafeInteger(maxBytes) || Number(maxBytes) < 1) throw new Error("candidate exact process output bound is unavailable");
|
|
644
|
+
return { maxBytes: Number(maxBytes) };
|
|
645
|
+
}
|
|
646
|
+
function executionKey(executionId, executionPlanDigest) {
|
|
647
|
+
return `${executionId}\0${executionPlanDigest}`;
|
|
648
|
+
}
|
|
649
|
+
function executionIdempotencyKey(executionId, executionPlanDigest) {
|
|
650
|
+
return `candidate-${canonicalCandidateDigest$1({
|
|
651
|
+
kind: "agent-candidate-execution-identity",
|
|
652
|
+
executionId,
|
|
653
|
+
executionPlanDigest
|
|
654
|
+
}).slice(7)}`;
|
|
655
|
+
}
|
|
656
|
+
function exactImage(image, manifestDigest) {
|
|
657
|
+
if (isSha256Digest$1(image)) {
|
|
658
|
+
if (image !== manifestDigest) throw new Error("candidate container image conflicts with its resolved manifest");
|
|
659
|
+
return image;
|
|
660
|
+
}
|
|
661
|
+
const marker = image.lastIndexOf("@");
|
|
662
|
+
if (marker < 0) return `${image}@${manifestDigest}`;
|
|
663
|
+
if (image.slice(marker + 1) !== manifestDigest) throw new Error("candidate container image conflicts with its resolved manifest");
|
|
664
|
+
return image;
|
|
665
|
+
}
|
|
666
|
+
function environmentLifetimeMs(timeoutMs, retentionMs) {
|
|
667
|
+
const total = positiveInteger(timeoutMs, "candidate execution timeout") + retentionMs;
|
|
668
|
+
if (!Number.isSafeInteger(total)) throw new Error("candidate environment lifetime is too large");
|
|
669
|
+
return Math.ceil(total / 1e3) * 1e3;
|
|
670
|
+
}
|
|
671
|
+
function exactResources(resources) {
|
|
672
|
+
if (!Number.isFinite(resources.cpu) || resources.cpu <= 0) throw new Error("exact process CPU must be positive and finite");
|
|
673
|
+
return Object.freeze({
|
|
674
|
+
cpu: resources.cpu,
|
|
675
|
+
memoryMb: positiveInteger(resources.memoryMb, "exact process memory"),
|
|
676
|
+
diskMb: positiveInteger(resources.diskMb, "exact process disk")
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
function positiveInteger(value, label) {
|
|
680
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new Error(`${label} must be a positive integer`);
|
|
681
|
+
return value;
|
|
682
|
+
}
|
|
683
|
+
function beneath(root, relativePath) {
|
|
684
|
+
if (posix.isAbsolute(relativePath)) throw new Error("candidate input path must be relative");
|
|
685
|
+
const path = posix.normalize(relativePath);
|
|
686
|
+
if (path === ".." || path.startsWith("../")) throw new Error("candidate input path escapes its execution root");
|
|
687
|
+
return posix.join(root, path);
|
|
688
|
+
}
|
|
689
|
+
function isSha256Digest$1(value) {
|
|
690
|
+
return typeof value === "string" && /^sha256:[a-f0-9]{64}$/.test(value);
|
|
691
|
+
}
|
|
692
|
+
//#endregion
|
|
693
|
+
//#region src/candidate-execution/model-settlement.ts
|
|
694
|
+
const USD_NANOS = 1e9;
|
|
695
|
+
/** Validate and detach the evaluator gateway's terminal, revoked call ledger. */
|
|
696
|
+
function sealAgentCandidateModelSettlement(settlement, expected) {
|
|
697
|
+
assertExactObjectKeys(settlement, [
|
|
698
|
+
"preparationId",
|
|
699
|
+
"grantDigest",
|
|
700
|
+
"closed",
|
|
701
|
+
"usageWithinLimits",
|
|
702
|
+
"calls"
|
|
703
|
+
], "model settlement");
|
|
704
|
+
if (settlement.closed !== true) throw new Error("protected model grant is not closed");
|
|
705
|
+
if (settlement.usageWithinLimits !== true) throw new Error("protected model settlement reports usage outside frozen limits");
|
|
706
|
+
if (settlement.grantDigest !== expected.grantDigest) throw new Error("protected model settlement grant digest does not match the reservation");
|
|
707
|
+
if (settlement.preparationId !== expected.preparationId) throw new Error("protected model settlement preparation does not match the reservation");
|
|
708
|
+
if (!Array.isArray(settlement.calls)) throw new Error("protected model settlement calls must be an array");
|
|
709
|
+
const callIds = /* @__PURE__ */ new Set();
|
|
710
|
+
const spanIds = /* @__PURE__ */ new Set();
|
|
711
|
+
let accountedInputTokens = 0;
|
|
712
|
+
let outputTokens = 0;
|
|
713
|
+
let cachedInputTokens = 0;
|
|
714
|
+
let reasoningTokens = 0;
|
|
715
|
+
let costUsdNanos = 0;
|
|
716
|
+
let costProvenance = "observed";
|
|
717
|
+
const calls = settlement.calls.map((source, index) => {
|
|
718
|
+
assertExactObjectKeys(source, [
|
|
719
|
+
"callId",
|
|
720
|
+
"generationId",
|
|
721
|
+
"traceSpanId",
|
|
722
|
+
"status",
|
|
723
|
+
"model",
|
|
724
|
+
"startedAtMs",
|
|
725
|
+
"endedAtMs",
|
|
726
|
+
"inputTokens",
|
|
727
|
+
"accountedInputTokens",
|
|
728
|
+
"outputTokens",
|
|
729
|
+
"cachedInputTokens",
|
|
730
|
+
"reasoningTokens",
|
|
731
|
+
"costUsdNanos",
|
|
732
|
+
"costProvenance"
|
|
733
|
+
], `model settlement call ${index}`);
|
|
734
|
+
assertIdentifier(source.callId, `model settlement call ${index} callId`);
|
|
735
|
+
assertIdentifier(source.generationId, `model settlement call ${index} generationId`);
|
|
736
|
+
assertIdentifier(source.traceSpanId, `model settlement call ${index} traceSpanId`);
|
|
737
|
+
if (source.traceSpanId !== source.generationId) throw new Error(`model settlement call ${index} traceSpanId is not its router generationId`);
|
|
738
|
+
if (source.status !== "succeeded" && source.status !== "failed") throw new Error(`model settlement call ${index} has an invalid status`);
|
|
739
|
+
if (callIds.has(source.callId)) throw new Error("protected model settlement has duplicate call ids");
|
|
740
|
+
if (spanIds.has(source.traceSpanId)) throw new Error("protected model settlement has duplicate trace span ids");
|
|
741
|
+
callIds.add(source.callId);
|
|
742
|
+
spanIds.add(source.traceSpanId);
|
|
743
|
+
if (source.model !== expected.model) throw new Error(`protected model settlement call ${index} has an unexpected model`);
|
|
744
|
+
assertTimestamp(source.startedAtMs, `model settlement call ${index} startedAtMs`);
|
|
745
|
+
assertTimestamp(source.endedAtMs, `model settlement call ${index} endedAtMs`);
|
|
746
|
+
if (source.endedAtMs < source.startedAtMs) throw new Error(`model settlement call ${index} ended before it started`);
|
|
747
|
+
assertCount$1(source.inputTokens, `model settlement call ${index} inputTokens`);
|
|
748
|
+
assertCount$1(source.accountedInputTokens, `model settlement call ${index} accountedInputTokens`);
|
|
749
|
+
if (source.accountedInputTokens < source.inputTokens) throw new Error(`model settlement call ${index} accountedInputTokens cannot be less than inputTokens`);
|
|
750
|
+
assertCount$1(source.outputTokens, `model settlement call ${index} outputTokens`);
|
|
751
|
+
assertCount$1(source.cachedInputTokens, `model settlement call ${index} cachedInputTokens`);
|
|
752
|
+
cachedInputTokens = safeAdd(cachedInputTokens, source.cachedInputTokens, "cached input token total");
|
|
753
|
+
assertCount$1(source.reasoningTokens, `model settlement call ${index} reasoningTokens`);
|
|
754
|
+
reasoningTokens = safeAdd(reasoningTokens, source.reasoningTokens, "reasoning token total");
|
|
755
|
+
assertCount$1(source.costUsdNanos, `model settlement call ${index} costUsdNanos`);
|
|
756
|
+
if (source.costProvenance !== "observed" && source.costProvenance !== "estimated") throw new Error(`model settlement call ${index} has an invalid cost provenance`);
|
|
757
|
+
if (source.costProvenance === "estimated") costProvenance = "estimated";
|
|
758
|
+
accountedInputTokens = safeAdd(accountedInputTokens, source.accountedInputTokens, "accounted input token total");
|
|
759
|
+
outputTokens = safeAdd(outputTokens, source.outputTokens, "output token total");
|
|
760
|
+
costUsdNanos = safeAdd(costUsdNanos, source.costUsdNanos, "cost total");
|
|
761
|
+
return Object.freeze({ ...source });
|
|
762
|
+
});
|
|
763
|
+
const usage = Object.freeze({
|
|
764
|
+
costUsdNanos,
|
|
765
|
+
inputTokens: accountedInputTokens,
|
|
766
|
+
outputTokens,
|
|
767
|
+
cachedInputTokens,
|
|
768
|
+
reasoningTokens,
|
|
769
|
+
modelCalls: calls.length,
|
|
770
|
+
costProvenance
|
|
771
|
+
});
|
|
772
|
+
return Object.freeze({
|
|
773
|
+
value: Object.freeze({
|
|
774
|
+
preparationId: settlement.preparationId,
|
|
775
|
+
grantDigest: settlement.grantDigest,
|
|
776
|
+
closed: true,
|
|
777
|
+
usageWithinLimits: true,
|
|
778
|
+
calls: Object.freeze(calls)
|
|
779
|
+
}),
|
|
780
|
+
usage
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Append the only accepted LLM spans from the router's closed ledger.
|
|
785
|
+
* Candidate and executor code may write tool/process spans, but never model usage.
|
|
786
|
+
*/
|
|
787
|
+
async function appendAuthoritativeModelSettlementSpans(traceStore, runId, settlement) {
|
|
788
|
+
const run = await traceStore.getRun(runId);
|
|
789
|
+
if (!run) throw new Error(`protected trace run is missing before model settlement: ${runId}`);
|
|
790
|
+
if (run.status === "running" || run.endedAt === void 0) throw new Error("protected trace run must be terminal before model spans are appended");
|
|
791
|
+
const existing = await traceStore.spans({ runId });
|
|
792
|
+
if (existing.some(isLlmSpan)) throw new Error("protected trace contains a model span not authored from the closed router ledger");
|
|
793
|
+
const occupiedIds = new Set(existing.map((span) => span.spanId));
|
|
794
|
+
for (const call of settlement.value.calls) {
|
|
795
|
+
if (occupiedIds.has(call.traceSpanId)) throw new Error(`protected trace span identity collides with router generation ${call.generationId}`);
|
|
796
|
+
await traceStore.appendSpan({
|
|
797
|
+
runId,
|
|
798
|
+
spanId: call.traceSpanId,
|
|
799
|
+
kind: "llm",
|
|
800
|
+
name: "protected model call",
|
|
801
|
+
model: call.model,
|
|
802
|
+
messages: [],
|
|
803
|
+
startedAt: call.startedAtMs,
|
|
804
|
+
endedAt: call.endedAtMs,
|
|
805
|
+
status: call.status === "succeeded" ? "ok" : "error",
|
|
806
|
+
inputTokens: call.inputTokens,
|
|
807
|
+
outputTokens: call.outputTokens,
|
|
808
|
+
cachedTokens: call.cachedInputTokens,
|
|
809
|
+
reasoningTokens: call.reasoningTokens,
|
|
810
|
+
costUsd: call.costUsdNanos / USD_NANOS,
|
|
811
|
+
attributes: {
|
|
812
|
+
"tangle.protected_model.source": "router-settlement",
|
|
813
|
+
"tangle.router.call_id": call.callId,
|
|
814
|
+
"tangle.router.generation_id": call.generationId
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
/** Match every protected trace span one-for-one against gateway call evidence. */
|
|
820
|
+
function assertTraceMatchesModelSettlement(spans, settlement) {
|
|
821
|
+
if (spans.length !== settlement.value.calls.length) throw new Error(`protected trace model calls ${spans.length} do not match model ledger ${settlement.value.calls.length}`);
|
|
822
|
+
const byId = new Map(spans.map((span) => [span.spanId, span]));
|
|
823
|
+
if (byId.size !== spans.length) throw new Error("protected trace has duplicate model span ids");
|
|
824
|
+
for (const call of settlement.value.calls) {
|
|
825
|
+
const span = byId.get(call.traceSpanId);
|
|
826
|
+
if (!span) throw new Error(`protected trace is missing model ledger span ${call.traceSpanId}`);
|
|
827
|
+
assertTraceCall(span, call);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
function usdToNanos(value, label) {
|
|
831
|
+
if (!Number.isFinite(value) || value < 0) throw new Error(`${label} must be nonnegative`);
|
|
832
|
+
const nanos = Math.round(value * USD_NANOS);
|
|
833
|
+
if (!Number.isSafeInteger(nanos)) throw new Error(`${label} exceeds fixed-point range`);
|
|
834
|
+
return nanos;
|
|
835
|
+
}
|
|
836
|
+
function assertTraceCall(span, call) {
|
|
837
|
+
if (span.model !== call.model) throw new Error(`protected trace span ${span.spanId} model does not match model ledger`);
|
|
838
|
+
if (span.startedAt !== call.startedAtMs || span.endedAt !== call.endedAtMs || span.status !== (call.status === "succeeded" ? "ok" : "error")) throw new Error(`protected trace span ${span.spanId} timing or status does not match model ledger`);
|
|
839
|
+
if (span.attributes?.["tangle.protected_model.source"] !== "router-settlement" || span.attributes?.["tangle.router.call_id"] !== call.callId || span.attributes?.["tangle.router.generation_id"] !== call.generationId) throw new Error(`protected trace span ${span.spanId} lacks router settlement provenance`);
|
|
840
|
+
for (const [name, traced, settled] of [
|
|
841
|
+
[
|
|
842
|
+
"inputTokens",
|
|
843
|
+
span.inputTokens,
|
|
844
|
+
call.inputTokens
|
|
845
|
+
],
|
|
846
|
+
[
|
|
847
|
+
"outputTokens",
|
|
848
|
+
span.outputTokens,
|
|
849
|
+
call.outputTokens
|
|
850
|
+
],
|
|
851
|
+
[
|
|
852
|
+
"cachedInputTokens",
|
|
853
|
+
span.cachedTokens ?? 0,
|
|
854
|
+
call.cachedInputTokens
|
|
855
|
+
],
|
|
856
|
+
[
|
|
857
|
+
"reasoningTokens",
|
|
858
|
+
span.reasoningTokens ?? 0,
|
|
859
|
+
call.reasoningTokens
|
|
860
|
+
]
|
|
861
|
+
]) if (traced === void 0 || traced !== settled) throw new Error(`protected trace span ${span.spanId} ${name} ${traced} does not match model ledger ${settled}`);
|
|
862
|
+
if (span.costUsd === void 0) throw new Error(`protected trace span ${span.spanId} is missing costUsd`);
|
|
863
|
+
const tracedCost = usdToNanos(span.costUsd, `protected trace span ${span.spanId} costUsd`);
|
|
864
|
+
if (tracedCost !== call.costUsdNanos) throw new Error(`protected trace span ${span.spanId} costUsdNanos ${tracedCost} does not match model ledger ${call.costUsdNanos}`);
|
|
865
|
+
}
|
|
866
|
+
function assertIdentifier(value, label) {
|
|
867
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 256) throw new Error(`${label} must be a non-empty bounded string`);
|
|
868
|
+
}
|
|
869
|
+
function assertCount$1(value, label) {
|
|
870
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${label} must be a nonnegative safe integer`);
|
|
871
|
+
}
|
|
872
|
+
function assertTimestamp(value, label) {
|
|
873
|
+
if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${label} must be a positive safe integer`);
|
|
874
|
+
}
|
|
875
|
+
function safeAdd(left, right, label) {
|
|
876
|
+
const total = left + right;
|
|
877
|
+
if (!Number.isSafeInteger(total)) throw new Error(`${label} exceeds safe integer range`);
|
|
878
|
+
return total;
|
|
879
|
+
}
|
|
880
|
+
//#endregion
|
|
881
|
+
//#region src/candidate-execution/protected-model-port.ts
|
|
882
|
+
const MAX_RECENT_SETTLEMENTS = 4096;
|
|
883
|
+
const RECOVERED_SETTLEMENT_RETENTION_MS = 900 * 1e3;
|
|
884
|
+
/**
|
|
885
|
+
* Bind a protected model-grant service to the immutable candidate runtime.
|
|
886
|
+
*
|
|
887
|
+
* The service remains the authority for expiry, admission, revocation, and
|
|
888
|
+
* metering. This adapter independently checks every response before allowing
|
|
889
|
+
* it to cross into candidate execution or durable receipt finalization.
|
|
890
|
+
*/
|
|
891
|
+
function createProtectedAgentCandidateModelPort(options) {
|
|
892
|
+
const gatewayDomain = assertGatewayDomain(options.gatewayDomain);
|
|
893
|
+
const activationEnvNames = exactEnvironmentNames(options.activationEnvNames);
|
|
894
|
+
const reservations = /* @__PURE__ */ new Map();
|
|
895
|
+
const expiredReservations = /* @__PURE__ */ new Map();
|
|
896
|
+
const recentSettlements = /* @__PURE__ */ new Map();
|
|
897
|
+
return {
|
|
898
|
+
resolve: async (input) => {
|
|
899
|
+
const request = immutableCandidateValue(input);
|
|
900
|
+
return immutableCandidateValue(validateResolvedModel(await options.resolveModel(request), request));
|
|
901
|
+
},
|
|
902
|
+
reserveGrant: async (input) => {
|
|
903
|
+
pruneExpiredState(reservations, expiredReservations, recentSettlements, Date.now());
|
|
904
|
+
const request = immutableCandidateValue(input);
|
|
905
|
+
validateReserveInput(request);
|
|
906
|
+
const requestDigest = canonicalCandidateDigest$1(request);
|
|
907
|
+
const key = reservationKey(request.executionId, request.preparationId);
|
|
908
|
+
if (recentSettlements.has(key)) throw new Error("protected model reservation is already settled");
|
|
909
|
+
if (expiredReservations.get(key)) throw new Error("protected model reservation has expired");
|
|
910
|
+
const previous = reservations.get(key);
|
|
911
|
+
if (previous && previous.requestDigest !== requestDigest) throw new Error("protected model reservation retry changed immutable input");
|
|
912
|
+
const reservation = validateReservation(await options.client.reserve(request), request, gatewayDomain);
|
|
913
|
+
const responseDigest = canonicalCandidateDigest$1(reservation);
|
|
914
|
+
if (recentSettlements.has(key)) throw new Error("protected model reservation completed after the grant was settled");
|
|
915
|
+
const recorded = previous ?? reservations.get(key);
|
|
916
|
+
if (recorded && recorded.requestDigest !== requestDigest) throw new Error("protected model reservation retry changed immutable input");
|
|
917
|
+
if (recorded && recorded.responseDigest !== responseDigest) throw new Error("protected model reservation retry returned different evidence");
|
|
918
|
+
if (!recorded) reservations.set(key, {
|
|
919
|
+
requestDigest,
|
|
920
|
+
responseDigest,
|
|
921
|
+
executionId: request.executionId,
|
|
922
|
+
preparationId: request.preparationId,
|
|
923
|
+
grantDigest: reservation.digest,
|
|
924
|
+
expiresAtMs: request.expiresAtMs,
|
|
925
|
+
resolved: immutableCandidateValue(request.resolved),
|
|
926
|
+
limits: immutableCandidateValue(request.limits),
|
|
927
|
+
maxCostUsdNanos: usdToNanos(request.limits.maxCostUsd, "reserved maxCostUsd"),
|
|
928
|
+
activation: "reserved"
|
|
929
|
+
});
|
|
930
|
+
return reservation;
|
|
931
|
+
},
|
|
932
|
+
activateGrant: async (input) => {
|
|
933
|
+
pruneExpiredState(reservations, expiredReservations, recentSettlements, Date.now());
|
|
934
|
+
const request = immutableCandidateValue(input);
|
|
935
|
+
const key = reservationKey(request.executionId, request.preparationId);
|
|
936
|
+
if (recentSettlements.has(key)) throw new Error("protected model grant is already settled");
|
|
937
|
+
const state = reservations.get(key);
|
|
938
|
+
if (!state && expiredReservations.has(key)) throw new Error("protected model grant reservation has expired");
|
|
939
|
+
if (!state) throw new Error("protected model grant was not reserved by this port");
|
|
940
|
+
assertGrantIdentity(state, request);
|
|
941
|
+
if (state.settlementDigest) throw new Error("protected model grant is already settled");
|
|
942
|
+
if (state.activation !== "reserved") throw new Error("protected model grant activation is single-use");
|
|
943
|
+
if (!Number.isSafeInteger(request.deadlineAtMs) || request.deadlineAtMs <= 0) throw new Error("protected model activation deadline must be a positive safe integer");
|
|
944
|
+
if (request.deadlineAtMs <= Date.now()) throw new Error("protected model activation deadline must be in the future");
|
|
945
|
+
if (request.deadlineAtMs > state.expiresAtMs) throw new Error("protected model activation deadline exceeds the reserved expiry");
|
|
946
|
+
state.activation = "activating";
|
|
947
|
+
try {
|
|
948
|
+
const activation = validateActivation(await options.client.activate(request), state.limits.maxModelCalls === 0 ? [] : activationEnvNames);
|
|
949
|
+
if (recentSettlements.has(key) || reservations.get(key) !== state) throw new Error("protected model grant settled while activation was in flight");
|
|
950
|
+
state.activation = "activated";
|
|
951
|
+
return activation;
|
|
952
|
+
} catch (error) {
|
|
953
|
+
state.activation = "reserved";
|
|
954
|
+
throw error;
|
|
955
|
+
}
|
|
956
|
+
},
|
|
957
|
+
settleGrant: async (input) => {
|
|
958
|
+
const now = Date.now();
|
|
959
|
+
pruneExpiredState(reservations, expiredReservations, recentSettlements, now);
|
|
960
|
+
const request = immutableCandidateValue(input);
|
|
961
|
+
const key = reservationKey(request.executionId, request.preparationId);
|
|
962
|
+
const state = reservations.get(key) ?? expiredReservations.get(key)?.state;
|
|
963
|
+
if (state) assertGrantIdentity(state, request);
|
|
964
|
+
const remembered = state ?? recentSettlements.get(key);
|
|
965
|
+
if (remembered?.settlementReason && remembered.settlementReason !== request.reason) throw new Error("protected model settlement retry changed the termination reason");
|
|
966
|
+
const sealed = sealAgentCandidateModelSettlement(await options.client.settle(request), {
|
|
967
|
+
preparationId: request.preparationId,
|
|
968
|
+
grantDigest: request.grantDigest,
|
|
969
|
+
model: request.resolved.model
|
|
970
|
+
});
|
|
971
|
+
if (state) assertWithinReservedLimits(sealed.usage, state);
|
|
972
|
+
const settlementDigest = canonicalCandidateDigest$1(sealed.value);
|
|
973
|
+
if (remembered?.settlementDigest && remembered.settlementDigest !== settlementDigest) throw new Error("protected model settlement retry returned a different final ledger");
|
|
974
|
+
if (remembered?.settlementReason && remembered.settlementReason !== request.reason) throw new Error("protected model settlement retry changed the termination reason");
|
|
975
|
+
const expiresAtMs = state?.expiresAtMs ?? now + RECOVERED_SETTLEMENT_RETENTION_MS;
|
|
976
|
+
if (state) {
|
|
977
|
+
state.settlementDigest = settlementDigest;
|
|
978
|
+
state.settlementReason = request.reason;
|
|
979
|
+
}
|
|
980
|
+
reservations.delete(key);
|
|
981
|
+
expiredReservations.delete(key);
|
|
982
|
+
rememberSettlement(recentSettlements, key, {
|
|
983
|
+
settlementDigest,
|
|
984
|
+
settlementReason: request.reason,
|
|
985
|
+
expiresAtMs
|
|
986
|
+
});
|
|
987
|
+
return sealed.value;
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
function validateReserveInput(input) {
|
|
992
|
+
if (!Number.isSafeInteger(input.expiresAtMs) || input.expiresAtMs <= 0) throw new Error("protected model reservation expiry must be a positive safe integer");
|
|
993
|
+
if (input.expiresAtMs <= Date.now()) throw new Error("protected model reservation expiry must be in the future");
|
|
994
|
+
for (const [name, value] of [
|
|
995
|
+
["maxModelCalls", input.limits.maxModelCalls],
|
|
996
|
+
["maxInputTokens", input.limits.maxInputTokens],
|
|
997
|
+
["maxOutputTokens", input.limits.maxOutputTokens],
|
|
998
|
+
["maxTotalTokens", input.limits.maxTotalTokens]
|
|
999
|
+
]) {
|
|
1000
|
+
if (value === void 0) continue;
|
|
1001
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`protected model reservation ${name} must be a nonnegative safe integer`);
|
|
1002
|
+
}
|
|
1003
|
+
usdToNanos(input.limits.maxCostUsd, "reserved maxCostUsd");
|
|
1004
|
+
}
|
|
1005
|
+
function validateReservation(value, expected, gatewayDomain) {
|
|
1006
|
+
const source = exactRecord(value, [
|
|
1007
|
+
"preparationId",
|
|
1008
|
+
"digest",
|
|
1009
|
+
"expiresAtMs",
|
|
1010
|
+
"enforcedLimits",
|
|
1011
|
+
"network"
|
|
1012
|
+
], "protected model reservation response");
|
|
1013
|
+
if (source.preparationId !== expected.preparationId) throw new Error("protected model reservation response changed preparation identity");
|
|
1014
|
+
if (!isSha256Digest(source.digest)) throw new Error("protected model reservation response has an invalid digest");
|
|
1015
|
+
if (source.expiresAtMs !== expected.expiresAtMs) throw new Error("protected model reservation response changed expiry");
|
|
1016
|
+
exactRecord(source.enforcedLimits, modelLimitKeys(expected.limits), "protected model reservation enforced limits");
|
|
1017
|
+
if (canonicalCandidateDigest$1(source.enforcedLimits) !== canonicalCandidateDigest$1(expected.limits)) throw new Error("protected model reservation response changed enforced limits");
|
|
1018
|
+
const network = validateReservationNetwork(source.network, expected.limits.maxModelCalls, gatewayDomain);
|
|
1019
|
+
return immutableCandidateValue({
|
|
1020
|
+
preparationId: source.preparationId,
|
|
1021
|
+
digest: source.digest,
|
|
1022
|
+
expiresAtMs: source.expiresAtMs,
|
|
1023
|
+
enforcedLimits: source.enforcedLimits,
|
|
1024
|
+
network
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
function validateReservationNetwork(value, maxModelCalls, gatewayDomain) {
|
|
1028
|
+
if (maxModelCalls === 0) {
|
|
1029
|
+
if (exactRecord(value, ["mode"], "protected model reservation network").mode !== "disabled") throw new Error("zero-call protected model reservation must disable gateway access");
|
|
1030
|
+
return { mode: "disabled" };
|
|
1031
|
+
}
|
|
1032
|
+
const source = exactRecord(value, ["mode", "domains"], "protected model reservation network");
|
|
1033
|
+
if (source.mode !== "gateway-only") throw new Error("protected model reservation must allow only its model gateway");
|
|
1034
|
+
if (!Array.isArray(source.domains) || source.domains.length !== 1 || source.domains[0] !== gatewayDomain) throw new Error("protected model reservation returned an unexpected gateway domain");
|
|
1035
|
+
return {
|
|
1036
|
+
mode: "gateway-only",
|
|
1037
|
+
domains: [gatewayDomain]
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function validateActivation(value, expectedNames) {
|
|
1041
|
+
const env = record(exactRecord(value, ["env"], "protected model activation response").env, "protected model activation environment");
|
|
1042
|
+
const actualNames = Object.keys(env).sort();
|
|
1043
|
+
if (actualNames.length !== expectedNames.length || actualNames.some((name, index) => name !== expectedNames[index])) throw new Error("protected model activation returned unexpected environment names");
|
|
1044
|
+
const detached = Object.create(null);
|
|
1045
|
+
for (const name of expectedNames) {
|
|
1046
|
+
const value = env[name];
|
|
1047
|
+
if (typeof value !== "string" || value.length < 8 || value.length > 65536) throw new Error(`protected model activation ${name} must be a non-empty protected value`);
|
|
1048
|
+
detached[name] = value;
|
|
1049
|
+
}
|
|
1050
|
+
return Object.freeze({ env: Object.freeze(detached) });
|
|
1051
|
+
}
|
|
1052
|
+
function validateResolvedModel(value, expected) {
|
|
1053
|
+
const source = exactRecord(value, [
|
|
1054
|
+
"requested",
|
|
1055
|
+
"provider",
|
|
1056
|
+
"model",
|
|
1057
|
+
"snapshot",
|
|
1058
|
+
"reasoningEffort"
|
|
1059
|
+
], "resolved candidate model");
|
|
1060
|
+
if (source.requested !== expected.requested) throw new Error("model resolver changed the evaluator-owned request");
|
|
1061
|
+
if (source.reasoningEffort !== expected.reasoningEffort) throw new Error("model resolver changed the evaluator-owned reasoning effort");
|
|
1062
|
+
for (const name of [
|
|
1063
|
+
"requested",
|
|
1064
|
+
"provider",
|
|
1065
|
+
"model",
|
|
1066
|
+
"snapshot"
|
|
1067
|
+
]) if (typeof source[name] !== "string" || source[name].length === 0) throw new Error(`resolved candidate model ${name} must be non-empty`);
|
|
1068
|
+
return source;
|
|
1069
|
+
}
|
|
1070
|
+
function assertGrantIdentity(expected, actual) {
|
|
1071
|
+
if (actual.executionId !== expected.executionId || actual.preparationId !== expected.preparationId || actual.grantDigest !== expected.grantDigest || canonicalCandidateDigest$1(actual.resolved) !== canonicalCandidateDigest$1(expected.resolved)) throw new Error("protected model grant input does not match its immutable reservation");
|
|
1072
|
+
}
|
|
1073
|
+
function assertWithinReservedLimits(usage, reservation) {
|
|
1074
|
+
for (const [name, actual, limit] of [
|
|
1075
|
+
[
|
|
1076
|
+
"model calls",
|
|
1077
|
+
usage.modelCalls,
|
|
1078
|
+
reservation.limits.maxModelCalls
|
|
1079
|
+
],
|
|
1080
|
+
[
|
|
1081
|
+
"input tokens",
|
|
1082
|
+
usage.inputTokens,
|
|
1083
|
+
reservation.limits.maxInputTokens
|
|
1084
|
+
],
|
|
1085
|
+
[
|
|
1086
|
+
"output tokens",
|
|
1087
|
+
usage.outputTokens,
|
|
1088
|
+
reservation.limits.maxOutputTokens
|
|
1089
|
+
],
|
|
1090
|
+
[
|
|
1091
|
+
"cost USD nanos",
|
|
1092
|
+
usage.costUsdNanos,
|
|
1093
|
+
reservation.maxCostUsdNanos
|
|
1094
|
+
]
|
|
1095
|
+
]) if (actual > limit) throw new Error(`protected model settlement ${name} ${actual} exceeds reserved ${limit}`);
|
|
1096
|
+
const maxTotalTokens = reservation.limits.maxTotalTokens;
|
|
1097
|
+
if (maxTotalTokens !== void 0) {
|
|
1098
|
+
const totalTokens = usage.inputTokens + usage.outputTokens;
|
|
1099
|
+
if (!Number.isSafeInteger(totalTokens)) throw new Error("protected model settlement total token usage is incomplete");
|
|
1100
|
+
if (totalTokens > maxTotalTokens) throw new Error(`protected model settlement total tokens ${totalTokens} exceeds reserved ${maxTotalTokens}`);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function modelLimitKeys(limits) {
|
|
1104
|
+
return limits.maxTotalTokens === void 0 ? [
|
|
1105
|
+
"maxModelCalls",
|
|
1106
|
+
"maxInputTokens",
|
|
1107
|
+
"maxOutputTokens",
|
|
1108
|
+
"maxCostUsd"
|
|
1109
|
+
] : [
|
|
1110
|
+
"maxModelCalls",
|
|
1111
|
+
"maxInputTokens",
|
|
1112
|
+
"maxOutputTokens",
|
|
1113
|
+
"maxTotalTokens",
|
|
1114
|
+
"maxCostUsd"
|
|
1115
|
+
];
|
|
1116
|
+
}
|
|
1117
|
+
function exactEnvironmentNames(values) {
|
|
1118
|
+
if (values.length === 0) throw new Error("protected model activation environment names must not be empty");
|
|
1119
|
+
const names = [...values].sort();
|
|
1120
|
+
if (new Set(names).size !== names.length) throw new Error("protected model activation environment names must be unique");
|
|
1121
|
+
for (const name of names) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || [
|
|
1122
|
+
"__proto__",
|
|
1123
|
+
"constructor",
|
|
1124
|
+
"prototype"
|
|
1125
|
+
].includes(name)) throw new Error(`protected model activation environment name is invalid: ${name}`);
|
|
1126
|
+
return Object.freeze(names);
|
|
1127
|
+
}
|
|
1128
|
+
function assertGatewayDomain(value) {
|
|
1129
|
+
if (value.length > 253 || value !== value.toLowerCase() || value.endsWith(".") || value.includes(":") || value === "localhost" || value.endsWith(".localhost") || value === "metadata.google" || value === "metadata.google.internal") throw new Error("protected model gateway must be an exact lowercase public DNS name");
|
|
1130
|
+
const labels = value.split(".");
|
|
1131
|
+
if (labels.length < 2 || !labels.every((label) => label.length >= 1 && label.length <= 63 && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(label)) || !/[a-z]/.test(labels.at(-1) ?? "")) throw new Error("protected model gateway must be an exact lowercase public DNS name");
|
|
1132
|
+
return value;
|
|
1133
|
+
}
|
|
1134
|
+
function reservationKey(executionId, preparationId) {
|
|
1135
|
+
return canonicalCandidateDigest$1({
|
|
1136
|
+
executionId,
|
|
1137
|
+
preparationId
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
function pruneExpiredState(reservations, expiredReservations, settlements, now) {
|
|
1141
|
+
for (const [key, value] of reservations) if (value.expiresAtMs <= now) {
|
|
1142
|
+
reservations.delete(key);
|
|
1143
|
+
expiredReservations.set(key, {
|
|
1144
|
+
state: value,
|
|
1145
|
+
retainedUntilMs: now + RECOVERED_SETTLEMENT_RETENTION_MS
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
for (const [key, value] of expiredReservations) if (value.retainedUntilMs <= now) expiredReservations.delete(key);
|
|
1149
|
+
for (const [key, value] of settlements) if (value.expiresAtMs <= now) settlements.delete(key);
|
|
1150
|
+
}
|
|
1151
|
+
function rememberSettlement(settlements, key, value) {
|
|
1152
|
+
settlements.delete(key);
|
|
1153
|
+
settlements.set(key, value);
|
|
1154
|
+
while (settlements.size > MAX_RECENT_SETTLEMENTS) {
|
|
1155
|
+
const oldest = settlements.keys().next().value;
|
|
1156
|
+
if (oldest === void 0) return;
|
|
1157
|
+
settlements.delete(oldest);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
function isSha256Digest(value) {
|
|
1161
|
+
return typeof value === "string" && /^sha256:[a-f0-9]{64}$/.test(value);
|
|
1162
|
+
}
|
|
1163
|
+
function exactRecord(value, keys, label) {
|
|
1164
|
+
const source = record(value, label);
|
|
1165
|
+
assertExactObjectKeys(source, keys, label);
|
|
1166
|
+
return source;
|
|
1167
|
+
}
|
|
1168
|
+
function record(value, label) {
|
|
1169
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
1170
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1171
|
+
if (prototype !== Object.prototype && prototype !== null) throw new Error(`${label} must be a plain object`);
|
|
1172
|
+
return value;
|
|
1173
|
+
}
|
|
1174
|
+
function modelLimits(limits) {
|
|
1175
|
+
return {
|
|
1176
|
+
maxModelCalls: limits.maxModelCalls,
|
|
1177
|
+
maxInputTokens: limits.maxInputTokens,
|
|
1178
|
+
maxOutputTokens: limits.maxOutputTokens,
|
|
1179
|
+
maxCostUsd: limits.maxCostUsd
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
//#endregion
|
|
12
1183
|
//#region src/candidate-execution/claim-file-formats.ts
|
|
13
1184
|
function sealCandidatePreparationEvidence(value, executionPlanDigest) {
|
|
14
1185
|
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("candidate execution preparation evidence must be an object");
|
|
@@ -249,7 +1420,7 @@ function sealUsage(usage) {
|
|
|
249
1420
|
"cachedInputTokens",
|
|
250
1421
|
"reasoningTokens",
|
|
251
1422
|
"modelCalls"
|
|
252
|
-
]) assertCount
|
|
1423
|
+
]) assertCount(usage[field], `terminal usage ${field}`);
|
|
253
1424
|
if (usage.costProvenance !== "observed" && usage.costProvenance !== "estimated") throw new Error("terminal usage costProvenance must be observed or estimated");
|
|
254
1425
|
return Object.freeze({
|
|
255
1426
|
costUsdNanos: usage.costUsdNanos,
|
|
@@ -302,11 +1473,14 @@ function assertExecutionId$1(value) {
|
|
|
302
1473
|
function assertSha256Digest$1(value, field) {
|
|
303
1474
|
if (!SHA256_PATTERN$1.test(value)) throw new Error(`candidate execution claim ${field} must be a lowercase sha256 digest`);
|
|
304
1475
|
}
|
|
305
|
-
function assertCount
|
|
1476
|
+
function assertCount(value, label) {
|
|
306
1477
|
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`candidate execution ${label} must be a non-negative safe integer`);
|
|
307
1478
|
}
|
|
1479
|
+
//#endregion
|
|
1480
|
+
//#region src/candidate-execution/cleanup.ts
|
|
1481
|
+
const DEFAULT_CANDIDATE_CLEANUP_TIMEOUT_MS = 3e4;
|
|
308
1482
|
function candidateCleanupTimeout(timeoutMs) {
|
|
309
|
-
const effective = timeoutMs ??
|
|
1483
|
+
const effective = timeoutMs ?? DEFAULT_CANDIDATE_CLEANUP_TIMEOUT_MS;
|
|
310
1484
|
if (!Number.isSafeInteger(effective) || effective <= 0 || effective > 2147483647) throw new Error("candidate cleanup timeout is outside the supported timer range");
|
|
311
1485
|
return effective;
|
|
312
1486
|
}
|
|
@@ -374,107 +1548,8 @@ function attemptRecord(claim, phase, staged, terminal) {
|
|
|
374
1548
|
phase,
|
|
375
1549
|
...staged ? { staged } : {},
|
|
376
1550
|
...terminal ? { terminal } : {}
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
/** Single-process lifecycle implementation. */
|
|
380
|
-
var InMemoryAgentCandidateExecutionClaimStore = class {
|
|
381
|
-
claims = /* @__PURE__ */ new Map();
|
|
382
|
-
now;
|
|
383
|
-
constructor(options = {}) {
|
|
384
|
-
this.now = options.now ?? Date.now;
|
|
385
|
-
}
|
|
386
|
-
async tryClaim(requested) {
|
|
387
|
-
const claim = sealClaim(requested);
|
|
388
|
-
const slot = claimSlot(claim);
|
|
389
|
-
const existing = this.claims.get(slot);
|
|
390
|
-
if (existing) return rejectedExistingClaim(existing.claim, claim);
|
|
391
|
-
assertUnexpiredLease(claim.leaseExpiresAtMs, this.now());
|
|
392
|
-
const retryRejection = retryRejectionFromMemory(this.claims, claim);
|
|
393
|
-
if (retryRejection) return rejectedRetry(claim, retryRejection);
|
|
394
|
-
const lease = newLease(claim);
|
|
395
|
-
this.claims.set(slot, {
|
|
396
|
-
claim,
|
|
397
|
-
leaseDigest: leaseDigest(lease),
|
|
398
|
-
phase: "claimed"
|
|
399
|
-
});
|
|
400
|
-
return Object.freeze({
|
|
401
|
-
acquired: true,
|
|
402
|
-
claim,
|
|
403
|
-
lease
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
async getAttempt(requestedAttempt) {
|
|
407
|
-
const attempt = sealAttemptRef(requestedAttempt);
|
|
408
|
-
const stored = this.claims.get(claimSlot(attempt));
|
|
409
|
-
return stored ? attemptRecord(stored.claim, stored.phase, stored.staged, stored.terminal) : void 0;
|
|
410
|
-
}
|
|
411
|
-
async markCandidateMayRun(requestedLease) {
|
|
412
|
-
const lease = sealLease(requestedLease);
|
|
413
|
-
const stored = this.requireClaim(lease);
|
|
414
|
-
assertLease(stored.leaseDigest, stored.claim.leaseExpiresAtMs, lease);
|
|
415
|
-
if (stored.phase === "candidate-may-run") return Object.freeze({
|
|
416
|
-
marked: false,
|
|
417
|
-
phase: "candidate-may-run"
|
|
418
|
-
});
|
|
419
|
-
if (stored.staged || stored.terminal) throw new Error("candidate execution terminal was staged before candidate-may-run phase");
|
|
420
|
-
assertUnexpiredLease(stored.claim.leaseExpiresAtMs, this.now());
|
|
421
|
-
stored.phase = "candidate-may-run";
|
|
422
|
-
return Object.freeze({
|
|
423
|
-
marked: true,
|
|
424
|
-
phase: "candidate-may-run"
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
async stageTerminal(requestedLease, result) {
|
|
428
|
-
const lease = sealLease(requestedLease);
|
|
429
|
-
const stored = this.requireClaim(lease);
|
|
430
|
-
assertLease(stored.leaseDigest, stored.claim.leaseExpiresAtMs, lease);
|
|
431
|
-
const terminal = terminalRecord(stored.claim, result);
|
|
432
|
-
if (stored.staged) return rejectedStage(stored.staged, terminal);
|
|
433
|
-
assertTerminalAllowedInPhase(stored.phase, terminal);
|
|
434
|
-
assertUnexpiredLease(stored.claim.leaseExpiresAtMs, this.now());
|
|
435
|
-
stored.staged = terminal;
|
|
436
|
-
return Object.freeze({
|
|
437
|
-
staged: true,
|
|
438
|
-
terminal
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
async finish(requestedLease, requestedTerminalDigest) {
|
|
442
|
-
const lease = sealLease(requestedLease);
|
|
443
|
-
const terminalDigest = sealTerminalDigest(requestedTerminalDigest);
|
|
444
|
-
const stored = this.requireClaim(lease);
|
|
445
|
-
assertLease(stored.leaseDigest, stored.claim.leaseExpiresAtMs, lease);
|
|
446
|
-
const staged = requireStagedTerminal(stored.staged, terminalDigest);
|
|
447
|
-
if (stored.terminal) return rejectedFinish(stored.terminal, terminalDigest);
|
|
448
|
-
assertUnexpiredLease(stored.claim.leaseExpiresAtMs, this.now());
|
|
449
|
-
stored.terminal = staged;
|
|
450
|
-
return Object.freeze({
|
|
451
|
-
finished: true,
|
|
452
|
-
terminal: staged
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
async recoverExpired(requestedAttempt, evidence) {
|
|
456
|
-
const attempt = sealAttemptRef(requestedAttempt);
|
|
457
|
-
const stored = this.requireClaim(attempt, "candidate execution recovery");
|
|
458
|
-
const recovered = recoveredTerminalRecord(stored.claim, stored.phase, evidence);
|
|
459
|
-
if (stored.staged) assertRecoveryMatchesStaged(stored.staged, recovered);
|
|
460
|
-
const requestedDigest = stored.staged?.terminalDigest ?? recovered.terminalDigest;
|
|
461
|
-
if (stored.terminal) return rejectedFinish(stored.terminal, requestedDigest);
|
|
462
|
-
assertExpiredLease(stored.claim.leaseExpiresAtMs, this.now());
|
|
463
|
-
const terminal = stored.staged ?? recovered;
|
|
464
|
-
assertRecoveryMatchesStaged(terminal, recovered);
|
|
465
|
-
stored.staged ??= terminal;
|
|
466
|
-
stored.terminal = terminal;
|
|
467
|
-
return Object.freeze({
|
|
468
|
-
finished: true,
|
|
469
|
-
terminal
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
requireClaim(attempt, operation = "candidate execution lease") {
|
|
473
|
-
const stored = this.claims.get(claimSlot(attempt));
|
|
474
|
-
if (!stored) throw new Error(`${operation} does not name an acquired attempt`);
|
|
475
|
-
return stored;
|
|
476
|
-
}
|
|
477
|
-
};
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
478
1553
|
const SHA256_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
479
1554
|
const LEASE_TOKEN_PATTERN = /^candidate-execution-lease\.[A-Za-z0-9_-]{43}$/;
|
|
480
1555
|
const PREPARATION_ID_PATTERN = /^candidate-preparation\.[A-Za-z0-9_-]{43}$/;
|
|
@@ -603,13 +1678,6 @@ function assertLease(expectedDigest, expectedExpiresAtMs, lease) {
|
|
|
603
1678
|
function claimSlot(claim) {
|
|
604
1679
|
return createHash("sha256").update(JSON.stringify([claim.executionId, claim.attempt]), "utf8").digest("hex");
|
|
605
1680
|
}
|
|
606
|
-
function retryRejectionFromMemory(claims, claim) {
|
|
607
|
-
if (claim.attempt === 1) return void 0;
|
|
608
|
-
return retryRejection(claim, claims.get(claimSlot({
|
|
609
|
-
executionId: claim.executionId,
|
|
610
|
-
attempt: claim.attempt - 1
|
|
611
|
-
})));
|
|
612
|
-
}
|
|
613
1681
|
function retryRejection(claim, prior) {
|
|
614
1682
|
if (!prior) return "prior-attempt-missing";
|
|
615
1683
|
if (claim.retryPolicy !== "pre-model-infrastructure-only" || prior.claim.retryPolicy !== claim.retryPolicy || prior.claim.maxAttempts !== claim.maxAttempts || prior.claim.bundleDigest !== claim.bundleDigest || prior.claim.retryLineageDigest !== claim.retryLineageDigest) return "retry-lineage-mismatch";
|
|
@@ -767,13 +1835,6 @@ function assertExecutionId(value) {
|
|
|
767
1835
|
function assertBoundedIdentifier(value, label, maxLength) {
|
|
768
1836
|
if (typeof value !== "string" || value.length === 0 || value.length > maxLength || hasControlCharacter(value)) throw new Error(`candidate execution ${label} is invalid`);
|
|
769
1837
|
}
|
|
770
|
-
function hasControlCharacter(value) {
|
|
771
|
-
for (let index = 0; index < value.length; index++) {
|
|
772
|
-
const code = value.charCodeAt(index);
|
|
773
|
-
if (code < 32 || code === 127) return true;
|
|
774
|
-
}
|
|
775
|
-
return false;
|
|
776
|
-
}
|
|
777
1838
|
function assertPositiveTimestamp(value, label) {
|
|
778
1839
|
if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`candidate execution ${label} must be a positive safe timestamp`);
|
|
779
1840
|
}
|
|
@@ -841,57 +1902,6 @@ function candidateTerminalWindowMs(cleanupTimeoutMs) {
|
|
|
841
1902
|
return windowMs;
|
|
842
1903
|
}
|
|
843
1904
|
//#endregion
|
|
844
|
-
//#region src/candidate-execution/execution-roots.ts
|
|
845
|
-
/** Validate the canonical identity and isolation boundary for execution roots. */
|
|
846
|
-
function assertAgentCandidateExecutionRoots(roots) {
|
|
847
|
-
const entries = [
|
|
848
|
-
["task", roots.taskRoot],
|
|
849
|
-
["candidate", roots.candidateRoot],
|
|
850
|
-
["profile", roots.profileRoot]
|
|
851
|
-
];
|
|
852
|
-
for (const [name, root] of entries) {
|
|
853
|
-
if (root === void 0) continue;
|
|
854
|
-
if (!posix.isAbsolute(root) || posix.normalize(root) !== root) throw new Error(`execution ${name} root must be a canonical absolute path`);
|
|
855
|
-
}
|
|
856
|
-
for (let left = 0; left < entries.length; left++) {
|
|
857
|
-
const entry = entries[left];
|
|
858
|
-
if (!entry) continue;
|
|
859
|
-
const [leftName, leftRoot] = entry;
|
|
860
|
-
if (leftRoot === void 0) continue;
|
|
861
|
-
for (let right = left + 1; right < entries.length; right++) {
|
|
862
|
-
const rightEntry = entries[right];
|
|
863
|
-
if (!rightEntry) continue;
|
|
864
|
-
const [rightName, rightRoot] = rightEntry;
|
|
865
|
-
if (rightRoot !== void 0 && executionPathsOverlap(leftRoot, rightRoot)) throw new Error(`execution ${leftName} and ${rightName} roots must be distinct and non-overlapping`);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
function executionPathsOverlap(left, right) {
|
|
870
|
-
const a = posix.normalize(left);
|
|
871
|
-
const b = posix.normalize(right);
|
|
872
|
-
return a === b || b.startsWith(a === "/" ? "/" : `${a}/`) || a.startsWith(b === "/" ? "/" : `${b}/`);
|
|
873
|
-
}
|
|
874
|
-
//#endregion
|
|
875
|
-
//#region src/candidate-execution/types.ts
|
|
876
|
-
const verifiedCandidateBrand = Symbol("verifiedAgentCandidate");
|
|
877
|
-
const preparedCandidateBrand = Symbol("preparedAgentCandidate");
|
|
878
|
-
const verifiedTaskOutcomeBrand = Symbol("verifiedTaskOutcome");
|
|
879
|
-
/** Protected trace tags that bind a run to one prepared candidate execution. */
|
|
880
|
-
const CANDIDATE_TRACE_TAGS = {
|
|
881
|
-
executionId: "tangle.candidate.execution_id",
|
|
882
|
-
bundleDigest: "tangle.candidate.bundle_digest",
|
|
883
|
-
executionPlanDigest: "tangle.candidate.execution_plan_digest",
|
|
884
|
-
materializationReceiptDigest: "tangle.candidate.materialization_receipt_digest"
|
|
885
|
-
};
|
|
886
|
-
/** Environment keys used to propagate immutable candidate trace identity. */
|
|
887
|
-
const CANDIDATE_TRACE_ENV = {
|
|
888
|
-
executionId: "TANGLE_CANDIDATE_EXECUTION_ID",
|
|
889
|
-
bundleDigest: "TANGLE_CANDIDATE_BUNDLE_DIGEST",
|
|
890
|
-
executionPlanDigest: "TANGLE_CANDIDATE_EXECUTION_PLAN_DIGEST",
|
|
891
|
-
materializationReceiptDigest: "TANGLE_CANDIDATE_MATERIALIZATION_RECEIPT_DIGEST",
|
|
892
|
-
traceRunId: "TANGLE_TRACE_RUN_ID"
|
|
893
|
-
};
|
|
894
|
-
//#endregion
|
|
895
1905
|
//#region src/candidate-execution/prepared-state.ts
|
|
896
1906
|
const stateByExecution = /* @__PURE__ */ new WeakMap();
|
|
897
1907
|
const lifecycleByExecution = /* @__PURE__ */ new WeakMap();
|
|
@@ -1088,7 +2098,7 @@ function assertPrivateCandidateIntegrity(state) {
|
|
|
1088
2098
|
assertSignedBenchmarkInput(state);
|
|
1089
2099
|
const instruction = Buffer.from(state.benchmarkTask.instruction, "utf8");
|
|
1090
2100
|
if (!Buffer.from(state.instruction.bytes).equals(instruction) || JSON.stringify(state.instruction.delivery) !== JSON.stringify(state.executionPlan.value.material.instructionDelivery)) throw new Error("prepared instruction no longer matches the signed execution plan");
|
|
1091
|
-
if (!/^candidate-preparation\.[A-Za-z0-9_-]{43}$/.test(state.preparationId) || !Number.isSafeInteger(state.reservationExpiresAtMs) || state.reservationExpiresAtMs <= 0 || !Number.isSafeInteger(state.cleanupTimeoutMs) || state.cleanupTimeoutMs <= 0 || !Number.isSafeInteger(state.resultTimeoutMs) || state.resultTimeoutMs <= 0 || JSON.stringify(state.resolvedModel) !== JSON.stringify(state.executionPlan.value.material.model.resolved) || state.modelReservation.digest !== state.executionPlan.value.material.model.access.grantDigest || state.modelReservation.preparationId !== state.preparationId || state.modelReservation.expiresAtMs !== state.reservationExpiresAtMs || canonicalCandidateDigest$1(state.modelReservation.network) !== canonicalCandidateDigest$1(state.executionPlan.value.material.model.access.network) || canonicalCandidateDigest$1(state.modelReservation.enforcedLimits) !== canonicalCandidateDigest$1(modelLimits
|
|
2101
|
+
if (!/^candidate-preparation\.[A-Za-z0-9_-]{43}$/.test(state.preparationId) || !Number.isSafeInteger(state.reservationExpiresAtMs) || state.reservationExpiresAtMs <= 0 || !Number.isSafeInteger(state.cleanupTimeoutMs) || state.cleanupTimeoutMs <= 0 || !Number.isSafeInteger(state.resultTimeoutMs) || state.resultTimeoutMs <= 0 || JSON.stringify(state.resolvedModel) !== JSON.stringify(state.executionPlan.value.material.model.resolved) || state.modelReservation.digest !== state.executionPlan.value.material.model.access.grantDigest || state.modelReservation.preparationId !== state.preparationId || state.modelReservation.expiresAtMs !== state.reservationExpiresAtMs || canonicalCandidateDigest$1(state.modelReservation.network) !== canonicalCandidateDigest$1(state.executionPlan.value.material.model.access.network) || canonicalCandidateDigest$1(state.modelReservation.enforcedLimits) !== canonicalCandidateDigest$1(modelLimits(state.executionPlan.value.material.limits))) throw new Error("prepared model access no longer matches the signed execution plan");
|
|
1092
2102
|
assertExecutorInputs(state);
|
|
1093
2103
|
assertPreparedKnowledge(state);
|
|
1094
2104
|
if (state.memory.mode === "isolated" && !state.memoryReservation || state.memory.mode === "disabled" && state.memoryReservation) throw new Error("prepared memory reservation does not match the signed execution plan");
|
|
@@ -1225,14 +2235,6 @@ function immutableExecutorFiles(files) {
|
|
|
1225
2235
|
bytes: Uint8Array.from(file.bytes)
|
|
1226
2236
|
})));
|
|
1227
2237
|
}
|
|
1228
|
-
function modelLimits$1(limits) {
|
|
1229
|
-
return {
|
|
1230
|
-
maxModelCalls: limits.maxModelCalls,
|
|
1231
|
-
maxInputTokens: limits.maxInputTokens,
|
|
1232
|
-
maxOutputTokens: limits.maxOutputTokens,
|
|
1233
|
-
maxCostUsd: limits.maxCostUsd
|
|
1234
|
-
};
|
|
1235
|
-
}
|
|
1236
2238
|
function transitionLifecycle(prepared, expected, next) {
|
|
1237
2239
|
const lifecycle = lifecycleByExecution.get(prepared);
|
|
1238
2240
|
if (!lifecycle || !expected.includes(lifecycle.status)) throw new Error(`prepared candidate execution is already ${lifecycle?.status ?? "unknown"}`);
|
|
@@ -1399,208 +2401,20 @@ function sealTaskOutcomeCapture(value, expected) {
|
|
|
1399
2401
|
], "candidate task capture");
|
|
1400
2402
|
if (capture.kind !== "workspace") throw new Error("candidate task capture has an invalid outcome kind");
|
|
1401
2403
|
if (expected.kind !== "workspace") throw new Error("candidate workspace capture does not match the signed outcome kind");
|
|
1402
|
-
if (typeof capture.resultTree !== "string" || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(capture.resultTree)) throw new Error("candidate task capture resultTree is not a Git object id");
|
|
1403
|
-
if (!(capture.archive instanceof Uint8Array) || !(capture.gitDiff instanceof Uint8Array)) throw new Error("candidate task capture archive and gitDiff must be byte arrays");
|
|
1404
|
-
const afterState = agentCandidateWorkspaceManifestMaterialSchema.parse(capture.afterState);
|
|
1405
|
-
return Object.freeze({
|
|
1406
|
-
kind: "workspace",
|
|
1407
|
-
resultTree: capture.resultTree,
|
|
1408
|
-
afterState: Object.freeze(afterState),
|
|
1409
|
-
archive: Uint8Array.from(capture.archive),
|
|
1410
|
-
gitDiff: Uint8Array.from(capture.gitDiff)
|
|
1411
|
-
});
|
|
1412
|
-
}
|
|
1413
|
-
function requireRecord(value, label) {
|
|
1414
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
1415
|
-
return value;
|
|
1416
|
-
}
|
|
1417
|
-
//#endregion
|
|
1418
|
-
//#region src/candidate-execution/model-settlement.ts
|
|
1419
|
-
const USD_NANOS = 1e9;
|
|
1420
|
-
/** Validate and detach the evaluator gateway's terminal, revoked call ledger. */
|
|
1421
|
-
function sealAgentCandidateModelSettlement(settlement, expected) {
|
|
1422
|
-
assertExactObjectKeys(settlement, [
|
|
1423
|
-
"preparationId",
|
|
1424
|
-
"grantDigest",
|
|
1425
|
-
"closed",
|
|
1426
|
-
"usageWithinLimits",
|
|
1427
|
-
"calls"
|
|
1428
|
-
], "model settlement");
|
|
1429
|
-
if (settlement.closed !== true) throw new Error("protected model grant is not closed");
|
|
1430
|
-
if (settlement.usageWithinLimits !== true) throw new Error("protected model settlement reports usage outside frozen limits");
|
|
1431
|
-
if (settlement.grantDigest !== expected.grantDigest) throw new Error("protected model settlement grant digest does not match the reservation");
|
|
1432
|
-
if (settlement.preparationId !== expected.preparationId) throw new Error("protected model settlement preparation does not match the reservation");
|
|
1433
|
-
if (!Array.isArray(settlement.calls)) throw new Error("protected model settlement calls must be an array");
|
|
1434
|
-
const callIds = /* @__PURE__ */ new Set();
|
|
1435
|
-
const spanIds = /* @__PURE__ */ new Set();
|
|
1436
|
-
let accountedInputTokens = 0;
|
|
1437
|
-
let outputTokens = 0;
|
|
1438
|
-
let cachedInputTokens = 0;
|
|
1439
|
-
let reasoningTokens = 0;
|
|
1440
|
-
let costUsdNanos = 0;
|
|
1441
|
-
let costProvenance = "observed";
|
|
1442
|
-
const calls = settlement.calls.map((source, index) => {
|
|
1443
|
-
assertExactObjectKeys(source, [
|
|
1444
|
-
"callId",
|
|
1445
|
-
"generationId",
|
|
1446
|
-
"traceSpanId",
|
|
1447
|
-
"status",
|
|
1448
|
-
"model",
|
|
1449
|
-
"startedAtMs",
|
|
1450
|
-
"endedAtMs",
|
|
1451
|
-
"inputTokens",
|
|
1452
|
-
"accountedInputTokens",
|
|
1453
|
-
"outputTokens",
|
|
1454
|
-
"cachedInputTokens",
|
|
1455
|
-
"reasoningTokens",
|
|
1456
|
-
"costUsdNanos",
|
|
1457
|
-
"costProvenance"
|
|
1458
|
-
], `model settlement call ${index}`);
|
|
1459
|
-
assertIdentifier(source.callId, `model settlement call ${index} callId`);
|
|
1460
|
-
assertIdentifier(source.generationId, `model settlement call ${index} generationId`);
|
|
1461
|
-
assertIdentifier(source.traceSpanId, `model settlement call ${index} traceSpanId`);
|
|
1462
|
-
if (source.traceSpanId !== source.generationId) throw new Error(`model settlement call ${index} traceSpanId is not its router generationId`);
|
|
1463
|
-
if (source.status !== "succeeded" && source.status !== "failed") throw new Error(`model settlement call ${index} has an invalid status`);
|
|
1464
|
-
if (callIds.has(source.callId)) throw new Error("protected model settlement has duplicate call ids");
|
|
1465
|
-
if (spanIds.has(source.traceSpanId)) throw new Error("protected model settlement has duplicate trace span ids");
|
|
1466
|
-
callIds.add(source.callId);
|
|
1467
|
-
spanIds.add(source.traceSpanId);
|
|
1468
|
-
if (source.model !== expected.model) throw new Error(`protected model settlement call ${index} has an unexpected model`);
|
|
1469
|
-
assertTimestamp(source.startedAtMs, `model settlement call ${index} startedAtMs`);
|
|
1470
|
-
assertTimestamp(source.endedAtMs, `model settlement call ${index} endedAtMs`);
|
|
1471
|
-
if (source.endedAtMs < source.startedAtMs) throw new Error(`model settlement call ${index} ended before it started`);
|
|
1472
|
-
assertCount(source.inputTokens, `model settlement call ${index} inputTokens`);
|
|
1473
|
-
assertCount(source.accountedInputTokens, `model settlement call ${index} accountedInputTokens`);
|
|
1474
|
-
if (source.accountedInputTokens < source.inputTokens) throw new Error(`model settlement call ${index} accountedInputTokens cannot be less than inputTokens`);
|
|
1475
|
-
assertCount(source.outputTokens, `model settlement call ${index} outputTokens`);
|
|
1476
|
-
assertCount(source.cachedInputTokens, `model settlement call ${index} cachedInputTokens`);
|
|
1477
|
-
cachedInputTokens = safeAdd(cachedInputTokens, source.cachedInputTokens, "cached input token total");
|
|
1478
|
-
assertCount(source.reasoningTokens, `model settlement call ${index} reasoningTokens`);
|
|
1479
|
-
reasoningTokens = safeAdd(reasoningTokens, source.reasoningTokens, "reasoning token total");
|
|
1480
|
-
assertCount(source.costUsdNanos, `model settlement call ${index} costUsdNanos`);
|
|
1481
|
-
if (source.costProvenance !== "observed" && source.costProvenance !== "estimated") throw new Error(`model settlement call ${index} has an invalid cost provenance`);
|
|
1482
|
-
if (source.costProvenance === "estimated") costProvenance = "estimated";
|
|
1483
|
-
accountedInputTokens = safeAdd(accountedInputTokens, source.accountedInputTokens, "accounted input token total");
|
|
1484
|
-
outputTokens = safeAdd(outputTokens, source.outputTokens, "output token total");
|
|
1485
|
-
costUsdNanos = safeAdd(costUsdNanos, source.costUsdNanos, "cost total");
|
|
1486
|
-
return Object.freeze({ ...source });
|
|
1487
|
-
});
|
|
1488
|
-
const usage = Object.freeze({
|
|
1489
|
-
costUsdNanos,
|
|
1490
|
-
inputTokens: accountedInputTokens,
|
|
1491
|
-
outputTokens,
|
|
1492
|
-
cachedInputTokens,
|
|
1493
|
-
reasoningTokens,
|
|
1494
|
-
modelCalls: calls.length,
|
|
1495
|
-
costProvenance
|
|
1496
|
-
});
|
|
1497
|
-
return Object.freeze({
|
|
1498
|
-
value: Object.freeze({
|
|
1499
|
-
preparationId: settlement.preparationId,
|
|
1500
|
-
grantDigest: settlement.grantDigest,
|
|
1501
|
-
closed: true,
|
|
1502
|
-
usageWithinLimits: true,
|
|
1503
|
-
calls: Object.freeze(calls)
|
|
1504
|
-
}),
|
|
1505
|
-
usage
|
|
1506
|
-
});
|
|
1507
|
-
}
|
|
1508
|
-
/**
|
|
1509
|
-
* Append the only accepted LLM spans from the router's closed ledger.
|
|
1510
|
-
* Candidate and executor code may write tool/process spans, but never model usage.
|
|
1511
|
-
*/
|
|
1512
|
-
async function appendAuthoritativeModelSettlementSpans(traceStore, runId, settlement) {
|
|
1513
|
-
const run = await traceStore.getRun(runId);
|
|
1514
|
-
if (!run) throw new Error(`protected trace run is missing before model settlement: ${runId}`);
|
|
1515
|
-
if (run.status === "running" || run.endedAt === void 0) throw new Error("protected trace run must be terminal before model spans are appended");
|
|
1516
|
-
const existing = await traceStore.spans({ runId });
|
|
1517
|
-
if (existing.some(isLlmSpan)) throw new Error("protected trace contains a model span not authored from the closed router ledger");
|
|
1518
|
-
const occupiedIds = new Set(existing.map((span) => span.spanId));
|
|
1519
|
-
for (const call of settlement.value.calls) {
|
|
1520
|
-
if (occupiedIds.has(call.traceSpanId)) throw new Error(`protected trace span identity collides with router generation ${call.generationId}`);
|
|
1521
|
-
await traceStore.appendSpan({
|
|
1522
|
-
runId,
|
|
1523
|
-
spanId: call.traceSpanId,
|
|
1524
|
-
kind: "llm",
|
|
1525
|
-
name: "protected model call",
|
|
1526
|
-
model: call.model,
|
|
1527
|
-
messages: [],
|
|
1528
|
-
startedAt: call.startedAtMs,
|
|
1529
|
-
endedAt: call.endedAtMs,
|
|
1530
|
-
status: call.status === "succeeded" ? "ok" : "error",
|
|
1531
|
-
inputTokens: call.inputTokens,
|
|
1532
|
-
outputTokens: call.outputTokens,
|
|
1533
|
-
cachedTokens: call.cachedInputTokens,
|
|
1534
|
-
reasoningTokens: call.reasoningTokens,
|
|
1535
|
-
costUsd: call.costUsdNanos / USD_NANOS,
|
|
1536
|
-
attributes: {
|
|
1537
|
-
"tangle.protected_model.source": "router-settlement",
|
|
1538
|
-
"tangle.router.call_id": call.callId,
|
|
1539
|
-
"tangle.router.generation_id": call.generationId
|
|
1540
|
-
}
|
|
1541
|
-
});
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
/** Match every protected trace span one-for-one against gateway call evidence. */
|
|
1545
|
-
function assertTraceMatchesModelSettlement(spans, settlement) {
|
|
1546
|
-
if (spans.length !== settlement.value.calls.length) throw new Error(`protected trace model calls ${spans.length} do not match model ledger ${settlement.value.calls.length}`);
|
|
1547
|
-
const byId = new Map(spans.map((span) => [span.spanId, span]));
|
|
1548
|
-
if (byId.size !== spans.length) throw new Error("protected trace has duplicate model span ids");
|
|
1549
|
-
for (const call of settlement.value.calls) {
|
|
1550
|
-
const span = byId.get(call.traceSpanId);
|
|
1551
|
-
if (!span) throw new Error(`protected trace is missing model ledger span ${call.traceSpanId}`);
|
|
1552
|
-
assertTraceCall(span, call);
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
function usdToNanos(value, label) {
|
|
1556
|
-
if (!Number.isFinite(value) || value < 0) throw new Error(`${label} must be nonnegative`);
|
|
1557
|
-
const nanos = Math.round(value * USD_NANOS);
|
|
1558
|
-
if (!Number.isSafeInteger(nanos)) throw new Error(`${label} exceeds fixed-point range`);
|
|
1559
|
-
return nanos;
|
|
1560
|
-
}
|
|
1561
|
-
function assertTraceCall(span, call) {
|
|
1562
|
-
if (span.model !== call.model) throw new Error(`protected trace span ${span.spanId} model does not match model ledger`);
|
|
1563
|
-
if (span.startedAt !== call.startedAtMs || span.endedAt !== call.endedAtMs || span.status !== (call.status === "succeeded" ? "ok" : "error")) throw new Error(`protected trace span ${span.spanId} timing or status does not match model ledger`);
|
|
1564
|
-
if (span.attributes?.["tangle.protected_model.source"] !== "router-settlement" || span.attributes?.["tangle.router.call_id"] !== call.callId || span.attributes?.["tangle.router.generation_id"] !== call.generationId) throw new Error(`protected trace span ${span.spanId} lacks router settlement provenance`);
|
|
1565
|
-
for (const [name, traced, settled] of [
|
|
1566
|
-
[
|
|
1567
|
-
"inputTokens",
|
|
1568
|
-
span.inputTokens,
|
|
1569
|
-
call.inputTokens
|
|
1570
|
-
],
|
|
1571
|
-
[
|
|
1572
|
-
"outputTokens",
|
|
1573
|
-
span.outputTokens,
|
|
1574
|
-
call.outputTokens
|
|
1575
|
-
],
|
|
1576
|
-
[
|
|
1577
|
-
"cachedInputTokens",
|
|
1578
|
-
span.cachedTokens ?? 0,
|
|
1579
|
-
call.cachedInputTokens
|
|
1580
|
-
],
|
|
1581
|
-
[
|
|
1582
|
-
"reasoningTokens",
|
|
1583
|
-
span.reasoningTokens ?? 0,
|
|
1584
|
-
call.reasoningTokens
|
|
1585
|
-
]
|
|
1586
|
-
]) if (traced === void 0 || traced !== settled) throw new Error(`protected trace span ${span.spanId} ${name} ${traced} does not match model ledger ${settled}`);
|
|
1587
|
-
if (span.costUsd === void 0) throw new Error(`protected trace span ${span.spanId} is missing costUsd`);
|
|
1588
|
-
const tracedCost = usdToNanos(span.costUsd, `protected trace span ${span.spanId} costUsd`);
|
|
1589
|
-
if (tracedCost !== call.costUsdNanos) throw new Error(`protected trace span ${span.spanId} costUsdNanos ${tracedCost} does not match model ledger ${call.costUsdNanos}`);
|
|
1590
|
-
}
|
|
1591
|
-
function assertIdentifier(value, label) {
|
|
1592
|
-
if (typeof value !== "string" || value.length === 0 || value.length > 256) throw new Error(`${label} must be a non-empty bounded string`);
|
|
1593
|
-
}
|
|
1594
|
-
function assertCount(value, label) {
|
|
1595
|
-
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${label} must be a nonnegative safe integer`);
|
|
1596
|
-
}
|
|
1597
|
-
function assertTimestamp(value, label) {
|
|
1598
|
-
if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${label} must be a positive safe integer`);
|
|
2404
|
+
if (typeof capture.resultTree !== "string" || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(capture.resultTree)) throw new Error("candidate task capture resultTree is not a Git object id");
|
|
2405
|
+
if (!(capture.archive instanceof Uint8Array) || !(capture.gitDiff instanceof Uint8Array)) throw new Error("candidate task capture archive and gitDiff must be byte arrays");
|
|
2406
|
+
const afterState = agentCandidateWorkspaceManifestMaterialSchema.parse(capture.afterState);
|
|
2407
|
+
return Object.freeze({
|
|
2408
|
+
kind: "workspace",
|
|
2409
|
+
resultTree: capture.resultTree,
|
|
2410
|
+
afterState: Object.freeze(afterState),
|
|
2411
|
+
archive: Uint8Array.from(capture.archive),
|
|
2412
|
+
gitDiff: Uint8Array.from(capture.gitDiff)
|
|
2413
|
+
});
|
|
1599
2414
|
}
|
|
1600
|
-
function
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
return total;
|
|
2415
|
+
function requireRecord(value, label) {
|
|
2416
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
2417
|
+
return value;
|
|
1604
2418
|
}
|
|
1605
2419
|
//#endregion
|
|
1606
2420
|
//#region src/candidate-execution/finalize.ts
|
|
@@ -2286,6 +3100,22 @@ function normalizeEvaluation(evaluation, termination) {
|
|
|
2286
3100
|
function assertUnitScore(value, label) {
|
|
2287
3101
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) throw new Error(`${label} must be finite and within [0, 1]`);
|
|
2288
3102
|
}
|
|
3103
|
+
async function persistFailureEvidence(state, reason, failureClass, termination, outputArtifacts) {
|
|
3104
|
+
const bytes = canonicalCandidateBytes$1({
|
|
3105
|
+
kind: "agent-candidate-execution-failure",
|
|
3106
|
+
executionId: state.executionId,
|
|
3107
|
+
bundleDigest: state.bundle.digest,
|
|
3108
|
+
executionPlanDigest: state.executionPlan.value.digest,
|
|
3109
|
+
failureClass,
|
|
3110
|
+
reason,
|
|
3111
|
+
...termination ? { termination } : {}
|
|
3112
|
+
});
|
|
3113
|
+
return await persistCandidateOutputArtifact(outputArtifacts, {
|
|
3114
|
+
executionId: state.executionId,
|
|
3115
|
+
purpose: "failure-evidence",
|
|
3116
|
+
bytes
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
2289
3119
|
//#endregion
|
|
2290
3120
|
//#region src/candidate-execution/protected-trace-store.ts
|
|
2291
3121
|
/** Keep the identity key in evaluator-owned closures and expose only the redacting store to code. */
|
|
@@ -2337,55 +3167,6 @@ function createProtectedAgentCandidateTraceAccess(inner, protectedValues) {
|
|
|
2337
3167
|
report: () => redactor.report()
|
|
2338
3168
|
});
|
|
2339
3169
|
}
|
|
2340
|
-
/** Recovery can read existing trace state but must never accept new unredactable writes. */
|
|
2341
|
-
var RecoveryAgentCandidateTraceStore = class {
|
|
2342
|
-
inner;
|
|
2343
|
-
constructor(inner) {
|
|
2344
|
-
this.inner = inner;
|
|
2345
|
-
}
|
|
2346
|
-
appendRun() {
|
|
2347
|
-
return this.rejectWrite();
|
|
2348
|
-
}
|
|
2349
|
-
updateRun() {
|
|
2350
|
-
return this.rejectWrite();
|
|
2351
|
-
}
|
|
2352
|
-
appendSpan() {
|
|
2353
|
-
return this.rejectWrite();
|
|
2354
|
-
}
|
|
2355
|
-
updateSpan() {
|
|
2356
|
-
return this.rejectWrite();
|
|
2357
|
-
}
|
|
2358
|
-
appendEvent() {
|
|
2359
|
-
return this.rejectWrite();
|
|
2360
|
-
}
|
|
2361
|
-
appendArtifact() {
|
|
2362
|
-
return this.rejectWrite();
|
|
2363
|
-
}
|
|
2364
|
-
appendBudgetEntry() {
|
|
2365
|
-
return this.rejectWrite();
|
|
2366
|
-
}
|
|
2367
|
-
getRun(...args) {
|
|
2368
|
-
return this.inner.getRun(...args);
|
|
2369
|
-
}
|
|
2370
|
-
listRuns(...args) {
|
|
2371
|
-
return this.inner.listRuns(...args);
|
|
2372
|
-
}
|
|
2373
|
-
spans(...args) {
|
|
2374
|
-
return this.inner.spans(...args);
|
|
2375
|
-
}
|
|
2376
|
-
events(...args) {
|
|
2377
|
-
return this.inner.events(...args);
|
|
2378
|
-
}
|
|
2379
|
-
budget(...args) {
|
|
2380
|
-
return this.inner.budget(...args);
|
|
2381
|
-
}
|
|
2382
|
-
artifacts(...args) {
|
|
2383
|
-
return this.inner.artifacts(...args);
|
|
2384
|
-
}
|
|
2385
|
-
rejectWrite() {
|
|
2386
|
-
return Promise.reject(/* @__PURE__ */ new Error("expired candidate recovery cannot append trace evidence"));
|
|
2387
|
-
}
|
|
2388
|
-
};
|
|
2389
3170
|
//#endregion
|
|
2390
3171
|
//#region src/candidate-execution/execute.ts
|
|
2391
3172
|
/** Executes and finalizes one durably claimed candidate without exposing an unproven result. */
|
|
@@ -2791,22 +3572,6 @@ async function finishClaim(store, lease, terminal, deadlineAtMs) {
|
|
|
2791
3572
|
return false;
|
|
2792
3573
|
}
|
|
2793
3574
|
}
|
|
2794
|
-
async function persistFailureEvidence(state, reason, failureClass, termination, outputArtifacts) {
|
|
2795
|
-
const bytes = canonicalCandidateBytes$1({
|
|
2796
|
-
kind: "agent-candidate-execution-failure",
|
|
2797
|
-
executionId: state.executionId,
|
|
2798
|
-
bundleDigest: state.bundle.digest,
|
|
2799
|
-
executionPlanDigest: state.executionPlan.value.digest,
|
|
2800
|
-
failureClass,
|
|
2801
|
-
reason,
|
|
2802
|
-
...termination ? { termination } : {}
|
|
2803
|
-
});
|
|
2804
|
-
return await persistCandidateOutputArtifact(outputArtifacts, {
|
|
2805
|
-
executionId: state.executionId,
|
|
2806
|
-
purpose: "failure-evidence",
|
|
2807
|
-
bytes
|
|
2808
|
-
});
|
|
2809
|
-
}
|
|
2810
3575
|
function protectedEnvironmentValues(activation, memoryActivation) {
|
|
2811
3576
|
return [...Object.values(activation?.env ?? {}), ...Object.values(memoryActivation?.env ?? {})];
|
|
2812
3577
|
}
|
|
@@ -2820,171 +3585,6 @@ var CandidateExecutionDeadlineError = class extends Error {
|
|
|
2820
3585
|
}
|
|
2821
3586
|
};
|
|
2822
3587
|
//#endregion
|
|
2823
|
-
//#region src/candidate-execution/verify.ts
|
|
2824
|
-
const verifiedCandidateState = /* @__PURE__ */ new WeakMap();
|
|
2825
|
-
/** Surfaces admitted by Runtime's verifier before an environment adapter is selected. */
|
|
2826
|
-
const AGENT_CANDIDATE_EXECUTION_SUPPORT = Object.freeze({
|
|
2827
|
-
outcomes: Object.freeze(["workspace", "output"]),
|
|
2828
|
-
code: Object.freeze([
|
|
2829
|
-
"disabled",
|
|
2830
|
-
"no-op",
|
|
2831
|
-
"git-patch"
|
|
2832
|
-
]),
|
|
2833
|
-
memory: Object.freeze(["disabled", "isolated"]),
|
|
2834
|
-
knowledge: true,
|
|
2835
|
-
profile: Object.freeze({
|
|
2836
|
-
mcpTransports: Object.freeze(["stdio"]),
|
|
2837
|
-
remoteMcp: false,
|
|
2838
|
-
tools: false,
|
|
2839
|
-
permissions: false,
|
|
2840
|
-
modes: false,
|
|
2841
|
-
confidential: false
|
|
2842
|
-
})
|
|
2843
|
-
});
|
|
2844
|
-
/** Verifies every digest, resource, workspace, and Git object in a candidate bundle. */
|
|
2845
|
-
async function verifyAgentCandidateBundle(input, ports) {
|
|
2846
|
-
const parsed = agentCandidateBundleSchema.parse(input);
|
|
2847
|
-
const withoutDigest = omitTopLevelDigest(parsed);
|
|
2848
|
-
const actualDigest = canonicalCandidateDigest$1(withoutDigest);
|
|
2849
|
-
if (actualDigest !== parsed.digest) throw new Error(`candidate bundle digest ${parsed.digest} does not match ${actualDigest}`);
|
|
2850
|
-
const canonicalBytes = canonicalCandidateBytes$1(withoutDigest);
|
|
2851
|
-
verifyBytes(canonicalBytes, parsed.digest, canonicalBytes.byteLength, "candidate bundle");
|
|
2852
|
-
assertCandidateProfileExecutionSupport(parsed.profile);
|
|
2853
|
-
const artifactBytes = /* @__PURE__ */ new Map();
|
|
2854
|
-
const readArtifact = async (artifact) => {
|
|
2855
|
-
const key = artifactCacheKey(artifact);
|
|
2856
|
-
const existing = artifactBytes.get(key);
|
|
2857
|
-
if (existing) return Uint8Array.from(existing);
|
|
2858
|
-
const bytes = await readVerifiedArtifact(artifact, ports.artifacts);
|
|
2859
|
-
artifactBytes.set(key, Uint8Array.from(bytes));
|
|
2860
|
-
return bytes;
|
|
2861
|
-
};
|
|
2862
|
-
let patchBytes;
|
|
2863
|
-
if (parsed.code.kind === "git-patch") patchBytes = await readArtifact(parsed.code.patch.artifact);
|
|
2864
|
-
const materializedTree = await verifyCandidateCode(parsed.code, ports.repositories, patchBytes);
|
|
2865
|
-
const resourceBytes = /* @__PURE__ */ new Map();
|
|
2866
|
-
for (const resource of candidateResources(parsed)) {
|
|
2867
|
-
const bytes = resource.kind === "inline" ? Buffer.from(resource.content, "utf8") : await readCandidateGitHubResource(resource, ports.repositories);
|
|
2868
|
-
verifyBytes(bytes, resource.sha256, resource.byteLength, `candidate resource ${resource.name ?? (resource.kind === "github" ? resource.path : "<unnamed>")}`);
|
|
2869
|
-
resourceBytes.set(resourceKey(resource), Uint8Array.from(bytes));
|
|
2870
|
-
resourceBytes.set(resource.sha256, Uint8Array.from(bytes));
|
|
2871
|
-
}
|
|
2872
|
-
if (parsed.execution.workspace) {
|
|
2873
|
-
const workspace = await verifyWorkspaceSnapshotArtifacts(parsed.execution.workspace, ports.artifacts);
|
|
2874
|
-
artifactBytes.set(artifactCacheKey(parsed.execution.workspace.manifest), workspace.manifest);
|
|
2875
|
-
artifactBytes.set(artifactCacheKey(parsed.execution.workspace.archive), workspace.archive);
|
|
2876
|
-
}
|
|
2877
|
-
if (parsed.knowledge) {
|
|
2878
|
-
const knowledge = await verifyWorkspaceSnapshotArtifacts(parsed.knowledge.snapshot, ports.artifacts);
|
|
2879
|
-
artifactBytes.set(artifactCacheKey(parsed.knowledge.snapshot.manifest), knowledge.manifest);
|
|
2880
|
-
artifactBytes.set(artifactCacheKey(parsed.knowledge.snapshot.archive), knowledge.archive);
|
|
2881
|
-
if (parsed.knowledge.retrievalConfig) await readArtifact(parsed.knowledge.retrievalConfig);
|
|
2882
|
-
await readArtifact(parsed.knowledge.evaluation);
|
|
2883
|
-
}
|
|
2884
|
-
if (parsed.memory.mode === "isolated" && parsed.memory.seed) await readArtifact(parsed.memory.seed);
|
|
2885
|
-
deepFreezeCandidate(parsed);
|
|
2886
|
-
const verified = Object.freeze({
|
|
2887
|
-
bundle: parsed,
|
|
2888
|
-
...materializedTree === void 0 ? {} : { materializedTree },
|
|
2889
|
-
[verifiedCandidateBrand]: true
|
|
2890
|
-
});
|
|
2891
|
-
verifiedCandidateState.set(verified, {
|
|
2892
|
-
ports,
|
|
2893
|
-
artifactBytes,
|
|
2894
|
-
resourceBytes
|
|
2895
|
-
});
|
|
2896
|
-
return verified;
|
|
2897
|
-
}
|
|
2898
|
-
function getVerifiedCandidateState(candidate) {
|
|
2899
|
-
const state = verifiedCandidateState.get(candidate);
|
|
2900
|
-
if (!state || candidate[verifiedCandidateBrand] !== true) throw new Error("candidate must come from verifyAgentCandidateBundle");
|
|
2901
|
-
return state;
|
|
2902
|
-
}
|
|
2903
|
-
async function verifiedArtifactBytes(candidate, artifact) {
|
|
2904
|
-
const state = getVerifiedCandidateState(candidate);
|
|
2905
|
-
const key = artifactCacheKey(artifact);
|
|
2906
|
-
const existing = state.artifactBytes.get(key);
|
|
2907
|
-
if (existing) return Uint8Array.from(existing);
|
|
2908
|
-
const bytes = await readVerifiedArtifact(artifact, state.ports.artifacts);
|
|
2909
|
-
state.artifactBytes.set(key, Uint8Array.from(bytes));
|
|
2910
|
-
return bytes;
|
|
2911
|
-
}
|
|
2912
|
-
function verifiedResourceTextByDigest(candidate) {
|
|
2913
|
-
const output = /* @__PURE__ */ new Map();
|
|
2914
|
-
for (const resource of candidateResources(candidate.bundle)) {
|
|
2915
|
-
const bytes = getVerifiedCandidateState(candidate).resourceBytes.get(resource.sha256);
|
|
2916
|
-
if (!bytes) throw new Error(`candidate resource digest was not verified: ${resource.sha256}`);
|
|
2917
|
-
output.set(resource.sha256, new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
2918
|
-
}
|
|
2919
|
-
return output;
|
|
2920
|
-
}
|
|
2921
|
-
function candidateResources(bundle) {
|
|
2922
|
-
const resources = bundle.profile.resources;
|
|
2923
|
-
if (!resources) return [];
|
|
2924
|
-
const output = [];
|
|
2925
|
-
for (const mount of resources.files ?? []) output.push(mount.resource);
|
|
2926
|
-
output.push(...resources.tools ?? []);
|
|
2927
|
-
output.push(...resources.skills ?? []);
|
|
2928
|
-
output.push(...resources.agents ?? []);
|
|
2929
|
-
output.push(...resources.commands ?? []);
|
|
2930
|
-
if (typeof resources.instructions === "object") output.push(resources.instructions);
|
|
2931
|
-
return output;
|
|
2932
|
-
}
|
|
2933
|
-
function resourceKey(resource) {
|
|
2934
|
-
return canonicalCandidateDigest$1(resource);
|
|
2935
|
-
}
|
|
2936
|
-
//#endregion
|
|
2937
|
-
//#region src/candidate-execution/knowledge.ts
|
|
2938
|
-
/** Environment variable containing the materialized candidate knowledge root. */
|
|
2939
|
-
const CANDIDATE_KNOWLEDGE_ROOT_ENV = "TANGLE_CANDIDATE_KNOWLEDGE_ROOT";
|
|
2940
|
-
/** Environment variable containing the materialized retrieval configuration path. */
|
|
2941
|
-
const CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV = "TANGLE_CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG";
|
|
2942
|
-
/** Deterministic, signed locations used by every candidate executor. */
|
|
2943
|
-
function candidateKnowledgeExecutionPaths(taskRoot, hasRetrievalConfig) {
|
|
2944
|
-
if (!posix.isAbsolute(taskRoot) || posix.normalize(taskRoot) !== taskRoot) throw new Error("candidate knowledge requires a normalized absolute task root");
|
|
2945
|
-
const parent = posix.join(taskRoot, ".tangle");
|
|
2946
|
-
return {
|
|
2947
|
-
root: posix.join(parent, "knowledge"),
|
|
2948
|
-
...hasRetrievalConfig ? { retrievalConfig: posix.join(parent, "knowledge-retrieval-config.json") } : {}
|
|
2949
|
-
};
|
|
2950
|
-
}
|
|
2951
|
-
/** Verify and detach the exact file-backed knowledge admitted by the bundle. */
|
|
2952
|
-
async function prepareAgentCandidateKnowledge(candidate, ports, arm) {
|
|
2953
|
-
const knowledge = candidate.bundle.knowledge;
|
|
2954
|
-
if (!knowledge) return void 0;
|
|
2955
|
-
const archive = await verifiedArtifactBytes(candidate, knowledge.snapshot.archive);
|
|
2956
|
-
const retrievalConfig = knowledge.retrievalConfig ? await verifiedArtifactBytes(candidate, knowledge.retrievalConfig) : void 0;
|
|
2957
|
-
const root = await mkdtemp(join(tmpdir(), "agent-candidate-knowledge-"));
|
|
2958
|
-
try {
|
|
2959
|
-
await ports.workspaces.materialize({
|
|
2960
|
-
role: "knowledge",
|
|
2961
|
-
snapshot: knowledge.snapshot,
|
|
2962
|
-
archive,
|
|
2963
|
-
destination: root
|
|
2964
|
-
});
|
|
2965
|
-
const expectedHash = arm === "baseline" ? knowledge.candidate.baseHash : knowledge.candidate.candidateHash;
|
|
2966
|
-
const actualHash = `sha256:${await hashKnowledgeBase(root, knowledge.stateScope)}`;
|
|
2967
|
-
if (actualHash !== expectedHash) throw new Error(`materialized ${arm} knowledge does not match its measured content: expected ${expectedHash}, got ${actualHash}`);
|
|
2968
|
-
const files = await readMaterializedWorkspaceFiles(root, knowledge.snapshot.material);
|
|
2969
|
-
return Object.freeze({
|
|
2970
|
-
candidate: knowledge.candidate,
|
|
2971
|
-
...knowledge.stateScope ? { stateScope: Object.freeze({ ...knowledge.stateScope }) } : {},
|
|
2972
|
-
snapshot: knowledge.snapshot,
|
|
2973
|
-
files: Object.freeze(files.map((file) => Object.freeze({
|
|
2974
|
-
path: file.path,
|
|
2975
|
-
mode: file.mode,
|
|
2976
|
-
bytes: Uint8Array.from(file.bytes)
|
|
2977
|
-
}))),
|
|
2978
|
-
...retrievalConfig ? { retrievalConfig: Uint8Array.from(retrievalConfig) } : {}
|
|
2979
|
-
});
|
|
2980
|
-
} finally {
|
|
2981
|
-
await rm(root, {
|
|
2982
|
-
recursive: true,
|
|
2983
|
-
force: true
|
|
2984
|
-
});
|
|
2985
|
-
}
|
|
2986
|
-
}
|
|
2987
|
-
//#endregion
|
|
2988
3588
|
//#region src/candidate-execution/prepare.ts
|
|
2989
3589
|
const MIN_RESERVATION_TTL_MS = 15 * 6e4;
|
|
2990
3590
|
const PREPARED_HOLD_MARGIN_MS = 5 * 6e4;
|
|
@@ -3454,32 +4054,6 @@ async function prepareMemory(candidate, task, ports, preparationId, expiresAtMs,
|
|
|
3454
4054
|
throw error;
|
|
3455
4055
|
}
|
|
3456
4056
|
}
|
|
3457
|
-
function buildLaunch(candidate, task, profileFlags) {
|
|
3458
|
-
const launch = candidate.bundle.execution.launch;
|
|
3459
|
-
if (launch.kind === "container-command") return {
|
|
3460
|
-
executable: launch.executable,
|
|
3461
|
-
args: [...launch.args ?? [], ...profileFlags]
|
|
3462
|
-
};
|
|
3463
|
-
const candidateRoot = task.executionRoots.candidateRoot;
|
|
3464
|
-
if (!candidateRoot) throw new Error("candidate entrypoint requires a container candidate root");
|
|
3465
|
-
const entrypoint = posix.join(candidateRoot, launch.entrypoint);
|
|
3466
|
-
const candidateArgs = launch.args ?? [];
|
|
3467
|
-
if (launch.interpreter) return {
|
|
3468
|
-
executable: launch.interpreter,
|
|
3469
|
-
args: [
|
|
3470
|
-
{
|
|
3471
|
-
kind: "public",
|
|
3472
|
-
value: entrypoint
|
|
3473
|
-
},
|
|
3474
|
-
...candidateArgs,
|
|
3475
|
-
...profileFlags
|
|
3476
|
-
]
|
|
3477
|
-
};
|
|
3478
|
-
return {
|
|
3479
|
-
executable: entrypoint,
|
|
3480
|
-
args: [...candidateArgs, ...profileFlags]
|
|
3481
|
-
};
|
|
3482
|
-
}
|
|
3483
4057
|
function mergePublicEnvironment(...records) {
|
|
3484
4058
|
const output = {};
|
|
3485
4059
|
for (const record of records) for (const [name, value] of Object.entries(record)) {
|
|
@@ -3493,50 +4067,6 @@ function mergePublicEnvironment(...records) {
|
|
|
3493
4067
|
function unwrapPublicEnvironment(values) {
|
|
3494
4068
|
return Object.fromEntries(Object.entries(values).map(([name, value]) => [name, requirePublicCandidateConfigValue(value, `env.${name}`)]));
|
|
3495
4069
|
}
|
|
3496
|
-
function modelRoutes(profile, requested) {
|
|
3497
|
-
const routes = [{
|
|
3498
|
-
kind: "primary",
|
|
3499
|
-
requested
|
|
3500
|
-
}];
|
|
3501
|
-
if (profile.model?.small) routes.push({
|
|
3502
|
-
kind: "small",
|
|
3503
|
-
requested
|
|
3504
|
-
});
|
|
3505
|
-
for (const name of Object.keys(profile.modes ?? {}).sort()) if (profile.modes?.[name]?.model) routes.push({
|
|
3506
|
-
kind: "mode",
|
|
3507
|
-
name,
|
|
3508
|
-
requested
|
|
3509
|
-
});
|
|
3510
|
-
for (const name of Object.keys(profile.subagents ?? {}).sort()) if (profile.subagents?.[name]?.model) routes.push({
|
|
3511
|
-
kind: "subagent",
|
|
3512
|
-
name,
|
|
3513
|
-
requested
|
|
3514
|
-
});
|
|
3515
|
-
return routes;
|
|
3516
|
-
}
|
|
3517
|
-
function candidateEntrypointReceipt(candidate) {
|
|
3518
|
-
const launch = candidate.bundle.execution.launch;
|
|
3519
|
-
const workspace = candidate.bundle.execution.workspace;
|
|
3520
|
-
if (launch.kind !== "candidate-entrypoint" || !workspace) return void 0;
|
|
3521
|
-
const file = workspace.material.files.find((entry) => entry.path === launch.entrypoint);
|
|
3522
|
-
if (!file) throw new Error("candidate entrypoint is absent from the verified workspace");
|
|
3523
|
-
return {
|
|
3524
|
-
path: file.path,
|
|
3525
|
-
sha256: file.sha256,
|
|
3526
|
-
byteLength: file.byteLength
|
|
3527
|
-
};
|
|
3528
|
-
}
|
|
3529
|
-
function absoluteExecutionCwd(cwd, roots) {
|
|
3530
|
-
const root = workspaceExecutionRoot(cwd.workspace, roots);
|
|
3531
|
-
const absolute = cwd.path === "." ? root : posix.join(root, cwd.path);
|
|
3532
|
-
if (absolute !== root && !absolute.startsWith(`${root}/`)) throw new Error("candidate cwd escapes its execution workspace");
|
|
3533
|
-
return absolute;
|
|
3534
|
-
}
|
|
3535
|
-
function workspaceExecutionRoot(workspace, roots) {
|
|
3536
|
-
const root = workspace === "task" ? roots.taskRoot : roots.candidateRoot;
|
|
3537
|
-
if (!root) throw new Error(`candidate ${workspace} workspace root is missing`);
|
|
3538
|
-
return root;
|
|
3539
|
-
}
|
|
3540
4070
|
function validateProtectedModelReservation(reservation, expectedLimits, preparationId, expiresAtMs) {
|
|
3541
4071
|
if (!/^sha256:[a-f0-9]{64}$/.test(reservation.digest)) throw new Error("protected model reservation has an invalid identity digest");
|
|
3542
4072
|
if (reservation.preparationId !== preparationId || reservation.expiresAtMs !== expiresAtMs) throw new Error("protected model reservation is not scoped to this preparation");
|
|
@@ -3552,34 +4082,7 @@ function assertEnvironmentDisjoint(publicEnv, traceEnv) {
|
|
|
3552
4082
|
seen.add(name);
|
|
3553
4083
|
}
|
|
3554
4084
|
}
|
|
3555
|
-
function modelLimits(limits) {
|
|
3556
|
-
return {
|
|
3557
|
-
maxModelCalls: limits.maxModelCalls,
|
|
3558
|
-
maxInputTokens: limits.maxInputTokens,
|
|
3559
|
-
maxOutputTokens: limits.maxOutputTokens,
|
|
3560
|
-
maxCostUsd: limits.maxCostUsd
|
|
3561
|
-
};
|
|
3562
|
-
}
|
|
3563
|
-
function exactProfileExecutorFiles(sourceFiles, expectedFiles) {
|
|
3564
|
-
const byIdentity = new Map(sourceFiles.map((file) => [profileFileIdentity(file.root, file.relPath), file]));
|
|
3565
|
-
if (byIdentity.size !== sourceFiles.length || sourceFiles.length !== expectedFiles.length) throw new Error("profile source files do not match the signed profile plan");
|
|
3566
|
-
return expectedFiles.map((expected) => {
|
|
3567
|
-
const source = byIdentity.get(profileFileIdentity(expected.root, expected.relPath));
|
|
3568
|
-
const mode = source?.mode ?? 420;
|
|
3569
|
-
const bytes = Buffer.from(source?.content ?? "", "utf8");
|
|
3570
|
-
if (!source || !Number.isInteger(mode) || mode < 0 || mode > 511 || mode !== expected.mode || sha256Bytes$1(bytes) !== expected.contentSha256) throw new Error("profile source files do not match the signed profile plan");
|
|
3571
|
-
return {
|
|
3572
|
-
path: expected.relPath,
|
|
3573
|
-
mode,
|
|
3574
|
-
...expected.root === "agent" ? { root: "agent" } : {},
|
|
3575
|
-
bytes: Uint8Array.from(bytes)
|
|
3576
|
-
};
|
|
3577
|
-
});
|
|
3578
|
-
}
|
|
3579
|
-
function profileFileIdentity(root, relPath) {
|
|
3580
|
-
return `${root ?? "workspace"}\0${relPath}`;
|
|
3581
|
-
}
|
|
3582
4085
|
//#endregion
|
|
3583
|
-
export {
|
|
4086
|
+
export { verifiedResourceTextByDigest as C, AGENT_CANDIDATE_EXECUTION_SUPPORT as S, sealTerminalDigest as _, consumePreparedCandidateExecution as a, sealAgentCandidateModelSettlement as b, withinCandidateCleanupDeadline as c, assertTerminalMatchesClaim as d, assertTerminalMatchesStaged as f, requireStagedTerminal as g, rejectedStage as h, beginPreparedCandidateDisposal as i, assertRecoveryMatchesStaged as l, rejectedFinish as m, executePreparedAgentCandidate as n, candidateClaimFileInternals as o, recoveredTerminalRecord as p, assertPreparedCandidateIntegrity as r, candidateCleanupDeadline as s, prepareAgentCandidateExecution as t, assertTerminalAllowedInPhase as u, terminalRecord as v, verifyAgentCandidateBundle as w, exactProcessProviderAsCandidateExecutor as x, createProtectedAgentCandidateModelPort as y };
|
|
3584
4087
|
|
|
3585
|
-
//# sourceMappingURL=prepare-
|
|
4088
|
+
//# sourceMappingURL=prepare-bXkvPMJA.js.map
|