@odla-ai/harness 0.11.11 → 0.11.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,
@@ -110,7 +119,8 @@ __export(node_exports, {
110
119
  verifyCodeCandidate: () => verifyCodeCandidate,
111
120
  verifyContainerEngineBoundary: () => verifyContainerEngineBoundary,
112
121
  withProofRecipes: () => withProofRecipes,
113
- withRecipeDependencies: () => withRecipeDependencies
122
+ withRecipeDependencies: () => withRecipeDependencies,
123
+ workspaceDirs: () => workspaceDirs
114
124
  });
115
125
  module.exports = __toCommonJS(node_exports);
116
126
 
@@ -250,7 +260,7 @@ async function selectContainerEngine(requested = "auto", options = {}) {
250
260
  throw new TypeError("no supported container engine found");
251
261
  }
252
262
  function inspectRootlessPodman() {
253
- return new Promise((resolve8, reject) => {
263
+ return new Promise((resolve9, reject) => {
254
264
  (0, import_node_child_process.execFile)(
255
265
  "podman",
256
266
  ["info", "--format", "{{.Host.Security.Rootless}}"],
@@ -260,7 +270,7 @@ function inspectRootlessPodman() {
260
270
  reject(new TypeError("could not verify that the active Podman service is rootless"));
261
271
  return;
262
272
  }
263
- resolve8(stdout.trim() === "true");
273
+ resolve9(stdout.trim() === "true");
264
274
  }
265
275
  );
266
276
  });
@@ -1361,7 +1371,7 @@ async function withOverloadRetry(call, wait2, onRetry = () => void 0, retryable
1361
1371
  }
1362
1372
 
1363
1373
  // src/code-runtime-reconciler.ts
1364
- var sleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
1374
+ var sleep = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
1365
1375
  var POST_ACK_RETRY_TICKS = 40;
1366
1376
  var CodeRuntimeReconciler = class {
1367
1377
  constructor(control, engine, onDiagnostic, options = {}) {
@@ -1463,12 +1473,12 @@ function retryableControlFailure(value) {
1463
1473
  return failure.status === 408 || failure.status === 425 || failure.status === 429 || failure.status >= 500;
1464
1474
  }
1465
1475
  function wait(ms, signal) {
1466
- return new Promise((resolve8) => {
1467
- if (signal?.aborted) return resolve8();
1468
- const timer = setTimeout(resolve8, ms);
1476
+ return new Promise((resolve9) => {
1477
+ if (signal?.aborted) return resolve9();
1478
+ const timer = setTimeout(resolve9, ms);
1469
1479
  signal?.addEventListener("abort", () => {
1470
1480
  clearTimeout(timer);
1471
- resolve8();
1481
+ resolve9();
1472
1482
  }, { once: true });
1473
1483
  });
1474
1484
  }
@@ -1700,19 +1710,127 @@ function isCheckpointEffectCompleted(checkpoint, effectId, actionDigest) {
1700
1710
  // src/code-verifier.ts
1701
1711
  var import_node_crypto3 = require("crypto");
1702
1712
  var import_node_fs2 = require("fs");
1703
- var import_promises7 = require("fs/promises");
1704
- var import_node_path7 = require("path");
1713
+ var import_promises9 = require("fs/promises");
1714
+ var import_node_path9 = require("path");
1705
1715
  var import_code3 = require("@odla-ai/camel/code");
1706
1716
 
1707
1717
  // src/recipe-container.ts
1708
- var import_node_child_process5 = require("child_process");
1718
+ var import_node_child_process6 = require("child_process");
1709
1719
  var import_node_process2 = require("process");
1710
1720
  var import_node_crypto2 = require("crypto");
1721
+
1722
+ // src/code-recipe-dependencies.ts
1723
+ var import_promises7 = require("fs/promises");
1724
+ var import_node_path7 = require("path");
1725
+ var RESERVED_MOUNTS = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage"]);
1726
+ function assertLentPath(mountAs) {
1727
+ const parts = mountAs.split("/");
1728
+ if ((0, import_node_path7.isAbsolute)(mountAs) || mountAs.includes("\\") || parts.some((part) => !part || part === "." || part === "..")) {
1729
+ throw new TypeError(`recipe dependencies must mount inside the workspace, not at "${mountAs}"`);
1730
+ }
1731
+ }
1732
+ function assertReservedMount(mountAs) {
1733
+ assertLentPath(mountAs);
1734
+ if (!RESERVED_MOUNTS.has(mountAs.split("/").at(-1))) {
1735
+ throw new TypeError(`recipe dependencies must mount at a reserved name, not "${mountAs}"`);
1736
+ }
1737
+ }
1738
+ function lentDirectories(dependencies) {
1739
+ return [{ source: dependencies.source, mountAs: dependencies.mountAs ?? "node_modules" }, ...dependencies.nested ?? []];
1740
+ }
1741
+ function withRecipeDependencies(executor, dependencies) {
1742
+ const lent = lentDirectories(dependencies);
1743
+ for (const entry of lent) {
1744
+ if (!(0, import_node_path7.isAbsolute)(entry.source)) {
1745
+ throw new TypeError("recipe dependency source must be an absolute path");
1746
+ }
1747
+ assertReservedMount(entry.mountAs);
1748
+ }
1749
+ return {
1750
+ run: async (input) => {
1751
+ const linked = [];
1752
+ try {
1753
+ for (const entry of lent) {
1754
+ const target = (0, import_node_path7.join)(input.workspaceDir, entry.mountAs);
1755
+ if (await (0, import_promises7.lstat)(target).catch(() => null)) continue;
1756
+ await (0, import_promises7.symlink)(entry.source, target, "dir").then(() => linked.push(target), () => void 0);
1757
+ }
1758
+ return await executor.run(input);
1759
+ } finally {
1760
+ for (const target of linked) await (0, import_promises7.rm)(target, { force: true, recursive: false }).catch(() => void 0);
1761
+ }
1762
+ }
1763
+ };
1764
+ }
1765
+ async function installedDependencies(repoRoot) {
1766
+ const source = (0, import_node_path7.join)(repoRoot, "node_modules");
1767
+ const info = await (0, import_promises7.lstat)(source).catch(() => null);
1768
+ return info?.isDirectory() ? { source } : null;
1769
+ }
1770
+
1771
+ // src/recipe-container-lending.ts
1772
+ var import_node_child_process5 = require("child_process");
1773
+ var import_promises8 = require("fs/promises");
1774
+ var import_node_path8 = require("path");
1775
+ var RUN_ID = /^[A-Za-z0-9_.-]{1,64}$/;
1776
+ function copy(args) {
1777
+ return new Promise((accept, reject) => {
1778
+ const child = (0, import_node_child_process5.spawn)("cp", args, { shell: false, stdio: "ignore" });
1779
+ child.once("error", reject);
1780
+ child.once("exit", (code) => accept(code ?? 1));
1781
+ });
1782
+ }
1783
+ async function cloneTree(source, target, platform = process.platform) {
1784
+ await (0, import_promises8.mkdir)((0, import_node_path8.dirname)(target), { recursive: true });
1785
+ const fast = platform === "darwin" ? ["-Rc", source, target] : platform === "linux" ? ["-R", "--reflink=auto", source, target] : null;
1786
+ if (fast && await copy(fast) === 0) return;
1787
+ await (0, import_promises8.rm)(target, { recursive: true, force: true });
1788
+ if (await copy(["-R", source, target]) !== 0) throw new Error(`could not clone ${source}`);
1789
+ }
1790
+ async function lendDependencies(dependencies, runId) {
1791
+ if (!dependencies?.runsDir) return { dependencies, release: async () => void 0 };
1792
+ if (!RUN_ID.test(runId)) throw new TypeError("run id is not a safe directory name");
1793
+ const treeRoot = (0, import_node_path8.dirname)(dependencies.source);
1794
+ for (const entry of lentDirectories(dependencies)) {
1795
+ if (entry.source !== (0, import_node_path8.join)(treeRoot, entry.mountAs)) {
1796
+ throw new TypeError("every directory of a cloned set must lie under its tree root at its mount path");
1797
+ }
1798
+ }
1799
+ const runDir = (0, import_node_path8.join)(dependencies.runsDir, runId);
1800
+ await cloneTree(treeRoot, runDir);
1801
+ const rebase = (entry) => ({ source: (0, import_node_path8.join)(runDir, entry.mountAs), mountAs: entry.mountAs });
1802
+ return {
1803
+ dependencies: {
1804
+ ...dependencies,
1805
+ source: (0, import_node_path8.join)(runDir, dependencies.mountAs ?? "node_modules"),
1806
+ nested: (dependencies.nested ?? []).map(rebase),
1807
+ writable: true
1808
+ },
1809
+ release: () => (0, import_promises8.rm)(runDir, { recursive: true, force: true })
1810
+ };
1811
+ }
1812
+ async function lendBuildProducts(products, workspaceDir) {
1813
+ const root = (0, import_node_path8.resolve)(workspaceDir);
1814
+ const lent = [];
1815
+ for (const product of products) {
1816
+ assertLentPath(product.mountAs);
1817
+ if (SECRET_WORKSPACE_FILE.test(product.mountAs.split("/").at(-1))) throw new TypeError("a secret is never a build product");
1818
+ const target = (0, import_node_path8.resolve)(root, product.mountAs);
1819
+ if (!target.startsWith(`${root}${import_node_path8.sep}`)) throw new TypeError("build product escapes the workspace");
1820
+ if (await (0, import_promises8.stat)(target).catch(() => null)) continue;
1821
+ if (!await (0, import_promises8.stat)((0, import_node_path8.join)(root, product.within ?? (0, import_node_path8.dirname)(product.mountAs))).catch(() => null)) continue;
1822
+ await cloneTree(product.source, target);
1823
+ lent.push(product.mountAs);
1824
+ }
1825
+ return lent;
1826
+ }
1827
+
1828
+ // src/recipe-container.ts
1711
1829
  var ARTIFACT_PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
1712
1830
  var PRIVATE_ARTIFACT_PART = /^(?:\.git|\.odla|\.wrangler|\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json)$/i;
1713
1831
  function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${(0, import_node_crypto2.randomUUID)().slice(0, 12)}`, dependencies = null) {
1714
1832
  assertCodeBuildRecipe(recipe2);
1715
- const mounts = dependencies ? [dependencyMount(engine, dependencies)] : [];
1833
+ const mounts = dependencies ? dependencyMounts(engine, dependencies) : [];
1716
1834
  if (/[,\r\n]/.test(workspaceDir)) throw new TypeError("workspace path contains unsupported mount characters");
1717
1835
  const uid = typeof import_node_process2.getuid === "function" ? (0, import_node_process2.getuid)() : 1e3;
1718
1836
  const gid = typeof import_node_process2.getgid === "function" ? (0, import_node_process2.getgid)() : 1e3;
@@ -1738,6 +1856,7 @@ function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-re
1738
1856
  ...mounts,
1739
1857
  "--workdir=/workspace",
1740
1858
  "--env=CI=1",
1859
+ "--env=HOME=/tmp",
1741
1860
  recipe2.image,
1742
1861
  ...recipe2.command
1743
1862
  ];
@@ -1760,26 +1879,34 @@ function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-re
1760
1879
  ...mounts,
1761
1880
  "--workdir=/workspace",
1762
1881
  "--env=CI=1",
1882
+ "--env=HOME=/tmp",
1763
1883
  recipe2.image,
1764
1884
  ...recipe2.command
1765
1885
  ];
1766
1886
  }
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`;
1887
+ function dependencyMounts(engine, dependencies) {
1888
+ const readonly = dependencies.writable ? "" : ",readonly";
1889
+ return lentDirectories(dependencies).map(({ source, mountAs }) => {
1890
+ if (!source.startsWith("/") || /[,\r\n]/.test(source)) {
1891
+ throw new TypeError("recipe dependency source must be an absolute path without mount separators");
1892
+ }
1893
+ assertReservedMount(mountAs);
1894
+ return engine === "container" ? `--mount=type=bind,source=${source},target=/workspace/${mountAs}${readonly}` : `--mount=type=bind,src=${source},dst=/workspace/${mountAs}${readonly}`;
1895
+ });
1775
1896
  }
1776
1897
  function createContainerRecipeExecutor(engine, options = {}) {
1777
1898
  return {
1778
1899
  async run(input) {
1779
1900
  await verifyContainerEngineBoundary(engine);
1780
1901
  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);
1902
+ const lent = await lendDependencies(options.dependencies ?? null, name);
1903
+ try {
1904
+ if (lent.dependencies?.products?.length) await lendBuildProducts(lent.dependencies.products, input.workspaceDir);
1905
+ const args = buildRecipeContainerArgs(engine, input.workspaceDir, input.recipe, name, lent.dependencies);
1906
+ return await execute(engine, args, name, input.recipe, input.signal);
1907
+ } finally {
1908
+ await lent.release();
1909
+ }
1783
1910
  }
1784
1911
  };
1785
1912
  }
@@ -1804,7 +1931,7 @@ function execute(engine, args, name, recipe2, signal) {
1804
1931
  function runContainerCommand(engine, args, name, recipe2, signal) {
1805
1932
  return new Promise((accept, reject) => {
1806
1933
  const started = Date.now();
1807
- const child = (0, import_node_child_process5.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
1934
+ const child = (0, import_node_child_process6.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
1808
1935
  const stdout = [];
1809
1936
  const stderr = [];
1810
1937
  let bytes = 0;
@@ -1817,7 +1944,7 @@ function runContainerCommand(engine, args, name, recipe2, signal) {
1817
1944
  timedOut = reason === "timeout";
1818
1945
  outputLimitExceeded = reason === "output";
1819
1946
  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" });
1947
+ const killer = (0, import_node_child_process6.spawn)(engine, remove, { shell: false, stdio: "ignore" });
1821
1948
  killer.unref();
1822
1949
  child.kill("SIGTERM");
1823
1950
  };
@@ -1961,8 +2088,8 @@ async function inspectArtifacts(workspaceDir, recipe2) {
1961
2088
  const receipts = [];
1962
2089
  for (const artifact of recipe2.expectedArtifacts ?? []) {
1963
2090
  try {
1964
- const path = (0, import_node_path7.join)(workspaceDir, artifact.path);
1965
- const info = await (0, import_promises7.lstat)(path);
2091
+ const path = (0, import_node_path9.join)(workspaceDir, artifact.path);
2092
+ const info = await (0, import_promises9.lstat)(path);
1966
2093
  if (!info.isFile() || info.isSymbolicLink()) {
1967
2094
  receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
1968
2095
  } else if (info.size > artifact.maximumBytes) {
@@ -2293,15 +2420,15 @@ async function prepareRuntimeLocalSource(input) {
2293
2420
  }
2294
2421
 
2295
2422
  // src/code-runtime-source.ts
2296
- var import_promises10 = require("fs/promises");
2423
+ var import_promises12 = require("fs/promises");
2297
2424
  var import_node_os4 = require("os");
2298
- var import_node_path10 = require("path");
2425
+ var import_node_path12 = require("path");
2299
2426
 
2300
2427
  // src/code-runtime-archive.ts
2301
2428
  var import_node_zlib = require("zlib");
2302
- var import_promises8 = require("fs/promises");
2429
+ var import_promises10 = require("fs/promises");
2303
2430
  var import_node_os3 = require("os");
2304
- var import_node_path8 = require("path");
2431
+ var import_node_path10 = require("path");
2305
2432
  var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
2306
2433
  var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
2307
2434
  var MAX_NUL_SHARE = 0.1;
@@ -2319,9 +2446,9 @@ async function materializeCodeRuntimeArchive(archive, visiblePaths, tempRoot = (
2319
2446
  throw new TypeError("Code source archive exceeds its decompressed byte bound");
2320
2447
  }
2321
2448
  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);
2449
+ const root = await (0, import_promises10.mkdtemp)((0, import_node_path10.join)(tempRoot, "odla-code-archive-"));
2450
+ const sourceDir = (0, import_node_path10.join)(root, "source");
2451
+ await (0, import_promises10.mkdir)(sourceDir);
2325
2452
  let visible = 0;
2326
2453
  try {
2327
2454
  for (const entry of entries) {
@@ -2334,16 +2461,16 @@ async function materializeCodeRuntimeArchive(archive, visiblePaths, tempRoot = (
2334
2461
  continue;
2335
2462
  }
2336
2463
  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 });
2464
+ const target = (0, import_node_path10.resolve)(sourceDir, entry.path);
2465
+ if (!target.startsWith(`${(0, import_node_path10.resolve)(sourceDir)}${import_node_path10.sep}`)) throw new TypeError("Code source path escapes its root");
2466
+ await (0, import_promises10.mkdir)((0, import_node_path10.dirname)(target), { recursive: true });
2467
+ await (0, import_promises10.writeFile)(target, content, { flag: "wx", mode: 420 });
2341
2468
  visible += 1;
2342
2469
  }
2343
2470
  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 }) };
2471
+ return { sourceDir, cleanup: () => (0, import_promises10.rm)(root, { recursive: true, force: true }) };
2345
2472
  } catch (cause) {
2346
- await (0, import_promises8.rm)(root, { recursive: true, force: true });
2473
+ await (0, import_promises10.rm)(root, { recursive: true, force: true });
2347
2474
  throw cause;
2348
2475
  }
2349
2476
  }
@@ -2538,8 +2665,8 @@ function nulShare(content) {
2538
2665
  }
2539
2666
 
2540
2667
  // src/code-runtime-selected-source.ts
2541
- var import_promises9 = require("fs/promises");
2542
- var import_node_path9 = require("path");
2668
+ var import_promises11 = require("fs/promises");
2669
+ var import_node_path11 = require("path");
2543
2670
  function selectedSourceSet(payload) {
2544
2671
  if (!payload.sourceSet) return null;
2545
2672
  const set = payload.sourceSet && typeof payload.sourceSet === "object" && !Array.isArray(payload.sourceSet) ? payload.sourceSet : null;
@@ -2576,9 +2703,9 @@ async function attachReferenceDirectories(workspace, references) {
2576
2703
  for (const reference of references) {
2577
2704
  validateAlias(reference.alias);
2578
2705
  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 });
2706
+ const target = (0, import_node_path11.join)(root, ".odla-references", reference.alias);
2707
+ await (0, import_promises11.mkdir)((0, import_node_path11.dirname)(target), { recursive: true });
2708
+ await (0, import_promises11.cp)(reference.sourceDir, target, { recursive: true, errorOnExist: true, force: false });
2582
2709
  await makeTreeReadOnly(target);
2583
2710
  }
2584
2711
  }
@@ -2589,10 +2716,10 @@ function validateAlias(alias) {
2589
2716
  }
2590
2717
  }
2591
2718
  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);
2719
+ for (const entry of await (0, import_promises11.readdir)(root, { withFileTypes: true })) {
2720
+ const target = (0, import_node_path11.join)(root, entry.name);
2594
2721
  if (entry.isDirectory()) await makeTreeReadOnly(target);
2595
- else if (entry.isFile()) await (0, import_promises9.chmod)(target, 292);
2722
+ else if (entry.isFile()) await (0, import_promises11.chmod)(target, 292);
2596
2723
  }
2597
2724
  }
2598
2725
 
@@ -2604,9 +2731,9 @@ var SOURCE_MAX_BYTES = 80 * 1024 * 1024;
2604
2731
  var SOURCE_SET_MAX_BYTES = 480 * 1024 * 1024;
2605
2732
  async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node_os4.tmpdir)()) {
2606
2733
  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);
2734
+ const root = await (0, import_promises12.mkdtemp)((0, import_node_path12.join)(tempRoot, "odla-code-source-"));
2735
+ const sourceDir = (0, import_node_path12.join)(root, "source");
2736
+ await (0, import_promises12.mkdir)(sourceDir);
2610
2737
  const seen = /* @__PURE__ */ new Set();
2611
2738
  let bytes = 0;
2612
2739
  try {
@@ -2616,10 +2743,10 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node
2616
2743
  seen.add(file.path);
2617
2744
  bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
2618
2745
  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 });
2746
+ const target = (0, import_node_path12.resolve)(sourceDir, file.path);
2747
+ if (!target.startsWith(`${(0, import_node_path12.resolve)(sourceDir)}${import_node_path12.sep}`)) throw new TypeError("Code source path escapes its root");
2748
+ await (0, import_promises12.mkdir)((0, import_node_path12.dirname)(target), { recursive: true });
2749
+ await (0, import_promises12.writeFile)(target, file.content, { flag: "wx", mode: 420 });
2623
2750
  }
2624
2751
  for (const reference of snapshot.references ?? []) {
2625
2752
  validateAlias2(reference.alias);
@@ -2631,15 +2758,15 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_node
2631
2758
  seen.add(path);
2632
2759
  bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
2633
2760
  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 });
2761
+ const target = (0, import_node_path12.resolve)(sourceDir, path);
2762
+ if (!target.startsWith(`${(0, import_node_path12.resolve)(sourceDir)}${import_node_path12.sep}`)) throw new TypeError("Code reference path escapes its root");
2763
+ await (0, import_promises12.mkdir)((0, import_node_path12.dirname)(target), { recursive: true });
2764
+ await (0, import_promises12.writeFile)(target, file.content, { flag: "wx", mode: 292 });
2638
2765
  }
2639
2766
  }
