@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
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
stageWorkspace,
|
|
5
5
|
stageWorkspacePair,
|
|
6
6
|
verifyContainerEngineBoundary
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-CR6RE3A2.js";
|
|
8
8
|
import {
|
|
9
9
|
HARNESS_PROTOCOL_VERSION
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-RXNHCGWE.js";
|
|
11
11
|
|
|
12
12
|
// src/workspace-digest.ts
|
|
13
13
|
import { createHash } from "crypto";
|
|
@@ -351,7 +351,7 @@ function validateCodePatch(rawPatch, maxBytes) {
|
|
|
351
351
|
if (FORBIDDEN.test(patch2) || /(?:old|new)(?: file)? mode 120000/.test(patch2)) {
|
|
352
352
|
throw new TypeError("patch uses a forbidden binary, link, mode, rename, or copy operation");
|
|
353
353
|
}
|
|
354
|
-
const
|
|
354
|
+
const paths2 = [];
|
|
355
355
|
const lines = patch2.split("\n");
|
|
356
356
|
for (let index = 0; index < lines.length; index += 1) {
|
|
357
357
|
const line = lines[index];
|
|
@@ -367,10 +367,10 @@ function validateCodePatch(rawPatch, maxBytes) {
|
|
|
367
367
|
if (!validHeaderPath(oldPath, path, "a") || !validHeaderPath(newPath, path, "b")) {
|
|
368
368
|
throw new TypeError("patch file headers do not match the declared path");
|
|
369
369
|
}
|
|
370
|
-
|
|
370
|
+
paths2.push(path);
|
|
371
371
|
}
|
|
372
|
-
if (!
|
|
373
|
-
return
|
|
372
|
+
if (!paths2.length || new Set(paths2).size !== paths2.length) throw new TypeError("patch has no diffs or repeats a path");
|
|
373
|
+
return paths2;
|
|
374
374
|
}
|
|
375
375
|
function validHeaderPath(value, path, prefix) {
|
|
376
376
|
return value === "/dev/null" || value === `${prefix}/${path}`;
|
|
@@ -395,11 +395,11 @@ function describePatchFailure(patch2, detail) {
|
|
|
395
395
|
const hint = hunks.length > 0 && contextless ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
|
|
396
396
|
return `patch did not apply: ${detail}${hint}`;
|
|
397
397
|
}
|
|
398
|
-
async function applyCodePatch(workspaceDir, rawPatch,
|
|
398
|
+
async function applyCodePatch(workspaceDir, rawPatch, paths2) {
|
|
399
399
|
const patch2 = stripPatchEnvelope(rawPatch);
|
|
400
400
|
await gitApply(workspaceDir, patch2, true);
|
|
401
401
|
await gitApply(workspaceDir, patch2, false);
|
|
402
|
-
for (const path of
|
|
402
|
+
for (const path of paths2) {
|
|
403
403
|
try {
|
|
404
404
|
const info = await lstat(resolveCodePath(workspaceDir, path));
|
|
405
405
|
if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
|
|
@@ -421,8 +421,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
421
421
|
});
|
|
422
422
|
let stderr = "";
|
|
423
423
|
child.stderr.setEncoding("utf8");
|
|
424
|
-
child.stderr.on("data", (
|
|
425
|
-
if (stderr.length < 4e3) stderr +=
|
|
424
|
+
child.stderr.on("data", (text2) => {
|
|
425
|
+
if (stderr.length < 4e3) stderr += text2.slice(0, 4e3);
|
|
426
426
|
});
|
|
427
427
|
child.once("error", reject);
|
|
428
428
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr.trim().slice(0, 500)))));
|
|
@@ -452,8 +452,8 @@ async function restoreCodeWorkspaceCheckpoint(input) {
|
|
|
452
452
|
const workspace = await stageWorkspace(input.trustedBaseDir, input.stage);
|
|
453
453
|
try {
|
|
454
454
|
if (checkpoint.patch) {
|
|
455
|
-
const
|
|
456
|
-
await applyCodePatch(workspace.workspaceDir, checkpoint.patch,
|
|
455
|
+
const paths2 = validateCodePatch(checkpoint.patch, 256 * 1024);
|
|
456
|
+
await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths2);
|
|
457
457
|
}
|
|
458
458
|
return { workspace, checkpoint };
|
|
459
459
|
} catch (error) {
|
|
@@ -622,8 +622,8 @@ async function verifyCodeCandidate(input) {
|
|
|
622
622
|
try {
|
|
623
623
|
const baseDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
|
|
624
624
|
if (baseDigest !== input.trustedBaseDigest) throw new TypeError("trusted base does not match its registered digest");
|
|
625
|
-
const
|
|
626
|
-
await applyCodePatch(staged.workspaceDir, input.candidatePatch,
|
|
625
|
+
const paths2 = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
|
|
626
|
+
await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths2);
|
|
627
627
|
const sourceDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
|
|
628
628
|
const policyDigest = digestPolicy(policy);
|
|
629
629
|
const patchDigest = digestBytes(input.candidatePatch);
|
|
@@ -632,7 +632,7 @@ async function verifyCodeCandidate(input) {
|
|
|
632
632
|
trustedBaseDigest: input.trustedBaseDigest,
|
|
633
633
|
patchDigest
|
|
634
634
|
});
|
|
635
|
-
const changedTests = changedTestPaths(
|
|
635
|
+
const changedTests = changedTestPaths(paths2, policy);
|
|
636
636
|
if (changedTests.length > policy.maximumChangedTests) throw new TypeError("candidate changes too many test files");
|
|
637
637
|
const recipes = [];
|
|
638
638
|
const logs = [];
|
|
@@ -699,8 +699,8 @@ function validate(input) {
|
|
|
699
699
|
}
|
|
700
700
|
return result;
|
|
701
701
|
}
|
|
702
|
-
function changedTestPaths(
|
|
703
|
-
return
|
|
702
|
+
function changedTestPaths(paths2, policy) {
|
|
703
|
+
return paths2.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
|
|
704
704
|
}
|
|
705
705
|
function recipeReceipt(recipe2, result, artifacts) {
|
|
706
706
|
const status = result.timedOut ? "timed_out" : result.outputLimitExceeded ? "output_limited" : result.exitCode === 0 && artifacts.every((item) => item.status === "verified") ? "passed" : "failed";
|
|
@@ -1128,7 +1128,10 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
|
|
|
1128
1128
|
var V2_SYSTEM_PROMPT = `You are the coding agent inside an odla Code harness.
|
|
1129
1129
|
Start by orienting: odla_list shows the files in the workspace and odla_search
|
|
1130
1130
|
finds a literal string across them. Prefer those over guessing a path.
|
|
1131
|
-
Then odla_read a bounded range, and odla_apply_git_diff to mutate.
|
|
1131
|
+
Then odla_read a bounded range, and odla_apply_git_diff to mutate. When you
|
|
1132
|
+
need several independent searches or file ranges, issue those read-only calls
|
|
1133
|
+
together in one turn; their results stay ordered and the harness overlaps them.
|
|
1134
|
+
Never issue odla_apply_git_diff or odla_run_recipe alongside another tool call.
|
|
1132
1135
|
For mutations, call odla_apply_git_diff with raw git diff text. It must start
|
|
1133
1136
|
with "diff --git a/<path> b/<path>", include matching "---" and "+++" file
|
|
1134
1137
|
headers and numbered "@@" hunks, and never use "*** Begin Patch" wrappers.
|
|
@@ -1159,18 +1162,26 @@ var SYSTEM_PROMPT_FOR = {
|
|
|
1159
1162
|
};
|
|
1160
1163
|
function codeSkill(opts) {
|
|
1161
1164
|
let seq = 0;
|
|
1165
|
+
let nextCompletion = 1;
|
|
1166
|
+
const completed = /* @__PURE__ */ new Map();
|
|
1162
1167
|
const call = async (tool, input, signal) => {
|
|
1168
|
+
const sequence = ++seq;
|
|
1163
1169
|
const startedAt = Date.now();
|
|
1164
1170
|
const response2 = await opts.broker.execute(
|
|
1165
1171
|
{ lease: opts.lease, workspaceDir: opts.workspaceDir, signal },
|
|
1166
|
-
{ requestId: `bench-${tool}-${
|
|
1172
|
+
{ requestId: `bench-${tool}-${sequence}`, tool, input }
|
|
1167
1173
|
);
|
|
1168
|
-
|
|
1174
|
+
completed.set(sequence, {
|
|
1169
1175
|
tool,
|
|
1170
1176
|
ok: response2.ok,
|
|
1171
1177
|
durationMs: Date.now() - startedAt,
|
|
1172
1178
|
...response2.ok ? {} : { error: String(response2.content).slice(0, 300) }
|
|
1173
1179
|
});
|
|
1180
|
+
while (completed.has(nextCompletion)) {
|
|
1181
|
+
const completion = completed.get(nextCompletion);
|
|
1182
|
+
completed.delete(nextCompletion++);
|
|
1183
|
+
opts.onToolCall?.(completion);
|
|
1184
|
+
}
|
|
1174
1185
|
return { content: response2.content, isError: !response2.ok };
|
|
1175
1186
|
};
|
|
1176
1187
|
const read2 = {
|
|
@@ -1186,6 +1197,7 @@ function codeSkill(opts) {
|
|
|
1186
1197
|
},
|
|
1187
1198
|
additionalProperties: false
|
|
1188
1199
|
},
|
|
1200
|
+
concurrency: "parallel",
|
|
1189
1201
|
handler: (input, ctx) => call("sandbox.read", input, ctx.signal)
|
|
1190
1202
|
};
|
|
1191
1203
|
const applyPatch = {
|
|
@@ -1227,6 +1239,7 @@ function codeSkill(opts) {
|
|
|
1227
1239
|
},
|
|
1228
1240
|
additionalProperties: false
|
|
1229
1241
|
},
|
|
1242
|
+
concurrency: "parallel",
|
|
1230
1243
|
handler: (input, ctx) => call("sandbox.list", input, ctx.signal)
|
|
1231
1244
|
};
|
|
1232
1245
|
const searchFiles = {
|
|
@@ -1243,11 +1256,13 @@ function codeSkill(opts) {
|
|
|
1243
1256
|
},
|
|
1244
1257
|
additionalProperties: false
|
|
1245
1258
|
},
|
|
1259
|
+
concurrency: "parallel",
|
|
1246
1260
|
handler: (input, ctx) => call("sandbox.search", input, ctx.signal)
|
|
1247
1261
|
};
|
|
1248
1262
|
const graphTool = (name, tool, description, required) => ({
|
|
1249
1263
|
name,
|
|
1250
1264
|
description,
|
|
1265
|
+
concurrency: "parallel",
|
|
1251
1266
|
inputSchema: {
|
|
1252
1267
|
type: "object",
|
|
1253
1268
|
...required ? { required: ["query"] } : {},
|
|
@@ -1437,13 +1452,32 @@ function createCodeRuntimeInference(options) {
|
|
|
1437
1452
|
}
|
|
1438
1453
|
|
|
1439
1454
|
// src/code-tool-discovery.ts
|
|
1455
|
+
import { spawn as spawn3 } from "child_process";
|
|
1440
1456
|
import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
|
|
1441
1457
|
import { relative as relative2, resolve as resolve4 } from "path";
|
|
1442
1458
|
var DEFAULT_MAX_FILES = 2e4;
|
|
1443
1459
|
var DEFAULT_MAX_RESULTS = 100;
|
|
1444
1460
|
var DEFAULT_MAX_FILE_BYTES = 512 * 1024;
|
|
1461
|
+
function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = registeredFiles) {
|
|
1462
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
1463
|
+
return {
|
|
1464
|
+
files(root) {
|
|
1465
|
+
const existing = cache2.get(root);
|
|
1466
|
+
if (existing) return existing;
|
|
1467
|
+
const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
|
|
1468
|
+
cache2.set(root, pending);
|
|
1469
|
+
void pending.catch(() => {
|
|
1470
|
+
if (cache2.get(root) === pending) cache2.delete(root);
|
|
1471
|
+
});
|
|
1472
|
+
return pending;
|
|
1473
|
+
},
|
|
1474
|
+
invalidate(root) {
|
|
1475
|
+
cache2.delete(root);
|
|
1476
|
+
}
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1445
1479
|
async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
1446
|
-
const
|
|
1480
|
+
const paths2 = [];
|
|
1447
1481
|
const walk = async (directory) => {
|
|
1448
1482
|
for (const entry of await readdir2(directory, { withFileTypes: true })) {
|
|
1449
1483
|
if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
@@ -1457,43 +1491,137 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
|
1457
1491
|
} catch {
|
|
1458
1492
|
continue;
|
|
1459
1493
|
}
|
|
1460
|
-
|
|
1461
|
-
if (
|
|
1494
|
+
paths2.push(path);
|
|
1495
|
+
if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
|
|
1462
1496
|
}
|
|
1463
1497
|
}
|
|
1464
1498
|
};
|
|
1465
1499
|
await walk(resolve4(root));
|
|
1466
|
-
return
|
|
1500
|
+
return paths2.sort();
|
|
1467
1501
|
}
|
|
1468
|
-
function listWorkspace(
|
|
1502
|
+
function listWorkspace(paths2, options = {}) {
|
|
1469
1503
|
const max = options.maxEntries ?? 1e3;
|
|
1470
1504
|
const prefix = options.prefix?.replace(/\/+$/, "");
|
|
1471
|
-
const scoped = prefix ?
|
|
1505
|
+
const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
|
|
1472
1506
|
return scoped.slice(0, max);
|
|
1473
1507
|
}
|
|
1474
|
-
async function searchWorkspace(root,
|
|
1475
|
-
|
|
1476
|
-
if (!query) throw new TypeError("search query must be a non-empty string");
|
|
1508
|
+
async function searchWorkspace(root, paths2, options) {
|
|
1509
|
+
options.signal?.throwIfAborted();
|
|
1510
|
+
if (!options.query) throw new TypeError("search query must be a non-empty string");
|
|
1477
1511
|
const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
1478
1512
|
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
|
|
1479
|
-
const scoped = listWorkspace(
|
|
1513
|
+
const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
|
|
1514
|
+
if (scoped.length === 0) return [];
|
|
1515
|
+
try {
|
|
1516
|
+
return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
1517
|
+
} catch (error) {
|
|
1518
|
+
options.signal?.throwIfAborted();
|
|
1519
|
+
return fallbackSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
var MAX_NATIVE_ARG_BYTES = 96 * 1024;
|
|
1523
|
+
async function nativeSearch(root, paths2, options) {
|
|
1524
|
+
const batches = [];
|
|
1525
|
+
let batch = [];
|
|
1526
|
+
let bytes = 0;
|
|
1527
|
+
for (const path of paths2) {
|
|
1528
|
+
const size = Buffer.byteLength(path) + 1;
|
|
1529
|
+
if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
|
|
1530
|
+
batches.push(batch);
|
|
1531
|
+
batch = [];
|
|
1532
|
+
bytes = 0;
|
|
1533
|
+
}
|
|
1534
|
+
batch.push(path);
|
|
1535
|
+
bytes += size;
|
|
1536
|
+
}
|
|
1537
|
+
if (batch.length > 0) batches.push(batch);
|
|
1538
|
+
const matches = [];
|
|
1539
|
+
for (const files of batches) {
|
|
1540
|
+
const remaining = options.maxResults - matches.length;
|
|
1541
|
+
if (remaining <= 0) break;
|
|
1542
|
+
matches.push(...await nativeSearchBatch(root, files, options, remaining));
|
|
1543
|
+
}
|
|
1544
|
+
return matches;
|
|
1545
|
+
}
|
|
1546
|
+
function nativeSearchBatch(root, paths2, options, remaining) {
|
|
1547
|
+
return new Promise((resolveMatches, reject) => {
|
|
1548
|
+
const args = [
|
|
1549
|
+
"--fixed-strings",
|
|
1550
|
+
"--json",
|
|
1551
|
+
"--no-messages",
|
|
1552
|
+
"--sort=path",
|
|
1553
|
+
`--max-filesize=${options.maxFileBytes}`,
|
|
1554
|
+
"--max-columns=4096",
|
|
1555
|
+
"--max-columns-preview",
|
|
1556
|
+
options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
|
|
1557
|
+
"--",
|
|
1558
|
+
options.query,
|
|
1559
|
+
...paths2
|
|
1560
|
+
];
|
|
1561
|
+
const child = spawn3("rg", args, {
|
|
1562
|
+
cwd: root,
|
|
1563
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1564
|
+
...options.signal ? { signal: options.signal } : {}
|
|
1565
|
+
});
|
|
1566
|
+
const matches = [];
|
|
1567
|
+
let carry = "";
|
|
1568
|
+
let stopped = false;
|
|
1569
|
+
const consume = (line) => {
|
|
1570
|
+
if (matches.length >= remaining) return;
|
|
1571
|
+
let event;
|
|
1572
|
+
try {
|
|
1573
|
+
event = JSON.parse(line);
|
|
1574
|
+
} catch {
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
const path = event.data?.path?.text;
|
|
1578
|
+
const lineNumber = event.data?.line_number;
|
|
1579
|
+
const source = event.data?.lines?.text;
|
|
1580
|
+
if (event.type !== "match" || path === void 0 || lineNumber === void 0 || source === void 0) return;
|
|
1581
|
+
matches.push({ path, line: lineNumber, text: source.trim().slice(0, 240) });
|
|
1582
|
+
if (matches.length >= remaining) {
|
|
1583
|
+
stopped = true;
|
|
1584
|
+
child.kill();
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
child.stdout.setEncoding("utf8");
|
|
1588
|
+
child.stdout.on("data", (chunk) => {
|
|
1589
|
+
carry += chunk;
|
|
1590
|
+
let newline = carry.indexOf("\n");
|
|
1591
|
+
while (newline >= 0) {
|
|
1592
|
+
consume(carry.slice(0, newline));
|
|
1593
|
+
carry = carry.slice(newline + 1);
|
|
1594
|
+
newline = carry.indexOf("\n");
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
child.once("error", reject);
|
|
1598
|
+
child.once("close", (code) => {
|
|
1599
|
+
if (carry) consume(carry);
|
|
1600
|
+
if (stopped || code === 0 || code === 1) resolveMatches(matches);
|
|
1601
|
+
else reject(new Error(`native search exited with status ${code ?? "unknown"}`));
|
|
1602
|
+
});
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
async function fallbackSearch(root, scoped, options) {
|
|
1606
|
+
const query = options.caseSensitive === false ? options.query.toLowerCase() : options.query;
|
|
1480
1607
|
const matches = [];
|
|
1481
1608
|
for (const path of scoped) {
|
|
1482
|
-
|
|
1609
|
+
options.signal?.throwIfAborted();
|
|
1610
|
+
if (matches.length >= options.maxResults) break;
|
|
1483
1611
|
let source;
|
|
1484
1612
|
try {
|
|
1485
1613
|
source = await readFile2(resolve4(root, path));
|
|
1486
1614
|
} catch {
|
|
1487
1615
|
continue;
|
|
1488
1616
|
}
|
|
1489
|
-
if (source.byteLength > maxFileBytes || source.includes(0)) continue;
|
|
1617
|
+
if (source.byteLength > options.maxFileBytes || source.includes(0)) continue;
|
|
1490
1618
|
const lines = source.toString("utf8").split("\n");
|
|
1491
1619
|
for (let index = 0; index < lines.length; index += 1) {
|
|
1492
1620
|
const raw = lines[index];
|
|
1493
1621
|
const haystack = options.caseSensitive === false ? raw.toLowerCase() : raw;
|
|
1494
1622
|
if (!haystack.includes(query)) continue;
|
|
1495
1623
|
matches.push({ path, line: index + 1, text: raw.trim().slice(0, 240) });
|
|
1496
|
-
if (matches.length >= maxResults) break;
|
|
1624
|
+
if (matches.length >= options.maxResults) break;
|
|
1497
1625
|
}
|
|
1498
1626
|
}
|
|
1499
1627
|
return matches;
|
|
@@ -1626,16 +1754,16 @@ function createCodePolicyGate(options) {
|
|
|
1626
1754
|
}
|
|
1627
1755
|
};
|
|
1628
1756
|
}
|
|
1629
|
-
function directoryPrefixes(
|
|
1757
|
+
function directoryPrefixes(paths2) {
|
|
1630
1758
|
const prefixes = /* @__PURE__ */ new Set(["."]);
|
|
1631
|
-
for (const path of
|
|
1759
|
+
for (const path of paths2) {
|
|
1632
1760
|
const parts = path.split("/");
|
|
1633
1761
|
for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
|
|
1634
1762
|
}
|
|
1635
1763
|
return [...prefixes].sort();
|
|
1636
1764
|
}
|
|
1637
|
-
async function safePrefix(base,
|
|
1638
|
-
const prefixes = directoryPrefixes(
|
|
1765
|
+
async function safePrefix(base, paths2, prefix) {
|
|
1766
|
+
const prefixes = directoryPrefixes(paths2);
|
|
1639
1767
|
const conversions = await conversionRegistry(
|
|
1640
1768
|
[await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
|
|
1641
1769
|
{ "code.prefixes.v1": prefixes }
|
|
@@ -1765,7 +1893,7 @@ import {
|
|
|
1765
1893
|
} from "@odla-ai/graph";
|
|
1766
1894
|
import { buildCodeGraph, FILE, IMPORTS, PACKAGE, READS, SYMBOL, WRITES } from "@odla-ai/graph/code";
|
|
1767
1895
|
var cache = /* @__PURE__ */ new Map();
|
|
1768
|
-
function workspaceGraphs(workspaceDir,
|
|
1896
|
+
function workspaceGraphs(workspaceDir, paths2) {
|
|
1769
1897
|
const existing = cache.get(workspaceDir);
|
|
1770
1898
|
if (existing) return existing;
|
|
1771
1899
|
const read2 = (path) => readFile3(join3(workspaceDir, path), "utf8");
|
|
@@ -1773,11 +1901,14 @@ function workspaceGraphs(workspaceDir, paths) {
|
|
|
1773
1901
|
// No knownTables: a staged workspace may not carry migrations, and a filter
|
|
1774
1902
|
// that silently drops every table is worse than an unfiltered one. Callers
|
|
1775
1903
|
// with ground truth should build the graph themselves.
|
|
1776
|
-
graph: await buildCodeGraph({ paths, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
|
|
1904
|
+
graph: await buildCodeGraph({ paths: paths2, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
|
|
1777
1905
|
}))();
|
|
1778
1906
|
cache.set(workspaceDir, built);
|
|
1779
1907
|
return built;
|
|
1780
1908
|
}
|
|
1909
|
+
function forgetWorkspaceGraphs(workspaceDir) {
|
|
1910
|
+
cache.delete(workspaceDir);
|
|
1911
|
+
}
|
|
1781
1912
|
var shortId = (id) => id.slice(id.indexOf(":") + 1);
|
|
1782
1913
|
function renderOverview(graphs, prefix) {
|
|
1783
1914
|
const rows = rollup(graphs.graph, FILE, prefix === void 0 ? {} : { prefix });
|
|
@@ -1824,7 +1955,7 @@ var GRAPH_TOOLS = /* @__PURE__ */ new Set([
|
|
|
1824
1955
|
"sandbox.who_imports",
|
|
1825
1956
|
"sandbox.who_touches"
|
|
1826
1957
|
]);
|
|
1827
|
-
async function read(context, request, options, policy) {
|
|
1958
|
+
async function read(context, request, options, policy, registry) {
|
|
1828
1959
|
exactKeys(request.input, ["path", "startLine", "endLine"]);
|
|
1829
1960
|
const path = stringField(request.input, "path");
|
|
1830
1961
|
const startLine = optionalInteger(request.input.startLine) ?? 1;
|
|
@@ -1832,11 +1963,11 @@ async function read(context, request, options, policy) {
|
|
|
1832
1963
|
if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
|
|
1833
1964
|
throw new TypeError("requested line range exceeds its bound");
|
|
1834
1965
|
}
|
|
1835
|
-
const
|
|
1836
|
-
if (!
|
|
1966
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
1967
|
+
if (!paths2.includes(path)) {
|
|
1837
1968
|
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.`);
|
|
1838
1969
|
}
|
|
1839
|
-
const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
|
|
1970
|
+
const allowed = await policy.read(policyContext(context, request, options, { paths: paths2, path, startLine, endLine }));
|
|
1840
1971
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
1841
1972
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
1842
1973
|
const info = await stat(target);
|
|
@@ -1852,22 +1983,22 @@ async function read(context, request, options, policy) {
|
|
|
1852
1983
|
}
|
|
1853
1984
|
return response(request, true, content, { path, startLine, endLine: Math.min(endLine, lines.length) });
|
|
1854
1985
|
}
|
|
1855
|
-
async function list(context, request, options, policy) {
|
|
1986
|
+
async function list(context, request, options, policy, registry) {
|
|
1856
1987
|
exactKeys(request.input, ["prefix", "maxEntries"]);
|
|
1857
1988
|
const raw = request.input.prefix;
|
|
1858
1989
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
1859
1990
|
const maxEntries = optionalInteger(request.input.maxEntries) ?? 1e3;
|
|
1860
1991
|
if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
|
|
1861
|
-
const
|
|
1862
|
-
const allowed = await policy.list(policyContext(context, request, options, { paths, ...prefix ? { prefix } : {} }));
|
|
1992
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
1993
|
+
const allowed = await policy.list(policyContext(context, request, options, { paths: paths2, ...prefix ? { prefix } : {} }));
|
|
1863
1994
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
1864
|
-
const entries = listWorkspace(
|
|
1995
|
+
const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
|
|
1865
1996
|
if (!entries.length) {
|
|
1866
1997
|
return response(request, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
|
|
1867
1998
|
}
|
|
1868
|
-
const truncated = entries.length <
|
|
1869
|
-
const hint = !prefix &&
|
|
1870
|
-
\u2026 ${
|
|
1999
|
+
const truncated = entries.length < paths2.length && entries.length === maxEntries;
|
|
2000
|
+
const hint = !prefix && paths2.length > 500 ? `
|
|
2001
|
+
\u2026 ${paths2.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
|
|
1871
2002
|
return response(
|
|
1872
2003
|
request,
|
|
1873
2004
|
true,
|
|
@@ -1876,7 +2007,7 @@ async function list(context, request, options, policy) {
|
|
|
1876
2007
|
{ count: entries.length, truncated }
|
|
1877
2008
|
);
|
|
1878
2009
|
}
|
|
1879
|
-
async function search(context, request, options, policy) {
|
|
2010
|
+
async function search(context, request, options, policy, registry) {
|
|
1880
2011
|
exactKeys(request.input, ["query", "prefix", "maxResults", "caseSensitive"]);
|
|
1881
2012
|
const query = stringField(request.input, "query");
|
|
1882
2013
|
if (query.length > 512) throw new TypeError("search query exceeds its bound");
|
|
@@ -1885,21 +2016,22 @@ async function search(context, request, options, policy) {
|
|
|
1885
2016
|
const maxResults = optionalInteger(request.input.maxResults) ?? 100;
|
|
1886
2017
|
if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
|
|
1887
2018
|
const caseSensitive = request.input.caseSensitive === void 0 ? true : request.input.caseSensitive === true;
|
|
1888
|
-
const
|
|
1889
|
-
const allowed = await policy.search(policyContext(context, request, options, { paths, query, ...prefix ? { prefix } : {} }));
|
|
2019
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2020
|
+
const allowed = await policy.search(policyContext(context, request, options, { paths: paths2, query, ...prefix ? { prefix } : {} }));
|
|
1890
2021
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
1891
|
-
const matches = await searchWorkspace(context.workspaceDir,
|
|
2022
|
+
const matches = await searchWorkspace(context.workspaceDir, paths2, {
|
|
1892
2023
|
query,
|
|
1893
2024
|
maxResults,
|
|
1894
2025
|
caseSensitive,
|
|
1895
|
-
...prefix ? { prefix } : {}
|
|
2026
|
+
...prefix ? { prefix } : {},
|
|
2027
|
+
...context.signal ? { signal: context.signal } : {}
|
|
1896
2028
|
});
|
|
1897
2029
|
if (!matches.length) return response(request, true, `No match for "${query}".`, { count: 0 });
|
|
1898
2030
|
return response(request, true, matches.map((match) => `${match.path}:${match.line}: ${match.text}`).join("\n"), {
|
|
1899
2031
|
count: matches.length
|
|
1900
2032
|
});
|
|
1901
2033
|
}
|
|
1902
|
-
async function graphQuery(context, request, options, policy) {
|
|
2034
|
+
async function graphQuery(context, request, options, policy, registry) {
|
|
1903
2035
|
exactKeys(request.input, ["query"]);
|
|
1904
2036
|
const raw = request.input.query;
|
|
1905
2037
|
const query = typeof raw === "string" ? raw : "";
|
|
@@ -1909,8 +2041,8 @@ async function graphQuery(context, request, options, policy) {
|
|
|
1909
2041
|
selector: query
|
|
1910
2042
|
}));
|
|
1911
2043
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
1912
|
-
const
|
|
1913
|
-
const graphs = await workspaceGraphs(context.workspaceDir,
|
|
2044
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
2045
|
+
const graphs = await workspaceGraphs(context.workspaceDir, paths2);
|
|
1914
2046
|
if (request.tool === "sandbox.overview") {
|
|
1915
2047
|
return response(request, true, renderOverview(graphs, query || void 0));
|
|
1916
2048
|
}
|
|
@@ -1925,23 +2057,38 @@ function createCodeToolBroker(options) {
|
|
|
1925
2057
|
validateOptions(options);
|
|
1926
2058
|
const recipes = new Map(options.recipes.map((recipe2) => [recipe2.id, recipe2]));
|
|
1927
2059
|
const policy = createCodePolicyGate(options);
|
|
1928
|
-
|
|
2060
|
+
const registry = createWorkspaceFileRegistry();
|
|
2061
|
+
let barrier = Promise.resolve();
|
|
2062
|
+
const activeReads = /* @__PURE__ */ new Set();
|
|
1929
2063
|
return {
|
|
1930
2064
|
execute(context, request) {
|
|
1931
|
-
|
|
1932
|
-
|
|
2065
|
+
if (isReadTool(request.tool)) {
|
|
2066
|
+
const result2 = barrier.then(() => route(context, request, options, recipes, policy, registry));
|
|
2067
|
+
const settled = result2.then(() => void 0, () => void 0);
|
|
2068
|
+
activeReads.add(settled);
|
|
2069
|
+
void settled.then(() => {
|
|
2070
|
+
activeReads.delete(settled);
|
|
2071
|
+
});
|
|
2072
|
+
return result2;
|
|
2073
|
+
}
|
|
2074
|
+
const earlierReads = [...activeReads];
|
|
2075
|
+
const result = barrier.then(() => Promise.all(earlierReads)).then(() => route(context, request, options, recipes, policy, registry));
|
|
2076
|
+
barrier = result.then(() => void 0, () => void 0);
|
|
1933
2077
|
return result;
|
|
1934
2078
|
}
|
|
1935
2079
|
};
|
|
1936
2080
|
}
|
|
1937
|
-
|
|
2081
|
+
function isReadTool(tool) {
|
|
2082
|
+
return tool === "sandbox.read" || tool === "sandbox.list" || tool === "sandbox.search" || GRAPH_TOOLS.has(tool);
|
|
2083
|
+
}
|
|
2084
|
+
async function route(context, request, options, recipes, policy, registry) {
|
|
1938
2085
|
try {
|
|
1939
2086
|
if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
|
|
1940
|
-
if (request.tool === "sandbox.read") return await read(context, request, options, policy);
|
|
1941
|
-
if (request.tool === "sandbox.list") return await list(context, request, options, policy);
|
|
1942
|
-
if (request.tool === "sandbox.search") return await search(context, request, options, policy);
|
|
1943
|
-
if (GRAPH_TOOLS.has(request.tool)) return await graphQuery(context, request, options, policy);
|
|
1944
|
-
if (request.tool === "sandbox.apply_patch") return await patch(context, request, options, policy);
|
|
2087
|
+
if (request.tool === "sandbox.read") return await read(context, request, options, policy, registry);
|
|
2088
|
+
if (request.tool === "sandbox.list") return await list(context, request, options, policy, registry);
|
|
2089
|
+
if (request.tool === "sandbox.search") return await search(context, request, options, policy, registry);
|
|
2090
|
+
if (GRAPH_TOOLS.has(request.tool)) return await graphQuery(context, request, options, policy, registry);
|
|
2091
|
+
if (request.tool === "sandbox.apply_patch") return await patch(context, request, options, policy, registry);
|
|
1945
2092
|
return await recipe(context, request, options, recipes, policy);
|
|
1946
2093
|
} catch (reason) {
|
|
1947
2094
|
return response(request, false, toolFailureMessage(reason));
|
|
@@ -1956,17 +2103,19 @@ function toolFailureMessage(reason) {
|
|
|
1956
2103
|
if (code === "EACCES" || code === "EPERM") return "that path is not readable through this tool";
|
|
1957
2104
|
return "tool failed closed";
|
|
1958
2105
|
}
|
|
1959
|
-
async function patch(context, request, options, policy) {
|
|
2106
|
+
async function patch(context, request, options, policy, registry) {
|
|
1960
2107
|
exactKeys(request.input, ["patch"]);
|
|
1961
2108
|
const value = stringField(request.input, "patch");
|
|
1962
|
-
const
|
|
1963
|
-
if (
|
|
2109
|
+
const paths2 = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
|
|
2110
|
+
if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
1964
2111
|
throw new TypeError("patch targets a read-only reference source");
|
|
1965
2112
|
}
|
|
1966
2113
|
const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
|
|
1967
2114
|
if (!allowed) return response(request, false, "tool denied by CaMeL policy");
|
|
1968
|
-
await applyCodePatch(context.workspaceDir, value,
|
|
1969
|
-
|
|
2115
|
+
await applyCodePatch(context.workspaceDir, value, paths2);
|
|
2116
|
+
registry.invalidate(context.workspaceDir);
|
|
2117
|
+
forgetWorkspaceGraphs(context.workspaceDir);
|
|
2118
|
+
return response(request, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
|
|
1970
2119
|
}
|
|
1971
2120
|
async function recipe(context, request, options, recipes, policy) {
|
|
1972
2121
|
exactKeys(request.input, ["recipeId"]);
|
|
@@ -2350,12 +2499,129 @@ import { createHash as createHash3 } from "crypto";
|
|
|
2350
2499
|
async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
2351
2500
|
const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
|
|
2352
2501
|
refs.push(eventId);
|
|
2353
|
-
const
|
|
2502
|
+
const attributed = { ...event, interactionId: command.commandId };
|
|
2503
|
+
const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
|
|
2354
2504
|
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
2355
2505
|
}
|
|
2356
2506
|
var digestRuntimeValue = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
|
|
2357
2507
|
var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
|
|
2358
2508
|
|
|
2509
|
+
// src/code-tool-presentation.ts
|
|
2510
|
+
var text = (value, maximum) => {
|
|
2511
|
+
if (typeof value !== "string") return void 0;
|
|
2512
|
+
const bounded = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
|
|
2513
|
+
return bounded ? bounded.slice(0, maximum) : void 0;
|
|
2514
|
+
};
|
|
2515
|
+
var integer2 = (value) => Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : void 0;
|
|
2516
|
+
var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2517
|
+
var excerpt = (value, tail = false) => {
|
|
2518
|
+
if (typeof value !== "string") return void 0;
|
|
2519
|
+
const safe = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
|
|
2520
|
+
const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
|
|
2521
|
+
if (!source) return void 0;
|
|
2522
|
+
const lines = source.split("\n").filter((line) => line.trim()).map((line) => line.slice(0, 240));
|
|
2523
|
+
const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
|
|
2524
|
+
return text(selected.join("\n"), 2400);
|
|
2525
|
+
};
|
|
2526
|
+
var paths = (value) => {
|
|
2527
|
+
if (!Array.isArray(value)) return void 0;
|
|
2528
|
+
const items = value.flatMap((item) => {
|
|
2529
|
+
const path = text(item, 1024);
|
|
2530
|
+
return path ? [path] : [];
|
|
2531
|
+
}).slice(0, 12);
|
|
2532
|
+
return items.length ? items : void 0;
|
|
2533
|
+
};
|
|
2534
|
+
function patchStats(value) {
|
|
2535
|
+
if (typeof value !== "string") return {};
|
|
2536
|
+
let additions = 0;
|
|
2537
|
+
let deletions = 0;
|
|
2538
|
+
for (const line of value.slice(0, 262144).split("\n")) {
|
|
2539
|
+
if (line.startsWith("+++") || line.startsWith("---")) continue;
|
|
2540
|
+
if (line.startsWith("+")) additions += 1;
|
|
2541
|
+
else if (line.startsWith("-")) deletions += 1;
|
|
2542
|
+
}
|
|
2543
|
+
return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
|
|
2544
|
+
}
|
|
2545
|
+
function searchResults(value) {
|
|
2546
|
+
if (typeof value !== "string") return void 0;
|
|
2547
|
+
const results = value.split("\n").flatMap((line) => {
|
|
2548
|
+
const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line);
|
|
2549
|
+
if (!match) return [];
|
|
2550
|
+
const lineNumber = Number(match[2]);
|
|
2551
|
+
const itemText = text(match[3], 240);
|
|
2552
|
+
if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
|
|
2553
|
+
return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
|
|
2554
|
+
}).slice(0, 5);
|
|
2555
|
+
return results.length ? results : void 0;
|
|
2556
|
+
}
|
|
2557
|
+
function codeToolRequestPresentation(request) {
|
|
2558
|
+
const input = request.input;
|
|
2559
|
+
if (request.tool === "sandbox.read") {
|
|
2560
|
+
const path = text(input.path, 1024);
|
|
2561
|
+
if (!path) return void 0;
|
|
2562
|
+
const startLine = integer2(input.startLine);
|
|
2563
|
+
const endLine = integer2(input.endLine);
|
|
2564
|
+
return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
|
|
2565
|
+
}
|
|
2566
|
+
if (request.tool === "sandbox.list") {
|
|
2567
|
+
const scope = text(input.prefix, 1024);
|
|
2568
|
+
return { kind: "list", ...scope ? { scope } : {} };
|
|
2569
|
+
}
|
|
2570
|
+
if (request.tool === "sandbox.search" || request.tool === "sandbox.overview" || request.tool === "sandbox.where_is" || request.tool === "sandbox.who_imports" || request.tool === "sandbox.who_touches") {
|
|
2571
|
+
const query = text(input.query, 512);
|
|
2572
|
+
const scope = request.tool === "sandbox.search" ? text(input.prefix, 1024) : void 0;
|
|
2573
|
+
if (request.tool === "sandbox.search" && !query) return void 0;
|
|
2574
|
+
return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
|
|
2575
|
+
}
|
|
2576
|
+
if (request.tool === "sandbox.apply_patch") {
|
|
2577
|
+
return { kind: "patch", ...patchStats(input.patch) };
|
|
2578
|
+
}
|
|
2579
|
+
const recipeId = text(input.recipeId, 120);
|
|
2580
|
+
return recipeId ? { kind: "recipe", recipeId } : void 0;
|
|
2581
|
+
}
|
|
2582
|
+
function codeToolResultPresentation(request, response2) {
|
|
2583
|
+
const started = codeToolRequestPresentation(request);
|
|
2584
|
+
if (!started || !response2.ok) return started;
|
|
2585
|
+
const details = record3(response2.details);
|
|
2586
|
+
if (started.kind === "read") {
|
|
2587
|
+
return {
|
|
2588
|
+
...started,
|
|
2589
|
+
...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
|
|
2590
|
+
...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
|
|
2591
|
+
...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
|
|
2592
|
+
};
|
|
2593
|
+
}
|
|
2594
|
+
if (started.kind === "list") {
|
|
2595
|
+
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);
|
|
2596
|
+
return {
|
|
2597
|
+
...started,
|
|
2598
|
+
...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
|
|
2599
|
+
...listed.length ? { paths: listed } : {}
|
|
2600
|
+
};
|
|
2601
|
+
}
|
|
2602
|
+
if (started.kind === "query") {
|
|
2603
|
+
const results = request.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
|
|
2604
|
+
const resultExcerpt = request.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
|
|
2605
|
+
return {
|
|
2606
|
+
...started,
|
|
2607
|
+
...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
|
|
2608
|
+
...results ? { results } : {},
|
|
2609
|
+
...resultExcerpt ? { excerpt: resultExcerpt } : {}
|
|
2610
|
+
};
|
|
2611
|
+
}
|
|
2612
|
+
if (started.kind === "patch") {
|
|
2613
|
+
return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
|
|
2614
|
+
}
|
|
2615
|
+
const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
|
|
2616
|
+
return {
|
|
2617
|
+
...started,
|
|
2618
|
+
...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
|
|
2619
|
+
...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
|
|
2620
|
+
...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
|
|
2621
|
+
...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2359
2625
|
// src/code-runtime-engine.ts
|
|
2360
2626
|
var TheseusRuntimeEngine = class {
|
|
2361
2627
|
constructor(options) {
|
|
@@ -2590,18 +2856,29 @@ var TheseusRuntimeEngine = class {
|
|
|
2590
2856
|
return {
|
|
2591
2857
|
execute: async (context, request) => {
|
|
2592
2858
|
const startedAt = Date.now();
|
|
2859
|
+
const operationId = digestRuntimeValue(`${command.commandId}:${request.requestId}`);
|
|
2860
|
+
const startedPresentation = codeToolRequestPresentation(request);
|
|
2593
2861
|
await this.#event(
|
|
2594
2862
|
command,
|
|
2595
|
-
{
|
|
2863
|
+
{
|
|
2864
|
+
type: "tool",
|
|
2865
|
+
phase: "started",
|
|
2866
|
+
tool: request.tool,
|
|
2867
|
+
operationId,
|
|
2868
|
+
...startedPresentation ? { presentation: startedPresentation } : {}
|
|
2869
|
+
},
|
|
2596
2870
|
active.conversationRefs
|
|
2597
2871
|
).catch(() => void 0);
|
|
2598
2872
|
const response2 = await broker.execute(context, request);
|
|
2873
|
+
const completedPresentation = codeToolResultPresentation(request, response2);
|
|
2599
2874
|
await this.#event(command, {
|
|
2600
2875
|
type: "tool",
|
|
2601
2876
|
phase: "completed",
|
|
2602
2877
|
tool: request.tool,
|
|
2603
2878
|
ok: response2.ok,
|
|
2604
|
-
durationMs: Date.now() - startedAt
|
|
2879
|
+
durationMs: Date.now() - startedAt,
|
|
2880
|
+
operationId,
|
|
2881
|
+
...completedPresentation ? { presentation: completedPresentation } : {}
|
|
2605
2882
|
}, active.conversationRefs).catch(() => void 0);
|
|
2606
2883
|
return response2;
|
|
2607
2884
|
}
|
|
@@ -2677,4 +2954,4 @@ export {
|
|
|
2677
2954
|
runGoal,
|
|
2678
2955
|
TheseusRuntimeEngine
|
|
2679
2956
|
};
|
|
2680
|
-
//# sourceMappingURL=chunk-
|
|
2957
|
+
//# sourceMappingURL=chunk-ISR434K7.js.map
|