@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
|
@@ -317,7 +317,7 @@ function validateCodePatch(rawPatch, maxBytes) {
|
|
|
317
317
|
if (FORBIDDEN.test(patch2) || /(?:old|new)(?: file)? mode 120000/.test(patch2)) {
|
|
318
318
|
throw new TypeError("patch uses a forbidden binary, link, mode, rename, or copy operation");
|
|
319
319
|
}
|
|
320
|
-
const
|
|
320
|
+
const paths2 = [];
|
|
321
321
|
const lines = patch2.split("\n");
|
|
322
322
|
for (let index = 0; index < lines.length; index += 1) {
|
|
323
323
|
const line = lines[index];
|
|
@@ -333,10 +333,10 @@ function validateCodePatch(rawPatch, maxBytes) {
|
|
|
333
333
|
if (!validHeaderPath(oldPath, path, "a") || !validHeaderPath(newPath, path, "b")) {
|
|
334
334
|
throw new TypeError("patch file headers do not match the declared path");
|
|
335
335
|
}
|
|
336
|
-
|
|
336
|
+
paths2.push(path);
|
|
337
337
|
}
|
|
338
|
-
if (!
|
|
339
|
-
return
|
|
338
|
+
if (!paths2.length || new Set(paths2).size !== paths2.length) throw new TypeError("patch has no diffs or repeats a path");
|
|
339
|
+
return paths2;
|
|
340
340
|
}
|
|
341
341
|
function validHeaderPath(value, path, prefix) {
|
|
342
342
|
return value === "/dev/null" || value === `${prefix}/${path}`;
|
|
@@ -361,11 +361,11 @@ function describePatchFailure(patch2, detail) {
|
|
|
361
361
|
const hint = hunks.length > 0 && contextless ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
|
|
362
362
|
return `patch did not apply: ${detail}${hint}`;
|
|
363
363
|
}
|
|
364
|
-
async function applyCodePatch(workspaceDir, rawPatch,
|
|
364
|
+
async function applyCodePatch(workspaceDir, rawPatch, paths2) {
|
|
365
365
|
const patch2 = stripPatchEnvelope(rawPatch);
|
|
366
366
|
await gitApply(workspaceDir, patch2, true);
|
|
367
367
|
await gitApply(workspaceDir, patch2, false);
|
|
368
|
-
for (const path of
|
|
368
|
+
for (const path of paths2) {
|
|
369
369
|
try {
|
|
370
370
|
const info = await (0, import_promises.lstat)(resolveCodePath(workspaceDir, path));
|
|
371
371
|
if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
|
|
@@ -387,8 +387,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
387
387
|
});
|
|
388
388
|
let stderr = "";
|
|
389
389
|
child.stderr.setEncoding("utf8");
|
|
390
|
-
child.stderr.on("data", (
|
|
391
|
-
if (stderr.length < 4e3) stderr +=
|
|
390
|
+
child.stderr.on("data", (text2) => {
|
|
391
|
+
if (stderr.length < 4e3) stderr += text2.slice(0, 4e3);
|
|
392
392
|
});
|
|
393
393
|
child.once("error", reject);
|
|
394
394
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr.trim().slice(0, 500)))));
|
|
@@ -471,12 +471,12 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
471
471
|
});
|
|
472
472
|
if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
|
|
473
473
|
if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
|
|
474
|
-
const
|
|
475
|
-
if (
|
|
474
|
+
const paths2 = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
|
|
475
|
+
if (paths2.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
476
476
|
const root = (0, import_node_path3.resolve)(sourceDir);
|
|
477
477
|
const files = [];
|
|
478
478
|
let bytes = 0;
|
|
479
|
-
for (const relativePath of
|
|
479
|
+
for (const relativePath of paths2) {
|
|
480
480
|
if (!allowedWorkspacePath(relativePath)) continue;
|
|
481
481
|
const source = (0, import_node_path3.resolve)(root, relativePath);
|
|
482
482
|
if (!source.startsWith(`${root}${import_node_path3.sep}`)) throw new TypeError("git file path escapes workspace");
|
|
@@ -610,8 +610,8 @@ async function restoreCodeWorkspaceCheckpoint(input) {
|
|
|
610
610
|
const workspace = await stageWorkspace(input.trustedBaseDir, input.stage);
|
|
611
611
|
try {
|
|
612
612
|
if (checkpoint.patch) {
|
|
613
|
-
const
|
|
614
|
-
await applyCodePatch(workspace.workspaceDir, checkpoint.patch,
|
|
613
|
+
const paths2 = validateCodePatch(checkpoint.patch, 256 * 1024);
|
|
614
|
+
await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths2);
|
|
615
615
|
}
|
|
616
616
|
return { workspace, checkpoint };
|
|
617
617
|
} catch (error) {
|
|
@@ -889,8 +889,8 @@ async function verifyCodeCandidate(input) {
|
|
|
889
889
|
try {
|
|
890
890
|
const baseDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
|
|
891
891
|
if (baseDigest !== input.trustedBaseDigest) throw new TypeError("trusted base does not match its registered digest");
|
|
892
|
-
const
|
|
893
|
-
await applyCodePatch(staged.workspaceDir, input.candidatePatch,
|
|
892
|
+
const paths2 = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
|
|
893
|
+
await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths2);
|
|
894
894
|
const sourceDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
|
|
895
895
|
const policyDigest = digestPolicy(policy);
|
|
896
896
|
const patchDigest = digestBytes(input.candidatePatch);
|
|
@@ -899,7 +899,7 @@ async function verifyCodeCandidate(input) {
|
|
|
899
899
|
trustedBaseDigest: input.trustedBaseDigest,
|
|
900
900
|
patchDigest
|
|
901
901
|
});
|
|
902
|
-
const changedTests = changedTestPaths(
|
|
902
|
+
const changedTests = changedTestPaths(paths2, policy);
|
|
903
903
|
if (changedTests.length > policy.maximumChangedTests) throw new TypeError("candidate changes too many test files");
|
|
904
904
|
const recipes = [];
|
|
905
905
|
const logs = [];
|
|
@@ -966,8 +966,8 @@ function validate(input) {
|
|
|
966
966
|
}
|
|
967
967
|
return result;
|
|
968
968
|
}
|
|
969
|
-
function changedTestPaths(
|
|
970
|
-
return
|
|
969
|
+
function changedTestPaths(paths2, policy) {
|
|
970
|
+
return paths2.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
|
|
971
971
|
}
|
|
972
972
|
function recipeReceipt(recipe2, result, artifacts) {
|
|
973
973
|
const status = result.timedOut ? "timed_out" : result.outputLimitExceeded ? "output_limited" : result.exitCode === 0 && artifacts.every((item) => item.status === "verified") ? "passed" : "failed";
|
|
@@ -1401,7 +1401,10 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
|
|
|
1401
1401
|
var V2_SYSTEM_PROMPT = `You are the coding agent inside an odla Code harness.
|
|
1402
1402
|
Start by orienting: odla_list shows the files in the workspace and odla_search
|
|
1403
1403
|
finds a literal string across them. Prefer those over guessing a path.
|
|
1404
|
-
Then odla_read a bounded range, and odla_apply_git_diff to mutate.
|
|
1404
|
+
Then odla_read a bounded range, and odla_apply_git_diff to mutate. When you
|
|
1405
|
+
need several independent searches or file ranges, issue those read-only calls
|
|
1406
|
+
together in one turn; their results stay ordered and the harness overlaps them.
|
|
1407
|
+
Never issue odla_apply_git_diff or odla_run_recipe alongside another tool call.
|
|
1405
1408
|
For mutations, call odla_apply_git_diff with raw git diff text. It must start
|
|
1406
1409
|
with "diff --git a/<path> b/<path>", include matching "---" and "+++" file
|
|
1407
1410
|
headers and numbered "@@" hunks, and never use "*** Begin Patch" wrappers.
|
|
@@ -1432,18 +1435,26 @@ var SYSTEM_PROMPT_FOR = {
|
|
|
1432
1435
|
};
|
|
1433
1436
|
function codeSkill(opts) {
|
|
1434
1437
|
let seq = 0;
|
|
1438
|
+
let nextCompletion = 1;
|
|
1439
|
+
const completed = /* @__PURE__ */ new Map();
|
|
1435
1440
|
const call = async (tool, input, signal) => {
|
|
1441
|
+
const sequence = ++seq;
|
|
1436
1442
|
const startedAt = Date.now();
|
|
1437
1443
|
const response2 = await opts.broker.execute(
|
|
1438
1444
|
{ lease: opts.lease, workspaceDir: opts.workspaceDir, signal },
|
|
1439
|
-
{ requestId: `bench-${tool}-${
|
|
1445
|
+
{ requestId: `bench-${tool}-${sequence}`, tool, input }
|
|
1440
1446
|
);
|
|
1441
|
-
|
|
1447
|
+
completed.set(sequence, {
|
|
1442
1448
|
tool,
|
|
1443
1449
|
ok: response2.ok,
|
|
1444
1450
|
durationMs: Date.now() - startedAt,
|
|
1445
1451
|
...response2.ok ? {} : { error: String(response2.content).slice(0, 300) }
|
|
1446
1452
|
});
|
|
1453
|
+
while (completed.has(nextCompletion)) {
|
|
1454
|
+
const completion = completed.get(nextCompletion);
|
|
1455
|
+
completed.delete(nextCompletion++);
|
|
1456
|
+
opts.onToolCall?.(completion);
|
|
1457
|
+
}
|
|
1447
1458
|
return { content: response2.content, isError: !response2.ok };
|
|
1448
1459
|
};
|
|
1449
1460
|
const read2 = {
|
|
@@ -1459,6 +1470,7 @@ function codeSkill(opts) {
|
|
|
1459
1470
|
},
|
|
1460
1471
|
additionalProperties: false
|
|
1461
1472
|
},
|
|
1473
|
+
concurrency: "parallel",
|
|
1462
1474
|
handler: (input, ctx) => call("sandbox.read", input, ctx.signal)
|
|
1463
1475
|
};
|
|
1464
1476
|
const applyPatch = {
|
|
@@ -1500,6 +1512,7 @@ function codeSkill(opts) {
|
|
|
1500
1512
|
},
|
|
1501
1513
|
additionalProperties: false
|
|
1502
1514
|
},
|
|
1515
|
+
concurrency: "parallel",
|
|
1503
1516
|
handler: (input, ctx) => call("sandbox.list", input, ctx.signal)
|
|
1504
1517
|
};
|
|
1505
1518
|
const searchFiles = {
|
|
@@ -1516,11 +1529,13 @@ function codeSkill(opts) {
|
|
|
1516
1529
|
},
|
|
1517
1530
|
additionalProperties: false
|
|
1518
1531
|
},
|
|
1532
|
+
concurrency: "parallel",
|
|
1519
1533
|
handler: (input, ctx) => call("sandbox.search", input, ctx.signal)
|
|
1520
1534
|
};
|
|
1521
1535
|
const graphTool = (name, tool, description, required) => ({
|
|
1522
1536
|
name,
|
|
1523
1537
|
description,
|
|
1538
|
+
concurrency: "parallel",
|
|
1524
1539
|
inputSchema: {
|
|
1525
1540
|
type: "object",
|
|
1526
1541
|
...required ? { required: ["query"] } : {},
|
|
@@ -1823,16 +1838,16 @@ function createCodePolicyGate(options) {
|
|
|
1823
1838
|
}
|
|
1824
1839
|
};
|
|
1825
1840
|
}
|
|
1826
|
-
function directoryPrefixes(
|
|
1841
|
+
function directoryPrefixes(paths2) {
|
|
1827
1842
|
const prefixes = /* @__PURE__ */ new Set(["."]);
|
|
1828
|
-
for (const path of
|
|
1843
|
+
for (const path of paths2) {
|
|
1829
1844
|
const parts = path.split("/");
|
|
1830
1845
|
for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
|
|
1831
1846
|
}
|
|
1832
1847
|
return [...prefixes].sort();
|
|
1833
1848
|
}
|
|
1834
|
-
async function safePrefix(base,
|
|
1835
|
-
const prefixes = directoryPrefixes(
|
|
1849
|
+
async function safePrefix(base, paths2, prefix) {
|
|
1850
|
+
const prefixes = directoryPrefixes(paths2);
|
|
1836
1851
|
const conversions = await conversionRegistry(
|
|
1837
1852
|
[await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
|
|
1838
1853
|
{ "code.prefixes.v1": prefixes }
|
|
@@ -1950,13 +1965,32 @@ function response(request, ok, content, details) {
|
|
|
1950
1965
|
var import_promises9 = require("fs/promises");
|
|
1951
1966
|
|
|
1952
1967
|
// src/code-tool-discovery.ts
|
|
1968
|
+
var import_node_child_process5 = require("child_process");
|
|
1953
1969
|
var import_promises7 = require("fs/promises");
|
|
1954
1970
|
var import_node_path8 = require("path");
|
|
1955
1971
|
var DEFAULT_MAX_FILES = 2e4;
|
|
1956
1972
|
var DEFAULT_MAX_RESULTS = 100;
|
|
1957
1973
|
var DEFAULT_MAX_FILE_BYTES = 512 * 1024;
|
|
1974
|
+
function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = registeredFiles) {
|
|
1975
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
1976
|
+
return {
|
|
1977
|
+
files(root) {
|
|
1978
|
+
const existing = cache2.get(root);
|
|
1979
|
+
if (existing) return existing;
|
|
1980
|
+
const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
|
|
1981
|
+
cache2.set(root, pending);
|
|
1982
|
+
void pending.catch(() => {
|
|
1983
|
+
if (cache2.get(root) === pending) cache2.delete(root);
|
|
1984
|
+
});
|
|
1985
|
+
return pending;
|
|
1986
|
+
},
|
|
1987
|
+
invalidate(root) {
|
|
1988
|
+
cache2.delete(root);
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1958
1992
|
async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
1959
|
-
const
|
|
1993
|
+
const paths2 = [];
|
|
1960
1994
|
const walk = async (directory) => {
|
|
1961
1995
|
for (const entry of await (0, import_promises7.readdir)(directory, { withFileTypes: true })) {
|
|
1962
1996
|
if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
@@ -1970,43 +2004,137 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
|
1970
2004
|
} catch {
|
|
1971
2005
|
continue;
|
|
1972
2006
|
}
|
|
1973
|
-
|
|
1974
|
-
if (
|
|
2007
|
+
paths2.push(path);
|
|
2008
|
+
if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
|
|
1975
2009
|
}
|
|
1976
2010
|
}
|
|
1977
2011
|
};
|
|
1978
2012
|
await walk((0, import_node_path8.resolve)(root));
|
|
1979
|
-
return
|
|
2013
|
+
return paths2.sort();
|
|
1980
2014
|
}
|
|
1981
|
-
function listWorkspace(
|
|
2015
|
+
function listWorkspace(paths2, options = {}) {
|
|
1982
2016
|
const max = options.maxEntries ?? 1e3;
|
|
1983
2017
|
const prefix = options.prefix?.replace(/\/+$/, "");
|
|
1984
|
-
const scoped = prefix ?
|
|
2018
|
+
const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
|
|
1985
2019
|
return scoped.slice(0, max);
|
|
1986
2020
|
}
|
|
1987
|
-
async function searchWorkspace(root,
|
|
1988
|
-
|
|
1989
|
-
if (!query) throw new TypeError("search query must be a non-empty string");
|
|
2021
|
+
async function searchWorkspace(root, paths2, options) {
|
|
2022
|
+
options.signal?.throwIfAborted();
|
|
2023
|
+
if (!options.query) throw new TypeError("search query must be a non-empty string");
|
|
1990
2024
|
const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
1991
2025
|
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
|
|
1992
|
-
const scoped = listWorkspace(
|
|
2026
|
+
const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
|
|
2027
|
+
if (scoped.length === 0) return [];
|
|
2028
|
+
try {
|
|
2029
|
+
return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
2030
|
+
} catch (error) {
|
|
2031
|
+
options.signal?.throwIfAborted();
|
|
2032
|
+
return fallbackSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
var MAX_NATIVE_ARG_BYTES = 96 * 1024;
|
|
2036
|
+
async function nativeSearch(root, paths2, options) {
|
|
2037
|
+
const batches = [];
|
|
2038
|
+
let batch = [];
|
|
2039
|
+
let bytes = 0;
|
|
2040
|
+
for (const path of paths2) {
|
|
2041
|
+
const size = Buffer.byteLength(path) + 1;
|
|
2042
|
+
if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
|
|
2043
|
+
batches.push(batch);
|
|
2044
|
+
batch = [];
|
|
2045
|
+
bytes = 0;
|
|
2046
|
+
}
|
|
2047
|
+
batch.push(path);
|
|
2048
|
+
bytes += size;
|
|
2049
|
+
}
|
|
2050
|
+
if (batch.length > 0) batches.push(batch);
|
|
2051
|
+
const matches = [];
|
|
2052
|
+
for (const files of batches) {
|
|
2053
|
+
const remaining = options.maxResults - matches.length;
|
|
2054
|
+
if (remaining <= 0) break;
|
|
2055
|
+
matches.push(...await nativeSearchBatch(root, files, options, remaining));
|
|
2056
|
+
}
|
|
2057
|
+
return matches;
|
|
2058
|
+
}
|
|
2059
|
+
function nativeSearchBatch(root, paths2, options, remaining) {
|
|
2060
|
+
return new Promise((resolveMatches, reject) => {
|
|
2061
|
+
const args = [
|
|
2062
|
+
"--fixed-strings",
|
|
2063
|
+
"--json",
|
|
2064
|
+
"--no-messages",
|
|
2065
|
+
"--sort=path",
|
|
2066
|
+
`--max-filesize=${options.maxFileBytes}`,
|
|
2067
|
+
"--max-columns=4096",
|
|
2068
|
+
"--max-columns-preview",
|
|
2069
|
+
options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
|
|
2070
|
+
"--",
|
|
2071
|
+
options.query,
|
|
2072
|
+
...paths2
|
|
2073
|
+
];
|
|
2074
|
+
const child = (0, import_node_child_process5.spawn)("rg", args, {
|
|
2075
|
+
cwd: root,
|
|
2076
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2077
|
+
...options.signal ? { signal: options.signal } : {}
|
|
2078
|
+
});
|
|
2079
|
+
const matches = [];
|
|
2080
|
+
let carry = "";
|
|
2081
|
+
let stopped = false;
|
|
2082
|
+
const consume = (line) => {
|
|
2083
|
+
if (matches.length >= remaining) return;
|
|
2084
|
+
let event;
|
|
2085
|
+
try {
|
|
2086
|
+
event = JSON.parse(line);
|
|
2087
|
+
} catch {
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
const path = event.data?.path?.text;
|
|
2091
|
+
const lineNumber = event.data?.line_number;
|
|
2092
|
+
const source = event.data?.lines?.text;
|
|
2093
|
+
if (event.type !== "match" || path === void 0 || lineNumber === void 0 || source === void 0) return;
|
|
2094
|
+
matches.push({ path, line: lineNumber, text: source.trim().slice(0, 240) });
|
|
2095
|
+
if (matches.length >= remaining) {
|
|
2096
|
+
stopped = true;
|
|
2097
|
+
child.kill();
|
|
2098
|
+
}
|
|
2099
|
+
};
|
|
2100
|
+
child.stdout.setEncoding("utf8");
|
|
2101
|
+
child.stdout.on("data", (chunk) => {
|
|
2102
|
+
carry += chunk;
|
|
2103
|
+
let newline = carry.indexOf("\n");
|
|
2104
|
+
while (newline >= 0) {
|
|
2105
|
+
consume(carry.slice(0, newline));
|
|
2106
|
+
carry = carry.slice(newline + 1);
|
|
2107
|
+
newline = carry.indexOf("\n");
|
|
2108
|
+
}
|
|
2109
|
+
});
|
|
2110
|
+
child.once("error", reject);
|
|
2111
|
+
child.once("close", (code) => {
|
|
2112
|
+
if (carry) consume(carry);
|
|
2113
|
+
if (stopped || code === 0 || code === 1) resolveMatches(matches);
|
|
2114
|
+
else reject(new Error(`native search exited with status ${code ?? "unknown"}`));
|
|
2115
|
+
});
|
|
2116
|
+
});
|
|
2117
|
+
}
|
|
2118
|
+
async function fallbackSearch(root, scoped, options) {
|
|
2119
|
+
const query = options.caseSensitive === false ? options.query.toLowerCase() : options.query;
|
|
1993
2120
|
const matches = [];
|
|
1994
2121
|
for (const path of scoped) {
|
|
1995
|
-
|
|
2122
|
+
options.signal?.throwIfAborted();
|
|
2123
|
+
if (matches.length >= options.maxResults) break;
|
|
1996
2124
|
let source;
|
|
1997
2125
|
try {
|
|
1998
2126
|
source = await (0, import_promises7.readFile)((0, import_node_path8.resolve)(root, path));
|
|
1999
2127
|
} catch {
|
|
2000
2128
|
continue;
|
|
2001
2129
|
}
|
|
2002
|
-
if (source.byteLength > maxFileBytes || source.includes(0)) continue;
|
|
2130
|
+
if (source.byteLength > options.maxFileBytes || source.includes(0)) continue;
|
|
2003
2131
|
const lines = source.toString("utf8").split("\n");
|
|
2004
2132
|
for (let index = 0; index < lines.length; index += 1) {
|
|
2005
2133
|
const raw = lines[index];
|
|
2006
2134
|
const haystack = options.caseSensitive === false ? raw.toLowerCase() : raw;
|
|
2007
2135
|
if (!haystack.includes(query)) continue;
|
|
2008
2136
|
matches.push({ path, line: index + 1, text: raw.trim().slice(0, 240) });
|
|
2009
|
-
if (matches.length >= maxResults) break;
|
|
2137
|
+
if (matches.length >= options.maxResults) break;
|
|
2010
2138
|
}
|
|
2011
2139
|
}
|
|
2012
2140
|
return matches;
|
|
@@ -2018,7 +2146,7 @@ var import_node_path9 = require("path");
|
|
|
2018
2146
|
var import_graph = require("@odla-ai/graph");
|
|
2019
2147
|
var import_code4 = require("@odla-ai/graph/code");
|
|
2020
2148
|
var cache = /* @__PURE__ */ new Map();
|
|
2021
|
-
function workspaceGraphs(workspaceDir,
|
|
2149
|
+
function workspaceGraphs(workspaceDir, paths2) {
|
|
2022
2150
|
const existing = cache.get(workspaceDir);
|
|
2023
2151
|
if (existing) return existing;
|
|
2024
2152
|
const read2 = (path) => (0, import_promises8.readFile)((0, import_node_path9.join)(workspaceDir, path), "utf8");
|
|
@@ -2026,11 +2154,14 @@ function workspaceGraphs(workspaceDir, paths) {
|
|
|
2026
2154
|
// No knownTables: a staged workspace may not carry migrations, and a filter
|
|
2027
2155
|
// that silently drops every table is worse than an unfiltered one. Callers
|
|
2028
2156
|
// with ground truth should build the graph themselves.
|
|
2029
|
-
graph: await (0, import_code4.buildCodeGraph)({ paths, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
|
|
2157
|
+
graph: await (0, import_code4.buildCodeGraph)({ paths: paths2, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
|
|
2030
2158
|
}))();
|
|
2031
2159
|
cache.set(workspaceDir, built);
|
|
2032
2160
|
return built;
|
|
2033
2161
|
}
|
|
2162
|
+
function forgetWorkspaceGraphs(workspaceDir) {
|
|
2163
|
+
cache.delete(workspaceDir);
|
|
2164
|
+
}
|
|
2034
2165
|
var shortId = (id) => id.slice(id.indexOf(":") + 1);
|
|
2035
2166
|
function renderOverview(graphs, prefix) {
|
|
2036
2167
|
const rows = (0, import_graph.rollup)(graphs.graph, import_code4.FILE, prefix === void 0 ? {} : { prefix });
|
|
@@ -2077,7 +2208,7 @@ var GRAPH_TOOLS = /* @__PURE__ */ new Set([
|
|
|
2077
2208
|
"sandbox.who_imports",
|
|
2078
2209
|
"sandbox.who_touches"
|
|
2079
2210
|
]);
|
|
2080
|
-
async function read(context, request, options, policy) {
|
|
2211
|
+
async function read(context, request, options, policy, registry) {
|
|
2081
2212
|
exactKeys(request.input, ["path", "startLine", "endLine"]);
|
|
2082
2213
|
const path = stringField(request.input, "path");
|
|
2083
2214
|
const startLine = optionalInteger(request.input.startLine) ?? 1;
|
|
@@ -2085,11 +2216,11 @@ async function read(context, request, options, policy) {
|
|
|
2085
2216
|
if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
|
|
2086
2217
|
throw new TypeError("requested line range exceeds its bound");
|
|
2087
2218
|
}
|
|
2088
|
-
const
|
|
2089
|
-
if (!
|
|
2219
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2220
|
+
if (!paths2.includes(path)) {
|
|
2090
2221
|
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.`);
|
|
2091
2222
|
}
|
|
2092
|
-
const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
|
|
2223
|
+
const allowed = await policy.read(policyContext(context, request, options, { paths: paths2, path, startLine, endLine }));
|
|
2093
2224
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2094
2225
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
2095
2226
|
const info = await (0, import_promises9.stat)(target);
|
|
@@ -2105,22 +2236,22 @@ async function read(context, request, options, policy) {
|
|
|
2105
2236
|
}
|
|
2106
2237
|
return response(request, true, content, { path, startLine, endLine: Math.min(endLine, lines.length) });
|
|
2107
2238
|
}
|
|
2108
|
-
async function list(context, request, options, policy) {
|
|
2239
|
+
async function list(context, request, options, policy, registry) {
|
|
2109
2240
|
exactKeys(request.input, ["prefix", "maxEntries"]);
|
|
2110
2241
|
const raw = request.input.prefix;
|
|
2111
2242
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
2112
2243
|
const maxEntries = optionalInteger(request.input.maxEntries) ?? 1e3;
|
|
2113
2244
|
if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
|
|
2114
|
-
const
|
|
2115
|
-
const allowed = await policy.list(policyContext(context, request, options, { paths, ...prefix ? { prefix } : {} }));
|
|
2245
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2246
|
+
const allowed = await policy.list(policyContext(context, request, options, { paths: paths2, ...prefix ? { prefix } : {} }));
|
|
2116
2247
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2117
|
-
const entries = listWorkspace(
|
|
2248
|
+
const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
|
|
2118
2249
|
if (!entries.length) {
|
|
2119
2250
|
return response(request, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
|
|
2120
2251
|
}
|
|
2121
|
-
const truncated = entries.length <
|
|
2122
|
-
const hint = !prefix &&
|
|
2123
|
-
\u2026 ${
|
|
2252
|
+
const truncated = entries.length < paths2.length && entries.length === maxEntries;
|
|
2253
|
+
const hint = !prefix && paths2.length > 500 ? `
|
|
2254
|
+
\u2026 ${paths2.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
|
|
2124
2255
|
return response(
|
|
2125
2256
|
request,
|
|
2126
2257
|
true,
|
|
@@ -2129,7 +2260,7 @@ async function list(context, request, options, policy) {
|
|
|
2129
2260
|
{ count: entries.length, truncated }
|
|
2130
2261
|
);
|
|
2131
2262
|
}
|
|
2132
|
-
async function search(context, request, options, policy) {
|
|
2263
|
+
async function search(context, request, options, policy, registry) {
|
|
2133
2264
|
exactKeys(request.input, ["query", "prefix", "maxResults", "caseSensitive"]);
|
|
2134
2265
|
const query = stringField(request.input, "query");
|
|
2135
2266
|
if (query.length > 512) throw new TypeError("search query exceeds its bound");
|
|
@@ -2138,21 +2269,22 @@ async function search(context, request, options, policy) {
|
|
|
2138
2269
|
const maxResults = optionalInteger(request.input.maxResults) ?? 100;
|
|
2139
2270
|
if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
|
|
2140
2271
|
const caseSensitive = request.input.caseSensitive === void 0 ? true : request.input.caseSensitive === true;
|
|
2141
|
-
const
|
|
2142
|
-
const allowed = await policy.search(policyContext(context, request, options, { paths, query, ...prefix ? { prefix } : {} }));
|
|
2272
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2273
|
+
const allowed = await policy.search(policyContext(context, request, options, { paths: paths2, query, ...prefix ? { prefix } : {} }));
|
|
2143
2274
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2144
|
-
const matches = await searchWorkspace(context.workspaceDir,
|
|
2275
|
+
const matches = await searchWorkspace(context.workspaceDir, paths2, {
|
|
2145
2276
|
query,
|
|
2146
2277
|
maxResults,
|
|
2147
2278
|
caseSensitive,
|
|
2148
|
-
...prefix ? { prefix } : {}
|
|
2279
|
+
...prefix ? { prefix } : {},
|
|
2280
|
+
...context.signal ? { signal: context.signal } : {}
|
|
2149
2281
|
});
|
|
2150
2282
|
if (!matches.length) return response(request, true, `No match for "${query}".`, { count: 0 });
|
|
2151
2283
|
return response(request, true, matches.map((match) => `${match.path}:${match.line}: ${match.text}`).join("\n"), {
|
|
2152
2284
|
count: matches.length
|
|
2153
2285
|
});
|
|
2154
2286
|
}
|
|
2155
|
-
async function graphQuery(context, request, options, policy) {
|
|
2287
|
+
async function graphQuery(context, request, options, policy, registry) {
|
|
2156
2288
|
exactKeys(request.input, ["query"]);
|
|
2157
2289
|
const raw = request.input.query;
|
|
2158
2290
|
const query = typeof raw === "string" ? raw : "";
|
|
@@ -2162,8 +2294,8 @@ async function graphQuery(context, request, options, policy) {
|
|
|
2162
2294
|
selector: query
|
|
2163
2295
|
}));
|
|
2164
2296
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2165
|
-
const
|
|
2166
|
-
const graphs = await workspaceGraphs(context.workspaceDir,
|
|
2297
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2298
|
+
const graphs = await workspaceGraphs(context.workspaceDir, paths2);
|
|
2167
2299
|
if (request.tool === "sandbox.overview") {
|
|
2168
2300
|
return response(request, true, renderOverview(graphs, query || void 0));
|
|
2169
2301
|
}
|
|
@@ -2178,23 +2310,38 @@ function createCodeToolBroker(options) {
|
|
|
2178
2310
|
validateOptions(options);
|
|
2179
2311
|
const recipes = new Map(options.recipes.map((recipe2) => [recipe2.id, recipe2]));
|
|
2180
2312
|
const policy = createCodePolicyGate(options);
|
|
2181
|
-
|
|
2313
|
+
const registry = createWorkspaceFileRegistry();
|
|
2314
|
+
let barrier = Promise.resolve();
|
|
2315
|
+
const activeReads = /* @__PURE__ */ new Set();
|
|
2182
2316
|
return {
|
|
2183
2317
|
execute(context, request) {
|
|
2184
|
-
|
|
2185
|
-
|
|
2318
|
+
if (isReadTool(request.tool)) {
|
|
2319
|
+
const result2 = barrier.then(() => route(context, request, options, recipes, policy, registry));
|
|
2320
|
+
const settled = result2.then(() => void 0, () => void 0);
|
|
2321
|
+
activeReads.add(settled);
|
|
2322
|
+
void settled.then(() => {
|
|
2323
|
+
activeReads.delete(settled);
|
|
2324
|
+
});
|
|
2325
|
+
return result2;
|
|
2326
|
+
}
|
|
2327
|
+
const earlierReads = [...activeReads];
|
|
2328
|
+
const result = barrier.then(() => Promise.all(earlierReads)).then(() => route(context, request, options, recipes, policy, registry));
|
|
2329
|
+
barrier = result.then(() => void 0, () => void 0);
|
|
2186
2330
|
return result;
|
|
2187
2331
|
}
|
|
2188
2332
|
};
|
|
2189
2333
|
}
|
|
2190
|
-
|
|
2334
|
+
function isReadTool(tool) {
|
|
2335
|
+
return tool === "sandbox.read" || tool === "sandbox.list" || tool === "sandbox.search" || GRAPH_TOOLS.has(tool);
|
|
2336
|
+
}
|
|
2337
|
+
async function route(context, request, options, recipes, policy, registry) {
|
|
2191
2338
|
try {
|
|
2192
2339
|
if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
|
|
2193
|
-
if (request.tool === "sandbox.read") return await read(context, request, options, policy);
|
|
2194
|
-
if (request.tool === "sandbox.list") return await list(context, request, options, policy);
|
|
2195
|
-
if (request.tool === "sandbox.search") return await search(context, request, options, policy);
|
|
2196
|
-
if (GRAPH_TOOLS.has(request.tool)) return await graphQuery(context, request, options, policy);
|
|
2197
|
-
if (request.tool === "sandbox.apply_patch") return await patch(context, request, options, policy);
|
|
2340
|
+
if (request.tool === "sandbox.read") return await read(context, request, options, policy, registry);
|
|
2341
|
+
if (request.tool === "sandbox.list") return await list(context, request, options, policy, registry);
|
|
2342
|
+
if (request.tool === "sandbox.search") return await search(context, request, options, policy, registry);
|
|
2343
|
+
if (GRAPH_TOOLS.has(request.tool)) return await graphQuery(context, request, options, policy, registry);
|
|
2344
|
+
if (request.tool === "sandbox.apply_patch") return await patch(context, request, options, policy, registry);
|
|
2198
2345
|
return await recipe(context, request, options, recipes, policy);
|
|
2199
2346
|
} catch (reason) {
|
|
2200
2347
|
return response(request, false, toolFailureMessage(reason));
|
|
@@ -2209,17 +2356,19 @@ function toolFailureMessage(reason) {
|
|
|
2209
2356
|
if (code === "EACCES" || code === "EPERM") return "that path is not readable through this tool";
|
|
2210
2357
|
return "tool failed closed";
|
|
2211
2358
|
}
|
|
2212
|
-
async function patch(context, request, options, policy) {
|
|
2359
|
+
async function patch(context, request, options, policy, registry) {
|
|
2213
2360
|
exactKeys(request.input, ["patch"]);
|
|
2214
2361
|
const value = stringField(request.input, "patch");
|
|
2215
|
-
const
|
|
2216
|
-
if (
|
|
2362
|
+
const paths2 = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
|
|
2363
|
+
if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
2217
2364
|
throw new TypeError("patch targets a read-only reference source");
|
|
2218
2365
|
}
|
|
2219
2366
|
const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
|
|
2220
2367
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2221
|
-
await applyCodePatch(context.workspaceDir, value,
|
|
2222
|
-
|
|
2368
|
+
await applyCodePatch(context.workspaceDir, value, paths2);
|
|
2369
|
+
registry.invalidate(context.workspaceDir);
|
|
2370
|
+
forgetWorkspaceGraphs(context.workspaceDir);
|
|
2371
|
+
return response(request, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
|
|
2223
2372
|
}
|
|
2224
2373
|
async function recipe(context, request, options, recipes, policy) {
|
|
2225
2374
|
exactKeys(request.input, ["recipeId"]);
|
|
@@ -2580,12 +2729,129 @@ var import_node_crypto4 = require("crypto");
|
|
|
2580
2729
|
async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
2581
2730
|
const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
|
|
2582
2731
|
refs.push(eventId);
|
|
2583
|
-
const
|
|
2732
|
+
const attributed = { ...event, interactionId: command.commandId };
|
|
2733
|
+
const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
|
|
2584
2734
|
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
2585
2735
|
}
|
|
2586
2736
|
var digestRuntimeValue = (value) => `sha256:${(0, import_node_crypto4.createHash)("sha256").update(value).digest("hex")}`;
|
|
2587
2737
|
var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
|
|
2588
2738
|
|
|
2739
|
+
// src/code-tool-presentation.ts
|
|
2740
|
+
var text = (value, maximum) => {
|
|
2741
|
+
if (typeof value !== "string") return void 0;
|
|
2742
|
+
const bounded = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
|
|
2743
|
+
return bounded ? bounded.slice(0, maximum) : void 0;
|
|
2744
|
+
};
|
|
2745
|
+
var integer2 = (value) => Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : void 0;
|
|
2746
|
+
var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2747
|
+
var excerpt = (value, tail = false) => {
|
|
2748
|
+
if (typeof value !== "string") return void 0;
|
|
2749
|
+
const safe = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
|
|
2750
|
+
const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
|
|
2751
|
+
if (!source) return void 0;
|
|
2752
|
+
const lines = source.split("\n").filter((line) => line.trim()).map((line) => line.slice(0, 240));
|
|
2753
|
+
const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
|
|
2754
|
+
return text(selected.join("\n"), 2400);
|
|
2755
|
+
};
|
|
2756
|
+
var paths = (value) => {
|
|
2757
|
+
if (!Array.isArray(value)) return void 0;
|
|
2758
|
+
const items = value.flatMap((item) => {
|
|
2759
|
+
const path = text(item, 1024);
|
|
2760
|
+
return path ? [path] : [];
|
|
2761
|
+
}).slice(0, 12);
|
|
2762
|
+
return items.length ? items : void 0;
|
|
2763
|
+
};
|
|
2764
|
+
function patchStats(value) {
|
|
2765
|
+
if (typeof value !== "string") return {};
|
|
2766
|
+
let additions = 0;
|
|
2767
|
+
let deletions = 0;
|
|
2768
|
+
for (const line of value.slice(0, 262144).split("\n")) {
|
|
2769
|
+
if (line.startsWith("+++") || line.startsWith("---")) continue;
|
|
2770
|
+
if (line.startsWith("+")) additions += 1;
|
|
2771
|
+
else if (line.startsWith("-")) deletions += 1;
|
|
2772
|
+
}
|
|
2773
|
+
return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
|
|
2774
|
+
}
|
|
2775
|
+
function searchResults(value) {
|
|
2776
|
+
if (typeof value !== "string") return void 0;
|
|
2777
|
+
const results = value.split("\n").flatMap((line) => {
|
|
2778
|
+
const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line);
|
|
2779
|
+
if (!match) return [];
|
|
2780
|
+
const lineNumber = Number(match[2]);
|
|
2781
|
+
const itemText = text(match[3], 240);
|
|
2782
|
+
if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
|
|
2783
|
+
return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
|
|
2784
|
+
}).slice(0, 5);
|
|
2785
|
+
return results.length ? results : void 0;
|
|
2786
|
+
}
|
|
2787
|
+
function codeToolRequestPresentation(request) {
|
|
2788
|
+
const input = request.input;
|
|
2789
|
+
if (request.tool === "sandbox.read") {
|
|
2790
|
+
const path = text(input.path, 1024);
|
|
2791
|
+
if (!path) return void 0;
|
|
2792
|
+
const startLine = integer2(input.startLine);
|
|
2793
|
+
const endLine = integer2(input.endLine);
|
|
2794
|
+
return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
|
|
2795
|
+
}
|
|
2796
|
+
if (request.tool === "sandbox.list") {
|
|
2797
|
+
const scope = text(input.prefix, 1024);
|
|
2798
|
+
return { kind: "list", ...scope ? { scope } : {} };
|
|
2799
|
+
}
|
|
2800
|
+
if (request.tool === "sandbox.search" || request.tool === "sandbox.overview" || request.tool === "sandbox.where_is" || request.tool === "sandbox.who_imports" || request.tool === "sandbox.who_touches") {
|
|
2801
|
+
const query = text(input.query, 512);
|
|
2802
|
+
const scope = request.tool === "sandbox.search" ? text(input.prefix, 1024) : void 0;
|
|
2803
|
+
if (request.tool === "sandbox.search" && !query) return void 0;
|
|
2804
|
+
return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
|
|
2805
|
+
}
|
|
2806
|
+
if (request.tool === "sandbox.apply_patch") {
|
|
2807
|
+
return { kind: "patch", ...patchStats(input.patch) };
|
|
2808
|
+
}
|
|
2809
|
+
const recipeId = text(input.recipeId, 120);
|
|
2810
|
+
return recipeId ? { kind: "recipe", recipeId } : void 0;
|
|
2811
|
+
}
|
|
2812
|
+
function codeToolResultPresentation(request, response2) {
|
|
2813
|
+
const started = codeToolRequestPresentation(request);
|
|
2814
|
+
if (!started || !response2.ok) return started;
|
|
2815
|
+
const details = record3(response2.details);
|
|
2816
|
+
if (started.kind === "read") {
|
|
2817
|
+
return {
|
|
2818
|
+
...started,
|
|
2819
|
+
...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
|
|
2820
|
+
...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
|
|
2821
|
+
...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
if (started.kind === "list") {
|
|
2825
|
+
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);
|
|
2826
|
+
return {
|
|
2827
|
+
...started,
|
|
2828
|
+
...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
|
|
2829
|
+
...listed.length ? { paths: listed } : {}
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2832
|
+
if (started.kind === "query") {
|
|
2833
|
+
const results = request.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
|
|
2834
|
+
const resultExcerpt = request.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
|
|
2835
|
+
return {
|
|
2836
|
+
...started,
|
|
2837
|
+
...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
|
|
2838
|
+
...results ? { results } : {},
|
|
2839
|
+
...resultExcerpt ? { excerpt: resultExcerpt } : {}
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
if (started.kind === "patch") {
|
|
2843
|
+
return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
|
|
2844
|
+
}
|
|
2845
|
+
const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
|
|
2846
|
+
return {
|
|
2847
|
+
...started,
|
|
2848
|
+
...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
|
|
2849
|
+
...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
|
|
2850
|
+
...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
|
|
2851
|
+
...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
|
|
2852
|
+
};
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2589
2855
|
// src/code-runtime-engine.ts
|
|
2590
2856
|
var TheseusRuntimeEngine = class {
|
|
2591
2857
|
constructor(options) {
|
|
@@ -2820,18 +3086,29 @@ var TheseusRuntimeEngine = class {
|
|
|
2820
3086
|
return {
|
|
2821
3087
|
execute: async (context, request) => {
|
|
2822
3088
|
const startedAt = Date.now();
|
|
3089
|
+
const operationId = digestRuntimeValue(`${command.commandId}:${request.requestId}`);
|
|
3090
|
+
const startedPresentation = codeToolRequestPresentation(request);
|
|
2823
3091
|
await this.#event(
|
|
2824
3092
|
command,
|
|
2825
|
-
{
|
|
3093
|
+
{
|
|
3094
|
+
type: "tool",
|
|
3095
|
+
phase: "started",
|
|
3096
|
+
tool: request.tool,
|
|
3097
|
+
operationId,
|
|
3098
|
+
...startedPresentation ? { presentation: startedPresentation } : {}
|
|
3099
|
+
},
|
|
2826
3100
|
active.conversationRefs
|
|
2827
3101
|
).catch(() => void 0);
|
|
2828
3102
|
const response2 = await broker.execute(context, request);
|
|
3103
|
+
const completedPresentation = codeToolResultPresentation(request, response2);
|
|
2829
3104
|
await this.#event(command, {
|
|
2830
3105
|
type: "tool",
|
|
2831
3106
|
phase: "completed",
|
|
2832
3107
|
tool: request.tool,
|
|
2833
3108
|
ok: response2.ok,
|
|
2834
|
-
durationMs: Date.now() - startedAt
|
|
3109
|
+
durationMs: Date.now() - startedAt,
|
|
3110
|
+
operationId,
|
|
3111
|
+
...completedPresentation ? { presentation: completedPresentation } : {}
|
|
2835
3112
|
}, active.conversationRefs).catch(() => void 0);
|
|
2836
3113
|
return response2;
|
|
2837
3114
|
}
|