@odla-ai/harness 0.10.1 → 0.10.2
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-U324RQ4N.js → chunk-CPV7ZVHH.js} +1 -1
- package/dist/{chunk-U324RQ4N.js.map → chunk-CPV7ZVHH.js.map} +1 -1
- package/dist/{chunk-OZBJNTML.js → chunk-ESLFS3FY.js} +4 -4
- package/dist/{chunk-5LRYJKUI.js → chunk-FUDTSAOZ.js} +3 -3
- package/dist/{chunk-VDY5V7ZG.js → chunk-JV45JAAW.js} +3 -3
- package/dist/chunk-JV45JAAW.js.map +1 -0
- package/dist/{chunk-FAN2R3GW.js → chunk-KXFI3WM2.js} +5 -1
- package/dist/chunk-KXFI3WM2.js.map +1 -0
- package/dist/{chunk-WM34GGTK.js → chunk-VIZALA6O.js} +101 -12
- package/dist/chunk-VIZALA6O.js.map +1 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/code-runtime-cli.cjs +173 -78
- package/dist/code-runtime-cli.cjs.map +1 -1
- package/dist/code-runtime-cli.js +5 -5
- package/dist/index.cjs +5 -1
- 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 +3 -3
- package/dist/node.cjs +177 -82
- 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 +6 -6
- 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-_8y8vBDI.d.ts → types-ocy70g_p.d.cts} +1 -1
- package/dist/{types-_8y8vBDI.d.cts → types-ocy70g_p.d.ts} +1 -1
- package/package.json +1 -1
- package/dist/chunk-FAN2R3GW.js.map +0 -1
- package/dist/chunk-VDY5V7ZG.js.map +0 -1
- package/dist/chunk-WM34GGTK.js.map +0 -1
- /package/dist/{chunk-OZBJNTML.js.map → chunk-ESLFS3FY.js.map} +0 -0
- /package/dist/{chunk-5LRYJKUI.js.map → chunk-FUDTSAOZ.js.map} +0 -0
package/dist/node.cjs
CHANGED
|
@@ -156,7 +156,7 @@ function parseAgentOutput(line) {
|
|
|
156
156
|
if (message2.type === "tool.request") {
|
|
157
157
|
const input = record(message2.input);
|
|
158
158
|
const tool = String(message2.tool);
|
|
159
|
-
if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
|
|
159
|
+
if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.edit", "sandbox.run_recipe"].includes(tool)) {
|
|
160
160
|
throw new HarnessProtocolError("tool.request requires a registered tool and object input");
|
|
161
161
|
}
|
|
162
162
|
return {
|
|
@@ -2658,10 +2658,12 @@ Prefer these over listing the tree \u2014 a full listing of a real repository is
|
|
|
2658
2658
|
of thousands of tokens and you will carry it for the rest of the session.
|
|
2659
2659
|
|
|
2660
2660
|
Then odla_search for a literal string, odla_read for a bounded range, and
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2661
|
+
odla_edit_file to change an existing file: give it the exact current text
|
|
2662
|
+
(copied verbatim from odla_read) and the text that replaces it; it must match
|
|
2663
|
+
once. Use odla_apply_git_diff only to create or delete whole files. A patch must
|
|
2664
|
+
start with "diff --git a/<path> b/<path>", include matching "---" and "+++"
|
|
2665
|
+
headers and numbered "@@" hunks with at least one line of surrounding context,
|
|
2666
|
+
and must never use "*** Begin Patch" wrappers.
|
|
2665
2667
|
|
|
2666
2668
|
The workspace, model, and tool effects are controlled by the host broker.
|
|
2667
2669
|
Never claim a build or test passed unless odla_run_recipe returned that result.`;
|
|
@@ -2721,6 +2723,21 @@ function codeSkill(opts) {
|
|
|
2721
2723
|
},
|
|
2722
2724
|
handler: (input, ctx) => call("sandbox.apply_patch", input, ctx.signal)
|
|
2723
2725
|
};
|
|
2726
|
+
const editFile = {
|
|
2727
|
+
name: "odla_edit_file",
|
|
2728
|
+
description: "Change an existing file by replacing one exact stretch of its current text with new text. oldText must match the file exactly once (copy it verbatim from odla_read, including indentation and blank lines; widen it if it would match more than once). Prefer this over odla_apply_git_diff for every change to an existing file; use odla_apply_git_diff only to create or delete whole files.",
|
|
2729
|
+
inputSchema: {
|
|
2730
|
+
type: "object",
|
|
2731
|
+
required: ["path", "oldText", "newText"],
|
|
2732
|
+
properties: {
|
|
2733
|
+
path: { type: "string", minLength: 1, maxLength: 1024 },
|
|
2734
|
+
oldText: { type: "string", minLength: 1, maxLength: 262144 },
|
|
2735
|
+
newText: { type: "string", maxLength: 262144 }
|
|
2736
|
+
},
|
|
2737
|
+
additionalProperties: false
|
|
2738
|
+
},
|
|
2739
|
+
handler: (input, ctx) => call("sandbox.edit", input, ctx.signal)
|
|
2740
|
+
};
|
|
2724
2741
|
const runRecipe = {
|
|
2725
2742
|
name: "odla_run_recipe",
|
|
2726
2743
|
description: `Run one app-registered build or test recipe through CaMeL policy.${opts.recipeIds?.length ? ` Available recipes: ${opts.recipeIds.join(", ")}.` : ""}`,
|
|
@@ -2808,7 +2825,7 @@ function codeSkill(opts) {
|
|
|
2808
2825
|
)
|
|
2809
2826
|
];
|
|
2810
2827
|
const effects = opts.readOnly ? [] : [applyPatch, runRecipe];
|
|
2811
|
-
const tools = opts.surface === "v3" ? [...orientation, searchFiles, read2, ...effects] : opts.surface === "v2" ? [listFiles, searchFiles, read2, ...effects] : [read2, ...effects];
|
|
2828
|
+
const tools = opts.surface === "v3" ? [...orientation, searchFiles, read2, ...opts.readOnly ? [] : [editFile], ...effects] : opts.surface === "v2" ? [listFiles, searchFiles, read2, ...effects] : [read2, ...effects];
|
|
2812
2829
|
return { name: "code", tools };
|
|
2813
2830
|
}
|
|
2814
2831
|
|
|
@@ -3098,6 +3115,10 @@ function codeToolRequestPresentation(request) {
|
|
|
3098
3115
|
if (request.tool === "sandbox.apply_patch") {
|
|
3099
3116
|
return { kind: "patch", ...patchStats(input.patch) };
|
|
3100
3117
|
}
|
|
3118
|
+
if (request.tool === "sandbox.edit") {
|
|
3119
|
+
const lines = (value) => typeof value === "string" ? value.slice(0, 262144).split("\n").length : 0;
|
|
3120
|
+
return { kind: "patch", additions: lines(input.newText), deletions: lines(input.oldText) };
|
|
3121
|
+
}
|
|
3101
3122
|
const recipeId = text(input.recipeId, 120);
|
|
3102
3123
|
return recipeId ? { kind: "recipe", recipeId } : void 0;
|
|
3103
3124
|
}
|
|
@@ -3225,6 +3246,11 @@ var PATCH = descriptor("sandbox.apply_patch", "reversible_mutation", {
|
|
|
3225
3246
|
authority: "authority",
|
|
3226
3247
|
patch: "payload"
|
|
3227
3248
|
});
|
|
3249
|
+
var EDIT = descriptor("sandbox.edit", "reversible_mutation", {
|
|
3250
|
+
workspace: "destination",
|
|
3251
|
+
authority: "authority",
|
|
3252
|
+
patch: "payload"
|
|
3253
|
+
});
|
|
3228
3254
|
var RECIPE = descriptor("sandbox.run_recipe", "code_execution", {
|
|
3229
3255
|
workspace: "destination",
|
|
3230
3256
|
authority: "authority",
|
|
@@ -3292,6 +3318,14 @@ function createCodePolicyGate(options) {
|
|
|
3292
3318
|
patch: { role: "payload", value: patch2 }
|
|
3293
3319
|
}, []);
|
|
3294
3320
|
},
|
|
3321
|
+
edit: async (input) => {
|
|
3322
|
+
const base = await environment(input, options, "sandbox.edit");
|
|
3323
|
+
const patch2 = unsafe(base, input.patch, "patch");
|
|
3324
|
+
return authorize(input, options, base, EDIT, {
|
|
3325
|
+
...base.fixedArgs,
|
|
3326
|
+
patch: { role: "payload", value: patch2 }
|
|
3327
|
+
}, []);
|
|
3328
|
+
},
|
|
3295
3329
|
recipe: async (input) => {
|
|
3296
3330
|
const base = await environment(input, options, "sandbox.run_recipe");
|
|
3297
3331
|
const conversions = await conversionRegistry([
|
|
@@ -3436,13 +3470,134 @@ function response(request, ok, content, details) {
|
|
|
3436
3470
|
return { requestId: request.requestId, ok, content, details: { ...details, failureReason } };
|
|
3437
3471
|
}
|
|
3438
3472
|
|
|
3473
|
+
// src/code-tool-edit.ts
|
|
3474
|
+
var import_promises12 = require("fs/promises");
|
|
3475
|
+
|
|
3476
|
+
// src/code-tool-graph.ts
|
|
3477
|
+
var import_promises11 = require("fs/promises");
|
|
3478
|
+
var import_node_path11 = require("path");
|
|
3479
|
+
var import_graph = require("@odla-ai/graph");
|
|
3480
|
+
var import_code4 = require("@odla-ai/graph/code");
|
|
3481
|
+
var cache = /* @__PURE__ */ new Map();
|
|
3482
|
+
function workspaceGraphs(workspaceDir, paths2) {
|
|
3483
|
+
const existing = cache.get(workspaceDir);
|
|
3484
|
+
if (existing) return existing;
|
|
3485
|
+
const read2 = (path) => (0, import_promises11.readFile)((0, import_node_path11.join)(workspaceDir, path), "utf8");
|
|
3486
|
+
const built = (async () => ({
|
|
3487
|
+
// No knownTables: a staged workspace may not carry migrations, and a filter
|
|
3488
|
+
// that silently drops every table is worse than an unfiltered one. Callers
|
|
3489
|
+
// with ground truth should build the graph themselves.
|
|
3490
|
+
graph: await (0, import_code4.buildCodeGraph)({ paths: paths2, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
|
|
3491
|
+
}))();
|
|
3492
|
+
cache.set(workspaceDir, built);
|
|
3493
|
+
return built;
|
|
3494
|
+
}
|
|
3495
|
+
function forgetWorkspaceGraphs(workspaceDir) {
|
|
3496
|
+
cache.delete(workspaceDir);
|
|
3497
|
+
}
|
|
3498
|
+
var shortId = (id) => id.slice(id.indexOf(":") + 1);
|
|
3499
|
+
function renderOverview(graphs, prefix) {
|
|
3500
|
+
const rows = (0, import_graph.rollup)(graphs.graph, import_code4.FILE, prefix === void 0 ? {} : { prefix });
|
|
3501
|
+
if (rows.length === 0) return prefix ? `No source under "${prefix}".` : "No source files.";
|
|
3502
|
+
const lines = rows.slice(0, 60).map((row) => `${row.prefix} (${row.count}) e.g. ${row.examples[0] ?? ""}`);
|
|
3503
|
+
const total = (0, import_graph.nodesOfKind)(graphs.graph, import_code4.FILE).length;
|
|
3504
|
+
return [`${total} source files. Directories, largest first \u2014 read one with sandbox.list --prefix.`, ...lines].join("\n");
|
|
3505
|
+
}
|
|
3506
|
+
function renderWhereIs(graphs, symbol) {
|
|
3507
|
+
const sites = (0, import_graph.neighbors)(graphs.graph, (0, import_graph.nodeId)(import_code4.SYMBOL, symbol), { direction: "in", kinds: ["exports"] }).map((id) => ({
|
|
3508
|
+
path: shortId(id),
|
|
3509
|
+
pkg: (0, import_graph.neighbors)(graphs.graph, id, { direction: "in", kinds: ["contains"] })[0],
|
|
3510
|
+
dependents: (0, import_graph.incident)(graphs.graph, id, { direction: "in", kinds: [import_code4.IMPORTS] }).length
|
|
3511
|
+
})).sort((left, right) => right.dependents - left.dependents || left.path.localeCompare(right.path));
|
|
3512
|
+
if (sites.length === 0) return `No exported symbol named "${symbol}". Try sandbox.search for a textual match.`;
|
|
3513
|
+
return sites.slice(0, 20).map((site) => `${site.path}${site.pkg ? ` [${shortId(site.pkg)}]` : ""} ${site.dependents} dependents`).join("\n");
|
|
3514
|
+
}
|
|
3515
|
+
function renderWhoImports(graphs, path) {
|
|
3516
|
+
const id = (0, import_graph.nodeId)(import_code4.FILE, path);
|
|
3517
|
+
const importers = (0, import_graph.neighbors)(graphs.graph, id, { direction: "in", kinds: [import_code4.IMPORTS] });
|
|
3518
|
+
if (importers.length === 0) {
|
|
3519
|
+
return graphs.graph.nodes.has(id) ? `Nothing imports ${path}. It is a leaf.` : `${path} is not a source file in this workspace.`;
|
|
3520
|
+
}
|
|
3521
|
+
return importers.slice(0, 40).map(shortId).sort().join("\n");
|
|
3522
|
+
}
|
|
3523
|
+
function renderWhoTouches(graphs, query) {
|
|
3524
|
+
const needle = query.toLowerCase();
|
|
3525
|
+
const hits = [...graphs.graph.nodes.values()].filter((node) => (node.kind === "table" || node.kind === "namespace") && node.name.toLowerCase().includes(needle)).slice(0, 10);
|
|
3526
|
+
if (hits.length === 0) return `No table or namespace matching "${query}".`;
|
|
3527
|
+
return hits.map((hit) => {
|
|
3528
|
+
const side = (kind) => (0, import_graph.neighbors)(graphs.graph, hit.id, { direction: "in", kinds: [kind] }).map(shortId).sort().slice(0, 8);
|
|
3529
|
+
return [
|
|
3530
|
+
`${hit.name} (${hit.kind})`,
|
|
3531
|
+
` writes: ${side(import_code4.WRITES).join(", ") || "(none)"}`,
|
|
3532
|
+
` reads: ${side(import_code4.READS).join(", ") || "(none)"}`
|
|
3533
|
+
].join("\n");
|
|
3534
|
+
}).join("\n\n");
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
// src/code-tool-edit.ts
|
|
3538
|
+
var MAX_TEXT_BYTES = 256 * 1024;
|
|
3539
|
+
function editAsDiff(path, oldText, newText) {
|
|
3540
|
+
const removed = oldText.split("\n").map((line) => `-${line}`);
|
|
3541
|
+
const added = newText.split("\n").map((line) => `+${line}`);
|
|
3542
|
+
return `diff --git a/${path} b/${path}
|
|
3543
|
+
--- a/${path}
|
|
3544
|
+
+++ b/${path}
|
|
3545
|
+
@@ -1,${removed.length} +1,${added.length} @@
|
|
3546
|
+
${[...removed, ...added].join("\n")}
|
|
3547
|
+
`;
|
|
3548
|
+
}
|
|
3549
|
+
function occurrences(haystack, needle) {
|
|
3550
|
+
let count = 0;
|
|
3551
|
+
for (let at = haystack.indexOf(needle); at !== -1; at = haystack.indexOf(needle, at + needle.length)) count += 1;
|
|
3552
|
+
return count;
|
|
3553
|
+
}
|
|
3554
|
+
async function editCodeFile(workspaceDir, path, oldText, newText) {
|
|
3555
|
+
if (!oldText) throw new TypeError("oldText must not be empty; to create a file use sandbox.apply_patch with a new-file diff");
|
|
3556
|
+
if (Buffer.byteLength(oldText) > MAX_TEXT_BYTES || Buffer.byteLength(newText) > MAX_TEXT_BYTES) {
|
|
3557
|
+
throw new TypeError(`oldText and newText must each stay under ${MAX_TEXT_BYTES} bytes; split the change`);
|
|
3558
|
+
}
|
|
3559
|
+
if (oldText.includes("\0") || newText.includes("\0")) throw new TypeError("edit text contains NUL bytes; use plain text");
|
|
3560
|
+
const target = resolveCodePath(workspaceDir, path);
|
|
3561
|
+
const current = await (0, import_promises12.readFile)(target, "utf8");
|
|
3562
|
+
const found = occurrences(current, oldText);
|
|
3563
|
+
if (found === 0) {
|
|
3564
|
+
throw new TypeError(`oldText was not found in "${path}"; sandbox.read the current lines and copy them exactly, including indentation and blank lines`);
|
|
3565
|
+
}
|
|
3566
|
+
if (found > 1) {
|
|
3567
|
+
throw new TypeError(`oldText occurs ${found} times in "${path}"; include more of the surrounding lines so it matches exactly once`);
|
|
3568
|
+
}
|
|
3569
|
+
const at = current.indexOf(oldText);
|
|
3570
|
+
await (0, import_promises12.writeFile)(target, `${current.slice(0, at)}${newText}${current.slice(at + oldText.length)}`, "utf8");
|
|
3571
|
+
return { deletions: oldText.split("\n").length, additions: newText.split("\n").length };
|
|
3572
|
+
}
|
|
3573
|
+
async function edit(context, request, options, policy, registry) {
|
|
3574
|
+
exactKeys(request.input, ["path", "oldText", "newText"]);
|
|
3575
|
+
const path = stringField(request.input, "path");
|
|
3576
|
+
const oldText = stringField(request.input, "oldText");
|
|
3577
|
+
const newText = typeof request.input.newText === "string" ? request.input.newText : stringField(request.input, "newText");
|
|
3578
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
3579
|
+
if (!paths2.includes(path)) {
|
|
3580
|
+
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.`);
|
|
3581
|
+
}
|
|
3582
|
+
if (options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`))) {
|
|
3583
|
+
throw new TypeError("edit targets a read-only reference source");
|
|
3584
|
+
}
|
|
3585
|
+
const patch2 = editAsDiff(path, oldText, newText);
|
|
3586
|
+
const allowed = await policy.edit(policyContext(context, request, options, { patch: patch2 }));
|
|
3587
|
+
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
3588
|
+
const changed = await editCodeFile(context.workspaceDir, path, oldText, newText);
|
|
3589
|
+
registry.invalidate(context.workspaceDir);
|
|
3590
|
+
forgetWorkspaceGraphs(context.workspaceDir);
|
|
3591
|
+
return response(request, true, `Edited ${path}: -${changed.deletions} +${changed.additions} line(s).`, { paths: [path], ...changed });
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3439
3594
|
// src/code-tool-reads.ts
|
|
3440
|
-
var
|
|
3595
|
+
var import_promises14 = require("fs/promises");
|
|
3441
3596
|
|
|
3442
3597
|
// src/code-tool-discovery.ts
|
|
3443
3598
|
var import_node_child_process6 = require("child_process");
|
|
3444
|
-
var
|
|
3445
|
-
var
|
|
3599
|
+
var import_promises13 = require("fs/promises");
|
|
3600
|
+
var import_node_path12 = require("path");
|
|
3446
3601
|
var DEFAULT_MAX_FILES = 2e4;
|
|
3447
3602
|
var DEFAULT_MAX_RESULTS = 100;
|
|
3448
3603
|
var DEFAULT_MAX_FILE_BYTES = 512 * 1024;
|
|
@@ -3467,13 +3622,13 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
3467
3622
|
async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
3468
3623
|
const paths2 = [];
|
|
3469
3624
|
const walk = async (directory) => {
|
|
3470
|
-
for (const entry of await (0,
|
|
3625
|
+
for (const entry of await (0, import_promises13.readdir)(directory, { withFileTypes: true })) {
|
|
3471
3626
|
if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
3472
3627
|
if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
|
|
3473
|
-
const target = (0,
|
|
3628
|
+
const target = (0, import_node_path12.resolve)(directory, entry.name);
|
|
3474
3629
|
if (entry.isDirectory()) await walk(target);
|
|
3475
3630
|
else if (entry.isFile()) {
|
|
3476
|
-
const path = (0,
|
|
3631
|
+
const path = (0, import_node_path12.relative)(root, target).split("\\").join("/");
|
|
3477
3632
|
try {
|
|
3478
3633
|
validateRelativePath(path);
|
|
3479
3634
|
} catch {
|
|
@@ -3484,7 +3639,7 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
|
3484
3639
|
}
|
|
3485
3640
|
}
|
|
3486
3641
|
};
|
|
3487
|
-
await walk((0,
|
|
3642
|
+
await walk((0, import_node_path12.resolve)(root));
|
|
3488
3643
|
return paths2.sort();
|
|
3489
3644
|
}
|
|
3490
3645
|
function listWorkspace(paths2, options = {}) {
|
|
@@ -3598,7 +3753,7 @@ async function fallbackSearch(root, scoped, options) {
|
|
|
3598
3753
|
if (matches.length >= options.maxResults) break;
|
|
3599
3754
|
let source;
|
|
3600
3755
|
try {
|
|
3601
|
-
source = await (0,
|
|
3756
|
+
source = await (0, import_promises13.readFile)((0, import_node_path12.resolve)(root, path));
|
|
3602
3757
|
} catch {
|
|
3603
3758
|
continue;
|
|
3604
3759
|
}
|
|
@@ -3615,67 +3770,6 @@ async function fallbackSearch(root, scoped, options) {
|
|
|
3615
3770
|
return matches;
|
|
3616
3771
|
}
|
|
3617
3772
|
|
|
3618
|
-
// src/code-tool-graph.ts
|
|
3619
|
-
var import_promises12 = require("fs/promises");
|
|
3620
|
-
var import_node_path12 = require("path");
|
|
3621
|
-
var import_graph = require("@odla-ai/graph");
|
|
3622
|
-
var import_code4 = require("@odla-ai/graph/code");
|
|
3623
|
-
var cache = /* @__PURE__ */ new Map();
|
|
3624
|
-
function workspaceGraphs(workspaceDir, paths2) {
|
|
3625
|
-
const existing = cache.get(workspaceDir);
|
|
3626
|
-
if (existing) return existing;
|
|
3627
|
-
const read2 = (path) => (0, import_promises12.readFile)((0, import_node_path12.join)(workspaceDir, path), "utf8");
|
|
3628
|
-
const built = (async () => ({
|
|
3629
|
-
// No knownTables: a staged workspace may not carry migrations, and a filter
|
|
3630
|
-
// that silently drops every table is worse than an unfiltered one. Callers
|
|
3631
|
-
// with ground truth should build the graph themselves.
|
|
3632
|
-
graph: await (0, import_code4.buildCodeGraph)({ paths: paths2, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
|
|
3633
|
-
}))();
|
|
3634
|
-
cache.set(workspaceDir, built);
|
|
3635
|
-
return built;
|
|
3636
|
-
}
|
|
3637
|
-
function forgetWorkspaceGraphs(workspaceDir) {
|
|
3638
|
-
cache.delete(workspaceDir);
|
|
3639
|
-
}
|
|
3640
|
-
var shortId = (id) => id.slice(id.indexOf(":") + 1);
|
|
3641
|
-
function renderOverview(graphs, prefix) {
|
|
3642
|
-
const rows = (0, import_graph.rollup)(graphs.graph, import_code4.FILE, prefix === void 0 ? {} : { prefix });
|
|
3643
|
-
if (rows.length === 0) return prefix ? `No source under "${prefix}".` : "No source files.";
|
|
3644
|
-
const lines = rows.slice(0, 60).map((row) => `${row.prefix} (${row.count}) e.g. ${row.examples[0] ?? ""}`);
|
|
3645
|
-
const total = (0, import_graph.nodesOfKind)(graphs.graph, import_code4.FILE).length;
|
|
3646
|
-
return [`${total} source files. Directories, largest first \u2014 read one with sandbox.list --prefix.`, ...lines].join("\n");
|
|
3647
|
-
}
|
|
3648
|
-
function renderWhereIs(graphs, symbol) {
|
|
3649
|
-
const sites = (0, import_graph.neighbors)(graphs.graph, (0, import_graph.nodeId)(import_code4.SYMBOL, symbol), { direction: "in", kinds: ["exports"] }).map((id) => ({
|
|
3650
|
-
path: shortId(id),
|
|
3651
|
-
pkg: (0, import_graph.neighbors)(graphs.graph, id, { direction: "in", kinds: ["contains"] })[0],
|
|
3652
|
-
dependents: (0, import_graph.incident)(graphs.graph, id, { direction: "in", kinds: [import_code4.IMPORTS] }).length
|
|
3653
|
-
})).sort((left, right) => right.dependents - left.dependents || left.path.localeCompare(right.path));
|
|
3654
|
-
if (sites.length === 0) return `No exported symbol named "${symbol}". Try sandbox.search for a textual match.`;
|
|
3655
|
-
return sites.slice(0, 20).map((site) => `${site.path}${site.pkg ? ` [${shortId(site.pkg)}]` : ""} ${site.dependents} dependents`).join("\n");
|
|
3656
|
-
}
|
|
3657
|
-
function renderWhoImports(graphs, path) {
|
|
3658
|
-
const id = (0, import_graph.nodeId)(import_code4.FILE, path);
|
|
3659
|
-
const importers = (0, import_graph.neighbors)(graphs.graph, id, { direction: "in", kinds: [import_code4.IMPORTS] });
|
|
3660
|
-
if (importers.length === 0) {
|
|
3661
|
-
return graphs.graph.nodes.has(id) ? `Nothing imports ${path}. It is a leaf.` : `${path} is not a source file in this workspace.`;
|
|
3662
|
-
}
|
|
3663
|
-
return importers.slice(0, 40).map(shortId).sort().join("\n");
|
|
3664
|
-
}
|
|
3665
|
-
function renderWhoTouches(graphs, query) {
|
|
3666
|
-
const needle = query.toLowerCase();
|
|
3667
|
-
const hits = [...graphs.graph.nodes.values()].filter((node) => (node.kind === "table" || node.kind === "namespace") && node.name.toLowerCase().includes(needle)).slice(0, 10);
|
|
3668
|
-
if (hits.length === 0) return `No table or namespace matching "${query}".`;
|
|
3669
|
-
return hits.map((hit) => {
|
|
3670
|
-
const side = (kind) => (0, import_graph.neighbors)(graphs.graph, hit.id, { direction: "in", kinds: [kind] }).map(shortId).sort().slice(0, 8);
|
|
3671
|
-
return [
|
|
3672
|
-
`${hit.name} (${hit.kind})`,
|
|
3673
|
-
` writes: ${side(import_code4.WRITES).join(", ") || "(none)"}`,
|
|
3674
|
-
` reads: ${side(import_code4.READS).join(", ") || "(none)"}`
|
|
3675
|
-
].join("\n");
|
|
3676
|
-
}).join("\n\n");
|
|
3677
|
-
}
|
|
3678
|
-
|
|
3679
3773
|
// src/code-tool-reads.ts
|
|
3680
3774
|
var GRAPH_TOOLS = /* @__PURE__ */ new Set([
|
|
3681
3775
|
"sandbox.overview",
|
|
@@ -3698,11 +3792,11 @@ async function read(context, request, options, policy, registry) {
|
|
|
3698
3792
|
const allowed = await policy.read(policyContext(context, request, options, { paths: paths2, path, startLine, endLine }));
|
|
3699
3793
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
3700
3794
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
3701
|
-
const info = await (0,
|
|
3795
|
+
const info = await (0, import_promises14.stat)(target);
|
|
3702
3796
|
if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
|
|
3703
3797
|
throw new TypeError("file is not a bounded regular source file");
|
|
3704
3798
|
}
|
|
3705
|
-
const source = await (0,
|
|
3799
|
+
const source = await (0, import_promises14.readFile)(target);
|
|
3706
3800
|
if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
|
|
3707
3801
|
const lines = source.toString("utf8").split("\n");
|
|
3708
3802
|
const content = lines.slice(startLine - 1, endLine).join("\n");
|
|
@@ -3817,6 +3911,7 @@ async function route(context, request, options, recipes, policy, registry) {
|
|
|
3817
3911
|
if (request.tool === "sandbox.search") return await search(context, request, options, policy, registry);
|
|
3818
3912
|
if (GRAPH_TOOLS.has(request.tool)) return await graphQuery(context, request, options, policy, registry);
|
|
3819
3913
|
if (request.tool === "sandbox.apply_patch") return await patch(context, request, options, policy, registry);
|
|
3914
|
+
if (request.tool === "sandbox.edit") return await edit(context, request, options, policy, registry);
|
|
3820
3915
|
return await recipe(context, request, options, recipes, policy);
|
|
3821
3916
|
} catch (reason) {
|
|
3822
3917
|
return response(request, false, toolFailureMessage(reason));
|
|
@@ -4857,7 +4952,7 @@ function chooseStrategy(signals = {}) {
|
|
|
4857
4952
|
var feedbackIsActionable = actionable;
|
|
4858
4953
|
|
|
4859
4954
|
// src/code-recipe-dependencies.ts
|
|
4860
|
-
var
|
|
4955
|
+
var import_promises15 = require("fs/promises");
|
|
4861
4956
|
var import_node_path13 = require("path");
|
|
4862
4957
|
var RESERVED_MOUNTS = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage"]);
|
|
4863
4958
|
function withRecipeDependencies(executor, dependencies) {
|
|
@@ -4873,21 +4968,21 @@ function withRecipeDependencies(executor, dependencies) {
|
|
|
4873
4968
|
const target = (0, import_node_path13.join)(input.workspaceDir, mountAs);
|
|
4874
4969
|
let linked = false;
|
|
4875
4970
|
try {
|
|
4876
|
-
const existing = await (0,
|
|
4971
|
+
const existing = await (0, import_promises15.lstat)(target).catch(() => null);
|
|
4877
4972
|
if (!existing) {
|
|
4878
|
-
await (0,
|
|
4973
|
+
await (0, import_promises15.symlink)(dependencies.source, target, "dir");
|
|
4879
4974
|
linked = true;
|
|
4880
4975
|
}
|
|
4881
4976
|
return await executor.run(input);
|
|
4882
4977
|
} finally {
|
|
4883
|
-
if (linked) await (0,
|
|
4978
|
+
if (linked) await (0, import_promises15.rm)(target, { force: true, recursive: false }).catch(() => void 0);
|
|
4884
4979
|
}
|
|
4885
4980
|
}
|
|
4886
4981
|
};
|
|
4887
4982
|
}
|
|
4888
4983
|
async function installedDependencies(repoRoot) {
|
|
4889
4984
|
const source = (0, import_node_path13.join)(repoRoot, "node_modules");
|
|
4890
|
-
const info = await (0,
|
|
4985
|
+
const info = await (0, import_promises15.lstat)(source).catch(() => null);
|
|
4891
4986
|
return info?.isDirectory() ? { source } : null;
|
|
4892
4987
|
}
|
|
4893
4988
|
// Annotate the CommonJS export names for ESM import in node:
|