@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.
package/dist/node.cjs CHANGED
@@ -40,11 +40,14 @@ __export(node_exports, {
40
40
  applyPatchDialectToDiff: () => applyPatchDialectToDiff,
41
41
  assertCodeBuildRecipe: () => assertCodeBuildRecipe,
42
42
  assertDisjointPlan: () => assertDisjointPlan,
43
+ assertLentPath: () => assertLentPath,
43
44
  assertPinnedImage: () => assertPinnedImage,
45
+ assertReservedMount: () => assertReservedMount,
44
46
  attachCodeRuntimeReferences: () => attachCodeRuntimeReferences,
45
47
  buildContainerRunArgs: () => buildContainerRunArgs,
46
48
  buildRecipeContainerArgs: () => buildRecipeContainerArgs,
47
49
  chooseStrategy: () => chooseStrategy,
50
+ cloneTree: () => cloneTree,
48
51
  codeSkill: () => codeSkill,
49
52
  createCodeRuntimeControlClient: () => createCodeRuntimeControlClient,
50
53
  createCodeRuntimeInference: () => createCodeRuntimeInference,
@@ -61,17 +64,23 @@ __export(node_exports, {
61
64
  feedbackIsActionable: () => feedbackIsActionable,
62
65
  hasContextFreeHunk: () => hasContextFreeHunk,
63
66
  hazardFromAttempt: () => hazardFromAttempt,
67
+ hostBuildProducts: () => hostBuildProducts,
64
68
  installedDependencies: () => installedDependencies,
65
69
  integrateSubGoals: () => integrateSubGoals,
66
70
  isCheckpointEffectCompleted: () => isCheckpointEffectCompleted,
71
+ lendBuildProducts: () => lendBuildProducts,
72
+ lendDependencies: () => lendDependencies,
73
+ lentDirectories: () => lentDirectories,
67
74
  materializeCodeRuntimeArchive: () => materializeCodeRuntimeArchive,
68
75
  materializeCodeRuntimeSource: () => materializeCodeRuntimeSource,
69
76
  materializeCommandWorkspace: () => materializeCommandWorkspace,
70
77
  materializeGitTree: () => materializeGitTree,
78
+ npmFailureSummary: () => npmFailureSummary,
71
79
  outcomeCloses: () => outcomeCloses,
72
80
  outcomeMemory: () => outcomeMemory,
73
81
  parseRepositoryRecipes: () => parseRepositoryRecipes,
74
82
  patchPaths: () => patchPaths,
83
+ pinnedNpmVersion: () => pinnedNpmVersion,
75
84
  planReachCollisions: () => planReachCollisions,
76
85
  prepareContainerDependencies: () => prepareContainerDependencies,
77
86
  prepareRuntimeCheckpoint: () => prepareRuntimeCheckpoint,
@@ -79,6 +88,7 @@ __export(node_exports, {
79
88
  readOnlyNotice: () => readOnlyNotice,
80
89
  readRepositoryRecipes: () => readRepositoryRecipes,
81
90
  recallAbout: () => recallAbout,
91
+ recipeOutput: () => recipeOutput,
82
92
  registeredFiles: () => registeredFiles,
83
93
  renderMemories: () => renderMemories,
84
94
  renderOutcome: () => renderOutcome,
@@ -110,7 +120,8 @@ __export(node_exports, {
110
120
  verifyCodeCandidate: () => verifyCodeCandidate,
111
121
  verifyContainerEngineBoundary: () => verifyContainerEngineBoundary,
112
122
  withProofRecipes: () => withProofRecipes,
113
- withRecipeDependencies: () => withRecipeDependencies
123
+ withRecipeDependencies: () => withRecipeDependencies,
124
+ workspaceDirs: () => workspaceDirs
114
125
  });
115
126
  module.exports = __toCommonJS(node_exports);
116
127
 
@@ -250,7 +261,7 @@ async function selectContainerEngine(requested = "auto", options = {}) {
250
261
  throw new TypeError("no supported container engine found");
251
262
  }
252
263
  function inspectRootlessPodman() {
253
- return new Promise((resolve8, reject) => {
264
+ return new Promise((resolve9, reject) => {
254
265
  (0, import_node_child_process.execFile)(
255
266
  "podman",
256
267
  ["info", "--format", "{{.Host.Security.Rootless}}"],
@@ -260,7 +271,7 @@ function inspectRootlessPodman() {
260
271
  reject(new TypeError("could not verify that the active Podman service is rootless"));
261
272
  return;
262
273
  }
263
- resolve8(stdout.trim() === "true");
274
+ resolve9(stdout.trim() === "true");
264
275
  }
265
276
  );
266
277
  });
@@ -1361,7 +1372,7 @@ async function withOverloadRetry(call, wait2, onRetry = () => void 0, retryable
1361
1372
  }
1362
1373
 
1363
1374
  // src/code-runtime-reconciler.ts
1364
- var sleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
1375
+ var sleep = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
1365
1376
  var POST_ACK_RETRY_TICKS = 40;
1366
1377
  var CodeRuntimeReconciler = class {
1367
1378
  constructor(control, engine, onDiagnostic, options = {}) {
@@ -1463,12 +1474,12 @@ function retryableControlFailure(value) {
1463
1474
  return failure.status === 408 || failure.status === 425 || failure.status === 429 || failure.status >= 500;
1464
1475
  }
1465
1476
  function wait(ms, signal) {
1466
- return new Promise((resolve8) => {
1467
- if (signal?.aborted) return resolve8();
1468
- const timer = setTimeout(resolve8, ms);
1477
+ return new Promise((resolve9) => {
1478
+ if (signal?.aborted) return resolve9();
1479
+ const timer = setTimeout(resolve9, ms);
1469
1480
  signal?.addEventListener("abort", () => {
1470
1481
  clearTimeout(timer);
1471
- resolve8();
1482
+ resolve9();
1472
1483
  }, { once: true });
1473
1484
  });
1474
1485
  }
@@ -1700,19 +1711,127 @@ function isCheckpointEffectCompleted(checkpoint, effectId, actionDigest) {
1700
1711
  // src/code-verifier.ts
1701
1712
  var import_node_crypto3 = require("crypto");
1702
1713
  var import_node_fs2 = require("fs");
1703
- var import_promises7 = require("fs/promises");
1704
- var import_node_path7 = require("path");
1714
+ var import_promises9 = require("fs/promises");
1715
+ var import_node_path9 = require("path");
1705
1716
  var import_code3 = require("@odla-ai/camel/code");
1706
1717
 
1707
1718
  // src/recipe-container.ts
1708
- var import_node_child_process5 = require("child_process");
1719
+ var import_node_child_process6 = require("child_process");
1709
1720
  var import_node_process2 = require("process");
1710
1721
  var import_node_crypto2 = require("crypto");
1722
+
1723
+ // src/code-recipe-dependencies.ts
1724
+ var import_promises7 = require("fs/promises");
1725
+ var import_node_path7 = require("path");
1726
+ var RESERVED_MOUNTS = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage"]);
1727
+ function assertLentPath(mountAs) {
1728
+ const parts = mountAs.split("/");
1729
+ if ((0, import_node_path7.isAbsolute)(mountAs) || mountAs.includes("\\") || parts.some((part) => !part || part === "." || part === "..")) {
1730
+ throw new TypeError(`recipe dependencies must mount inside the workspace, not at "${mountAs}"`);
1731
+ }
1732
+ }
1733
+ function assertReservedMount(mountAs) {
1734
+ assertLentPath(mountAs);
1735
+ if (!RESERVED_MOUNTS.has(mountAs.split("/").at(-1))) {
1736
+ throw new TypeError(`recipe dependencies must mount at a reserved name, not "${mountAs}"`);
1737
+ }
1738
+ }
1739
+ function lentDirectories(dependencies) {
1740
+ return [{ source: dependencies.source, mountAs: dependencies.mountAs ?? "node_modules" }, ...dependencies.nested ?? []];
1741
+ }
1742
+ function withRecipeDependencies(executor, dependencies) {
1743
+ const lent = lentDirectories(dependencies);
1744
+ for (const entry of lent) {
1745
+ if (!(0, import_node_path7.isAbsolute)(entry.source)) {
1746
+ throw new TypeError("recipe dependency source must be an absolute path");
1747
+ }
1748
+ assertReservedMount(entry.mountAs);
1749
+ }
1750
+ return {
1751
+ run: async (input) => {
1752
+ const linked = [];
1753
+ try {
1754
+ for (const entry of lent) {
1755
+ const target = (0, import_node_path7.join)(input.workspaceDir, entry.mountAs);
1756
+ if (await (0, import_promises7.lstat)(target).catch(() => null)) continue;
1757
+ await (0, import_promises7.symlink)(entry.source, target, "dir").then(() => linked.push(target), () => void 0);
1758
+ }
1759
+ return await executor.run(input);
1760
+ } finally {
1761
+ for (const target of linked) await (0, import_promises7.rm)(target, { force: true, recursive: false }).catch(() => void 0);
1762
+ }
1763
+ }
1764
+ };
1765
+ }
1766
+ async function installedDependencies(repoRoot) {
1767
+ const source = (0, import_node_path7.join)(repoRoot, "node_modules");
1768
+ const info = await (0, import_promises7.lstat)(source).catch(() => null);
1769
+ return info?.isDirectory() ? { source } : null;
1770
+ }
1771
+
1772
+ // src/recipe-container-lending.ts
1773
+ var import_node_child_process5 = require("child_process");
1774
+ var import_promises8 = require("fs/promises");
1775
+ var import_node_path8 = require("path");
1776
+ var RUN_ID = /^[A-Za-z0-9_.-]{1,64}$/;
1777
+ function copy(args) {
1778
+ return new Promise((accept, reject) => {
1779
+ const child = (0, import_node_child_process5.spawn)("cp", args, { shell: false, stdio: "ignore" });
1780
+ child.once("error", reject);
1781
+ child.once("exit", (code) => accept(code ?? 1));
1782
+ });
1783
+ }
1784
+ async function cloneTree(source, target, platform = process.platform) {
1785
+ await (0, import_promises8.mkdir)((0, import_node_path8.dirname)(target), { recursive: true });
1786
+ const fast = platform === "darwin" ? ["-Rc", source, target] : platform === "linux" ? ["-R", "--reflink=auto", source, target] : null;
1787
+ if (fast && await copy(fast) === 0) return;
1788
+ await (0, import_promises8.rm)(target, { recursive: true, force: true });
1789
+ if (await copy(["-R", source, target]) !== 0) throw new Error(`could not clone ${source}`);
1790
+ }
1791
+ async function lendDependencies(dependencies, runId) {
1792
+ if (!dependencies?.runsDir) return { dependencies, release: async () => void 0 };
1793
+ if (!RUN_ID.test(runId)) throw new TypeError("run id is not a safe directory name");
1794
+ const treeRoot = (0, import_node_path8.dirname)(dependencies.source);
1795
+ for (const entry of lentDirectories(dependencies)) {
1796
+ if (entry.source !== (0, import_node_path8.join)(treeRoot, entry.mountAs)) {
1797
+ throw new TypeError("every directory of a cloned set must lie under its tree root at its mount path");
1798
+ }
1799
+ }
1800
+ const runDir = (0, import_node_path8.join)(dependencies.runsDir, runId);
1801
+ await cloneTree(treeRoot, runDir);
1802
+ const rebase = (entry) => ({ source: (0, import_node_path8.join)(runDir, entry.mountAs), mountAs: entry.mountAs });
1803
+ return {
1804
+ dependencies: {
1805
+ ...dependencies,
1806
+ source: (0, import_node_path8.join)(runDir, dependencies.mountAs ?? "node_modules"),
1807
+ nested: (dependencies.nested ?? []).map(rebase),
1808
+ writable: true
1809
+ },
1810
+ release: () => (0, import_promises8.rm)(runDir, { recursive: true, force: true })
1811
+ };
1812
+ }
1813
+ async function lendBuildProducts(products, workspaceDir) {
1814
+ const root = (0, import_node_path8.resolve)(workspaceDir);
1815
+ const lent = [];
1816
+ for (const product of products) {
1817
+ assertLentPath(product.mountAs);
1818
+ if (SECRET_WORKSPACE_FILE.test(product.mountAs.split("/").at(-1))) throw new TypeError("a secret is never a build product");
1819
+ const target = (0, import_node_path8.resolve)(root, product.mountAs);
1820
+ if (!target.startsWith(`${root}${import_node_path8.sep}`)) throw new TypeError("build product escapes the workspace");
1821
+ if (await (0, import_promises8.stat)(target).catch(() => null)) continue;
1822
+ if (!await (0, import_promises8.stat)((0, import_node_path8.join)(root, product.within ?? (0, import_node_path8.dirname)(product.mountAs))).catch(() => null)) continue;
1823
+ await cloneTree(product.source, target);
1824
+ lent.push(product.mountAs);
1825
+ }
1826
+ return lent;
1827
+ }
1828
+
1829
+ // src/recipe-container.ts
1711
1830
  var ARTIFACT_PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
1712
1831
  var PRIVATE_ARTIFACT_PART = /^(?:\.git|\.odla|\.wrangler|\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json)$/i;
1713
1832
  function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${(0, import_node_crypto2.randomUUID)().slice(0, 12)}`, dependencies = null) {
1714
1833
  assertCodeBuildRecipe(recipe2);
1715
- const mounts = dependencies ? [dependencyMount(engine, dependencies)] : [];
1834
+ const mounts = dependencies ? dependencyMounts(engine, dependencies) : [];
1716
1835
  if (/[,\r\n]/.test(workspaceDir)) throw new TypeError("workspace path contains unsupported mount characters");
1717
1836
  const uid = typeof import_node_process2.getuid === "function" ? (0, import_node_process2.getuid)() : 1e3;
1718
1837
  const gid = typeof import_node_process2.getgid === "function" ? (0, import_node_process2.getgid)() : 1e3;
@@ -1738,6 +1857,7 @@ function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-re
1738
1857
  ...mounts,
1739
1858
  "--workdir=/workspace",
1740
1859
  "--env=CI=1",
1860
+ "--env=HOME=/tmp",
1741
1861
  recipe2.image,
1742
1862
  ...recipe2.command
1743
1863
  ];
@@ -1760,26 +1880,34 @@ function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-re
1760
1880
  ...mounts,
1761
1881
  "--workdir=/workspace",
1762
1882
  "--env=CI=1",
1883
+ "--env=HOME=/tmp",
1763
1884
  recipe2.image,
1764
1885
  ...recipe2.command
1765
1886
  ];
1766
1887
  }
1767
- var LENDABLE = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage"]);
1768
- function dependencyMount(engine, dependencies) {
1769
- const mountAs = dependencies.mountAs ?? "node_modules";
1770
- if (!dependencies.source.startsWith("/") || /[,\r\n]/.test(dependencies.source)) {
1771
- throw new TypeError("recipe dependency source must be an absolute path without mount separators");
1772
- }
1773
- if (!LENDABLE.has(mountAs)) throw new TypeError(`recipe dependencies must mount at a reserved name, not "${mountAs}"`);
1774
- return engine === "container" ? `--mount=type=bind,source=${dependencies.source},target=/workspace/${mountAs},readonly` : `--mount=type=bind,src=${dependencies.source},dst=/workspace/${mountAs},readonly`;
1888
+ function dependencyMounts(engine, dependencies) {
1889
+ const readonly = dependencies.writable ? "" : ",readonly";
1890
+ return lentDirectories(dependencies).map(({ source, mountAs }) => {
1891
+ if (!source.startsWith("/") || /[,\r\n]/.test(source)) {
1892
+ throw new TypeError("recipe dependency source must be an absolute path without mount separators");
1893
+ }
1894
+ assertReservedMount(mountAs);
1895
+ return engine === "container" ? `--mount=type=bind,source=${source},target=/workspace/${mountAs}${readonly}` : `--mount=type=bind,src=${source},dst=/workspace/${mountAs}${readonly}`;
1896
+ });
1775
1897
  }
1776
1898
  function createContainerRecipeExecutor(engine, options = {}) {
1777
1899
  return {
1778
1900
  async run(input) {
1779
1901
  await verifyContainerEngineBoundary(engine);
1780
1902
  const name = `odla-recipe-${(0, import_node_crypto2.randomUUID)().slice(0, 12)}`;
1781
- const args = buildRecipeContainerArgs(engine, input.workspaceDir, input.recipe, name, options.dependencies ?? null);
1782
- return execute(engine, args, name, input.recipe, input.signal);
1903
+ const lent = await lendDependencies(options.dependencies ?? null, name);
1904
+ try {
1905
+ if (lent.dependencies?.products?.length) await lendBuildProducts(lent.dependencies.products, input.workspaceDir);
1906
+ const args = buildRecipeContainerArgs(engine, input.workspaceDir, input.recipe, name, lent.dependencies);
1907
+ return await execute(engine, args, name, input.recipe, input.signal);
1908
+ } finally {
1909
+ await lent.release();
1910
+ }
1783
1911
  }
1784
1912
  };
1785
1913
  }
@@ -1801,10 +1929,14 @@ function parseMemory(value) {
1801
1929
  function execute(engine, args, name, recipe2, signal) {
1802
1930
  return runContainerCommand(engine, args, name, { timeoutMs: recipe2.timeoutMs, maxOutputBytes: recipe2.maxOutputBytes }, signal);
1803
1931
  }
1932
+ var CONTAINER_PROGRESS = /^\[\d+\/\d+\] .*$/gm;
1933
+ function recipeOutput(engine, stderr) {
1934
+ return engine === "container" ? stderr.replace(CONTAINER_PROGRESS, "").replace(/^\n+/, "") : stderr;
1935
+ }
1804
1936
  function runContainerCommand(engine, args, name, recipe2, signal) {
1805
1937
  return new Promise((accept, reject) => {
1806
1938
  const started = Date.now();
1807
- const child = (0, import_node_child_process5.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
1939
+ const child = (0, import_node_child_process6.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
1808
1940
  const stdout = [];
1809
1941
  const stderr = [];
1810
1942
  let bytes = 0;
@@ -1817,7 +1949,7 @@ function runContainerCommand(engine, args, name, recipe2, signal) {
1817
1949
  timedOut = reason === "timeout";
1818
1950
  outputLimitExceeded = reason === "output";
1819
1951
  const remove = engine === "container" ? ["delete", "--force", name] : ["rm", "-f", name];
1820
- const killer = (0, import_node_child_process5.spawn)(engine, remove, { shell: false, stdio: "ignore" });
1952
+ const killer = (0, import_node_child_process6.spawn)(engine, remove, { shell: false, stdio: "ignore" });
1821
1953
  killer.unref();
1822
1954
  child.kill("SIGTERM");
1823
1955
  };
@@ -1843,7 +1975,7 @@ function runContainerCommand(engine, args, name, recipe2, signal) {
1843
1975
  accept({
1844
1976
  exitCode: code ?? 1,
1845
1977
  stdout: Buffer.concat(stdout).toString("utf8"),
1846
- stderr: Buffer.concat(stderr).toString("utf8"),
1978
+ stderr: recipeOutput(engine, Buffer.concat(stderr).toString("utf8")),
1847
1979
  durationMs: Date.now() - started,
1848
1980
  outputLimitExceeded,
1849
1981
  timedOut
@@ -1961,8 +2093,8 @@ async function inspectArtifacts(workspaceDir, recipe2) {
1961
2093
  const receipts = [];
1962
2094
  for (const artifact of recipe2.expectedArtifacts ?? []) {
1963
2095
  try {
1964
- const path = (0, import_node_path7.join)(workspaceDir, artifact.path);
1965
- const info = await (0, import_promises7.lstat)(path);
2096
+ const path = (0, import_node_path9.join)(workspaceDir, artifact.path);
2097
+ const info = await (0, import_promises9.lstat)(path);
1966
2098
  if (!info.isFile() || info.isSymbolicLink()) {
1967
2099
  receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
1968
2100
  } else if (info.size > artifact.maximumBytes) {
@@ -2293,15 +2425,15 @@ async function prepareRuntimeLocalSource(input) {
2293
2425
  }
2294
2426
 
2295
2427
  // src/code-runtime-source.ts
2296
- var import_promises10 = require("fs/promises");
2428
+ var import_promises12 = require("fs/promises");
2297
2429
  var import_node_os4 = require("os");
2298
- var import_node_path10 = require("path");
2430
+ var import_node_path12 = require("path");
2299
2431
 
2300
2432
  // src/code-runtime-archive.ts
2301
2433
  var import_node_zlib = require("zlib");
2302
- var import_promises8 = require("fs/promises");
2434
+ var import_promises10 = require("fs/promises");
2303
2435
  var import_node_os3 = require("os");
2304
- var import_node_path8 = require("path");
2436
+ var import_node_path10 = require("path");
2305
2437
  var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
2306
2438
  var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
2307
2439
  var MAX_NUL_SHARE = 0.1;
@@ -2319,9 +2451,9 @@ async function materializeCodeRuntimeArchive(archive, visiblePaths, tempRoot = (
2319
2451
  throw new TypeError("Code source archive exceeds its decompressed byte bound");
2320
2452
  }
2321
2453
  const entries = parseTar(bytes, archive.limits);
2322
- const root = await (0, import_promises8.mkdtemp)((0, import_node_path8.join)(tempRoot, "odla-code-archive-"));
2323
- const sourceDir = (0, import_node_path8.join)(root, "source");
2324
- await (0, import_promises8.mkdir)(sourceDir);
2454
+ const root = await (0, import_promises10.mkdtemp)((0, import_node_path10.join)(tempRoot, "odla-code-archive-"));
2455
+ const sourceDir = (0, import_node_path10.join)(root, "source");
2456
+ await (0, import_promises10.mkdir)(sourceDir);
2325
2457
  let visible = 0;
2326
2458
  try {
2327
2459
  for (const entry of entries) {
@@ -2334,16 +2466,16 @@ async function materializeCodeRuntimeArchive(archive, visiblePaths, tempRoot = (
2334
2466
  continue;
2335
2467
  }
2336
2468
  if (nulShare(content) > MAX_NUL_SHARE) continue;
2337
- const target = (0, import_node_path8.resolve)(sourceDir, entry.path);
2338
- if (!target.startsWith(`${(0, import_node_path8.resolve)(sourceDir)}${import_node_path8.sep}`)) throw new TypeError("Code source path escapes its root");
2339
- await (0, import_promises8.mkdir)((0, import_node_path8.dirname)(target), { recursive: true });
2340
- await (0, import_promises8.writeFile)(target, content, { flag: "wx", mode: 420 });
2469
+ const target = (0, import_node_path10.resolve)(sourceDir, entry.path);
2470
+ if (!target.startsWith(`${(0, import_node_path10.resolve)(sourceDir)}${import_node_path10.sep}`)) throw new TypeError("Code source path escapes its root");
2471
+ await (0, import_promises10.mkdir)((0, import_node_path10.dirname)(target), { recursive: true });
2472
+ await (0, import_promises10.writeFile)(target, content, { flag: "wx", mode: 420 });
2341
2473
  visible += 1;
2342
2474
  }
2343
2475
  if (!visible && !visiblePaths) throw new TypeError("GitHub commit has no Code-visible text source");
2344
- return { sourceDir, cleanup: () => (0, import_promises8.rm)(root, { recursive: true, force: true }) };
2476
+ return { sourceDir, cleanup: () => (0, import_promises10.rm)(root, { recursive: true, force: true }) };
2345
2477
  } catch (cause) {
2346
- await (0, import_promises8.rm)(root, { recursive: true, force: true });
2478
+ await (0, import_promises10.rm)(root, { recursive: true, force: true });
2347
2479
  throw cause;
2348
2480
  }
2349
2481
  }
@@ -2538,8 +2670,8 @@ function nulShare(content) {
2538
2670
  }
2539
2671
 
2540
2672
  // src/code-runtime-selected-source.ts
2541
- var import_promises9 = require("fs/promises");
2542
- var import_node_path9 = require("path");
2673
+ var import_promises11 = require("fs/promises");
2674
+ var import_node_path11 = require("path");
2543
2675
  function selectedSourceSet(payload) {
2544
2676
  if (!payload.sourceSet) return null;
2545
2677
  const set = payload.sourceSet && typeof payload.sourceSet === "object" && !Array.isArray(payload.sourceSet) ? payload.sourceSet : null;
@@ -2576,9 +2708,9 @@ async function attachReferenceDirectories(workspace, references) {
2576
2708
  for (const reference of references) {
2577
2709
  validateAlias(reference.alias);
2578
2710
  for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
2579
- const target = (0, import_node_path9.join)(root, ".odla-references", reference.alias);
2580
- await (0, import_promises9.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
2581
- await (0, import_promises9.cp)(reference.sourceDir, target, { recursive: true, errorOnExist: true, force: false });
2711
+ const target = (0, import_node_path11.join)(root, ".odla-references", reference.alias);
2712
+ await (0, import_promises11.mkdir)((0, import_node_path11.dirname)(target), { recursive: true });
2713
+ await (0, import_promises11.cp)(reference.sourceDir, target, { recursive: true, errorOnExist: true, force: false });
2582
2714
  await makeTreeReadOnly(target);
2583
2715
  }
2584
2716
  }
@@ -2589,10 +2721,10 @@ function validateAlias(alias) {
2589
2721
  }
2590
2722
  }
2591
2723
  async function makeTreeReadOnly(root) {
2592
- for (const entry of await (0, import_promises9.readdir)(root, { withFileTypes: true })) {
2593
- const target = (0, import_node_path9.join)(root, entry.name);
2724
+ for (const entry of await (0, import_promises11.readdir)(root, { withFileTypes: true })) {
2725
+ const target = (0, import_node_path11.join)(root, entry.name);
2594
2726
  if (entry.isDirectory()) await makeTreeReadOnly(target);
2595
- else if (entry.isFile()) await (0, import_promises9.chmod)(target, 292);
2727
+ else if (entry.isFile()) await (0, import_promises11.chmod)(target, 292);
2596
2728
  }
2597
2729
  }
2598
2730
 
@@ -2604,9 +2736,9 @@ var SOURCE_MAX_BYTES = 80 * 1024 * 1024;
2604
2736
  var SOURCE_SET_MAX_BYTES = 480 * 1024 * 1024;
2605
2737
  async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node_os4.tmpdir)()) {
2606
2738
  if (!snapshot.files.length || snapshot.files.length > SOURCE_MAX_FILES) throw new TypeError("Code source file count is invalid");
2607
- const root = await (0, import_promises10.mkdtemp)((0, import_node_path10.join)(tempRoot, "odla-code-source-"));
2608
- const sourceDir = (0, import_node_path10.join)(root, "source");
2609
- await (0, import_promises10.mkdir)(sourceDir);
2739
+ const root = await (0, import_promises12.mkdtemp)((0, import_node_path12.join)(tempRoot, "odla-code-source-"));
2740
+ const sourceDir = (0, import_node_path12.join)(root, "source");
2741
+ await (0, import_promises12.mkdir)(sourceDir);
2610
2742
  const seen = /* @__PURE__ */ new Set();
2611
2743
  let bytes = 0;
2612
2744
  try {
@@ -2616,10 +2748,10 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node
2616
2748
  seen.add(file.path);
2617
2749
  bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
2618
2750
  if (bytes > SOURCE_MAX_BYTES) throw new TypeError("Code source exceeds its byte bound");
2619
- const target = (0, import_node_path10.resolve)(sourceDir, file.path);
2620
- if (!target.startsWith(`${(0, import_node_path10.resolve)(sourceDir)}${import_node_path10.sep}`)) throw new TypeError("Code source path escapes its root");
2621
- await (0, import_promises10.mkdir)((0, import_node_path10.dirname)(target), { recursive: true });
2622
- await (0, import_promises10.writeFile)(target, file.content, { flag: "wx", mode: 420 });
2751
+ const target = (0, import_node_path12.resolve)(sourceDir, file.path);
2752
+ if (!target.startsWith(`${(0, import_node_path12.resolve)(sourceDir)}${import_node_path12.sep}`)) throw new TypeError("Code source path escapes its root");
2753
+ await (0, import_promises12.mkdir)((0, import_node_path12.dirname)(target), { recursive: true });
2754
+ await (0, import_promises12.writeFile)(target, file.content, { flag: "wx", mode: 420 });
2623
2755
  }
2624
2756
  for (const reference of snapshot.references ?? []) {
2625
2757
  validateAlias2(reference.alias);
@@ -2631,15 +2763,15 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node
2631
2763
  seen.add(path);
2632
2764
  bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
2633
2765
  if (bytes > SOURCE_SET_MAX_BYTES) throw new TypeError("Code source set exceeds its byte bound");
2634
- const target = (0, import_node_path10.resolve)(sourceDir, path);
2635
- if (!target.startsWith(`${(0, import_node_path10.resolve)(sourceDir)}${import_node_path10.sep}`)) throw new TypeError("Code reference path escapes its root");
2636
- await (0, import_promises10.mkdir)((0, import_node_path10.dirname)(target), { recursive: true });
2637
- await (0, import_promises10.writeFile)(target, file.content, { flag: "wx", mode: 292 });
2766
+ const target = (0, import_node_path12.resolve)(sourceDir, path);
2767
+ if (!target.startsWith(`${(0, import_node_path12.resolve)(sourceDir)}${import_node_path12.sep}`)) throw new TypeError("Code reference path escapes its root");
2768
+ await (0, import_promises12.mkdir)((0, import_node_path12.dirname)(target), { recursive: true });
2769
+ await (0, import_promises12.writeFile)(target, file.content, { flag: "wx", mode: 292 });
2638
2770
  }
2639
2771
  }
2640
- return { sourceDir, cleanup: () => (0, import_promises10.rm)(root, { recursive: true, force: true }) };
2772
+ return { sourceDir, cleanup: () => (0, import_promises12.rm)(root, { recursive: true, force: true }) };
2641
2773
  } catch (cause) {
2642
- await (0, import_promises10.rm)(root, { recursive: true, force: true });
2774
+ await (0, import_promises12.rm)(root, { recursive: true, force: true });
2643
2775
  throw cause;
2644
2776
  }
2645
2777
  }
@@ -2658,10 +2790,10 @@ async function attachCodeRuntimeReferences(workspace, references) {
2658
2790
  bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
2659
2791
  if (bytes > SOURCE_SET_MAX_BYTES - SOURCE_MAX_BYTES) throw new TypeError("Code reference set exceeds its byte bound");
2660
2792
  for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
2661
- const target = (0, import_node_path10.resolve)(root, path);
2662
- if (!target.startsWith(`${(0, import_node_path10.resolve)(root)}${import_node_path10.sep}`)) throw new TypeError("Code reference path escapes its root");
2663
- await (0, import_promises10.mkdir)((0, import_node_path10.dirname)(target), { recursive: true });
2664
- await (0, import_promises10.writeFile)(target, file.content, { flag: "wx", mode: 292 });
2793
+ const target = (0, import_node_path12.resolve)(root, path);
2794
+ if (!target.startsWith(`${(0, import_node_path12.resolve)(root)}${import_node_path12.sep}`)) throw new TypeError("Code reference path escapes its root");
2795
+ await (0, import_promises12.mkdir)((0, import_node_path12.dirname)(target), { recursive: true });
2796
+ await (0, import_promises12.writeFile)(target, file.content, { flag: "wx", mode: 292 });
2665
2797
  }
2666
2798
  }
2667
2799
  }
@@ -3107,7 +3239,7 @@ Finish with a concise, non-empty answer to the owner. Do not call tools or promi
3107
3239
  }
3108
3240
 
3109
3241
  // src/code-runtime-session-skills.ts
3110
- var sleep2 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
3242
+ var sleep2 = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
3111
3243
  function createCodeRuntimeSessionSkillLoader(control, options = {}) {
3112
3244
  const wait2 = options.wait ?? sleep2;
3113
3245
  const load = control.collaborationSkills?.bind(control);
@@ -3166,7 +3298,7 @@ var inferWithBackoff = (infer, wait2, onRetry) => withOverloadRetry(infer, wait2
3166
3298
  async function handleCodeRuntimeInference(input) {
3167
3299
  const { command, request, state } = input;
3168
3300
  const startedAt = Date.now();
3169
- const wait2 = input.wait ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
3301
+ const wait2 = input.wait ?? ((ms) => new Promise((resolve9) => setTimeout(resolve9, ms)));
3170
3302
  const response2 = await inferWithBackoff(
3171
3303
  () => input.control.infer(command.sessionId, {
3172
3304
  requestId: request.requestId,
@@ -3364,7 +3496,10 @@ function toolFailureReason(response2) {
3364
3496
  if (response2.ok) return void 0;
3365
3497
  const supplied = response2.details?.failureReason;
3366
3498
  const reason = typeof supplied === "string" && supplied || DEFAULT_FAILURE_REASON[response2.content] || response2.content || "tool request failed; inspect the tool input and workspace state";
3367
- return reason.slice(0, MAX_FAILURE_REASON);
3499
+ if (reason.length <= MAX_FAILURE_REASON) return reason;
3500
+ const head = reason.slice(0, 60);
3501
+ const gap = " \u2026 ";
3502
+ return `${head}${gap}${reason.slice(-(MAX_FAILURE_REASON - head.length - gap.length))}`;
3368
3503
  }
3369
3504
  var DEFAULT_FAILURE_REASON = {
3370
3505
  "tool denied by CaMeL policy": "tool denied by CaMeL policy",
@@ -3677,18 +3812,18 @@ function response(request, ok, content, details) {
3677
3812
  }
3678
3813
 
3679
3814
  // src/code-tool-edit.ts
3680
- var import_promises12 = require("fs/promises");
3815
+ var import_promises14 = require("fs/promises");
3681
3816
 
3682
3817
  // src/code-tool-graph.ts
3683
- var import_promises11 = require("fs/promises");
3684
- var import_node_path11 = require("path");
3818
+ var import_promises13 = require("fs/promises");
3819
+ var import_node_path13 = require("path");
3685
3820
  var import_graph = require("@odla-ai/graph");
3686
3821
  var import_code4 = require("@odla-ai/graph/code");
3687
3822
  var cache = /* @__PURE__ */ new Map();
3688
3823
  function workspaceGraphs(workspaceDir, paths2) {
3689
3824
  const existing = cache.get(workspaceDir);
3690
3825
  if (existing) return existing;
3691
- const read2 = (path) => (0, import_promises11.readFile)((0, import_node_path11.join)(workspaceDir, path), "utf8");
3826
+ const read2 = (path) => (0, import_promises13.readFile)((0, import_node_path13.join)(workspaceDir, path), "utf8");
3692
3827
  const built = (async () => ({
3693
3828
  // No knownTables: a staged workspace may not carry migrations, and a filter
3694
3829
  // that silently drops every table is worse than an unfiltered one. Callers
@@ -3764,7 +3899,7 @@ async function editCodeFile(workspaceDir, path, oldText, newText) {
3764
3899
  }
3765
3900
  if (oldText.includes("\0") || newText.includes("\0")) throw new TypeError("edit text contains NUL bytes; use plain text");
3766
3901
  const target = resolveCodePath(workspaceDir, path);
3767
- const current = await (0, import_promises12.readFile)(target, "utf8");
3902
+ const current = await (0, import_promises14.readFile)(target, "utf8");
3768
3903
  const found = occurrences(current, oldText);
3769
3904
  if (found === 0) {
3770
3905
  throw new TypeError(`oldText was not found in "${path}"; sandbox.read the current lines and copy them exactly, including indentation and blank lines`);
@@ -3773,7 +3908,7 @@ async function editCodeFile(workspaceDir, path, oldText, newText) {
3773
3908
  throw new TypeError(`oldText occurs ${found} times in "${path}"; include more of the surrounding lines so it matches exactly once`);
3774
3909
  }
3775
3910
  const at = current.indexOf(oldText);
3776
- await (0, import_promises12.writeFile)(target, `${current.slice(0, at)}${newText}${current.slice(at + oldText.length)}`, "utf8");
3911
+ await (0, import_promises14.writeFile)(target, `${current.slice(0, at)}${newText}${current.slice(at + oldText.length)}`, "utf8");
3777
3912
  return { deletions: oldText.split("\n").length, additions: newText.split("\n").length };
3778
3913
  }
3779
3914
  async function edit(context, request, options, policy, registry) {
@@ -3798,8 +3933,8 @@ async function edit(context, request, options, policy, registry) {
3798
3933
  }
3799
3934
 
3800
3935
  // src/code-tool-write.ts
3801
- var import_promises13 = require("fs/promises");
3802
- var import_node_path12 = require("path");
3936
+ var import_promises15 = require("fs/promises");
3937
+ var import_node_path14 = require("path");
3803
3938
  var MAX_WRITE_BYTES = 256 * 1024;
3804
3939
  var lines = (text2) => text2 === "" ? [] : text2.replace(/\n$/, "").split("\n");
3805
3940
  function writeAsDiff(path, current, content) {
@@ -3824,8 +3959,8 @@ async function writeCodeFile(workspaceDir, path, content, current) {
3824
3959
  }
3825
3960
  if (content.includes("\0")) throw new TypeError("content contains NUL bytes; use plain text");
3826
3961
  const target = resolveCodePath(workspaceDir, path);
3827
- await (0, import_promises13.mkdir)((0, import_node_path12.dirname)(target), { recursive: true });
3828
- await (0, import_promises13.writeFile)(target, content, "utf8");
3962
+ await (0, import_promises15.mkdir)((0, import_node_path14.dirname)(target), { recursive: true });
3963
+ await (0, import_promises15.writeFile)(target, content, "utf8");
3829
3964
  return { created: current === null, deletions: current === null ? 0 : lines(current).length, additions: lines(content).length };
3830
3965
  }
3831
3966
  async function write(context, request, options, policy, registry) {
@@ -3836,7 +3971,7 @@ async function write(context, request, options, policy, registry) {
3836
3971
  throw new TypeError("write targets a read-only reference source");
3837
3972
  }
3838
3973
  const exists2 = (await registry.files(context.workspaceDir)).includes(path);
3839
- const current = exists2 ? await (0, import_promises13.readFile)(resolveCodePath(context.workspaceDir, path), "utf8") : null;
3974
+ const current = exists2 ? await (0, import_promises15.readFile)(resolveCodePath(context.workspaceDir, path), "utf8") : null;
3840
3975
  const patch2 = writeAsDiff(path, current, content);
3841
3976
  const allowed = await policy.write(policyContext(context, request, options, { patch: patch2 }));
3842
3977
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
@@ -3852,12 +3987,12 @@ async function write(context, request, options, policy, registry) {
3852
3987
  }
3853
3988
 
3854
3989
  // src/code-tool-reads.ts
3855
- var import_promises15 = require("fs/promises");
3990
+ var import_promises17 = require("fs/promises");
3856
3991
 
3857
3992
  // src/code-tool-discovery.ts
3858
- var import_node_child_process6 = require("child_process");
3859
- var import_promises14 = require("fs/promises");
3860
- var import_node_path13 = require("path");
3993
+ var import_node_child_process7 = require("child_process");
3994
+ var import_promises16 = require("fs/promises");
3995
+ var import_node_path15 = require("path");
3861
3996
  var DEFAULT_MAX_FILES = 2e4;
3862
3997
  var DEFAULT_MAX_RESULTS = 100;
3863
3998
  var DEFAULT_MAX_FILE_BYTES = 512 * 1024;
@@ -3882,13 +4017,13 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
3882
4017
  async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
3883
4018
  const paths2 = [];
3884
4019
  const walk = async (directory) => {
3885
- for (const entry of await (0, import_promises14.readdir)(directory, { withFileTypes: true })) {
4020
+ for (const entry of await (0, import_promises16.readdir)(directory, { withFileTypes: true })) {
3886
4021
  if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
3887
4022
  if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
3888
- const target = (0, import_node_path13.resolve)(directory, entry.name);
4023
+ const target = (0, import_node_path15.resolve)(directory, entry.name);
3889
4024
  if (entry.isDirectory()) await walk(target);
3890
4025
  else if (entry.isFile()) {
3891
- const path = (0, import_node_path13.relative)(root, target).split("\\").join("/");
4026
+ const path = (0, import_node_path15.relative)(root, target).split("\\").join("/");
3892
4027
  try {
3893
4028
  validateRelativePath(path);
3894
4029
  } catch {
@@ -3899,7 +4034,7 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
3899
4034
  }
3900
4035
  }
3901
4036
  };
3902
- await walk((0, import_node_path13.resolve)(root));
4037
+ await walk((0, import_node_path15.resolve)(root));
3903
4038
  return paths2.sort();
3904
4039
  }
3905
4040
  function listWorkspace(paths2, options = {}) {
@@ -3961,7 +4096,7 @@ function nativeSearchBatch(root, paths2, options, remaining) {
3961
4096
  options.query,
3962
4097
  ...paths2
3963
4098
  ];
3964
- const child = (0, import_node_child_process6.spawn)("rg", args, {
4099
+ const child = (0, import_node_child_process7.spawn)("rg", args, {
3965
4100
  cwd: root,
3966
4101
  stdio: ["ignore", "pipe", "ignore"],
3967
4102
  ...options.signal ? { signal: options.signal } : {}
@@ -4013,7 +4148,7 @@ async function fallbackSearch(root, scoped, options) {
4013
4148
  if (matches.length >= options.maxResults) break;
4014
4149
  let source;
4015
4150
  try {
4016
- source = await (0, import_promises14.readFile)((0, import_node_path13.resolve)(root, path));
4151
+ source = await (0, import_promises16.readFile)((0, import_node_path15.resolve)(root, path));
4017
4152
  } catch {
4018
4153
  continue;
4019
4154
  }
@@ -4052,11 +4187,11 @@ async function read(context, request, options, policy, registry) {
4052
4187
  const allowed = await policy.read(policyContext(context, request, options, { paths: paths2, path, startLine, endLine }));
4053
4188
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
4054
4189
  const target = resolveCodePath(context.workspaceDir, path);
4055
- const info = await (0, import_promises15.stat)(target);
4190
+ const info = await (0, import_promises17.stat)(target);
4056
4191
  if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
4057
4192
  throw new TypeError("file is not a bounded regular source file");
4058
4193
  }
4059
- const source = await (0, import_promises15.readFile)(target);
4194
+ const source = await (0, import_promises17.readFile)(target);
4060
4195
  if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
4061
4196
  const lines2 = source.toString("utf8").split("\n");
4062
4197
  const content = lines2.slice(startLine - 1, endLine).join("\n");
@@ -4604,8 +4739,8 @@ var runtimeErrorMessage = (value) => value instanceof Error ? value.message : St
4604
4739
  function codeRuntimeAcknowledgementGate(signal) {
4605
4740
  let settle;
4606
4741
  let settled = false;
4607
- const ready = new Promise((resolve8) => {
4608
- settle = resolve8;
4742
+ const ready = new Promise((resolve9) => {
4743
+ settle = resolve9;
4609
4744
  });
4610
4745
  const release = (run) => {
4611
4746
  if (settled) return;
@@ -4671,8 +4806,8 @@ function observeCodeRuntimeSessionSkills(command, skills, emit) {
4671
4806
  }
4672
4807
 
4673
4808
  // src/code-repository-recipes.ts
4674
- var import_promises16 = require("fs/promises");
4675
- var import_node_path14 = require("path");
4809
+ var import_promises18 = require("fs/promises");
4810
+ var import_node_path16 = require("path");
4676
4811
  var REPOSITORY_RECIPES_FILE = "odla.recipes.json";
4677
4812
  var MAX_RECIPES = 16;
4678
4813
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -4733,7 +4868,7 @@ function parseRepositoryRecipes(text2, envelope) {
4733
4868
  async function readRepositoryRecipes(baselineDir, envelope) {
4734
4869
  let text2;
4735
4870
  try {
4736
- text2 = await (0, import_promises16.readFile)((0, import_node_path14.join)(baselineDir, REPOSITORY_RECIPES_FILE), "utf8");
4871
+ text2 = await (0, import_promises18.readFile)((0, import_node_path16.join)(baselineDir, REPOSITORY_RECIPES_FILE), "utf8");
4737
4872
  } catch (cause) {
4738
4873
  if (cause.code === "ENOENT") return null;
4739
4874
  throw cause;
@@ -5334,82 +5469,58 @@ function chooseStrategy(signals = {}) {
5334
5469
  }
5335
5470
  var feedbackIsActionable = actionable;
5336
5471
 
5337
- // src/code-recipe-dependencies.ts
5338
- var import_promises17 = require("fs/promises");
5339
- var import_node_path15 = require("path");
5340
- var RESERVED_MOUNTS = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage"]);
5341
- function withRecipeDependencies(executor, dependencies) {
5342
- const mountAs = dependencies.mountAs ?? "node_modules";
5343
- if (!(0, import_node_path15.isAbsolute)(dependencies.source)) {
5344
- throw new TypeError("recipe dependency source must be an absolute path");
5345
- }
5346
- if (!RESERVED_MOUNTS.has(mountAs)) {
5347
- throw new TypeError(`recipe dependencies must mount at a reserved name, not "${mountAs}"`);
5348
- }
5349
- return {
5350
- run: async (input) => {
5351
- const target = (0, import_node_path15.join)(input.workspaceDir, mountAs);
5352
- let linked = false;
5353
- try {
5354
- const existing = await (0, import_promises17.lstat)(target).catch(() => null);
5355
- if (!existing) {
5356
- await (0, import_promises17.symlink)(dependencies.source, target, "dir");
5357
- linked = true;
5358
- }
5359
- return await executor.run(input);
5360
- } finally {
5361
- if (linked) await (0, import_promises17.rm)(target, { force: true, recursive: false }).catch(() => void 0);
5362
- }
5363
- }
5364
- };
5365
- }
5366
- async function installedDependencies(repoRoot) {
5367
- const source = (0, import_node_path15.join)(repoRoot, "node_modules");
5368
- const info = await (0, import_promises17.lstat)(source).catch(() => null);
5369
- return info?.isDirectory() ? { source } : null;
5370
- }
5371
-
5372
5472
  // src/code-recipe-dependency-cache.ts
5373
5473
  var import_node_crypto5 = require("crypto");
5374
- var import_promises18 = require("fs/promises");
5474
+ var import_promises19 = require("fs/promises");
5375
5475
  var import_node_process3 = require("process");
5376
- var import_node_path16 = require("path");
5476
+ var import_node_path17 = require("path");
5377
5477
  var DEPENDENCY_INSTALL_TIMEOUT_MS = 20 * 6e4;
5378
5478
  var OUTPUT_CAP = 4 * 1024 * 1024;
5379
- var exists = async (path) => Boolean(await (0, import_promises18.stat)(path).catch(() => null));
5380
- async function dependencyCacheKey(repoRoot, image, arch = process.arch) {
5381
- const lock = await (0, import_promises18.readFile)((0, import_node_path16.join)(repoRoot, "package-lock.json")).catch(() => null);
5479
+ var EXACT_VERSION = /^\d+\.\d+\.\d+$/;
5480
+ var exists = async (path) => Boolean(await (0, import_promises19.stat)(path).catch(() => null));
5481
+ async function pinnedNpmVersion(repoRoot) {
5482
+ const manifest = await (0, import_promises19.readFile)((0, import_node_path17.join)(repoRoot, "package.json"), "utf8").catch(() => null);
5483
+ if (!manifest) return null;
5484
+ const engines = JSON.parse(manifest).engines;
5485
+ return typeof engines?.npm === "string" && EXACT_VERSION.test(engines.npm) ? engines.npm : null;
5486
+ }
5487
+ async function dependencyCacheKey(repoRoot, image, arch = process.arch, npmVersion = null) {
5488
+ const lock = await (0, import_promises19.readFile)((0, import_node_path17.join)(repoRoot, "package-lock.json")).catch(() => null);
5382
5489
  if (!lock) return null;
5383
- return (0, import_node_crypto5.createHash)("sha256").update(lock).update("\n").update(image).update("\n").update(arch).digest("hex").slice(0, 32);
5490
+ return (0, import_node_crypto5.createHash)("sha256").update(lock).update("\n").update(image).update("\n").update(arch).update("\n").update(npmVersion ?? "image").digest("hex").slice(0, 32);
5491
+ }
5492
+ async function workspaceDirs(repoRoot) {
5493
+ const root = JSON.parse(await (0, import_promises19.readFile)((0, import_node_path17.join)(repoRoot, "package.json"), "utf8"));
5494
+ const globs = Array.isArray(root.workspaces) ? root.workspaces : root.workspaces?.packages ?? [];
5495
+ const dirs = [];
5496
+ for (const glob of globs) {
5497
+ const found = glob.endsWith("/*") ? (await (0, import_promises19.readdir)((0, import_node_path17.join)(repoRoot, glob.slice(0, -2)), { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map((entry) => `${glob.slice(0, -2)}/${entry.name}`) : [glob];
5498
+ for (const dir of found) if (await exists((0, import_node_path17.join)(repoRoot, dir, "package.json"))) dirs.push(dir);
5499
+ }
5500
+ return dirs;
5384
5501
  }
5385
5502
  async function stageManifests(repoRoot, stageDir) {
5386
- await (0, import_promises18.mkdir)(stageDir, { recursive: true });
5503
+ await (0, import_promises19.mkdir)(stageDir, { recursive: true });
5387
5504
  const staged = [];
5388
5505
  for (const name of ["package.json", "package-lock.json", ".npmrc"]) {
5389
- if (!await exists((0, import_node_path16.join)(repoRoot, name))) continue;
5390
- await (0, import_promises18.copyFile)((0, import_node_path16.join)(repoRoot, name), (0, import_node_path16.join)(stageDir, name));
5506
+ if (!await exists((0, import_node_path17.join)(repoRoot, name))) continue;
5507
+ await (0, import_promises19.copyFile)((0, import_node_path17.join)(repoRoot, name), (0, import_node_path17.join)(stageDir, name));
5391
5508
  staged.push(name);
5392
5509
  }
5393
- const root = JSON.parse(await (0, import_promises18.readFile)((0, import_node_path16.join)(repoRoot, "package.json"), "utf8"));
5394
- const globs = Array.isArray(root.workspaces) ? root.workspaces : root.workspaces?.packages ?? [];
5395
- for (const glob of globs) {
5396
- const dirs = glob.endsWith("/*") ? (await (0, import_promises18.readdir)((0, import_node_path16.join)(repoRoot, glob.slice(0, -2)), { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map((entry) => `${glob.slice(0, -2)}/${entry.name}`) : [glob];
5397
- for (const dir of dirs) {
5398
- const manifest = (0, import_node_path16.join)(repoRoot, dir, "package.json");
5399
- if (!await exists(manifest)) continue;
5400
- await (0, import_promises18.mkdir)((0, import_node_path16.join)(stageDir, dir), { recursive: true });
5401
- await (0, import_promises18.copyFile)(manifest, (0, import_node_path16.join)(stageDir, dir, "package.json"));
5402
- staged.push(`${dir}/package.json`);
5403
- }
5510
+ for (const dir of await workspaceDirs(repoRoot)) {
5511
+ await (0, import_promises19.mkdir)((0, import_node_path17.join)(stageDir, dir), { recursive: true });
5512
+ await (0, import_promises19.copyFile)((0, import_node_path17.join)(repoRoot, dir, "package.json"), (0, import_node_path17.join)(stageDir, dir, "package.json"));
5513
+ staged.push(`${dir}/package.json`);
5404
5514
  }
5405
5515
  return staged;
5406
5516
  }
5407
- function dependencyInstallArgs(engine, image, stageDir, name) {
5517
+ function dependencyInstallArgs(engine, image, stageDir, name, npmVersion = null) {
5408
5518
  if (/[,\r\n]/.test(stageDir)) throw new TypeError("stage path contains unsupported mount characters");
5409
5519
  const uid = typeof import_node_process3.getuid === "function" ? (0, import_node_process3.getuid)() : 1e3;
5410
5520
  const gid = typeof import_node_process3.getgid === "function" ? (0, import_node_process3.getgid)() : 1e3;
5411
5521
  const mount = engine === "container" ? `--mount=type=bind,source=${stageDir},target=/workspace` : `--mount=type=bind,src=${stageDir},dst=/workspace`;
5412
- const npm = ["npm", "ci", "--no-audit", "--no-fund", "--ignore-scripts", "--engine-strict=false"];
5522
+ const install = ["ci", "--no-audit", "--no-fund", "--ignore-scripts", "--engine-strict=false"];
5523
+ const npm = npmVersion ? ["npx", "--yes", `npm@${npmVersion}`, ...install] : ["npm", ...install];
5413
5524
  return [
5414
5525
  "run",
5415
5526
  "--rm",
@@ -5428,34 +5539,79 @@ function dependencyInstallArgs(engine, image, stageDir, name) {
5428
5539
  ...npm
5429
5540
  ];
5430
5541
  }
5542
+ function npmFailureSummary(stderr) {
5543
+ const lines2 = stderr.split("\n").map((line) => line.trim()).filter((line) => line && line !== "npm error");
5544
+ const at = lines2.findIndex((line) => /^npm error code /.test(line));
5545
+ return (at >= 0 ? lines2.slice(at, at + 3) : lines2.slice(-3)).join(" | ").slice(0, 400);
5546
+ }
5547
+ async function nestedTrees(treeRoot, dirs) {
5548
+ const nested = [];
5549
+ for (const dir of dirs) {
5550
+ if (await exists((0, import_node_path17.join)(treeRoot, dir, "node_modules"))) nested.push({ source: (0, import_node_path17.join)(treeRoot, dir, "node_modules"), mountAs: `${dir}/node_modules` });
5551
+ }
5552
+ return nested;
5553
+ }
5431
5554
  async function prepareContainerDependencies(input) {
5432
- const key = await dependencyCacheKey(input.repoRoot, input.image);
5555
+ const npmVersion = await pinnedNpmVersion(input.repoRoot);
5556
+ const key = await dependencyCacheKey(input.repoRoot, input.image, process.arch, npmVersion);
5433
5557
  if (!key) return null;
5434
- const dir = (0, import_node_path16.join)(input.cacheRoot, key);
5435
- const tree = (0, import_node_path16.join)(dir, "node_modules");
5436
- const stage = (0, import_node_path16.join)(dir, "stage");
5437
- if (await exists(tree)) return { source: tree };
5558
+ const dir = (0, import_node_path17.join)(input.cacheRoot, key);
5559
+ const tree = (0, import_node_path17.join)(dir, "tree");
5560
+ const stage = (0, import_node_path17.join)(dir, "stage");
5561
+ const runsDir = (0, import_node_path17.join)(dir, "runs");
5562
+ await (0, import_promises19.rm)(runsDir, { recursive: true, force: true });
5563
+ const dirs = await workspaceDirs(input.repoRoot);
5564
+ const lend = async () => ({ source: (0, import_node_path17.join)(tree, "node_modules"), nested: await nestedTrees(tree, dirs), runsDir });
5565
+ if (await exists((0, import_node_path17.join)(tree, "node_modules"))) return lend();
5438
5566
  const run = input.run ?? runContainerCommand;
5439
- await (0, import_promises18.rm)(stage, { recursive: true, force: true });
5567
+ await (0, import_promises19.rm)(stage, { recursive: true, force: true });
5440
5568
  const staged = await stageManifests(input.repoRoot, stage);
5441
- input.log?.(`installing ${key.slice(0, 12)}: ${staged.length} manifests, npm ci in ${input.image}`);
5569
+ input.log?.(`installing ${key.slice(0, 12)}: ${staged.length} manifests, npm${npmVersion ? `@${npmVersion}` : ""} ci in ${input.image}`);
5442
5570
  const name = `odla-deps-${key.slice(0, 12)}`;
5443
5571
  const result = await run(
5444
5572
  input.engine,
5445
- dependencyInstallArgs(input.engine, input.image, stage, name),
5573
+ dependencyInstallArgs(input.engine, input.image, stage, name, npmVersion),
5446
5574
  name,
5447
5575
  { timeoutMs: DEPENDENCY_INSTALL_TIMEOUT_MS, maxOutputBytes: OUTPUT_CAP }
5448
5576
  );
5449
- if (result.exitCode !== 0 || result.timedOut || !await exists((0, import_node_path16.join)(stage, "node_modules"))) {
5450
- input.log?.(`install failed (exit ${result.exitCode}${result.timedOut ? ", timed out" : ""}): ${result.stderr.trim().split("\n").slice(-3).join(" | ").slice(0, 400)}`);
5451
- await (0, import_promises18.rm)(stage, { recursive: true, force: true });
5577
+ if (result.exitCode !== 0 || result.timedOut || !await exists((0, import_node_path17.join)(stage, "node_modules"))) {
5578
+ input.log?.(`install failed (exit ${result.exitCode}${result.timedOut ? ", timed out" : ""}): ${npmFailureSummary(result.stderr)}`);
5579
+ await (0, import_promises19.rm)(stage, { recursive: true, force: true });
5452
5580
  return null;
5453
5581
  }
5454
- await (0, import_promises18.rename)((0, import_node_path16.join)(stage, "node_modules"), tree);
5455
- await (0, import_promises18.rm)(stage, { recursive: true, force: true });
5456
- await (0, import_promises18.writeFile)((0, import_node_path16.join)(dir, "manifest.json"), JSON.stringify({ image: input.image, key, createdAt: Date.now() }, null, 2));
5457
- input.log?.(`installed ${key.slice(0, 12)} in ${Math.round(result.durationMs / 1e3)}s`);
5458
- return { source: tree };
5582
+ await (0, import_promises19.rm)(tree, { recursive: true, force: true });
5583
+ await (0, import_promises19.rename)(stage, tree);
5584
+ await (0, import_promises19.writeFile)((0, import_node_path17.join)(dir, "manifest.json"), JSON.stringify({ image: input.image, key, npmVersion, createdAt: Date.now() }, null, 2));
5585
+ const lent = await lend();
5586
+ input.log?.(`installed ${key.slice(0, 12)} in ${Math.round(result.durationMs / 1e3)}s, ${lent.nested?.length ?? 0} nested tree(s)`);
5587
+ return lent;
5588
+ }
5589
+
5590
+ // src/code-recipe-build-products.ts
5591
+ var import_node_child_process8 = require("child_process");
5592
+ var import_promises20 = require("fs/promises");
5593
+ var import_node_path18 = require("path");
5594
+ function gitIgnoredFiles(repoRoot) {
5595
+ return new Promise((accept) => {
5596
+ const child = (0, import_node_child_process8.spawn)("git", ["-C", repoRoot, "ls-files", "--others", "--ignored", "--exclude-standard", "-z"], { shell: false, stdio: ["ignore", "pipe", "ignore"] });
5597
+ const chunks = [];
5598
+ child.stdout.on("data", (chunk) => chunks.push(chunk));
5599
+ child.once("error", () => accept([]));
5600
+ child.once("exit", (code) => accept(code === 0 ? Buffer.concat(chunks).toString("utf8").split("\0").filter(Boolean) : []));
5601
+ });
5602
+ }
5603
+ async function hostBuildProducts(repoRoot) {
5604
+ const dirs = await workspaceDirs(repoRoot);
5605
+ const within = (path) => dirs.find((dir) => path.startsWith(`${dir}/`)) ?? ".";
5606
+ const products = [];
5607
+ for (const dir of dirs) {
5608
+ const source = (0, import_node_path18.join)(repoRoot, dir, "dist");
5609
+ if ((await (0, import_promises20.stat)(source).catch(() => null))?.isDirectory()) products.push({ source, mountAs: `${dir}/dist`, within: dir });
5610
+ }
5611
+ for (const path of await gitIgnoredFiles(repoRoot)) {
5612
+ if (allowedWorkspacePath(path)) products.push({ source: (0, import_node_path18.join)(repoRoot, path), mountAs: path, within: within(path) });
5613
+ }
5614
+ return products;
5459
5615
  }
5460
5616
  // Annotate the CommonJS export names for ESM import in node:
5461
5617
  0 && (module.exports = {
@@ -5479,11 +5635,14 @@ async function prepareContainerDependencies(input) {
5479
5635
  applyPatchDialectToDiff,
5480
5636
  assertCodeBuildRecipe,
5481
5637
  assertDisjointPlan,
5638
+ assertLentPath,
5482
5639
  assertPinnedImage,
5640
+ assertReservedMount,
5483
5641
  attachCodeRuntimeReferences,
5484
5642
  buildContainerRunArgs,
5485
5643
  buildRecipeContainerArgs,
5486
5644
  chooseStrategy,
5645
+ cloneTree,
5487
5646
  codeSkill,
5488
5647
  createCodeRuntimeControlClient,
5489
5648
  createCodeRuntimeInference,
@@ -5500,17 +5659,23 @@ async function prepareContainerDependencies(input) {
5500
5659
  feedbackIsActionable,
5501
5660
  hasContextFreeHunk,
5502
5661
  hazardFromAttempt,
5662
+ hostBuildProducts,
5503
5663
  installedDependencies,
5504
5664
  integrateSubGoals,
5505
5665
  isCheckpointEffectCompleted,
5666
+ lendBuildProducts,
5667
+ lendDependencies,
5668
+ lentDirectories,
5506
5669
  materializeCodeRuntimeArchive,
5507
5670
  materializeCodeRuntimeSource,
5508
5671
  materializeCommandWorkspace,
5509
5672
  materializeGitTree,
5673
+ npmFailureSummary,
5510
5674
  outcomeCloses,
5511
5675
  outcomeMemory,
5512
5676
  parseRepositoryRecipes,
5513
5677
  patchPaths,
5678
+ pinnedNpmVersion,
5514
5679
  planReachCollisions,
5515
5680
  prepareContainerDependencies,
5516
5681
  prepareRuntimeCheckpoint,
@@ -5518,6 +5683,7 @@ async function prepareContainerDependencies(input) {
5518
5683
  readOnlyNotice,
5519
5684
  readRepositoryRecipes,
5520
5685
  recallAbout,
5686
+ recipeOutput,
5521
5687
  registeredFiles,
5522
5688
  renderMemories,
5523
5689
  renderOutcome,
@@ -5549,6 +5715,7 @@ async function prepareContainerDependencies(input) {
5549
5715
  verifyCodeCandidate,
5550
5716
  verifyContainerEngineBoundary,
5551
5717
  withProofRecipes,
5552
- withRecipeDependencies
5718
+ withRecipeDependencies,
5719
+ workspaceDirs
5553
5720
  });
5554
5721
  //# sourceMappingURL=node.cjs.map