@odla-ai/harness 0.7.0 → 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-QZXCQSPZ.js → chunk-ISR434K7.js} +353 -76
- 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 +353 -76
- 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 +362 -85
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- 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-LNQNFGQC.js.map +0 -1
- package/dist/chunk-QZXCQSPZ.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";
|
|
@@ -1983,7 +1983,10 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
|
|
|
1983
1983
|
var V2_SYSTEM_PROMPT = `You are the coding agent inside an odla Code harness.
|
|
1984
1984
|
Start by orienting: odla_list shows the files in the workspace and odla_search
|
|
1985
1985
|
finds a literal string across them. Prefer those over guessing a path.
|
|
1986
|
-
Then odla_read a bounded range, and odla_apply_git_diff to mutate.
|
|
1986
|
+
Then odla_read a bounded range, and odla_apply_git_diff to mutate. When you
|
|
1987
|
+
need several independent searches or file ranges, issue those read-only calls
|
|
1988
|
+
together in one turn; their results stay ordered and the harness overlaps them.
|
|
1989
|
+
Never issue odla_apply_git_diff or odla_run_recipe alongside another tool call.
|
|
1987
1990
|
For mutations, call odla_apply_git_diff with raw git diff text. It must start
|
|
1988
1991
|
with "diff --git a/<path> b/<path>", include matching "---" and "+++" file
|
|
1989
1992
|
headers and numbered "@@" hunks, and never use "*** Begin Patch" wrappers.
|
|
@@ -2014,18 +2017,26 @@ var SYSTEM_PROMPT_FOR = {
|
|
|
2014
2017
|
};
|
|
2015
2018
|
function codeSkill(opts) {
|
|
2016
2019
|
let seq = 0;
|
|
2020
|
+
let nextCompletion = 1;
|
|
2021
|
+
const completed = /* @__PURE__ */ new Map();
|
|
2017
2022
|
const call = async (tool, input, signal) => {
|
|
2023
|
+
const sequence = ++seq;
|
|
2018
2024
|
const startedAt = Date.now();
|
|
2019
2025
|
const response2 = await opts.broker.execute(
|
|
2020
2026
|
{ lease: opts.lease, workspaceDir: opts.workspaceDir, signal },
|
|
2021
|
-
{ requestId: `bench-${tool}-${
|
|
2027
|
+
{ requestId: `bench-${tool}-${sequence}`, tool, input }
|
|
2022
2028
|
);
|
|
2023
|
-
|
|
2029
|
+
completed.set(sequence, {
|
|
2024
2030
|
tool,
|
|
2025
2031
|
ok: response2.ok,
|
|
2026
2032
|
durationMs: Date.now() - startedAt,
|
|
2027
2033
|
...response2.ok ? {} : { error: String(response2.content).slice(0, 300) }
|
|
2028
2034
|
});
|
|
2035
|
+
while (completed.has(nextCompletion)) {
|
|
2036
|
+
const completion = completed.get(nextCompletion);
|
|
2037
|
+
completed.delete(nextCompletion++);
|
|
2038
|
+
opts.onToolCall?.(completion);
|
|
2039
|
+
}
|
|
2029
2040
|
return { content: response2.content, isError: !response2.ok };
|
|
2030
2041
|
};
|
|
2031
2042
|
const read2 = {
|
|
@@ -2041,6 +2052,7 @@ function codeSkill(opts) {
|
|
|
2041
2052
|
},
|
|
2042
2053
|
additionalProperties: false
|
|
2043
2054
|
},
|
|
2055
|
+
concurrency: "parallel",
|
|
2044
2056
|
handler: (input, ctx) => call("sandbox.read", input, ctx.signal)
|
|
2045
2057
|
};
|
|
2046
2058
|
const applyPatch = {
|
|
@@ -2082,6 +2094,7 @@ function codeSkill(opts) {
|
|
|
2082
2094
|
},
|
|
2083
2095
|
additionalProperties: false
|
|
2084
2096
|
},
|
|
2097
|
+
concurrency: "parallel",
|
|
2085
2098
|
handler: (input, ctx) => call("sandbox.list", input, ctx.signal)
|
|
2086
2099
|
};
|
|
2087
2100
|
const searchFiles = {
|
|
@@ -2098,11 +2111,13 @@ function codeSkill(opts) {
|
|
|
2098
2111
|
},
|
|
2099
2112
|
additionalProperties: false
|
|
2100
2113
|
},
|
|
2114
|
+
concurrency: "parallel",
|
|
2101
2115
|
handler: (input, ctx) => call("sandbox.search", input, ctx.signal)
|
|
2102
2116
|
};
|
|
2103
2117
|
const graphTool = (name, tool, description, required) => ({
|
|
2104
2118
|
name,
|
|
2105
2119
|
description,
|
|
2120
|
+
concurrency: "parallel",
|
|
2106
2121
|
inputSchema: {
|
|
2107
2122
|
type: "object",
|
|
2108
2123
|
...required ? { required: ["query"] } : {},
|
|
@@ -2405,16 +2420,16 @@ function createCodePolicyGate(options) {
|
|
|
2405
2420
|
}
|
|
2406
2421
|
};
|
|
2407
2422
|
}
|
|
2408
|
-
function directoryPrefixes(
|
|
2423
|
+
function directoryPrefixes(paths2) {
|
|
2409
2424
|
const prefixes = /* @__PURE__ */ new Set(["."]);
|
|
2410
|
-
for (const path of
|
|
2425
|
+
for (const path of paths2) {
|
|
2411
2426
|
const parts = path.split("/");
|
|
2412
2427
|
for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
|
|
2413
2428
|
}
|
|
2414
2429
|
return [...prefixes].sort();
|
|
2415
2430
|
}
|
|
2416
|
-
async function safePrefix(base,
|
|
2417
|
-
const prefixes = directoryPrefixes(
|
|
2431
|
+
async function safePrefix(base, paths2, prefix) {
|
|
2432
|
+
const prefixes = directoryPrefixes(paths2);
|
|
2418
2433
|
const conversions = await conversionRegistry(
|
|
2419
2434
|
[await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
|
|
2420
2435
|
{ "code.prefixes.v1": prefixes }
|
|
@@ -2532,13 +2547,32 @@ function response(request, ok, content, details) {
|
|
|
2532
2547
|
var import_promises11 = require("fs/promises");
|
|
2533
2548
|
|
|
2534
2549
|
// src/code-tool-discovery.ts
|
|
2550
|
+
var import_node_child_process6 = require("child_process");
|
|
2535
2551
|
var import_promises9 = require("fs/promises");
|
|
2536
2552
|
var import_node_path9 = require("path");
|
|
2537
2553
|
var DEFAULT_MAX_FILES = 2e4;
|
|
2538
2554
|
var DEFAULT_MAX_RESULTS = 100;
|
|
2539
2555
|
var DEFAULT_MAX_FILE_BYTES = 512 * 1024;
|
|
2556
|
+
function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = registeredFiles) {
|
|
2557
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
2558
|
+
return {
|
|
2559
|
+
files(root) {
|
|
2560
|
+
const existing = cache2.get(root);
|
|
2561
|
+
if (existing) return existing;
|
|
2562
|
+
const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
|
|
2563
|
+
cache2.set(root, pending);
|
|
2564
|
+
void pending.catch(() => {
|
|
2565
|
+
if (cache2.get(root) === pending) cache2.delete(root);
|
|
2566
|
+
});
|
|
2567
|
+
return pending;
|
|
2568
|
+
},
|
|
2569
|
+
invalidate(root) {
|
|
2570
|
+
cache2.delete(root);
|
|
2571
|
+
}
|
|
2572
|
+
};
|
|
2573
|
+
}
|
|
2540
2574
|
async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
2541
|
-
const
|
|
2575
|
+
const paths2 = [];
|
|
2542
2576
|
const walk = async (directory) => {
|
|
2543
2577
|
for (const entry of await (0, import_promises9.readdir)(directory, { withFileTypes: true })) {
|
|
2544
2578
|
if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
@@ -2552,43 +2586,137 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
|
2552
2586
|
} catch {
|
|
2553
2587
|
continue;
|
|
2554
2588
|
}
|
|
2555
|
-
|
|
2556
|
-
if (
|
|
2589
|
+
paths2.push(path);
|
|
2590
|
+
if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
|
|
2557
2591
|
}
|
|
2558
2592
|
}
|
|
2559
2593
|
};
|
|
2560
2594
|
await walk((0, import_node_path9.resolve)(root));
|
|
2561
|
-
return
|
|
2595
|
+
return paths2.sort();
|
|
2562
2596
|
}
|
|
2563
|
-
function listWorkspace(
|
|
2597
|
+
function listWorkspace(paths2, options = {}) {
|
|
2564
2598
|
const max = options.maxEntries ?? 1e3;
|
|
2565
2599
|
const prefix = options.prefix?.replace(/\/+$/, "");
|
|
2566
|
-
const scoped = prefix ?
|
|
2600
|
+
const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
|
|
2567
2601
|
return scoped.slice(0, max);
|
|
2568
2602
|
}
|
|
2569
|
-
async function searchWorkspace(root,
|
|
2570
|
-
|
|
2571
|
-
if (!query) throw new TypeError("search query must be a non-empty string");
|
|
2603
|
+
async function searchWorkspace(root, paths2, options) {
|
|
2604
|
+
options.signal?.throwIfAborted();
|
|
2605
|
+
if (!options.query) throw new TypeError("search query must be a non-empty string");
|
|
2572
2606
|
const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
2573
2607
|
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
|
|
2574
|
-
const scoped = listWorkspace(
|
|
2608
|
+
const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
|
|
2609
|
+
if (scoped.length === 0) return [];
|
|
2610
|
+
try {
|
|
2611
|
+
return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
2612
|
+
} catch (error) {
|
|
2613
|
+
options.signal?.throwIfAborted();
|
|
2614
|
+
return fallbackSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
var MAX_NATIVE_ARG_BYTES = 96 * 1024;
|
|
2618
|
+
async function nativeSearch(root, paths2, options) {
|
|
2619
|
+
const batches = [];
|
|
2620
|
+
let batch = [];
|
|
2621
|
+
let bytes = 0;
|
|
2622
|
+
for (const path of paths2) {
|
|
2623
|
+
const size = Buffer.byteLength(path) + 1;
|
|
2624
|
+
if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
|
|
2625
|
+
batches.push(batch);
|
|
2626
|
+
batch = [];
|
|
2627
|
+
bytes = 0;
|
|
2628
|
+
}
|
|
2629
|
+
batch.push(path);
|
|
2630
|
+
bytes += size;
|
|
2631
|
+
}
|
|
2632
|
+
if (batch.length > 0) batches.push(batch);
|
|
2633
|
+
const matches = [];
|
|
2634
|
+
for (const files of batches) {
|
|
2635
|
+
const remaining = options.maxResults - matches.length;
|
|
2636
|
+
if (remaining <= 0) break;
|
|
2637
|
+
matches.push(...await nativeSearchBatch(root, files, options, remaining));
|
|
2638
|
+
}
|
|
2639
|
+
return matches;
|
|
2640
|
+
}
|
|
2641
|
+
function nativeSearchBatch(root, paths2, options, remaining) {
|
|
2642
|
+
return new Promise((resolveMatches, reject) => {
|
|
2643
|
+
const args = [
|
|
2644
|
+
"--fixed-strings",
|
|
2645
|
+
"--json",
|
|
2646
|
+
"--no-messages",
|
|
2647
|
+
"--sort=path",
|
|
2648
|
+
`--max-filesize=${options.maxFileBytes}`,
|
|
2649
|
+
"--max-columns=4096",
|
|
2650
|
+
"--max-columns-preview",
|
|
2651
|
+
options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
|
|
2652
|
+
"--",
|
|
2653
|
+
options.query,
|
|
2654
|
+
...paths2
|
|
2655
|
+
];
|
|
2656
|
+
const child = (0, import_node_child_process6.spawn)("rg", args, {
|
|
2657
|
+
cwd: root,
|
|
2658
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2659
|
+
...options.signal ? { signal: options.signal } : {}
|
|
2660
|
+
});
|
|
2661
|
+
const matches = [];
|
|
2662
|
+
let carry = "";
|
|
2663
|
+
let stopped = false;
|
|
2664
|
+
const consume = (line) => {
|
|
2665
|
+
if (matches.length >= remaining) return;
|
|
2666
|
+
let event;
|
|
2667
|
+
try {
|
|
2668
|
+
event = JSON.parse(line);
|
|
2669
|
+
} catch {
|
|
2670
|
+
return;
|
|
2671
|
+
}
|
|
2672
|
+
const path = event.data?.path?.text;
|
|
2673
|
+
const lineNumber = event.data?.line_number;
|
|
2674
|
+
const source = event.data?.lines?.text;
|
|
2675
|
+
if (event.type !== "match" || path === void 0 || lineNumber === void 0 || source === void 0) return;
|
|
2676
|
+
matches.push({ path, line: lineNumber, text: source.trim().slice(0, 240) });
|
|
2677
|
+
if (matches.length >= remaining) {
|
|
2678
|
+
stopped = true;
|
|
2679
|
+
child.kill();
|
|
2680
|
+
}
|
|
2681
|
+
};
|
|
2682
|
+
child.stdout.setEncoding("utf8");
|
|
2683
|
+
child.stdout.on("data", (chunk) => {
|
|
2684
|
+
carry += chunk;
|
|
2685
|
+
let newline = carry.indexOf("\n");
|
|
2686
|
+
while (newline >= 0) {
|
|
2687
|
+
consume(carry.slice(0, newline));
|
|
2688
|
+
carry = carry.slice(newline + 1);
|
|
2689
|
+
newline = carry.indexOf("\n");
|
|
2690
|
+
}
|
|
2691
|
+
});
|
|
2692
|
+
child.once("error", reject);
|
|
2693
|
+
child.once("close", (code) => {
|
|
2694
|
+
if (carry) consume(carry);
|
|
2695
|
+
if (stopped || code === 0 || code === 1) resolveMatches(matches);
|
|
2696
|
+
else reject(new Error(`native search exited with status ${code ?? "unknown"}`));
|
|
2697
|
+
});
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2700
|
+
async function fallbackSearch(root, scoped, options) {
|
|
2701
|
+
const query = options.caseSensitive === false ? options.query.toLowerCase() : options.query;
|
|
2575
2702
|
const matches = [];
|
|
2576
2703
|
for (const path of scoped) {
|
|
2577
|
-
|
|
2704
|
+
options.signal?.throwIfAborted();
|
|
2705
|
+
if (matches.length >= options.maxResults) break;
|
|
2578
2706
|
let source;
|
|
2579
2707
|
try {
|
|
2580
2708
|
source = await (0, import_promises9.readFile)((0, import_node_path9.resolve)(root, path));
|
|
2581
2709
|
} catch {
|
|
2582
2710
|
continue;
|
|
2583
2711
|
}
|
|
2584
|
-
if (source.byteLength > maxFileBytes || source.includes(0)) continue;
|
|
2712
|
+
if (source.byteLength > options.maxFileBytes || source.includes(0)) continue;
|
|
2585
2713
|
const lines = source.toString("utf8").split("\n");
|
|
2586
2714
|
for (let index = 0; index < lines.length; index += 1) {
|
|
2587
2715
|
const raw = lines[index];
|
|
2588
2716
|
const haystack = options.caseSensitive === false ? raw.toLowerCase() : raw;
|
|
2589
2717
|
if (!haystack.includes(query)) continue;
|
|
2590
2718
|
matches.push({ path, line: index + 1, text: raw.trim().slice(0, 240) });
|
|
2591
|
-
if (matches.length >= maxResults) break;
|
|
2719
|
+
if (matches.length >= options.maxResults) break;
|
|
2592
2720
|
}
|
|
2593
2721
|
}
|
|
2594
2722
|
return matches;
|
|
@@ -2600,7 +2728,7 @@ var import_node_path10 = require("path");
|
|
|
2600
2728
|
var import_graph = require("@odla-ai/graph");
|
|
2601
2729
|
var import_code4 = require("@odla-ai/graph/code");
|
|
2602
2730
|
var cache = /* @__PURE__ */ new Map();
|
|
2603
|
-
function workspaceGraphs(workspaceDir,
|
|
2731
|
+
function workspaceGraphs(workspaceDir, paths2) {
|
|
2604
2732
|
const existing = cache.get(workspaceDir);
|
|
2605
2733
|
if (existing) return existing;
|
|
2606
2734
|
const read2 = (path) => (0, import_promises10.readFile)((0, import_node_path10.join)(workspaceDir, path), "utf8");
|
|
@@ -2608,11 +2736,14 @@ function workspaceGraphs(workspaceDir, paths) {
|
|
|
2608
2736
|
// No knownTables: a staged workspace may not carry migrations, and a filter
|
|
2609
2737
|
// that silently drops every table is worse than an unfiltered one. Callers
|
|
2610
2738
|
// with ground truth should build the graph themselves.
|
|
2611
|
-
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.") } })
|
|
2612
2740
|
}))();
|
|
2613
2741
|
cache.set(workspaceDir, built);
|
|
2614
2742
|
return built;
|
|
2615
2743
|
}
|
|
2744
|
+
function forgetWorkspaceGraphs(workspaceDir) {
|
|
2745
|
+
cache.delete(workspaceDir);
|
|
2746
|
+
}
|
|
2616
2747
|
var shortId = (id) => id.slice(id.indexOf(":") + 1);
|
|
2617
2748
|
function renderOverview(graphs, prefix) {
|
|
2618
2749
|
const rows = (0, import_graph.rollup)(graphs.graph, import_code4.FILE, prefix === void 0 ? {} : { prefix });
|
|
@@ -2659,7 +2790,7 @@ var GRAPH_TOOLS = /* @__PURE__ */ new Set([
|
|
|
2659
2790
|
"sandbox.who_imports",
|
|
2660
2791
|
"sandbox.who_touches"
|
|
2661
2792
|
]);
|
|
2662
|
-
async function read(context, request, options, policy) {
|
|
2793
|
+
async function read(context, request, options, policy, registry) {
|
|
2663
2794
|
exactKeys(request.input, ["path", "startLine", "endLine"]);
|
|
2664
2795
|
const path = stringField(request.input, "path");
|
|
2665
2796
|
const startLine = optionalInteger(request.input.startLine) ?? 1;
|
|
@@ -2667,11 +2798,11 @@ async function read(context, request, options, policy) {
|
|
|
2667
2798
|
if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
|
|
2668
2799
|
throw new TypeError("requested line range exceeds its bound");
|
|
2669
2800
|
}
|
|
2670
|
-
const
|
|
2671
|
-
if (!
|
|
2801
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2802
|
+
if (!paths2.includes(path)) {
|
|
2672
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.`);
|
|
2673
2804
|
}
|
|
2674
|
-
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 }));
|
|
2675
2806
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2676
2807
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
2677
2808
|
const info = await (0, import_promises11.stat)(target);
|
|
@@ -2687,22 +2818,22 @@ async function read(context, request, options, policy) {
|
|
|
2687
2818
|
}
|
|
2688
2819
|
return response(request, true, content, { path, startLine, endLine: Math.min(endLine, lines.length) });
|
|
2689
2820
|
}
|
|
2690
|
-
async function list(context, request, options, policy) {
|
|
2821
|
+
async function list(context, request, options, policy, registry) {
|
|
2691
2822
|
exactKeys(request.input, ["prefix", "maxEntries"]);
|
|
2692
2823
|
const raw = request.input.prefix;
|
|
2693
2824
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
2694
2825
|
const maxEntries = optionalInteger(request.input.maxEntries) ?? 1e3;
|
|
2695
2826
|
if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
|
|
2696
|
-
const
|
|
2697
|
-
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 } : {} }));
|
|
2698
2829
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2699
|
-
const entries = listWorkspace(
|
|
2830
|
+
const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
|
|
2700
2831
|
if (!entries.length) {
|
|
2701
2832
|
return response(request, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
|
|
2702
2833
|
}
|
|
2703
|
-
const truncated = entries.length <
|
|
2704
|
-
const hint = !prefix &&
|
|
2705
|
-
\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.` : "";
|
|
2706
2837
|
return response(
|
|
2707
2838
|
request,
|
|
2708
2839
|
true,
|
|
@@ -2711,7 +2842,7 @@ async function list(context, request, options, policy) {
|
|
|
2711
2842
|
{ count: entries.length, truncated }
|
|
2712
2843
|
);
|
|
2713
2844
|
}
|
|
2714
|
-
async function search(context, request, options, policy) {
|
|
2845
|
+
async function search(context, request, options, policy, registry) {
|
|
2715
2846
|
exactKeys(request.input, ["query", "prefix", "maxResults", "caseSensitive"]);
|
|
2716
2847
|
const query = stringField(request.input, "query");
|
|
2717
2848
|
if (query.length > 512) throw new TypeError("search query exceeds its bound");
|
|
@@ -2720,21 +2851,22 @@ async function search(context, request, options, policy) {
|
|
|
2720
2851
|
const maxResults = optionalInteger(request.input.maxResults) ?? 100;
|
|
2721
2852
|
if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
|
|
2722
2853
|
const caseSensitive = request.input.caseSensitive === void 0 ? true : request.input.caseSensitive === true;
|
|
2723
|
-
const
|
|
2724
|
-
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 } : {} }));
|
|
2725
2856
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2726
|
-
const matches = await searchWorkspace(context.workspaceDir,
|
|
2857
|
+
const matches = await searchWorkspace(context.workspaceDir, paths2, {
|
|
2727
2858
|
query,
|
|
2728
2859
|
maxResults,
|
|
2729
2860
|
caseSensitive,
|
|
2730
|
-
...prefix ? { prefix } : {}
|
|
2861
|
+
...prefix ? { prefix } : {},
|
|
2862
|
+
...context.signal ? { signal: context.signal } : {}
|
|
2731
2863
|
});
|
|
2732
2864
|
if (!matches.length) return response(request, true, `No match for "${query}".`, { count: 0 });
|
|
2733
2865
|
return response(request, true, matches.map((match) => `${match.path}:${match.line}: ${match.text}`).join("\n"), {
|
|
2734
2866
|
count: matches.length
|
|
2735
2867
|
});
|
|
2736
2868
|
}
|
|
2737
|
-
async function graphQuery(context, request, options, policy) {
|
|
2869
|
+
async function graphQuery(context, request, options, policy, registry) {
|
|
2738
2870
|
exactKeys(request.input, ["query"]);
|
|
2739
2871
|
const raw = request.input.query;
|
|
2740
2872
|
const query = typeof raw === "string" ? raw : "";
|
|
@@ -2744,8 +2876,8 @@ async function graphQuery(context, request, options, policy) {
|
|
|
2744
2876
|
selector: query
|
|
2745
2877
|
}));
|
|
2746
2878
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2747
|
-
const
|
|
2748
|
-
const graphs = await workspaceGraphs(context.workspaceDir,
|
|
2879
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2880
|
+
const graphs = await workspaceGraphs(context.workspaceDir, paths2);
|
|
2749
2881
|
if (request.tool === "sandbox.overview") {
|
|
2750
2882
|
return response(request, true, renderOverview(graphs, query || void 0));
|
|
2751
2883
|
}
|
|
@@ -2760,23 +2892,38 @@ function createCodeToolBroker(options) {
|
|
|
2760
2892
|
validateOptions(options);
|
|
2761
2893
|
const recipes = new Map(options.recipes.map((recipe2) => [recipe2.id, recipe2]));
|
|
2762
2894
|
const policy = createCodePolicyGate(options);
|
|
2763
|
-
|
|
2895
|
+
const registry = createWorkspaceFileRegistry();
|
|
2896
|
+
let barrier = Promise.resolve();
|
|
2897
|
+
const activeReads = /* @__PURE__ */ new Set();
|
|
2764
2898
|
return {
|
|
2765
2899
|
execute(context, request) {
|
|
2766
|
-
|
|
2767
|
-
|
|
2900
|
+
if (isReadTool(request.tool)) {
|
|
2901
|
+
const result2 = barrier.then(() => route(context, request, options, recipes, policy, registry));
|
|
2902
|
+
const settled = result2.then(() => void 0, () => void 0);
|
|
2903
|
+
activeReads.add(settled);
|
|
2904
|
+
void settled.then(() => {
|
|
2905
|
+
activeReads.delete(settled);
|
|
2906
|
+
});
|
|
2907
|
+
return result2;
|
|
2908
|
+
}
|
|
2909
|
+
const earlierReads = [...activeReads];
|
|
2910
|
+
const result = barrier.then(() => Promise.all(earlierReads)).then(() => route(context, request, options, recipes, policy, registry));
|
|
2911
|
+
barrier = result.then(() => void 0, () => void 0);
|
|
2768
2912
|
return result;
|
|
2769
2913
|
}
|
|
2770
2914
|
};
|
|
2771
2915
|
}
|
|
2772
|
-
|
|
2916
|
+
function isReadTool(tool) {
|
|
2917
|
+
return tool === "sandbox.read" || tool === "sandbox.list" || tool === "sandbox.search" || GRAPH_TOOLS.has(tool);
|
|
2918
|
+
}
|
|
2919
|
+
async function route(context, request, options, recipes, policy, registry) {
|
|
2773
2920
|
try {
|
|
2774
2921
|
if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
|
|
2775
|
-
if (request.tool === "sandbox.read") return await read(context, request, options, policy);
|
|
2776
|
-
if (request.tool === "sandbox.list") return await list(context, request, options, policy);
|
|
2777
|
-
if (request.tool === "sandbox.search") return await search(context, request, options, policy);
|
|
2778
|
-
if (GRAPH_TOOLS.has(request.tool)) return await graphQuery(context, request, options, policy);
|
|
2779
|
-
if (request.tool === "sandbox.apply_patch") return await patch(context, request, options, policy);
|
|
2922
|
+
if (request.tool === "sandbox.read") return await read(context, request, options, policy, registry);
|
|
2923
|
+
if (request.tool === "sandbox.list") return await list(context, request, options, policy, registry);
|
|
2924
|
+
if (request.tool === "sandbox.search") return await search(context, request, options, policy, registry);
|
|
2925
|
+
if (GRAPH_TOOLS.has(request.tool)) return await graphQuery(context, request, options, policy, registry);
|
|
2926
|
+
if (request.tool === "sandbox.apply_patch") return await patch(context, request, options, policy, registry);
|
|
2780
2927
|
return await recipe(context, request, options, recipes, policy);
|
|
2781
2928
|
} catch (reason) {
|
|
2782
2929
|
return response(request, false, toolFailureMessage(reason));
|
|
@@ -2791,17 +2938,19 @@ function toolFailureMessage(reason) {
|
|
|
2791
2938
|
if (code === "EACCES" || code === "EPERM") return "that path is not readable through this tool";
|
|
2792
2939
|
return "tool failed closed";
|
|
2793
2940
|
}
|
|
2794
|
-
async function patch(context, request, options, policy) {
|
|
2941
|
+
async function patch(context, request, options, policy, registry) {
|
|
2795
2942
|
exactKeys(request.input, ["patch"]);
|
|
2796
2943
|
const value = stringField(request.input, "patch");
|
|
2797
|
-
const
|
|
2798
|
-
if (
|
|
2944
|
+
const paths2 = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
|
|
2945
|
+
if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
2799
2946
|
throw new TypeError("patch targets a read-only reference source");
|
|
2800
2947
|
}
|
|
2801
2948
|
const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
|
|
2802
2949
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2803
|
-
await applyCodePatch(context.workspaceDir, value,
|
|
2804
|
-
|
|
2950
|
+
await applyCodePatch(context.workspaceDir, value, paths2);
|
|
2951
|
+
registry.invalidate(context.workspaceDir);
|
|
2952
|
+
forgetWorkspaceGraphs(context.workspaceDir);
|
|
2953
|
+
return response(request, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
|
|
2805
2954
|
}
|
|
2806
2955
|
async function recipe(context, request, options, recipes, policy) {
|
|
2807
2956
|
exactKeys(request.input, ["recipeId"]);
|
|
@@ -3185,12 +3334,129 @@ var import_node_crypto4 = require("crypto");
|
|
|
3185
3334
|
async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
3186
3335
|
const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
|
|
3187
3336
|
refs.push(eventId);
|
|
3188
|
-
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;
|
|
3189
3339
|
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
3190
3340
|
}
|
|
3191
3341
|
var digestRuntimeValue = (value) => `sha256:${(0, import_node_crypto4.createHash)("sha256").update(value).digest("hex")}`;
|
|
3192
3342
|
var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
|
|
3193
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
|
+
|
|
3194
3460
|
// src/code-runtime-engine.ts
|
|
3195
3461
|
var TheseusRuntimeEngine = class {
|
|
3196
3462
|
constructor(options) {
|
|
@@ -3425,18 +3691,29 @@ var TheseusRuntimeEngine = class {
|
|
|
3425
3691
|
return {
|
|
3426
3692
|
execute: async (context, request) => {
|
|
3427
3693
|
const startedAt = Date.now();
|
|
3694
|
+
const operationId = digestRuntimeValue(`${command.commandId}:${request.requestId}`);
|
|
3695
|
+
const startedPresentation = codeToolRequestPresentation(request);
|
|
3428
3696
|
await this.#event(
|
|
3429
3697
|
command,
|
|
3430
|
-
{
|
|
3698
|
+
{
|
|
3699
|
+
type: "tool",
|
|
3700
|
+
phase: "started",
|
|
3701
|
+
tool: request.tool,
|
|
3702
|
+
operationId,
|
|
3703
|
+
...startedPresentation ? { presentation: startedPresentation } : {}
|
|
3704
|
+
},
|
|
3431
3705
|
active.conversationRefs
|
|
3432
3706
|
).catch(() => void 0);
|
|
3433
3707
|
const response2 = await broker.execute(context, request);
|
|
3708
|
+
const completedPresentation = codeToolResultPresentation(request, response2);
|
|
3434
3709
|
await this.#event(command, {
|
|
3435
3710
|
type: "tool",
|
|
3436
3711
|
phase: "completed",
|
|
3437
3712
|
tool: request.tool,
|
|
3438
3713
|
ok: response2.ok,
|
|
3439
|
-
durationMs: Date.now() - startedAt
|
|
3714
|
+
durationMs: Date.now() - startedAt,
|
|
3715
|
+
operationId,
|
|
3716
|
+
...completedPresentation ? { presentation: completedPresentation } : {}
|
|
3440
3717
|
}, active.conversationRefs).catch(() => void 0);
|
|
3441
3718
|
return response2;
|
|
3442
3719
|
}
|
|
@@ -3681,9 +3958,9 @@ var MEASURED_PREMIUM = Object.freeze({
|
|
|
3681
3958
|
decomposePerSubGoal: 0.23
|
|
3682
3959
|
});
|
|
3683
3960
|
var actionable = (feedback) => {
|
|
3684
|
-
const
|
|
3685
|
-
if (
|
|
3686
|
-
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);
|
|
3687
3964
|
};
|
|
3688
3965
|
function chooseStrategy(signals = {}) {
|
|
3689
3966
|
const width = Math.max(1, signals.width ?? 3);
|