@odla-ai/harness 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-K76I2TCQ.js → chunk-CR6RE3A2.js} +3 -3
- package/dist/{chunk-GYWQM76X.js → chunk-ISR434K7.js} +186 -58
- package/dist/chunk-ISR434K7.js.map +1 -0
- package/dist/{chunk-XWXRNPGY.js → chunk-NWNBSX56.js} +4 -4
- package/dist/{chunk-FVOMJKWK.js → chunk-Q7CKOT7T.js} +2 -2
- package/dist/{chunk-LNQNFGQC.js → chunk-RXNHCGWE.js} +1 -1
- package/dist/chunk-RXNHCGWE.js.map +1 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/code-runtime-cli.cjs +186 -58
- package/dist/code-runtime-cli.cjs.map +1 -1
- package/dist/code-runtime-cli.js +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/node.cjs +195 -67
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +5 -5
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/dist/{types-CK5EKmKm.d.cts → types-BNJikP5h.d.cts} +53 -5
- package/dist/{types-CK5EKmKm.d.ts → types-BNJikP5h.d.ts} +53 -5
- package/package.json +1 -1
- package/dist/chunk-GYWQM76X.js.map +0 -1
- package/dist/chunk-LNQNFGQC.js.map +0 -1
- /package/dist/{chunk-K76I2TCQ.js.map → chunk-CR6RE3A2.js.map} +0 -0
- /package/dist/{chunk-XWXRNPGY.js.map → chunk-NWNBSX56.js.map} +0 -0
- /package/dist/{chunk-FVOMJKWK.js.map → chunk-Q7CKOT7T.js.map} +0 -0
|
@@ -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";
|
|
@@ -1838,16 +1838,16 @@ function createCodePolicyGate(options) {
|
|
|
1838
1838
|
}
|
|
1839
1839
|
};
|
|
1840
1840
|
}
|
|
1841
|
-
function directoryPrefixes(
|
|
1841
|
+
function directoryPrefixes(paths2) {
|
|
1842
1842
|
const prefixes = /* @__PURE__ */ new Set(["."]);
|
|
1843
|
-
for (const path of
|
|
1843
|
+
for (const path of paths2) {
|
|
1844
1844
|
const parts = path.split("/");
|
|
1845
1845
|
for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
|
|
1846
1846
|
}
|
|
1847
1847
|
return [...prefixes].sort();
|
|
1848
1848
|
}
|
|
1849
|
-
async function safePrefix(base,
|
|
1850
|
-
const prefixes = directoryPrefixes(
|
|
1849
|
+
async function safePrefix(base, paths2, prefix) {
|
|
1850
|
+
const prefixes = directoryPrefixes(paths2);
|
|
1851
1851
|
const conversions = await conversionRegistry(
|
|
1852
1852
|
[await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
|
|
1853
1853
|
{ "code.prefixes.v1": prefixes }
|
|
@@ -1977,7 +1977,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
1977
1977
|
files(root) {
|
|
1978
1978
|
const existing = cache2.get(root);
|
|
1979
1979
|
if (existing) return existing;
|
|
1980
|
-
const pending = enumerate(root, limit).then((
|
|
1980
|
+
const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
|
|
1981
1981
|
cache2.set(root, pending);
|
|
1982
1982
|
void pending.catch(() => {
|
|
1983
1983
|
if (cache2.get(root) === pending) cache2.delete(root);
|
|
@@ -1990,7 +1990,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
1990
1990
|
};
|
|
1991
1991
|
}
|
|
1992
1992
|
async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
1993
|
-
const
|
|
1993
|
+
const paths2 = [];
|
|
1994
1994
|
const walk = async (directory) => {
|
|
1995
1995
|
for (const entry of await (0, import_promises7.readdir)(directory, { withFileTypes: true })) {
|
|
1996
1996
|
if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
@@ -2004,26 +2004,26 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
|
2004
2004
|
} catch {
|
|
2005
2005
|
continue;
|
|
2006
2006
|
}
|
|
2007
|
-
|
|
2008
|
-
if (
|
|
2007
|
+
paths2.push(path);
|
|
2008
|
+
if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
|
|
2009
2009
|
}
|
|
2010
2010
|
}
|
|
2011
2011
|
};
|
|
2012
2012
|
await walk((0, import_node_path8.resolve)(root));
|
|
2013
|
-
return
|
|
2013
|
+
return paths2.sort();
|
|
2014
2014
|
}
|
|
2015
|
-
function listWorkspace(
|
|
2015
|
+
function listWorkspace(paths2, options = {}) {
|
|
2016
2016
|
const max = options.maxEntries ?? 1e3;
|
|
2017
2017
|
const prefix = options.prefix?.replace(/\/+$/, "");
|
|
2018
|
-
const scoped = prefix ?
|
|
2018
|
+
const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
|
|
2019
2019
|
return scoped.slice(0, max);
|
|
2020
2020
|
}
|
|
2021
|
-
async function searchWorkspace(root,
|
|
2021
|
+
async function searchWorkspace(root, paths2, options) {
|
|
2022
2022
|
options.signal?.throwIfAborted();
|
|
2023
2023
|
if (!options.query) throw new TypeError("search query must be a non-empty string");
|
|
2024
2024
|
const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
2025
2025
|
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
|
|
2026
|
-
const scoped = listWorkspace(
|
|
2026
|
+
const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
|
|
2027
2027
|
if (scoped.length === 0) return [];
|
|
2028
2028
|
try {
|
|
2029
2029
|
return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
@@ -2033,11 +2033,11 @@ async function searchWorkspace(root, paths, options) {
|
|
|
2033
2033
|
}
|
|
2034
2034
|
}
|
|
2035
2035
|
var MAX_NATIVE_ARG_BYTES = 96 * 1024;
|
|
2036
|
-
async function nativeSearch(root,
|
|
2036
|
+
async function nativeSearch(root, paths2, options) {
|
|
2037
2037
|
const batches = [];
|
|
2038
2038
|
let batch = [];
|
|
2039
2039
|
let bytes = 0;
|
|
2040
|
-
for (const path of
|
|
2040
|
+
for (const path of paths2) {
|
|
2041
2041
|
const size = Buffer.byteLength(path) + 1;
|
|
2042
2042
|
if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
|
|
2043
2043
|
batches.push(batch);
|
|
@@ -2056,7 +2056,7 @@ async function nativeSearch(root, paths, options) {
|
|
|
2056
2056
|
}
|
|
2057
2057
|
return matches;
|
|
2058
2058
|
}
|
|
2059
|
-
function nativeSearchBatch(root,
|
|
2059
|
+
function nativeSearchBatch(root, paths2, options, remaining) {
|
|
2060
2060
|
return new Promise((resolveMatches, reject) => {
|
|
2061
2061
|
const args = [
|
|
2062
2062
|
"--fixed-strings",
|
|
@@ -2069,7 +2069,7 @@ function nativeSearchBatch(root, paths, options, remaining) {
|
|
|
2069
2069
|
options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
|
|
2070
2070
|
"--",
|
|
2071
2071
|
options.query,
|
|
2072
|
-
...
|
|
2072
|
+
...paths2
|
|
2073
2073
|
];
|
|
2074
2074
|
const child = (0, import_node_child_process5.spawn)("rg", args, {
|
|
2075
2075
|
cwd: root,
|
|
@@ -2146,7 +2146,7 @@ var import_node_path9 = require("path");
|
|
|
2146
2146
|
var import_graph = require("@odla-ai/graph");
|
|
2147
2147
|
var import_code4 = require("@odla-ai/graph/code");
|
|
2148
2148
|
var cache = /* @__PURE__ */ new Map();
|
|
2149
|
-
function workspaceGraphs(workspaceDir,
|
|
2149
|
+
function workspaceGraphs(workspaceDir, paths2) {
|
|
2150
2150
|
const existing = cache.get(workspaceDir);
|
|
2151
2151
|
if (existing) return existing;
|
|
2152
2152
|
const read2 = (path) => (0, import_promises8.readFile)((0, import_node_path9.join)(workspaceDir, path), "utf8");
|
|
@@ -2154,7 +2154,7 @@ function workspaceGraphs(workspaceDir, paths) {
|
|
|
2154
2154
|
// No knownTables: a staged workspace may not carry migrations, and a filter
|
|
2155
2155
|
// that silently drops every table is worse than an unfiltered one. Callers
|
|
2156
2156
|
// with ground truth should build the graph themselves.
|
|
2157
|
-
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.") } })
|
|
2158
2158
|
}))();
|
|
2159
2159
|
cache.set(workspaceDir, built);
|
|
2160
2160
|
return built;
|
|
@@ -2216,11 +2216,11 @@ async function read(context, request, options, policy, registry) {
|
|
|
2216
2216
|
if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
|
|
2217
2217
|
throw new TypeError("requested line range exceeds its bound");
|
|
2218
2218
|
}
|
|
2219
|
-
const
|
|
2220
|
-
if (!
|
|
2219
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2220
|
+
if (!paths2.includes(path)) {
|
|
2221
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.`);
|
|
2222
2222
|
}
|
|
2223
|
-
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 }));
|
|
2224
2224
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2225
2225
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
2226
2226
|
const info = await (0, import_promises9.stat)(target);
|
|
@@ -2242,16 +2242,16 @@ async function list(context, request, options, policy, registry) {
|
|
|
2242
2242
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
2243
2243
|
const maxEntries = optionalInteger(request.input.maxEntries) ?? 1e3;
|
|
2244
2244
|
if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
|
|
2245
|
-
const
|
|
2246
|
-
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 } : {} }));
|
|
2247
2247
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2248
|
-
const entries = listWorkspace(
|
|
2248
|
+
const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
|
|
2249
2249
|
if (!entries.length) {
|
|
2250
2250
|
return response(request, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
|
|
2251
2251
|
}
|
|
2252
|
-
const truncated = entries.length <
|
|
2253
|
-
const hint = !prefix &&
|
|
2254
|
-
\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.` : "";
|
|
2255
2255
|
return response(
|
|
2256
2256
|
request,
|
|
2257
2257
|
true,
|
|
@@ -2269,10 +2269,10 @@ async function search(context, request, options, policy, registry) {
|
|
|
2269
2269
|
const maxResults = optionalInteger(request.input.maxResults) ?? 100;
|
|
2270
2270
|
if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
|
|
2271
2271
|
const caseSensitive = request.input.caseSensitive === void 0 ? true : request.input.caseSensitive === true;
|
|
2272
|
-
const
|
|
2273
|
-
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 } : {} }));
|
|
2274
2274
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2275
|
-
const matches = await searchWorkspace(context.workspaceDir,
|
|
2275
|
+
const matches = await searchWorkspace(context.workspaceDir, paths2, {
|
|
2276
2276
|
query,
|
|
2277
2277
|
maxResults,
|
|
2278
2278
|
caseSensitive,
|
|
@@ -2294,8 +2294,8 @@ async function graphQuery(context, request, options, policy, registry) {
|
|
|
2294
2294
|
selector: query
|
|
2295
2295
|
}));
|
|
2296
2296
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2297
|
-
const
|
|
2298
|
-
const graphs = await workspaceGraphs(context.workspaceDir,
|
|
2297
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2298
|
+
const graphs = await workspaceGraphs(context.workspaceDir, paths2);
|
|
2299
2299
|
if (request.tool === "sandbox.overview") {
|
|
2300
2300
|
return response(request, true, renderOverview(graphs, query || void 0));
|
|
2301
2301
|
}
|
|
@@ -2359,16 +2359,16 @@ function toolFailureMessage(reason) {
|
|
|
2359
2359
|
async function patch(context, request, options, policy, registry) {
|
|
2360
2360
|
exactKeys(request.input, ["patch"]);
|
|
2361
2361
|
const value = stringField(request.input, "patch");
|
|
2362
|
-
const
|
|
2363
|
-
if (
|
|
2362
|
+
const paths2 = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
|
|
2363
|
+
if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
2364
2364
|
throw new TypeError("patch targets a read-only reference source");
|
|
2365
2365
|
}
|
|
2366
2366
|
const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
|
|
2367
2367
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2368
|
-
await applyCodePatch(context.workspaceDir, value,
|
|
2368
|
+
await applyCodePatch(context.workspaceDir, value, paths2);
|
|
2369
2369
|
registry.invalidate(context.workspaceDir);
|
|
2370
2370
|
forgetWorkspaceGraphs(context.workspaceDir);
|
|
2371
|
-
return response(request, true, `Applied patch to ${
|
|
2371
|
+
return response(request, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
|
|
2372
2372
|
}
|
|
2373
2373
|
async function recipe(context, request, options, recipes, policy) {
|
|
2374
2374
|
exactKeys(request.input, ["recipeId"]);
|
|
@@ -2729,12 +2729,129 @@ var import_node_crypto4 = require("crypto");
|
|
|
2729
2729
|
async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
2730
2730
|
const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
|
|
2731
2731
|
refs.push(eventId);
|
|
2732
|
-
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;
|
|
2733
2734
|
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
2734
2735
|
}
|
|
2735
2736
|
var digestRuntimeValue = (value) => `sha256:${(0, import_node_crypto4.createHash)("sha256").update(value).digest("hex")}`;
|
|
2736
2737
|
var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
|
|
2737
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
|
+
|
|
2738
2855
|
// src/code-runtime-engine.ts
|
|
2739
2856
|
var TheseusRuntimeEngine = class {
|
|
2740
2857
|
constructor(options) {
|
|
@@ -2969,18 +3086,29 @@ var TheseusRuntimeEngine = class {
|
|
|
2969
3086
|
return {
|
|
2970
3087
|
execute: async (context, request) => {
|
|
2971
3088
|
const startedAt = Date.now();
|
|
3089
|
+
const operationId = digestRuntimeValue(`${command.commandId}:${request.requestId}`);
|
|
3090
|
+
const startedPresentation = codeToolRequestPresentation(request);
|
|
2972
3091
|
await this.#event(
|
|
2973
3092
|
command,
|
|
2974
|
-
{
|
|
3093
|
+
{
|
|
3094
|
+
type: "tool",
|
|
3095
|
+
phase: "started",
|
|
3096
|
+
tool: request.tool,
|
|
3097
|
+
operationId,
|
|
3098
|
+
...startedPresentation ? { presentation: startedPresentation } : {}
|
|
3099
|
+
},
|
|
2975
3100
|
active.conversationRefs
|
|
2976
3101
|
).catch(() => void 0);
|
|
2977
3102
|
const response2 = await broker.execute(context, request);
|
|
3103
|
+
const completedPresentation = codeToolResultPresentation(request, response2);
|
|
2978
3104
|
await this.#event(command, {
|
|
2979
3105
|
type: "tool",
|
|
2980
3106
|
phase: "completed",
|
|
2981
3107
|
tool: request.tool,
|
|
2982
3108
|
ok: response2.ok,
|
|
2983
|
-
durationMs: Date.now() - startedAt
|
|
3109
|
+
durationMs: Date.now() - startedAt,
|
|
3110
|
+
operationId,
|
|
3111
|
+
...completedPresentation ? { presentation: completedPresentation } : {}
|
|
2984
3112
|
}, active.conversationRefs).catch(() => void 0);
|
|
2985
3113
|
return response2;
|
|
2986
3114
|
}
|