@odla-ai/harness 0.9.1 → 0.9.2

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-FAN2R3GW.js +166 -0
  3. package/dist/chunk-FAN2R3GW.js.map +1 -0
  4. package/dist/{chunk-CIKYMC67.js → chunk-OZBJNTML.js} +4 -4
  5. package/dist/{chunk-4EPJJMFG.js → chunk-Q4NL5XO3.js} +142 -214
  6. package/dist/chunk-Q4NL5XO3.js.map +1 -0
  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 +243 -160
  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 +246 -159
  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
@@ -1626,7 +1687,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
1626
1687
  files(root) {
1627
1688
  const existing = cache2.get(root);
1628
1689
  if (existing) return existing;
1629
- const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
1690
+ const pending = enumerate(root, limit).then((paths) => Object.freeze(paths));
1630
1691
  cache2.set(root, pending);
1631
1692
  void pending.catch(() => {
1632
1693
  if (cache2.get(root) === pending) cache2.delete(root);
@@ -1639,7 +1700,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
1639
1700
  };
1640
1701
  }
1641
1702
  async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
1642
- const paths2 = [];
1703
+ const paths = [];
1643
1704
  const walk = async (directory) => {
1644
1705
  for (const entry of await readdir2(directory, { withFileTypes: true })) {
1645
1706
  if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
@@ -1653,26 +1714,26 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
1653
1714
  } catch {
1654
1715
  continue;
1655
1716
  }
1656
- paths2.push(path);
1657
- if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
1717
+ paths.push(path);
1718
+ if (paths.length > limit) throw new TypeError("workspace file registry exceeds its bound");
1658
1719
  }
1659
1720
  }
1660
1721
  };
1661
1722
  await walk(resolve4(root));
1662
- return paths2.sort();
1723
+ return paths.sort();
1663
1724
  }
1664
- function listWorkspace(paths2, options = {}) {
1725
+ function listWorkspace(paths, options = {}) {
1665
1726
  const max = options.maxEntries ?? 1e3;
1666
1727
  const prefix = options.prefix?.replace(/\/+$/, "");
1667
- const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
1728
+ const scoped = prefix ? paths.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths];
1668
1729
  return scoped.slice(0, max);
1669
1730
  }
