@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,4 +1,5 @@
|
|
|
1
1
|
import { m as ValidationError } from "./errors-DodWX-cb.js";
|
|
2
|
+
import { t as isRecord } from "./record-value-B6bwJxLa.js";
|
|
2
3
|
import { runTaskQueue } from "./task-queue.js";
|
|
3
4
|
import { agentCandidateArtifactRefSchema, canonicalCandidateBytes as canonicalCandidateBytes$1 } from "@tangle-network/agent-interface";
|
|
4
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -18,8 +19,6 @@ const DEFAULT_SUCCESSFUL_SHUTDOWN_MS = 5e3;
|
|
|
18
19
|
const CHILD_DEADLINE_REASON = "child deadline exceeded";
|
|
19
20
|
/** The abort reason the root's budget deadline cascades to every node. */
|
|
20
21
|
const ROOT_DEADLINE_REASON = "root budget deadline exceeded";
|
|
21
|
-
/** How long a checked-in child may go without progress before it is stopped: 30 min. */
|
|
22
|
-
const DEFAULT_CHECK_IN_IDLE_MS = 30 * 6e4;
|
|
23
22
|
/** The check-in a child receives at its own time box. */
|
|
24
23
|
const DEADLINE_CHECK_IN_MESSAGE = "Check-in: your time box has ended. This is not a stop. Finish the step you are on and start nothing new. Record your current state and every result you have not recorded yet, then call submit_result with what you have now; if the work is not done, mark it incomplete and name what remains. Your lead decides whether to continue you or hand the work on. You keep running while you make progress.";
|
|
25
24
|
/** The idle bound a check-in policy sets, or undefined when time boxes are hard kills. */
|
|
@@ -233,9 +232,9 @@ function randomSuffix(len = 8) {
|
|
|
233
232
|
function randomUuid() {
|
|
234
233
|
return crypto.randomUUID();
|
|
235
234
|
}
|
|
236
|
-
/** Construct an AbortError. Downstream code pattern-matches on `err.name`. */
|
|
237
|
-
function abortError() {
|
|
238
|
-
const err =
|
|
235
|
+
/** Construct an AbortError carrying the abort reason's message. Downstream code pattern-matches on `err.name`. */
|
|
236
|
+
function abortError(reason) {
|
|
237
|
+
const err = new Error(reason instanceof Error ? reason.message : reason === void 0 ? "aborted" : String(reason));
|
|
239
238
|
err.name = "AbortError";
|
|
240
239
|
return err;
|
|
241
240
|
}
|
|
@@ -635,6 +634,16 @@ function fnv1a32Hex(text) {
|
|
|
635
634
|
return (h >>> 0).toString(16).padStart(8, "0");
|
|
636
635
|
}
|
|
637
636
|
//#endregion
|
|
637
|
+
//#region src/path-control-characters.ts
|
|
638
|
+
/** Whether a serialized field contains an ASCII control or delete character. */
|
|
639
|
+
function hasControlCharacter(value) {
|
|
640
|
+
for (let index = 0; index < value.length; index++) {
|
|
641
|
+
const code = value.charCodeAt(index);
|
|
642
|
+
if (code < 32 || code === 127) return true;
|
|
643
|
+
}
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
//#endregion
|
|
638
647
|
//#region src/candidate-execution/digest.ts
|
|
639
648
|
/** Use native hashing for workspace archives. */
|
|
640
649
|
function sha256Bytes$1(bytes) {
|
|
@@ -1035,170 +1044,609 @@ function isWellFormedUnicode(value) {
|
|
|
1035
1044
|
return true;
|
|
1036
1045
|
}
|
|
1037
1046
|
//#endregion
|
|
1038
|
-
//#region src/candidate-execution/
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
"--whitespace=nowarn",
|
|
1057
|
-
"-"
|
|
1058
|
-
], patchBytes, { GIT_INDEX_FILE: indexFile });
|
|
1059
|
-
const candidateTree = (await runCandidateGit(repositoryRoot, ["write-tree"], void 0, { GIT_INDEX_FILE: indexFile })).stdout.toString("utf8").trim();
|
|
1060
|
-
if (candidateTree !== code.candidateTree) throw new Error(`git patch materialized tree ${candidateTree} does not match ${code.candidateTree}`);
|
|
1061
|
-
await assertSafeTree(repositoryRoot, candidateTree);
|
|
1062
|
-
return candidateTree;
|
|
1063
|
-
} finally {
|
|
1064
|
-
await rm(temporary, {
|
|
1065
|
-
recursive: true,
|
|
1066
|
-
force: true
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1047
|
+
//#region src/candidate-execution/output-artifacts.ts
|
|
1048
|
+
/** Persist evaluator evidence, read it back, and bind the returned locator to the exact bytes. */
|
|
1049
|
+
async function persistCandidateOutputArtifact(port, input) {
|
|
1050
|
+
input.signal?.throwIfAborted();
|
|
1051
|
+
const bytes = Uint8Array.from(input.bytes);
|
|
1052
|
+
const expectedDigest = sha256Bytes$1(bytes);
|
|
1053
|
+
const ref = agentCandidateArtifactRefSchema.parse(await port.put({
|
|
1054
|
+
executionId: input.executionId,
|
|
1055
|
+
purpose: input.purpose,
|
|
1056
|
+
bytes: Uint8Array.from(bytes),
|
|
1057
|
+
...input.signal ? { signal: input.signal } : {}
|
|
1058
|
+
}));
|
|
1059
|
+
input.signal?.throwIfAborted();
|
|
1060
|
+
if (ref.sha256 !== expectedDigest || ref.byteLength !== bytes.byteLength) throw new Error("candidate output locator does not identify the submitted bytes");
|
|
1061
|
+
const stored = await port.read(ref);
|
|
1062
|
+
input.signal?.throwIfAborted();
|
|
1063
|
+
verifyBytes(stored, expectedDigest, bytes.byteLength, "persisted candidate output");
|
|
1064
|
+
return immutableCandidateValue(ref);
|
|
1069
1065
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
])).stdout;
|
|
1093
|
-
verifyBytes(bytes, resource.sha256, resource.byteLength, `GitHub resource ${resource.path}`);
|
|
1094
|
-
return Uint8Array.from(bytes);
|
|
1066
|
+
//#endregion
|
|
1067
|
+
//#region src/candidate-execution/workspace-archive.ts
|
|
1068
|
+
const archiveKind = "agent-candidate-workspace-archive";
|
|
1069
|
+
const workspaceEntryPrefix = "workspace/";
|
|
1070
|
+
const repositoryMetadataEntry = "metadata/repository.json";
|
|
1071
|
+
const repositoryBundleEntry = "metadata/repository.bundle";
|
|
1072
|
+
const repositoryIndexEntry = "metadata/repository.index.json";
|
|
1073
|
+
const repositoryIndexPrefix = "index/";
|
|
1074
|
+
const fixedTarTime = /* @__PURE__ */ new Date(0);
|
|
1075
|
+
const defaultLimits = Object.freeze({
|
|
1076
|
+
maxArchiveBytes: 512 * 1024 * 1024,
|
|
1077
|
+
maxEmbeddedArtifactBytes: 1024 * 1024,
|
|
1078
|
+
maxFiles: 5e4,
|
|
1079
|
+
maxFileBytes: 128 * 1024 * 1024,
|
|
1080
|
+
maxTotalFileBytes: 256 * 1024 * 1024,
|
|
1081
|
+
maxPathBytes: 4096,
|
|
1082
|
+
maxRepositoryBundleBytes: 128 * 1024 * 1024
|
|
1083
|
+
});
|
|
1084
|
+
/** Capture detached files returned by a remote executor into the standard archive. */
|
|
1085
|
+
async function captureAgentCandidateWorkspaceFiles(input, options = {}) {
|
|
1086
|
+
const limits = workspaceLimits(options.limits);
|
|
1087
|
+
return captureWorkspaceFiles(normalizeWorkspaceFiles(input, limits), void 0, limits, options.artifactPersistence);
|
|
1095
1088
|
}
|
|
1096
|
-
async function
|
|
1097
|
-
const
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1089
|
+
async function captureWorkspaceFiles(files, repository, limits, artifactPersistence) {
|
|
1090
|
+
const material = candidateWorkspaceManifest(files);
|
|
1091
|
+
const manifest = canonicalCandidateBytes$1(material);
|
|
1092
|
+
const archive = await encodeWorkspaceArchive(files, repository, limits.maxArchiveBytes);
|
|
1093
|
+
if (!artifactPersistence && (manifest.byteLength > limits.maxEmbeddedArtifactBytes || archive.byteLength > limits.maxEmbeddedArtifactBytes)) throw new Error("candidate workspace artifacts exceed maxEmbeddedArtifactBytes; artifactPersistence is required");
|
|
1094
|
+
const manifestArtifact = await captureWorkspaceArtifact("manifest", manifest, artifactPersistence);
|
|
1095
|
+
const archiveArtifact = await captureWorkspaceArtifact("archive", archive, artifactPersistence);
|
|
1096
|
+
const snapshot = deepFreezeCandidate({
|
|
1097
|
+
kind: "agent-candidate-workspace-snapshot",
|
|
1098
|
+
digest: canonicalCandidateDigest$1(material),
|
|
1099
|
+
material,
|
|
1100
|
+
manifest: manifestArtifact,
|
|
1101
|
+
archive: archiveArtifact
|
|
1102
|
+
});
|
|
1103
|
+
return Object.freeze({
|
|
1104
|
+
snapshot,
|
|
1105
|
+
archive
|
|
1106
|
+
});
|
|
1102
1107
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
await assertNoGitIndirection(repositoryRoot, gitDir, "task repository");
|
|
1113
|
-
const temporary = await mkdtemp(join(tmpdir(), "agent-candidate-task-outcome-"));
|
|
1114
|
-
try {
|
|
1115
|
-
const objectDirectory = join(temporary, "objects");
|
|
1116
|
-
const indexFile = join(temporary, "index");
|
|
1117
|
-
await mkdir(objectDirectory);
|
|
1118
|
-
const gitEnvironment = {
|
|
1119
|
-
GIT_INDEX_FILE: indexFile,
|
|
1120
|
-
GIT_OBJECT_DIRECTORY: objectDirectory,
|
|
1121
|
-
GIT_ALTERNATE_OBJECT_DIRECTORIES: join(gitDir, "objects")
|
|
1122
|
-
};
|
|
1123
|
-
await runCandidateGit(repositoryRoot, ["read-tree", input.baseTree], void 0, gitEnvironment);
|
|
1124
|
-
if (input.patch.byteLength > 0) await runCandidateGit(repositoryRoot, [
|
|
1125
|
-
"apply",
|
|
1126
|
-
"--cached",
|
|
1127
|
-
"--binary",
|
|
1128
|
-
"--whitespace=nowarn",
|
|
1129
|
-
"-"
|
|
1130
|
-
], input.patch, gitEnvironment);
|
|
1131
|
-
const resultTree = (await runCandidateGit(repositoryRoot, ["write-tree"], void 0, gitEnvironment)).stdout.toString("utf8").trim();
|
|
1132
|
-
if (resultTree !== input.resultTree) throw new Error(`task outcome patch materialized tree ${resultTree} does not match ${input.resultTree}`);
|
|
1133
|
-
await assertSafeTree(repositoryRoot, resultTree, gitEnvironment);
|
|
1134
|
-
const observed = await workspaceManifestFromGitTree(repositoryRoot, resultTree, gitEnvironment);
|
|
1135
|
-
if (!Buffer.from(canonicalCandidateBytes$1(observed)).equals(Buffer.from(canonicalCandidateBytes$1(input.afterState)))) throw new Error("task outcome after-state does not match the materialized result tree");
|
|
1136
|
-
const resultCommit = (await runCandidateGit(repositoryRoot, [
|
|
1137
|
-
"commit-tree",
|
|
1138
|
-
resultTree,
|
|
1139
|
-
"-p",
|
|
1140
|
-
input.baseCommit
|
|
1141
|
-
], Buffer.from("candidate task outcome\n", "utf8"), {
|
|
1142
|
-
...gitEnvironment,
|
|
1143
|
-
GIT_AUTHOR_NAME: "Tangle Evaluator",
|
|
1144
|
-
GIT_AUTHOR_EMAIL: "evaluator@tangle.tools",
|
|
1145
|
-
GIT_AUTHOR_DATE: "2000-01-01T00:00:00Z",
|
|
1146
|
-
GIT_COMMITTER_NAME: "Tangle Evaluator",
|
|
1147
|
-
GIT_COMMITTER_EMAIL: "evaluator@tangle.tools",
|
|
1148
|
-
GIT_COMMITTER_DATE: "2000-01-01T00:00:00Z"
|
|
1149
|
-
})).stdout.toString("utf8").trim();
|
|
1150
|
-
if ((await runCandidateGit(repositoryRoot, ["rev-parse", `${resultCommit}^{tree}`], void 0, gitEnvironment)).stdout.toString("utf8").trim() !== resultTree) throw new Error("task outcome evaluator commit does not bind the verified result tree");
|
|
1151
|
-
return {
|
|
1152
|
-
resultTree,
|
|
1153
|
-
resultCommit
|
|
1154
|
-
};
|
|
1155
|
-
} finally {
|
|
1156
|
-
await rm(temporary, {
|
|
1157
|
-
recursive: true,
|
|
1158
|
-
force: true
|
|
1159
|
-
});
|
|
1160
|
-
}
|
|
1108
|
+
async function captureWorkspaceArtifact(kind, bytes, persistence) {
|
|
1109
|
+
if (!persistence) return embeddedCandidateArtifact(bytes);
|
|
1110
|
+
if (!persistence.executionId.trim()) throw new Error("candidate workspace artifact persistence requires an executionId");
|
|
1111
|
+
return persistCandidateOutputArtifact(persistence.outputArtifacts, {
|
|
1112
|
+
executionId: persistence.executionId,
|
|
1113
|
+
purpose: kind === "manifest" ? "candidate-workspace-manifest" : "candidate-workspace-archive",
|
|
1114
|
+
bytes,
|
|
1115
|
+
...persistence.signal ? { signal: persistence.signal } : {}
|
|
1116
|
+
});
|
|
1161
1117
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
"origin"
|
|
1169
|
-
])).stdout.toString("utf8").trim();
|
|
1170
|
-
const actual = parseGitHubRemote(origin);
|
|
1171
|
-
if (!actual || actual.owner !== repository.owner || actual.repo !== repository.repo) throw new Error(`local repository origin ${origin || "<missing>"} does not match github.com/${repository.owner}/${repository.repo}`);
|
|
1172
|
-
await assertNoGitIndirection(repositoryRoot, resolve((await runCandidateGit(repositoryRoot, ["rev-parse", "--absolute-git-dir"])).stdout.toString("utf8").trim()), "candidate repository");
|
|
1173
|
-
return repositoryRoot;
|
|
1118
|
+
/** Verify a portable archive before its source environment may be discarded. */
|
|
1119
|
+
async function verifyAgentCandidateWorkspaceArchive(input) {
|
|
1120
|
+
await decodeVerifiedWorkspaceArchive({
|
|
1121
|
+
...input,
|
|
1122
|
+
limits: workspaceLimits(input.limits)
|
|
1123
|
+
});
|
|
1174
1124
|
}
|
|
1175
|
-
async function
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
const commonDir = await realpath(resolve(repositoryRoot, (await runCandidateGit(repositoryRoot, ["rev-parse", "--git-common-dir"])).stdout.toString("utf8").trim()));
|
|
1182
|
-
if (commonDir.includes(":")) throw new Error(`${label} Git common directory has an unsupported path`);
|
|
1183
|
-
for (const objectRoot of /* @__PURE__ */ new Set([gitDir, commonDir])) for (const name of ["alternates", "http-alternates"]) {
|
|
1184
|
-
const path = join(objectRoot, "objects", "info", name);
|
|
1185
|
-
try {
|
|
1186
|
-
if ((await readFile(path, "utf8")).trim()) throw new Error(`${label} uses forbidden Git ${name}`);
|
|
1187
|
-
} catch (error) {
|
|
1188
|
-
if (!isNoEntry(error)) throw error;
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1125
|
+
async function decodeVerifiedWorkspaceArchive(input) {
|
|
1126
|
+
verifyBytes(input.archive, input.snapshot.archive.sha256, input.snapshot.archive.byteLength, "candidate workspace archive");
|
|
1127
|
+
const decoded = await parseWorkspaceArchive(input.archive, input.limits);
|
|
1128
|
+
if (decoded.repository && input.role !== "task") throw new Error("only task workspaces may carry a Git repository");
|
|
1129
|
+
assertArchiveMatchesSnapshot(decoded.files, input.snapshot);
|
|
1130
|
+
return decoded;
|
|
1191
1131
|
}
|
|
1192
|
-
async function
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1132
|
+
async function encodeWorkspaceArchive(files, repository, maxArchiveBytes) {
|
|
1133
|
+
const archive = pack();
|
|
1134
|
+
const output = collectStream(archive, maxArchiveBytes, "candidate workspace archive");
|
|
1135
|
+
try {
|
|
1136
|
+
await writeWorkspaceArchiveEntries(archive, files, repository);
|
|
1137
|
+
archive.finalize();
|
|
1138
|
+
return await output;
|
|
1139
|
+
} catch (error) {
|
|
1140
|
+
archive.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
1141
|
+
await output.catch(() => void 0);
|
|
1142
|
+
throw error;
|
|
1143
|
+
}
|
|
1200
1144
|
}
|
|
1201
|
-
async function
|
|
1145
|
+
async function verifyCanonicalWorkspaceArchive(files, repository, expected, maxArchiveBytes) {
|
|
1146
|
+
const archive = pack();
|
|
1147
|
+
const comparison = streamEqualsBytes(archive, expected, maxArchiveBytes, "candidate workspace archive");
|
|
1148
|
+
try {
|
|
1149
|
+
await writeWorkspaceArchiveEntries(archive, files, repository);
|
|
1150
|
+
archive.finalize();
|
|
1151
|
+
if (!await comparison) throw new Error("candidate workspace tar is not canonical");
|
|
1152
|
+
} catch (error) {
|
|
1153
|
+
archive.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
1154
|
+
await comparison.catch(() => void 0);
|
|
1155
|
+
throw error;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
async function writeWorkspaceArchiveEntries(archive, files, repository) {
|
|
1159
|
+
for (const file of files) await writeTarEntry(archive, `${workspaceEntryPrefix}${file.path}`, file.mode, file.bytes);
|
|
1160
|
+
if (!repository) return;
|
|
1161
|
+
const indexManifest = repository.workingTree ? canonicalCandidateBytes$1(candidateWorkspaceManifest(repository.workingTree.files)) : void 0;
|
|
1162
|
+
const metadata = canonicalCandidateBytes$1({
|
|
1163
|
+
kind: archiveKind,
|
|
1164
|
+
headCommit: repository.headCommit,
|
|
1165
|
+
headTree: repository.headTree,
|
|
1166
|
+
bundle: {
|
|
1167
|
+
sha256: repository.bundle.sha256,
|
|
1168
|
+
byteLength: repository.bundle.byteLength
|
|
1169
|
+
},
|
|
1170
|
+
...indexManifest ? { workingTree: { indexManifest: {
|
|
1171
|
+
sha256: sha256Bytes$1(indexManifest),
|
|
1172
|
+
byteLength: indexManifest.byteLength
|
|
1173
|
+
} } } : {}
|
|
1174
|
+
});
|
|
1175
|
+
await writeTarEntry(archive, repositoryMetadataEntry, 384, metadata);
|
|
1176
|
+
await writeTarEntry(archive, repositoryBundleEntry, 384, repository.bundle.bytes);
|
|
1177
|
+
if (repository.workingTree && indexManifest) {
|
|
1178
|
+
await writeTarEntry(archive, repositoryIndexEntry, 384, indexManifest);
|
|
1179
|
+
for (const file of repository.workingTree.files) await writeTarEntry(archive, `${repositoryIndexPrefix}${file.path}`, file.mode, file.bytes);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
async function parseWorkspaceArchive(bytes, limits) {
|
|
1183
|
+
if (bytes.byteLength > limits.maxArchiveBytes) throw new Error("candidate workspace archive exceeds maxArchiveBytes");
|
|
1184
|
+
const parser = extract();
|
|
1185
|
+
const files = [];
|
|
1186
|
+
const observedEntryNames = /* @__PURE__ */ new Set();
|
|
1187
|
+
const observedPaths = /* @__PURE__ */ new Set();
|
|
1188
|
+
let totalBytes = 0;
|
|
1189
|
+
let retainedEntryBytes = 0;
|
|
1190
|
+
let previousPath;
|
|
1191
|
+
let repositoryMetadata;
|
|
1192
|
+
let repositoryBundle;
|
|
1193
|
+
let repositoryIndex;
|
|
1194
|
+
const indexFiles = [];
|
|
1195
|
+
const indexPaths = /* @__PURE__ */ new Set();
|
|
1196
|
+
let previousIndexPath;
|
|
1197
|
+
let indexBytes = 0;
|
|
1198
|
+
const decoded = (async () => {
|
|
1199
|
+
for await (const entry of parser) {
|
|
1200
|
+
const name = entry.header.name;
|
|
1201
|
+
if (entry.header.type !== "file" || typeof name !== "string" || observedEntryNames.has(name)) throw new Error("candidate workspace tar contains an invalid entry");
|
|
1202
|
+
observedEntryNames.add(name);
|
|
1203
|
+
if (name.startsWith(workspaceEntryPrefix)) {
|
|
1204
|
+
if (files.length >= limits.maxFiles) throw new Error("candidate workspace archive has an invalid file count");
|
|
1205
|
+
const path = safeArchivePath(name.slice(10), limits.maxPathBytes);
|
|
1206
|
+
if (!isWorkspaceFileMode(entry.header.mode)) throw new Error(`candidate workspace archive has an unsupported mode: ${path}`);
|
|
1207
|
+
assertRetainedArchiveSize(bytes.byteLength, retainedEntryBytes, entry.header.size, limits);
|
|
1208
|
+
const fileBytes = await readTarEntry(entry, limits.maxFileBytes, `workspace file ${path}`);
|
|
1209
|
+
retainedEntryBytes += fileBytes.byteLength;
|
|
1210
|
+
assertWorkspacePathOrder(path, previousPath, observedPaths, "candidate workspace archive paths must be unique and sorted");
|
|
1211
|
+
previousPath = path;
|
|
1212
|
+
if (fileBytes.byteLength > limits.maxTotalFileBytes - totalBytes) throw new Error("candidate workspace archive exceeds maxTotalFileBytes");
|
|
1213
|
+
totalBytes += fileBytes.byteLength;
|
|
1214
|
+
files.push({
|
|
1215
|
+
path,
|
|
1216
|
+
mode: entry.header.mode,
|
|
1217
|
+
bytes: fileBytes
|
|
1218
|
+
});
|
|
1219
|
+
} else if (name === repositoryMetadataEntry) {
|
|
1220
|
+
if (entry.header.mode !== 384 || repositoryMetadata) throw new Error("candidate workspace archive repository metadata is invalid");
|
|
1221
|
+
assertRetainedArchiveSize(bytes.byteLength, retainedEntryBytes, entry.header.size, limits);
|
|
1222
|
+
const metadataBytes = await readTarEntry(entry, limits.maxPathBytes, "candidate repository metadata");
|
|
1223
|
+
retainedEntryBytes += metadataBytes.byteLength;
|
|
1224
|
+
repositoryMetadata = parseRepositoryMetadata(metadataBytes, limits);
|
|
1225
|
+
} else if (name === repositoryBundleEntry) {
|
|
1226
|
+
if (entry.header.mode !== 384 || repositoryBundle) throw new Error("candidate workspace archive repository bundle is invalid");
|
|
1227
|
+
assertRetainedArchiveSize(bytes.byteLength, retainedEntryBytes, entry.header.size, limits);
|
|
1228
|
+
repositoryBundle = await readTarEntry(entry, limits.maxRepositoryBundleBytes, "candidate Git bundle");
|
|
1229
|
+
retainedEntryBytes += repositoryBundle.byteLength;
|
|
1230
|
+
} else if (name === repositoryIndexEntry) {
|
|
1231
|
+
if (entry.header.mode !== 384 || repositoryIndex) throw new Error("candidate workspace archive repository index is invalid");
|
|
1232
|
+
assertRetainedArchiveSize(bytes.byteLength, retainedEntryBytes, entry.header.size, limits);
|
|
1233
|
+
repositoryIndex = await readTarEntry(entry, limits.maxArchiveBytes, "candidate Git index manifest");
|
|
1234
|
+
retainedEntryBytes += repositoryIndex.byteLength;
|
|
1235
|
+
} else if (name.startsWith(repositoryIndexPrefix)) {
|
|
1236
|
+
if (!repositoryMetadata?.workingTree || !repositoryIndex || indexFiles.length >= limits.maxFiles) throw new Error("candidate Git index evidence is incomplete or exceeds maxFiles");
|
|
1237
|
+
const path = safeArchivePath(name.slice(6), limits.maxPathBytes);
|
|
1238
|
+
if (entry.header.mode !== 420 && entry.header.mode !== 493) throw new Error("candidate Git index contains an unsupported mode");
|
|
1239
|
+
assertWorkspacePathOrder(path, previousIndexPath, indexPaths, "candidate Git index paths must be unique and sorted");
|
|
1240
|
+
previousIndexPath = path;
|
|
1241
|
+
assertRetainedArchiveSize(bytes.byteLength, retainedEntryBytes, entry.header.size, limits);
|
|
1242
|
+
if (entry.header.size === void 0 || entry.header.size > limits.maxTotalFileBytes - indexBytes) throw new Error("candidate Git index exceeds maxTotalFileBytes");
|
|
1243
|
+
if (entry.header.size > limits.maxFileBytes) throw new Error("candidate Git index exceeds maxFileBytes");
|
|
1244
|
+
const fileBytes = await readTarEntry(entry, limits.maxFileBytes, `Git index file ${path}`);
|
|
1245
|
+
retainedEntryBytes += fileBytes.byteLength;
|
|
1246
|
+
indexBytes += fileBytes.byteLength;
|
|
1247
|
+
indexFiles.push({
|
|
1248
|
+
path,
|
|
1249
|
+
mode: entry.header.mode,
|
|
1250
|
+
bytes: fileBytes
|
|
1251
|
+
});
|
|
1252
|
+
} else throw new Error(`candidate workspace tar contains an unsupported entry: ${name}`);
|
|
1253
|
+
}
|
|
1254
|
+
if (Boolean(repositoryMetadata) !== Boolean(repositoryBundle)) throw new Error("candidate workspace archive repository evidence is incomplete");
|
|
1255
|
+
if (Boolean(repositoryMetadata?.workingTree) !== Boolean(repositoryIndex)) throw new Error("candidate workspace archive repository index evidence is incomplete");
|
|
1256
|
+
const repository = repositoryMetadata && repositoryBundle ? repositoryFromTar(repositoryMetadata, repositoryBundle, repositoryIndex, indexFiles) : void 0;
|
|
1257
|
+
return {
|
|
1258
|
+
files,
|
|
1259
|
+
...repository ? { repository } : {}
|
|
1260
|
+
};
|
|
1261
|
+
})();
|
|
1262
|
+
parser.end(Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength));
|
|
1263
|
+
const result = await decoded;
|
|
1264
|
+
await verifyCanonicalWorkspaceArchive(result.files, result.repository, bytes, limits.maxArchiveBytes);
|
|
1265
|
+
return result;
|
|
1266
|
+
}
|
|
1267
|
+
function assertRetainedArchiveSize(archiveBytes, retainedEntryBytes, entryBytes, limits) {
|
|
1268
|
+
if (!Number.isSafeInteger(entryBytes) || entryBytes === void 0 || entryBytes < 0 || entryBytes > limits.maxArchiveBytes - archiveBytes - retainedEntryBytes) throw new Error("candidate workspace archive exceeds maxArchiveBytes while decoding");
|
|
1269
|
+
}
|
|
1270
|
+
function parseRepositoryMetadata(bytes, limits) {
|
|
1271
|
+
let value;
|
|
1272
|
+
try {
|
|
1273
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
1274
|
+
} catch (error) {
|
|
1275
|
+
throw new Error("candidate workspace archive repository metadata is invalid", { cause: error });
|
|
1276
|
+
}
|
|
1277
|
+
if (!isRecord(value) || !["bundle,headCommit,headTree,kind", "bundle,headCommit,headTree,kind,workingTree"].includes(Object.keys(value).sort().join(",")) || value.kind !== archiveKind || typeof value.headCommit !== "string" || typeof value.headTree !== "string" || !isRecord(value.bundle) || Object.keys(value.bundle).sort().join(",") !== "byteLength,sha256" || typeof value.bundle.sha256 !== "string" || !/^sha256:[a-f0-9]{64}$/.test(value.bundle.sha256) || !Number.isSafeInteger(value.bundle.byteLength) || value.bundle.byteLength <= 0 || value.bundle.byteLength > limits.maxRepositoryBundleBytes) throw new Error("candidate workspace archive repository is invalid");
|
|
1278
|
+
if (!Buffer.from(canonicalCandidateBytes$1(value)).equals(Buffer.from(bytes))) throw new Error("candidate workspace archive repository metadata is not canonical");
|
|
1279
|
+
assertGitObjectId(value.headCommit, "repository HEAD");
|
|
1280
|
+
assertGitObjectId(value.headTree, "repository HEAD tree");
|
|
1281
|
+
if (value.headCommit.length !== value.headTree.length) throw new Error("candidate repository HEAD and tree use different object formats");
|
|
1282
|
+
let workingTree;
|
|
1283
|
+
if ("workingTree" in value) {
|
|
1284
|
+
const working = value.workingTree;
|
|
1285
|
+
if (!isRecord(working) || Object.keys(working).join(",") !== "indexManifest" || !isRecord(working.indexManifest) || Object.keys(working.indexManifest).sort().join(",") !== "byteLength,sha256" || typeof working.indexManifest.sha256 !== "string" || !/^sha256:[a-f0-9]{64}$/.test(working.indexManifest.sha256) || !Number.isSafeInteger(working.indexManifest.byteLength) || working.indexManifest.byteLength < 0 || working.indexManifest.byteLength > limits.maxArchiveBytes) throw new Error("candidate workspace archive repository index is invalid");
|
|
1286
|
+
workingTree = { indexManifest: {
|
|
1287
|
+
sha256: working.indexManifest.sha256,
|
|
1288
|
+
byteLength: working.indexManifest.byteLength
|
|
1289
|
+
} };
|
|
1290
|
+
}
|
|
1291
|
+
return {
|
|
1292
|
+
kind: archiveKind,
|
|
1293
|
+
headCommit: value.headCommit,
|
|
1294
|
+
headTree: value.headTree,
|
|
1295
|
+
bundle: {
|
|
1296
|
+
sha256: value.bundle.sha256,
|
|
1297
|
+
byteLength: value.bundle.byteLength
|
|
1298
|
+
},
|
|
1299
|
+
...workingTree ? { workingTree } : {}
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
function repositoryFromTar(metadata, bundle, indexManifest, indexFiles) {
|
|
1303
|
+
verifyBytes(bundle, metadata.bundle.sha256, metadata.bundle.byteLength, "candidate Git bundle");
|
|
1304
|
+
if (metadata.workingTree && indexManifest) {
|
|
1305
|
+
verifyBytes(indexManifest, metadata.workingTree.indexManifest.sha256, metadata.workingTree.indexManifest.byteLength, "candidate Git index manifest");
|
|
1306
|
+
if (!Buffer.from(indexManifest).equals(Buffer.from(canonicalCandidateBytes$1(candidateWorkspaceManifest(indexFiles))))) throw new Error("candidate Git index files do not match the captured manifest");
|
|
1307
|
+
}
|
|
1308
|
+
return {
|
|
1309
|
+
headCommit: metadata.headCommit,
|
|
1310
|
+
headTree: metadata.headTree,
|
|
1311
|
+
bundle: {
|
|
1312
|
+
...metadata.bundle,
|
|
1313
|
+
bytes: bundle
|
|
1314
|
+
},
|
|
1315
|
+
...metadata.workingTree && indexManifest ? { workingTree: { files: indexFiles } } : {}
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
async function writeTarEntry(archive, name, mode, bytes) {
|
|
1319
|
+
await new Promise((resolveEntry, rejectEntry) => {
|
|
1320
|
+
archive.entry(workspaceTarHeader(name, mode, bytes.byteLength), Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength), (error) => error ? rejectEntry(error) : resolveEntry());
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
async function readTarEntry(entry, maxBytes, label) {
|
|
1324
|
+
const size = entry.header.size;
|
|
1325
|
+
if (!Number.isSafeInteger(size) || size === void 0 || size < 0 || size > maxBytes) throw new Error(`${label} has an invalid tar size`);
|
|
1326
|
+
const bytes = await collectStream(entry, maxBytes, label);
|
|
1327
|
+
if (bytes.byteLength !== size) throw new Error(`${label} differs from its tar size`);
|
|
1328
|
+
return bytes;
|
|
1329
|
+
}
|
|
1330
|
+
async function collectStream(stream, maxBytes, label) {
|
|
1331
|
+
const chunks = [];
|
|
1332
|
+
let totalBytes = 0;
|
|
1333
|
+
for await (const chunk of stream) {
|
|
1334
|
+
if (!(chunk instanceof Uint8Array)) throw new Error(`${label} emitted a non-byte chunk`);
|
|
1335
|
+
if (chunk.byteLength > maxBytes - totalBytes) {
|
|
1336
|
+
const error = /* @__PURE__ */ new Error(`${label} exceeds its size limit`);
|
|
1337
|
+
if ("destroy" in stream && typeof stream.destroy === "function") stream.destroy(error);
|
|
1338
|
+
throw error;
|
|
1339
|
+
}
|
|
1340
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1341
|
+
chunks.push(bytes);
|
|
1342
|
+
totalBytes += bytes.byteLength;
|
|
1343
|
+
}
|
|
1344
|
+
return Buffer.concat(chunks, totalBytes);
|
|
1345
|
+
}
|
|
1346
|
+
async function streamEqualsBytes(stream, expected, maxBytes, label) {
|
|
1347
|
+
const expectedBuffer = Buffer.from(expected.buffer, expected.byteOffset, expected.byteLength);
|
|
1348
|
+
let equal = true;
|
|
1349
|
+
let offset = 0;
|
|
1350
|
+
for await (const chunk of stream) {
|
|
1351
|
+
if (!(chunk instanceof Uint8Array)) throw new Error(`${label} emitted a non-byte chunk`);
|
|
1352
|
+
if (chunk.byteLength > maxBytes - offset) {
|
|
1353
|
+
const error = /* @__PURE__ */ new Error(`${label} exceeds its size limit`);
|
|
1354
|
+
if ("destroy" in stream && typeof stream.destroy === "function") stream.destroy(error);
|
|
1355
|
+
throw error;
|
|
1356
|
+
}
|
|
1357
|
+
const observed = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
1358
|
+
if (offset + observed.byteLength > expectedBuffer.byteLength || !observed.equals(expectedBuffer.subarray(offset, offset + observed.byteLength))) equal = false;
|
|
1359
|
+
offset += observed.byteLength;
|
|
1360
|
+
}
|
|
1361
|
+
return equal && offset === expectedBuffer.byteLength;
|
|
1362
|
+
}
|
|
1363
|
+
function assertArchiveMatchesSnapshot(files, snapshot) {
|
|
1364
|
+
const material = candidateWorkspaceManifest(files);
|
|
1365
|
+
const materialBytes = canonicalCandidateBytes$1(material);
|
|
1366
|
+
verifyBytes(materialBytes, snapshot.manifest.sha256, snapshot.manifest.byteLength, "candidate workspace manifest");
|
|
1367
|
+
if (canonicalCandidateDigest$1(material) !== snapshot.digest || !Buffer.from(materialBytes).equals(Buffer.from(canonicalCandidateBytes$1(snapshot.material)))) throw new Error("candidate workspace archive does not match its snapshot manifest");
|
|
1368
|
+
}
|
|
1369
|
+
function assertWorkspaceFilesWithinLimits(files, limits) {
|
|
1370
|
+
if (files.length > limits.maxFiles) throw new Error("candidate workspace exceeds maxFiles");
|
|
1371
|
+
let totalBytes = 0;
|
|
1372
|
+
let previousPath;
|
|
1373
|
+
const observedPaths = /* @__PURE__ */ new Set();
|
|
1374
|
+
for (const file of files) {
|
|
1375
|
+
const path = safeArchivePath(file.path, limits.maxPathBytes);
|
|
1376
|
+
assertWorkspacePathOrder(path, previousPath, observedPaths, "candidate workspace paths must be unique and sorted");
|
|
1377
|
+
previousPath = path;
|
|
1378
|
+
if (!isWorkspaceFileMode(file.mode)) throw new Error(`candidate workspace file has unsupported mode: ${path}`);
|
|
1379
|
+
if (file.bytes.byteLength > limits.maxFileBytes) throw new Error(`candidate workspace file exceeds maxFileBytes: ${path}`);
|
|
1380
|
+
totalBytes += file.bytes.byteLength;
|
|
1381
|
+
if (totalBytes > limits.maxTotalFileBytes) throw new Error("candidate workspace exceeds maxTotalFileBytes");
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
function normalizeWorkspaceFiles(input, limits) {
|
|
1385
|
+
if (!Array.isArray(input)) throw new Error("candidate workspace files must be an array");
|
|
1386
|
+
const fileCount = input.length;
|
|
1387
|
+
if (fileCount > limits.maxFiles) throw new Error("candidate workspace exceeds maxFiles");
|
|
1388
|
+
let totalBytes = 0;
|
|
1389
|
+
const files = [];
|
|
1390
|
+
for (let index = 0; index < fileCount; index++) {
|
|
1391
|
+
const inputFile = input[index];
|
|
1392
|
+
if (!isRecord(inputFile) || Object.keys(inputFile).sort().join(",") !== "bytes,mode,path") throw new Error(`candidate workspace file ${index} is invalid`);
|
|
1393
|
+
const descriptors = Object.getOwnPropertyDescriptors(inputFile);
|
|
1394
|
+
if (!("value" in (descriptors.path ?? {})) || !("value" in (descriptors.mode ?? {})) || !("value" in (descriptors.bytes ?? {}))) throw new Error(`candidate workspace file ${index} must use fixed values`);
|
|
1395
|
+
const path = safeArchivePath(descriptors.path?.value, limits.maxPathBytes);
|
|
1396
|
+
const mode = descriptors.mode?.value;
|
|
1397
|
+
const inputBytes = descriptors.bytes?.value;
|
|
1398
|
+
if (!isWorkspaceFileMode(mode)) throw new Error(`candidate workspace file has unsupported mode: ${path}`);
|
|
1399
|
+
const view = inspectWorkspaceBytes(inputBytes, path);
|
|
1400
|
+
if (view.byteLength > limits.maxFileBytes) throw new Error(`candidate workspace file exceeds maxFileBytes: ${path}`);
|
|
1401
|
+
if (view.byteLength > limits.maxTotalFileBytes - totalBytes) throw new Error("candidate workspace exceeds maxTotalFileBytes");
|
|
1402
|
+
if (view.byteLength > Math.max(0, limits.maxArchiveBytes - 1024)) throw new Error("candidate workspace archive exceeds maxArchiveBytes");
|
|
1403
|
+
totalBytes += view.byteLength;
|
|
1404
|
+
files.push({
|
|
1405
|
+
path,
|
|
1406
|
+
mode,
|
|
1407
|
+
bytes: detachWorkspaceBytes(view)
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
files.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
1411
|
+
assertWorkspaceFilesWithinLimits(files, limits);
|
|
1412
|
+
return files;
|
|
1413
|
+
}
|
|
1414
|
+
function isWorkspaceFileMode(value) {
|
|
1415
|
+
return Number.isSafeInteger(value) && value >= 0 && value <= 511;
|
|
1416
|
+
}
|
|
1417
|
+
function workspaceLimits(overrides) {
|
|
1418
|
+
const limits = {
|
|
1419
|
+
...defaultLimits,
|
|
1420
|
+
...overrides
|
|
1421
|
+
};
|
|
1422
|
+
for (const [name, value] of Object.entries(limits)) if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`candidate workspace ${name} must be a positive safe integer`);
|
|
1423
|
+
return Object.freeze(limits);
|
|
1424
|
+
}
|
|
1425
|
+
function safeArchivePath(value, maxPathBytes) {
|
|
1426
|
+
if (typeof value !== "string" || !value || !isWellFormedUnicode(value) || Buffer.byteLength(value, "utf8") > maxPathBytes || isAbsolute(value) || win32.isAbsolute(value) || value.includes("\\") || value.includes("\0") || hasControlCharacter(value) || value.split("/").some((part) => !part || part === "." || part === "..") || [".git", ".sidecar"].includes(value.split("/")[0]?.toLowerCase() ?? "")) throw new Error(`candidate workspace archive contains an unsafe path: ${String(value)}`);
|
|
1427
|
+
return value;
|
|
1428
|
+
}
|
|
1429
|
+
function assertWorkspacePathOrder(path, previousPath, observedPaths, errorMessage) {
|
|
1430
|
+
if (previousPath !== void 0 && previousPath >= path) throw new Error(errorMessage);
|
|
1431
|
+
for (let slash = path.indexOf("/"); slash !== -1; slash = path.indexOf("/", slash + 1)) if (observedPaths.has(path.slice(0, slash))) throw new Error(errorMessage);
|
|
1432
|
+
observedPaths.add(path);
|
|
1433
|
+
}
|
|
1434
|
+
function inspectWorkspaceBytes(input, path) {
|
|
1435
|
+
let buffer;
|
|
1436
|
+
let byteOffset;
|
|
1437
|
+
let byteLength;
|
|
1438
|
+
try {
|
|
1439
|
+
buffer = typedArrayBufferGetter.call(input);
|
|
1440
|
+
byteOffset = typedArrayByteOffsetGetter.call(input);
|
|
1441
|
+
byteLength = typedArrayByteLengthGetter.call(input);
|
|
1442
|
+
} catch {
|
|
1443
|
+
throw new Error(`candidate workspace file bytes are invalid: ${path}`);
|
|
1444
|
+
}
|
|
1445
|
+
if (isSharedArrayBuffer(buffer)) throw new Error(`candidate workspace file uses shared bytes: ${path}`);
|
|
1446
|
+
if (!isAnyArrayBuffer(buffer)) throw new Error(`candidate workspace file bytes are invalid: ${path}`);
|
|
1447
|
+
return {
|
|
1448
|
+
buffer,
|
|
1449
|
+
byteOffset,
|
|
1450
|
+
byteLength
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
function detachWorkspaceBytes(input) {
|
|
1454
|
+
const { buffer, byteOffset, byteLength } = input;
|
|
1455
|
+
const copy = new Uint8Array(byteLength);
|
|
1456
|
+
Uint8Array.prototype.set.call(copy, new Uint8Array(buffer, byteOffset, byteLength));
|
|
1457
|
+
return copy;
|
|
1458
|
+
}
|
|
1459
|
+
function assertGitObjectId(value, label) {
|
|
1460
|
+
if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(value)) throw new Error(`candidate repository ${label} is not a Git object id`);
|
|
1461
|
+
}
|
|
1462
|
+
function requireTypedArrayGetter(name) {
|
|
1463
|
+
const typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
|
|
1464
|
+
const getter = Object.getOwnPropertyDescriptor(typedArrayPrototype, name)?.get;
|
|
1465
|
+
if (!getter) throw new Error(`typed array intrinsic ${name} accessor is unavailable`);
|
|
1466
|
+
return getter;
|
|
1467
|
+
}
|
|
1468
|
+
const typedArrayBufferGetter = requireTypedArrayGetter("buffer");
|
|
1469
|
+
const typedArrayByteOffsetGetter = requireTypedArrayGetter("byteOffset");
|
|
1470
|
+
const typedArrayByteLengthGetter = requireTypedArrayGetter("byteLength");
|
|
1471
|
+
/** Shared canonical header for both archive encoders. */
|
|
1472
|
+
function workspaceTarHeader(name, mode, size) {
|
|
1473
|
+
return {
|
|
1474
|
+
name,
|
|
1475
|
+
mode,
|
|
1476
|
+
uid: 0,
|
|
1477
|
+
gid: 0,
|
|
1478
|
+
size,
|
|
1479
|
+
mtime: fixedTarTime,
|
|
1480
|
+
type: "file",
|
|
1481
|
+
uname: "",
|
|
1482
|
+
gname: ""
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
//#endregion
|
|
1486
|
+
//#region src/candidate-execution/git-materialize.ts
|
|
1487
|
+
async function verifyCandidateCode(code, repositories, patchBytes) {
|
|
1488
|
+
if (code.kind === "disabled") return void 0;
|
|
1489
|
+
const repositoryRoot = await verifiedRepositoryRoot(code.repository, repositories);
|
|
1490
|
+
await assertCommitAndBaseTree(repositoryRoot, code.baseCommit, code.baseTree);
|
|
1491
|
+
if (code.kind === "no-op") {
|
|
1492
|
+
await assertSafeTree(repositoryRoot, code.baseTree);
|
|
1493
|
+
return code.baseTree;
|
|
1494
|
+
}
|
|
1495
|
+
if (!patchBytes) throw new Error("git-patch candidate is missing verified patch bytes");
|
|
1496
|
+
const temporary = await mkdtemp(join(tmpdir(), "agent-candidate-git-"));
|
|
1497
|
+
try {
|
|
1498
|
+
const indexFile = join(temporary, "index");
|
|
1499
|
+
await runCandidateGit(repositoryRoot, ["read-tree", code.baseTree], void 0, { GIT_INDEX_FILE: indexFile });
|
|
1500
|
+
await runCandidateGit(repositoryRoot, [
|
|
1501
|
+
"apply",
|
|
1502
|
+
"--cached",
|
|
1503
|
+
"--binary",
|
|
1504
|
+
"--whitespace=nowarn",
|
|
1505
|
+
"-"
|
|
1506
|
+
], patchBytes, { GIT_INDEX_FILE: indexFile });
|
|
1507
|
+
const candidateTree = (await runCandidateGit(repositoryRoot, ["write-tree"], void 0, { GIT_INDEX_FILE: indexFile })).stdout.toString("utf8").trim();
|
|
1508
|
+
if (candidateTree !== code.candidateTree) throw new Error(`git patch materialized tree ${candidateTree} does not match ${code.candidateTree}`);
|
|
1509
|
+
await assertSafeTree(repositoryRoot, candidateTree);
|
|
1510
|
+
return candidateTree;
|
|
1511
|
+
} finally {
|
|
1512
|
+
await rm(temporary, {
|
|
1513
|
+
recursive: true,
|
|
1514
|
+
force: true
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
async function readCandidateGitHubResource(resource, repositories) {
|
|
1519
|
+
const repositoryRoot = await verifiedRepositoryRoot(resource.repository, repositories);
|
|
1520
|
+
if ((await runCandidateGit(repositoryRoot, [
|
|
1521
|
+
"cat-file",
|
|
1522
|
+
"-t",
|
|
1523
|
+
resource.commit
|
|
1524
|
+
])).stdout.toString("utf8").trim() !== "commit") throw new Error(`GitHub resource commit is not a commit object: ${resource.commit}`);
|
|
1525
|
+
const listing = (await runCandidateGit(repositoryRoot, [
|
|
1526
|
+
"ls-tree",
|
|
1527
|
+
"-z",
|
|
1528
|
+
resource.commit,
|
|
1529
|
+
"--",
|
|
1530
|
+
resource.path
|
|
1531
|
+
])).stdout;
|
|
1532
|
+
const entries = parseTreeEntries(listing);
|
|
1533
|
+
if (entries.length !== 1 || entries[0]?.path !== resource.path) throw new Error(`GitHub resource path is not one exact file: ${resource.path}`);
|
|
1534
|
+
const entry = entries[0];
|
|
1535
|
+
if (entry?.type !== "blob" || entry.mode !== "100644" && entry.mode !== "100755") throw new Error(`GitHub resource path is not a regular Git blob: ${resource.path}`);
|
|
1536
|
+
const bytes = (await runCandidateGit(repositoryRoot, [
|
|
1537
|
+
"cat-file",
|
|
1538
|
+
"blob",
|
|
1539
|
+
entry.object
|
|
1540
|
+
])).stdout;
|
|
1541
|
+
verifyBytes(bytes, resource.sha256, resource.byteLength, `GitHub resource ${resource.path}`);
|
|
1542
|
+
return Uint8Array.from(bytes);
|
|
1543
|
+
}
|
|
1544
|
+
async function verifyTaskCheckout(taskRoot, expected) {
|
|
1545
|
+
const root = resolve(taskRoot);
|
|
1546
|
+
const head = (await runCandidateGit(root, ["rev-parse", "HEAD"])).stdout.toString("utf8").trim();
|
|
1547
|
+
if (head !== expected.baseCommit) throw new Error(`task checkout HEAD ${head} does not match ${expected.baseCommit}`);
|
|
1548
|
+
const tree = (await runCandidateGit(root, ["rev-parse", "HEAD^{tree}"])).stdout.toString("utf8").trim();
|
|
1549
|
+
if (tree !== expected.baseTree) throw new Error(`task checkout base tree ${tree} does not match ${expected.baseTree}`);
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Apply an evaluator-captured binary diff in a detached object database and
|
|
1553
|
+
* prove its result tree exactly matches the captured after-state manifest.
|
|
1554
|
+
*/
|
|
1555
|
+
async function verifyTaskOutcomePatch(input) {
|
|
1556
|
+
const repositoryRoot = resolve(input.repositoryRoot);
|
|
1557
|
+
await verifyTaskCheckout(repositoryRoot, input);
|
|
1558
|
+
const gitDir = resolve((await runCandidateGit(repositoryRoot, ["rev-parse", "--absolute-git-dir"])).stdout.toString("utf8").trim());
|
|
1559
|
+
if (await realpath(gitDir) !== gitDir || gitDir.includes(":")) throw new Error("task Git object store has an unsupported path");
|
|
1560
|
+
await assertNoGitIndirection(repositoryRoot, gitDir, "task repository");
|
|
1561
|
+
const temporary = await mkdtemp(join(tmpdir(), "agent-candidate-task-outcome-"));
|
|
1562
|
+
try {
|
|
1563
|
+
const objectDirectory = join(temporary, "objects");
|
|
1564
|
+
const indexFile = join(temporary, "index");
|
|
1565
|
+
await mkdir(objectDirectory);
|
|
1566
|
+
const gitEnvironment = {
|
|
1567
|
+
GIT_INDEX_FILE: indexFile,
|
|
1568
|
+
GIT_OBJECT_DIRECTORY: objectDirectory,
|
|
1569
|
+
GIT_ALTERNATE_OBJECT_DIRECTORIES: join(gitDir, "objects")
|
|
1570
|
+
};
|
|
1571
|
+
await runCandidateGit(repositoryRoot, ["read-tree", input.baseTree], void 0, gitEnvironment);
|
|
1572
|
+
if (input.patch.byteLength > 0) await runCandidateGit(repositoryRoot, [
|
|
1573
|
+
"apply",
|
|
1574
|
+
"--cached",
|
|
1575
|
+
"--binary",
|
|
1576
|
+
"--whitespace=nowarn",
|
|
1577
|
+
"-"
|
|
1578
|
+
], input.patch, gitEnvironment);
|
|
1579
|
+
const resultTree = (await runCandidateGit(repositoryRoot, ["write-tree"], void 0, gitEnvironment)).stdout.toString("utf8").trim();
|
|
1580
|
+
if (resultTree !== input.resultTree) throw new Error(`task outcome patch materialized tree ${resultTree} does not match ${input.resultTree}`);
|
|
1581
|
+
await assertSafeTree(repositoryRoot, resultTree, gitEnvironment);
|
|
1582
|
+
const observed = await workspaceManifestFromGitTree(repositoryRoot, resultTree, gitEnvironment);
|
|
1583
|
+
if (!Buffer.from(canonicalCandidateBytes$1(observed)).equals(Buffer.from(canonicalCandidateBytes$1(input.afterState)))) throw new Error("task outcome after-state does not match the materialized result tree");
|
|
1584
|
+
const resultCommit = (await runCandidateGit(repositoryRoot, [
|
|
1585
|
+
"commit-tree",
|
|
1586
|
+
resultTree,
|
|
1587
|
+
"-p",
|
|
1588
|
+
input.baseCommit
|
|
1589
|
+
], Buffer.from("candidate task outcome\n", "utf8"), {
|
|
1590
|
+
...gitEnvironment,
|
|
1591
|
+
GIT_AUTHOR_NAME: "Tangle Evaluator",
|
|
1592
|
+
GIT_AUTHOR_EMAIL: "evaluator@tangle.tools",
|
|
1593
|
+
GIT_AUTHOR_DATE: "2000-01-01T00:00:00Z",
|
|
1594
|
+
GIT_COMMITTER_NAME: "Tangle Evaluator",
|
|
1595
|
+
GIT_COMMITTER_EMAIL: "evaluator@tangle.tools",
|
|
1596
|
+
GIT_COMMITTER_DATE: "2000-01-01T00:00:00Z"
|
|
1597
|
+
})).stdout.toString("utf8").trim();
|
|
1598
|
+
if ((await runCandidateGit(repositoryRoot, ["rev-parse", `${resultCommit}^{tree}`], void 0, gitEnvironment)).stdout.toString("utf8").trim() !== resultTree) throw new Error("task outcome evaluator commit does not bind the verified result tree");
|
|
1599
|
+
return {
|
|
1600
|
+
resultTree,
|
|
1601
|
+
resultCommit
|
|
1602
|
+
};
|
|
1603
|
+
} finally {
|
|
1604
|
+
await rm(temporary, {
|
|
1605
|
+
recursive: true,
|
|
1606
|
+
force: true
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
async function verifiedRepositoryRoot(repository, repositories) {
|
|
1611
|
+
const repositoryRoot = resolve(await repositories.resolve(repository));
|
|
1612
|
+
if (!(await stat(repositoryRoot)).isDirectory()) throw new Error("candidate repository path is not a directory");
|
|
1613
|
+
const origin = (await runCandidateGit(repositoryRoot, [
|
|
1614
|
+
"remote",
|
|
1615
|
+
"get-url",
|
|
1616
|
+
"origin"
|
|
1617
|
+
])).stdout.toString("utf8").trim();
|
|
1618
|
+
const actual = parseGitHubRemote(origin);
|
|
1619
|
+
if (!actual || actual.owner !== repository.owner || actual.repo !== repository.repo) throw new Error(`local repository origin ${origin || "<missing>"} does not match github.com/${repository.owner}/${repository.repo}`);
|
|
1620
|
+
await assertNoGitIndirection(repositoryRoot, resolve((await runCandidateGit(repositoryRoot, ["rev-parse", "--absolute-git-dir"])).stdout.toString("utf8").trim()), "candidate repository");
|
|
1621
|
+
return repositoryRoot;
|
|
1622
|
+
}
|
|
1623
|
+
async function assertNoGitIndirection(repositoryRoot, gitDir, label) {
|
|
1624
|
+
if ((await runCandidateGit(repositoryRoot, [
|
|
1625
|
+
"for-each-ref",
|
|
1626
|
+
"--format=%(refname)",
|
|
1627
|
+
"refs/replace"
|
|
1628
|
+
])).stdout.toString("utf8").trim()) throw new Error(`${label} contains Git replace refs`);
|
|
1629
|
+
const commonDir = await realpath(resolve(repositoryRoot, (await runCandidateGit(repositoryRoot, ["rev-parse", "--git-common-dir"])).stdout.toString("utf8").trim()));
|
|
1630
|
+
if (commonDir.includes(":")) throw new Error(`${label} Git common directory has an unsupported path`);
|
|
1631
|
+
for (const objectRoot of /* @__PURE__ */ new Set([gitDir, commonDir])) for (const name of ["alternates", "http-alternates"]) {
|
|
1632
|
+
const path = join(objectRoot, "objects", "info", name);
|
|
1633
|
+
try {
|
|
1634
|
+
if ((await readFile(path, "utf8")).trim()) throw new Error(`${label} uses forbidden Git ${name}`);
|
|
1635
|
+
} catch (error) {
|
|
1636
|
+
if (!isNoEntry(error)) throw error;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
async function assertCommitAndBaseTree(repositoryRoot, commit, expectedTree) {
|
|
1641
|
+
if ((await runCandidateGit(repositoryRoot, [
|
|
1642
|
+
"cat-file",
|
|
1643
|
+
"-t",
|
|
1644
|
+
commit
|
|
1645
|
+
])).stdout.toString("utf8").trim() !== "commit") throw new Error(`candidate base object is not a commit: ${commit}`);
|
|
1646
|
+
const actualTree = (await runCandidateGit(repositoryRoot, ["rev-parse", `${commit}^{tree}`])).stdout.toString("utf8").trim();
|
|
1647
|
+
if (actualTree !== expectedTree) throw new Error(`candidate base commit tree ${actualTree} does not match ${expectedTree}`);
|
|
1648
|
+
}
|
|
1649
|
+
async function assertSafeTree(repositoryRoot, tree, environment = {}) {
|
|
1202
1650
|
const listing = (await runCandidateGit(repositoryRoot, [
|
|
1203
1651
|
"ls-tree",
|
|
1204
1652
|
"-rz",
|
|
@@ -1269,14 +1717,7 @@ function parseTreeEntries(bytes) {
|
|
|
1269
1717
|
});
|
|
1270
1718
|
}
|
|
1271
1719
|
function assertSafeGitPath(path) {
|
|
1272
|
-
if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || hasControlCharacter
|
|
1273
|
-
}
|
|
1274
|
-
function hasControlCharacter$1(value) {
|
|
1275
|
-
for (let index = 0; index < value.length; index++) {
|
|
1276
|
-
const code = value.charCodeAt(index);
|
|
1277
|
-
if (code < 32 || code === 127) return true;
|
|
1278
|
-
}
|
|
1279
|
-
return false;
|
|
1720
|
+
if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || hasControlCharacter(path) || path.split("/").some((part) => !part || part === "." || part === "..") || path.split("/")[0]?.toLowerCase() === ".git") throw new Error(`candidate Git tree contains an unsafe path: ${path}`);
|
|
1280
1721
|
}
|
|
1281
1722
|
function parseGitHubRemote(value) {
|
|
1282
1723
|
const match = value.match(/^(?:https?:\/\/github\.com\/|ssh:\/\/git@github\.com\/|git@github\.com:)([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
@@ -1371,91 +1812,21 @@ function isNoEntry(error) {
|
|
|
1371
1812
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1372
1813
|
}
|
|
1373
1814
|
//#endregion
|
|
1374
|
-
//#region src/candidate-execution/
|
|
1375
|
-
/** Persist evaluator evidence, read it back, and bind the returned locator to the exact bytes. */
|
|
1376
|
-
async function persistCandidateOutputArtifact(port, input) {
|
|
1377
|
-
input.signal?.throwIfAborted();
|
|
1378
|
-
const bytes = Uint8Array.from(input.bytes);
|
|
1379
|
-
const expectedDigest = sha256Bytes$1(bytes);
|
|
1380
|
-
const ref = agentCandidateArtifactRefSchema.parse(await port.put({
|
|
1381
|
-
executionId: input.executionId,
|
|
1382
|
-
purpose: input.purpose,
|
|
1383
|
-
bytes: Uint8Array.from(bytes),
|
|
1384
|
-
...input.signal ? { signal: input.signal } : {}
|
|
1385
|
-
}));
|
|
1386
|
-
input.signal?.throwIfAborted();
|
|
1387
|
-
if (ref.sha256 !== expectedDigest || ref.byteLength !== bytes.byteLength) throw new Error("candidate output locator does not identify the submitted bytes");
|
|
1388
|
-
const stored = await port.read(ref);
|
|
1389
|
-
input.signal?.throwIfAborted();
|
|
1390
|
-
verifyBytes(stored, expectedDigest, bytes.byteLength, "persisted candidate output");
|
|
1391
|
-
return immutableCandidateValue(ref);
|
|
1392
|
-
}
|
|
1393
|
-
//#endregion
|
|
1394
|
-
//#region src/candidate-execution/workspace-archive.ts
|
|
1395
|
-
const archiveKind = "agent-candidate-workspace-archive";
|
|
1396
|
-
const workspaceEntryPrefix = "workspace/";
|
|
1397
|
-
const repositoryMetadataEntry = "metadata/repository.json";
|
|
1398
|
-
const repositoryBundleEntry = "metadata/repository.bundle";
|
|
1399
|
-
const repositoryIndexEntry = "metadata/repository.index.json";
|
|
1400
|
-
const repositoryIndexPrefix = "index/";
|
|
1401
|
-
const fixedTarTime = /* @__PURE__ */ new Date(0);
|
|
1402
|
-
const defaultLimits = Object.freeze({
|
|
1403
|
-
maxArchiveBytes: 512 * 1024 * 1024,
|
|
1404
|
-
maxEmbeddedArtifactBytes: 1024 * 1024,
|
|
1405
|
-
maxFiles: 5e4,
|
|
1406
|
-
maxFileBytes: 128 * 1024 * 1024,
|
|
1407
|
-
maxTotalFileBytes: 256 * 1024 * 1024,
|
|
1408
|
-
maxPathBytes: 4096,
|
|
1409
|
-
maxRepositoryBundleBytes: 128 * 1024 * 1024
|
|
1410
|
-
});
|
|
1815
|
+
//#region src/candidate-execution/workspace-archive-local.ts
|
|
1411
1816
|
/** Capture one exact regular-file workspace for immutable candidate execution. */
|
|
1412
|
-
async function captureAgentCandidateWorkspace(rootInput, options = {}) {
|
|
1413
|
-
const root = resolve(rootInput);
|
|
1414
|
-
const limits = workspaceLimits(options.limits);
|
|
1415
|
-
if (options.repositoryContents !== void 0 && options.repositoryContents !== "head" && options.repositoryContents !== "working-tree") throw new Error("candidate repositoryContents is invalid");
|
|
1416
|
-
if (options.repositoryContents !== void 0 && !options.includeRepository) throw new Error("candidate repositoryContents requires includeRepository");
|
|
1417
|
-
const captured = options.includeRepository ? await captureRepository(root, limits, options.repositoryContents === "working-tree") : await captureMaterializedWorkspace(root, { limits: {
|
|
1418
|
-
maxFiles: limits.maxFiles,
|
|
1419
|
-
maxFileBytes: limits.maxFileBytes,
|
|
1420
|
-
maxTotalFileBytes: limits.maxTotalFileBytes
|
|
1421
|
-
} });
|
|
1422
|
-
assertWorkspaceFilesWithinLimits(captured.files, limits);
|
|
1423
|
-
const repository = "repository" in captured ? captured.repository : void 0;
|
|
1424
|
-
return captureWorkspaceFiles(captured.files, repository, limits, options.artifactPersistence);
|
|
1425
|
-
}
|
|
1426
|
-
/** Capture detached files returned by a remote executor into the standard archive. */
|
|
1427
|
-
async function captureAgentCandidateWorkspaceFiles(input, options = {}) {
|
|
1428
|
-
const limits = workspaceLimits(options.limits);
|
|
1429
|
-
return captureWorkspaceFiles(normalizeWorkspaceFiles(input, limits), void 0, limits, options.artifactPersistence);
|
|
1430
|
-
}
|
|
1431
|
-
async function captureWorkspaceFiles(files, repository, limits, artifactPersistence) {
|
|
1432
|
-
const material = candidateWorkspaceManifest(files);
|
|
1433
|
-
const manifest = canonicalCandidateBytes$1(material);
|
|
1434
|
-
const archive = await encodeWorkspaceArchive(files, repository, limits.maxArchiveBytes);
|
|
1435
|
-
if (!artifactPersistence && (manifest.byteLength > limits.maxEmbeddedArtifactBytes || archive.byteLength > limits.maxEmbeddedArtifactBytes)) throw new Error("candidate workspace artifacts exceed maxEmbeddedArtifactBytes; artifactPersistence is required");
|
|
1436
|
-
const manifestArtifact = await captureWorkspaceArtifact("manifest", manifest, artifactPersistence);
|
|
1437
|
-
const archiveArtifact = await captureWorkspaceArtifact("archive", archive, artifactPersistence);
|
|
1438
|
-
const snapshot = deepFreezeCandidate({
|
|
1439
|
-
kind: "agent-candidate-workspace-snapshot",
|
|
1440
|
-
digest: canonicalCandidateDigest$1(material),
|
|
1441
|
-
material,
|
|
1442
|
-
manifest: manifestArtifact,
|
|
1443
|
-
archive: archiveArtifact
|
|
1444
|
-
});
|
|
1445
|
-
return Object.freeze({
|
|
1446
|
-
snapshot,
|
|
1447
|
-
archive
|
|
1448
|
-
});
|
|
1449
|
-
}
|
|
1450
|
-
async function captureWorkspaceArtifact(kind, bytes, persistence) {
|
|
1451
|
-
if (!persistence) return embeddedCandidateArtifact(bytes);
|
|
1452
|
-
if (!persistence.executionId.trim()) throw new Error("candidate workspace artifact persistence requires an executionId");
|
|
1453
|
-
return persistCandidateOutputArtifact(persistence.outputArtifacts, {
|
|
1454
|
-
executionId: persistence.executionId,
|
|
1455
|
-
purpose: kind === "manifest" ? "candidate-workspace-manifest" : "candidate-workspace-archive",
|
|
1456
|
-
bytes,
|
|
1457
|
-
...persistence.signal ? { signal: persistence.signal } : {}
|
|
1458
|
-
});
|
|
1817
|
+
async function captureAgentCandidateWorkspace(rootInput, options = {}) {
|
|
1818
|
+
const root = resolve(rootInput);
|
|
1819
|
+
const limits = workspaceLimits(options.limits);
|
|
1820
|
+
if (options.repositoryContents !== void 0 && options.repositoryContents !== "head" && options.repositoryContents !== "working-tree") throw new Error("candidate repositoryContents is invalid");
|
|
1821
|
+
if (options.repositoryContents !== void 0 && !options.includeRepository) throw new Error("candidate repositoryContents requires includeRepository");
|
|
1822
|
+
const captured = options.includeRepository ? await captureRepository(root, limits, options.repositoryContents === "working-tree") : await captureMaterializedWorkspace(root, { limits: {
|
|
1823
|
+
maxFiles: limits.maxFiles,
|
|
1824
|
+
maxFileBytes: limits.maxFileBytes,
|
|
1825
|
+
maxTotalFileBytes: limits.maxTotalFileBytes
|
|
1826
|
+
} });
|
|
1827
|
+
assertWorkspaceFilesWithinLimits(captured.files, limits);
|
|
1828
|
+
const repository = "repository" in captured ? captured.repository : void 0;
|
|
1829
|
+
return captureWorkspaceFiles(captured.files, repository, limits, options.artifactPersistence);
|
|
1459
1830
|
}
|
|
1460
1831
|
/** Create the standard bounded materializer for candidate execution ports. */
|
|
1461
1832
|
function createAgentCandidateWorkspacePort(options = {}) {
|
|
@@ -1495,20 +1866,6 @@ async function materializeAgentCandidateWorkspace(input) {
|
|
|
1495
1866
|
throw error;
|
|
1496
1867
|
}
|
|
1497
1868
|
}
|
|
1498
|
-
/** Verify a portable archive before its source environment may be discarded. */
|
|
1499
|
-
async function verifyAgentCandidateWorkspaceArchive(input) {
|
|
1500
|
-
await decodeVerifiedWorkspaceArchive({
|
|
1501
|
-
...input,
|
|
1502
|
-
limits: workspaceLimits(input.limits)
|
|
1503
|
-
});
|
|
1504
|
-
}
|
|
1505
|
-
async function decodeVerifiedWorkspaceArchive(input) {
|
|
1506
|
-
verifyBytes(input.archive, input.snapshot.archive.sha256, input.snapshot.archive.byteLength, "candidate workspace archive");
|
|
1507
|
-
const decoded = await parseWorkspaceArchive(input.archive, input.limits);
|
|
1508
|
-
if (decoded.repository && input.role !== "task") throw new Error("only task workspaces may carry a Git repository");
|
|
1509
|
-
assertArchiveMatchesSnapshot(decoded.files, input.snapshot);
|
|
1510
|
-
return decoded;
|
|
1511
|
-
}
|
|
1512
1869
|
async function captureRepository(requestedRoot, limits, workingTree) {
|
|
1513
1870
|
const requestedStats = await lstat(requestedRoot);
|
|
1514
1871
|
if (!requestedStats.isDirectory() || requestedStats.isSymbolicLink()) throw new Error("candidate repository root must be a real directory");
|
|
@@ -1611,309 +1968,78 @@ async function readRepositoryWorkingState(root, limits) {
|
|
|
1611
1968
|
if (size > limits.maxFileBytes) throw new Error(`candidate Git index file exceeds maxFileBytes: ${entry.path}`);
|
|
1612
1969
|
if (size > limits.maxTotalFileBytes - totalBytes) throw new Error("candidate Git index exceeds maxTotalFileBytes");
|
|
1613
1970
|
totalBytes += size;
|
|
1614
|
-
return size;
|
|
1615
|
-
});
|
|
1616
|
-
const files = [];
|
|
1617
|
-
for (const [index, entry] of entries.entries()) {
|
|
1618
|
-
const bytes = (await runCandidateGit(root, [
|
|
1619
|
-
"cat-file",
|
|
1620
|
-
"blob",
|
|
1621
|
-
entry.object
|
|
1622
|
-
], void 0, env, { maxOutputBytes: limits.maxFileBytes })).stdout;
|
|
1623
|
-
if (bytes.byteLength !== byteLengths[index]) throw new Error("candidate Git index blob size changed");
|
|
1624
|
-
files.push({
|
|
1625
|
-
path: entry.path,
|
|
1626
|
-
mode: entry.mode,
|
|
1627
|
-
bytes: Uint8Array.from(bytes)
|
|
1628
|
-
});
|
|
1629
|
-
}
|
|
1630
|
-
files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
1631
|
-
assertWorkspaceFilesWithinLimits(files, limits);
|
|
1632
|
-
const defaultExcludes = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "git", "ignore");
|
|
1633
|
-
const excludes = new TextDecoder("utf-8", { fatal: true }).decode((await runCandidateGit(root, [
|
|
1634
|
-
"config",
|
|
1635
|
-
"--includes",
|
|
1636
|
-
"--path",
|
|
1637
|
-
"--default",
|
|
1638
|
-
defaultExcludes,
|
|
1639
|
-
"--get",
|
|
1640
|
-
"core.excludesFile"
|
|
1641
|
-
], void 0, {}, {
|
|
1642
|
-
maxOutputBytes: limits.maxPathBytes,
|
|
1643
|
-
readUserExclusions: true
|
|
1644
|
-
})).stdout).replace(/\n$/u, "");
|
|
1645
|
-
const paths = new TextDecoder("utf-8", { fatal: true }).decode((await runCandidateGit(root, [
|
|
1646
|
-
"-c",
|
|
1647
|
-
`core.excludesFile=${excludes}`,
|
|
1648
|
-
"ls-files",
|
|
1649
|
-
"--cached",
|
|
1650
|
-
"--others",
|
|
1651
|
-
"--exclude-standard",
|
|
1652
|
-
"-z"
|
|
1653
|
-
], void 0, env, { maxOutputBytes: listingLimit })).stdout);
|
|
1654
|
-
for (const path of paths.split("\0").filter(Boolean)) safeArchivePath(path, limits.maxPathBytes);
|
|
1655
|
-
return {
|
|
1656
|
-
paths,
|
|
1657
|
-
index,
|
|
1658
|
-
files
|
|
1659
|
-
};
|
|
1660
|
-
}
|
|
1661
|
-
async function captureRepositoryWorkingTree(root, limits) {
|
|
1662
|
-
const state = await readRepositoryWorkingState(root, limits);
|
|
1663
|
-
return {
|
|
1664
|
-
state,
|
|
1665
|
-
files: [...(await captureMaterializedWorkspace(root, {
|
|
1666
|
-
ignoredProtectedRootEntries: [".git"],
|
|
1667
|
-
selectedPaths: await existingRepositoryPaths(root, state.paths, limits),
|
|
1668
|
-
limits
|
|
1669
|
-
})).files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
|
|
1670
|
-
};
|
|
1671
|
-
}
|
|
1672
|
-
async function existingRepositoryPaths(root, listing, limits) {
|
|
1673
|
-
const paths = [...new Set(listing.split("\0").filter(Boolean))].sort();
|
|
1674
|
-
if (paths.length > limits.maxFiles) throw new Error("candidate workspace exceeds maxFiles");
|
|
1675
|
-
const selectedPaths = [];
|
|
1676
|
-
for (const path of paths) {
|
|
1677
|
-
const safe = safeArchivePath(path, limits.maxPathBytes);
|
|
1678
|
-
try {
|
|
1679
|
-
await lstat(workspacePath(root, safe));
|
|
1680
|
-
selectedPaths.push(safe);
|
|
1681
|
-
} catch (error) {
|
|
1682
|
-
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
return selectedPaths;
|
|
1686
|
-
}
|
|
1687
|
-
async function encodeWorkspaceArchive(files, repository, maxArchiveBytes) {
|
|
1688
|
-
const archive = pack();
|
|
1689
|
-
const output = collectStream(archive, maxArchiveBytes, "candidate workspace archive");
|
|
1690
|
-
try {
|
|
1691
|
-
await writeWorkspaceArchiveEntries(archive, files, repository);
|
|
1692
|
-
archive.finalize();
|
|
1693
|
-
return await output;
|
|
1694
|
-
} catch (error) {
|
|
1695
|
-
archive.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
1696
|
-
await output.catch(() => void 0);
|
|
1697
|
-
throw error;
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
async function verifyCanonicalWorkspaceArchive(files, repository, expected, maxArchiveBytes) {
|
|
1701
|
-
const archive = pack();
|
|
1702
|
-
const comparison = streamEqualsBytes(archive, expected, maxArchiveBytes, "candidate workspace archive");
|
|
1703
|
-
try {
|
|
1704
|
-
await writeWorkspaceArchiveEntries(archive, files, repository);
|
|
1705
|
-
archive.finalize();
|
|
1706
|
-
if (!await comparison) throw new Error("candidate workspace tar is not canonical");
|
|
1707
|
-
} catch (error) {
|
|
1708
|
-
archive.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
1709
|
-
await comparison.catch(() => void 0);
|
|
1710
|
-
throw error;
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
async function writeWorkspaceArchiveEntries(archive, files, repository) {
|
|
1714
|
-
for (const file of files) await writeTarEntry(archive, `${workspaceEntryPrefix}${file.path}`, file.mode, file.bytes);
|
|
1715
|
-
if (!repository) return;
|
|
1716
|
-
const indexManifest = repository.workingTree ? canonicalCandidateBytes$1(candidateWorkspaceManifest(repository.workingTree.files)) : void 0;
|
|
1717
|
-
const metadata = canonicalCandidateBytes$1({
|
|
1718
|
-
kind: archiveKind,
|
|
1719
|
-
headCommit: repository.headCommit,
|
|
1720
|
-
headTree: repository.headTree,
|
|
1721
|
-
bundle: {
|
|
1722
|
-
sha256: repository.bundle.sha256,
|
|
1723
|
-
byteLength: repository.bundle.byteLength
|
|
1724
|
-
},
|
|
1725
|
-
...indexManifest ? { workingTree: { indexManifest: {
|
|
1726
|
-
sha256: sha256Bytes$1(indexManifest),
|
|
1727
|
-
byteLength: indexManifest.byteLength
|
|
1728
|
-
} } } : {}
|
|
1729
|
-
});
|
|
1730
|
-
await writeTarEntry(archive, repositoryMetadataEntry, 384, metadata);
|
|
1731
|
-
await writeTarEntry(archive, repositoryBundleEntry, 384, repository.bundle.bytes);
|
|
1732
|
-
if (repository.workingTree && indexManifest) {
|
|
1733
|
-
await writeTarEntry(archive, repositoryIndexEntry, 384, indexManifest);
|
|
1734
|
-
for (const file of repository.workingTree.files) await writeTarEntry(archive, `${repositoryIndexPrefix}${file.path}`, file.mode, file.bytes);
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
async function parseWorkspaceArchive(bytes, limits) {
|
|
1738
|
-
if (bytes.byteLength > limits.maxArchiveBytes) throw new Error("candidate workspace archive exceeds maxArchiveBytes");
|
|
1739
|
-
const parser = extract();
|
|
1740
|
-
const files = [];
|
|
1741
|
-
const observedEntryNames = /* @__PURE__ */ new Set();
|
|
1742
|
-
const observedPaths = /* @__PURE__ */ new Set();
|
|
1743
|
-
let totalBytes = 0;
|
|
1744
|
-
let retainedEntryBytes = 0;
|
|
1745
|
-
let previousPath;
|
|
1746
|
-
let repositoryMetadata;
|
|
1747
|
-
let repositoryBundle;
|
|
1748
|
-
let repositoryIndex;
|
|
1749
|
-
const indexFiles = [];
|
|
1750
|
-
const indexPaths = /* @__PURE__ */ new Set();
|
|
1751
|
-
let previousIndexPath;
|
|
1752
|
-
let indexBytes = 0;
|
|
1753
|
-
const decoded = (async () => {
|
|
1754
|
-
for await (const entry of parser) {
|
|
1755
|
-
const name = entry.header.name;
|
|
1756
|
-
if (entry.header.type !== "file" || typeof name !== "string" || observedEntryNames.has(name)) throw new Error("candidate workspace tar contains an invalid entry");
|
|
1757
|
-
observedEntryNames.add(name);
|
|
1758
|
-
if (name.startsWith(workspaceEntryPrefix)) {
|
|
1759
|
-
if (files.length >= limits.maxFiles) throw new Error("candidate workspace archive has an invalid file count");
|
|
1760
|
-
const path = safeArchivePath(name.slice(10), limits.maxPathBytes);
|
|
1761
|
-
if (!isWorkspaceFileMode(entry.header.mode)) throw new Error(`candidate workspace archive has an unsupported mode: ${path}`);
|
|
1762
|
-
assertRetainedArchiveSize(bytes.byteLength, retainedEntryBytes, entry.header.size, limits);
|
|
1763
|
-
const fileBytes = await readTarEntry(entry, limits.maxFileBytes, `workspace file ${path}`);
|
|
1764
|
-
retainedEntryBytes += fileBytes.byteLength;
|
|
1765
|
-
assertWorkspacePathOrder(path, previousPath, observedPaths, "candidate workspace archive paths must be unique and sorted");
|
|
1766
|
-
previousPath = path;
|
|
1767
|
-
if (fileBytes.byteLength > limits.maxTotalFileBytes - totalBytes) throw new Error("candidate workspace archive exceeds maxTotalFileBytes");
|
|
1768
|
-
totalBytes += fileBytes.byteLength;
|
|
1769
|
-
files.push({
|
|
1770
|
-
path,
|
|
1771
|
-
mode: entry.header.mode,
|
|
1772
|
-
bytes: fileBytes
|
|
1773
|
-
});
|
|
1774
|
-
} else if (name === repositoryMetadataEntry) {
|
|
1775
|
-
if (entry.header.mode !== 384 || repositoryMetadata) throw new Error("candidate workspace archive repository metadata is invalid");
|
|
1776
|
-
assertRetainedArchiveSize(bytes.byteLength, retainedEntryBytes, entry.header.size, limits);
|
|
1777
|
-
const metadataBytes = await readTarEntry(entry, limits.maxPathBytes, "candidate repository metadata");
|
|
1778
|
-
retainedEntryBytes += metadataBytes.byteLength;
|
|
1779
|
-
repositoryMetadata = parseRepositoryMetadata(metadataBytes, limits);
|
|
1780
|
-
} else if (name === repositoryBundleEntry) {
|
|
1781
|
-
if (entry.header.mode !== 384 || repositoryBundle) throw new Error("candidate workspace archive repository bundle is invalid");
|
|
1782
|
-
assertRetainedArchiveSize(bytes.byteLength, retainedEntryBytes, entry.header.size, limits);
|
|
1783
|
-
repositoryBundle = await readTarEntry(entry, limits.maxRepositoryBundleBytes, "candidate Git bundle");
|
|
1784
|
-
retainedEntryBytes += repositoryBundle.byteLength;
|
|
1785
|
-
} else if (name === repositoryIndexEntry) {
|
|
1786
|
-
if (entry.header.mode !== 384 || repositoryIndex) throw new Error("candidate workspace archive repository index is invalid");
|
|
1787
|
-
assertRetainedArchiveSize(bytes.byteLength, retainedEntryBytes, entry.header.size, limits);
|
|
1788
|
-
repositoryIndex = await readTarEntry(entry, limits.maxArchiveBytes, "candidate Git index manifest");
|
|
1789
|
-
retainedEntryBytes += repositoryIndex.byteLength;
|
|
1790
|
-
} else if (name.startsWith(repositoryIndexPrefix)) {
|
|
1791
|
-
if (!repositoryMetadata?.workingTree || !repositoryIndex || indexFiles.length >= limits.maxFiles) throw new Error("candidate Git index evidence is incomplete or exceeds maxFiles");
|
|
1792
|
-
const path = safeArchivePath(name.slice(6), limits.maxPathBytes);
|
|
1793
|
-
if (entry.header.mode !== 420 && entry.header.mode !== 493) throw new Error("candidate Git index contains an unsupported mode");
|
|
1794
|
-
assertWorkspacePathOrder(path, previousIndexPath, indexPaths, "candidate Git index paths must be unique and sorted");
|
|
1795
|
-
previousIndexPath = path;
|
|
1796
|
-
assertRetainedArchiveSize(bytes.byteLength, retainedEntryBytes, entry.header.size, limits);
|
|
1797
|
-
if (entry.header.size === void 0 || entry.header.size > limits.maxTotalFileBytes - indexBytes) throw new Error("candidate Git index exceeds maxTotalFileBytes");
|
|
1798
|
-
if (entry.header.size > limits.maxFileBytes) throw new Error("candidate Git index exceeds maxFileBytes");
|
|
1799
|
-
const fileBytes = await readTarEntry(entry, limits.maxFileBytes, `Git index file ${path}`);
|
|
1800
|
-
retainedEntryBytes += fileBytes.byteLength;
|
|
1801
|
-
indexBytes += fileBytes.byteLength;
|
|
1802
|
-
indexFiles.push({
|
|
1803
|
-
path,
|
|
1804
|
-
mode: entry.header.mode,
|
|
1805
|
-
bytes: fileBytes
|
|
1806
|
-
});
|
|
1807
|
-
} else throw new Error(`candidate workspace tar contains an unsupported entry: ${name}`);
|
|
1808
|
-
}
|
|
1809
|
-
if (Boolean(repositoryMetadata) !== Boolean(repositoryBundle)) throw new Error("candidate workspace archive repository evidence is incomplete");
|
|
1810
|
-
if (Boolean(repositoryMetadata?.workingTree) !== Boolean(repositoryIndex)) throw new Error("candidate workspace archive repository index evidence is incomplete");
|
|
1811
|
-
const repository = repositoryMetadata && repositoryBundle ? repositoryFromTar(repositoryMetadata, repositoryBundle, repositoryIndex, indexFiles) : void 0;
|
|
1812
|
-
return {
|
|
1813
|
-
files,
|
|
1814
|
-
...repository ? { repository } : {}
|
|
1815
|
-
};
|
|
1816
|
-
})();
|
|
1817
|
-
parser.end(Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength));
|
|
1818
|
-
const result = await decoded;
|
|
1819
|
-
await verifyCanonicalWorkspaceArchive(result.files, result.repository, bytes, limits.maxArchiveBytes);
|
|
1820
|
-
return result;
|
|
1821
|
-
}
|
|
1822
|
-
function assertRetainedArchiveSize(archiveBytes, retainedEntryBytes, entryBytes, limits) {
|
|
1823
|
-
if (!Number.isSafeInteger(entryBytes) || entryBytes === void 0 || entryBytes < 0 || entryBytes > limits.maxArchiveBytes - archiveBytes - retainedEntryBytes) throw new Error("candidate workspace archive exceeds maxArchiveBytes while decoding");
|
|
1824
|
-
}
|
|
1825
|
-
function parseRepositoryMetadata(bytes, limits) {
|
|
1826
|
-
let value;
|
|
1827
|
-
try {
|
|
1828
|
-
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
1829
|
-
} catch (error) {
|
|
1830
|
-
throw new Error("candidate workspace archive repository metadata is invalid", { cause: error });
|
|
1831
|
-
}
|
|
1832
|
-
if (!isRecord$1(value) || !["bundle,headCommit,headTree,kind", "bundle,headCommit,headTree,kind,workingTree"].includes(Object.keys(value).sort().join(",")) || value.kind !== archiveKind || typeof value.headCommit !== "string" || typeof value.headTree !== "string" || !isRecord$1(value.bundle) || Object.keys(value.bundle).sort().join(",") !== "byteLength,sha256" || typeof value.bundle.sha256 !== "string" || !/^sha256:[a-f0-9]{64}$/.test(value.bundle.sha256) || !Number.isSafeInteger(value.bundle.byteLength) || value.bundle.byteLength <= 0 || value.bundle.byteLength > limits.maxRepositoryBundleBytes) throw new Error("candidate workspace archive repository is invalid");
|
|
1833
|
-
if (!Buffer.from(canonicalCandidateBytes$1(value)).equals(Buffer.from(bytes))) throw new Error("candidate workspace archive repository metadata is not canonical");
|
|
1834
|
-
assertGitObjectId(value.headCommit, "repository HEAD");
|
|
1835
|
-
assertGitObjectId(value.headTree, "repository HEAD tree");
|
|
1836
|
-
if (value.headCommit.length !== value.headTree.length) throw new Error("candidate repository HEAD and tree use different object formats");
|
|
1837
|
-
let workingTree;
|
|
1838
|
-
if ("workingTree" in value) {
|
|
1839
|
-
const working = value.workingTree;
|
|
1840
|
-
if (!isRecord$1(working) || Object.keys(working).join(",") !== "indexManifest" || !isRecord$1(working.indexManifest) || Object.keys(working.indexManifest).sort().join(",") !== "byteLength,sha256" || typeof working.indexManifest.sha256 !== "string" || !/^sha256:[a-f0-9]{64}$/.test(working.indexManifest.sha256) || !Number.isSafeInteger(working.indexManifest.byteLength) || working.indexManifest.byteLength < 0 || working.indexManifest.byteLength > limits.maxArchiveBytes) throw new Error("candidate workspace archive repository index is invalid");
|
|
1841
|
-
workingTree = { indexManifest: {
|
|
1842
|
-
sha256: working.indexManifest.sha256,
|
|
1843
|
-
byteLength: working.indexManifest.byteLength
|
|
1844
|
-
} };
|
|
1971
|
+
return size;
|
|
1972
|
+
});
|
|
1973
|
+
const files = [];
|
|
1974
|
+
for (const [index, entry] of entries.entries()) {
|
|
1975
|
+
const bytes = (await runCandidateGit(root, [
|
|
1976
|
+
"cat-file",
|
|
1977
|
+
"blob",
|
|
1978
|
+
entry.object
|
|
1979
|
+
], void 0, env, { maxOutputBytes: limits.maxFileBytes })).stdout;
|
|
1980
|
+
if (bytes.byteLength !== byteLengths[index]) throw new Error("candidate Git index blob size changed");
|
|
1981
|
+
files.push({
|
|
1982
|
+
path: entry.path,
|
|
1983
|
+
mode: entry.mode,
|
|
1984
|
+
bytes: Uint8Array.from(bytes)
|
|
1985
|
+
});
|
|
1845
1986
|
}
|
|
1987
|
+
files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
1988
|
+
assertWorkspaceFilesWithinLimits(files, limits);
|
|
1989
|
+
const defaultExcludes = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "git", "ignore");
|
|
1990
|
+
const excludes = new TextDecoder("utf-8", { fatal: true }).decode((await runCandidateGit(root, [
|
|
1991
|
+
"config",
|
|
1992
|
+
"--includes",
|
|
1993
|
+
"--path",
|
|
1994
|
+
"--default",
|
|
1995
|
+
defaultExcludes,
|
|
1996
|
+
"--get",
|
|
1997
|
+
"core.excludesFile"
|
|
1998
|
+
], void 0, {}, {
|
|
1999
|
+
maxOutputBytes: limits.maxPathBytes,
|
|
2000
|
+
readUserExclusions: true
|
|
2001
|
+
})).stdout).replace(/\n$/u, "");
|
|
2002
|
+
const paths = new TextDecoder("utf-8", { fatal: true }).decode((await runCandidateGit(root, [
|
|
2003
|
+
"-c",
|
|
2004
|
+
`core.excludesFile=${excludes}`,
|
|
2005
|
+
"ls-files",
|
|
2006
|
+
"--cached",
|
|
2007
|
+
"--others",
|
|
2008
|
+
"--exclude-standard",
|
|
2009
|
+
"-z"
|
|
2010
|
+
], void 0, env, { maxOutputBytes: listingLimit })).stdout);
|
|
2011
|
+
for (const path of paths.split("\0").filter(Boolean)) safeArchivePath(path, limits.maxPathBytes);
|
|
1846
2012
|
return {
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
bundle: {
|
|
1851
|
-
sha256: value.bundle.sha256,
|
|
1852
|
-
byteLength: value.bundle.byteLength
|
|
1853
|
-
},
|
|
1854
|
-
...workingTree ? { workingTree } : {}
|
|
2013
|
+
paths,
|
|
2014
|
+
index,
|
|
2015
|
+
files
|
|
1855
2016
|
};
|
|
1856
2017
|
}
|
|
1857
|
-
function
|
|
1858
|
-
|
|
1859
|
-
if (metadata.workingTree && indexManifest) {
|
|
1860
|
-
verifyBytes(indexManifest, metadata.workingTree.indexManifest.sha256, metadata.workingTree.indexManifest.byteLength, "candidate Git index manifest");
|
|
1861
|
-
if (!Buffer.from(indexManifest).equals(Buffer.from(canonicalCandidateBytes$1(candidateWorkspaceManifest(indexFiles))))) throw new Error("candidate Git index files do not match the captured manifest");
|
|
1862
|
-
}
|
|
2018
|
+
async function captureRepositoryWorkingTree(root, limits) {
|
|
2019
|
+
const state = await readRepositoryWorkingState(root, limits);
|
|
1863
2020
|
return {
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
},
|
|
1870
|
-
...metadata.workingTree && indexManifest ? { workingTree: { files: indexFiles } } : {}
|
|
2021
|
+
state,
|
|
2022
|
+
files: [...(await captureMaterializedWorkspace(root, {
|
|
2023
|
+
ignoredProtectedRootEntries: [".git"],
|
|
2024
|
+
selectedPaths: await existingRepositoryPaths(root, state.paths, limits),
|
|
2025
|
+
limits
|
|
2026
|
+
})).files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
|
|
1871
2027
|
};
|
|
1872
2028
|
}
|
|
1873
|
-
async function
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
}
|
|
1885
|
-
async function collectStream(stream, maxBytes, label) {
|
|
1886
|
-
const chunks = [];
|
|
1887
|
-
let totalBytes = 0;
|
|
1888
|
-
for await (const chunk of stream) {
|
|
1889
|
-
if (!(chunk instanceof Uint8Array)) throw new Error(`${label} emitted a non-byte chunk`);
|
|
1890
|
-
if (chunk.byteLength > maxBytes - totalBytes) {
|
|
1891
|
-
const error = /* @__PURE__ */ new Error(`${label} exceeds its size limit`);
|
|
1892
|
-
if ("destroy" in stream && typeof stream.destroy === "function") stream.destroy(error);
|
|
1893
|
-
throw error;
|
|
1894
|
-
}
|
|
1895
|
-
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1896
|
-
chunks.push(bytes);
|
|
1897
|
-
totalBytes += bytes.byteLength;
|
|
1898
|
-
}
|
|
1899
|
-
return Buffer.concat(chunks, totalBytes);
|
|
1900
|
-
}
|
|
1901
|
-
async function streamEqualsBytes(stream, expected, maxBytes, label) {
|
|
1902
|
-
const expectedBuffer = Buffer.from(expected.buffer, expected.byteOffset, expected.byteLength);
|
|
1903
|
-
let equal = true;
|
|
1904
|
-
let offset = 0;
|
|
1905
|
-
for await (const chunk of stream) {
|
|
1906
|
-
if (!(chunk instanceof Uint8Array)) throw new Error(`${label} emitted a non-byte chunk`);
|
|
1907
|
-
if (chunk.byteLength > maxBytes - offset) {
|
|
1908
|
-
const error = /* @__PURE__ */ new Error(`${label} exceeds its size limit`);
|
|
1909
|
-
if ("destroy" in stream && typeof stream.destroy === "function") stream.destroy(error);
|
|
1910
|
-
throw error;
|
|
2029
|
+
async function existingRepositoryPaths(root, listing, limits) {
|
|
2030
|
+
const paths = [...new Set(listing.split("\0").filter(Boolean))].sort();
|
|
2031
|
+
if (paths.length > limits.maxFiles) throw new Error("candidate workspace exceeds maxFiles");
|
|
2032
|
+
const selectedPaths = [];
|
|
2033
|
+
for (const path of paths) {
|
|
2034
|
+
const safe = safeArchivePath(path, limits.maxPathBytes);
|
|
2035
|
+
try {
|
|
2036
|
+
await lstat(workspacePath(root, safe));
|
|
2037
|
+
selectedPaths.push(safe);
|
|
2038
|
+
} catch (error) {
|
|
2039
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
1911
2040
|
}
|
|
1912
|
-
const observed = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
1913
|
-
if (offset + observed.byteLength > expectedBuffer.byteLength || !observed.equals(expectedBuffer.subarray(offset, offset + observed.byteLength))) equal = false;
|
|
1914
|
-
offset += observed.byteLength;
|
|
1915
2041
|
}
|
|
1916
|
-
return
|
|
2042
|
+
return selectedPaths;
|
|
1917
2043
|
}
|
|
1918
2044
|
async function prepareEmptyDestination(destination, options = {}) {
|
|
1919
2045
|
await mkdir(options.createDestination === false ? dirname(destination) : destination, {
|
|
@@ -2033,143 +2159,11 @@ async function materializeRepository(repository, destination, limits, signal) {
|
|
|
2033
2159
|
});
|
|
2034
2160
|
}
|
|
2035
2161
|
}
|
|
2036
|
-
function assertArchiveMatchesSnapshot(files, snapshot) {
|
|
2037
|
-
const material = candidateWorkspaceManifest(files);
|
|
2038
|
-
const materialBytes = canonicalCandidateBytes$1(material);
|
|
2039
|
-
verifyBytes(materialBytes, snapshot.manifest.sha256, snapshot.manifest.byteLength, "candidate workspace manifest");
|
|
2040
|
-
if (canonicalCandidateDigest$1(material) !== snapshot.digest || !Buffer.from(materialBytes).equals(Buffer.from(canonicalCandidateBytes$1(snapshot.material)))) throw new Error("candidate workspace archive does not match its snapshot manifest");
|
|
2041
|
-
}
|
|
2042
|
-
function assertWorkspaceFilesWithinLimits(files, limits) {
|
|
2043
|
-
if (files.length > limits.maxFiles) throw new Error("candidate workspace exceeds maxFiles");
|
|
2044
|
-
let totalBytes = 0;
|
|
2045
|
-
let previousPath;
|
|
2046
|
-
const observedPaths = /* @__PURE__ */ new Set();
|
|
2047
|
-
for (const file of files) {
|
|
2048
|
-
const path = safeArchivePath(file.path, limits.maxPathBytes);
|
|
2049
|
-
assertWorkspacePathOrder(path, previousPath, observedPaths, "candidate workspace paths must be unique and sorted");
|
|
2050
|
-
previousPath = path;
|
|
2051
|
-
if (!isWorkspaceFileMode(file.mode)) throw new Error(`candidate workspace file has unsupported mode: ${path}`);
|
|
2052
|
-
if (file.bytes.byteLength > limits.maxFileBytes) throw new Error(`candidate workspace file exceeds maxFileBytes: ${path}`);
|
|
2053
|
-
totalBytes += file.bytes.byteLength;
|
|
2054
|
-
if (totalBytes > limits.maxTotalFileBytes) throw new Error("candidate workspace exceeds maxTotalFileBytes");
|
|
2055
|
-
}
|
|
2056
|
-
}
|
|
2057
|
-
function normalizeWorkspaceFiles(input, limits) {
|
|
2058
|
-
if (!Array.isArray(input)) throw new Error("candidate workspace files must be an array");
|
|
2059
|
-
const fileCount = input.length;
|
|
2060
|
-
if (fileCount > limits.maxFiles) throw new Error("candidate workspace exceeds maxFiles");
|
|
2061
|
-
let totalBytes = 0;
|
|
2062
|
-
const files = [];
|
|
2063
|
-
for (let index = 0; index < fileCount; index++) {
|
|
2064
|
-
const inputFile = input[index];
|
|
2065
|
-
if (!isRecord$1(inputFile) || Object.keys(inputFile).sort().join(",") !== "bytes,mode,path") throw new Error(`candidate workspace file ${index} is invalid`);
|
|
2066
|
-
const descriptors = Object.getOwnPropertyDescriptors(inputFile);
|
|
2067
|
-
if (!("value" in (descriptors.path ?? {})) || !("value" in (descriptors.mode ?? {})) || !("value" in (descriptors.bytes ?? {}))) throw new Error(`candidate workspace file ${index} must use fixed values`);
|
|
2068
|
-
const path = safeArchivePath(descriptors.path?.value, limits.maxPathBytes);
|
|
2069
|
-
const mode = descriptors.mode?.value;
|
|
2070
|
-
const inputBytes = descriptors.bytes?.value;
|
|
2071
|
-
if (!isWorkspaceFileMode(mode)) throw new Error(`candidate workspace file has unsupported mode: ${path}`);
|
|
2072
|
-
const view = inspectWorkspaceBytes(inputBytes, path);
|
|
2073
|
-
if (view.byteLength > limits.maxFileBytes) throw new Error(`candidate workspace file exceeds maxFileBytes: ${path}`);
|
|
2074
|
-
if (view.byteLength > limits.maxTotalFileBytes - totalBytes) throw new Error("candidate workspace exceeds maxTotalFileBytes");
|
|
2075
|
-
if (view.byteLength > Math.max(0, limits.maxArchiveBytes - 1024)) throw new Error("candidate workspace archive exceeds maxArchiveBytes");
|
|
2076
|
-
totalBytes += view.byteLength;
|
|
2077
|
-
files.push({
|
|
2078
|
-
path,
|
|
2079
|
-
mode,
|
|
2080
|
-
bytes: detachWorkspaceBytes(view)
|
|
2081
|
-
});
|
|
2082
|
-
}
|
|
2083
|
-
files.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
2084
|
-
assertWorkspaceFilesWithinLimits(files, limits);
|
|
2085
|
-
return files;
|
|
2086
|
-
}
|
|
2087
|
-
function isWorkspaceFileMode(value) {
|
|
2088
|
-
return Number.isSafeInteger(value) && value >= 0 && value <= 511;
|
|
2089
|
-
}
|
|
2090
|
-
function workspaceLimits(overrides) {
|
|
2091
|
-
const limits = {
|
|
2092
|
-
...defaultLimits,
|
|
2093
|
-
...overrides
|
|
2094
|
-
};
|
|
2095
|
-
for (const [name, value] of Object.entries(limits)) if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`candidate workspace ${name} must be a positive safe integer`);
|
|
2096
|
-
return Object.freeze(limits);
|
|
2097
|
-
}
|
|
2098
|
-
function safeArchivePath(value, maxPathBytes) {
|
|
2099
|
-
if (typeof value !== "string" || !value || !isWellFormedUnicode(value) || Buffer.byteLength(value, "utf8") > maxPathBytes || isAbsolute(value) || win32.isAbsolute(value) || value.includes("\\") || value.includes("\0") || hasControlCharacter(value) || value.split("/").some((part) => !part || part === "." || part === "..") || [".git", ".sidecar"].includes(value.split("/")[0]?.toLowerCase() ?? "")) throw new Error(`candidate workspace archive contains an unsafe path: ${String(value)}`);
|
|
2100
|
-
return value;
|
|
2101
|
-
}
|
|
2102
|
-
function assertWorkspacePathOrder(path, previousPath, observedPaths, errorMessage) {
|
|
2103
|
-
if (previousPath !== void 0 && previousPath >= path) throw new Error(errorMessage);
|
|
2104
|
-
for (let slash = path.indexOf("/"); slash !== -1; slash = path.indexOf("/", slash + 1)) if (observedPaths.has(path.slice(0, slash))) throw new Error(errorMessage);
|
|
2105
|
-
observedPaths.add(path);
|
|
2106
|
-
}
|
|
2107
|
-
function inspectWorkspaceBytes(input, path) {
|
|
2108
|
-
let buffer;
|
|
2109
|
-
let byteOffset;
|
|
2110
|
-
let byteLength;
|
|
2111
|
-
try {
|
|
2112
|
-
buffer = typedArrayBufferGetter.call(input);
|
|
2113
|
-
byteOffset = typedArrayByteOffsetGetter.call(input);
|
|
2114
|
-
byteLength = typedArrayByteLengthGetter.call(input);
|
|
2115
|
-
} catch {
|
|
2116
|
-
throw new Error(`candidate workspace file bytes are invalid: ${path}`);
|
|
2117
|
-
}
|
|
2118
|
-
if (isSharedArrayBuffer(buffer)) throw new Error(`candidate workspace file uses shared bytes: ${path}`);
|
|
2119
|
-
if (!isAnyArrayBuffer(buffer)) throw new Error(`candidate workspace file bytes are invalid: ${path}`);
|
|
2120
|
-
return {
|
|
2121
|
-
buffer,
|
|
2122
|
-
byteOffset,
|
|
2123
|
-
byteLength
|
|
2124
|
-
};
|
|
2125
|
-
}
|
|
2126
|
-
function detachWorkspaceBytes(input) {
|
|
2127
|
-
const { buffer, byteOffset, byteLength } = input;
|
|
2128
|
-
const copy = new Uint8Array(byteLength);
|
|
2129
|
-
Uint8Array.prototype.set.call(copy, new Uint8Array(buffer, byteOffset, byteLength));
|
|
2130
|
-
return copy;
|
|
2131
|
-
}
|
|
2132
2162
|
function workspacePath(root, relativePath) {
|
|
2133
2163
|
const path = resolve(root, relativePath);
|
|
2134
2164
|
if (!path.startsWith(`${root}${sep}`)) throw new Error(`candidate workspace archive path escapes its destination: ${relativePath}`);
|
|
2135
2165
|
return path;
|
|
2136
2166
|
}
|
|
2137
|
-
function assertGitObjectId(value, label) {
|
|
2138
|
-
if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(value)) throw new Error(`candidate repository ${label} is not a Git object id`);
|
|
2139
|
-
}
|
|
2140
|
-
function hasControlCharacter(value) {
|
|
2141
|
-
for (let index = 0; index < value.length; index++) {
|
|
2142
|
-
const code = value.charCodeAt(index);
|
|
2143
|
-
if (code < 32 || code === 127) return true;
|
|
2144
|
-
}
|
|
2145
|
-
return false;
|
|
2146
|
-
}
|
|
2147
|
-
function isRecord$1(value) {
|
|
2148
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2149
|
-
}
|
|
2150
|
-
function requireTypedArrayGetter(name) {
|
|
2151
|
-
const typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
|
|
2152
|
-
const getter = Object.getOwnPropertyDescriptor(typedArrayPrototype, name)?.get;
|
|
2153
|
-
if (!getter) throw new Error(`typed array intrinsic ${name} accessor is unavailable`);
|
|
2154
|
-
return getter;
|
|
2155
|
-
}
|
|
2156
|
-
const typedArrayBufferGetter = requireTypedArrayGetter("buffer");
|
|
2157
|
-
const typedArrayByteOffsetGetter = requireTypedArrayGetter("byteOffset");
|
|
2158
|
-
const typedArrayByteLengthGetter = requireTypedArrayGetter("byteLength");
|
|
2159
|
-
/** Shared canonical header for both archive encoders. */
|
|
2160
|
-
function workspaceTarHeader(name, mode, size) {
|
|
2161
|
-
return {
|
|
2162
|
-
name,
|
|
2163
|
-
mode,
|
|
2164
|
-
uid: 0,
|
|
2165
|
-
gid: 0,
|
|
2166
|
-
size,
|
|
2167
|
-
mtime: fixedTarTime,
|
|
2168
|
-
type: "file",
|
|
2169
|
-
uname: "",
|
|
2170
|
-
gname: ""
|
|
2171
|
-
};
|
|
2172
|
-
}
|
|
2173
2167
|
//#endregion
|
|
2174
2168
|
//#region src/candidate-execution/workspace-streams.ts
|
|
2175
2169
|
/**
|
|
@@ -2533,10 +2527,7 @@ function refuseNonPublicConfigValues(values, path, context) {
|
|
|
2533
2527
|
function refuseSandboxProfileField(field, context = "candidate profile") {
|
|
2534
2528
|
throw new ValidationError(`${context} field ${field} is refused: Sandbox's Interface 4.0.0 cannot carry it yet, and Runtime does not drop it`);
|
|
2535
2529
|
}
|
|
2536
|
-
function isRecord(value) {
|
|
2537
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2538
|
-
}
|
|
2539
2530
|
//#endregion
|
|
2540
|
-
export {
|
|
2531
|
+
export { hasCompleteCacheBreakdown as $, verifyMaterializedWorkspace as A, teardownSurfaces as At, omitTopLevelDigest as B, candidateWorkspaceManifest as C, ROOT_DEADLINE_REASON as Ct, scanMaterializedWorkspaceManifest as D, isDeadlineAbortReason as Dt, readVerifiedArtifact as E, checkInIdleMs as Et, canonicalCandidateDigest$1 as F, addSpend as G, verifyCanonicalCandidateDocument as H, canonicalCandidateDocument as I, cloneSpend as J, addTokenUsage as K, deepFreezeCandidate as L, markPutStreamVerified as M, verifiedArtifactChunks as N, verifyBytes as O, singleFlightTeardown as Ot, canonicalCandidateBytes$1 as P, fnv1a32Hex as Q, embeddedCandidateArtifact as R, artifactCacheKey as S, DEFAULT_SUCCESSFUL_SHUTDOWN_MS as St, readMaterializedWorkspaceFiles as T, boundedChildDeadlineAt as Tt, hasControlCharacter as U, sha256Bytes$1 as V, abortError as W, deleteBoxSafe as X, cloneTokenUsage as Y, errorMessage as Z, workspaceLimits as _, resourceTelemetry as _t, materializeAgentCandidateWorkspaceFromArtifacts as a, randomUuid as at, isWellFormedUnicode as b, CHILD_DEADLINE_REASON as bt, verifyAgentCandidateWorkspaceTree as c, stringifySafe as ct, readCandidateGitHubResource as d, unmeteredSpend as dt, isAbortError as et, verifyCandidateCode as f, withTimeout as ft, verifyAgentCandidateWorkspaceArchive as g, assertResources as gt, captureAgentCandidateWorkspaceFiles as h, addResourceSpend as ht, captureAgentCandidateWorkspaceTreeToArtifacts as i, randomSuffix as it, verifyWorkspaceSnapshotArtifacts as j, verifyMaterializedProfileWorkspace as k, teardownExecutor as kt, captureAgentCandidateWorkspace as l, throwAbort as lt, verifyTaskOutcomePatch as m, zeroTokenUsage as mt, refuseSandboxProfileField as n, mapWithConcurrency as nt, readAgentCandidateWorkspaceArtifactFiles as o, roundBoxMinutes as ot, verifyTaskCheckout as p, zeroSpend as pt, chargedTokens as q, captureAgentCandidateWorkspaceToArtifacts as r, promptCacheTokenClasses as rt, verifyAgentCandidateWorkspaceArtifacts as s, sleep as st, assertSandboxProfileCompatibility as t, isAsyncIterable as tt, createAgentCandidateWorkspacePort as u, throwIfAborted as ut, persistCandidateOutputArtifact as v, withBudgetResources as vt, captureMaterializedWorkspace as w, armDeadlineTimer as wt, WorkspaceScanLimitError as x, DEADLINE_CHECK_IN_MESSAGE as xt, assertExactObjectKeys as y, ACCEPTED_RESULT_GRACE_MS as yt, immutableCandidateValue as z };
|
|
2541
2532
|
|
|
2542
|
-
//# sourceMappingURL=sandbox-profile-compat-
|
|
2533
|
+
//# sourceMappingURL=sandbox-profile-compat-BzVBFqrz.js.map
|