@odla-ai/harness 0.11.10 → 0.11.12
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/{chunk-ZM6AITC2.js → chunk-3ON6UAOV.js} +3 -1
- package/dist/{chunk-4IGSN53G.js → chunk-6QILNLBY.js} +199 -75
- package/dist/chunk-6QILNLBY.js.map +1 -0
- package/dist/{chunk-GNNAQ2JX.js → chunk-EXSEQEZ6.js} +2 -2
- package/dist/cli.js +2 -2
- package/dist/code-runtime-cli.cjs +188 -102
- package/dist/code-runtime-cli.cjs.map +1 -1
- package/dist/code-runtime-cli.js +2 -2
- package/dist/node.cjs +326 -168
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +62 -10
- package/dist/node.d.ts +62 -10
- package/dist/node.js +116 -76
- package/dist/node.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-4IGSN53G.js.map +0 -1
- /package/dist/{chunk-ZM6AITC2.js.map → chunk-3ON6UAOV.js.map} +0 -0
- /package/dist/{chunk-GNNAQ2JX.js.map → chunk-EXSEQEZ6.js.map} +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// src/code-runtime-cli.ts
|
|
5
5
|
var import_node_os4 = require("os");
|
|
6
|
-
var
|
|
6
|
+
var import_promises17 = require("fs/promises");
|
|
7
7
|
|
|
8
8
|
// src/code-runtime-client-validation.ts
|
|
9
9
|
var import_code = require("@odla-ai/camel/code");
|
|
@@ -452,7 +452,7 @@ async function withOverloadRetry(call, wait2, onRetry = () => void 0, retryable
|
|
|
452
452
|
}
|
|
453
453
|
|
|
454
454
|
// src/code-runtime-reconciler.ts
|
|
455
|
-
var sleep = (ms) => new Promise((
|
|
455
|
+
var sleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
456
456
|
var POST_ACK_RETRY_TICKS = 40;
|
|
457
457
|
var CodeRuntimeReconciler = class {
|
|
458
458
|
constructor(control, engine, onDiagnostic, options = {}) {
|
|
@@ -554,12 +554,12 @@ function retryableControlFailure(value) {
|
|
|
554
554
|
return failure.status === 408 || failure.status === 425 || failure.status === 429 || failure.status >= 500;
|
|
555
555
|
}
|
|
556
556
|
function wait(ms, signal) {
|
|
557
|
-
return new Promise((
|
|
558
|
-
if (signal?.aborted) return
|
|
559
|
-
const timer = setTimeout(
|
|
557
|
+
return new Promise((resolve8) => {
|
|
558
|
+
if (signal?.aborted) return resolve8();
|
|
559
|
+
const timer = setTimeout(resolve8, ms);
|
|
560
560
|
signal?.addEventListener("abort", () => {
|
|
561
561
|
clearTimeout(timer);
|
|
562
|
-
|
|
562
|
+
resolve8();
|
|
563
563
|
}, { once: true });
|
|
564
564
|
});
|
|
565
565
|
}
|
|
@@ -981,12 +981,12 @@ async function restoreCodeWorkspaceCheckpoint(input) {
|
|
|
981
981
|
// src/code-verifier.ts
|
|
982
982
|
var import_node_crypto3 = require("crypto");
|
|
983
983
|
var import_node_fs2 = require("fs");
|
|
984
|
-
var
|
|
985
|
-
var
|
|
984
|
+
var import_promises7 = require("fs/promises");
|
|
985
|
+
var import_node_path8 = require("path");
|
|
986
986
|
var import_code3 = require("@odla-ai/camel/code");
|
|
987
987
|
|
|
988
988
|
// src/recipe-container.ts
|
|
989
|
-
var
|
|
989
|
+
var import_node_child_process5 = require("child_process");
|
|
990
990
|
var import_node_process2 = require("process");
|
|
991
991
|
var import_node_crypto = require("crypto");
|
|
992
992
|
|
|
@@ -1044,7 +1044,7 @@ async function selectContainerEngine(requested = "auto", options = {}) {
|
|
|
1044
1044
|
throw new TypeError("no supported container engine found");
|
|
1045
1045
|
}
|
|
1046
1046
|
function inspectRootlessPodman() {
|
|
1047
|
-
return new Promise((
|
|
1047
|
+
return new Promise((resolve8, reject) => {
|
|
1048
1048
|
(0, import_node_child_process3.execFile)(
|
|
1049
1049
|
"podman",
|
|
1050
1050
|
["info", "--format", "{{.Host.Security.Rootless}}"],
|
|
@@ -1054,7 +1054,7 @@ function inspectRootlessPodman() {
|
|
|
1054
1054
|
reject(new TypeError("could not verify that the active Podman service is rootless"));
|
|
1055
1055
|
return;
|
|
1056
1056
|
}
|
|
1057
|
-
|
|
1057
|
+
resolve8(stdout.trim() === "true");
|
|
1058
1058
|
}
|
|
1059
1059
|
);
|
|
1060
1060
|
});
|
|
@@ -1072,12 +1072,89 @@ async function verifyContainerEngineBoundary(engine, options = {}) {
|
|
|
1072
1072
|
if (!rootless) throw new TypeError("the active Podman service is not rootless; refusing to run the harness");
|
|
1073
1073
|
}
|
|
1074
1074
|
|
|
1075
|
+
// src/code-recipe-dependencies.ts
|
|
1076
|
+
var import_promises4 = require("fs/promises");
|
|
1077
|
+
var import_node_path5 = require("path");
|
|
1078
|
+
var RESERVED_MOUNTS = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage"]);
|
|
1079
|
+
function assertLentPath(mountAs) {
|
|
1080
|
+
const parts = mountAs.split("/");
|
|
1081
|
+
if ((0, import_node_path5.isAbsolute)(mountAs) || mountAs.includes("\\") || parts.some((part) => !part || part === "." || part === "..")) {
|
|
1082
|
+
throw new TypeError(`recipe dependencies must mount inside the workspace, not at "${mountAs}"`);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
function assertReservedMount(mountAs) {
|
|
1086
|
+
assertLentPath(mountAs);
|
|
1087
|
+
if (!RESERVED_MOUNTS.has(mountAs.split("/").at(-1))) {
|
|
1088
|
+
throw new TypeError(`recipe dependencies must mount at a reserved name, not "${mountAs}"`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
function lentDirectories(dependencies) {
|
|
1092
|
+
return [{ source: dependencies.source, mountAs: dependencies.mountAs ?? "node_modules" }, ...dependencies.nested ?? []];
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// src/recipe-container-lending.ts
|
|
1096
|
+
var import_node_child_process4 = require("child_process");
|
|
1097
|
+
var import_promises5 = require("fs/promises");
|
|
1098
|
+
var import_node_path6 = require("path");
|
|
1099
|
+
var RUN_ID = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
1100
|
+
function copy(args) {
|
|
1101
|
+
return new Promise((accept, reject) => {
|
|
1102
|
+
const child = (0, import_node_child_process4.spawn)("cp", args, { shell: false, stdio: "ignore" });
|
|
1103
|
+
child.once("error", reject);
|
|
1104
|
+
child.once("exit", (code) => accept(code ?? 1));
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
async function cloneTree(source, target, platform = process.platform) {
|
|
1108
|
+
await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(target), { recursive: true });
|
|
1109
|
+
const fast = platform === "darwin" ? ["-Rc", source, target] : platform === "linux" ? ["-R", "--reflink=auto", source, target] : null;
|
|
1110
|
+
if (fast && await copy(fast) === 0) return;
|
|
1111
|
+
await (0, import_promises5.rm)(target, { recursive: true, force: true });
|
|
1112
|
+
if (await copy(["-R", source, target]) !== 0) throw new Error(`could not clone ${source}`);
|
|
1113
|
+
}
|
|
1114
|
+
async function lendDependencies(dependencies, runId) {
|
|
1115
|
+
if (!dependencies?.runsDir) return { dependencies, release: async () => void 0 };
|
|
1116
|
+
if (!RUN_ID.test(runId)) throw new TypeError("run id is not a safe directory name");
|
|
1117
|
+
const treeRoot = (0, import_node_path6.dirname)(dependencies.source);
|
|
1118
|
+
for (const entry of lentDirectories(dependencies)) {
|
|
1119
|
+
if (entry.source !== (0, import_node_path6.join)(treeRoot, entry.mountAs)) {
|
|
1120
|
+
throw new TypeError("every directory of a cloned set must lie under its tree root at its mount path");
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
const runDir = (0, import_node_path6.join)(dependencies.runsDir, runId);
|
|
1124
|
+
await cloneTree(treeRoot, runDir);
|
|
1125
|
+
const rebase = (entry) => ({ source: (0, import_node_path6.join)(runDir, entry.mountAs), mountAs: entry.mountAs });
|
|
1126
|
+
return {
|
|
1127
|
+
dependencies: {
|
|
1128
|
+
...dependencies,
|
|
1129
|
+
source: (0, import_node_path6.join)(runDir, dependencies.mountAs ?? "node_modules"),
|
|
1130
|
+
nested: (dependencies.nested ?? []).map(rebase),
|
|
1131
|
+
writable: true
|
|
1132
|
+
},
|
|
1133
|
+
release: () => (0, import_promises5.rm)(runDir, { recursive: true, force: true })
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
async function lendBuildProducts(products, workspaceDir) {
|
|
1137
|
+
const root = (0, import_node_path6.resolve)(workspaceDir);
|
|
1138
|
+
const lent = [];
|
|
1139
|
+
for (const product of products) {
|
|
1140
|
+
assertLentPath(product.mountAs);
|
|
1141
|
+
if (SECRET_WORKSPACE_FILE.test(product.mountAs.split("/").at(-1))) throw new TypeError("a secret is never a build product");
|
|
1142
|
+
const target = (0, import_node_path6.resolve)(root, product.mountAs);
|
|
1143
|
+
if (!target.startsWith(`${root}${import_node_path6.sep}`)) throw new TypeError("build product escapes the workspace");
|
|
1144
|
+
if (await (0, import_promises5.stat)(target).catch(() => null)) continue;
|
|
1145
|
+
if (!await (0, import_promises5.stat)((0, import_node_path6.join)(root, product.within ?? (0, import_node_path6.dirname)(product.mountAs))).catch(() => null)) continue;
|
|
1146
|
+
await cloneTree(product.source, target);
|
|
1147
|
+
lent.push(product.mountAs);
|
|
1148
|
+
}
|
|
1149
|
+
return lent;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1075
1152
|
// src/recipe-container.ts
|
|
1076
1153
|
var ARTIFACT_PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
|
|
1077
1154
|
var PRIVATE_ARTIFACT_PART = /^(?:\.git|\.odla|\.wrangler|\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json)$/i;
|
|
1078
1155
|
function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${(0, import_node_crypto.randomUUID)().slice(0, 12)}`, dependencies = null) {
|
|
1079
1156
|
assertCodeBuildRecipe(recipe2);
|
|
1080
|
-
const mounts = dependencies ?
|
|
1157
|
+
const mounts = dependencies ? dependencyMounts(engine, dependencies) : [];
|
|
1081
1158
|
if (/[,\r\n]/.test(workspaceDir)) throw new TypeError("workspace path contains unsupported mount characters");
|
|
1082
1159
|
const uid = typeof import_node_process2.getuid === "function" ? (0, import_node_process2.getuid)() : 1e3;
|
|
1083
1160
|
const gid = typeof import_node_process2.getgid === "function" ? (0, import_node_process2.getgid)() : 1e3;
|
|
@@ -1103,6 +1180,7 @@ function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-re
|
|
|
1103
1180
|
...mounts,
|
|
1104
1181
|
"--workdir=/workspace",
|
|
1105
1182
|
"--env=CI=1",
|
|
1183
|
+
"--env=HOME=/tmp",
|
|
1106
1184
|
recipe2.image,
|
|
1107
1185
|
...recipe2.command
|
|
1108
1186
|
];
|
|
@@ -1125,26 +1203,34 @@ function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-re
|
|
|
1125
1203
|
...mounts,
|
|
1126
1204
|
"--workdir=/workspace",
|
|
1127
1205
|
"--env=CI=1",
|
|
1206
|
+
"--env=HOME=/tmp",
|
|
1128
1207
|
recipe2.image,
|
|
1129
1208
|
...recipe2.command
|
|
1130
1209
|
];
|
|
1131
1210
|
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1211
|
+
function dependencyMounts(engine, dependencies) {
|
|
1212
|
+
const readonly = dependencies.writable ? "" : ",readonly";
|
|
1213
|
+
return lentDirectories(dependencies).map(({ source, mountAs }) => {
|
|
1214
|
+
if (!source.startsWith("/") || /[,\r\n]/.test(source)) {
|
|
1215
|
+
throw new TypeError("recipe dependency source must be an absolute path without mount separators");
|
|
1216
|
+
}
|
|
1217
|
+
assertReservedMount(mountAs);
|
|
1218
|
+
return engine === "container" ? `--mount=type=bind,source=${source},target=/workspace/${mountAs}${readonly}` : `--mount=type=bind,src=${source},dst=/workspace/${mountAs}${readonly}`;
|
|
1219
|
+
});
|
|
1140
1220
|
}
|
|
1141
1221
|
function createContainerRecipeExecutor(engine, options = {}) {
|
|
1142
1222
|
return {
|
|
1143
1223
|
async run(input) {
|
|
1144
1224
|
await verifyContainerEngineBoundary(engine);
|
|
1145
1225
|
const name = `odla-recipe-${(0, import_node_crypto.randomUUID)().slice(0, 12)}`;
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1226
|
+
const lent = await lendDependencies(options.dependencies ?? null, name);
|
|
1227
|
+
try {
|
|
1228
|
+
if (lent.dependencies?.products?.length) await lendBuildProducts(lent.dependencies.products, input.workspaceDir);
|
|
1229
|
+
const args = buildRecipeContainerArgs(engine, input.workspaceDir, input.recipe, name, lent.dependencies);
|
|
1230
|
+
return await execute(engine, args, name, input.recipe, input.signal);
|
|
1231
|
+
} finally {
|
|
1232
|
+
await lent.release();
|
|
1233
|
+
}
|
|
1148
1234
|
}
|
|
1149
1235
|
};
|
|
1150
1236
|
}
|
|
@@ -1169,7 +1255,7 @@ function execute(engine, args, name, recipe2, signal) {
|
|
|
1169
1255
|
function runContainerCommand(engine, args, name, recipe2, signal) {
|
|
1170
1256
|
return new Promise((accept, reject) => {
|
|
1171
1257
|
const started = Date.now();
|
|
1172
|
-
const child = (0,
|
|
1258
|
+
const child = (0, import_node_child_process5.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
1173
1259
|
const stdout = [];
|
|
1174
1260
|
const stderr = [];
|
|
1175
1261
|
let bytes = 0;
|
|
@@ -1182,7 +1268,7 @@ function runContainerCommand(engine, args, name, recipe2, signal) {
|
|
|
1182
1268
|
timedOut = reason === "timeout";
|
|
1183
1269
|
outputLimitExceeded = reason === "output";
|
|
1184
1270
|
const remove = engine === "container" ? ["delete", "--force", name] : ["rm", "-f", name];
|
|
1185
|
-
const killer = (0,
|
|
1271
|
+
const killer = (0, import_node_child_process5.spawn)(engine, remove, { shell: false, stdio: "ignore" });
|
|
1186
1272
|
killer.unref();
|
|
1187
1273
|
child.kill("SIGTERM");
|
|
1188
1274
|
};
|
|
@@ -1219,27 +1305,27 @@ function runContainerCommand(engine, args, name, recipe2, signal) {
|
|
|
1219
1305
|
|
|
1220
1306
|
// src/workspace-digest.ts
|
|
1221
1307
|
var import_node_crypto2 = require("crypto");
|
|
1222
|
-
var
|
|
1223
|
-
var
|
|
1308
|
+
var import_promises6 = require("fs/promises");
|
|
1309
|
+
var import_node_path7 = require("path");
|
|
1224
1310
|
async function digestStagedWorkspace(root, limits) {
|
|
1225
1311
|
const files = [];
|
|
1226
1312
|
const walk = async (directory) => {
|
|
1227
|
-
const entries = await (0,
|
|
1313
|
+
const entries = await (0, import_promises6.readdir)(directory, { withFileTypes: true });
|
|
1228
1314
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
1229
1315
|
if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
|
|
1230
|
-
const target = (0,
|
|
1316
|
+
const target = (0, import_node_path7.resolve)(directory, entry.name);
|
|
1231
1317
|
if (entry.isDirectory()) await walk(target);
|
|
1232
1318
|
else if (entry.isFile()) {
|
|
1233
|
-
files.push({ path: (0,
|
|
1319
|
+
files.push({ path: (0, import_node_path7.relative)(root, target).split("\\").join("/"), target });
|
|
1234
1320
|
if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
|
|
1235
1321
|
}
|
|
1236
1322
|
}
|
|
1237
1323
|
};
|
|
1238
|
-
await walk((0,
|
|
1324
|
+
await walk((0, import_node_path7.resolve)(root));
|
|
1239
1325
|
const hash = (0, import_node_crypto2.createHash)("sha256");
|
|
1240
1326
|
let bytes = 0;
|
|
1241
1327
|
for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
|
|
1242
|
-
const content = await (0,
|
|
1328
|
+
const content = await (0, import_promises6.readFile)(file.target);
|
|
1243
1329
|
bytes += Buffer.byteLength(file.path) + content.byteLength;
|
|
1244
1330
|
if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
|
|
1245
1331
|
hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
|
|
@@ -1357,8 +1443,8 @@ async function inspectArtifacts(workspaceDir, recipe2) {
|
|
|
1357
1443
|
const receipts = [];
|
|
1358
1444
|
for (const artifact of recipe2.expectedArtifacts ?? []) {
|
|
1359
1445
|
try {
|
|
1360
|
-
const path = (0,
|
|
1361
|
-
const info = await (0,
|
|
1446
|
+
const path = (0, import_node_path8.join)(workspaceDir, artifact.path);
|
|
1447
|
+
const info = await (0, import_promises7.lstat)(path);
|
|
1362
1448
|
if (!info.isFile() || info.isSymbolicLink()) {
|
|
1363
1449
|
receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
|
|
1364
1450
|
} else if (info.size > artifact.maximumBytes) {
|
|
@@ -1689,15 +1775,15 @@ async function prepareRuntimeLocalSource(input) {
|
|
|
1689
1775
|
}
|
|
1690
1776
|
|
|
1691
1777
|
// src/code-runtime-source.ts
|
|
1692
|
-
var
|
|
1778
|
+
var import_promises10 = require("fs/promises");
|
|
1693
1779
|
var import_node_os3 = require("os");
|
|
1694
|
-
var
|
|
1780
|
+
var import_node_path11 = require("path");
|
|
1695
1781
|
|
|
1696
1782
|
// src/code-runtime-archive.ts
|
|
1697
1783
|
var import_node_zlib = require("zlib");
|
|
1698
|
-
var
|
|
1784
|
+
var import_promises8 = require("fs/promises");
|
|
1699
1785
|
var import_node_os2 = require("os");
|
|
1700
|
-
var
|
|
1786
|
+
var import_node_path9 = require("path");
|
|
1701
1787
|
var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
1702
1788
|
var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
1703
1789
|
var MAX_NUL_SHARE = 0.1;
|
|
@@ -1715,9 +1801,9 @@ async function materializeCodeRuntimeArchive(archive, visiblePaths, tempRoot = (
|
|
|
1715
1801
|
throw new TypeError("Code source archive exceeds its decompressed byte bound");
|
|
1716
1802
|
}
|
|
1717
1803
|
const entries = parseTar(bytes, archive.limits);
|
|
1718
|
-
const root = await (0,
|
|
1719
|
-
const sourceDir = (0,
|
|
1720
|
-
await (0,
|
|
1804
|
+
const root = await (0, import_promises8.mkdtemp)((0, import_node_path9.join)(tempRoot, "odla-code-archive-"));
|
|
1805
|
+
const sourceDir = (0, import_node_path9.join)(root, "source");
|
|
1806
|
+
await (0, import_promises8.mkdir)(sourceDir);
|
|
1721
1807
|
let visible = 0;
|
|
1722
1808
|
try {
|
|
1723
1809
|
for (const entry of entries) {
|
|
@@ -1730,16 +1816,16 @@ async function materializeCodeRuntimeArchive(archive, visiblePaths, tempRoot = (
|
|
|
1730
1816
|
continue;
|
|
1731
1817
|
}
|
|
1732
1818
|
if (nulShare(content) > MAX_NUL_SHARE) continue;
|
|
1733
|
-
const target = (0,
|
|
1734
|
-
if (!target.startsWith(`${(0,
|
|
1735
|
-
await (0,
|
|
1736
|
-
await (0,
|
|
1819
|
+
const target = (0, import_node_path9.resolve)(sourceDir, entry.path);
|
|
1820
|
+
if (!target.startsWith(`${(0, import_node_path9.resolve)(sourceDir)}${import_node_path9.sep}`)) throw new TypeError("Code source path escapes its root");
|
|
1821
|
+
await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
|
|
1822
|
+
await (0, import_promises8.writeFile)(target, content, { flag: "wx", mode: 420 });
|
|
1737
1823
|
visible += 1;
|
|
1738
1824
|
}
|
|
1739
1825
|
if (!visible && !visiblePaths) throw new TypeError("GitHub commit has no Code-visible text source");
|
|
1740
|
-
return { sourceDir, cleanup: () => (0,
|
|
1826
|
+
return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
|
|
1741
1827
|
} catch (cause) {
|
|
1742
|
-
await (0,
|
|
1828
|
+
await (0, import_promises8.rm)(root, { recursive: true, force: true });
|
|
1743
1829
|
throw cause;
|
|
1744
1830
|
}
|
|
1745
1831
|
}
|
|
@@ -1934,8 +2020,8 @@ function nulShare(content) {
|
|
|
1934
2020
|
}
|
|
1935
2021
|
|
|
1936
2022
|
// src/code-runtime-selected-source.ts
|
|
1937
|
-
var
|
|
1938
|
-
var
|
|
2023
|
+
var import_promises9 = require("fs/promises");
|
|
2024
|
+
var import_node_path10 = require("path");
|
|
1939
2025
|
function selectedSourceSet(payload) {
|
|
1940
2026
|
if (!payload.sourceSet) return null;
|
|
1941
2027
|
const set = payload.sourceSet && typeof payload.sourceSet === "object" && !Array.isArray(payload.sourceSet) ? payload.sourceSet : null;
|
|
@@ -1972,9 +2058,9 @@ async function attachReferenceDirectories(workspace, references) {
|
|
|
1972
2058
|
for (const reference of references) {
|
|
1973
2059
|
validateAlias(reference.alias);
|
|
1974
2060
|
for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
|
|
1975
|
-
const target = (0,
|
|
1976
|
-
await (0,
|
|
1977
|
-
await (0,
|
|
2061
|
+
const target = (0, import_node_path10.join)(root, ".odla-references", reference.alias);
|
|
2062
|
+
await (0, import_promises9.mkdir)((0, import_node_path10.dirname)(target), { recursive: true });
|
|
2063
|
+
await (0, import_promises9.cp)(reference.sourceDir, target, { recursive: true, errorOnExist: true, force: false });
|
|
1978
2064
|
await makeTreeReadOnly(target);
|
|
1979
2065
|
}
|
|
1980
2066
|
}
|
|
@@ -1985,10 +2071,10 @@ function validateAlias(alias) {
|
|
|
1985
2071
|
}
|
|
1986
2072
|
}
|
|
1987
2073
|
async function makeTreeReadOnly(root) {
|
|
1988
|
-
for (const entry of await (0,
|
|
1989
|
-
const target = (0,
|
|
2074
|
+
for (const entry of await (0, import_promises9.readdir)(root, { withFileTypes: true })) {
|
|
2075
|
+
const target = (0, import_node_path10.join)(root, entry.name);
|
|
1990
2076
|
if (entry.isDirectory()) await makeTreeReadOnly(target);
|
|
1991
|
-
else if (entry.isFile()) await (0,
|
|
2077
|
+
else if (entry.isFile()) await (0, import_promises9.chmod)(target, 292);
|
|
1992
2078
|
}
|
|
1993
2079
|
}
|
|
1994
2080
|
|
|
@@ -2000,9 +2086,9 @@ var SOURCE_MAX_BYTES = 80 * 1024 * 1024;
|
|
|
2000
2086
|
var SOURCE_SET_MAX_BYTES = 480 * 1024 * 1024;
|
|
2001
2087
|
async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node_os3.tmpdir)()) {
|
|
2002
2088
|
if (!snapshot.files.length || snapshot.files.length > SOURCE_MAX_FILES) throw new TypeError("Code source file count is invalid");
|
|
2003
|
-
const root = await (0,
|
|
2004
|
-
const sourceDir = (0,
|
|
2005
|
-
await (0,
|
|
2089
|
+
const root = await (0, import_promises10.mkdtemp)((0, import_node_path11.join)(tempRoot, "odla-code-source-"));
|
|
2090
|
+
const sourceDir = (0, import_node_path11.join)(root, "source");
|
|
2091
|
+
await (0, import_promises10.mkdir)(sourceDir);
|
|
2006
2092
|
const seen = /* @__PURE__ */ new Set();
|
|
2007
2093
|
let bytes = 0;
|
|
2008
2094
|
try {
|
|
@@ -2012,10 +2098,10 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node
|
|
|
2012
2098
|
seen.add(file.path);
|
|
2013
2099
|
bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
|
|
2014
2100
|
if (bytes > SOURCE_MAX_BYTES) throw new TypeError("Code source exceeds its byte bound");
|
|
2015
|
-
const target = (0,
|
|
2016
|
-
if (!target.startsWith(`${(0,
|
|
2017
|
-
await (0,
|
|
2018
|
-
await (0,
|
|
2101
|
+
const target = (0, import_node_path11.resolve)(sourceDir, file.path);
|
|
2102
|
+
if (!target.startsWith(`${(0, import_node_path11.resolve)(sourceDir)}${import_node_path11.sep}`)) throw new TypeError("Code source path escapes its root");
|
|
2103
|
+
await (0, import_promises10.mkdir)((0, import_node_path11.dirname)(target), { recursive: true });
|
|
2104
|
+
await (0, import_promises10.writeFile)(target, file.content, { flag: "wx", mode: 420 });
|
|
2019
2105
|
}
|
|
2020
2106
|
for (const reference of snapshot.references ?? []) {
|
|
2021
2107
|
validateAlias2(reference.alias);
|
|
@@ -2027,15 +2113,15 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node
|
|
|
2027
2113
|
seen.add(path);
|
|
2028
2114
|
bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
|
|
2029
2115
|
if (bytes > SOURCE_SET_MAX_BYTES) throw new TypeError("Code source set exceeds its byte bound");
|
|
2030
|
-
const target = (0,
|
|
2031
|
-
if (!target.startsWith(`${(0,
|
|
2032
|
-
await (0,
|
|
2033
|
-
await (0,
|
|
2116
|
+
const target = (0, import_node_path11.resolve)(sourceDir, path);
|
|
2117
|
+
if (!target.startsWith(`${(0, import_node_path11.resolve)(sourceDir)}${import_node_path11.sep}`)) throw new TypeError("Code reference path escapes its root");
|
|
2118
|
+
await (0, import_promises10.mkdir)((0, import_node_path11.dirname)(target), { recursive: true });
|
|
2119
|
+
await (0, import_promises10.writeFile)(target, file.content, { flag: "wx", mode: 292 });
|
|
2034
2120
|
}
|
|
2035
2121
|
}
|
|
2036
|
-
return { sourceDir, cleanup: () => (0,
|
|
2122
|
+
return { sourceDir, cleanup: () => (0, import_promises10.rm)(root, { recursive: true, force: true }) };
|
|
2037
2123
|
} catch (cause) {
|
|
2038
|
-
await (0,
|
|
2124
|
+
await (0, import_promises10.rm)(root, { recursive: true, force: true });
|
|
2039
2125
|
throw cause;
|
|
2040
2126
|
}
|
|
2041
2127
|
}
|
|
@@ -2054,10 +2140,10 @@ async function attachCodeRuntimeReferences(workspace, references) {
|
|
|
2054
2140
|
bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
|
|
2055
2141
|
if (bytes > SOURCE_SET_MAX_BYTES - SOURCE_MAX_BYTES) throw new TypeError("Code reference set exceeds its byte bound");
|
|
2056
2142
|
for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
|
|
2057
|
-
const target = (0,
|
|
2058
|
-
if (!target.startsWith(`${(0,
|
|
2059
|
-
await (0,
|
|
2060
|
-
await (0,
|
|
2143
|
+
const target = (0, import_node_path11.resolve)(root, path);
|
|
2144
|
+
if (!target.startsWith(`${(0, import_node_path11.resolve)(root)}${import_node_path11.sep}`)) throw new TypeError("Code reference path escapes its root");
|
|
2145
|
+
await (0, import_promises10.mkdir)((0, import_node_path11.dirname)(target), { recursive: true });
|
|
2146
|
+
await (0, import_promises10.writeFile)(target, file.content, { flag: "wx", mode: 292 });
|
|
2061
2147
|
}
|
|
2062
2148
|
}
|
|
2063
2149
|
}
|
|
@@ -2503,7 +2589,7 @@ Finish with a concise, non-empty answer to the owner. Do not call tools or promi
|
|
|
2503
2589
|
}
|
|
2504
2590
|
|
|
2505
2591
|
// src/code-runtime-session-skills.ts
|
|
2506
|
-
var sleep2 = (ms) => new Promise((
|
|
2592
|
+
var sleep2 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
2507
2593
|
function createCodeRuntimeSessionSkillLoader(control, options = {}) {
|
|
2508
2594
|
const wait2 = options.wait ?? sleep2;
|
|
2509
2595
|
const load = control.collaborationSkills?.bind(control);
|
|
@@ -2562,7 +2648,7 @@ var inferWithBackoff = (infer, wait2, onRetry) => withOverloadRetry(infer, wait2
|
|
|
2562
2648
|
async function handleCodeRuntimeInference(input) {
|
|
2563
2649
|
const { command, request, state } = input;
|
|
2564
2650
|
const startedAt = Date.now();
|
|
2565
|
-
const wait2 = input.wait ?? ((ms) => new Promise((
|
|
2651
|
+
const wait2 = input.wait ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
|
|
2566
2652
|
const response2 = await inferWithBackoff(
|
|
2567
2653
|
() => input.control.infer(command.sessionId, {
|
|
2568
2654
|
requestId: request.requestId,
|
|
@@ -3073,18 +3159,18 @@ function response(request, ok, content, details) {
|
|
|
3073
3159
|
}
|
|
3074
3160
|
|
|
3075
3161
|
// src/code-tool-edit.ts
|
|
3076
|
-
var
|
|
3162
|
+
var import_promises12 = require("fs/promises");
|
|
3077
3163
|
|
|
3078
3164
|
// src/code-tool-graph.ts
|
|
3079
|
-
var
|
|
3080
|
-
var
|
|
3165
|
+
var import_promises11 = require("fs/promises");
|
|
3166
|
+
var import_node_path12 = require("path");
|
|
3081
3167
|
var import_graph = require("@odla-ai/graph");
|
|
3082
3168
|
var import_code4 = require("@odla-ai/graph/code");
|
|
3083
3169
|
var cache = /* @__PURE__ */ new Map();
|
|
3084
3170
|
function workspaceGraphs(workspaceDir, paths2) {
|
|
3085
3171
|
const existing = cache.get(workspaceDir);
|
|
3086
3172
|
if (existing) return existing;
|
|
3087
|
-
const read2 = (path) => (0,
|
|
3173
|
+
const read2 = (path) => (0, import_promises11.readFile)((0, import_node_path12.join)(workspaceDir, path), "utf8");
|
|
3088
3174
|
const built = (async () => ({
|
|
3089
3175
|
// No knownTables: a staged workspace may not carry migrations, and a filter
|
|
3090
3176
|
// that silently drops every table is worse than an unfiltered one. Callers
|
|
@@ -3160,7 +3246,7 @@ async function editCodeFile(workspaceDir, path, oldText, newText) {
|
|
|
3160
3246
|
}
|
|
3161
3247
|
if (oldText.includes("\0") || newText.includes("\0")) throw new TypeError("edit text contains NUL bytes; use plain text");
|
|
3162
3248
|
const target = resolveCodePath(workspaceDir, path);
|
|
3163
|
-
const current = await (0,
|
|
3249
|
+
const current = await (0, import_promises12.readFile)(target, "utf8");
|
|
3164
3250
|
const found = occurrences(current, oldText);
|
|
3165
3251
|
if (found === 0) {
|
|
3166
3252
|
throw new TypeError(`oldText was not found in "${path}"; sandbox.read the current lines and copy them exactly, including indentation and blank lines`);
|
|
@@ -3169,7 +3255,7 @@ async function editCodeFile(workspaceDir, path, oldText, newText) {
|
|
|
3169
3255
|
throw new TypeError(`oldText occurs ${found} times in "${path}"; include more of the surrounding lines so it matches exactly once`);
|
|
3170
3256
|
}
|
|
3171
3257
|
const at = current.indexOf(oldText);
|
|
3172
|
-
await (0,
|
|
3258
|
+
await (0, import_promises12.writeFile)(target, `${current.slice(0, at)}${newText}${current.slice(at + oldText.length)}`, "utf8");
|
|
3173
3259
|
return { deletions: oldText.split("\n").length, additions: newText.split("\n").length };
|
|
3174
3260
|
}
|
|
3175
3261
|
async function edit(context, request, options, policy, registry) {
|
|
@@ -3194,8 +3280,8 @@ async function edit(context, request, options, policy, registry) {
|
|
|
3194
3280
|
}
|
|
3195
3281
|
|
|
3196
3282
|
// src/code-tool-write.ts
|
|
3197
|
-
var
|
|
3198
|
-
var
|
|
3283
|
+
var import_promises13 = require("fs/promises");
|
|
3284
|
+
var import_node_path13 = require("path");
|
|
3199
3285
|
var MAX_WRITE_BYTES = 256 * 1024;
|
|
3200
3286
|
var lines = (text2) => text2 === "" ? [] : text2.replace(/\n$/, "").split("\n");
|
|
3201
3287
|
function writeAsDiff(path, current, content) {
|
|
@@ -3220,8 +3306,8 @@ async function writeCodeFile(workspaceDir, path, content, current) {
|
|
|
3220
3306
|
}
|
|
3221
3307
|
if (content.includes("\0")) throw new TypeError("content contains NUL bytes; use plain text");
|
|
3222
3308
|
const target = resolveCodePath(workspaceDir, path);
|
|
3223
|
-
await (0,
|
|
3224
|
-
await (0,
|
|
3309
|
+
await (0, import_promises13.mkdir)((0, import_node_path13.dirname)(target), { recursive: true });
|
|
3310
|
+
await (0, import_promises13.writeFile)(target, content, "utf8");
|
|
3225
3311
|
return { created: current === null, deletions: current === null ? 0 : lines(current).length, additions: lines(content).length };
|
|
3226
3312
|
}
|
|
3227
3313
|
async function write(context, request, options, policy, registry) {
|
|
@@ -3232,7 +3318,7 @@ async function write(context, request, options, policy, registry) {
|
|
|
3232
3318
|
throw new TypeError("write targets a read-only reference source");
|
|
3233
3319
|
}
|
|
3234
3320
|
const exists = (await registry.files(context.workspaceDir)).includes(path);
|
|
3235
|
-
const current = exists ? await (0,
|
|
3321
|
+
const current = exists ? await (0, import_promises13.readFile)(resolveCodePath(context.workspaceDir, path), "utf8") : null;
|
|
3236
3322
|
const patch2 = writeAsDiff(path, current, content);
|
|
3237
3323
|
const allowed = await policy.write(policyContext(context, request, options, { patch: patch2 }));
|
|
3238
3324
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
@@ -3248,12 +3334,12 @@ async function write(context, request, options, policy, registry) {
|
|
|
3248
3334
|
}
|
|
3249
3335
|
|
|
3250
3336
|
// src/code-tool-reads.ts
|
|
3251
|
-
var
|
|
3337
|
+
var import_promises15 = require("fs/promises");
|
|
3252
3338
|
|
|
3253
3339
|
// src/code-tool-discovery.ts
|
|
3254
|
-
var
|
|
3255
|
-
var
|
|
3256
|
-
var
|
|
3340
|
+
var import_node_child_process6 = require("child_process");
|
|
3341
|
+
var import_promises14 = require("fs/promises");
|
|
3342
|
+
var import_node_path14 = require("path");
|
|
3257
3343
|
var DEFAULT_MAX_FILES = 2e4;
|
|
3258
3344
|
var DEFAULT_MAX_RESULTS = 100;
|
|
3259
3345
|
var DEFAULT_MAX_FILE_BYTES = 512 * 1024;
|
|
@@ -3278,13 +3364,13 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
3278
3364
|
async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
3279
3365
|
const paths2 = [];
|
|
3280
3366
|
const walk = async (directory) => {
|
|
3281
|
-
for (const entry of await (0,
|
|
3367
|
+
for (const entry of await (0, import_promises14.readdir)(directory, { withFileTypes: true })) {
|
|
3282
3368
|
if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
3283
3369
|
if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
|
|
3284
|
-
const target = (0,
|
|
3370
|
+
const target = (0, import_node_path14.resolve)(directory, entry.name);
|
|
3285
3371
|
if (entry.isDirectory()) await walk(target);
|
|
3286
3372
|
else if (entry.isFile()) {
|
|
3287
|
-
const path = (0,
|
|
3373
|
+
const path = (0, import_node_path14.relative)(root, target).split("\\").join("/");
|
|
3288
3374
|
try {
|
|
3289
3375
|
validateRelativePath(path);
|
|
3290
3376
|
} catch {
|
|
@@ -3295,7 +3381,7 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
|
3295
3381
|
}
|
|
3296
3382
|
}
|
|
3297
3383
|
};
|
|
3298
|
-
await walk((0,
|
|
3384
|
+
await walk((0, import_node_path14.resolve)(root));
|
|
3299
3385
|
return paths2.sort();
|
|
3300
3386
|
}
|
|
3301
3387
|
function listWorkspace(paths2, options = {}) {
|
|
@@ -3357,7 +3443,7 @@ function nativeSearchBatch(root, paths2, options, remaining) {
|
|
|
3357
3443
|
options.query,
|
|
3358
3444
|
...paths2
|
|
3359
3445
|
];
|
|
3360
|
-
const child = (0,
|
|
3446
|
+
const child = (0, import_node_child_process6.spawn)("rg", args, {
|
|
3361
3447
|
cwd: root,
|
|
3362
3448
|
stdio: ["ignore", "pipe", "ignore"],
|
|
3363
3449
|
...options.signal ? { signal: options.signal } : {}
|
|
@@ -3409,7 +3495,7 @@ async function fallbackSearch(root, scoped, options) {
|
|
|
3409
3495
|
if (matches.length >= options.maxResults) break;
|
|
3410
3496
|
let source;
|
|
3411
3497
|
try {
|
|
3412
|
-
source = await (0,
|
|
3498
|
+
source = await (0, import_promises14.readFile)((0, import_node_path14.resolve)(root, path));
|
|
3413
3499
|
} catch {
|
|
3414
3500
|
continue;
|
|
3415
3501
|
}
|
|
@@ -3448,11 +3534,11 @@ async function read(context, request, options, policy, registry) {
|
|
|
3448
3534
|
const allowed = await policy.read(policyContext(context, request, options, { paths: paths2, path, startLine, endLine }));
|
|
3449
3535
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
3450
3536
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
3451
|
-
const info = await (0,
|
|
3537
|
+
const info = await (0, import_promises15.stat)(target);
|
|
3452
3538
|
if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
|
|
3453
3539
|
throw new TypeError("file is not a bounded regular source file");
|
|
3454
3540
|
}
|
|
3455
|
-
const source = await (0,
|
|
3541
|
+
const source = await (0, import_promises15.readFile)(target);
|
|
3456
3542
|
if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
|
|
3457
3543
|
const lines2 = source.toString("utf8").split("\n");
|
|
3458
3544
|
const content = lines2.slice(startLine - 1, endLine).join("\n");
|
|
@@ -3977,8 +4063,8 @@ var runtimeErrorMessage = (value) => value instanceof Error ? value.message : St
|
|
|
3977
4063
|
function codeRuntimeAcknowledgementGate(signal) {
|
|
3978
4064
|
let settle;
|
|
3979
4065
|
let settled = false;
|
|
3980
|
-
const ready = new Promise((
|
|
3981
|
-
settle =
|
|
4066
|
+
const ready = new Promise((resolve8) => {
|
|
4067
|
+
settle = resolve8;
|
|
3982
4068
|
});
|
|
3983
4069
|
const release = (run) => {
|
|
3984
4070
|
if (settled) return;
|
|
@@ -4044,8 +4130,8 @@ function observeCodeRuntimeSessionSkills(command, skills, emit) {
|
|
|
4044
4130
|
}
|
|
4045
4131
|
|
|
4046
4132
|
// src/code-repository-recipes.ts
|
|
4047
|
-
var
|
|
4048
|
-
var
|
|
4133
|
+
var import_promises16 = require("fs/promises");
|
|
4134
|
+
var import_node_path15 = require("path");
|
|
4049
4135
|
var REPOSITORY_RECIPES_FILE = "odla.recipes.json";
|
|
4050
4136
|
var MAX_RECIPES = 16;
|
|
4051
4137
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -4106,7 +4192,7 @@ function parseRepositoryRecipes(text2, envelope) {
|
|
|
4106
4192
|
async function readRepositoryRecipes(baselineDir, envelope) {
|
|
4107
4193
|
let text2;
|
|
4108
4194
|
try {
|
|
4109
|
-
text2 = await (0,
|
|
4195
|
+
text2 = await (0, import_promises16.readFile)((0, import_node_path15.join)(baselineDir, REPOSITORY_RECIPES_FILE), "utf8");
|
|
4110
4196
|
} catch (cause) {
|
|
4111
4197
|
if (cause.code === "ENOENT") return null;
|
|
4112
4198
|
throw cause;
|
|
@@ -4486,7 +4572,7 @@ function parse(argv) {
|
|
|
4486
4572
|
};
|
|
4487
4573
|
}
|
|
4488
4574
|
async function readPolicy(path) {
|
|
4489
|
-
const value = JSON.parse(await (0,
|
|
4575
|
+
const value = JSON.parse(await (0, import_promises17.readFile)(path, "utf8"));
|
|
4490
4576
|
if (!value || Object.keys(value).some((key) => !["recipes", "recipeAuthorization"].includes(key)) || !Array.isArray(value.recipes) || !value.recipes.length || value.recipeAuthorization !== void 0 && value.recipeAuthorization !== "registered_recipe" && value.recipeAuthorization !== "exact_approval") throw new TypeError("invalid Code build policy file");
|
|
4491
4577
|
const recipes = value.recipes;
|
|
4492
4578
|
for (const recipe2 of recipes) assertCodeBuildRecipe(recipe2);
|