@tangle-network/agent-runtime 0.188.1 → 0.190.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-C39nGToq.js → activation-BgRWFKZi.js} +3 -3
- package/dist/{activation-C39nGToq.js.map → activation-BgRWFKZi.js.map} +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +3 -3
- package/dist/{authoring-fsUwd9hg.js → authoring-dQQ2z1fT.js} +2 -2
- package/dist/{authoring-fsUwd9hg.js.map → authoring-dQQ2z1fT.js.map} +1 -1
- package/dist/candidate-execution/index.d.ts +4 -4
- package/dist/candidate-execution/index.js +5 -5
- package/dist/{candidate-execution-CehLeZmx.js → candidate-execution-BSkZErxD.js} +255 -9
- package/dist/candidate-execution-BSkZErxD.js.map +1 -0
- package/dist/conversation.d.ts +1 -1
- package/dist/{coordination-driver-CtbyZv5d.js → coordination-driver-Da1IGdTP.js} +6 -6
- package/dist/coordination-driver-Da1IGdTP.js.map +1 -0
- package/dist/{durable-file-g3YdfEh4.js → durable-file-D24y9zg7.js} +2 -2
- package/dist/{durable-file-g3YdfEh4.js.map → durable-file-D24y9zg7.js.map} +1 -1
- package/dist/durable.d.ts +2 -2
- package/dist/durable.js +3 -3
- package/dist/{environment-provider-C4AQiUxj.d.ts → environment-provider-Bf-hgR6p.d.ts} +2 -2
- package/dist/{environment-provider-DZxuH3E4.js → environment-provider-DcelIawX.js} +2 -2
- package/dist/{environment-provider-DZxuH3E4.js.map → environment-provider-DcelIawX.js.map} +1 -1
- package/dist/environment-provider.d.ts +1 -1
- package/dist/environment-provider.js +1 -1
- package/dist/{graph-Cox_7pht.js → graph-Bd1elhdL.js} +5 -3
- package/dist/graph-Bd1elhdL.js.map +1 -0
- package/dist/graph.d.ts +3 -3
- package/dist/graph.js +5 -5
- package/dist/{improvement-cycle-CnHGNT5r.js → improvement-cycle-BanlFMyL.js} +7 -7
- package/dist/{improvement-cycle-CnHGNT5r.js.map → improvement-cycle-BanlFMyL.js.map} +1 -1
- package/dist/{index-DdmozvB8.d.ts → index-C-gBk5M9.d.ts} +5 -5
- package/dist/{index-DQv7iCeE.d.ts → index-CUosKU4N.d.ts} +145 -5
- package/dist/{index-DA9jKuJc.d.ts → index-Clb7_qML.d.ts} +2 -2
- package/dist/{index-DVob1IA1.d.ts → index-DpNil9jh.d.ts} +130 -56
- package/dist/index.d.ts +11 -11
- package/dist/index.js +12 -12
- package/dist/intelligence.d.ts +6 -6
- package/dist/intelligence.js +6 -6
- package/dist/kernel.d.ts +6 -6
- package/dist/kernel.js +12 -12
- package/dist/{knowledge-BZDVXRCl.js → knowledge-BXdXlv48.js} +5 -5
- package/dist/{knowledge-BZDVXRCl.js.map → knowledge-BXdXlv48.js.map} +1 -1
- package/dist/knowledge.d.ts +1 -1
- package/dist/knowledge.js +1 -1
- package/dist/{loop-runner-bin-hXqgNrPN.js → loop-runner-bin-CGQzzWAc.js} +3 -3
- package/dist/{loop-runner-bin-hXqgNrPN.js.map → loop-runner-bin-CGQzzWAc.js.map} +1 -1
- package/dist/{loop-runner-bin-g9fWDuOh.d.ts → loop-runner-bin-Cy4f9M6p.d.ts} +3 -3
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +3 -3
- package/dist/mcp/index.js +7 -7
- package/dist/mcp/memory-bin.js +1 -1
- package/dist/{memory-server-DsKOhzGf.js → memory-server-Crkevawz.js} +2 -2
- package/dist/{memory-server-DsKOhzGf.js.map → memory-server-Crkevawz.js.map} +1 -1
- package/dist/{openai-tools-Cz04RKfD.js → openai-tools-BSDk_umV.js} +2 -2
- package/dist/{openai-tools-Cz04RKfD.js.map → openai-tools-BSDk_umV.js.map} +1 -1
- package/dist/{prepare-BpIGs5Tc.js → prepare-B5fJ7mLS.js} +336 -246
- package/dist/prepare-B5fJ7mLS.js.map +1 -0
- package/dist/primeintellect/index.d.ts +1 -1
- package/dist/primeintellect/index.js +1 -1
- package/dist/profiles.js +1 -1
- package/dist/{protected-model-port-YclORl0x.js → protected-model-port-B0xFSAjt.js} +19 -7
- package/dist/protected-model-port-B0xFSAjt.js.map +1 -0
- package/dist/{protected-model-port-B8ssMyUg.d.ts → protected-model-port-BtXp2dUY.d.ts} +2 -2
- package/dist/{provision-supervisor-B-_4I1In.js → provision-supervisor-bwpzajiz.js} +8 -8
- package/dist/{provision-supervisor-B-_4I1In.js.map → provision-supervisor-bwpzajiz.js.map} +1 -1
- package/dist/{redact-C9EWk1FT.d.ts → redact-Cv1GfOFO.d.ts} +3 -3
- package/dist/{run-layout-Cd_l2XWV.js → run-layout-B8I_LXN-.js} +3 -3
- package/dist/{run-layout-Cd_l2XWV.js.map → run-layout-B8I_LXN-.js.map} +1 -1
- package/dist/{runtime-EQgE3942.d.ts → runtime-BMWmSu4A.d.ts} +202 -4
- package/dist/{runtime-CFltmoiL.js → runtime-Ddh7PyCy.js} +15 -15
- package/dist/{runtime-CFltmoiL.js.map → runtime-Ddh7PyCy.js.map} +1 -1
- package/dist/{spawn-journal-CxRa-NJ2.js → spawn-journal-B9eTmVAn.js} +2 -2
- package/dist/{spawn-journal-CxRa-NJ2.js.map → spawn-journal-B9eTmVAn.js.map} +1 -1
- package/dist/{stream-agent-turn-BomJ21AK.d.ts → stream-agent-turn-uqzf9ZEh.d.ts} +2 -2
- package/dist/{structural-rollout-C8sB6B4q.js → structural-rollout-0NKvKuFm.js} +3 -3
- package/dist/{structural-rollout-C8sB6B4q.js.map → structural-rollout-0NKvKuFm.js.map} +1 -1
- package/dist/{supervise-BUy1cr2L.js → supervise-yDiZAqlN.js} +372 -26
- package/dist/supervise-yDiZAqlN.js.map +1 -0
- package/dist/{supervisor-BSNkaM2a.js → supervisor-CqJW4qap.js} +489 -11
- package/dist/supervisor-CqJW4qap.js.map +1 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +13 -13
- package/dist/{top-app-DIBApkt1.js → top-app-KgIFpD9H.js} +3 -3
- package/dist/{top-app-DIBApkt1.js.map → top-app-KgIFpD9H.js.map} +1 -1
- package/dist/tui/bin.js +1 -1
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.js +2 -2
- package/dist/{types-BbkiJR1D.d.ts → types-02hXqnSP.d.ts} +37 -2
- package/dist/{types-19ZOddS3.d.ts → types-1s_29INY.d.ts} +15 -10
- package/dist/{util-BfoW6RU7.js → util-mCNUeboW.js} +21 -2
- package/dist/util-mCNUeboW.js.map +1 -0
- package/dist/{workspace-archive-5K72EnlT.js → workspace-archive-yAPDklLV.js} +4 -4
- package/dist/{workspace-archive-5K72EnlT.js.map → workspace-archive-yAPDklLV.js.map} +1 -1
- package/package.json +9 -9
- package/dist/candidate-execution-CehLeZmx.js.map +0 -1
- package/dist/coordination-driver-CtbyZv5d.js.map +0 -1
- package/dist/graph-Cox_7pht.js.map +0 -1
- package/dist/prepare-BpIGs5Tc.js.map +0 -1
- package/dist/protected-model-port-YclORl0x.js.map +0 -1
- package/dist/supervise-BUy1cr2L.js.map +0 -1
- package/dist/supervisor-BSNkaM2a.js.map +0 -1
- package/dist/util-BfoW6RU7.js.map +0 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { x as contentAddress } from "./spawn-journal-
|
|
1
|
+
import { x as contentAddress } from "./spawn-journal-B9eTmVAn.js";
|
|
2
2
|
import { i as redactProtectedValue, n as createProtectedRecordRedactor, r as redactProtectedReason, t as assertNoProtectedBytes } from "./protected-redaction--F3v1oo8.js";
|
|
3
3
|
import { agentCandidateArtifactRefSchema, agentCandidateBenchmarkResultEvidenceSchema, agentCandidateBenchmarkSuiteSchema, agentCandidateBenchmarkTaskSchema, agentCandidateBundleSchema, agentCandidateContainerSchema, agentCandidateExecutionLimitsSchema, agentCandidateExecutionPlanEvidenceSchema, agentCandidateExecutionPlanMaterialSchema, agentCandidateMaterializationReceiptSchema, agentCandidateModelAccessNetworkSchema, agentCandidateModelSettlementEvidenceSchema, agentCandidateProfileActivationSchema, agentCandidateProfilePlanEvidenceSchema, agentCandidateProfileSchema, agentCandidateResolvedModelSchema, agentCandidateRunCellSchema, agentCandidateRunReceiptSchema, agentCandidateTaskOutcomeEvidenceSchema, agentCandidateTaskOutcomeSpecSchema, agentCandidateTerminationSchema, agentCandidateWorkspaceManifestMaterialSchema, agentCandidateWorkspaceSnapshotEvidenceSchema, agentProfileDiffSchema, agentProfileSchema, applyAgentProfileDiff, sha256DigestSchema } from "@tangle-network/agent-interface";
|
|
4
4
|
import { REDACTION_VERSION, canonicalJson, isLlmSpan } from "@tangle-network/agent-eval";
|
|
5
5
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
6
|
-
import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
7
|
-
import { applyAgentCandidateWorkspacePlan, isMaterializerHarness, materializeCandidateProfile } from "@tangle-network/agent-profile-materialize";
|
|
8
|
-
import { lstat, mkdir, mkdtemp, open, readFile, readdir, readlink, realpath, rm, stat } from "node:fs/promises";
|
|
9
6
|
import { constants } from "node:fs";
|
|
7
|
+
import { lstat, mkdir, mkdtemp, open, readFile, readdir, readlink, realpath, rm, stat } from "node:fs/promises";
|
|
8
|
+
import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
9
|
+
import { applyAgentCandidateWorkspacePlan, isMaterializerHarness, materializeCandidateProfile, resolvePiRuntimePaths } from "@tangle-network/agent-profile-materialize";
|
|
10
10
|
import { spawn } from "node:child_process";
|
|
11
11
|
import { tmpdir } from "node:os";
|
|
12
12
|
import { hashKnowledgeBase } from "@tangle-network/agent-knowledge";
|
|
@@ -64,6 +64,240 @@ function deepFreezeCandidate(value, seen = /* @__PURE__ */ new Set()) {
|
|
|
64
64
|
return Object.freeze(value);
|
|
65
65
|
}
|
|
66
66
|
//#endregion
|
|
67
|
+
//#region src/candidate-execution/artifacts.ts
|
|
68
|
+
function artifactCacheKey(artifact) {
|
|
69
|
+
return `${artifact.sha256}:${artifact.byteLength}`;
|
|
70
|
+
}
|
|
71
|
+
async function readVerifiedArtifact(artifact, port) {
|
|
72
|
+
const bytes = "content" in artifact ? Buffer.from(artifact.content, "base64") : await port.read(artifact);
|
|
73
|
+
verifyBytes(bytes, artifact.sha256, artifact.byteLength, "candidate artifact");
|
|
74
|
+
return Uint8Array.from(bytes);
|
|
75
|
+
}
|
|
76
|
+
function verifyBytes(bytes, digest, byteLength, label) {
|
|
77
|
+
if (bytes.byteLength !== byteLength) throw new Error(`${label} byte length ${bytes.byteLength} does not match ${byteLength}`);
|
|
78
|
+
const actual = sha256Bytes(bytes);
|
|
79
|
+
if (actual !== digest) throw new Error(`${label} digest ${actual} does not match ${digest}`);
|
|
80
|
+
}
|
|
81
|
+
async function verifyWorkspaceSnapshotArtifacts(snapshot, port) {
|
|
82
|
+
const [manifest, archive] = await Promise.all([readVerifiedArtifact(snapshot.manifest, port), readVerifiedArtifact(snapshot.archive, port)]);
|
|
83
|
+
const canonicalManifest = canonicalCandidateBytes(snapshot.material);
|
|
84
|
+
if (!Buffer.from(manifest).equals(Buffer.from(canonicalManifest))) throw new Error("workspace manifest artifact is not the exact canonical manifest material");
|
|
85
|
+
if (sha256Bytes(canonicalManifest) !== snapshot.digest) throw new Error("workspace snapshot digest does not match its canonical manifest material");
|
|
86
|
+
return {
|
|
87
|
+
manifest,
|
|
88
|
+
archive
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Refuse a materialized workspace whose files, modes, or bytes are not the signed manifest.
|
|
93
|
+
*
|
|
94
|
+
* The scan streams, so the size of the largest file does not decide whether the check can run, and
|
|
95
|
+
* the refusal names the one mismatch a caller can produce on its own: a capture and a verify that
|
|
96
|
+
* disagree about `portableTree`.
|
|
97
|
+
*/
|
|
98
|
+
async function verifyMaterializedWorkspace(root, expected, options = {}) {
|
|
99
|
+
assertWorkspaceManifest(await scanMaterializedWorkspaceManifest(root, options), expected);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The canonical manifest of one materialized workspace, read without holding any file.
|
|
103
|
+
*
|
|
104
|
+
* Every file is digested by streaming, so the size of the largest file does not decide whether the
|
|
105
|
+
* workspace can be described. `FileHandle.readFile` refuses anything above 2 GiB with
|
|
106
|
+
* `ERR_FS_FILE_TOO_LARGE`, which made a workspace holding one such artifact impossible to verify
|
|
107
|
+
* against a manifest it already matched. The digest is sha-256 over the same bytes either way, so
|
|
108
|
+
* a manifest a buffered read produced is reproduced exactly.
|
|
109
|
+
*/
|
|
110
|
+
async function scanMaterializedWorkspaceManifest(root, options = {}) {
|
|
111
|
+
return workspaceManifestFromEntries(await walkWorkspace(root, options, false));
|
|
112
|
+
}
|
|
113
|
+
async function captureMaterializedWorkspace(root, options = {}) {
|
|
114
|
+
const entries = await walkWorkspace(root, options, true);
|
|
115
|
+
return {
|
|
116
|
+
manifest: workspaceManifestFromEntries(entries),
|
|
117
|
+
files: entries.map((entry) => ({
|
|
118
|
+
path: entry.path,
|
|
119
|
+
mode: entry.mode,
|
|
120
|
+
bytes: Uint8Array.from(capturedBytes(entry))
|
|
121
|
+
}))
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/** Capture exact verified regular-file bytes for fresh isolated materialization. */
|
|
125
|
+
async function readMaterializedWorkspaceFiles(root, expected, options = {}) {
|
|
126
|
+
const observed = await captureMaterializedWorkspace(root, options);
|
|
127
|
+
assertWorkspaceManifest(observed.manifest, expected);
|
|
128
|
+
return observed.files.map((file) => Object.freeze({
|
|
129
|
+
path: file.path,
|
|
130
|
+
mode: file.mode,
|
|
131
|
+
bytes: Uint8Array.from(file.bytes)
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Build the canonical manifest for files a caller already holds — the shape a remote executor
|
|
136
|
+
* returns. Pass `portableTree` to record Git's two file modes instead of exact permission bits, and
|
|
137
|
+
* pass the same flag to every verify that reads the result.
|
|
138
|
+
*/
|
|
139
|
+
function candidateWorkspaceManifest(files, options = {}) {
|
|
140
|
+
return workspaceManifestFromEntries(files.map((file) => ({
|
|
141
|
+
path: file.path,
|
|
142
|
+
mode: options.portableTree === true ? portableFileMode(file.mode) : file.mode,
|
|
143
|
+
sha256: sha256Bytes(file.bytes),
|
|
144
|
+
byteLength: file.bytes.byteLength
|
|
145
|
+
})));
|
|
146
|
+
}
|
|
147
|
+
function workspaceManifestFromEntries(entries) {
|
|
148
|
+
return {
|
|
149
|
+
kind: "agent-candidate-workspace-manifest",
|
|
150
|
+
files: entries.map((entry) => ({
|
|
151
|
+
path: entry.path,
|
|
152
|
+
mode: entry.mode,
|
|
153
|
+
sha256: entry.sha256,
|
|
154
|
+
byteLength: entry.byteLength
|
|
155
|
+
})).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** Git records exactly two file modes, and a portable-tree scan records the same two. */
|
|
159
|
+
function portableFileMode(mode) {
|
|
160
|
+
return (mode & 73) !== 0 ? 493 : 420;
|
|
161
|
+
}
|
|
162
|
+
function capturedBytes(entry) {
|
|
163
|
+
if (!entry.bytes) throw new Error(`workspace file was digested without its bytes: ${entry.path}`);
|
|
164
|
+
return entry.bytes;
|
|
165
|
+
}
|
|
166
|
+
function assertWorkspaceManifest(observed, expected) {
|
|
167
|
+
if (Buffer.from(canonicalCandidateBytes(observed)).equals(canonicalCandidateBytes(expected))) return;
|
|
168
|
+
if (Buffer.from(canonicalCandidateBytes(withPortableModes(observed))).equals(canonicalCandidateBytes(withPortableModes(expected)))) throw new Error("materialized workspace files do not match the signed manifest: only the file modes differ, so the capture and this verify disagree about portableTree");
|
|
169
|
+
throw new Error("materialized workspace files, modes, or bytes do not match the signed manifest");
|
|
170
|
+
}
|
|
171
|
+
function withPortableModes(material) {
|
|
172
|
+
return {
|
|
173
|
+
...material,
|
|
174
|
+
files: material.files.map((file) => ({
|
|
175
|
+
...file,
|
|
176
|
+
mode: portableFileMode(file.mode)
|
|
177
|
+
}))
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
async function verifyMaterializedProfileWorkspace(root, expected, fileRoot = "workspace") {
|
|
181
|
+
const observedProfile = (await scanMaterializedWorkspaceManifest(root)).files.map(({ path, mode, sha256 }) => ({
|
|
182
|
+
relPath: path,
|
|
183
|
+
mode,
|
|
184
|
+
contentSha256: sha256
|
|
185
|
+
}));
|
|
186
|
+
const expectedFiles = expected.files.filter((file) => (file.root ?? "workspace") === fileRoot).map(({ root: _root, ...file }) => file);
|
|
187
|
+
if (!Buffer.from(canonicalCandidateBytes(observedProfile)).equals(canonicalCandidateBytes(expectedFiles))) throw new Error(`profile ${fileRoot} staging files, modes, or bytes do not match the signed profile plan`);
|
|
188
|
+
}
|
|
189
|
+
async function walkWorkspace(root, options, keepBytes) {
|
|
190
|
+
const ignoredProtectedRootEntries = new Set(options.ignoredProtectedRootEntries ?? []);
|
|
191
|
+
const limits = options.limits;
|
|
192
|
+
const requestedRoot = resolve(root);
|
|
193
|
+
const rootStats = await lstat(requestedRoot);
|
|
194
|
+
if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) throw new Error("workspace root must be a real directory");
|
|
195
|
+
const absoluteRoot = await realpath(requestedRoot);
|
|
196
|
+
const resolvedStats = await lstat(absoluteRoot);
|
|
197
|
+
if (!resolvedStats.isDirectory() || resolvedStats.isSymbolicLink()) throw new Error("workspace root must resolve to a real directory");
|
|
198
|
+
const scanned = [];
|
|
199
|
+
let totalBytes = 0;
|
|
200
|
+
async function visit(directory) {
|
|
201
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
202
|
+
entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
203
|
+
for (const entry of entries) {
|
|
204
|
+
if (directory === absoluteRoot && ignoredProtectedRootEntries.has(entry.name)) continue;
|
|
205
|
+
const absolute = resolve(directory, entry.name);
|
|
206
|
+
const relPath = relative(absoluteRoot, absolute).split(sep).join("/");
|
|
207
|
+
if (!relPath || relPath.startsWith("../") || relPath.includes("/../")) throw new Error(`workspace entry escapes root: ${relPath}`);
|
|
208
|
+
const stats = await lstat(absolute);
|
|
209
|
+
if (stats.isSymbolicLink()) throw new Error(await symlinkRefusal(absolute, relPath, absoluteRoot));
|
|
210
|
+
if (stats.isDirectory()) {
|
|
211
|
+
await visit(absolute);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (!stats.isFile()) throw new Error(`workspace contains a non-regular entry: ${relPath}`);
|
|
215
|
+
if (limits && scanned.length >= limits.maxFiles) throw new Error("workspace exceeds maxFiles");
|
|
216
|
+
const descriptor = await open(absolute, constants.O_RDONLY | (typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0));
|
|
217
|
+
try {
|
|
218
|
+
const openedStats = await descriptor.stat();
|
|
219
|
+
if (!openedStats.isFile()) throw new Error(`workspace contains a non-regular entry: ${relPath}`);
|
|
220
|
+
if (openedStats.nlink !== 1) throw new Error(`workspace contains a hard-linked file: ${relPath}`);
|
|
221
|
+
const mode = options.portableTree === true ? portableFileMode(openedStats.mode) : openedStats.mode & 511;
|
|
222
|
+
if (limits && openedStats.size > limits.maxFileBytes) throw new Error(`workspace file exceeds maxFileBytes: ${relPath}`);
|
|
223
|
+
const remainingBytes = limits ? limits.maxTotalFileBytes - totalBytes : void 0;
|
|
224
|
+
if (remainingBytes !== void 0 && openedStats.size > remainingBytes) throw new Error("workspace exceeds maxTotalFileBytes");
|
|
225
|
+
const read = await readWorkspaceFile(descriptor, limits ? Math.min(limits.maxFileBytes, remainingBytes ?? limits.maxFileBytes) : void 0, keepBytes, relPath);
|
|
226
|
+
totalBytes += read.byteLength;
|
|
227
|
+
scanned.push({
|
|
228
|
+
path: relPath,
|
|
229
|
+
mode,
|
|
230
|
+
sha256: read.sha256,
|
|
231
|
+
byteLength: read.byteLength,
|
|
232
|
+
...read.bytes === void 0 ? {} : { bytes: read.bytes }
|
|
233
|
+
});
|
|
234
|
+
} finally {
|
|
235
|
+
await descriptor.close();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
await visit(absoluteRoot);
|
|
240
|
+
return scanned;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Why one symbolic link is refused, in the terms that separate a portable link from a link that
|
|
244
|
+
* reaches outside the tree.
|
|
245
|
+
*
|
|
246
|
+
* Every link is refused, in both mode policies. `AgentCandidateWorkspaceManifestMaterial` has no
|
|
247
|
+
* representation for one — `mode`, `sha256` and `byteLength` describe a regular file — so a link
|
|
248
|
+
* recorded as a file would let two different trees share one digest. The reason says which kind of
|
|
249
|
+
* link it was, so a caller can tell a link it can rewrite from one it cannot.
|
|
250
|
+
*/
|
|
251
|
+
async function symlinkRefusal(absolute, relPath, absoluteRoot) {
|
|
252
|
+
const target = await readlink(absolute);
|
|
253
|
+
if (isAbsolute(target)) return `workspace contains an absolute symlink: ${relPath} -> ${target}`;
|
|
254
|
+
if (!isWithin(absoluteRoot, resolve(dirname(absolute), target))) return `workspace contains a symlink that escapes its tree: ${relPath} -> ${target}`;
|
|
255
|
+
let physical;
|
|
256
|
+
try {
|
|
257
|
+
physical = await realpath(absolute);
|
|
258
|
+
} catch {
|
|
259
|
+
return `workspace contains an unresolved symlink: ${relPath} -> ${target}`;
|
|
260
|
+
}
|
|
261
|
+
if (!isWithin(absoluteRoot, physical)) return `workspace contains a symlink that resolves outside its tree: ${relPath} -> ${target}`;
|
|
262
|
+
return `workspace contains a symlink: ${relPath} -> ${target}`;
|
|
263
|
+
}
|
|
264
|
+
function isWithin(root, path) {
|
|
265
|
+
return path === root || path.startsWith(`${root}${sep}`);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Read one open regular file into its digest, keeping the bytes only when the caller needs them.
|
|
269
|
+
*
|
|
270
|
+
* The content reaches the hash in 1 MiB chunks and is never held whole for a digest-only walk, so
|
|
271
|
+
* the size of the largest file does not decide whether a workspace can be described.
|
|
272
|
+
* `FileHandle.readFile` refuses anything above 2 GiB with `ERR_FS_FILE_TOO_LARGE`. Hashing the
|
|
273
|
+
* chunks in order equals hashing the whole buffer, so a manifest a buffered read produced is
|
|
274
|
+
* reproduced byte for byte.
|
|
275
|
+
*/
|
|
276
|
+
async function readWorkspaceFile(descriptor, maxBytes, keepBytes, path) {
|
|
277
|
+
const hash = createHash("sha256");
|
|
278
|
+
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
279
|
+
const chunks = [];
|
|
280
|
+
let total = 0;
|
|
281
|
+
while (true) {
|
|
282
|
+
const { bytesRead } = await descriptor.read(buffer, 0, buffer.byteLength, null);
|
|
283
|
+
if (bytesRead === 0) break;
|
|
284
|
+
total += bytesRead;
|
|
285
|
+
if (maxBytes !== void 0 && total > maxBytes) throw new Error(`workspace file exceeds its capture limit: ${path}`);
|
|
286
|
+
const chunk = buffer.subarray(0, bytesRead);
|
|
287
|
+
hash.update(chunk);
|
|
288
|
+
if (keepBytes) chunks.push(Buffer.from(chunk));
|
|
289
|
+
}
|
|
290
|
+
const sha256 = `sha256:${hash.digest("hex")}`;
|
|
291
|
+
return keepBytes ? {
|
|
292
|
+
sha256,
|
|
293
|
+
byteLength: total,
|
|
294
|
+
bytes: Buffer.concat(chunks, total)
|
|
295
|
+
} : {
|
|
296
|
+
sha256,
|
|
297
|
+
byteLength: total
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
//#endregion
|
|
67
301
|
//#region src/candidate-execution/system-prompt.ts
|
|
68
302
|
const SYSTEM_PROMPT_FILE = ".tangle/system-prompt.md";
|
|
69
303
|
const APPEND_SYSTEM_PROMPT_FILE = ".tangle/append-system-prompt.md";
|
|
@@ -215,6 +449,8 @@ function candidateMaterializerHarness(harness) {
|
|
|
215
449
|
}
|
|
216
450
|
/** Runtime applies the materializer's launch flags to the candidate process. */
|
|
217
451
|
const CANDIDATE_PROFILE_MATERIALIZER_BINDS = ["systemPrompt"];
|
|
452
|
+
const PI_CANDIDATE_AGENT_DIR_ENV = "PI_CODING_AGENT_DIR";
|
|
453
|
+
const PI_CANDIDATE_SESSION_DIR_ENV = "PI_CODING_AGENT_SESSION_DIR";
|
|
218
454
|
/** Derive the exact native profile plan used by preparation and later evidence checks. */
|
|
219
455
|
function materializeAgentCandidateProfilePlan(options) {
|
|
220
456
|
const root = options.workspace === "task" ? options.workspaces.taskRoot : options.workspaces.candidateRoot;
|
|
@@ -224,15 +460,22 @@ function materializeAgentCandidateProfilePlan(options) {
|
|
|
224
460
|
resolvedResources: options.resolvedResources
|
|
225
461
|
}), options.launch, posix.join(root, ".tangle/system-prompt.md"), options.profile.prompt?.systemPrompt, options.profile.prompt?.appendSystemPrompt);
|
|
226
462
|
}
|
|
463
|
+
/** Resolve the private Pi roots needed by an agent-root profile plan. */
|
|
464
|
+
function candidateProfileAgentPaths(plan, runtimeHome) {
|
|
465
|
+
if (!plan.files.some((file) => file.root === "agent")) return void 0;
|
|
466
|
+
if (plan.harness !== "pi") throw new Error(`candidate profile agent-root files are unsupported for ${plan.harness}`);
|
|
467
|
+
if (runtimeHome === void 0) throw new Error("candidate profile agent-root files require executionRoots.profileRoot");
|
|
468
|
+
return resolvePiRuntimePaths(runtimeHome);
|
|
469
|
+
}
|
|
227
470
|
/** Bind exact native profile text to the canonical plan captured during preparation. */
|
|
228
471
|
function createAgentCandidateProfileActivation(plan, profilePlan) {
|
|
229
|
-
const
|
|
230
|
-
if (
|
|
472
|
+
const sourceByIdentity = new Map(plan.files.map((file) => [profileFileIdentity$1(file.root, file.relPath), file]));
|
|
473
|
+
if (sourceByIdentity.size !== plan.files.length) throw new Error("candidate profile activation contains duplicate rooted native file paths");
|
|
231
474
|
const files = profilePlan.material.files.map((expected) => {
|
|
232
|
-
const source =
|
|
233
|
-
if (!source) throw new Error(`candidate profile activation is missing ${expected.relPath}`);
|
|
475
|
+
const source = sourceByIdentity.get(profileFileIdentity$1(expected.root, expected.relPath));
|
|
476
|
+
if (!source) throw new Error(`candidate profile activation is missing ${profileFileLabel(expected.root, expected.relPath)}`);
|
|
234
477
|
const mode = source.mode ?? 420;
|
|
235
|
-
if (!Number.isSafeInteger(mode) || mode < 0 || mode > 511) throw new Error(`candidate profile activation has invalid mode for ${expected.relPath}`);
|
|
478
|
+
if (!Number.isSafeInteger(mode) || mode < 0 || mode > 511) throw new Error(`candidate profile activation has invalid mode for ${profileFileLabel(expected.root, expected.relPath)}`);
|
|
236
479
|
return {
|
|
237
480
|
path: expected.relPath,
|
|
238
481
|
mode,
|
|
@@ -246,6 +489,12 @@ function createAgentCandidateProfileActivation(plan, profilePlan) {
|
|
|
246
489
|
files
|
|
247
490
|
}).value, profilePlan.digest);
|
|
248
491
|
}
|
|
492
|
+
function profileFileIdentity$1(root, relPath) {
|
|
493
|
+
return `${root ?? "workspace"}\0${relPath}`;
|
|
494
|
+
}
|
|
495
|
+
function profileFileLabel(root, relPath) {
|
|
496
|
+
return root === "agent" ? `agent:${relPath}` : relPath;
|
|
497
|
+
}
|
|
249
498
|
/** Parse and check every native file hash plus both canonical document digests. */
|
|
250
499
|
function parseAgentCandidateProfileActivation(input, expectedProfilePlanDigest) {
|
|
251
500
|
const activation = agentCandidateProfileActivationSchema.parse(input);
|
|
@@ -885,225 +1134,35 @@ function candidateTerminalWindowMs(cleanupTimeoutMs) {
|
|
|
885
1134
|
return windowMs;
|
|
886
1135
|
}
|
|
887
1136
|
//#endregion
|
|
888
|
-
//#region src/candidate-execution/
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
};
|
|
911
|
-
}
|
|
912
|
-
async function verifyMaterializedWorkspace(root, expected, options = {}) {
|
|
913
|
-
assertWorkspaceManifest(await scanMaterializedWorkspaceManifest(root, options), expected);
|
|
914
|
-
}
|
|
915
|
-
/**
|
|
916
|
-
* The canonical manifest of one materialized workspace, read without holding any file.
|
|
917
|
-
*
|
|
918
|
-
* Every file is digested by streaming, so the size of the largest file does not decide whether the
|
|
919
|
-
* workspace can be described. `FileHandle.readFile` refuses anything above 2 GiB with
|
|
920
|
-
* `ERR_FS_FILE_TOO_LARGE`, which made a workspace holding one such artifact impossible to verify
|
|
921
|
-
* against a manifest it already matched. The digest is sha-256 over the same bytes either way, so
|
|
922
|
-
* a manifest a buffered read produced is reproduced exactly.
|
|
923
|
-
*/
|
|
924
|
-
async function scanMaterializedWorkspaceManifest(root, options = {}) {
|
|
925
|
-
return workspaceManifestFromEntries(await walkWorkspace(root, options, false));
|
|
926
|
-
}
|
|
927
|
-
async function captureMaterializedWorkspace(root, options = {}) {
|
|
928
|
-
const entries = await walkWorkspace(root, options, true);
|
|
929
|
-
return {
|
|
930
|
-
manifest: workspaceManifestFromEntries(entries),
|
|
931
|
-
files: entries.map((entry) => ({
|
|
932
|
-
path: entry.path,
|
|
933
|
-
mode: entry.mode,
|
|
934
|
-
bytes: Uint8Array.from(capturedBytes(entry))
|
|
935
|
-
}))
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
/** Capture exact verified regular-file bytes for fresh isolated materialization. */
|
|
939
|
-
async function readMaterializedWorkspaceFiles(root, expected, options = {}) {
|
|
940
|
-
const observed = await captureMaterializedWorkspace(root, options);
|
|
941
|
-
assertWorkspaceManifest(observed.manifest, expected);
|
|
942
|
-
return observed.files.map((file) => Object.freeze({
|
|
943
|
-
path: file.path,
|
|
944
|
-
mode: file.mode,
|
|
945
|
-
bytes: Uint8Array.from(file.bytes)
|
|
946
|
-
}));
|
|
947
|
-
}
|
|
948
|
-
function candidateWorkspaceManifest(files, options = {}) {
|
|
949
|
-
return workspaceManifestFromEntries(files.map((file) => ({
|
|
950
|
-
path: file.path,
|
|
951
|
-
mode: options.portableTree === true ? portableFileMode(file.mode) : file.mode,
|
|
952
|
-
sha256: sha256Bytes(file.bytes),
|
|
953
|
-
byteLength: file.bytes.byteLength
|
|
954
|
-
})));
|
|
955
|
-
}
|
|
956
|
-
function workspaceManifestFromEntries(entries) {
|
|
957
|
-
return {
|
|
958
|
-
kind: "agent-candidate-workspace-manifest",
|
|
959
|
-
files: entries.map((entry) => ({
|
|
960
|
-
path: entry.path,
|
|
961
|
-
mode: entry.mode,
|
|
962
|
-
sha256: entry.sha256,
|
|
963
|
-
byteLength: entry.byteLength
|
|
964
|
-
})).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
/** Git records exactly two file modes, and a portable-tree scan records the same two. */
|
|
968
|
-
function portableFileMode(mode) {
|
|
969
|
-
return (mode & 73) !== 0 ? 493 : 420;
|
|
970
|
-
}
|
|
971
|
-
function capturedBytes(entry) {
|
|
972
|
-
if (!entry.bytes) throw new Error(`workspace file was digested without its bytes: ${entry.path}`);
|
|
973
|
-
return entry.bytes;
|
|
974
|
-
}
|
|
975
|
-
function assertWorkspaceManifest(observed, expected) {
|
|
976
|
-
if (Buffer.from(canonicalCandidateBytes(observed)).equals(canonicalCandidateBytes(expected))) return;
|
|
977
|
-
if (Buffer.from(canonicalCandidateBytes(withPortableModes(observed))).equals(canonicalCandidateBytes(withPortableModes(expected)))) throw new Error("materialized workspace files do not match the signed manifest: only the file modes differ, so the capture and this verify disagree about portableTree");
|
|
978
|
-
throw new Error("materialized workspace files, modes, or bytes do not match the signed manifest");
|
|
979
|
-
}
|
|
980
|
-
function withPortableModes(material) {
|
|
981
|
-
return {
|
|
982
|
-
...material,
|
|
983
|
-
files: material.files.map((file) => ({
|
|
984
|
-
...file,
|
|
985
|
-
mode: portableFileMode(file.mode)
|
|
986
|
-
}))
|
|
987
|
-
};
|
|
988
|
-
}
|
|
989
|
-
async function verifyMaterializedProfileWorkspace(root, expected) {
|
|
990
|
-
const observedProfile = (await scanMaterializedWorkspaceManifest(root)).files.map(({ path, mode, sha256 }) => ({
|
|
991
|
-
relPath: path,
|
|
992
|
-
mode,
|
|
993
|
-
contentSha256: sha256
|
|
994
|
-
}));
|
|
995
|
-
if (!Buffer.from(canonicalCandidateBytes(observedProfile)).equals(canonicalCandidateBytes(expected.files))) throw new Error("profile staging files, modes, or bytes do not match the signed profile plan");
|
|
996
|
-
}
|
|
997
|
-
async function walkWorkspace(root, options, keepBytes) {
|
|
998
|
-
const ignoredProtectedRootEntries = new Set(options.ignoredProtectedRootEntries ?? []);
|
|
999
|
-
const limits = options.limits;
|
|
1000
|
-
const requestedRoot = resolve(root);
|
|
1001
|
-
const rootStats = await lstat(requestedRoot);
|
|
1002
|
-
if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) throw new Error("workspace root must be a real directory");
|
|
1003
|
-
const absoluteRoot = await realpath(requestedRoot);
|
|
1004
|
-
const resolvedStats = await lstat(absoluteRoot);
|
|
1005
|
-
if (!resolvedStats.isDirectory() || resolvedStats.isSymbolicLink()) throw new Error("workspace root must resolve to a real directory");
|
|
1006
|
-
const scanned = [];
|
|
1007
|
-
let totalBytes = 0;
|
|
1008
|
-
async function visit(directory) {
|
|
1009
|
-
const entries = await readdir(directory, { withFileTypes: true });
|
|
1010
|
-
entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
1011
|
-
for (const entry of entries) {
|
|
1012
|
-
if (directory === absoluteRoot && ignoredProtectedRootEntries.has(entry.name)) continue;
|
|
1013
|
-
const absolute = resolve(directory, entry.name);
|
|
1014
|
-
const relPath = relative(absoluteRoot, absolute).split(sep).join("/");
|
|
1015
|
-
if (!relPath || relPath.startsWith("../") || relPath.includes("/../")) throw new Error(`workspace entry escapes root: ${relPath}`);
|
|
1016
|
-
const stats = await lstat(absolute);
|
|
1017
|
-
if (stats.isSymbolicLink()) throw new Error(await symlinkRefusal(absolute, relPath, absoluteRoot));
|
|
1018
|
-
if (stats.isDirectory()) {
|
|
1019
|
-
await visit(absolute);
|
|
1020
|
-
continue;
|
|
1021
|
-
}
|
|
1022
|
-
if (!stats.isFile()) throw new Error(`workspace contains a non-regular entry: ${relPath}`);
|
|
1023
|
-
if (limits && scanned.length >= limits.maxFiles) throw new Error("workspace exceeds maxFiles");
|
|
1024
|
-
const descriptor = await open(absolute, constants.O_RDONLY | (typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0));
|
|
1025
|
-
try {
|
|
1026
|
-
const openedStats = await descriptor.stat();
|
|
1027
|
-
if (!openedStats.isFile()) throw new Error(`workspace contains a non-regular entry: ${relPath}`);
|
|
1028
|
-
if (openedStats.nlink !== 1) throw new Error(`workspace contains a hard-linked file: ${relPath}`);
|
|
1029
|
-
const mode = options.portableTree === true ? portableFileMode(openedStats.mode) : openedStats.mode & 511;
|
|
1030
|
-
if (limits && openedStats.size > limits.maxFileBytes) throw new Error(`workspace file exceeds maxFileBytes: ${relPath}`);
|
|
1031
|
-
const remainingBytes = limits ? limits.maxTotalFileBytes - totalBytes : void 0;
|
|
1032
|
-
if (remainingBytes !== void 0 && openedStats.size > remainingBytes) throw new Error("workspace exceeds maxTotalFileBytes");
|
|
1033
|
-
const read = await readWorkspaceFile(descriptor, limits ? Math.min(limits.maxFileBytes, remainingBytes ?? limits.maxFileBytes) : void 0, keepBytes, relPath);
|
|
1034
|
-
totalBytes += read.byteLength;
|
|
1035
|
-
scanned.push({
|
|
1036
|
-
path: relPath,
|
|
1037
|
-
mode,
|
|
1038
|
-
sha256: read.sha256,
|
|
1039
|
-
byteLength: read.byteLength,
|
|
1040
|
-
...read.bytes === void 0 ? {} : { bytes: read.bytes }
|
|
1041
|
-
});
|
|
1042
|
-
} finally {
|
|
1043
|
-
await descriptor.close();
|
|
1044
|
-
}
|
|
1137
|
+
//#region src/candidate-execution/execution-roots.ts
|
|
1138
|
+
/** Validate the canonical identity and isolation boundary for execution roots. */
|
|
1139
|
+
function assertAgentCandidateExecutionRoots(roots) {
|
|
1140
|
+
const entries = [
|
|
1141
|
+
["task", roots.taskRoot],
|
|
1142
|
+
["candidate", roots.candidateRoot],
|
|
1143
|
+
["profile", roots.profileRoot]
|
|
1144
|
+
];
|
|
1145
|
+
for (const [name, root] of entries) {
|
|
1146
|
+
if (root === void 0) continue;
|
|
1147
|
+
if (!posix.isAbsolute(root) || posix.normalize(root) !== root) throw new Error(`execution ${name} root must be a canonical absolute path`);
|
|
1148
|
+
}
|
|
1149
|
+
for (let left = 0; left < entries.length; left++) {
|
|
1150
|
+
const entry = entries[left];
|
|
1151
|
+
if (!entry) continue;
|
|
1152
|
+
const [leftName, leftRoot] = entry;
|
|
1153
|
+
if (leftRoot === void 0) continue;
|
|
1154
|
+
for (let right = left + 1; right < entries.length; right++) {
|
|
1155
|
+
const rightEntry = entries[right];
|
|
1156
|
+
if (!rightEntry) continue;
|
|
1157
|
+
const [rightName, rightRoot] = rightEntry;
|
|
1158
|
+
if (rightRoot !== void 0 && executionPathsOverlap(leftRoot, rightRoot)) throw new Error(`execution ${leftName} and ${rightName} roots must be distinct and non-overlapping`);
|
|
1045
1159
|
}
|
|
1046
1160
|
}
|
|
1047
|
-
await visit(absoluteRoot);
|
|
1048
|
-
return scanned;
|
|
1049
1161
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
* Every link is refused, in both mode policies. `AgentCandidateWorkspaceManifestMaterial` has no
|
|
1055
|
-
* representation for one — `mode`, `sha256` and `byteLength` describe a regular file — so a link
|
|
1056
|
-
* recorded as a file would let two different trees share one digest. The reason says which kind of
|
|
1057
|
-
* link it was, so a caller can tell a link it can rewrite from one it cannot.
|
|
1058
|
-
*/
|
|
1059
|
-
async function symlinkRefusal(absolute, relPath, absoluteRoot) {
|
|
1060
|
-
const target = await readlink(absolute);
|
|
1061
|
-
if (isAbsolute(target)) return `workspace contains an absolute symlink: ${relPath} -> ${target}`;
|
|
1062
|
-
if (!isWithin(absoluteRoot, resolve(dirname(absolute), target))) return `workspace contains a symlink that escapes its tree: ${relPath} -> ${target}`;
|
|
1063
|
-
let physical;
|
|
1064
|
-
try {
|
|
1065
|
-
physical = await realpath(absolute);
|
|
1066
|
-
} catch {
|
|
1067
|
-
return `workspace contains an unresolved symlink: ${relPath} -> ${target}`;
|
|
1068
|
-
}
|
|
1069
|
-
if (!isWithin(absoluteRoot, physical)) return `workspace contains a symlink that resolves outside its tree: ${relPath} -> ${target}`;
|
|
1070
|
-
return `workspace contains a symlink: ${relPath} -> ${target}`;
|
|
1071
|
-
}
|
|
1072
|
-
function isWithin(root, path) {
|
|
1073
|
-
return path === root || path.startsWith(`${root}${sep}`);
|
|
1074
|
-
}
|
|
1075
|
-
/**
|
|
1076
|
-
* Read one open regular file into its digest, keeping the bytes only when the caller needs them.
|
|
1077
|
-
*
|
|
1078
|
-
* The content reaches the hash in 1 MiB chunks and is never held whole for a digest-only walk, so
|
|
1079
|
-
* the size of the largest file does not decide whether a workspace can be described.
|
|
1080
|
-
* `FileHandle.readFile` refuses anything above 2 GiB with `ERR_FS_FILE_TOO_LARGE`. Hashing the
|
|
1081
|
-
* chunks in order equals hashing the whole buffer, so a manifest a buffered read produced is
|
|
1082
|
-
* reproduced byte for byte.
|
|
1083
|
-
*/
|
|
1084
|
-
async function readWorkspaceFile(descriptor, maxBytes, keepBytes, path) {
|
|
1085
|
-
const hash = createHash("sha256");
|
|
1086
|
-
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
1087
|
-
const chunks = [];
|
|
1088
|
-
let total = 0;
|
|
1089
|
-
while (true) {
|
|
1090
|
-
const { bytesRead } = await descriptor.read(buffer, 0, buffer.byteLength, null);
|
|
1091
|
-
if (bytesRead === 0) break;
|
|
1092
|
-
total += bytesRead;
|
|
1093
|
-
if (maxBytes !== void 0 && total > maxBytes) throw new Error(`workspace file exceeds its capture limit: ${path}`);
|
|
1094
|
-
const chunk = buffer.subarray(0, bytesRead);
|
|
1095
|
-
hash.update(chunk);
|
|
1096
|
-
if (keepBytes) chunks.push(Buffer.from(chunk));
|
|
1097
|
-
}
|
|
1098
|
-
const sha256 = `sha256:${hash.digest("hex")}`;
|
|
1099
|
-
return keepBytes ? {
|
|
1100
|
-
sha256,
|
|
1101
|
-
byteLength: total,
|
|
1102
|
-
bytes: Buffer.concat(chunks, total)
|
|
1103
|
-
} : {
|
|
1104
|
-
sha256,
|
|
1105
|
-
byteLength: total
|
|
1106
|
-
};
|
|
1162
|
+
function executionPathsOverlap(left, right) {
|
|
1163
|
+
const a = posix.normalize(left);
|
|
1164
|
+
const b = posix.normalize(right);
|
|
1165
|
+
return a === b || b.startsWith(a === "/" ? "/" : `${a}/`) || a.startsWith(b === "/" ? "/" : `${b}/`);
|
|
1107
1166
|
}
|
|
1108
1167
|
//#endregion
|
|
1109
1168
|
//#region src/candidate-execution/git-materialize.ts
|
|
@@ -1618,6 +1677,7 @@ function detachPreparedCandidateState(input) {
|
|
|
1618
1677
|
});
|
|
1619
1678
|
}
|
|
1620
1679
|
function assertPrivateCandidateIntegrity(state) {
|
|
1680
|
+
assertAgentCandidateExecutionRoots(state.roots.execution);
|
|
1621
1681
|
if (canonicalCandidateDigest$1(omitTopLevelDigest(state.bundle)) !== state.bundle.digest) throw new Error("prepared candidate bundle no longer matches its digest");
|
|
1622
1682
|
assertPlanEvidence(state.profilePlan.value, state.profilePlan.bytes, "profile plan");
|
|
1623
1683
|
parseAgentCandidateProfileActivation(state.profileActivation, state.profilePlan.value.digest);
|
|
@@ -1694,6 +1754,7 @@ function profileFileView(source) {
|
|
|
1694
1754
|
return Object.freeze({
|
|
1695
1755
|
path: source.path,
|
|
1696
1756
|
mode: source.mode,
|
|
1757
|
+
...source.root === "agent" ? { root: "agent" } : {},
|
|
1697
1758
|
get bytes() {
|
|
1698
1759
|
return Uint8Array.from(bytes);
|
|
1699
1760
|
}
|
|
@@ -1721,7 +1782,7 @@ function assertExecutorInputs(state) {
|
|
|
1721
1782
|
for (let index = 0; index < expectedFiles.length; index++) {
|
|
1722
1783
|
const expected = expectedFiles[index];
|
|
1723
1784
|
const actual = state.executorInputs.profileFiles[index];
|
|
1724
|
-
if (!expected || !actual || actual.path !== expected.relPath || actual.mode !== expected.mode || sha256Bytes(actual.bytes) !== expected.contentSha256) throw new Error("prepared profile executor files do not match the signed profile plan");
|
|
1785
|
+
if (!expected || !actual || actual.path !== expected.relPath || (actual.root ?? "workspace") !== (expected.root ?? "workspace") || actual.mode !== expected.mode || sha256Bytes(actual.bytes) !== expected.contentSha256) throw new Error("prepared profile executor files do not match the signed profile plan");
|
|
1725
1786
|
}
|
|
1726
1787
|
}
|
|
1727
1788
|
function assertPreparedKnowledge(state) {
|
|
@@ -4050,11 +4111,25 @@ async function prepareAgentCandidateExecution(candidate, task, ports, options =
|
|
|
4050
4111
|
harness: bundle.execution.harness,
|
|
4051
4112
|
launch: bundle.execution.launch,
|
|
4052
4113
|
workspace: bundle.execution.cwd.workspace,
|
|
4053
|
-
workspaces:
|
|
4114
|
+
workspaces: {
|
|
4115
|
+
taskRoot: task.executionRoots.taskRoot,
|
|
4116
|
+
...task.executionRoots.candidateRoot ? { candidateRoot: task.executionRoots.candidateRoot } : {}
|
|
4117
|
+
},
|
|
4054
4118
|
resolvedResources: verifiedResourceTextByDigest(candidate)
|
|
4055
4119
|
});
|
|
4056
|
-
const
|
|
4057
|
-
|
|
4120
|
+
const profileAgentPaths = candidateProfileAgentPaths(profileWorkspacePlan, task.executionRoots.profileRoot);
|
|
4121
|
+
const profileAgentStagingRoot = profileWorkspacePlan.files.some((file) => file.root === "agent") ? await mkdtemp(join(tmpdir(), "tangle-candidate-profile-agent-")) : void 0;
|
|
4122
|
+
let profileApplication;
|
|
4123
|
+
try {
|
|
4124
|
+
profileApplication = applyAgentCandidateWorkspacePlan(profileWorkspacePlan, task.stagingRoots.profileRoot, bundle.execution.cwd.workspace, profileAgentStagingRoot ? { agentDir: profileAgentStagingRoot } : void 0);
|
|
4125
|
+
await verifyMaterializedProfileWorkspace(task.stagingRoots.profileRoot, profileApplication.profileActivation.profilePlan.material);
|
|
4126
|
+
if (profileAgentStagingRoot) await verifyMaterializedProfileWorkspace(profileAgentStagingRoot, profileApplication.profileActivation.profilePlan.material, "agent");
|
|
4127
|
+
} finally {
|
|
4128
|
+
if (profileAgentStagingRoot) await rm(profileAgentStagingRoot, {
|
|
4129
|
+
recursive: true,
|
|
4130
|
+
force: true
|
|
4131
|
+
});
|
|
4132
|
+
}
|
|
4058
4133
|
const profilePlanBytes = await readVerifiedArtifact(profileApplication.profileActivation.profilePlan.artifact, ports.artifacts);
|
|
4059
4134
|
if (!Buffer.from(profilePlanBytes).equals(Buffer.from(canonicalCandidateBytes(profileApplication.profileActivation.profilePlan.material)))) throw new Error("profile materializer did not capture exact canonical plan bytes");
|
|
4060
4135
|
const profileActivation = profileApplication.profileActivation;
|
|
@@ -4079,7 +4154,16 @@ async function prepareAgentCandidateExecution(candidate, task, ports, options =
|
|
|
4079
4154
|
const knowledge = await prepareAgentCandidateKnowledge(candidate, ports, task.runCell.arm);
|
|
4080
4155
|
const knowledgePaths = knowledge ? candidateKnowledgeExecutionPaths(task.executionRoots.taskRoot, knowledge.retrievalConfig !== void 0) : void 0;
|
|
4081
4156
|
const baseLaunch = buildLaunch(candidate, task, profileApplication.flags);
|
|
4082
|
-
const publicEnv = mergePublicEnvironment(bundle.execution.env ?? {}, profileApplication.env,
|
|
4157
|
+
const publicEnv = mergePublicEnvironment(bundle.execution.env ?? {}, profileApplication.env, profileAgentPaths ? {
|
|
4158
|
+
[PI_CANDIDATE_AGENT_DIR_ENV]: {
|
|
4159
|
+
kind: "public",
|
|
4160
|
+
value: profileAgentPaths.agentDir
|
|
4161
|
+
},
|
|
4162
|
+
[PI_CANDIDATE_SESSION_DIR_ENV]: {
|
|
4163
|
+
kind: "public",
|
|
4164
|
+
value: profileAgentPaths.sessionDir
|
|
4165
|
+
}
|
|
4166
|
+
} : {}, bundle.execution.instructionDelivery.kind === "utf8-file" ? { [bundle.execution.instructionDelivery.env]: {
|
|
4083
4167
|
kind: "public",
|
|
4084
4168
|
value: bundle.execution.instructionDelivery.path
|
|
4085
4169
|
} } : {}, knowledgePaths ? {
|
|
@@ -4311,15 +4395,14 @@ function assertTaskInput(task, delivery) {
|
|
|
4311
4395
|
if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(benchmarkTask.repository.baseTree)) throw new Error("task repository base tree is not a full Git object id");
|
|
4312
4396
|
if (benchmarkTask.repository.baseCommit.length !== benchmarkTask.repository.baseTree.length) throw new Error("task repository Git object formats disagree");
|
|
4313
4397
|
}
|
|
4398
|
+
assertAgentCandidateExecutionRoots(task.executionRoots);
|
|
4314
4399
|
for (const [name, root] of [
|
|
4315
|
-
["execution task root", task.executionRoots.taskRoot],
|
|
4316
|
-
["execution candidate root", task.executionRoots.candidateRoot],
|
|
4317
4400
|
["staging task root", task.stagingRoots.taskRoot],
|
|
4318
4401
|
["staging candidate root", task.stagingRoots.candidateRoot],
|
|
4319
4402
|
["staging profile root", task.stagingRoots.profileRoot]
|
|
4320
4403
|
]) {
|
|
4321
4404
|
if (root === void 0) continue;
|
|
4322
|
-
if (!(
|
|
4405
|
+
if (!(isAbsolute(root) && resolve(root) === root)) throw new Error(`${name} must be a canonical absolute path`);
|
|
4323
4406
|
}
|
|
4324
4407
|
if (!Number.isInteger(runCell.attempt) || !Number.isInteger(benchmarkTask.attempt.maxAttempts) || runCell.attempt < 1 || runCell.attempt > benchmarkTask.attempt.maxAttempts || benchmarkTask.attempt.retryPolicy === "none" && benchmarkTask.attempt.maxAttempts !== 1) throw new Error("task attempt policy is invalid");
|
|
4325
4408
|
const limits = benchmarkTask.limits;
|
|
@@ -4339,14 +4422,13 @@ function assertTaskInput(task, delivery) {
|
|
|
4339
4422
|
});
|
|
4340
4423
|
}
|
|
4341
4424
|
if (delivery.kind === "utf8-file") {
|
|
4342
|
-
if (
|
|
4425
|
+
if ([
|
|
4426
|
+
task.executionRoots.taskRoot,
|
|
4427
|
+
task.executionRoots.candidateRoot,
|
|
4428
|
+
task.executionRoots.profileRoot
|
|
4429
|
+
].some((root) => root !== void 0 && executionPathsOverlap(root, delivery.path))) throw new Error("task instruction file must remain outside execution workspaces");
|
|
4343
4430
|
}
|
|
4344
4431
|
}
|
|
4345
|
-
function executionPathsOverlap(left, right) {
|
|
4346
|
-
const a = posix.normalize(left);
|
|
4347
|
-
const b = posix.normalize(right);
|
|
4348
|
-
return a === b || b.startsWith(a === "/" ? "/" : `${a}/`) || a.startsWith(b === "/" ? "/" : `${b}/`);
|
|
4349
|
-
}
|
|
4350
4432
|
function assertDisjointHostStagingRoots(task) {
|
|
4351
4433
|
const roots = [
|
|
4352
4434
|
task.stagingRoots.taskRoot,
|
|
@@ -4519,12 +4601,16 @@ function candidateEntrypointReceipt(candidate) {
|
|
|
4519
4601
|
};
|
|
4520
4602
|
}
|
|
4521
4603
|
function absoluteExecutionCwd(cwd, roots) {
|
|
4522
|
-
const root = cwd.workspace
|
|
4523
|
-
if (!root) throw new Error("candidate cwd is missing its execution workspace root");
|
|
4604
|
+
const root = workspaceExecutionRoot(cwd.workspace, roots);
|
|
4524
4605
|
const absolute = cwd.path === "." ? root : posix.join(root, cwd.path);
|
|
4525
4606
|
if (absolute !== root && !absolute.startsWith(`${root}/`)) throw new Error("candidate cwd escapes its execution workspace");
|
|
4526
4607
|
return absolute;
|
|
4527
4608
|
}
|
|
4609
|
+
function workspaceExecutionRoot(workspace, roots) {
|
|
4610
|
+
const root = workspace === "task" ? roots.taskRoot : roots.candidateRoot;
|
|
4611
|
+
if (!root) throw new Error(`candidate ${workspace} workspace root is missing`);
|
|
4612
|
+
return root;
|
|
4613
|
+
}
|
|
4528
4614
|
function validateProtectedModelReservation(reservation, expectedLimits, preparationId, expiresAtMs) {
|
|
4529
4615
|
if (!/^sha256:[a-f0-9]{64}$/.test(reservation.digest)) throw new Error("protected model reservation has an invalid identity digest");
|
|
4530
4616
|
if (reservation.preparationId !== preparationId || reservation.expiresAtMs !== expiresAtMs) throw new Error("protected model reservation is not scoped to this preparation");
|
|
@@ -4549,24 +4635,28 @@ function modelLimits(limits) {
|
|
|
4549
4635
|
};
|
|
4550
4636
|
}
|
|
4551
4637
|
function exactProfileExecutorFiles(sourceFiles, expectedFiles) {
|
|
4552
|
-
const
|
|
4553
|
-
if (
|
|
4638
|
+
const byIdentity = new Map(sourceFiles.map((file) => [profileFileIdentity(file.root, file.relPath), file]));
|
|
4639
|
+
if (byIdentity.size !== sourceFiles.length || sourceFiles.length !== expectedFiles.length) throw new Error("profile source files do not match the signed profile plan");
|
|
4554
4640
|
return expectedFiles.map((expected) => {
|
|
4555
|
-
const source =
|
|
4641
|
+
const source = byIdentity.get(profileFileIdentity(expected.root, expected.relPath));
|
|
4556
4642
|
const mode = source?.mode ?? 420;
|
|
4557
4643
|
const bytes = Buffer.from(source?.content ?? "", "utf8");
|
|
4558
4644
|
if (!source || !Number.isInteger(mode) || mode < 0 || mode > 511 || mode !== expected.mode || sha256Bytes(bytes) !== expected.contentSha256) throw new Error("profile source files do not match the signed profile plan");
|
|
4559
4645
|
return {
|
|
4560
4646
|
path: expected.relPath,
|
|
4561
4647
|
mode,
|
|
4648
|
+
...expected.root === "agent" ? { root: "agent" } : {},
|
|
4562
4649
|
bytes: Uint8Array.from(bytes)
|
|
4563
4650
|
};
|
|
4564
4651
|
});
|
|
4565
4652
|
}
|
|
4653
|
+
function profileFileIdentity(root, relPath) {
|
|
4654
|
+
return `${root ?? "workspace"}\0${relPath}`;
|
|
4655
|
+
}
|
|
4566
4656
|
function errorMessage(error) {
|
|
4567
4657
|
return error instanceof Error ? error.message : String(error);
|
|
4568
4658
|
}
|
|
4569
4659
|
//#endregion
|
|
4570
|
-
export {
|
|
4660
|
+
export { parseExactAgentProfile as $, assertRecoveryMatchesStaged as A, assertExactObjectKeys as B, assertNoGitIndirection as C, candidateCleanupDeadline as D, assertAgentCandidateExecutionRoots as E, rejectedFinish as F, applyExactAgentProfileDiff as G, PI_CANDIDATE_AGENT_DIR_ENV as H, rejectedStage as I, createAgentCandidateProfileActivation as J, assertCandidateProfileBinding as K, requireStagedTerminal as L, assertTerminalMatchesClaim as M, assertTerminalMatchesStaged as N, candidateCleanupTimeout as O, recoveredTerminalRecord as P, parseAgentCandidateProfileActivation as Q, sealTerminalDigest as R, CANDIDATE_TRACE_TAGS as S, runCandidateGit as T, PI_CANDIDATE_SESSION_DIR_ENV as U, isWellFormedUnicode as V, agentCandidateProfileAsAgentProfile as W, materializeAgentCandidateProfilePlan as X, freezeGenericAgentCandidateProfile as Y, omitUndefinedObjectFields as Z, candidateExecutionClaim as _, persistCandidateOutputArtifact as a, scanMaterializedWorkspaceManifest as at, consumePreparedCandidateExecution as b, CANDIDATE_KNOWLEDGE_ROOT_ENV as c, canonicalCandidateBytes as ct, verifiedResourceTextByDigest as d, deepFreezeCandidate as dt, parseExactAgentProfileDiff as et, verifyAgentCandidateBundle as f, embeddedCandidateArtifact as ft, candidateClaimFileInternals as g, verifyCanonicalCandidateDocument as gt, InMemoryAgentCandidateExecutionClaimStore as h, sha256Bytes as ht, persistCandidateModelSettlementEvidence as i, readVerifiedArtifact as it, assertTerminalAllowedInPhase as j, withinCandidateCleanupDeadline as k, candidateKnowledgeExecutionPaths as l, canonicalCandidateDigest$1 as lt, usdToNanos as m, omitTopLevelDigest as mt, executePreparedAgentCandidate as n, candidateWorkspaceManifest as nt, sealAgentCandidateExecutorStopAcknowledgement as o, verifyBytes as ot, sealAgentCandidateModelSettlement as p, immutableCandidateValue as pt, candidateProfileAgentPaths as q, RecoveryAgentCandidateTraceStore as r, captureMaterializedWorkspace as rt, CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV as s, verifyMaterializedWorkspace as st, prepareAgentCandidateExecution as t, parseExactCandidateProfile as tt, AGENT_CANDIDATE_EXECUTION_SUPPORT as u, canonicalCandidateDocument as ut, assertPreparedCandidateIntegrity as v, readCandidateGitTreeFiles as w, CANDIDATE_TRACE_ENV as x, beginPreparedCandidateDisposal as y, terminalRecord as z };
|
|
4571
4661
|
|
|
4572
|
-
//# sourceMappingURL=prepare-
|
|
4662
|
+
//# sourceMappingURL=prepare-B5fJ7mLS.js.map
|