@odla-ai/harness 0.7.1 → 0.8.1
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-IWVGSWY6.js} +202 -59
- package/dist/chunk-IWVGSWY6.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 +202 -59
- 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 +211 -68
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +15 -1
- package/dist/node.d.ts +15 -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";
|
|
@@ -2208,6 +2208,7 @@ async function runCodeAgentAttempt(options) {
|
|
|
2208
2208
|
model: "brokered",
|
|
2209
2209
|
surface,
|
|
2210
2210
|
...options.recipeIds ? { recipeIds: options.recipeIds } : {},
|
|
2211
|
+
...options.extraSkills ? { extraSkills: options.extraSkills } : {},
|
|
2211
2212
|
...options.maxSteps === void 0 ? {} : { maxSteps: options.maxSteps },
|
|
2212
2213
|
...options.budget ? { budget: options.budget } : {},
|
|
2213
2214
|
...options.signal ? { signal: options.signal } : {},
|
|
@@ -2239,6 +2240,18 @@ Finish with a concise, non-empty answer to the owner. Do not call tools or promi
|
|
|
2239
2240
|
}
|
|
2240
2241
|
}
|
|
2241
2242
|
|
|
2243
|
+
// src/code-runtime-session-skills.ts
|
|
2244
|
+
async function sessionSkillsFor(options, command) {
|
|
2245
|
+
try {
|
|
2246
|
+
return await options.sessionSkills?.(command) ?? [];
|
|
2247
|
+
} catch (cause) {
|
|
2248
|
+
options.onDiagnostic?.(
|
|
2249
|
+
`session skills unavailable, continuing with code tools only: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
2250
|
+
);
|
|
2251
|
+
return [];
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2242
2255
|
// src/code-runtime-inference.ts
|
|
2243
2256
|
async function handleCodeRuntimeInference(input) {
|
|
2244
2257
|
const { command, request, state } = input;
|
|
@@ -2420,16 +2433,16 @@ function createCodePolicyGate(options) {
|
|
|
2420
2433
|
}
|
|
2421
2434
|
};
|
|
2422
2435
|
}
|
|
2423
|
-
function directoryPrefixes(
|
|
2436
|
+
function directoryPrefixes(paths2) {
|
|
2424
2437
|
const prefixes = /* @__PURE__ */ new Set(["."]);
|
|
2425
|
-
for (const path of
|
|
2438
|
+
for (const path of paths2) {
|
|
2426
2439
|
const parts = path.split("/");
|
|
2427
2440
|
for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
|
|
2428
2441
|
}
|
|
2429
2442
|
return [...prefixes].sort();
|
|
2430
2443
|
}
|
|
2431
|
-
async function safePrefix(base,
|
|
2432
|
-
const prefixes = directoryPrefixes(
|
|
2444
|
+
async function safePrefix(base, paths2, prefix) {
|
|
2445
|
+
const prefixes = directoryPrefixes(paths2);
|
|
2433
2446
|
const conversions = await conversionRegistry(
|
|
2434
2447
|
[await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
|
|
2435
2448
|
{ "code.prefixes.v1": prefixes }
|
|
@@ -2559,7 +2572,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
2559
2572
|
files(root) {
|
|
2560
2573
|
const existing = cache2.get(root);
|
|
2561
2574
|
if (existing) return existing;
|
|
2562
|
-
const pending = enumerate(root, limit).then((
|
|
2575
|
+
const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
|
|
2563
2576
|
cache2.set(root, pending);
|
|
2564
2577
|
void pending.catch(() => {
|
|
2565
2578
|
if (cache2.get(root) === pending) cache2.delete(root);
|
|
@@ -2572,7 +2585,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
2572
2585
|
};
|
|
2573
2586
|
}
|
|
2574
2587
|
async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
2575
|
-
const
|
|
2588
|
+
const paths2 = [];
|
|
2576
2589
|
const walk = async (directory) => {
|
|
2577
2590
|
for (const entry of await (0, import_promises9.readdir)(directory, { withFileTypes: true })) {
|
|
2578
2591
|
if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
@@ -2586,26 +2599,26 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
|
2586
2599
|
} catch {
|
|
2587
2600
|
continue;
|
|
2588
2601
|
}
|
|
2589
|
-
|
|
2590
|
-
if (
|
|
2602
|
+
paths2.push(path);
|
|
2603
|
+
if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
|
|
2591
2604
|
}
|
|
2592
2605
|
}
|
|
2593
2606
|
};
|
|
2594
2607
|
await walk((0, import_node_path9.resolve)(root));
|
|
2595
|
-
return
|
|
2608
|
+
return paths2.sort();
|
|
2596
2609
|
}
|
|
2597
|
-
function listWorkspace(
|
|
2610
|
+
function listWorkspace(paths2, options = {}) {
|
|
2598
2611
|
const max = options.maxEntries ?? 1e3;
|
|
2599
2612
|
const prefix = options.prefix?.replace(/\/+$/, "");
|
|
2600
|
-
const scoped = prefix ?
|
|
2613
|
+
const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
|
|
2601
2614
|
return scoped.slice(0, max);
|
|
2602
2615
|
}
|
|
2603
|
-
async function searchWorkspace(root,
|
|
2616
|
+
async function searchWorkspace(root, paths2, options) {
|
|
2604
2617
|
options.signal?.throwIfAborted();
|
|
2605
2618
|
if (!options.query) throw new TypeError("search query must be a non-empty string");
|
|
2606
2619
|
const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
2607
2620
|
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
|
|
2608
|
-
const scoped = listWorkspace(
|
|
2621
|
+
const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
|
|
2609
2622
|
if (scoped.length === 0) return [];
|
|
2610
2623
|
try {
|
|
2611
2624
|
return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
@@ -2615,11 +2628,11 @@ async function searchWorkspace(root, paths, options) {
|
|
|
2615
2628
|
}
|
|
2616
2629
|
}
|
|
2617
2630
|
var MAX_NATIVE_ARG_BYTES = 96 * 1024;
|
|
2618
|
-
async function nativeSearch(root,
|
|
2631
|
+
async function nativeSearch(root, paths2, options) {
|
|
2619
2632
|
const batches = [];
|
|
2620
2633
|
let batch = [];
|
|
2621
2634
|
let bytes = 0;
|
|
2622
|
-
for (const path of
|
|
2635
|
+
for (const path of paths2) {
|
|
2623
2636
|
const size = Buffer.byteLength(path) + 1;
|
|
2624
2637
|
if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
|
|
2625
2638
|
batches.push(batch);
|
|
@@ -2638,7 +2651,7 @@ async function nativeSearch(root, paths, options) {
|
|
|
2638
2651
|
}
|
|
2639
2652
|
return matches;
|
|
2640
2653
|
}
|
|
2641
|
-
function nativeSearchBatch(root,
|
|
2654
|
+
function nativeSearchBatch(root, paths2, options, remaining) {
|
|
2642
2655
|
return new Promise((resolveMatches, reject) => {
|
|
2643
2656
|
const args = [
|
|
2644
2657
|
"--fixed-strings",
|
|
@@ -2651,7 +2664,7 @@ function nativeSearchBatch(root, paths, options, remaining) {
|
|
|
2651
2664
|
options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
|
|
2652
2665
|
"--",
|
|
2653
2666
|
options.query,
|
|
2654
|
-
...
|
|
2667
|
+
...paths2
|
|
2655
2668
|
];
|
|
2656
2669
|
const child = (0, import_node_child_process6.spawn)("rg", args, {
|
|
2657
2670
|
cwd: root,
|
|
@@ -2728,7 +2741,7 @@ var import_node_path10 = require("path");
|
|
|
2728
2741
|
var import_graph = require("@odla-ai/graph");
|
|
2729
2742
|
var import_code4 = require("@odla-ai/graph/code");
|
|
2730
2743
|
var cache = /* @__PURE__ */ new Map();
|
|
2731
|
-
function workspaceGraphs(workspaceDir,
|
|
2744
|
+
function workspaceGraphs(workspaceDir, paths2) {
|
|
2732
2745
|
const existing = cache.get(workspaceDir);
|
|
2733
2746
|
if (existing) return existing;
|
|
2734
2747
|
const read2 = (path) => (0, import_promises10.readFile)((0, import_node_path10.join)(workspaceDir, path), "utf8");
|
|
@@ -2736,7 +2749,7 @@ function workspaceGraphs(workspaceDir, paths) {
|
|
|
2736
2749
|
// No knownTables: a staged workspace may not carry migrations, and a filter
|
|
2737
2750
|
// that silently drops every table is worse than an unfiltered one. Callers
|
|
2738
2751
|
// with ground truth should build the graph themselves.
|
|
2739
|
-
graph: await (0, import_code4.buildCodeGraph)({ paths, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
|
|
2752
|
+
graph: await (0, import_code4.buildCodeGraph)({ paths: paths2, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
|
|
2740
2753
|
}))();
|
|
2741
2754
|
cache.set(workspaceDir, built);
|
|
2742
2755
|
return built;
|
|
@@ -2798,11 +2811,11 @@ async function read(context, request, options, policy, registry) {
|
|
|
2798
2811
|
if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
|
|
2799
2812
|
throw new TypeError("requested line range exceeds its bound");
|
|
2800
2813
|
}
|
|
2801
|
-
const
|
|
2802
|
-
if (!
|
|
2814
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2815
|
+
if (!paths2.includes(path)) {
|
|
2803
2816
|
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
2817
|
}
|
|
2805
|
-
const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
|
|
2818
|
+
const allowed = await policy.read(policyContext(context, request, options, { paths: paths2, path, startLine, endLine }));
|
|
2806
2819
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2807
2820
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
2808
2821
|
const info = await (0, import_promises11.stat)(target);
|
|
@@ -2824,16 +2837,16 @@ async function list(context, request, options, policy, registry) {
|
|
|
2824
2837
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
2825
2838
|
const maxEntries = optionalInteger(request.input.maxEntries) ?? 1e3;
|
|
2826
2839
|
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 } : {} }));
|
|
2840
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2841
|
+
const allowed = await policy.list(policyContext(context, request, options, { paths: paths2, ...prefix ? { prefix } : {} }));
|
|
2829
2842
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2830
|
-
const entries = listWorkspace(
|
|
2843
|
+
const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
|
|
2831
2844
|
if (!entries.length) {
|
|
2832
2845
|
return response(request, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
|
|
2833
2846
|
}
|
|
2834
|
-
const truncated = entries.length <
|
|
2835
|
-
const hint = !prefix &&
|
|
2836
|
-
\u2026 ${
|
|
2847
|
+
const truncated = entries.length < paths2.length && entries.length === maxEntries;
|
|
2848
|
+
const hint = !prefix && paths2.length > 500 ? `
|
|
2849
|
+
\u2026 ${paths2.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
|
|
2837
2850
|
return response(
|
|
2838
2851
|
request,
|
|
2839
2852
|
true,
|
|
@@ -2851,10 +2864,10 @@ async function search(context, request, options, policy, registry) {
|
|
|
2851
2864
|
const maxResults = optionalInteger(request.input.maxResults) ?? 100;
|
|
2852
2865
|
if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
|
|
2853
2866
|
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 } : {} }));
|
|
2867
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2868
|
+
const allowed = await policy.search(policyContext(context, request, options, { paths: paths2, query, ...prefix ? { prefix } : {} }));
|
|
2856
2869
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2857
|
-
const matches = await searchWorkspace(context.workspaceDir,
|
|
2870
|
+
const matches = await searchWorkspace(context.workspaceDir, paths2, {
|
|
2858
2871
|
query,
|
|
2859
2872
|
maxResults,
|
|
2860
2873
|
caseSensitive,
|
|
@@ -2876,8 +2889,8 @@ async function graphQuery(context, request, options, policy, registry) {
|
|
|
2876
2889
|
selector: query
|
|
2877
2890
|
}));
|
|
2878
2891
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2879
|
-
const
|
|
2880
|
-
const graphs = await workspaceGraphs(context.workspaceDir,
|
|
2892
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2893
|
+
const graphs = await workspaceGraphs(context.workspaceDir, paths2);
|
|
2881
2894
|
if (request.tool === "sandbox.overview") {
|
|
2882
2895
|
return response(request, true, renderOverview(graphs, query || void 0));
|
|
2883
2896
|
}
|
|
@@ -2941,16 +2954,16 @@ function toolFailureMessage(reason) {
|
|
|
2941
2954
|
async function patch(context, request, options, policy, registry) {
|
|
2942
2955
|
exactKeys(request.input, ["patch"]);
|
|
2943
2956
|
const value = stringField(request.input, "patch");
|
|
2944
|
-
const
|
|
2945
|
-
if (
|
|
2957
|
+
const paths2 = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
|
|
2958
|
+
if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
2946
2959
|
throw new TypeError("patch targets a read-only reference source");
|
|
2947
2960
|
}
|
|
2948
2961
|
const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
|
|
2949
2962
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2950
|
-
await applyCodePatch(context.workspaceDir, value,
|
|
2963
|
+
await applyCodePatch(context.workspaceDir, value, paths2);
|
|
2951
2964
|
registry.invalidate(context.workspaceDir);
|
|
2952
2965
|
forgetWorkspaceGraphs(context.workspaceDir);
|
|
2953
|
-
return response(request, true, `Applied patch to ${
|
|
2966
|
+
return response(request, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
|
|
2954
2967
|
}
|
|
2955
2968
|
async function recipe(context, request, options, recipes, policy) {
|
|
2956
2969
|
exactKeys(request.input, ["recipeId"]);
|
|
@@ -3334,12 +3347,129 @@ var import_node_crypto4 = require("crypto");
|
|
|
3334
3347
|
async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
3335
3348
|
const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
|
|
3336
3349
|
refs.push(eventId);
|
|
3337
|
-
const
|
|
3350
|
+
const attributed = { ...event, interactionId: command.commandId };
|
|
3351
|
+
const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
|
|
3338
3352
|
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
3339
3353
|
}
|
|
3340
3354
|
var digestRuntimeValue = (value) => `sha256:${(0, import_node_crypto4.createHash)("sha256").update(value).digest("hex")}`;
|
|
3341
3355
|
var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
|
|
3342
3356
|
|
|
3357
|
+
// src/code-tool-presentation.ts
|
|
3358
|
+
var text = (value, maximum) => {
|
|
3359
|
+
if (typeof value !== "string") return void 0;
|
|
3360
|
+
const bounded = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
|
|
3361
|
+
return bounded ? bounded.slice(0, maximum) : void 0;
|
|
3362
|
+
};
|
|
3363
|
+
var integer2 = (value) => Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : void 0;
|
|
3364
|
+
var record4 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3365
|
+
var excerpt = (value, tail = false) => {
|
|
3366
|
+
if (typeof value !== "string") return void 0;
|
|
3367
|
+
const safe = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
|
|
3368
|
+
const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
|
|
3369
|
+
if (!source) return void 0;
|
|
3370
|
+
const lines = source.split("\n").filter((line) => line.trim()).map((line) => line.slice(0, 240));
|
|
3371
|
+
const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
|
|
3372
|
+
return text(selected.join("\n"), 2400);
|
|
3373
|
+
};
|
|
3374
|
+
var paths = (value) => {
|
|
3375
|
+
if (!Array.isArray(value)) return void 0;
|
|
3376
|
+
const items = value.flatMap((item) => {
|
|
3377
|
+
const path = text(item, 1024);
|
|
3378
|
+
return path ? [path] : [];
|
|
3379
|
+
}).slice(0, 12);
|
|
3380
|
+
return items.length ? items : void 0;
|
|
3381
|
+
};
|
|
3382
|
+
function patchStats(value) {
|
|
3383
|
+
if (typeof value !== "string") return {};
|
|
3384
|
+
let additions = 0;
|
|
3385
|
+
let deletions = 0;
|
|
3386
|
+
for (const line of value.slice(0, 262144).split("\n")) {
|
|
3387
|
+
if (line.startsWith("+++") || line.startsWith("---")) continue;
|
|
3388
|
+
if (line.startsWith("+")) additions += 1;
|
|
3389
|
+
else if (line.startsWith("-")) deletions += 1;
|
|
3390
|
+
}
|
|
3391
|
+
return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
|
|
3392
|
+
}
|
|
3393
|
+
function searchResults(value) {
|
|
3394
|
+
if (typeof value !== "string") return void 0;
|
|
3395
|
+
const results = value.split("\n").flatMap((line) => {
|
|
3396
|
+
const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line);
|
|
3397
|
+
if (!match) return [];
|
|
3398
|
+
const lineNumber = Number(match[2]);
|
|
3399
|
+
const itemText = text(match[3], 240);
|
|
3400
|
+
if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
|
|
3401
|
+
return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
|
|
3402
|
+
}).slice(0, 5);
|
|
3403
|
+
return results.length ? results : void 0;
|
|
3404
|
+
}
|
|
3405
|
+
function codeToolRequestPresentation(request) {
|
|
3406
|
+
const input = request.input;
|
|
3407
|
+
if (request.tool === "sandbox.read") {
|
|
3408
|
+
const path = text(input.path, 1024);
|
|
3409
|
+
if (!path) return void 0;
|
|
3410
|
+
const startLine = integer2(input.startLine);
|
|
3411
|
+
const endLine = integer2(input.endLine);
|
|
3412
|
+
return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
|
|
3413
|
+
}
|
|
3414
|
+
if (request.tool === "sandbox.list") {
|
|
3415
|
+
const scope = text(input.prefix, 1024);
|
|
3416
|
+
return { kind: "list", ...scope ? { scope } : {} };
|
|
3417
|
+
}
|
|
3418
|
+
if (request.tool === "sandbox.search" || request.tool === "sandbox.overview" || request.tool === "sandbox.where_is" || request.tool === "sandbox.who_imports" || request.tool === "sandbox.who_touches") {
|
|
3419
|
+
const query = text(input.query, 512);
|
|
3420
|
+
const scope = request.tool === "sandbox.search" ? text(input.prefix, 1024) : void 0;
|
|
3421
|
+
if (request.tool === "sandbox.search" && !query) return void 0;
|
|
3422
|
+
return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
|
|
3423
|
+
}
|
|
3424
|
+
if (request.tool === "sandbox.apply_patch") {
|
|
3425
|
+
return { kind: "patch", ...patchStats(input.patch) };
|
|
3426
|
+
}
|
|
3427
|
+
const recipeId = text(input.recipeId, 120);
|
|
3428
|
+
return recipeId ? { kind: "recipe", recipeId } : void 0;
|
|
3429
|
+
}
|
|
3430
|
+
function codeToolResultPresentation(request, response2) {
|
|
3431
|
+
const started = codeToolRequestPresentation(request);
|
|
3432
|
+
if (!started || !response2.ok) return started;
|
|
3433
|
+
const details = record4(response2.details);
|
|
3434
|
+
if (started.kind === "read") {
|
|
3435
|
+
return {
|
|
3436
|
+
...started,
|
|
3437
|
+
...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
|
|
3438
|
+
...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
|
|
3439
|
+
...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
|
|
3440
|
+
};
|
|
3441
|
+
}
|
|
3442
|
+
if (started.kind === "list") {
|
|
3443
|
+
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);
|
|
3444
|
+
return {
|
|
3445
|
+
...started,
|
|
3446
|
+
...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
|
|
3447
|
+
...listed.length ? { paths: listed } : {}
|
|
3448
|
+
};
|
|
3449
|
+
}
|
|
3450
|
+
if (started.kind === "query") {
|
|
3451
|
+
const results = request.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
|
|
3452
|
+
const resultExcerpt = request.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
|
|
3453
|
+
return {
|
|
3454
|
+
...started,
|
|
3455
|
+
...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
|
|
3456
|
+
...results ? { results } : {},
|
|
3457
|
+
...resultExcerpt ? { excerpt: resultExcerpt } : {}
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
if (started.kind === "patch") {
|
|
3461
|
+
return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
|
|
3462
|
+
}
|
|
3463
|
+
const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
|
|
3464
|
+
return {
|
|
3465
|
+
...started,
|
|
3466
|
+
...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
|
|
3467
|
+
...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
|
|
3468
|
+
...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
|
|
3469
|
+
...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3472
|
+
|
|
3343
3473
|
// src/code-runtime-engine.ts
|
|
3344
3474
|
var TheseusRuntimeEngine = class {
|
|
3345
3475
|
constructor(options) {
|
|
@@ -3534,6 +3664,7 @@ var TheseusRuntimeEngine = class {
|
|
|
3534
3664
|
event: (event) => this.#event(command, event, active.conversationRefs)
|
|
3535
3665
|
});
|
|
3536
3666
|
await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
|
|
3667
|
+
const extraSkills = await sessionSkillsFor(this.options, command);
|
|
3537
3668
|
const result = await this.#attempt({
|
|
3538
3669
|
inference,
|
|
3539
3670
|
broker,
|
|
@@ -3541,7 +3672,8 @@ var TheseusRuntimeEngine = class {
|
|
|
3541
3672
|
workspaceDir: active.workspace.workspaceDir,
|
|
3542
3673
|
prompt: metadata.prompt,
|
|
3543
3674
|
signal: active.abort.signal,
|
|
3544
|
-
recipeIds: this.options.recipes.map((recipe2) => recipe2.id)
|
|
3675
|
+
recipeIds: this.options.recipes.map((recipe2) => recipe2.id),
|
|
3676
|
+
...extraSkills.length ? { extraSkills } : {}
|
|
3545
3677
|
});
|
|
3546
3678
|
const closing = result.finalText.trim();
|
|
3547
3679
|
const completed = result.status === "completed" && Boolean(closing);
|
|
@@ -3574,18 +3706,29 @@ var TheseusRuntimeEngine = class {
|
|
|
3574
3706
|
return {
|
|
3575
3707
|
execute: async (context, request) => {
|
|
3576
3708
|
const startedAt = Date.now();
|
|
3709
|
+
const operationId = digestRuntimeValue(`${command.commandId}:${request.requestId}`);
|
|
3710
|
+
const startedPresentation = codeToolRequestPresentation(request);
|
|
3577
3711
|
await this.#event(
|
|
3578
3712
|
command,
|
|
3579
|
-
{
|
|
3713
|
+
{
|
|
3714
|
+
type: "tool",
|
|
3715
|
+
phase: "started",
|
|
3716
|
+
tool: request.tool,
|
|
3717
|
+
operationId,
|
|
3718
|
+
...startedPresentation ? { presentation: startedPresentation } : {}
|
|
3719
|
+
},
|
|
3580
3720
|
active.conversationRefs
|
|
3581
3721
|
).catch(() => void 0);
|
|
3582
3722
|
const response2 = await broker.execute(context, request);
|
|
3723
|
+
const completedPresentation = codeToolResultPresentation(request, response2);
|
|
3583
3724
|
await this.#event(command, {
|
|
3584
3725
|
type: "tool",
|
|
3585
3726
|
phase: "completed",
|
|
3586
3727
|
tool: request.tool,
|
|
3587
3728
|
ok: response2.ok,
|
|
3588
|
-
durationMs: Date.now() - startedAt
|
|
3729
|
+
durationMs: Date.now() - startedAt,
|
|
3730
|
+
operationId,
|
|
3731
|
+
...completedPresentation ? { presentation: completedPresentation } : {}
|
|
3589
3732
|
}, active.conversationRefs).catch(() => void 0);
|
|
3590
3733
|
return response2;
|
|
3591
3734
|
}
|
|
@@ -3830,9 +3973,9 @@ var MEASURED_PREMIUM = Object.freeze({
|
|
|
3830
3973
|
decomposePerSubGoal: 0.23
|
|
3831
3974
|
});
|
|
3832
3975
|
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(
|
|
3976
|
+
const text2 = feedback.trim();
|
|
3977
|
+
if (text2.length < 12) return false;
|
|
3978
|
+
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
3979
|
};
|
|
3837
3980
|
function chooseStrategy(signals = {}) {
|
|
3838
3981
|
const width = Math.max(1, signals.width ?? 3);
|