2640
- return { sourceDir, cleanup: () => (0, import_promises10.rm)(root, { recursive: true, force: true }) };
2767
+ return { sourceDir, cleanup: () => (0, import_promises12.rm)(root, { recursive: true, force: true }) };
2641
2768
  } catch (cause) {
2642
- await (0, import_promises10.rm)(root, { recursive: true, force: true });
2769
+ await (0, import_promises12.rm)(root, { recursive: true, force: true });
2643
2770
  throw cause;
2644
2771
  }
2645
2772
  }
@@ -2658,10 +2785,10 @@ async function attachCodeRuntimeReferences(workspace, references) {
2658
2785
  bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
2659
2786
  if (bytes > SOURCE_SET_MAX_BYTES - SOURCE_MAX_BYTES) throw new TypeError("Code reference set exceeds its byte bound");
2660
2787
  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 });
2788
+ const target = (0, import_node_path12.resolve)(root, path);
2789
+ if (!target.startsWith(`${(0, import_node_path12.resolve)(root)}${import_node_path12.sep}`)) throw new TypeError("Code reference path escapes its root");
2790
+ await (0, import_promises12.mkdir)((0, import_node_path12.dirname)(target), { recursive: true });
2791
+ await (0, import_promises12.writeFile)(target, file.content, { flag: "wx", mode: 292 });
2665
2792
  }
