@odla-ai/harness 0.7.1 → 0.8.0
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-K76I2TCQ.js → chunk-CR6RE3A2.js} +3 -3
- package/dist/{chunk-GYWQM76X.js → chunk-ISR434K7.js} +186 -58
- package/dist/chunk-ISR434K7.js.map +1 -0
- package/dist/{chunk-XWXRNPGY.js → chunk-NWNBSX56.js} +4 -4
- package/dist/{chunk-FVOMJKWK.js → chunk-Q7CKOT7T.js} +2 -2
- package/dist/{chunk-LNQNFGQC.js → chunk-RXNHCGWE.js} +1 -1
- package/dist/chunk-RXNHCGWE.js.map +1 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/code-runtime-cli.cjs +186 -58
- package/dist/code-runtime-cli.cjs.map +1 -1
- package/dist/code-runtime-cli.js +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/node.cjs +195 -67
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +5 -5
- 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-CK5EKmKm.d.cts → types-BNJikP5h.d.cts} +53 -5
- package/dist/{types-CK5EKmKm.d.ts → types-BNJikP5h.d.ts} +53 -5
- package/package.json +1 -1
- package/dist/chunk-GYWQM76X.js.map +0 -1
- package/dist/chunk-LNQNFGQC.js.map +0 -1
- /package/dist/{chunk-K76I2TCQ.js.map → chunk-CR6RE3A2.js.map} +0 -0
- /package/dist/{chunk-XWXRNPGY.js.map → chunk-NWNBSX56.js.map} +0 -0
- /package/dist/{chunk-FVOMJKWK.js.map → chunk-Q7CKOT7T.js.map} +0 -0
package/dist/node.cjs
CHANGED
|
@@ -323,8 +323,8 @@ async function runContainerAttempt(options) {
|
|
|
323
323
|
let stopped = false;
|
|
324
324
|
let exited = false;
|
|
325
325
|
child.stderr.setEncoding("utf8");
|
|
326
|
-
child.stderr.on("data", (
|
|
327
|
-
if (stderr.length < 64 * 1024) stderr +=
|
|
326
|
+
child.stderr.on("data", (text2) => {
|
|
327
|
+
if (stderr.length < 64 * 1024) stderr += text2.slice(0, 64 * 1024 - stderr.length);
|
|
328
328
|
});
|
|
329
329
|
const stop = (reason) => {
|
|
330
330
|
if (stopped || exited) return;
|
|
@@ -497,8 +497,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
497
497
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
498
498
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
499
499
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
500
|
-
const entries = inventory.flatMap((
|
|
501
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
500
|
+
const entries = inventory.flatMap((record5) => {
|
|
501
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record5);
|
|
502
502
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
503
503
|
});
|
|
504
504
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -583,12 +583,12 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
583
583
|
});
|
|
584
584
|
if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
|
|
585
585
|
if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
|
|
586
|
-
const
|
|
587
|
-
if (
|
|
586
|
+
const paths2 = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
|
|
587
|
+
if (paths2.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
588
588
|
const root = (0, import_node_path4.resolve)(sourceDir);
|
|
589
589
|
const files = [];
|
|
590
590
|
let bytes = 0;
|
|
591
|
-
for (const relativePath of
|
|
591
|
+
for (const relativePath of paths2) {
|
|
592
592
|
if (!allowedWorkspacePath(relativePath)) continue;
|
|
593
593
|
const source = (0, import_node_path4.resolve)(root, relativePath);
|
|
594
594
|
if (!source.startsWith(`${root}${import_node_path4.sep}`)) throw new TypeError("git file path escapes workspace");
|
|
@@ -796,8 +796,8 @@ async function runLeasedAttempt(lease, options) {
|
|
|
796
796
|
allowUnpinnedImage: options.allowUnpinnedImage,
|
|
797
797
|
workspaceAccess: options.workspaceAccess ?? (options.toolBroker ? "none" : "read-write"),
|
|
798
798
|
signal: controller.signal,
|
|
799
|
-
onStderr: async (
|
|
800
|
-
await append(options.control, lease, "agent.stderr", "agent", { text:
|
|
799
|
+
onStderr: async (text2) => {
|
|
800
|
+
await append(options.control, lease, "agent.stderr", "agent", { text: text2.slice(0, 64 * 1024) });
|
|
801
801
|
},
|
|
802
802
|
onMessage: async (message2) => {
|
|
803
803
|
if (message2.type === "event") {
|
|
@@ -1204,7 +1204,7 @@ function validateCodePatch(rawPatch, maxBytes) {
|
|
|
1204
1204
|
if (FORBIDDEN.test(patch2) || /(?:old|new)(?: file)? mode 120000/.test(patch2)) {
|
|
1205
1205
|
throw new TypeError("patch uses a forbidden binary, link, mode, rename, or copy operation");
|
|
1206
1206
|
}
|
|
1207
|
-
const
|
|
1207
|
+
const paths2 = [];
|
|
1208
1208
|
const lines = patch2.split("\n");
|
|
1209
1209
|
for (let index = 0; index < lines.length; index += 1) {
|
|
1210
1210
|
const line = lines[index];
|
|
@@ -1220,10 +1220,10 @@ function validateCodePatch(rawPatch, maxBytes) {
|
|
|
1220
1220
|
if (!validHeaderPath(oldPath, path, "a") || !validHeaderPath(newPath, path, "b")) {
|
|
1221
1221
|
throw new TypeError("patch file headers do not match the declared path");
|
|
1222
1222
|
}
|
|
1223
|
-
|
|
1223
|
+
paths2.push(path);
|
|
1224
1224
|
}
|
|
1225
|
-
if (!
|
|
1226
|
-
return
|
|
1225
|
+
if (!paths2.length || new Set(paths2).size !== paths2.length) throw new TypeError("patch has no diffs or repeats a path");
|
|
1226
|
+
return paths2;
|
|
1227
1227
|
}
|
|
1228
1228
|
function validHeaderPath(value, path, prefix) {
|
|
1229
1229
|
return value === "/dev/null" || value === `${prefix}/${path}`;
|
|
@@ -1248,11 +1248,11 @@ function describePatchFailure(patch2, detail) {
|
|
|
1248
1248
|
const hint = hunks.length > 0 && contextless ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
|
|
1249
1249
|
return `patch did not apply: ${detail}${hint}`;
|
|
1250
1250
|
}
|
|
1251
|
-
async function applyCodePatch(workspaceDir, rawPatch,
|
|
1251
|
+
async function applyCodePatch(workspaceDir, rawPatch, paths2) {
|
|
1252
1252
|
const patch2 = stripPatchEnvelope(rawPatch);
|
|
1253
1253
|
await gitApply(workspaceDir, patch2, true);
|
|
1254
1254
|
await gitApply(workspaceDir, patch2, false);
|
|
1255
|
-
for (const path of
|
|
1255
|
+
for (const path of paths2) {
|
|
1256
1256
|
try {
|
|
1257
1257
|
const info = await (0, import_promises6.lstat)(resolveCodePath(workspaceDir, path));
|
|
1258
1258
|
if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
|
|
@@ -1274,8 +1274,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
1274
1274
|
});
|
|
1275
1275
|
let stderr = "";
|
|
1276
1276
|
child.stderr.setEncoding("utf8");
|
|
1277
|
-
child.stderr.on("data", (
|
|
1278
|
-
if (stderr.length < 4e3) stderr +=
|
|
1277
|
+
child.stderr.on("data", (text2) => {
|
|
1278
|
+
if (stderr.length < 4e3) stderr += text2.slice(0, 4e3);
|
|
1279
1279
|
});
|
|
1280
1280
|
child.once("error", reject);
|
|
1281
1281
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr.trim().slice(0, 500)))));
|
|
@@ -1301,8 +1301,8 @@ async function restoreCodeWorkspaceCheckpoint(input) {
|
|
|
1301
1301
|
const workspace = await stageWorkspace(input.trustedBaseDir, input.stage);
|
|
1302
1302
|
try {
|
|
1303
1303
|
if (checkpoint.patch) {
|
|
1304
|
-
const
|
|
1305
|
-
await applyCodePatch(workspace.workspaceDir, checkpoint.patch,
|
|
1304
|
+
const paths2 = validateCodePatch(checkpoint.patch, 256 * 1024);
|
|
1305
|
+
await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths2);
|
|
1306
1306
|
}
|
|
1307
1307
|
return { workspace, checkpoint };
|
|
1308
1308
|
} catch (error) {
|
|
@@ -1471,8 +1471,8 @@ async function verifyCodeCandidate(input) {
|
|
|
1471
1471
|
try {
|
|
1472
1472
|
const baseDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
|
|
1473
1473
|
if (baseDigest !== input.trustedBaseDigest) throw new TypeError("trusted base does not match its registered digest");
|
|
1474
|
-
const
|
|
1475
|
-
await applyCodePatch(staged.workspaceDir, input.candidatePatch,
|
|
1474
|
+
const paths2 = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
|
|
1475
|
+
await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths2);
|
|
1476
1476
|
const sourceDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
|
|
1477
1477
|
const policyDigest = digestPolicy(policy);
|
|
1478
1478
|
const patchDigest = digestBytes(input.candidatePatch);
|
|
@@ -1481,7 +1481,7 @@ async function verifyCodeCandidate(input) {
|
|
|
1481
1481
|
trustedBaseDigest: input.trustedBaseDigest,
|
|
1482
1482
|
patchDigest
|
|
1483
1483
|
});
|
|
1484
|
-
const changedTests = changedTestPaths(
|
|
1484
|
+
const changedTests = changedTestPaths(paths2, policy);
|
|
1485
1485
|
if (changedTests.length > policy.maximumChangedTests) throw new TypeError("candidate changes too many test files");
|
|
1486
1486
|
const recipes = [];
|
|
1487
1487
|
const logs = [];
|
|
@@ -1548,8 +1548,8 @@ function validate(input) {
|
|
|
1548
1548
|
}
|
|
1549
1549
|
return result;
|
|
1550
1550
|
}
|
|
1551
|
-
function changedTestPaths(
|
|
1552
|
-
return
|
|
1551
|
+
function changedTestPaths(paths2, policy) {
|
|
1552
|
+
return paths2.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
|
|
1553
1553
|
}
|
|
1554
1554
|
function recipeReceipt(recipe2, result, artifacts) {
|
|
1555
1555
|
const status = result.timedOut ? "timed_out" : result.outputLimitExceeded ? "output_limited" : result.exitCode === 0 && artifacts.every((item) => item.status === "verified") ? "passed" : "failed";
|
|
@@ -2420,16 +2420,16 @@ function createCodePolicyGate(options) {
|
|
|
2420
2420
|
}
|
|
2421
2421
|
};
|
|
2422
2422
|
}
|
|
2423
|
-
function directoryPrefixes(
|
|
2423
|
+
function directoryPrefixes(paths2) {
|
|
2424
2424
|
const prefixes = /* @__PURE__ */ new Set(["."]);
|
|
2425
|
-
for (const path of
|
|
2425
|
+
for (const path of paths2) {
|
|
2426
2426
|
const parts = path.split("/");
|
|
2427
2427
|
for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
|
|
2428
2428
|
}
|
|
2429
2429
|
return [...prefixes].sort();
|
|
2430
2430
|
}
|
|
2431
|
-
async function safePrefix(base,
|
|
2432
|
-
const prefixes = directoryPrefixes(
|
|
2431
|
+
async function safePrefix(base, paths2, prefix) {
|
|
2432
|
+
const prefixes = directoryPrefixes(paths2);
|
|
2433
2433
|
const conversions = await conversionRegistry(
|
|
2434
2434
|
[await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
|
|
2435
2435
|
{ "code.prefixes.v1": prefixes }
|
|
@@ -2559,7 +2559,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
2559
2559
|
files(root) {
|
|
2560
2560
|
const existing = cache2.get(root);
|
|
2561
2561
|
if (existing) return existing;
|
|
2562
|
-
const pending = enumerate(root, limit).then((
|
|
2562
|
+
const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
|
|
2563
2563
|
cache2.set(root, pending);
|
|
2564
2564
|
void pending.catch(() => {
|
|
2565
2565
|
if (cache2.get(root) === pending) cache2.delete(root);
|
|
@@ -2572,7 +2572,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
2572
2572
|
};
|
|
2573
2573
|
}
|
|
2574
2574
|
async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
2575
|
-
const
|
|
2575
|
+
const paths2 = [];
|
|
2576
2576
|
const walk = async (directory) => {
|
|
2577
2577
|
for (const entry of await (0, import_promises9.readdir)(directory, { withFileTypes: true })) {
|
|
2578
2578
|
if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
@@ -2586,26 +2586,26 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
|
2586
2586
|
} catch {
|
|
2587
2587
|
continue;
|
|
2588
2588
|
}
|
|
2589
|
-
|
|
2590
|
-
if (
|
|
2589
|
+
paths2.push(path);
|
|
2590
|
+
if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
|
|
2591
2591
|
}
|
|
2592
2592
|
}
|
|
2593
2593
|
};
|
|
2594
2594
|
await walk((0, import_node_path9.resolve)(root));
|
|
2595
|
-
return
|
|
2595
|
+
return paths2.sort();
|
|
2596
2596
|
}
|
|
2597
|
-
function listWorkspace(
|
|
2597
|
+
function listWorkspace(paths2, options = {}) {
|
|
2598
2598
|
const max = options.maxEntries ?? 1e3;
|
|
2599
2599
|
const prefix = options.prefix?.replace(/\/+$/, "");
|
|
2600
|
-
const scoped = prefix ?
|
|
2600
|
+
const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
|
|
2601
2601
|
return scoped.slice(0, max);
|
|
2602
2602
|
}
|
|
2603
|
-
async function searchWorkspace(root,
|
|
2603
|
+
async function searchWorkspace(root, paths2, options) {
|
|
2604
2604
|
options.signal?.throwIfAborted();
|
|
2605
2605
|
if (!options.query) throw new TypeError("search query must be a non-empty string");
|
|
2606
2606
|
const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
2607
2607
|
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
|
|
2608
|
-
const scoped = listWorkspace(
|
|
2608
|
+
const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
|
|
2609
2609
|
if (scoped.length === 0) return [];
|
|
2610
2610
|
try {
|
|
2611
2611
|
return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
@@ -2615,11 +2615,11 @@ async function searchWorkspace(root, paths, options) {
|
|
|
2615
2615
|
}
|
|
2616
2616
|
}
|
|
2617
2617
|
var MAX_NATIVE_ARG_BYTES = 96 * 1024;
|
|
2618
|
-
async function nativeSearch(root,
|
|
2618
|
+
async function nativeSearch(root, paths2, options) {
|
|
2619
2619
|
const batches = [];
|
|
2620
2620
|
let batch = [];
|
|
2621
2621
|
let bytes = 0;
|
|
2622
|
-
for (const path of
|
|
2622
|
+
for (const path of paths2) {
|
|
2623
2623
|
const size = Buffer.byteLength(path) + 1;
|
|
2624
2624
|
if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
|
|
2625
2625
|
batches.push(batch);
|
|
@@ -2638,7 +2638,7 @@ async function nativeSearch(root, paths, options) {
|
|
|
2638
2638
|
}
|
|
2639
2639
|
return matches;
|
|
2640
2640
|
}
|
|
2641
|
-
function nativeSearchBatch(root,
|
|
2641
|
+
function nativeSearchBatch(root, paths2, options, remaining) {
|
|
2642
2642
|
return new Promise((resolveMatches, reject) => {
|
|
2643
2643
|
const args = [
|
|
2644
2644
|
"--fixed-strings",
|
|
@@ -2651,7 +2651,7 @@ function nativeSearchBatch(root, paths, options, remaining) {
|
|
|
2651
2651
|
options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
|
|
2652
2652
|
"--",
|
|
2653
2653
|
options.query,
|
|
2654
|
-
...
|
|
2654
|
+
...paths2
|
|
2655
2655
|
];
|
|
2656
2656
|
const child = (0, import_node_child_process6.spawn)("rg", args, {
|
|
2657
2657
|
cwd: root,
|
|
@@ -2728,7 +2728,7 @@ var import_node_path10 = require("path");
|
|
|
2728
2728
|
var import_graph = require("@odla-ai/graph");
|
|
2729
2729
|
var import_code4 = require("@odla-ai/graph/code");
|
|
2730
2730
|
var cache = /* @__PURE__ */ new Map();
|
|
2731
|
-
function workspaceGraphs(workspaceDir,
|
|
2731
|
+
function workspaceGraphs(workspaceDir, paths2) {
|
|
2732
2732
|
const existing = cache.get(workspaceDir);
|
|
2733
2733
|
if (existing) return existing;
|
|
2734
2734
|
const read2 = (path) => (0, import_promises10.readFile)((0, import_node_path10.join)(workspaceDir, path), "utf8");
|
|
@@ -2736,7 +2736,7 @@ function workspaceGraphs(workspaceDir, paths) {
|
|
|
2736
2736
|
// No knownTables: a staged workspace may not carry migrations, and a filter
|
|
2737
2737
|
// that silently drops every table is worse than an unfiltered one. Callers
|
|
2738
2738
|
// with ground truth should build the graph themselves.
|
|
2739
|
-
graph: await (0, import_code4.buildCodeGraph)({ paths, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
|
|
2739
|
+
graph: await (0, import_code4.buildCodeGraph)({ paths: paths2, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
|
|
2740
2740
|
}))();
|
|
2741
2741
|
cache.set(workspaceDir, built);
|
|
2742
2742
|
return built;
|
|
@@ -2798,11 +2798,11 @@ async function read(context, request, options, policy, registry) {
|
|
|
2798
2798
|
if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
|
|
2799
2799
|
throw new TypeError("requested line range exceeds its bound");
|
|
2800
2800
|
}
|
|
2801
|
-
const
|
|
2802
|
-
if (!
|
|
2801
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2802
|
+
if (!paths2.includes(path)) {
|
|
2803
2803
|
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.`);
|
|
2804
2804
|
}
|
|
2805
|
-
const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
|
|
2805
|
+
const allowed = await policy.read(policyContext(context, request, options, { paths: paths2, path, startLine, endLine }));
|
|
2806
2806
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2807
2807
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
2808
2808
|
const info = await (0, import_promises11.stat)(target);
|
|
@@ -2824,16 +2824,16 @@ async function list(context, request, options, policy, registry) {
|
|
|
2824
2824
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
2825
2825
|
const maxEntries = optionalInteger(request.input.maxEntries) ?? 1e3;
|
|
2826
2826
|
if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
|
|
2827
|
-
const
|
|
2828
|
-
const allowed = await policy.list(policyContext(context, request, options, { paths, ...prefix ? { prefix } : {} }));
|
|
2827
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2828
|
+
const allowed = await policy.list(policyContext(context, request, options, { paths: paths2, ...prefix ? { prefix } : {} }));
|
|
2829
2829
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2830
|
-
const entries = listWorkspace(
|
|
2830
|
+
const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
|
|
2831
2831
|
if (!entries.length) {
|
|
2832
2832
|
return response(request, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
|
|
2833
2833
|
}
|
|
2834
|
-
const truncated = entries.length <
|
|
2835
|
-
const hint = !prefix &&
|
|
2836
|
-
\u2026 ${
|
|
2834
|
+
const truncated = entries.length < paths2.length && entries.length === maxEntries;
|
|
2835
|
+
const hint = !prefix && paths2.length > 500 ? `
|
|
2836
|
+
\u2026 ${paths2.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
|
|
2837
2837
|
return response(
|
|
2838
2838
|
request,
|
|
2839
2839
|
true,
|
|
@@ -2851,10 +2851,10 @@ async function search(context, request, options, policy, registry) {
|
|
|
2851
2851
|
const maxResults = optionalInteger(request.input.maxResults) ?? 100;
|
|
2852
2852
|
if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
|
|
2853
2853
|
const caseSensitive = request.input.caseSensitive === void 0 ? true : request.input.caseSensitive === true;
|
|
2854
|
-
const
|
|
2855
|
-
const allowed = await policy.search(policyContext(context, request, options, { paths, query, ...prefix ? { prefix } : {} }));
|
|
2854
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2855
|
+
const allowed = await policy.search(policyContext(context, request, options, { paths: paths2, query, ...prefix ? { prefix } : {} }));
|
|
2856
2856
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2857
|
-
const matches = await searchWorkspace(context.workspaceDir,
|
|
2857
|
+
const matches = await searchWorkspace(context.workspaceDir, paths2, {
|
|
2858
2858
|
query,
|
|
2859
2859
|
maxResults,
|
|
2860
2860
|
caseSensitive,
|
|
@@ -2876,8 +2876,8 @@ async function graphQuery(context, request, options, policy, registry) {
|
|
|
2876
2876
|
selector: query
|
|
2877
2877
|
}));
|
|
2878
2878
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2879
|
-
const
|
|
2880
|
-
const graphs = await workspaceGraphs(context.workspaceDir,
|
|
2879
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2880
|
+
const graphs = await workspaceGraphs(context.workspaceDir, paths2);
|
|
2881
2881
|
if (request.tool === "sandbox.overview") {
|
|
2882
2882
|
return response(request, true, renderOverview(graphs, query || void 0));
|
|
2883
2883
|
}
|
|
@@ -2941,16 +2941,16 @@ function toolFailureMessage(reason) {
|
|
|
2941
2941
|
async function patch(context, request, options, policy, registry) {
|
|
2942
2942
|
exactKeys(request.input, ["patch"]);
|
|
2943
2943
|
const value = stringField(request.input, "patch");
|
|
2944
|
-
const
|
|
2945
|
-
if (
|
|
2944
|
+
const paths2 = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
|
|
2945
|
+
if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
2946
2946
|
throw new TypeError("patch targets a read-only reference source");
|
|
2947
2947
|
}
|
|
2948
2948
|
const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
|
|
2949
2949
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2950
|
-
await applyCodePatch(context.workspaceDir, value,
|
|
2950
|
+
await applyCodePatch(context.workspaceDir, value, paths2);
|
|
2951
2951
|
registry.invalidate(context.workspaceDir);
|
|
2952
2952
|
forgetWorkspaceGraphs(context.workspaceDir);
|
|
2953
|
-
return response(request, true, `Applied patch to ${
|
|
2953
|
+
return response(request, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
|
|
2954
2954
|
}
|
|
2955
2955
|
async function recipe(context, request, options, recipes, policy) {
|
|
2956
2956
|
exactKeys(request.input, ["recipeId"]);
|
|
@@ -3334,12 +3334,129 @@ var import_node_crypto4 = require("crypto");
|
|
|
3334
3334
|
async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
3335
3335
|
const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
|
|
3336
3336
|
refs.push(eventId);
|
|
3337
|
-
const
|
|
3337
|
+
const attributed = { ...event, interactionId: command.commandId };
|
|
3338
|
+
const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
|
|
3338
3339
|
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
3339
3340
|
}
|
|
3340
3341
|
var digestRuntimeValue = (value) => `sha256:${(0, import_node_crypto4.createHash)("sha256").update(value).digest("hex")}`;
|
|
3341
3342
|
var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
|
|
3342
3343
|
|
|
3344
|
+
// src/code-tool-presentation.ts
|
|
3345
|
+
var text = (value, maximum) => {
|
|
3346
|
+
if (typeof value !== "string") return void 0;
|
|
3347
|
+
const bounded = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
|
|
3348
|
+
return bounded ? bounded.slice(0, maximum) : void 0;
|
|
3349
|
+
};
|
|
3350
|
+
var integer2 = (value) => Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : void 0;
|
|
3351
|
+
var record4 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3352
|
+
var excerpt = (value, tail = false) => {
|
|
3353
|
+
if (typeof value !== "string") return void 0;
|
|
3354
|
+
const safe = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
|
|
3355
|
+
const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
|
|
3356
|
+
if (!source) return void 0;
|
|
3357
|
+
const lines = source.split("\n").filter((line) => line.trim()).map((line) => line.slice(0, 240));
|
|
3358
|
+
const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
|
|
3359
|
+
return text(selected.join("\n"), 2400);
|
|
3360
|
+
};
|
|
3361
|
+
var paths = (value) => {
|
|
3362
|
+
if (!Array.isArray(value)) return void 0;
|
|
3363
|
+
const items = value.flatMap((item) => {
|
|
3364
|
+
const path = text(item, 1024);
|
|
3365
|
+
return path ? [path] : [];
|
|
3366
|
+
}).slice(0, 12);
|
|
3367
|
+
return items.length ? items : void 0;
|
|
3368
|
+
};
|
|
3369
|
+
function patchStats(value) {
|
|
3370
|
+
if (typeof value !== "string") return {};
|
|
3371
|
+
let additions = 0;
|
|
3372
|
+
let deletions = 0;
|
|
3373
|
+
for (const line of value.slice(0, 262144).split("\n")) {
|
|
3374
|
+
if (line.startsWith("+++") || line.startsWith("---")) continue;
|
|
3375
|
+
if (line.startsWith("+")) additions += 1;
|
|
3376
|
+
else if (line.startsWith("-")) deletions += 1;
|
|
3377
|
+
}
|
|
3378
|
+
return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
|
|
3379
|
+
}
|
|
3380
|
+
function searchResults(value) {
|
|
3381
|
+
if (typeof value !== "string") return void 0;
|
|
3382
|
+
const results = value.split("\n").flatMap((line) => {
|
|
3383
|
+
const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line);
|
|
3384
|
+
if (!match) return [];
|
|
3385
|
+
const lineNumber = Number(match[2]);
|
|
3386
|
+
const itemText = text(match[3], 240);
|
|
3387
|
+
if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
|
|
3388
|
+
return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
|
|
3389
|
+
}).slice(0, 5);
|
|
3390
|
+
return results.length ? results : void 0;
|
|
3391
|
+
}
|
|
3392
|
+
function codeToolRequestPresentation(request) {
|
|
3393
|
+
const input = request.input;
|
|
3394
|
+
if (request.tool === "sandbox.read") {
|
|
3395
|
+
const path = text(input.path, 1024);
|
|
3396
|
+
if (!path) return void 0;
|
|
3397
|
+
const startLine = integer2(input.startLine);
|
|
3398
|
+
const endLine = integer2(input.endLine);
|
|
3399
|
+
return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
|
|
3400
|
+
}
|
|
3401
|
+
if (request.tool === "sandbox.list") {
|
|
3402
|
+
const scope = text(input.prefix, 1024);
|
|
3403
|
+
return { kind: "list", ...scope ? { scope } : {} };
|
|
3404
|
+
}
|
|
3405
|
+
if (request.tool === "sandbox.search" || request.tool === "sandbox.overview" || request.tool === "sandbox.where_is" || request.tool === "sandbox.who_imports" || request.tool === "sandbox.who_touches") {
|
|
3406
|
+
const query = text(input.query, 512);
|
|
3407
|
+
const scope = request.tool === "sandbox.search" ? text(input.prefix, 1024) : void 0;
|
|
3408
|
+
if (request.tool === "sandbox.search" && !query) return void 0;
|
|
3409
|
+
return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
|
|
3410
|
+
}
|
|
3411
|
+
if (request.tool === "sandbox.apply_patch") {
|
|
3412
|
+
return { kind: "patch", ...patchStats(input.patch) };
|
|
3413
|
+
}
|
|
3414
|
+
const recipeId = text(input.recipeId, 120);
|
|
3415
|
+
return recipeId ? { kind: "recipe", recipeId } : void 0;
|
|
3416
|
+
}
|
|
3417
|
+
function codeToolResultPresentation(request, response2) {
|
|
3418
|
+
const started = codeToolRequestPresentation(request);
|
|
3419
|
+
if (!started || !response2.ok) return started;
|
|
3420
|
+
const details = record4(response2.details);
|
|
3421
|
+
if (started.kind === "read") {
|
|
3422
|
+
return {
|
|
3423
|
+
...started,
|
|
3424
|
+
...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
|
|
3425
|
+
...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
|
|
3426
|
+
...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
|
|
3427
|
+
};
|
|
3428
|
+
}
|
|
3429
|
+
if (started.kind === "list") {
|
|
3430
|
+
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);
|
|
3431
|
+
return {
|
|
3432
|
+
...started,
|
|
3433
|
+
...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
|
|
3434
|
+
...listed.length ? { paths: listed } : {}
|
|
3435
|
+
};
|
|
3436
|
+
}
|
|
3437
|
+
if (started.kind === "query") {
|
|
3438
|
+
const results = request.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
|
|
3439
|
+
const resultExcerpt = request.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
|
|
3440
|
+
return {
|
|
3441
|
+
...started,
|
|
3442
|
+
...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
|
|
3443
|
+
...results ? { results } : {},
|
|
3444
|
+
...resultExcerpt ? { excerpt: resultExcerpt } : {}
|
|
3445
|
+
};
|
|
3446
|
+
}
|
|
3447
|
+
if (started.kind === "patch") {
|
|
3448
|
+
return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
|
|
3449
|
+
}
|
|
3450
|
+
const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
|
|
3451
|
+
return {
|
|
3452
|
+
...started,
|
|
3453
|
+
...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
|
|
3454
|
+
...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
|
|
3455
|
+
...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
|
|
3456
|
+
...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
|
|
3457
|
+
};
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3343
3460
|
// src/code-runtime-engine.ts
|
|
3344
3461
|
var TheseusRuntimeEngine = class {
|
|
3345
3462
|
constructor(options) {
|
|
@@ -3574,18 +3691,29 @@ var TheseusRuntimeEngine = class {
|
|
|
3574
3691
|
return {
|
|
3575
3692
|
execute: async (context, request) => {
|
|
3576
3693
|
const startedAt = Date.now();
|
|
3694
|
+
const operationId = digestRuntimeValue(`${command.commandId}:${request.requestId}`);
|
|
3695
|
+
const startedPresentation = codeToolRequestPresentation(request);
|
|
3577
3696
|
await this.#event(
|
|
3578
3697
|
command,
|
|
3579
|
-
{
|
|
3698
|
+
{
|
|
3699
|
+
type: "tool",
|
|
3700
|
+
phase: "started",
|
|
3701
|
+
tool: request.tool,
|
|
3702
|
+
operationId,
|
|
3703
|
+
...startedPresentation ? { presentation: startedPresentation } : {}
|
|
3704
|
+
},
|
|
3580
3705
|
active.conversationRefs
|
|
3581
3706
|
).catch(() => void 0);
|
|
3582
3707
|
const response2 = await broker.execute(context, request);
|
|
3708
|
+
const completedPresentation = codeToolResultPresentation(request, response2);
|
|
3583
3709
|
await this.#event(command, {
|
|
3584
3710
|
type: "tool",
|
|
3585
3711
|
phase: "completed",
|
|
3586
3712
|
tool: request.tool,
|
|
3587
3713
|
ok: response2.ok,
|
|
3588
|
-
durationMs: Date.now() - startedAt
|
|
3714
|
+
durationMs: Date.now() - startedAt,
|
|
3715
|
+
operationId,
|
|
3716
|
+
...completedPresentation ? { presentation: completedPresentation } : {}
|
|
3589
3717
|
}, active.conversationRefs).catch(() => void 0);
|
|
3590
3718
|
return response2;
|
|
3591
3719
|
}
|
|
@@ -3830,9 +3958,9 @@ var MEASURED_PREMIUM = Object.freeze({
|
|
|
3830
3958
|
decomposePerSubGoal: 0.23
|
|
3831
3959
|
});
|
|
3832
3960
|
var actionable = (feedback) => {
|
|
3833
|
-
const
|
|
3834
|
-
if (
|
|
3835
|
-
return /\b(expected|assert|error|fail(?:ed|ure)?|exit|line \d+|\.[a-z]{1,4}:\d+)\b/i.test(
|
|
3961
|
+
const text2 = feedback.trim();
|
|
3962
|
+
if (text2.length < 12) return false;
|
|
3963
|
+
return /\b(expected|assert|error|fail(?:ed|ure)?|exit|line \d+|\.[a-z]{1,4}:\d+)\b/i.test(text2) || /\.(js|ts|tsx|jsx|mjs|cjs|py|go|rs|java|rb)\b/i.test(text2);
|
|
3836
3964
|
};
|
|
3837
3965
|
function chooseStrategy(signals = {}) {
|
|
3838
3966
|
const width = Math.max(1, signals.width ?? 3);
|