@odla-ai/harness 0.11.11 → 0.11.13

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.
@@ -3,7 +3,7 @@
3
3
 
4
4
  // src/code-runtime-cli.ts
5
5
  var import_node_os4 = require("os");
6
- var import_promises15 = require("fs/promises");
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((resolve7) => setTimeout(resolve7, ms));
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((resolve7) => {
558
- if (signal?.aborted) return resolve7();
559
- const timer = setTimeout(resolve7, ms);
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
- resolve7();
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 import_promises5 = require("fs/promises");
985
- var import_node_path6 = require("path");
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 import_node_child_process4 = require("child_process");
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((resolve7, reject) => {
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
- resolve7(stdout.trim() === "true");
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 ? [dependencyMount(engine, 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
- var LENDABLE = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage"]);
1133
- function dependencyMount(engine, dependencies) {
1134
- const mountAs = dependencies.mountAs ?? "node_modules";
1135
- if (!dependencies.source.startsWith("/") || /[,\r\n]/.test(dependencies.source)) {
1136
- throw new TypeError("recipe dependency source must be an absolute path without mount separators");
1137
- }
1138
- if (!LENDABLE.has(mountAs)) throw new TypeError(`recipe dependencies must mount at a reserved name, not "${mountAs}"`);
1139
- return engine === "container" ? `--mount=type=bind,source=${dependencies.source},target=/workspace/${mountAs},readonly` : `--mount=type=bind,src=${dependencies.source},dst=/workspace/${mountAs},readonly`;
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 args = buildRecipeContainerArgs(engine, input.workspaceDir, input.recipe, name, options.dependencies ?? null);
1147
- return execute(engine, args, name, input.recipe, input.signal);
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
  }
@@ -1166,10 +1252,14 @@ function parseMemory(value) {
1166
1252
  function execute(engine, args, name, recipe2, signal) {
1167
1253
  return runContainerCommand(engine, args, name, { timeoutMs: recipe2.timeoutMs, maxOutputBytes: recipe2.maxOutputBytes }, signal);
1168
1254
  }
1255
+ var CONTAINER_PROGRESS = /^\[\d+\/\d+\] .*$/gm;
1256
+ function recipeOutput(engine, stderr) {
1257
+ return engine === "container" ? stderr.replace(CONTAINER_PROGRESS, "").replace(/^\n+/, "") : stderr;
1258
+ }
1169
1259
  function runContainerCommand(engine, args, name, recipe2, signal) {
1170
1260
  return new Promise((accept, reject) => {
1171
1261
  const started = Date.now();
1172
- const child = (0, import_node_child_process4.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
1262
+ const child = (0, import_node_child_process5.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
1173
1263
  const stdout = [];
1174
1264
  const stderr = [];
1175
1265
  let bytes = 0;
@@ -1182,7 +1272,7 @@ function runContainerCommand(engine, args, name, recipe2, signal) {
1182
1272
  timedOut = reason === "timeout";
1183
1273
  outputLimitExceeded = reason === "output";
1184
1274
  const remove = engine === "container" ? ["delete", "--force", name] : ["rm", "-f", name];
1185
- const killer = (0, import_node_child_process4.spawn)(engine, remove, { shell: false, stdio: "ignore" });
1275
+ const killer = (0, import_node_child_process5.spawn)(engine, remove, { shell: false, stdio: "ignore" });
1186
1276
  killer.unref();
1187
1277
  child.kill("SIGTERM");
1188
1278
  };
@@ -1208,7 +1298,7 @@ function runContainerCommand(engine, args, name, recipe2, signal) {
1208
1298
  accept({
1209
1299
  exitCode: code ?? 1,
1210
1300
  stdout: Buffer.concat(stdout).toString("utf8"),
1211
- stderr: Buffer.concat(stderr).toString("utf8"),
1301
+ stderr: recipeOutput(engine, Buffer.concat(stderr).toString("utf8")),
1212
1302
  durationMs: Date.now() - started,
1213
1303
  outputLimitExceeded,
1214
1304
  timedOut
@@ -1219,27 +1309,27 @@ function runContainerCommand(engine, args, name, recipe2, signal) {
1219
1309
 
1220
1310
  // src/workspace-digest.ts
1221
1311
  var import_node_crypto2 = require("crypto");
1222
- var import_promises4 = require("fs/promises");
1223
- var import_node_path5 = require("path");
1312
+ var import_promises6 = require("fs/promises");
1313
+ var import_node_path7 = require("path");
1224
1314
  async function digestStagedWorkspace(root, limits) {
1225
1315
  const files = [];
1226
1316
  const walk = async (directory) => {
1227
- const entries = await (0, import_promises4.readdir)(directory, { withFileTypes: true });
1317
+ const entries = await (0, import_promises6.readdir)(directory, { withFileTypes: true });
1228
1318
  for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
1229
1319
  if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
1230
- const target = (0, import_node_path5.resolve)(directory, entry.name);
1320
+ const target = (0, import_node_path7.resolve)(directory, entry.name);
1231
1321
  if (entry.isDirectory()) await walk(target);
1232
1322
  else if (entry.isFile()) {
1233
- files.push({ path: (0, import_node_path5.relative)(root, target).split("\\").join("/"), target });
1323
+ files.push({ path: (0, import_node_path7.relative)(root, target).split("\\").join("/"), target });
1234
1324
  if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
1235
1325
  }
1236
1326
  }
1237
1327
  };
1238
- await walk((0, import_node_path5.resolve)(root));
1328
+ await walk((0, import_node_path7.resolve)(root));
1239
1329
  const hash = (0, import_node_crypto2.createHash)("sha256");
1240
1330
  let bytes = 0;
1241
1331
  for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
1242
- const content = await (0, import_promises4.readFile)(file.target);
1332
+ const content = await (0, import_promises6.readFile)(file.target);
1243
1333
  bytes += Buffer.byteLength(file.path) + content.byteLength;
1244
1334
  if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
1245
1335
  hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
@@ -1357,8 +1447,8 @@ async function inspectArtifacts(workspaceDir, recipe2) {
1357
1447
  const receipts = [];
1358
1448
  for (const artifact of recipe2.expectedArtifacts ?? []) {
1359
1449
  try {
1360
- const path = (0, import_node_path6.join)(workspaceDir, artifact.path);
1361
- const info = await (0, import_promises5.lstat)(path);
1450
+ const path = (0, import_node_path8.join)(workspaceDir, artifact.path);
1451
+ const info = await (0, import_promises7.lstat)(path);
1362
1452
  if (!info.isFile() || info.isSymbolicLink()) {
1363
1453
  receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
1364
1454
  } else if (info.size > artifact.maximumBytes) {
@@ -1689,15 +1779,15 @@ async function prepareRuntimeLocalSource(input) {
1689
1779
  }
1690
1780
 
1691
1781
  // src/code-runtime-source.ts
1692
- var import_promises8 = require("fs/promises");
1782
+ var import_promises10 = require("fs/promises");
1693
1783
  var import_node_os3 = require("os");
1694
- var import_node_path9 = require("path");
1784
+ var import_node_path11 = require("path");
1695
1785
 
1696
1786
  // src/code-runtime-archive.ts
1697
1787
  var import_node_zlib = require("zlib");
1698
- var import_promises6 = require("fs/promises");
1788
+ var import_promises8 = require("fs/promises");
1699
1789
  var import_node_os2 = require("os");
1700
- var import_node_path7 = require("path");
1790
+ var import_node_path9 = require("path");
1701
1791
  var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
1702
1792
  var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
1703
1793
  var MAX_NUL_SHARE = 0.1;
@@ -1715,9 +1805,9 @@ async function materializeCodeRuntimeArchive(archive, visiblePaths, tempRoot = (
1715
1805
  throw new TypeError("Code source archive exceeds its decompressed byte bound");
1716
1806
  }
1717
1807
  const entries = parseTar(bytes, archive.limits);
1718
- const root = await (0, import_promises6.mkdtemp)((0, import_node_path7.join)(tempRoot, "odla-code-archive-"));
1719
- const sourceDir = (0, import_node_path7.join)(root, "source");
1720
- await (0, import_promises6.mkdir)(sourceDir);
1808
+ const root = await (0, import_promises8.mkdtemp)((0, import_node_path9.join)(tempRoot, "odla-code-archive-"));
1809
+ const sourceDir = (0, import_node_path9.join)(root, "source");
1810
+ await (0, import_promises8.mkdir)(sourceDir);
1721
1811
  let visible = 0;
1722
1812
  try {
1723
1813
  for (const entry of entries) {
@@ -1730,16 +1820,16 @@ async function materializeCodeRuntimeArchive(archive, visiblePaths, tempRoot = (
1730
1820
  continue;
1731
1821
  }
1732
1822
  if (nulShare(content) > MAX_NUL_SHARE) continue;
1733
- const target = (0, import_node_path7.resolve)(sourceDir, entry.path);
1734
- if (!target.startsWith(`${(0, import_node_path7.resolve)(sourceDir)}${import_node_path7.sep}`)) throw new TypeError("Code source path escapes its root");
1735
- await (0, import_promises6.mkdir)((0, import_node_path7.dirname)(target), { recursive: true });
1736
- await (0, import_promises6.writeFile)(target, content, { flag: "wx", mode: 420 });
1823
+ const target = (0, import_node_path9.resolve)(sourceDir, entry.path);
1824
+ if (!target.startsWith(`${(0, import_node_path9.resolve)(sourceDir)}${import_node_path9.sep}`)) throw new TypeError("Code source path escapes its root");
1825
+ await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
1826
+ await (0, import_promises8.writeFile)(target, content, { flag: "wx", mode: 420 });
1737
1827
  visible += 1;
1738
1828
  }
1739
1829
  if (!visible && !visiblePaths) throw new TypeError("GitHub commit has no Code-visible text source");
1740
- return { sourceDir, cleanup: () => (0, import_promises6.rm)(root, { recursive: true, force: true }) };
1830
+ return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
1741
1831
  } catch (cause) {
1742
- await (0, import_promises6.rm)(root, { recursive: true, force: true });
1832
+ await (0, import_promises8.rm)(root, { recursive: true, force: true });
1743
1833
  throw cause;
1744
1834
  }
1745
1835
  }
@@ -1934,8 +2024,8 @@ function nulShare(content) {
1934
2024
  }
1935
2025
 
1936
2026
  // src/code-runtime-selected-source.ts
1937
- var import_promises7 = require("fs/promises");
1938
- var import_node_path8 = require("path");
2027
+ var import_promises9 = require("fs/promises");
2028
+ var import_node_path10 = require("path");
1939
2029
  function selectedSourceSet(payload) {
1940
2030
  if (!payload.sourceSet) return null;
1941
2031
  const set = payload.sourceSet && typeof payload.sourceSet === "object" && !Array.isArray(payload.sourceSet) ? payload.sourceSet : null;
@@ -1972,9 +2062,9 @@ async function attachReferenceDirectories(workspace, references) {
1972
2062
  for (const reference of references) {
1973
2063
  validateAlias(reference.alias);
1974
2064
  for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
1975
- const target = (0, import_node_path8.join)(root, ".odla-references", reference.alias);
1976
- await (0, import_promises7.mkdir)((0, import_node_path8.dirname)(target), { recursive: true });
1977
- await (0, import_promises7.cp)(reference.sourceDir, target, { recursive: true, errorOnExist: true, force: false });
2065
+ const target = (0, import_node_path10.join)(root, ".odla-references", reference.alias);
2066
+ await (0, import_promises9.mkdir)((0, import_node_path10.dirname)(target), { recursive: true });
2067
+ await (0, import_promises9.cp)(reference.sourceDir, target, { recursive: true, errorOnExist: true, force: false });
1978
2068
  await makeTreeReadOnly(target);
1979
2069
  }
1980
2070
  }
@@ -1985,10 +2075,10 @@ function validateAlias(alias) {
1985
2075
  }
1986
2076
  }
1987
2077
  async function makeTreeReadOnly(root) {
1988
- for (const entry of await (0, import_promises7.readdir)(root, { withFileTypes: true })) {
1989
- const target = (0, import_node_path8.join)(root, entry.name);
2078
+ for (const entry of await (0, import_promises9.readdir)(root, { withFileTypes: true })) {
2079
+ const target = (0, import_node_path10.join)(root, entry.name);
1990
2080
  if (entry.isDirectory()) await makeTreeReadOnly(target);
1991
- else if (entry.isFile()) await (0, import_promises7.chmod)(target, 292);
2081
+ else if (entry.isFile()) await (0, import_promises9.chmod)(target, 292);
1992
2082
  }
1993
2083
  }
1994
2084
 
@@ -2000,9 +2090,9 @@ var SOURCE_MAX_BYTES = 80 * 1024 * 1024;
2000
2090
  var SOURCE_SET_MAX_BYTES = 480 * 1024 * 1024;
2001
2091
  async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node_os3.tmpdir)()) {
2002
2092
  if (!snapshot.files.length || snapshot.files.length > SOURCE_MAX_FILES) throw new TypeError("Code source file count is invalid");
2003
- const root = await (0, import_promises8.mkdtemp)((0, import_node_path9.join)(tempRoot, "odla-code-source-"));
2004
- const sourceDir = (0, import_node_path9.join)(root, "source");
2005
- await (0, import_promises8.mkdir)(sourceDir);
2093
+ const root = await (0, import_promises10.mkdtemp)((0, import_node_path11.join)(tempRoot, "odla-code-source-"));
2094
+ const sourceDir = (0, import_node_path11.join)(root, "source");
2095
+ await (0, import_promises10.mkdir)(sourceDir);
2006
2096
  const seen = /* @__PURE__ */ new Set();
2007
2097
  let bytes = 0;
2008
2098
  try {
@@ -2012,10 +2102,10 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node
2012
2102
  seen.add(file.path);
2013
2103
  bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
2014
2104
  if (bytes > SOURCE_MAX_BYTES) throw new TypeError("Code source exceeds its byte bound");
2015
- const target = (0, import_node_path9.resolve)(sourceDir, file.path);
2016
- if (!target.startsWith(`${(0, import_node_path9.resolve)(sourceDir)}${import_node_path9.sep}`)) throw new TypeError("Code source path escapes its root");
2017
- await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
2018
- await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 420 });
2105
+ const target = (0, import_node_path11.resolve)(sourceDir, file.path);
2106
+ if (!target.startsWith(`${(0, import_node_path11.resolve)(sourceDir)}${import_node_path11.sep}`)) throw new TypeError("Code source path escapes its root");
2107
+ await (0, import_promises10.mkdir)((0, import_node_path11.dirname)(target), { recursive: true });
2108
+ await (0, import_promises10.writeFile)(target, file.content, { flag: "wx", mode: 420 });
2019
2109
  }
2020
2110
  for (const reference of snapshot.references ?? []) {
2021
2111
  validateAlias2(reference.alias);
@@ -2027,15 +2117,15 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node
2027
2117
  seen.add(path);
2028
2118
  bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
2029
2119
  if (bytes > SOURCE_SET_MAX_BYTES) throw new TypeError("Code source set exceeds its byte bound");
2030
- const target = (0, import_node_path9.resolve)(sourceDir, path);
2031
- if (!target.startsWith(`${(0, import_node_path9.resolve)(sourceDir)}${import_node_path9.sep}`)) throw new TypeError("Code reference path escapes its root");
2032
- await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
2033
- await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
2120
+ const target = (0, import_node_path11.resolve)(sourceDir, path);
2121
+ if (!target.startsWith(`${(0, import_node_path11.resolve)(sourceDir)}${import_node_path11.sep}`)) throw new TypeError("Code reference path escapes its root");
2122
+ await (0, import_promises10.mkdir)((0, import_node_path11.dirname)(target), { recursive: true });
2123
+ await (0, import_promises10.writeFile)(target, file.content, { flag: "wx", mode: 292 });
2034
2124
  }
2035
2125
  }
2036
- return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
2126
+ return { sourceDir, cleanup: () => (0, import_promises10.rm)(root, { recursive: true, force: true }) };
2037
2127
  } catch (cause) {
2038
- await (0, import_promises8.rm)(root, { recursive: true, force: true });
2128
+ await (0, import_promises10.rm)(root, { recursive: true, force: true });
2039
2129
  throw cause;
2040
2130
  }
2041
2131
  }
@@ -2054,10 +2144,10 @@ async function attachCodeRuntimeReferences(workspace, references) {
2054
2144
  bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
2055
2145
  if (bytes > SOURCE_SET_MAX_BYTES - SOURCE_MAX_BYTES) throw new TypeError("Code reference set exceeds its byte bound");
2056
2146
  for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
2057
- const target = (0, import_node_path9.resolve)(root, path);
2058
- if (!target.startsWith(`${(0, import_node_path9.resolve)(root)}${import_node_path9.sep}`)) throw new TypeError("Code reference path escapes its root");
2059
- await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
2060
- await (0, import_promises8.writeFile)(target, file.content, { flag: "wx", mode: 292 });
2147
+ const target = (0, import_node_path11.resolve)(root, path);
2148
+ if (!target.startsWith(`${(0, import_node_path11.resolve)(root)}${import_node_path11.sep}`)) throw new TypeError("Code reference path escapes its root");
2149
+ await (0, import_promises10.mkdir)((0, import_node_path11.dirname)(target), { recursive: true });
2150
+ await (0, import_promises10.writeFile)(target, file.content, { flag: "wx", mode: 292 });
2061
2151
  }
2062
2152
  }
2063
2153
  }
@@ -2503,7 +2593,7 @@ Finish with a concise, non-empty answer to the owner. Do not call tools or promi
2503
2593
  }
2504
2594
 
2505
2595
  // src/code-runtime-session-skills.ts
2506
- var sleep2 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
2596
+ var sleep2 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
2507
2597
  function createCodeRuntimeSessionSkillLoader(control, options = {}) {
2508
2598
  const wait2 = options.wait ?? sleep2;
2509
2599
  const load = control.collaborationSkills?.bind(control);
@@ -2562,7 +2652,7 @@ var inferWithBackoff = (infer, wait2, onRetry) => withOverloadRetry(infer, wait2
2562
2652
  async function handleCodeRuntimeInference(input) {
2563
2653
  const { command, request, state } = input;
2564
2654
  const startedAt = Date.now();
2565
- const wait2 = input.wait ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
2655
+ const wait2 = input.wait ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
2566
2656
  const response2 = await inferWithBackoff(
2567
2657
  () => input.control.infer(command.sessionId, {
2568
2658
  requestId: request.requestId,
@@ -2760,7 +2850,10 @@ function toolFailureReason(response2) {
2760
2850
  if (response2.ok) return void 0;
2761
2851
  const supplied = response2.details?.failureReason;
2762
2852
  const reason = typeof supplied === "string" && supplied || DEFAULT_FAILURE_REASON[response2.content] || response2.content || "tool request failed; inspect the tool input and workspace state";
2763
- return reason.slice(0, MAX_FAILURE_REASON);
2853
+ if (reason.length <= MAX_FAILURE_REASON) return reason;
2854
+ const head = reason.slice(0, 60);
2855
+ const gap = " \u2026 ";
2856
+ return `${head}${gap}${reason.slice(-(MAX_FAILURE_REASON - head.length - gap.length))}`;
2764
2857
  }
2765
2858
  var DEFAULT_FAILURE_REASON = {
2766
2859
  "tool denied by CaMeL policy": "tool denied by CaMeL policy",
@@ -3073,18 +3166,18 @@ function response(request, ok, content, details) {
3073
3166
  }
3074
3167
 
3075
3168
  // src/code-tool-edit.ts
3076
- var import_promises10 = require("fs/promises");
3169
+ var import_promises12 = require("fs/promises");
3077
3170
 
3078
3171
  // src/code-tool-graph.ts
3079
- var import_promises9 = require("fs/promises");
3080
- var import_node_path10 = require("path");
3172
+ var import_promises11 = require("fs/promises");
3173
+ var import_node_path12 = require("path");
3081
3174
  var import_graph = require("@odla-ai/graph");
3082
3175
  var import_code4 = require("@odla-ai/graph/code");
3083
3176
  var cache = /* @__PURE__ */ new Map();
3084
3177
  function workspaceGraphs(workspaceDir, paths2) {
3085
3178
  const existing = cache.get(workspaceDir);
3086
3179
  if (existing) return existing;
3087
- const read2 = (path) => (0, import_promises9.readFile)((0, import_node_path10.join)(workspaceDir, path), "utf8");
3180
+ const read2 = (path) => (0, import_promises11.readFile)((0, import_node_path12.join)(workspaceDir, path), "utf8");
3088
3181
  const built = (async () => ({
3089
3182
  // No knownTables: a staged workspace may not carry migrations, and a filter
3090
3183
  // that silently drops every table is worse than an unfiltered one. Callers
@@ -3160,7 +3253,7 @@ async function editCodeFile(workspaceDir, path, oldText, newText) {
3160
3253
  }
3161
3254
  if (oldText.includes("\0") || newText.includes("\0")) throw new TypeError("edit text contains NUL bytes; use plain text");
3162
3255
  const target = resolveCodePath(workspaceDir, path);
3163
- const current = await (0, import_promises10.readFile)(target, "utf8");
3256
+ const current = await (0, import_promises12.readFile)(target, "utf8");
3164
3257
  const found = occurrences(current, oldText);
3165
3258
  if (found === 0) {
3166
3259
  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 +3262,7 @@ async function editCodeFile(workspaceDir, path, oldText, newText) {
3169
3262
  throw new TypeError(`oldText occurs ${found} times in "${path}"; include more of the surrounding lines so it matches exactly once`);
3170
3263
  }
3171
3264
  const at = current.indexOf(oldText);
3172
- await (0, import_promises10.writeFile)(target, `${current.slice(0, at)}${newText}${current.slice(at + oldText.length)}`, "utf8");
3265
+ await (0, import_promises12.writeFile)(target, `${current.slice(0, at)}${newText}${current.slice(at + oldText.length)}`, "utf8");
3173
3266
  return { deletions: oldText.split("\n").length, additions: newText.split("\n").length };
3174
3267
  }
3175
3268
  async function edit(context, request, options, policy, registry) {
@@ -3194,8 +3287,8 @@ async function edit(context, request, options, policy, registry) {
3194
3287
  }
3195
3288
 
3196
3289
  // src/code-tool-write.ts
3197
- var import_promises11 = require("fs/promises");
3198
- var import_node_path11 = require("path");
3290
+ var import_promises13 = require("fs/promises");
3291
+ var import_node_path13 = require("path");
3199
3292
  var MAX_WRITE_BYTES = 256 * 1024;
3200
3293
  var lines = (text2) => text2 === "" ? [] : text2.replace(/\n$/, "").split("\n");
3201
3294
  function writeAsDiff(path, current, content) {
@@ -3220,8 +3313,8 @@ async function writeCodeFile(workspaceDir, path, content, current) {
3220
3313
  }
3221
3314
  if (content.includes("\0")) throw new TypeError("content contains NUL bytes; use plain text");
3222
3315
  const target = resolveCodePath(workspaceDir, path);
3223
- await (0, import_promises11.mkdir)((0, import_node_path11.dirname)(target), { recursive: true });
3224
- await (0, import_promises11.writeFile)(target, content, "utf8");
3316
+ await (0, import_promises13.mkdir)((0, import_node_path13.dirname)(target), { recursive: true });
3317
+ await (0, import_promises13.writeFile)(target, content, "utf8");
3225
3318
  return { created: current === null, deletions: current === null ? 0 : lines(current).length, additions: lines(content).length };
3226
3319
  }
3227
3320
  async function write(context, request, options, policy, registry) {
@@ -3232,7 +3325,7 @@ async function write(context, request, options, policy, registry) {
3232
3325
  throw new TypeError("write targets a read-only reference source");
3233
3326
  }
3234
3327
  const exists = (await registry.files(context.workspaceDir)).includes(path);
3235
- const current = exists ? await (0, import_promises11.readFile)(resolveCodePath(context.workspaceDir, path), "utf8") : null;
3328
+ const current = exists ? await (0, import_promises13.readFile)(resolveCodePath(context.workspaceDir, path), "utf8") : null;
3236
3329
  const patch2 = writeAsDiff(path, current, content);
3237
3330
  const allowed = await policy.write(policyContext(context, request, options, { patch: patch2 }));
3238
3331
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
@@ -3248,12 +3341,12 @@ async function write(context, request, options, policy, registry) {
3248
3341
  }
3249
3342
 
3250
3343
  // src/code-tool-reads.ts
3251
- var import_promises13 = require("fs/promises");
3344
+ var import_promises15 = require("fs/promises");
3252
3345
 
3253
3346
  // src/code-tool-discovery.ts
3254
- var import_node_child_process5 = require("child_process");
3255
- var import_promises12 = require("fs/promises");
3256
- var import_node_path12 = require("path");
3347
+ var import_node_child_process6 = require("child_process");
3348
+ var import_promises14 = require("fs/promises");
3349
+ var import_node_path14 = require("path");
3257
3350
  var DEFAULT_MAX_FILES = 2e4;
3258
3351
  var DEFAULT_MAX_RESULTS = 100;
3259
3352
  var DEFAULT_MAX_FILE_BYTES = 512 * 1024;
@@ -3278,13 +3371,13 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
3278
3371
  async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
3279
3372
  const paths2 = [];
3280
3373
  const walk = async (directory) => {
3281
- for (const entry of await (0, import_promises12.readdir)(directory, { withFileTypes: true })) {
3374
+ for (const entry of await (0, import_promises14.readdir)(directory, { withFileTypes: true })) {
3282
3375
  if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
3283
3376
  if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
3284
- const target = (0, import_node_path12.resolve)(directory, entry.name);
3377
+ const target = (0, import_node_path14.resolve)(directory, entry.name);
3285
3378
  if (entry.isDirectory()) await walk(target);
3286
3379
  else if (entry.isFile()) {
3287
- const path = (0, import_node_path12.relative)(root, target).split("\\").join("/");
3380
+ const path = (0, import_node_path14.relative)(root, target).split("\\").join("/");
3288
3381
  try {
3289
3382
  validateRelativePath(path);
3290
3383
  } catch {
@@ -3295,7 +3388,7 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
3295
3388
  }
3296
3389
  }
3297
3390
  };
3298
- await walk((0, import_node_path12.resolve)(root));
3391
+ await walk((0, import_node_path14.resolve)(root));
3299
3392
  return paths2.sort();
3300
3393
  }
3301
3394
  function listWorkspace(paths2, options = {}) {
@@ -3357,7 +3450,7 @@ function nativeSearchBatch(root, paths2, options, remaining) {
3357
3450
  options.query,
3358
3451
  ...paths2
3359
3452
  ];
3360
- const child = (0, import_node_child_process5.spawn)("rg", args, {
3453
+ const child = (0, import_node_child_process6.spawn)("rg", args, {
3361
3454
  cwd: root,
3362
3455
  stdio: ["ignore", "pipe", "ignore"],
3363
3456
  ...options.signal ? { signal: options.signal } : {}
@@ -3409,7 +3502,7 @@ async function fallbackSearch(root, scoped, options) {
3409
3502
  if (matches.length >= options.maxResults) break;
3410
3503
  let source;
3411
3504
  try {
3412
- source = await (0, import_promises12.readFile)((0, import_node_path12.resolve)(root, path));
3505
+ source = await (0, import_promises14.readFile)((0, import_node_path14.resolve)(root, path));
3413
3506
  } catch {
3414
3507
  continue;
3415
3508
  }
@@ -3448,11 +3541,11 @@ async function read(context, request, options, policy, registry) {
3448
3541
  const allowed = await policy.read(policyContext(context, request, options, { paths: paths2, path, startLine, endLine }));
3449
3542
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
3450
3543
  const target = resolveCodePath(context.workspaceDir, path);
3451
- const info = await (0, import_promises13.stat)(target);
3544
+ const info = await (0, import_promises15.stat)(target);
3452
3545
  if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
3453
3546
  throw new TypeError("file is not a bounded regular source file");
3454
3547
  }
3455
- const source = await (0, import_promises13.readFile)(target);
3548
+ const source = await (0, import_promises15.readFile)(target);
3456
3549
  if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
3457
3550
  const lines2 = source.toString("utf8").split("\n");
3458
3551
  const content = lines2.slice(startLine - 1, endLine).join("\n");
@@ -3977,8 +4070,8 @@ var runtimeErrorMessage = (value) => value instanceof Error ? value.message : St
3977
4070
  function codeRuntimeAcknowledgementGate(signal) {
3978
4071
  let settle;
3979
4072
  let settled = false;
3980
- const ready = new Promise((resolve7) => {
3981
- settle = resolve7;
4073
+ const ready = new Promise((resolve8) => {
4074
+ settle = resolve8;
3982
4075
  });
3983
4076
  const release = (run) => {
3984
4077
  if (settled) return;
@@ -4044,8 +4137,8 @@ function observeCodeRuntimeSessionSkills(command, skills, emit) {
4044
4137
  }
4045
4138
 
4046
4139
  // src/code-repository-recipes.ts
4047
- var import_promises14 = require("fs/promises");
4048
- var import_node_path13 = require("path");
4140
+ var import_promises16 = require("fs/promises");
4141
+ var import_node_path15 = require("path");
4049
4142
  var REPOSITORY_RECIPES_FILE = "odla.recipes.json";
4050
4143
  var MAX_RECIPES = 16;
4051
4144
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -4106,7 +4199,7 @@ function parseRepositoryRecipes(text2, envelope) {
4106
4199
  async function readRepositoryRecipes(baselineDir, envelope) {
4107
4200
  let text2;
4108
4201
  try {
4109
- text2 = await (0, import_promises14.readFile)((0, import_node_path13.join)(baselineDir, REPOSITORY_RECIPES_FILE), "utf8");
4202
+ text2 = await (0, import_promises16.readFile)((0, import_node_path15.join)(baselineDir, REPOSITORY_RECIPES_FILE), "utf8");
4110
4203
  } catch (cause) {
4111
4204
  if (cause.code === "ENOENT") return null;
4112
4205
  throw cause;
@@ -4486,7 +4579,7 @@ function parse(argv) {
4486
4579
  };
4487
4580
  }
4488
4581
  async function readPolicy(path) {
4489
- const value = JSON.parse(await (0, import_promises15.readFile)(path, "utf8"));
4582
+ const value = JSON.parse(await (0, import_promises17.readFile)(path, "utf8"));
4490
4583
  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
4584
  const recipes = value.recipes;
4492
4585
  for (const recipe2 of recipes) assertCodeBuildRecipe(recipe2);