@odla-ai/harness 0.7.1 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-K76I2TCQ.js → chunk-CR6RE3A2.js} +3 -3
- package/dist/{chunk-GYWQM76X.js → chunk-IWVGSWY6.js} +202 -59
- package/dist/chunk-IWVGSWY6.js.map +1 -0
- package/dist/{chunk-XWXRNPGY.js → chunk-NWNBSX56.js} +4 -4
- package/dist/{chunk-FVOMJKWK.js → chunk-Q7CKOT7T.js} +2 -2
- package/dist/{chunk-LNQNFGQC.js → chunk-RXNHCGWE.js} +1 -1
- package/dist/chunk-RXNHCGWE.js.map +1 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/code-runtime-cli.cjs +202 -59
- package/dist/code-runtime-cli.cjs.map +1 -1
- package/dist/code-runtime-cli.js +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/node.cjs +211 -68
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +15 -1
- package/dist/node.d.ts +15 -1
- package/dist/node.js +5 -5
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/dist/{types-CK5EKmKm.d.cts → types-BNJikP5h.d.cts} +53 -5
- package/dist/{types-CK5EKmKm.d.ts → types-BNJikP5h.d.ts} +53 -5
- package/package.json +1 -1
- package/dist/chunk-GYWQM76X.js.map +0 -1
- package/dist/chunk-LNQNFGQC.js.map +0 -1
- /package/dist/{chunk-K76I2TCQ.js.map → chunk-CR6RE3A2.js.map} +0 -0
- /package/dist/{chunk-XWXRNPGY.js.map → chunk-NWNBSX56.js.map} +0 -0
- /package/dist/{chunk-FVOMJKWK.js.map → chunk-Q7CKOT7T.js.map} +0 -0
|
@@ -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";
|
|
@@ -1626,6 +1626,7 @@ async function runCodeAgentAttempt(options) {
|
|
|
1626
1626
|
model: "brokered",
|
|
1627
1627
|
surface,
|
|
1628
1628
|
...options.recipeIds ? { recipeIds: options.recipeIds } : {},
|
|
1629
|
+
...options.extraSkills ? { extraSkills: options.extraSkills } : {},
|
|
1629
1630
|
...options.maxSteps === void 0 ? {} : { maxSteps: options.maxSteps },
|
|
1630
1631
|
...options.budget ? { budget: options.budget } : {},
|
|
1631
1632
|
...options.signal ? { signal: options.signal } : {},
|
|
@@ -1657,6 +1658,18 @@ Finish with a concise, non-empty answer to the owner. Do not call tools or promi
|
|
|
1657
1658
|
}
|
|
1658
1659
|
}
|
|
1659
1660
|
|
|
1661
|
+
// src/code-runtime-session-skills.ts
|
|
1662
|
+
async function sessionSkillsFor(options, command) {
|
|
1663
|
+
try {
|
|
1664
|
+
return await options.sessionSkills?.(command) ?? [];
|
|
1665
|
+
} catch (cause) {
|
|
1666
|
+
options.onDiagnostic?.(
|
|
1667
|
+
`session skills unavailable, continuing with code tools only: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
1668
|
+
);
|
|
1669
|
+
return [];
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1660
1673
|
// src/code-runtime-inference.ts
|
|
1661
1674
|
async function handleCodeRuntimeInference(input) {
|
|
1662
1675
|
const { command, request, state } = input;
|
|
@@ -1838,16 +1851,16 @@ function createCodePolicyGate(options) {
|
|
|
1838
1851
|
}
|
|
1839
1852
|
};
|
|
1840
1853
|
}
|
|
1841
|
-
function directoryPrefixes(
|
|
1854
|
+
function directoryPrefixes(paths2) {
|
|
1842
1855
|
const prefixes = /* @__PURE__ */ new Set(["."]);
|
|
1843
|
-
for (const path of
|
|
1856
|
+
for (const path of paths2) {
|
|
1844
1857
|
const parts = path.split("/");
|
|
1845
1858
|
for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
|
|
1846
1859
|
}
|
|
1847
1860
|
return [...prefixes].sort();
|
|
1848
1861
|
}
|
|
1849
|
-
async function safePrefix(base,
|
|
1850
|
-
const prefixes = directoryPrefixes(
|
|
1862
|
+
async function safePrefix(base, paths2, prefix) {
|
|
1863
|
+
const prefixes = directoryPrefixes(paths2);
|
|
1851
1864
|
const conversions = await conversionRegistry(
|
|
1852
1865
|
[await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
|
|
1853
1866
|
{ "code.prefixes.v1": prefixes }
|
|
@@ -1977,7 +1990,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
1977
1990
|
files(root) {
|
|
1978
1991
|
const existing = cache2.get(root);
|
|
1979
1992
|
if (existing) return existing;
|
|
1980
|
-
const pending = enumerate(root, limit).then((
|
|
1993
|
+
const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
|
|
1981
1994
|
cache2.set(root, pending);
|
|
1982
1995
|
void pending.catch(() => {
|
|
1983
1996
|
if (cache2.get(root) === pending) cache2.delete(root);
|
|
@@ -1990,7 +2003,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
1990
2003
|
};
|
|
1991
2004
|
}
|
|
1992
2005
|
async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
1993
|
-
const
|
|
2006
|
+
const paths2 = [];
|
|
1994
2007
|
const walk = async (directory) => {
|
|
1995
2008
|
for (const entry of await (0, import_promises7.readdir)(directory, { withFileTypes: true })) {
|
|
1996
2009
|
if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
@@ -2004,26 +2017,26 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
|
2004
2017
|
} catch {
|
|
2005
2018
|
continue;
|
|
2006
2019
|
}
|
|
2007
|
-
|
|
2008
|
-
if (
|
|
2020
|
+
paths2.push(path);
|
|
2021
|
+
if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
|
|
2009
2022
|
}
|
|
2010
2023
|
}
|
|
2011
2024
|
};
|
|
2012
2025
|
await walk((0, import_node_path8.resolve)(root));
|
|
2013
|
-
return
|
|
2026
|
+
return paths2.sort();
|
|
2014
2027
|
}
|
|
2015
|
-
function listWorkspace(
|
|
2028
|
+
function listWorkspace(paths2, options = {}) {
|
|
2016
2029
|
const max = options.maxEntries ?? 1e3;
|
|
2017
2030
|
const prefix = options.prefix?.replace(/\/+$/, "");
|
|
2018
|
-
const scoped = prefix ?
|
|
2031
|
+
const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
|
|
2019
2032
|
return scoped.slice(0, max);
|
|
2020
2033
|
}
|
|
2021
|
-
async function searchWorkspace(root,
|
|
2034
|
+
async function searchWorkspace(root, paths2, options) {
|
|
2022
2035
|
options.signal?.throwIfAborted();
|
|
2023
2036
|
if (!options.query) throw new TypeError("search query must be a non-empty string");
|
|
2024
2037
|
const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
2025
2038
|
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
|
|
2026
|
-
const scoped = listWorkspace(
|
|
2039
|
+
const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
|
|
2027
2040
|
if (scoped.length === 0) return [];
|
|
2028
2041
|
try {
|
|
2029
2042
|
return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
@@ -2033,11 +2046,11 @@ async function searchWorkspace(root, paths, options) {
|
|
|
2033
2046
|
}
|
|
2034
2047
|
}
|
|
2035
2048
|
var MAX_NATIVE_ARG_BYTES = 96 * 1024;
|
|
2036
|
-
async function nativeSearch(root,
|
|
2049
|
+
async function nativeSearch(root, paths2, options) {
|
|
2037
2050
|
const batches = [];
|
|
2038
2051
|
let batch = [];
|
|
2039
2052
|
let bytes = 0;
|
|
2040
|
-
for (const path of
|
|
2053
|
+
for (const path of paths2) {
|
|
2041
2054
|
const size = Buffer.byteLength(path) + 1;
|
|
2042
2055
|
if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
|
|
2043
2056
|
batches.push(batch);
|
|
@@ -2056,7 +2069,7 @@ async function nativeSearch(root, paths, options) {
|
|
|
2056
2069
|
}
|
|
2057
2070
|
return matches;
|
|
2058
2071
|
}
|
|
2059
|
-
function nativeSearchBatch(root,
|
|
2072
|
+
function nativeSearchBatch(root, paths2, options, remaining) {
|
|
2060
2073
|
return new Promise((resolveMatches, reject) => {
|
|
2061
2074
|
const args = [
|
|
2062
2075
|
"--fixed-strings",
|
|
@@ -2069,7 +2082,7 @@ function nativeSearchBatch(root, paths, options, remaining) {
|
|
|
2069
2082
|
options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
|
|
2070
2083
|
"--",
|
|
2071
2084
|
options.query,
|
|
2072
|
-
...
|
|
2085
|
+
...paths2
|
|
2073
2086
|
];
|
|
2074
2087
|
const child = (0, import_node_child_process5.spawn)("rg", args, {
|
|
2075
2088
|
cwd: root,
|
|
@@ -2146,7 +2159,7 @@ var import_node_path9 = require("path");
|
|
|
2146
2159
|
var import_graph = require("@odla-ai/graph");
|
|
2147
2160
|
var import_code4 = require("@odla-ai/graph/code");
|
|
2148
2161
|
var cache = /* @__PURE__ */ new Map();
|
|
2149
|
-
function workspaceGraphs(workspaceDir,
|
|
2162
|
+
function workspaceGraphs(workspaceDir, paths2) {
|
|
2150
2163
|
const existing = cache.get(workspaceDir);
|
|
2151
2164
|
if (existing) return existing;
|
|
2152
2165
|
const read2 = (path) => (0, import_promises8.readFile)((0, import_node_path9.join)(workspaceDir, path), "utf8");
|
|
@@ -2154,7 +2167,7 @@ function workspaceGraphs(workspaceDir, paths) {
|
|
|
2154
2167
|
// No knownTables: a staged workspace may not carry migrations, and a filter
|
|
2155
2168
|
// that silently drops every table is worse than an unfiltered one. Callers
|
|
2156
2169
|
// with ground truth should build the graph themselves.
|
|
2157
|
-
graph: await (0, import_code4.buildCodeGraph)({ paths, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
|
|
2170
|
+
graph: await (0, import_code4.buildCodeGraph)({ paths: paths2, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
|
|
2158
2171
|
}))();
|
|
2159
2172
|
cache.set(workspaceDir, built);
|
|
2160
2173
|
return built;
|
|
@@ -2216,11 +2229,11 @@ async function read(context, request, options, policy, registry) {
|
|
|
2216
2229
|
if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
|
|
2217
2230
|
throw new TypeError("requested line range exceeds its bound");
|
|
2218
2231
|
}
|
|
2219
|
-
const
|
|
2220
|
-
if (!
|
|
2232
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2233
|
+
if (!paths2.includes(path)) {
|
|
2221
2234
|
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
2235
|
}
|
|
2223
|
-
const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
|
|
2236
|
+
const allowed = await policy.read(policyContext(context, request, options, { paths: paths2, path, startLine, endLine }));
|
|
2224
2237
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2225
2238
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
2226
2239
|
const info = await (0, import_promises9.stat)(target);
|
|
@@ -2242,16 +2255,16 @@ async function list(context, request, options, policy, registry) {
|
|
|
2242
2255
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
2243
2256
|
const maxEntries = optionalInteger(request.input.maxEntries) ?? 1e3;
|
|
2244
2257
|
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 } : {} }));
|
|
2258
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2259
|
+
const allowed = await policy.list(policyContext(context, request, options, { paths: paths2, ...prefix ? { prefix } : {} }));
|
|
2247
2260
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2248
|
-
const entries = listWorkspace(
|
|
2261
|
+
const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
|
|
2249
2262
|
if (!entries.length) {
|
|
2250
2263
|
return response(request, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
|
|
2251
2264
|
}
|
|
2252
|
-
const truncated = entries.length <
|
|
2253
|
-
const hint = !prefix &&
|
|
2254
|
-
\u2026 ${
|
|
2265
|
+
const truncated = entries.length < paths2.length && entries.length === maxEntries;
|
|
2266
|
+
const hint = !prefix && paths2.length > 500 ? `
|
|
2267
|
+
\u2026 ${paths2.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
|
|
2255
2268
|
return response(
|
|
2256
2269
|
request,
|
|
2257
2270
|
true,
|
|
@@ -2269,10 +2282,10 @@ async function search(context, request, options, policy, registry) {
|
|
|
2269
2282
|
const maxResults = optionalInteger(request.input.maxResults) ?? 100;
|
|
2270
2283
|
if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
|
|
2271
2284
|
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 } : {} }));
|
|
2285
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2286
|
+
const allowed = await policy.search(policyContext(context, request, options, { paths: paths2, query, ...prefix ? { prefix } : {} }));
|
|
2274
2287
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2275
|
-
const matches = await searchWorkspace(context.workspaceDir,
|
|
2288
|
+
const matches = await searchWorkspace(context.workspaceDir, paths2, {
|
|
2276
2289
|
query,
|
|
2277
2290
|
maxResults,
|
|
2278
2291
|
caseSensitive,
|
|
@@ -2294,8 +2307,8 @@ async function graphQuery(context, request, options, policy, registry) {
|
|
|
2294
2307
|
selector: query
|
|
2295
2308
|
}));
|
|
2296
2309
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2297
|
-
const
|
|
2298
|
-
const graphs = await workspaceGraphs(context.workspaceDir,
|
|
2310
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2311
|
+
const graphs = await workspaceGraphs(context.workspaceDir, paths2);
|
|
2299
2312
|
if (request.tool === "sandbox.overview") {
|
|
2300
2313
|
return response(request, true, renderOverview(graphs, query || void 0));
|
|
2301
2314
|
}
|
|
@@ -2359,16 +2372,16 @@ function toolFailureMessage(reason) {
|
|
|
2359
2372
|
async function patch(context, request, options, policy, registry) {
|
|
2360
2373
|
exactKeys(request.input, ["patch"]);
|
|
2361
2374
|
const value = stringField(request.input, "patch");
|
|
2362
|
-
const
|
|
2363
|
-
if (
|
|
2375
|
+
const paths2 = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
|
|
2376
|
+
if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
2364
2377
|
throw new TypeError("patch targets a read-only reference source");
|
|
2365
2378
|
}
|
|
2366
2379
|
const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
|
|
2367
2380
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
2368
|
-
await applyCodePatch(context.workspaceDir, value,
|
|
2381
|
+
await applyCodePatch(context.workspaceDir, value, paths2);
|
|
2369
2382
|
registry.invalidate(context.workspaceDir);
|
|
2370
2383
|
forgetWorkspaceGraphs(context.workspaceDir);
|
|
2371
|
-
return response(request, true, `Applied patch to ${
|
|
2384
|
+
return response(request, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
|
|
2372
2385
|
}
|
|
2373
2386
|
async function recipe(context, request, options, recipes, policy) {
|
|
2374
2387
|
exactKeys(request.input, ["recipeId"]);
|
|
@@ -2729,12 +2742,129 @@ var import_node_crypto4 = require("crypto");
|
|
|
2729
2742
|
async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
2730
2743
|
const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
|
|
2731
2744
|
refs.push(eventId);
|
|
2732
|
-
const
|
|
2745
|
+
const attributed = { ...event, interactionId: command.commandId };
|
|
2746
|
+
const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
|
|
2733
2747
|
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
2734
2748
|
}
|
|
2735
2749
|
var digestRuntimeValue = (value) => `sha256:${(0, import_node_crypto4.createHash)("sha256").update(value).digest("hex")}`;
|
|
2736
2750
|
var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
|
|
2737
2751
|
|
|
2752
|
+
// src/code-tool-presentation.ts
|
|
2753
|
+
var text = (value, maximum) => {
|
|
2754
|
+
if (typeof value !== "string") return void 0;
|
|
2755
|
+
const bounded = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
|
|
2756
|
+
return bounded ? bounded.slice(0, maximum) : void 0;
|
|
2757
|
+
};
|
|
2758
|
+
var integer2 = (value) => Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : void 0;
|
|
2759
|
+
var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2760
|
+
var excerpt = (value, tail = false) => {
|
|
2761
|
+
if (typeof value !== "string") return void 0;
|
|
2762
|
+
const safe = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
|
|
2763
|
+
const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
|
|
2764
|
+
if (!source) return void 0;
|
|
2765
|
+
const lines = source.split("\n").filter((line) => line.trim()).map((line) => line.slice(0, 240));
|
|
2766
|
+
const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
|
|
2767
|
+
return text(selected.join("\n"), 2400);
|
|
2768
|
+
};
|
|
2769
|
+
var paths = (value) => {
|
|
2770
|
+
if (!Array.isArray(value)) return void 0;
|
|
2771
|
+
const items = value.flatMap((item) => {
|
|
2772
|
+
const path = text(item, 1024);
|
|
2773
|
+
return path ? [path] : [];
|
|
2774
|
+
}).slice(0, 12);
|
|
2775
|
+
return items.length ? items : void 0;
|
|
2776
|
+
};
|
|
2777
|
+
function patchStats(value) {
|
|
2778
|
+
if (typeof value !== "string") return {};
|
|
2779
|
+
let additions = 0;
|
|
2780
|
+
let deletions = 0;
|
|
2781
|
+
for (const line of value.slice(0, 262144).split("\n")) {
|
|
2782
|
+
if (line.startsWith("+++") || line.startsWith("---")) continue;
|
|
2783
|
+
if (line.startsWith("+")) additions += 1;
|
|
2784
|
+
else if (line.startsWith("-")) deletions += 1;
|
|
2785
|
+
}
|
|
2786
|
+
return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
|
|
2787
|
+
}
|
|
2788
|
+
function searchResults(value) {
|
|
2789
|
+
if (typeof value !== "string") return void 0;
|
|
2790
|
+
const results = value.split("\n").flatMap((line) => {
|
|
2791
|
+
const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line);
|
|
2792
|
+
if (!match) return [];
|
|
2793
|
+
const lineNumber = Number(match[2]);
|
|
2794
|
+
const itemText = text(match[3], 240);
|
|
2795
|
+
if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
|
|
2796
|
+
return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
|
|
2797
|
+
}).slice(0, 5);
|
|
2798
|
+
return results.length ? results : void 0;
|
|
2799
|
+
}
|
|
2800
|
+
function codeToolRequestPresentation(request) {
|
|
2801
|
+
const input = request.input;
|
|
2802
|
+
if (request.tool === "sandbox.read") {
|
|
2803
|
+
const path = text(input.path, 1024);
|
|
2804
|
+
if (!path) return void 0;
|
|
2805
|
+
const startLine = integer2(input.startLine);
|
|
2806
|
+
const endLine = integer2(input.endLine);
|
|
2807
|
+
return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
|
|
2808
|
+
}
|
|
2809
|
+
if (request.tool === "sandbox.list") {
|
|
2810
|
+
const scope = text(input.prefix, 1024);
|
|
2811
|
+
return { kind: "list", ...scope ? { scope } : {} };
|
|
2812
|
+
}
|
|
2813
|
+
if (request.tool === "sandbox.search" || request.tool === "sandbox.overview" || request.tool === "sandbox.where_is" || request.tool === "sandbox.who_imports" || request.tool === "sandbox.who_touches") {
|
|
2814
|
+
const query = text(input.query, 512);
|
|
2815
|
+
const scope = request.tool === "sandbox.search" ? text(input.prefix, 1024) : void 0;
|
|
2816
|
+
if (request.tool === "sandbox.search" && !query) return void 0;
|
|
2817
|
+
return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
|
|
2818
|
+
}
|
|
2819
|
+
if (request.tool === "sandbox.apply_patch") {
|
|
2820
|
+
return { kind: "patch", ...patchStats(input.patch) };
|
|
2821
|
+
}
|
|
2822
|
+
const recipeId = text(input.recipeId, 120);
|
|
2823
|
+
return recipeId ? { kind: "recipe", recipeId } : void 0;
|
|
2824
|
+
}
|
|
2825
|
+
function codeToolResultPresentation(request, response2) {
|
|
2826
|
+
const started = codeToolRequestPresentation(request);
|
|
2827
|
+
if (!started || !response2.ok) return started;
|
|
2828
|
+
const details = record3(response2.details);
|
|
2829
|
+
if (started.kind === "read") {
|
|
2830
|
+
return {
|
|
2831
|
+
...started,
|
|
2832
|
+
...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
|
|
2833
|
+
...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
|
|
2834
|
+
...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
|
|
2835
|
+
};
|
|
2836
|
+
}
|
|
2837
|
+
if (started.kind === "list") {
|
|
2838
|
+
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);
|
|
2839
|
+
return {
|
|
2840
|
+
...started,
|
|
2841
|
+
...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
|
|
2842
|
+
...listed.length ? { paths: listed } : {}
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2845
|
+
if (started.kind === "query") {
|
|
2846
|
+
const results = request.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
|
|
2847
|
+
const resultExcerpt = request.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
|
|
2848
|
+
return {
|
|
2849
|
+
...started,
|
|
2850
|
+
...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
|
|
2851
|
+
...results ? { results } : {},
|
|
2852
|
+
...resultExcerpt ? { excerpt: resultExcerpt } : {}
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
if (started.kind === "patch") {
|
|
2856
|
+
return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
|
|
2857
|
+
}
|
|
2858
|
+
const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
|
|
2859
|
+
return {
|
|
2860
|
+
...started,
|
|
2861
|
+
...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
|
|
2862
|
+
...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
|
|
2863
|
+
...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
|
|
2864
|
+
...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2738
2868
|
// src/code-runtime-engine.ts
|
|
2739
2869
|
var TheseusRuntimeEngine = class {
|
|
2740
2870
|
constructor(options) {
|
|
@@ -2929,6 +3059,7 @@ var TheseusRuntimeEngine = class {
|
|
|
2929
3059
|
event: (event) => this.#event(command, event, active.conversationRefs)
|
|
2930
3060
|
});
|
|
2931
3061
|
await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
|
|
3062
|
+
const extraSkills = await sessionSkillsFor(this.options, command);
|
|
2932
3063
|
const result = await this.#attempt({
|
|
2933
3064
|
inference,
|
|
2934
3065
|
broker,
|
|
@@ -2936,7 +3067,8 @@ var TheseusRuntimeEngine = class {
|
|
|
2936
3067
|
workspaceDir: active.workspace.workspaceDir,
|
|
2937
3068
|
prompt: metadata.prompt,
|
|
2938
3069
|
signal: active.abort.signal,
|
|
2939
|
-
recipeIds: this.options.recipes.map((recipe2) => recipe2.id)
|
|
3070
|
+
recipeIds: this.options.recipes.map((recipe2) => recipe2.id),
|
|
3071
|
+
...extraSkills.length ? { extraSkills } : {}
|
|
2940
3072
|
});
|
|
2941
3073
|
const closing = result.finalText.trim();
|
|
2942
3074
|
const completed = result.status === "completed" && Boolean(closing);
|
|
@@ -2969,18 +3101,29 @@ var TheseusRuntimeEngine = class {
|
|
|
2969
3101
|
return {
|
|
2970
3102
|
execute: async (context, request) => {
|
|
2971
3103
|
const startedAt = Date.now();
|
|
3104
|
+
const operationId = digestRuntimeValue(`${command.commandId}:${request.requestId}`);
|
|
3105
|
+
const startedPresentation = codeToolRequestPresentation(request);
|
|
2972
3106
|
await this.#event(
|
|
2973
3107
|
command,
|
|
2974
|
-
{
|
|
3108
|
+
{
|
|
3109
|
+
type: "tool",
|
|
3110
|
+
phase: "started",
|
|
3111
|
+
tool: request.tool,
|
|
3112
|
+
operationId,
|
|
3113
|
+
...startedPresentation ? { presentation: startedPresentation } : {}
|
|
3114
|
+
},
|
|
2975
3115
|
active.conversationRefs
|
|
2976
3116
|
).catch(() => void 0);
|
|
2977
3117
|
const response2 = await broker.execute(context, request);
|
|
3118
|
+
const completedPresentation = codeToolResultPresentation(request, response2);
|
|
2978
3119
|
await this.#event(command, {
|
|
2979
3120
|
type: "tool",
|
|
2980
3121
|
phase: "completed",
|
|
2981
3122
|
tool: request.tool,
|
|
2982
3123
|
ok: response2.ok,
|
|
2983
|
-
durationMs: Date.now() - startedAt
|
|
3124
|
+
durationMs: Date.now() - startedAt,
|
|
3125
|
+
operationId,
|
|
3126
|
+
...completedPresentation ? { presentation: completedPresentation } : {}
|
|
2984
3127
|
}, active.conversationRefs).catch(() => void 0);
|
|
2985
3128
|
return response2;
|
|
2986
3129
|
}
|