@odla-ai/harness 0.9.1 → 0.9.3

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.
Files changed (38) hide show
  1. package/dist/{chunk-HDIR4MM5.js → chunk-5LRYJKUI.js} +3 -3
  2. package/dist/{chunk-4EPJJMFG.js → chunk-AEAISFY3.js} +157 -217
  3. package/dist/chunk-AEAISFY3.js.map +1 -0
  4. package/dist/chunk-FAN2R3GW.js +166 -0
  5. package/dist/chunk-FAN2R3GW.js.map +1 -0
  6. package/dist/{chunk-CIKYMC67.js → chunk-OZBJNTML.js} +4 -4
  7. package/dist/{chunk-I43KTCJ2.js → chunk-U324RQ4N.js} +1 -1
  8. package/dist/{chunk-I43KTCJ2.js.map → chunk-U324RQ4N.js.map} +1 -1
  9. package/dist/{chunk-VGNIDRKM.js → chunk-VDY5V7ZG.js} +2 -2
  10. package/dist/cli.cjs.map +1 -1
  11. package/dist/cli.js +4 -4
  12. package/dist/code-runtime-cli.cjs +258 -163
  13. package/dist/code-runtime-cli.cjs.map +1 -1
  14. package/dist/code-runtime-cli.js +6 -5
  15. package/dist/code-runtime-cli.js.map +1 -1
  16. package/dist/index.cjs +172 -6
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +24 -3
  19. package/dist/index.d.ts +24 -3
  20. package/dist/index.js +11 -3
  21. package/dist/index.js.map +1 -1
  22. package/dist/node.cjs +261 -162
  23. package/dist/node.cjs.map +1 -1
  24. package/dist/node.d.cts +39 -6
  25. package/dist/node.d.ts +39 -6
  26. package/dist/node.js +10 -5
  27. package/dist/node.js.map +1 -1
  28. package/dist/testing.cjs.map +1 -1
  29. package/dist/testing.d.cts +1 -1
  30. package/dist/testing.d.ts +1 -1
  31. package/dist/testing.js +1 -1
  32. package/dist/{types-BazqxWK8.d.cts → types-_8y8vBDI.d.cts} +5 -1
  33. package/dist/{types-BazqxWK8.d.ts → types-_8y8vBDI.d.ts} +5 -1
  34. package/package.json +1 -1
  35. package/dist/chunk-4EPJJMFG.js.map +0 -1
  36. /package/dist/{chunk-HDIR4MM5.js.map → chunk-5LRYJKUI.js.map} +0 -0
  37. /package/dist/{chunk-CIKYMC67.js.map → chunk-OZBJNTML.js.map} +0 -0
  38. /package/dist/{chunk-VGNIDRKM.js.map → chunk-VDY5V7ZG.js.map} +0 -0
@@ -1,13 +1,16 @@
1
+ import {
2
+ observedBroker
3
+ } from "./chunk-FAN2R3GW.js";
1
4
  import {
2
5
  SKIP_WORKSPACE_DIRS,
3
6
  assertPinnedImage,
4
7
  stageWorkspace,
5
8
  stageWorkspacePair,
6
9
  verifyContainerEngineBoundary
7
- } from "./chunk-HDIR4MM5.js";
10
+ } from "./chunk-5LRYJKUI.js";
8
11
  import {
9
12
  HARNESS_PROTOCOL_VERSION
10
- } from "./chunk-I43KTCJ2.js";
13
+ } from "./chunk-U324RQ4N.js";
11
14
 
12
15
  // src/workspace-digest.ts
13
16
  import { createHash } from "crypto";
@@ -344,7 +347,7 @@ async function parseSource(value) {
344
347
  }
345
348
  function parseReview(value) {
346
349
  const review = record(record(value)?.review);
347
- if (!review || !["approved", "rejected"].includes(String(review.verdict)) || typeof review.reviewDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(review.reviewDigest) || typeof review.provider !== "string" || !review.provider || typeof review.model !== "string" || !review.model || !Number.isSafeInteger(review.policyVersion) || Number(review.policyVersion) < 1) throw invalid("review");
350
+ if (!review || !["approved", "rejected"].includes(String(review.verdict)) || typeof review.reviewDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(review.reviewDigest) || typeof review.provider !== "string" || !review.provider || typeof review.model !== "string" || !review.model || !Number.isSafeInteger(review.policyVersion) || Number(review.policyVersion) < 1 || !Number.isSafeInteger(review.score) || Number(review.score) < 0 || Number(review.score) > 100 || typeof review.summary !== "string" || !Array.isArray(review.findings) || review.findings.length > 12) throw invalid("review");
348
351
  return review;
349
352
  }