1670
- async function searchWorkspace(root, paths2, options) {
1731
+ async function searchWorkspace(root, paths, options) {
1671
1732
  options.signal?.throwIfAborted();
1672
1733
  if (!options.query) throw new TypeError("search query must be a non-empty string");
1673
1734
  const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
1674
1735
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
1675
- const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
1736
+ const scoped = listWorkspace(paths, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths.length });
1676
1737
  if (scoped.length === 0) return [];
1677
1738
  try {
1678
1739
  return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
@@ -1682,11 +1743,11 @@ async function searchWorkspace(root, paths2, options) {
1682
1743
  }
1683
1744
  }
1684
1745
  var MAX_NATIVE_ARG_BYTES = 96 * 1024;
1685
- async function nativeSearch(root, paths2, options) {
1746
+ async function nativeSearch(root, paths, options) {
1686
1747
  const batches = [];
1687
1748
  let batch = [];
1688
1749
  let bytes = 0;
1689
- for (const path of paths2) {
1750
+ for (const path of paths) {
1690
1751
  const size = Buffer.byteLength(path) + 1;
1691
1752
  if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
1692
1753
  batches.push(batch);
@@ -1705,7 +1766,7 @@ async function nativeSearch(root, paths2, options) {
1705
1766
  }
1706
1767
  return matches;
1707
1768
  }
1708
- function nativeSearchBatch(root, paths2, options, remaining) {
1769
+ function nativeSearchBatch(root, paths, options, remaining) {
1709
1770
  return new Promise((resolveMatches, reject) => {
1710
1771
  const args = [
1711
1772
  "--fixed-strings",
@@ -1718,7 +1779,7 @@ function nativeSearchBatch(root, paths2, options, remaining) {
1718
1779
  options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
1719
1780
  "--",
1720
1781
  options.query,
1721
- ...paths2
1782
+ ...paths
1722
1783
  ];
1723
1784
  const child = spawn3("rg", args, {
1724
1785
  cwd: root,
@@ -1916,16 +1977,16 @@ function createCodePolicyGate(options) {
1916
1977
  }
1917
1978
  };
1918
1979
  }
1919
- function directoryPrefixes(paths2) {
1980
+ function directoryPrefixes(paths) {
1920
1981
  const prefixes = /* @__PURE__ */ new Set(["."]);
1921
- for (const path of paths2) {
1982
+ for (const path of paths) {
1922
1983
  const parts = path.split("/");
1923
1984
  for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
1924
1985
  }
1925
1986
  return [...prefixes].sort();
1926
1987
  }
1927
- async function safePrefix(base, paths2, prefix) {
1928
- const prefixes = directoryPrefixes(paths2);
1988
+ async function safePrefix(base, paths, prefix) {
1989
+ const prefixes = directoryPrefixes(paths);
1929
1990
  const conversions = await conversionRegistry(
1930
1991
  [await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
1931
1992
  { "code.prefixes.v1": prefixes }
@@ -2035,8 +2096,14 @@ function optionalInteger(value) {
2035
2096
  if (!Number.isSafeInteger(value) || value < 1) throw new TypeError("line bounds must be positive integers");
2036
2097
  return value;
2037
2098
  }
2099
+ var DEFAULT_FAILURE_REASON = {
2100
+ "tool denied by CaMeL policy": "tool denied by CaMeL policy",
2101
+ "review sessions are read-only": "review session is read-only; workspace changes are not permitted"
2102
+ };
2038
2103
  function response(request, ok, content, details) {
2039
- return { requestId: request.requestId, ok, content, ...details ? { details } : {} };
2104
+ if (ok) return { requestId: request.requestId, ok, content, ...details ? { details } : {} };
2105
+ const failureReason = typeof details?.failureReason === "string" && details.failureReason || DEFAULT_FAILURE_REASON[content] || content || "tool request failed; inspect the tool input and workspace state";
2106
+ return { requestId: request.requestId, ok, content, details: { ...details, failureReason } };
2040
2107
  }
2041
2108
 
2042
2109
  // src/code-tool-reads.ts
@@ -2055,7 +2122,7 @@ import {
2055
2122
  } from "@odla-ai/graph";
2056
2123
  import { buildCodeGraph, FILE, IMPORTS, PACKAGE, READS, SYMBOL, WRITES } from "@odla-ai/graph/code";
2057
2124
  var cache = /* @__PURE__ */ new Map();
2058
- function workspaceGraphs(workspaceDir, paths2) {
2125
+ function workspaceGraphs(workspaceDir, paths) {
2059
2126
  const existing = cache.get(workspaceDir);
2060
2127
  if (existing) return existing;
2061
2128
  const read2 = (path) => readFile3(join3(workspaceDir, path), "utf8");
@@ -2063,7 +2130,7 @@ function workspaceGraphs(workspaceDir, paths2) {
2063
2130
  // No knownTables: a staged workspace may not carry migrations, and a filter
2064
2131
  // that silently drops every table is worse than an unfiltered one. Callers
2065
2132
  // with ground truth should build the graph themselves.
2066
- graph: await buildCodeGraph({ paths: paths2, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
2133
+ graph: await buildCodeGraph({ paths, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
2067
2134
  }))();
2068
2135
  cache.set(workspaceDir, built);
2069
2136
  return built;
@@ -2125,11 +2192,11 @@ async function read(context, request, options, policy, registry) {
2125
2192
  if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
2126
2193
  throw new TypeError("requested line range exceeds its bound");
2127
2194
  }
2128
- const paths2 = await registry.files(context.workspaceDir);
2129
- if (!paths2.includes(path)) {
2195
+ const paths = await registry.files(context.workspaceDir);
2196
+ if (!paths.includes(path)) {
2130
2197
  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
2198
  }
2132
- const allowed = await policy.read(policyContext(context, request, options, { paths: paths2, path, startLine, endLine }));
2199
+ const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
2133
2200
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
2134
2201
  const target = resolveCodePath(context.workspaceDir, path);
2135
2202
  const info = await stat(target);
@@ -2151,16 +2218,16 @@ async function list(context, request, options, policy, registry) {
2151
2218
  const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
2152
2219
  const maxEntries = optionalInteger(request.input.maxEntries) ?? 1e3;
2153
2220
  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 } : {} }));
2221
+ const paths = await registry.files(context.workspaceDir);
2222
+ const allowed = await policy.list(policyContext(context, request, options, { paths, ...prefix ? { prefix } : {} }));
2156
2223
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
2157
- const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
2224
+ const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
2158
2225
  if (!entries.length) {
2159
2226
  return response(request, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
2160
2227
  }
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.` : "";
2228
+ const truncated = entries.length < paths.length && entries.length === maxEntries;
2229
+ const hint = !prefix && paths.length > 500 ? `
2230
+ \u2026 ${paths.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
2164
2231
  return response(
2165
2232
  request,
2166
2233
  true,
@@ -2178,10 +2245,10 @@ async function search(context, request, options, policy, registry) {
2178
2245
  const maxResults = optionalInteger(request.input.maxResults) ?? 100;
2179
2246
  if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
2180
2247
  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 } : {} }));
2248
+ const paths = await registry.files(context.workspaceDir);
2249
+ const allowed = await policy.search(policyContext(context, request, options, { paths, query, ...prefix ? { prefix } : {} }));
2183
2250
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
2184
- const matches = await searchWorkspace(context.workspaceDir, paths2, {
2251
+ const matches = await searchWorkspace(context.workspaceDir, paths, {
2185
2252
  query,
2186
2253
  maxResults,
2187
2254
  caseSensitive,
@@ -2203,8 +2270,8 @@ async function graphQuery(context, request, options, policy, registry) {
2203
2270
  selector: query
2204
2271
  }));
2205
2272
  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);
2273
+ const paths = await registry.files(context.workspaceDir);
2274
+ const graphs = await workspaceGraphs(context.workspaceDir, paths);
2208
2275
  if (request.tool === "sandbox.overview") {
2209
2276
  return response(request, true, renderOverview(graphs, query || void 0));
2210
2277
  }
@@ -2268,16 +2335,16 @@ function toolFailureMessage(reason) {
2268
2335
  async function patch(context, request, options, policy, registry) {
2269
2336
  exactKeys(request.input, ["patch"]);
2270
2337
  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}/`)))) {
2338
+ const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
2339
+ if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
2273
2340
  throw new TypeError("patch targets a read-only reference source");
2274
2341
  }
2275
2342
  const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
2276
2343
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
2277
- await applyCodePatch(context.workspaceDir, value, paths2);
2344
+ await applyCodePatch(context.workspaceDir, value, paths);
2278
2345
  registry.invalidate(context.workspaceDir);
2279
2346
  forgetWorkspaceGraphs(context.workspaceDir);
2280
- return response(request, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
2347
+ return response(request, true, `Applied patch to ${paths.length} file(s).`, { paths });
2281
2348
  }
2282
2349
  async function recipe(context, request, options, recipes, policy) {
2283
2350
  exactKeys(request.input, ["recipeId"]);
@@ -2668,122 +2735,6 @@ async function appendCodeRuntimeEvent(control, command, event, refs) {
2668
2735
  var digestRuntimeValue = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
2669
2736
  var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
2670
2737
 
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
2738
  // src/code-runtime-acknowledgement-gate.ts
2788
2739
  function codeRuntimeAcknowledgementGate(signal) {
2789
2740
  let settle;
@@ -3090,36 +3041,11 @@ var TheseusRuntimeEngine = class {
3090
3041
  }
3091
3042
  /** Report every brokered effect as it starts and finishes. */
3092
3043
  #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
- };
3044
+ return observedBroker({
3045
+ broker,
3046
+ operationIdFor: (requestId) => digestRuntimeValue(`${command.commandId}:${requestId}`),
3047
+ emit: (event) => this.#event(command, event, active.conversationRefs).catch(() => void 0)
3048
+ });
3123
3049
  }
3124
3050
  async #checkpoint(command) {
3125
3051
  const active = this.#active.get(command.sessionId);
@@ -3156,9 +3082,11 @@ export {
3156
3082
  runCodeRuntimeHeartbeatLoop,
3157
3083
  CodeRuntimeReconciler,
3158
3084
  stripPatchEnvelope,
3085
+ applyPatchDialectToDiff,
3159
3086
  validateCodePatch,
3160
3087
  validateRelativePath,
3161
3088
  resolveCodePath,
3089
+ hasContextFreeHunk,
3162
3090
  describePatchFailure,
3163
3091
  applyCodePatch,
3164
3092
  createCodeWorkspaceCheckpoint,
@@ -3193,4 +3121,4 @@ export {
3193
3121
  runGoal,
3194
3122
  TheseusRuntimeEngine
3195
3123
  };
3196
- //# sourceMappingURL=chunk-4EPJJMFG.js.map
3124
+ //# sourceMappingURL=chunk-Q4NL5XO3.js.map