@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.
- package/dist/{chunk-HDIR4MM5.js → chunk-5LRYJKUI.js} +3 -3
- package/dist/chunk-FAN2R3GW.js +166 -0
- package/dist/chunk-FAN2R3GW.js.map +1 -0
- package/dist/{chunk-CIKYMC67.js → chunk-OZBJNTML.js} +4 -4
- package/dist/{chunk-4EPJJMFG.js → chunk-Q4NL5XO3.js} +142 -214
- package/dist/chunk-Q4NL5XO3.js.map +1 -0
- package/dist/{chunk-I43KTCJ2.js → chunk-U324RQ4N.js} +1 -1
- package/dist/{chunk-I43KTCJ2.js.map → chunk-U324RQ4N.js.map} +1 -1
- package/dist/{chunk-VGNIDRKM.js → chunk-VDY5V7ZG.js} +2 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/code-runtime-cli.cjs +243 -160
- package/dist/code-runtime-cli.cjs.map +1 -1
- package/dist/code-runtime-cli.js +6 -5
- package/dist/code-runtime-cli.js.map +1 -1
- package/dist/index.cjs +172 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -3
- package/dist/index.d.ts +24 -3
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +246 -159
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +39 -6
- package/dist/node.d.ts +39 -6
- package/dist/node.js +10 -5
- package/dist/node.js.map +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/dist/{types-BazqxWK8.d.cts → types-_8y8vBDI.d.cts} +5 -1
- package/dist/{types-BazqxWK8.d.ts → types-_8y8vBDI.d.ts} +5 -1
- package/package.json +1 -1
- package/dist/chunk-4EPJJMFG.js.map +0 -1
- /package/dist/{chunk-HDIR4MM5.js.map → chunk-5LRYJKUI.js.map} +0 -0
- /package/dist/{chunk-CIKYMC67.js.map → chunk-OZBJNTML.js.map} +0 -0
- /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-
|
|
10
|
+
} from "./chunk-5LRYJKUI.js";
|
|
8
11
|
import {
|
|
9
12
|
HARNESS_PROTOCOL_VERSION
|
|
10
|
-
} from "./chunk-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
523
|
+
paths.push(path);
|
|
490
524
|
}
|
|
491
|
-
if (!
|
|
492
|
-
return
|
|
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
|
|
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,
|
|
553
|
+
async function applyCodePatch(workspaceDir, rawPatch, paths) {
|
|
518
554
|
const patch2 = stripPatchEnvelope(rawPatch);
|
|
519
|
-
|
|
520
|
-
await gitApply(workspaceDir, patch2,
|
|
521
|
-
|
|
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 = [
|
|
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", (
|
|
544
|
-
if (stderr.length < 4e3) stderr +=
|
|
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
|
|
575
|
-
await applyCodePatch(workspace.workspaceDir, checkpoint.patch,
|
|
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
|
|
745
|
-
await applyCodePatch(staged.workspaceDir, input.candidatePatch,
|
|
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(
|
|
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(
|
|
822
|
-
return
|
|
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
|
|
990
|
+
note = describeReview(review);
|
|
947
991
|
} else {
|
|
948
|
-
note =
|
|
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((
|
|
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
|
|
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
|
-
|
|
1657
|
-
if (
|
|
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
|
|
1723
|
+
return paths.sort();
|
|
1663
1724
|
}
|
|
1664
|
-
function listWorkspace(
|
|
1725
|
+
function listWorkspace(paths, options = {}) {
|
|
1665
1726
|
const max = options.maxEntries ?? 1e3;
|
|
1666
1727
|
const prefix = options.prefix?.replace(/\/+$/, "");
|
|
1667
|
-
const scoped = prefix ?
|
|
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,
|
|
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(
|
|
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,
|
|
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
|
|
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,
|
|
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
|
-
...
|
|
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(
|
|
1980
|
+
function directoryPrefixes(paths) {
|
|
1920
1981
|
const prefixes = /* @__PURE__ */ new Set(["."]);
|
|
1921
|
-
for (const path of
|
|
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,
|
|
1928
|
-
const prefixes = directoryPrefixes(
|
|
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,
|
|
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
|
|
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
|
|
2129
|
-
if (!
|
|
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
|
|
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
|
|
2155
|
-
const allowed = await policy.list(policyContext(context, request, options, { paths
|
|
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(
|
|
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 <
|
|
2162
|
-
const hint = !prefix &&
|
|
2163
|
-
\u2026 ${
|
|
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
|
|
2182
|
-
const allowed = await policy.search(policyContext(context, request, options, { paths
|
|
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,
|
|
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
|
|
2207
|
-
const graphs = await workspaceGraphs(context.workspaceDir,
|
|
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
|
|
2272
|
-
if (
|
|
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,
|
|
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 ${
|
|
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
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
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-
|
|
3124
|
+
//# sourceMappingURL=chunk-Q4NL5XO3.js.map
|