2666
2793
  }
2667
2794
  }
@@ -3107,7 +3234,7 @@ Finish with a concise, non-empty answer to the owner. Do not call tools or promi
3107
3234
  }
3108
3235
 
3109
3236
  // src/code-runtime-session-skills.ts
3110
- var sleep2 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
3237
+ var sleep2 = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
3111
3238
  function createCodeRuntimeSessionSkillLoader(control, options = {}) {
3112
3239
  const wait2 = options.wait ?? sleep2;
3113
3240
  const load = control.collaborationSkills?.bind(control);
@@ -3166,7 +3293,7 @@ var inferWithBackoff = (infer, wait2, onRetry) => withOverloadRetry(infer, wait2
3166
3293
  async function handleCodeRuntimeInference(input) {
3167
3294
  const { command, request, state } = input;
3168
3295
  const startedAt = Date.now();
3169
- const wait2 = input.wait ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
3296
+ const wait2 = input.wait ?? ((ms) => new Promise((resolve9) => setTimeout(resolve9, ms)));
3170
3297
  const response2 = await inferWithBackoff(
3171
3298
  () => input.control.infer(command.sessionId, {
3172
3299
  requestId: request.requestId,
@@ -3677,18 +3804,18 @@ function response(request, ok, content, details) {
3677
3804
  }
3678
3805
 
3679
3806
  // src/code-tool-edit.ts
3680
- var import_promises12 = require("fs/promises");
3807
+ var import_promises14 = require("fs/promises");
3681
3808
 
3682
3809
  // src/code-tool-graph.ts
3683
- var import_promises11 = require("fs/promises");
3684
- var import_node_path11 = require("path");
3810
+ var import_promises13 = require("fs/promises");
3811
+ var import_node_path13 = require("path");
3685
3812
  var import_graph = require("@odla-ai/graph");
3686
3813
  var import_code4 = require("@odla-ai/graph/code");
3687
3814
  var cache = /* @__PURE__ */ new Map();
3688
3815
  function workspaceGraphs(workspaceDir, paths2) {
3689
3816
  const existing = cache.get(workspaceDir);
3690
3817
  if (existing) return existing;
3691
- const read2 = (path) => (0, import_promises11.readFile)((0, import_node_path11.join)(workspaceDir, path), "utf8");
3818
+ const read2 = (path) => (0, import_promises13.readFile)((0, import_node_path13.join)(workspaceDir, path), "utf8");
3692
3819
  const built = (async () => ({
3693
3820
  // No knownTables: a staged workspace may not carry migrations, and a filter
3694
3821
  // that silently drops every table is worse than an unfiltered one. Callers
@@ -3764,7 +3891,7 @@ async function editCodeFile(workspaceDir, path, oldText, newText) {
3764
3891
  }
3765
3892
  if (oldText.includes("\0") || newText.includes("\0")) throw new TypeError("edit text contains NUL bytes; use plain text");
3766
3893
  const target = resolveCodePath(workspaceDir, path);
3767
- const current = await (0, import_promises12.readFile)(target, "utf8");
3894
+ const current = await (0, import_promises14.readFile)(target, "utf8");
3768
3895
  const found = occurrences(current, oldText);
3769
3896
  if (found === 0) {
3770
3897
  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 +3900,7 @@ async function editCodeFile(workspaceDir, path, oldText, newText) {
3773
3900
  throw new TypeError(`oldText occurs ${found} times in "${path}"; include more of the surrounding lines so it matches exactly once`);
3774
3901
  }
3775
3902
  const at = current.indexOf(oldText);
3776
- await (0, import_promises12.writeFile)(target, `${current.slice(0, at)}${newText}${current.slice(at + oldText.length)}`, "utf8");
3903
+ await (0, import_promises14.writeFile)(target, `${current.slice(0, at)}${newText}${current.slice(at + oldText.length)}`, "utf8");
3777
3904
  return { deletions: oldText.split("\n").length, additions: newText.split("\n").length };
3778
3905
  }
3779
3906
  async function edit(context, request, options, policy, registry) {
@@ -3798,8 +3925,8 @@ async function edit(context, request, options, policy, registry) {
3798
3925
  }
3799
3926
 
3800
3927
  // src/code-tool-write.ts
3801
- var import_promises13 = require("fs/promises");
3802
- var import_node_path12 = require("path");
3928
+ var import_promises15 = require("fs/promises");
3929
+ var import_node_path14 = require("path");
3803
3930
  var MAX_WRITE_BYTES = 256 * 1024;
3804
3931
  var lines = (text2) => text2 === "" ? [] : text2.replace(/\n$/, "").split("\n");
3805
3932
  function writeAsDiff(path, current, content) {
@@ -3824,8 +3951,8 @@ async function writeCodeFile(workspaceDir, path, content, current) {
3824
3951
  }
3825
3952
  if (content.includes("\0")) throw new TypeError("content contains NUL bytes; use plain text");
3826
3953
  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");
3954
+ await (0, import_promises15.mkdir)((0, import_node_path14.dirname)(target), { recursive: true });
3955
+ await (0, import_promises15.writeFile)(target, content, "utf8");
3829
3956
  return { created: current === null, deletions: current === null ? 0 : lines(current).length, additions: lines(content).length };
3830
3957
  }
3831
3958
  async function write(context, request, options, policy, registry) {
@@ -3836,7 +3963,7 @@ async function write(context, request, options, policy, registry) {
3836
3963
  throw new TypeError("write targets a read-only reference source");
3837
3964
  }
3838
3965
  const exists2 = (await registry.files(context.workspaceDir)).includes(path);
3839
- const current = exists2 ? await (0, import_promises13.readFile)(resolveCodePath(context.workspaceDir, path), "utf8") : null;
3966
+ const current = exists2 ? await (0, import_promises15.readFile)(resolveCodePath(context.workspaceDir, path), "utf8") : null;
3840
3967
  const patch2 = writeAsDiff(path, current, content);
3841
3968
  const allowed = await policy.write(policyContext(context, request, options, { patch: patch2 }));
3842
3969
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
@@ -3852,12 +3979,12 @@ async function write(context, request, options, policy, registry) {
3852
3979
  }
3853
3980
 
3854
3981
  // src/code-tool-reads.ts
3855
- var import_promises15 = require("fs/promises");
3982
+ var import_promises17 = require("fs/promises");
3856
3983
 
3857
3984
  // 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");
3985
+ var import_node_child_process7 = require("child_process");
3986
+ var import_promises16 = require("fs/promises");
3987
+ var import_node_path15 = require("path");
3861
3988
  var DEFAULT_MAX_FILES = 2e4;
3862
3989
  var DEFAULT_MAX_RESULTS = 100;
3863
3990
  var DEFAULT_MAX_FILE_BYTES = 512 * 1024;
@@ -3882,13 +4009,13 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
3882
4009
  async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
3883
4010
  const paths2 = [];
3884
4011
  const walk = async (directory) => {
3885
- for (const entry of await (0, import_promises14.readdir)(directory, { withFileTypes: true })) {
4012
+ for (const entry of await (0, import_promises16.readdir)(directory, { withFileTypes: true })) {
3886
4013
  if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
3887
4014
  if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
3888
- const target = (0, import_node_path13.resolve)(directory, entry.name);
4015
+ const target = (0, import_node_path15.resolve)(directory, entry.name);
3889
4016
  if (entry.isDirectory()) await walk(target);
3890
4017
  else if (entry.isFile()) {
3891
- const path = (0, import_node_path13.relative)(root, target).split("\\").join("/");
4018
+ const path = (0, import_node_path15.relative)(root, target).split("\\").join("/");
3892
4019
  try {
3893
4020
  validateRelativePath(path);
3894
4021
  } catch {
@@ -3899,7 +4026,7 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
3899
4026
  }
3900
4027
  }
3901
4028
  };
3902
- await walk((0, import_node_path13.resolve)(root));
4029
+ await walk((0, import_node_path15.resolve)(root));
3903
4030
  return paths2.sort();
3904
4031
  }
3905
4032
  function listWorkspace(paths2, options = {}) {
@@ -3961,7 +4088,7 @@ function nativeSearchBatch(root, paths2, options, remaining) {
3961
4088
  options.query,
3962
4089
  ...paths2
3963
4090
  ];
3964
- const child = (0, import_node_child_process6.spawn)("rg", args, {
4091
+ const child = (0, import_node_child_process7.spawn)("rg", args, {
3965
4092
  cwd: root,
3966
4093
  stdio: ["ignore", "pipe", "ignore"],
3967
4094
  ...options.signal ? { signal: options.signal } : {}
@@ -4013,7 +4140,7 @@ async function fallbackSearch(root, scoped, options) {
4013
4140
  if (matches.length >= options.maxResults) break;
4014
4141
  let source;
4015
4142
  try {
4016
- source = await (0, import_promises14.readFile)((0, import_node_path13.resolve)(root, path));
4143
+ source = await (0, import_promises16.readFile)((0, import_node_path15.resolve)(root, path));
4017
4144
  } catch {
4018
4145
  continue;
4019
4146
  }
@@ -4052,11 +4179,11 @@ async function read(context, request, options, policy, registry) {
4052
4179
  const allowed = await policy.read(policyContext(context, request, options, { paths: paths2, path, startLine, endLine }));
4053
4180
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
4054
4181
  const target = resolveCodePath(context.workspaceDir, path);
4055
- const info = await (0, import_promises15.stat)(target);
4182
+ const info = await (0, import_promises17.stat)(target);
4056
4183
  if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
4057
4184
  throw new TypeError("file is not a bounded regular source file");
4058
4185
  }
4059
- const source = await (0, import_promises15.readFile)(target);
4186
+ const source = await (0, import_promises17.readFile)(target);
4060
4187
  if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
4061
4188
  const lines2 = source.toString("utf8").split("\n");
4062
4189
  const content = lines2.slice(startLine - 1, endLine).join("\n");
@@ -4604,8 +4731,8 @@ var runtimeErrorMessage = (value) => value instanceof Error ? value.message : St
4604
4731
  function codeRuntimeAcknowledgementGate(signal) {
4605
4732
  let settle;
4606
4733
  let settled = false;
4607
- const ready = new Promise((resolve8) => {
4608
- settle = resolve8;
4734
+ const ready = new Promise((resolve9) => {
4735
+ settle = resolve9;
4609
4736
  });
4610
4737
  const release = (run) => {
4611
4738
  if (settled) return;
@@ -4671,8 +4798,8 @@ function observeCodeRuntimeSessionSkills(command, skills, emit) {
4671
4798
  }
4672
4799
 
4673
4800
  // src/code-repository-recipes.ts
4674
- var import_promises16 = require("fs/promises");
4675
- var import_node_path14 = require("path");
4801
+ var import_promises18 = require("fs/promises");
4802
+ var import_node_path16 = require("path");
4676
4803
  var REPOSITORY_RECIPES_FILE = "odla.recipes.json";
4677
4804
  var MAX_RECIPES = 16;
4678
4805
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -4733,7 +4860,7 @@ function parseRepositoryRecipes(text2, envelope) {
4733
4860
  async function readRepositoryRecipes(baselineDir, envelope) {
4734
4861
  let text2;
4735
4862
  try {
4736
- text2 = await (0, import_promises16.readFile)((0, import_node_path14.join)(baselineDir, REPOSITORY_RECIPES_FILE), "utf8");
4863
+ text2 = await (0, import_promises18.readFile)((0, import_node_path16.join)(baselineDir, REPOSITORY_RECIPES_FILE), "utf8");
4737
4864
  } catch (cause) {
4738
4865
  if (cause.code === "ENOENT") return null;
4739
4866
  throw cause;
@@ -5334,82 +5461,58 @@ function chooseStrategy(signals = {}) {
5334
5461
  }
5335
5462
  var feedbackIsActionable = actionable;
5336
5463
 
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
5464
  // src/code-recipe-dependency-cache.ts
5373
5465
  var import_node_crypto5 = require("crypto");
5374
- var import_promises18 = require("fs/promises");
5466
+ var import_promises19 = require("fs/promises");
5375
5467
  var import_node_process3 = require("process");
5376
- var import_node_path16 = require("path");
5468
+ var import_node_path17 = require("path");
5377
5469
  var DEPENDENCY_INSTALL_TIMEOUT_MS = 20 * 6e4;
5378
5470
  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);
5471
+ var EXACT_VERSION = /^\d+\.\d+\.\d+$/;
5472
+ var exists = async (path) => Boolean(await (0, import_promises19.stat)(path).catch(() => null));
5473
+ async function pinnedNpmVersion(repoRoot) {
5474
+ const manifest = await (0, import_promises19.readFile)((0, import_node_path17.join)(repoRoot, "package.json"), "utf8").catch(() => null);
5475
+ if (!manifest) return null;
5476
+ const engines = JSON.parse(manifest).engines;
5477
+ return typeof engines?.npm === "string" && EXACT_VERSION.test(engines.npm) ? engines.npm : null;
5478
+ }
5479
+ async function dependencyCacheKey(repoRoot, image, arch = process.arch, npmVersion = null) {
5480
+ const lock = await (0, import_promises19.readFile)((0, import_node_path17.join)(repoRoot, "package-lock.json")).catch(() => null);
5382
5481
  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);
5482
+ 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);
5483
+ }
5484
+ async function workspaceDirs(repoRoot) {
5485
+ const root = JSON.parse(await (0, import_promises19.readFile)((0, import_node_path17.join)(repoRoot, "package.json"), "utf8"));
5486
+ const globs = Array.isArray(root.workspaces) ? root.workspaces : root.workspaces?.packages ?? [];
5487
+ const dirs = [];
5488
+ for (const glob of globs) {
5489
+ 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];
5490
+ for (const dir of found) if (await exists((0, import_node_path17.join)(repoRoot, dir, "package.json"))) dirs.push(dir);
5491
+ }
5492
+ return dirs;
5384
5493
  }
5385
5494
  async function stageManifests(repoRoot, stageDir) {
5386
- await (0, import_promises18.mkdir)(stageDir, { recursive: true });
5495
+ await (0, import_promises19.mkdir)(stageDir, { recursive: true });
5387
5496
  const staged = [];
5388
5497
  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));
5498
+ if (!await exists((0, import_node_path17.join)(repoRoot, name))) continue;
5499
+ await (0, import_promises19.copyFile)((0, import_node_path17.join)(repoRoot, name), (0, import_node_path17.join)(stageDir, name));
5391
5500
  staged.push(name);
5392
5501
  }
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
- }
5502
+ for (const dir of await workspaceDirs(repoRoot)) {
5503
+ await (0, import_promises19.mkdir)((0, import_node_path17.join)(stageDir, dir), { recursive: true });
5504
+ await (0, import_promises19.copyFile)((0, import_node_path17.join)(repoRoot, dir, "package.json"), (0, import_node_path17.join)(stageDir, dir, "package.json"));
5505
+ staged.push(`${dir}/package.json`);
5404
5506
  }
5405
5507
  return staged;
5406
5508
  }
5407
- function dependencyInstallArgs(engine, image, stageDir, name) {
5509
+ function dependencyInstallArgs(engine, image, stageDir, name, npmVersion = null) {
5408
5510
  if (/[,\r\n]/.test(stageDir)) throw new TypeError("stage path contains unsupported mount characters");
5409
5511
  const uid = typeof import_node_process3.getuid === "function" ? (0, import_node_process3.getuid)() : 1e3;
5410
5512
  const gid = typeof import_node_process3.getgid === "function" ? (0, import_node_process3.getgid)() : 1e3;
5411
5513
  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"];
5514
+ const install = ["ci", "--no-audit", "--no-fund", "--ignore-scripts", "--engine-strict=false"];
5515
+ const npm = npmVersion ? ["npx", "--yes", `npm@${npmVersion}`, ...install] : ["npm", ...install];
5413
5516
  return [
5414
5517
  "run",
5415
5518
  "--rm",
@@ -5428,34 +5531,79 @@ function dependencyInstallArgs(engine, image, stageDir, name) {
5428
5531
  ...npm
5429
5532
  ];
5430
5533
  }
5534
+ function npmFailureSummary(stderr) {
5535
+ const lines2 = stderr.split("\n").map((line) => line.trim()).filter((line) => line && line !== "npm error");
5536
+ const at = lines2.findIndex((line) => /^npm error code /.test(line));
5537
+ return (at >= 0 ? lines2.slice(at, at + 3) : lines2.slice(-3)).join(" | ").slice(0, 400);
5538
+ }
5539
+ async function nestedTrees(treeRoot, dirs) {
5540
+ const nested = [];
5541
+ for (const dir of dirs) {
5542
+ 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` });
5543
+ }
5544
+ return nested;
5545
+ }
5431
5546
  async function prepareContainerDependencies(input) {
5432
- const key = await dependencyCacheKey(input.repoRoot, input.image);
5547
+ const npmVersion = await pinnedNpmVersion(input.repoRoot);
5548
+ const key = await dependencyCacheKey(input.repoRoot, input.image, process.arch, npmVersion);
5433
5549
  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 };
5550
+ const dir = (0, import_node_path17.join)(input.cacheRoot, key);
5551
+ const tree = (0, import_node_path17.join)(dir, "tree");
5552
+ const stage = (0, import_node_path17.join)(dir, "stage");
5553
+ const runsDir = (0, import_node_path17.join)(dir, "runs");
5554
+ await (0, import_promises19.rm)(runsDir, { recursive: true, force: true });
5555
+ const dirs = await workspaceDirs(input.repoRoot);
5556
+ const lend = async () => ({ source: (0, import_node_path17.join)(tree, "node_modules"), nested: await nestedTrees(tree, dirs), runsDir });
5557
+ if (await exists((0, import_node_path17.join)(tree, "node_modules"))) return lend();
5438
5558
  const run = input.run ?? runContainerCommand;
5439
- await (0, import_promises18.rm)(stage, { recursive: true, force: true });
5559
+ await (0, import_promises19.rm)(stage, { recursive: true, force: true });
5440
5560
  const staged = await stageManifests(input.repoRoot, stage);
5441
- input.log?.(`installing ${key.slice(0, 12)}: ${staged.length} manifests, npm ci in ${input.image}`);
5561
+ input.log?.(`installing ${key.slice(0, 12)}: ${staged.length} manifests, npm${npmVersion ? `@${npmVersion}` : ""} ci in ${input.image}`);
5442
5562
  const name = `odla-deps-${key.slice(0, 12)}`;
5443
5563
  const result = await run(
5444
5564
  input.engine,
5445
- dependencyInstallArgs(input.engine, input.image, stage, name),
5565
+ dependencyInstallArgs(input.engine, input.image, stage, name, npmVersion),
5446
5566
  name,
5447
5567
  { timeoutMs: DEPENDENCY_INSTALL_TIMEOUT_MS, maxOutputBytes: OUTPUT_CAP }
5448
5568
  );
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 });
5569
+ if (result.exitCode !== 0 || result.timedOut || !await exists((0, import_node_path17.join)(stage, "node_modules"))) {
5570
+ input.log?.(`install failed (exit ${result.exitCode}${result.timedOut ? ", timed out" : ""}): ${npmFailureSummary(result.stderr)}`);
5571
+ await (0, import_promises19.rm)(stage, { recursive: true, force: true });
5452
5572
  return null;
5453
5573
  }
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 };
5574
+ await (0, import_promises19.rm)(tree, { recursive: true, force: true });
5575
+ await (0, import_promises19.rename)(stage, tree);
5576
+ 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));
5577
+ const lent = await lend();
5578
+ input.log?.(`installed ${key.slice(0, 12)} in ${Math.round(result.durationMs / 1e3)}s, ${lent.nested?.length ?? 0} nested tree(s)`);
5579
+ return lent;
5580
+ }
5581
+
5582
+ // src/code-recipe-build-products.ts
5583
+ var import_node_child_process8 = require("child_process");
5584
+ var import_promises20 = require("fs/promises");
5585
+ var import_node_path18 = require("path");
5586
+ function gitIgnoredFiles(repoRoot) {
5587
+ return new Promise((accept) => {
5588
+ const child = (0, import_node_child_process8.spawn)("git", ["-C", repoRoot, "ls-files", "--others", "--ignored", "--exclude-standard", "-z"], { shell: false, stdio: ["ignore", "pipe", "ignore"] });
5589
+ const chunks = [];
5590
+ child.stdout.on("data", (chunk) => chunks.push(chunk));
5591
+ child.once("error", () => accept([]));
5592
+ child.once("exit", (code) => accept(code === 0 ? Buffer.concat(chunks).toString("utf8").split("\0").filter(Boolean) : []));
5593
+ });
5594
+ }
5595
+ async function hostBuildProducts(repoRoot) {
5596
+ const dirs = await workspaceDirs(repoRoot);
5597
+ const within = (path) => dirs.find((dir) => path.startsWith(`${dir}/`)) ?? ".";
5598
+ const products = [];
5599
+ for (const dir of dirs) {
5600
+ const source = (0, import_node_path18.join)(repoRoot, dir, "dist");
5601
+ if ((await (0, import_promises20.stat)(source).catch(() => null))?.isDirectory()) products.push({ source, mountAs: `${dir}/dist`, within: dir });
5602
+ }
5603
+ for (const path of await gitIgnoredFiles(repoRoot)) {
5604
+ if (allowedWorkspacePath(path)) products.push({ source: (0, import_node_path18.join)(repoRoot, path), mountAs: path, within: within(path) });
5605
+ }
5606
+ return products;
5459
5607
  }
5460
5608
  // Annotate the CommonJS export names for ESM import in node:
5461
5609
  0 && (module.exports = {
@@ -5479,11 +5627,14 @@ async function prepareContainerDependencies(input) {
5479
5627
  applyPatchDialectToDiff,
5480
5628
  assertCodeBuildRecipe,
5481
5629
  assertDisjointPlan,
5630
+ assertLentPath,
5482
5631
  assertPinnedImage,
5632
+ assertReservedMount,
5483
5633
  attachCodeRuntimeReferences,
5484
5634
  buildContainerRunArgs,
5485
5635
  buildRecipeContainerArgs,
5486
5636
  chooseStrategy,
5637
+ cloneTree,
5487
5638
  codeSkill,
5488
5639
  createCodeRuntimeControlClient,
5489
5640
  createCodeRuntimeInference,
@@ -5500,17 +5651,23 @@ async function prepareContainerDependencies(input) {
5500
5651
  feedbackIsActionable,
5501
5652
  hasContextFreeHunk,
5502
5653
  hazardFromAttempt,
5654
+ hostBuildProducts,
5503
5655
  installedDependencies,
5504
5656
  integrateSubGoals,
5505
5657
  isCheckpointEffectCompleted,
5658
+ lendBuildProducts,
5659
+ lendDependencies,
5660
+ lentDirectories,
5506
5661
  materializeCodeRuntimeArchive,
5507
5662
  materializeCodeRuntimeSource,
5508
5663
  materializeCommandWorkspace,
5509
5664
  materializeGitTree,
5665
+ npmFailureSummary,
5510
5666
  outcomeCloses,
5511
5667
  outcomeMemory,
5512
5668
  parseRepositoryRecipes,
5513
5669
  patchPaths,
5670
+ pinnedNpmVersion,
5514
5671
  planReachCollisions,
5515
5672
  prepareContainerDependencies,
5516
5673
  prepareRuntimeCheckpoint,
@@ -5549,6 +5706,7 @@ async function prepareContainerDependencies(input) {
5549
5706
  verifyCodeCandidate,
5550
5707
  verifyContainerEngineBoundary,
5551
5708
  withProofRecipes,
5552
- withRecipeDependencies
5709
+ withRecipeDependencies,
5710
+ workspaceDirs
5553
5711
  });
5554
5712
  //# sourceMappingURL=node.cjs.map