350
353
  function parseCandidate(value) {
@@ -451,10 +454,41 @@ var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-toke
451
454
  var PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
452
455
  var FORBIDDEN = /^(?:GIT binary patch|Binary files |rename (?:from|to) |copy (?:from|to) |similarity index |old mode |new mode |deleted file mode 160000|new file mode 160000)/m;
453
456
  function stripPatchEnvelope(patch2) {
454
- if (!/^\*\*\* (?:Begin|End) Patch\s*$/m.test(patch2)) return patch2;
457
+ if (!/^\*\*\* (?:Begin|End) Patch\s*$/m.test(patch2)) return applyPatchDialectToDiff(patch2);
455
458
  const kept = patch2.split("\n").filter((line) => !/^\*\*\* (?:Begin|End) Patch\s*$/.test(line));
456
459
  const stripped = kept.join("\n");
457
- return /^diff --git /m.test(stripped) ? stripped : patch2;
460
+ if (/^diff --git /m.test(stripped)) return stripped;
461
+ const translated = applyPatchDialectToDiff(stripped);
462
+ return translated === stripped ? patch2 : translated;
463
+ }
464
+ function applyPatchDialectToDiff(patch2) {
465
+ if (!/^\*\*\* (?:Update|Add|Delete) File: /m.test(patch2)) return patch2;
466
+ const out = [];
467
+ let open = false;
468
+ for (const line of patch2.split("\n")) {
469
+ const file = /^\*\*\* (Update|Add|Delete) File: (.+?)\s*$/.exec(line);
470
+ if (file) {
471
+ const [, verb, raw] = file;
472
+ const path = raw.trim();
473
+ if (!PATH.test(path)) return patch2;
474
+ out.push(`diff --git a/${path} b/${path}`);
475
+ if (verb === "Add") out.push("new file mode 100644", "--- /dev/null", `+++ b/${path}`);
476
+ else if (verb === "Delete") out.push(`--- a/${path}`, "+++ /dev/null");
477
+ else out.push(`--- a/${path}`, `+++ b/${path}`);
478
+ open = true;
479
+ continue;
480
+ }
481
+ if (/^\*\*\* /.test(line)) continue;
482
+ if (!open) continue;
483
+ if (/^@@/.test(line)) {
484
+ out.push("@@ -1 +1 @@");
485
+ continue;
486
+ }
487
+ out.push(line);
488
+ }
489
+ if (!open) return patch2;
490
+ return `${out.join("\n").replace(/\n+$/, "")}
491
+ `;
458
492
  }
459
493
  function validateCodePatch(rawPatch, maxBytes) {
460
494
  const patch2 = stripPatchEnvelope(rawPatch);
@@ -470,7 +504,7 @@ function validateCodePatch(rawPatch, maxBytes) {
470
504
  if (FORBIDDEN.test(patch2) || /(?:old|new)(?: file)? mode 120000/.test(patch2)) {
471
505
  throw new TypeError("patch uses a forbidden binary, link, mode, rename, or copy operation");
472
506
  }
473
- const paths2 = [];
507
+ const paths = [];
474
508
  const lines = patch2.split("\n");
475
509
  for (let index = 0; index < lines.length; index += 1) {
476
510
  const line = lines[index];
@@ -486,10 +520,10 @@ function validateCodePatch(rawPatch, maxBytes) {
486
520
  if (!validHeaderPath(oldPath, path, "a") || !validHeaderPath(newPath, path, "b")) {
487
521
  throw new TypeError("patch file headers do not match the declared path");
488
522
  }
489
- paths2.push(path);
523
+ paths.push(path);
490
524
  }
491
- if (!paths2.length || new Set(paths2).size !== paths2.length) throw new TypeError("patch has no diffs or repeats a path");
492
- return paths2;
525
+ if (!paths.length || new Set(paths).size !== paths.length) throw new TypeError("patch has no diffs or repeats a path");
526
+ return paths;
493
527
  }
494
528
  function validHeaderPath(value, path, prefix) {
495
529
  return value === "/dev/null" || value === `${prefix}/${path}`;
@@ -507,18 +541,21 @@ function resolveCodePath(workspaceDir, path) {
507
541
  if (target !== root && !target.startsWith(`${root}${sep}`)) throw new TypeError("path escapes the staged workspace");
508
542
  return target;
509
543
  }
544
+ function hasContextFreeHunk(patch2) {
545
+ const bodies = patch2.split(/^@@.*$/m).slice(1);
546
+ return bodies.some((body) => !body.split("\n").some((line) => line.startsWith(" ") && line.trim().length > 0));
547
+ }
510
548
  function describePatchFailure(patch2, detail) {
511
549
  const hunks = patch2.split("\n").filter((line) => line.startsWith("@@"));
512
- const bodies = patch2.split(/^@@.*$/m).slice(1);
513
- const contextless = bodies.some((body) => !body.split("\n").some((line) => line.startsWith(" ") && line.trim().length > 0));
514
- const hint = hunks.length > 0 && contextless ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
550
+ const hint = hunks.length > 0 && hasContextFreeHunk(patch2) ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
515
551
  return `patch did not apply: ${detail}${hint}`;
516
552
  }
517
- async function applyCodePatch(workspaceDir, rawPatch, paths2) {
553
+ async function applyCodePatch(workspaceDir, rawPatch, paths) {
518
554
  const patch2 = stripPatchEnvelope(rawPatch);
519
- await gitApply(workspaceDir, patch2, true);
520
- await gitApply(workspaceDir, patch2, false);
521
- for (const path of paths2) {
555
+ const zero = hasContextFreeHunk(patch2);
556
+ await gitApply(workspaceDir, patch2, true, zero);
557
+ await gitApply(workspaceDir, patch2, false, zero);
558
+ for (const path of paths) {
522
559
  try {
523
560
  const info = await lstat(resolveCodePath(workspaceDir, path));
524
561
  if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
@@ -529,9 +566,16 @@ async function applyCodePatch(workspaceDir, rawPatch, paths2) {
529
566
  }
530
567
  }
531
568
  }
532
- function gitApply(cwd, patch2, check) {
569
+ function gitApply(cwd, patch2, check, unidiffZero = false) {
533
570
  return new Promise((accept, reject) => {
534
- const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
571
+ const args = [
572
+ "apply",
573
+ "--recount",
574
+ ...unidiffZero ? ["--unidiff-zero"] : [],
575
+ "--whitespace=nowarn",
576
+ ...check ? ["--check"] : [],
577
+ "-"
578
+ ];
535
579
  const child = spawn("git", args, {
536
580
  cwd,
537
581
  shell: false,
@@ -540,8 +584,8 @@ function gitApply(cwd, patch2, check) {
540
584
  });
541
585
  let stderr = "";
542
586
  child.stderr.setEncoding("utf8");
543
- child.stderr.on("data", (text2) => {
544
- if (stderr.length < 4e3) stderr += text2.slice(0, 4e3);
587
+ child.stderr.on("data", (text) => {
588
+ if (stderr.length < 4e3) stderr += text.slice(0, 4e3);
545
589
  });
546
590
  child.once("error", reject);
547
591
  child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr.trim().slice(0, 500)))));
@@ -571,8 +615,8 @@ async function restoreCodeWorkspaceCheckpoint(input) {
571
615
  const workspace = await stageWorkspace(input.trustedBaseDir, input.stage);
572
616
  try {
573
617
  if (checkpoint.patch) {
574
- const paths2 = validateCodePatch(checkpoint.patch, 256 * 1024);
575
- await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths2);
618
+ const paths = validateCodePatch(checkpoint.patch, 256 * 1024);
619
+ await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths);
576
620
  }
577
621
  return { workspace, checkpoint };
578
622
  } catch (error) {
@@ -741,8 +785,8 @@ async function verifyCodeCandidate(input) {
741
785
  try {
742
786
  const baseDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
743
787
  if (baseDigest !== input.trustedBaseDigest) throw new TypeError("trusted base does not match its registered digest");
744
- const paths2 = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
745
- await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths2);
788
+ const paths = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
789
+ await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths);
746
790
  const sourceDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
747
791
  const policyDigest = digestPolicy(policy);
748
792
  const patchDigest = digestBytes(input.candidatePatch);
@@ -751,7 +795,7 @@ async function verifyCodeCandidate(input) {
751
795
  trustedBaseDigest: input.trustedBaseDigest,
752
796
  patchDigest
753
797
  });
754
- const changedTests = changedTestPaths(paths2, policy);
798
+ const changedTests = changedTestPaths(paths, policy);
755
799
  if (changedTests.length > policy.maximumChangedTests) throw new TypeError("candidate changes too many test files");
756
800
  const recipes = [];
757
801
  const logs = [];
@@ -818,8 +862,8 @@ function validate(input) {
818
862
  }
819
863
  return result;
820
864
  }
821
- function changedTestPaths(paths2, policy) {
822
- return paths2.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
865
+ function changedTestPaths(paths, policy) {
866
+ return paths.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
823
867
  }
824
868
  function recipeReceipt(recipe2, result, artifacts) {
825
869
  const status = result.timedOut ? "timed_out" : result.outputLimitExceeded ? "output_limited" : result.exitCode === 0 && artifacts.every((item) => item.status === "verified") ? "passed" : "failed";
@@ -943,9 +987,9 @@ async function prepareRuntimeCheckpoint(input) {
943
987
  if (evidence.receipt.outcome === "passed") {
944
988
  verification = evidence.receipt;
945
989
  review = await input.review(patch2, verification);
946
- note = review.verdict === "approved" ? "Clean verification and independent review passed" : "Clean verification passed; independent review rejected the candidate";
990
+ note = describeReview(review);
947
991
  } else {
948
- note = `Clean verification failed: ${evidence.receipt.recipes.filter((recipe2) => recipe2.status !== "passed").map((recipe2) => `${recipe2.recipeId}=${recipe2.status}`).join(", ")}`;
992
+ note = describeGateFailure(evidence);
949
993
  }
950
994
  } catch (cause) {
951
995
  note = `Candidate verification or review failed closed: ${message(cause)}`;
@@ -970,6 +1014,23 @@ async function prepareRuntimeCheckpoint(input) {
970
1014
  });
971
1015
  return { checkpoint, verification, review, note };
972
1016
  }
1017
+ function describeReview(review) {
1018
+ const findings = review.findings.map((finding) => ` - [${finding.severity}] ${finding.detail}`).join("\n");
1019
+ const head = review.verdict === "approved" ? `Clean verification and independent review passed (score ${review.score}/100).` : `Clean verification passed; independent review REJECTED this candidate (score ${review.score}/100). Address the findings and checkpoint again.`;
1020
+ return [head, review.summary, findings].filter(Boolean).join("\n").slice(0, 4e3);
1021
+ }
1022
+ function describeGateFailure(evidence) {
1023
+ const failed = evidence.receipt.recipes.filter((recipe2) => recipe2.status !== "passed");
1024
+ const detail = failed.map((recipe2) => {
1025
+ const log = evidence.logs.find((entry) => entry.recipeId === recipe2.recipeId);
1026
+ const output = `${log?.stdout ?? ""}
1027
+ ${log?.stderr ?? ""}`.trim();
1028
+ return `${recipe2.recipeId}=${recipe2.status}${output ? `
1029
+ ${output.slice(0, 1500)}` : ""}`;
1030
+ }).join("\n\n");
1031
+ return `Clean verification failed. Fix this and checkpoint again:
1032
+ ${detail}`.slice(0, 4e3);
1033
+ }
973
1034
  var message = (value) => (value instanceof Error ? value.message : String(value)).slice(0, 500);
974
1035
 
975
1036
  // src/code-runtime-checkpoint-manager.ts
@@ -1205,12 +1266,24 @@ async function materializeCommandWorkspace(input) {
1205
1266
  resume
1206
1267
  });
1207
1268
  if (command.payload.sourceSet) {
1208
- const selected = await input.control.source(command.sessionId);
1209
- if (selected.repository !== metadata.repository || selected.commitSha !== metadata.baseCommitSha || selected.treeDigest !== metadata.sourceTreeDigest) {
1269
+ if (prepared.trustedBaseDigest !== metadata.sourceTreeDigest) {
1210
1270
  await prepared.workspace.cleanup();
1211
1271
  throw new TypeError("Code local source does not match the selected GitHub primary source");
1212
1272
  }
1213
- await attachCodeRuntimeReferences(prepared.workspace, selected.references ?? []);
1273
+ const set = command.payload.sourceSet && typeof command.payload.sourceSet === "object" && !Array.isArray(command.payload.sourceSet) ? command.payload.sourceSet : null;
1274
+ const references = set?.references;
1275
+ if (!Array.isArray(references)) {
1276
+ await prepared.workspace.cleanup();
1277
+ throw new TypeError("Code selected source set is invalid");
1278
+ }
1279
+ if (references.length) {
1280
+ const selected = await input.control.source(command.sessionId);
1281
+ if (selected.repository !== metadata.repository || selected.commitSha !== metadata.baseCommitSha || selected.treeDigest !== metadata.sourceTreeDigest) {
1282
+ await prepared.workspace.cleanup();
1283
+ throw new TypeError("Code local source does not match the selected GitHub primary source");
1284
+ }
1285
+ await attachCodeRuntimeReferences(prepared.workspace, selected.references ?? []);
1286
+ }
1214
1287
  }
1215
1288
  return {
1216
1289
  workspace: prepared.workspace,
@@ -1626,7 +1699,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
1626
1699
  files(root) {
1627
1700
  const existing = cache2.get(root);
1628
1701
  if (existing) return existing;
1629
- const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
1702
+ const pending = enumerate(root, limit).then((paths) => Object.freeze(paths));
1630
1703
  cache2.set(root, pending);
1631
1704
  void pending.catch(() => {
1632
1705
  if (cache2.get(root) === pending) cache2.delete(root);
@@ -1639,7 +1712,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
1639
1712
  };
1640
1713
  }
1641
1714
  async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
1642
- const paths2 = [];
1715
+ const paths = [];
1643
1716
  const walk = async (directory) => {
1644
1717
  for (const entry of await readdir2(directory, { withFileTypes: true })) {
1645
1718
  if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
@@ -1653,26 +1726,26 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
1653
1726
  } catch {
1654
1727
  continue;
1655
1728
  }
1656
- paths2.push(path);
1657
- if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
1729
+ paths.push(path);
1730
+ if (paths.length > limit) throw new TypeError("workspace file registry exceeds its bound");
1658
1731
  }
1659
1732
  }
1660
1733
  };
1661
1734
  await walk(resolve4(root));
1662
- return paths2.sort();
1735
+ return paths.sort();
1663
1736
  }
1664
- function listWorkspace(paths2, options = {}) {
1737
+ function listWorkspace(paths, options = {}) {
1665
1738
  const max = options.maxEntries ?? 1e3;
1666
1739
  const prefix = options.prefix?.replace(/\/+$/, "");
1667
- const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
1740
+ const scoped = prefix ? paths.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths];
1668
1741
  return scoped.slice(0, max);
1669
1742
  }
1670
- async function searchWorkspace(root, paths2, options) {
1743
+ async function searchWorkspace(root, paths, options) {
1671
1744
  options.signal?.throwIfAborted();
1672
1745
  if (!options.query) throw new TypeError("search query must be a non-empty string");
1673
1746
  const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
1674
1747
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
1675
- const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
1748
+ const scoped = listWorkspace(paths, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths.length });
1676
1749
  if (scoped.length === 0) return [];
1677
1750
  try {
1678
1751
  return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
@@ -1682,11 +1755,11 @@ async function searchWorkspace(root, paths2, options) {
1682
1755
  }
1683
1756
  }
1684
1757
  var MAX_NATIVE_ARG_BYTES = 96 * 1024;
1685
- async function nativeSearch(root, paths2, options) {
1758
+ async function nativeSearch(root, paths, options) {
1686
1759
  const batches = [];
1687
1760
  let batch = [];
1688
1761
  let bytes = 0;
1689
- for (const path of paths2) {
1762
+ for (const path of paths) {
1690
1763
  const size = Buffer.byteLength(path) + 1;
1691
1764
  if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
1692
1765
  batches.push(batch);
@@ -1705,7 +1778,7 @@ async function nativeSearch(root, paths2, options) {
1705
1778
  }
1706
1779
  return matches;
1707
1780
  }
1708
- function nativeSearchBatch(root, paths2, options, remaining) {
1781
+ function nativeSearchBatch(root, paths, options, remaining) {
1709
1782
  return new Promise((resolveMatches, reject) => {
1710
1783
  const args = [
1711
1784
  "--fixed-strings",
@@ -1718,7 +1791,7 @@ function nativeSearchBatch(root, paths2, options, remaining) {
1718
1791
  options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
1719
1792
  "--",
1720
1793
  options.query,
1721
- ...paths2
1794
+ ...paths
1722
1795
  ];
1723
1796
  const child = spawn3("rg", args, {
1724
1797
  cwd: root,
@@ -1916,16 +1989,16 @@ function createCodePolicyGate(options) {
1916
1989
  }
1917
1990
  };
1918
1991
  }
1919
- function directoryPrefixes(paths2) {
1992
+ function directoryPrefixes(paths) {
1920
1993
  const prefixes = /* @__PURE__ */ new Set(["."]);
1921
- for (const path of paths2) {
1994
+ for (const path of paths) {
1922
1995
  const parts = path.split("/");
1923
1996
  for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
1924
1997
  }
1925
1998
  return [...prefixes].sort();
1926
1999
  }
1927
- async function safePrefix(base, paths2, prefix) {
1928
- const prefixes = directoryPrefixes(paths2);
2000
+ async function safePrefix(base, paths, prefix) {
2001
+ const prefixes = directoryPrefixes(paths);
1929
2002
  const conversions = await conversionRegistry(
1930
2003
  [await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
1931
2004
  { "code.prefixes.v1": prefixes }
@@ -2035,8 +2108,14 @@ function optionalInteger(value) {
2035
2108
  if (!Number.isSafeInteger(value) || value < 1) throw new TypeError("line bounds must be positive integers");
2036
2109
  return value;
2037
2110
  }
2111
+ var DEFAULT_FAILURE_REASON = {
2112
+ "tool denied by CaMeL policy": "tool denied by CaMeL policy",
2113
+ "review sessions are read-only": "review session is read-only; workspace changes are not permitted"
2114
+ };
2038
2115
  function response(request, ok, content, details) {
2039
- return { requestId: request.requestId, ok, content, ...details ? { details } : {} };
2116
+ if (ok) return { requestId: request.requestId, ok, content, ...details ? { details } : {} };
2117
+ const failureReason = typeof details?.failureReason === "string" && details.failureReason || DEFAULT_FAILURE_REASON[content] || content || "tool request failed; inspect the tool input and workspace state";
2118
+ return { requestId: request.requestId, ok, content, details: { ...details, failureReason } };
2040
2119
  }
2041
2120
 
2042
2121
  // src/code-tool-reads.ts
@@ -2055,7 +2134,7 @@ import {
2055
2134
  } from "@odla-ai/graph";
2056
2135
  import { buildCodeGraph, FILE, IMPORTS, PACKAGE, READS, SYMBOL, WRITES } from "@odla-ai/graph/code";
2057
2136
  var cache = /* @__PURE__ */ new Map();
2058
- function workspaceGraphs(workspaceDir, paths2) {
2137
+ function workspaceGraphs(workspaceDir, paths) {
2059
2138
  const existing = cache.get(workspaceDir);
2060
2139
  if (existing) return existing;
2061
2140
  const read2 = (path) => readFile3(join3(workspaceDir, path), "utf8");
@@ -2063,7 +2142,7 @@ function workspaceGraphs(workspaceDir, paths2) {
2063
2142
  // No knownTables: a staged workspace may not carry migrations, and a filter
2064
2143
  // that silently drops every table is worse than an unfiltered one. Callers
2065
2144
  // with ground truth should build the graph themselves.
2066
- graph: await buildCodeGraph({ paths: paths2, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
2145
+ graph: await buildCodeGraph({ paths, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
2067
2146
  }))();
2068
2147
  cache.set(workspaceDir, built);
2069
2148
  return built;
@@ -2125,11 +2204,11 @@ async function read(context, request, options, policy, registry) {
2125
2204
  if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
2126
2205
  throw new TypeError("requested line range exceeds its bound");
2127
2206
  }
2128
- const paths2 = await registry.files(context.workspaceDir);
2129
- if (!paths2.includes(path)) {
2207
+ const paths = await registry.files(context.workspaceDir);
2208
+ if (!paths.includes(path)) {
2130
2209
  throw new TypeError(`no such file in the staged workspace: "${path}". Use sandbox.overview, sandbox.where_is or sandbox.search to find the correct path.`);
2131
2210
  }
2132
- const allowed = await policy.read(policyContext(context, request, options, { paths: paths2, path, startLine, endLine }));
2211
+ const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
2133
2212
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
2134
2213
  const target = resolveCodePath(context.workspaceDir, path);
2135
2214
  const info = await stat(target);
@@ -2151,16 +2230,16 @@ async function list(context, request, options, policy, registry) {
2151
2230
  const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
2152
2231
  const maxEntries = optionalInteger(request.input.maxEntries) ?? 1e3;
2153
2232
  if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
2154
- const paths2 = await registry.files(context.workspaceDir);
2155
- const allowed = await policy.list(policyContext(context, request, options, { paths: paths2, ...prefix ? { prefix } : {} }));
2233
+ const paths = await registry.files(context.workspaceDir);
2234
+ const allowed = await policy.list(policyContext(context, request, options, { paths, ...prefix ? { prefix } : {} }));
2156
2235
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
2157
- const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
2236
+ const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
2158
2237
  if (!entries.length) {
2159
2238
  return response(request, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
2160
2239
  }
2161
- const truncated = entries.length < paths2.length && entries.length === maxEntries;
2162
- const hint = !prefix && paths2.length > 500 ? `
2163
- \u2026 ${paths2.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
2240
+ const truncated = entries.length < paths.length && entries.length === maxEntries;
2241
+ const hint = !prefix && paths.length > 500 ? `
2242
+ \u2026 ${paths.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
2164
2243
  return response(
2165
2244
  request,
2166
2245
  true,
@@ -2178,10 +2257,10 @@ async function search(context, request, options, policy, registry) {
2178
2257
  const maxResults = optionalInteger(request.input.maxResults) ?? 100;
2179
2258
  if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
2180
2259
  const caseSensitive = request.input.caseSensitive === void 0 ? true : request.input.caseSensitive === true;
2181
- const paths2 = await registry.files(context.workspaceDir);
2182
- const allowed = await policy.search(policyContext(context, request, options, { paths: paths2, query, ...prefix ? { prefix } : {} }));
2260
+ const paths = await registry.files(context.workspaceDir);
2261
+ const allowed = await policy.search(policyContext(context, request, options, { paths, query, ...prefix ? { prefix } : {} }));
2183
2262
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
2184
- const matches = await searchWorkspace(context.workspaceDir, paths2, {
2263
+ const matches = await searchWorkspace(context.workspaceDir, paths, {
2185
2264
  query,
2186
2265
  maxResults,
2187
2266
  caseSensitive,
@@ -2203,8 +2282,8 @@ async function graphQuery(context, request, options, policy, registry) {
2203
2282
  selector: query
2204
2283
  }));
2205
2284
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
2206
- const paths2 = await registry.files(context.workspaceDir);
2207
- const graphs = await workspaceGraphs(context.workspaceDir, paths2);
2285
+ const paths = await registry.files(context.workspaceDir);
2286
+ const graphs = await workspaceGraphs(context.workspaceDir, paths);
2208
2287
  if (request.tool === "sandbox.overview") {
2209
2288
  return response(request, true, renderOverview(graphs, query || void 0));
2210
2289
  }
@@ -2268,16 +2347,16 @@ function toolFailureMessage(reason) {
2268
2347
  async function patch(context, request, options, policy, registry) {
2269
2348
  exactKeys(request.input, ["patch"]);
2270
2349
  const value = stringField(request.input, "patch");
2271
- const paths2 = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
2272
- if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
2350
+ const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
2351
+ if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
2273
2352
  throw new TypeError("patch targets a read-only reference source");
2274
2353
  }
2275
2354
  const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
2276
2355
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
2277
- await applyCodePatch(context.workspaceDir, value, paths2);
2356
+ await applyCodePatch(context.workspaceDir, value, paths);
2278
2357
  registry.invalidate(context.workspaceDir);
2279
2358
  forgetWorkspaceGraphs(context.workspaceDir);
2280
- return response(request, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
2359
+ return response(request, true, `Applied patch to ${paths.length} file(s).`, { paths });
2281
2360
  }
2282
2361
  async function recipe(context, request, options, recipes, policy) {
2283
2362
  exactKeys(request.input, ["recipeId"]);
@@ -2668,122 +2747,6 @@ async function appendCodeRuntimeEvent(control, command, event, refs) {
2668
2747
  var digestRuntimeValue = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
2669
2748
  var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
2670
2749
 
2671
- // src/code-tool-presentation.ts
2672
- var text = (value, maximum) => {
2673
- if (typeof value !== "string") return void 0;
2674
- const bounded = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
2675
- return bounded ? bounded.slice(0, maximum) : void 0;
2676
- };
2677
- var integer2 = (value) => Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : void 0;
2678
- var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2679
- var excerpt = (value, tail = false) => {
2680
- if (typeof value !== "string") return void 0;
2681
- const safe = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
2682
- const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
2683
- if (!source) return void 0;
2684
- const lines = source.split("\n").filter((line) => line.trim()).map((line) => line.slice(0, 240));
2685
- const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
2686
- return text(selected.join("\n"), 2400);
2687
- };
2688
- var paths = (value) => {
2689
- if (!Array.isArray(value)) return void 0;
2690
- const items = value.flatMap((item) => {
2691
- const path = text(item, 1024);
2692
- return path ? [path] : [];
2693
- }).slice(0, 12);
2694
- return items.length ? items : void 0;
2695
- };
2696
- function patchStats(value) {
2697
- if (typeof value !== "string") return {};
2698
- let additions = 0;
2699
- let deletions = 0;
2700
- for (const line of value.slice(0, 262144).split("\n")) {
2701
- if (line.startsWith("+++") || line.startsWith("---")) continue;
2702
- if (line.startsWith("+")) additions += 1;
2703
- else if (line.startsWith("-")) deletions += 1;
2704
- }
2705
- return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
2706
- }
2707
- function searchResults(value) {
2708
- if (typeof value !== "string") return void 0;
2709
- const results = value.split("\n").flatMap((line) => {
2710
- const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line);
2711
- if (!match) return [];
2712
- const lineNumber = Number(match[2]);
2713
- const itemText = text(match[3], 240);
2714
- if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
2715
- return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
2716
- }).slice(0, 5);
2717
- return results.length ? results : void 0;
2718
- }
2719
- function codeToolRequestPresentation(request) {
2720
- const input = request.input;
2721
- if (request.tool === "sandbox.read") {
2722
- const path = text(input.path, 1024);
2723
- if (!path) return void 0;
2724
- const startLine = integer2(input.startLine);
2725
- const endLine = integer2(input.endLine);
2726
- return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
2727
- }
2728
- if (request.tool === "sandbox.list") {
2729
- const scope = text(input.prefix, 1024);
2730
- return { kind: "list", ...scope ? { scope } : {} };
2731
- }
2732
- if (request.tool === "sandbox.search" || request.tool === "sandbox.overview" || request.tool === "sandbox.where_is" || request.tool === "sandbox.who_imports" || request.tool === "sandbox.who_touches") {
2733
- const query = text(input.query, 512);
2734
- const scope = request.tool === "sandbox.search" ? text(input.prefix, 1024) : void 0;
2735
- if (request.tool === "sandbox.search" && !query) return void 0;
2736
- return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
2737
- }
2738
- if (request.tool === "sandbox.apply_patch") {
2739
- return { kind: "patch", ...patchStats(input.patch) };
2740
- }
2741
- const recipeId = text(input.recipeId, 120);
2742
- return recipeId ? { kind: "recipe", recipeId } : void 0;
2743
- }
2744
- function codeToolResultPresentation(request, response2) {
2745
- const started = codeToolRequestPresentation(request);
2746
- if (!started || !response2.ok) return started;
2747
- const details = record3(response2.details);
2748
- if (started.kind === "read") {
2749
- return {
2750
- ...started,
2751
- ...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
2752
- ...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
2753
- ...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
2754
- };
2755
- }
2756
- if (started.kind === "list") {
2757
- const listed = response2.content.split("\n").filter((line) => line && !line.startsWith("\u2026") && !line.startsWith("Workspace ")).map((line) => text(line, 1024)).filter((line) => Boolean(line)).slice(0, 8);
2758
- return {
2759
- ...started,
2760
- ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
2761
- ...listed.length ? { paths: listed } : {}
2762
- };
2763
- }
2764
- if (started.kind === "query") {
2765
- const results = request.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
2766
- const resultExcerpt = request.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
2767
- return {
2768
- ...started,
2769
- ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
2770
- ...results ? { results } : {},
2771
- ...resultExcerpt ? { excerpt: resultExcerpt } : {}
2772
- };
2773
- }
2774
- if (started.kind === "patch") {
2775
- return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
2776
- }
2777
- const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
2778
- return {
2779
- ...started,
2780
- ...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
2781
- ...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
2782
- ...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
2783
- ...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
2784
- };
2785
- }
2786
-
2787
2750
  // src/code-runtime-acknowledgement-gate.ts
2788
2751
  function codeRuntimeAcknowledgementGate(signal) {
2789
2752
  let settle;
@@ -3090,36 +3053,11 @@ var TheseusRuntimeEngine = class {
3090
3053
  }
3091
3054
  /** Report every brokered effect as it starts and finishes. */
3092
3055
  #observed(command, active, broker) {
3093
- return {
3094
- execute: async (context, request) => {
3095
- const startedAt = Date.now();
3096
- const operationId = digestRuntimeValue(`${command.commandId}:${request.requestId}`);
3097
- const startedPresentation = codeToolRequestPresentation(request);
3098
- await this.#event(
3099
- command,
3100
- {
3101
- type: "tool",
3102
- phase: "started",
3103
- tool: request.tool,
3104
- operationId,
3105
- ...startedPresentation ? { presentation: startedPresentation } : {}
3106
- },
3107
- active.conversationRefs
3108
- ).catch(() => void 0);
3109
- const response2 = await broker.execute(context, request);
3110
- const completedPresentation = codeToolResultPresentation(request, response2);
3111
- await this.#event(command, {
3112
- type: "tool",
3113
- phase: "completed",
3114
- tool: request.tool,
3115
- ok: response2.ok,
3116
- durationMs: Date.now() - startedAt,
3117
- operationId,
3118
- ...completedPresentation ? { presentation: completedPresentation } : {}
3119
- }, active.conversationRefs).catch(() => void 0);
3120
- return response2;
3121
- }
3122
- };
3056
+ return observedBroker({
3057
+ broker,
3058
+ operationIdFor: (requestId) => digestRuntimeValue(`${command.commandId}:${requestId}`),
3059
+ emit: (event) => this.#event(command, event, active.conversationRefs).catch(() => void 0)
3060
+ });
3123
3061
  }
3124
3062
  async #checkpoint(command) {
3125
3063
  const active = this.#active.get(command.sessionId);
@@ -3156,9 +3094,11 @@ export {
3156
3094
  runCodeRuntimeHeartbeatLoop,
3157
3095
  CodeRuntimeReconciler,
3158
3096
  stripPatchEnvelope,
3097
+ applyPatchDialectToDiff,
3159
3098
  validateCodePatch,
3160
3099
  validateRelativePath,
3161
3100
  resolveCodePath,
3101
+ hasContextFreeHunk,
3162
3102
  describePatchFailure,
3163
3103
  applyCodePatch,
3164
3104
  createCodeWorkspaceCheckpoint,
@@ -3193,4 +3133,4 @@ export {
3193
3133
  runGoal,
3194
3134
  TheseusRuntimeEngine
3195
3135
  };
3196
- //# sourceMappingURL=chunk-4EPJJMFG.js.map
3136
+ //# sourceMappingURL=chunk-AEAISFY3.js.map