@odla-ai/harness 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  encodeAgentInput,
3
3
  parseAgentOutput
4
- } from "./chunk-FVOMJKWK.js";
4
+ } from "./chunk-Q7CKOT7T.js";
5
5
  import {
6
6
  HARNESS_PROTOCOL_VERSION
7
- } from "./chunk-LNQNFGQC.js";
7
+ } from "./chunk-RXNHCGWE.js";
8
8
 
9
9
  // src/container.ts
10
10
  import { execFile, spawn } from "child_process";
@@ -548,4 +548,4 @@ export {
548
548
  stageWorkspacePair,
549
549
  safeWorkspaceLabel
550
550
  };
551
- //# sourceMappingURL=chunk-K76I2TCQ.js.map
551
+ //# sourceMappingURL=chunk-CR6RE3A2.js.map
@@ -4,10 +4,10 @@ import {
4
4
  stageWorkspace,
5
5
  stageWorkspacePair,
6
6
  verifyContainerEngineBoundary
7
- } from "./chunk-K76I2TCQ.js";
7
+ } from "./chunk-CR6RE3A2.js";
8
8
  import {
9
9
  HARNESS_PROTOCOL_VERSION
10
- } from "./chunk-LNQNFGQC.js";
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 paths = [];
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
- paths.push(path);
370
+ paths2.push(path);
371
371
  }
372
- if (!paths.length || new Set(paths).size !== paths.length) throw new TypeError("patch has no diffs or repeats a path");
373
- return paths;
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, paths) {
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 paths) {
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", (text) => {
425
- if (stderr.length < 4e3) stderr += text.slice(0, 4e3);
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 paths = validateCodePatch(checkpoint.patch, 256 * 1024);
456
- await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths);
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 paths = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
626
- await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths);
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(paths, policy);
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(paths, policy) {
703
- return paths.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
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";
@@ -1464,7 +1464,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
1464
1464
  files(root) {
1465
1465
  const existing = cache2.get(root);
1466
1466
  if (existing) return existing;
1467
- const pending = enumerate(root, limit).then((paths) => Object.freeze(paths));
1467
+ const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
1468
1468
  cache2.set(root, pending);
1469
1469
  void pending.catch(() => {
1470
1470
  if (cache2.get(root) === pending) cache2.delete(root);
@@ -1477,7 +1477,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
1477
1477
  };
1478
1478
  }
1479
1479
  async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
1480
- const paths = [];
1480
+ const paths2 = [];
1481
1481
  const walk = async (directory) => {
1482
1482
  for (const entry of await readdir2(directory, { withFileTypes: true })) {
1483
1483
  if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
@@ -1491,26 +1491,26 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
1491
1491
  } catch {
1492
1492
  continue;
1493
1493
  }
1494
- paths.push(path);
1495
- if (paths.length > limit) throw new TypeError("workspace file registry exceeds its bound");
1494
+ paths2.push(path);
1495
+ if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
1496
1496
  }
1497
1497
  }
1498
1498
  };
1499
1499
  await walk(resolve4(root));
1500
- return paths.sort();
1500
+ return paths2.sort();
1501
1501
  }
1502
- function listWorkspace(paths, options = {}) {
1502
+ function listWorkspace(paths2, options = {}) {
1503
1503
  const max = options.maxEntries ?? 1e3;
1504
1504
  const prefix = options.prefix?.replace(/\/+$/, "");
1505
- const scoped = prefix ? paths.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths];
1505
+ const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
1506
1506
  return scoped.slice(0, max);
1507
1507
  }
1508
- async function searchWorkspace(root, paths, options) {
1508
+ async function searchWorkspace(root, paths2, options) {
1509
1509
  options.signal?.throwIfAborted();
1510
1510
  if (!options.query) throw new TypeError("search query must be a non-empty string");
1511
1511
  const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
1512
1512
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
1513
- const scoped = listWorkspace(paths, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths.length });
1513
+ const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
1514
1514
  if (scoped.length === 0) return [];
1515
1515
  try {
1516
1516
  return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
@@ -1520,11 +1520,11 @@ async function searchWorkspace(root, paths, options) {
1520
1520
  }
1521
1521
  }
1522
1522
  var MAX_NATIVE_ARG_BYTES = 96 * 1024;
1523
- async function nativeSearch(root, paths, options) {
1523
+ async function nativeSearch(root, paths2, options) {
1524
1524
  const batches = [];
1525
1525
  let batch = [];
1526
1526
  let bytes = 0;
1527
- for (const path of paths) {
1527
+ for (const path of paths2) {
1528
1528
  const size = Buffer.byteLength(path) + 1;
1529
1529
  if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
1530
1530
  batches.push(batch);
@@ -1543,7 +1543,7 @@ async function nativeSearch(root, paths, options) {
1543
1543
  }
1544
1544
  return matches;
1545
1545
  }
1546
- function nativeSearchBatch(root, paths, options, remaining) {
1546
+ function nativeSearchBatch(root, paths2, options, remaining) {
1547
1547
  return new Promise((resolveMatches, reject) => {
1548
1548
  const args = [
1549
1549
  "--fixed-strings",
@@ -1556,7 +1556,7 @@ function nativeSearchBatch(root, paths, options, remaining) {
1556
1556
  options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
1557
1557
  "--",
1558
1558
  options.query,
1559
- ...paths
1559
+ ...paths2
1560
1560
  ];
1561
1561
  const child = spawn3("rg", args, {
1562
1562
  cwd: root,
@@ -1754,16 +1754,16 @@ function createCodePolicyGate(options) {
1754
1754
  }
1755
1755
  };
1756
1756
  }
1757
- function directoryPrefixes(paths) {
1757
+ function directoryPrefixes(paths2) {
1758
1758
  const prefixes = /* @__PURE__ */ new Set(["."]);
1759
- for (const path of paths) {
1759
+ for (const path of paths2) {
1760
1760
  const parts = path.split("/");
1761
1761
  for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
1762
1762
  }
1763
1763
  return [...prefixes].sort();
1764
1764
  }
1765
- async function safePrefix(base, paths, prefix) {
1766
- const prefixes = directoryPrefixes(paths);
1765
+ async function safePrefix(base, paths2, prefix) {
1766
+ const prefixes = directoryPrefixes(paths2);
1767
1767
  const conversions = await conversionRegistry(
1768
1768
  [await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
1769
1769
  { "code.prefixes.v1": prefixes }
@@ -1893,7 +1893,7 @@ import {
1893
1893
  } from "@odla-ai/graph";
1894
1894
  import { buildCodeGraph, FILE, IMPORTS, PACKAGE, READS, SYMBOL, WRITES } from "@odla-ai/graph/code";
1895
1895
  var cache = /* @__PURE__ */ new Map();
1896
- function workspaceGraphs(workspaceDir, paths) {
1896
+ function workspaceGraphs(workspaceDir, paths2) {
1897
1897
  const existing = cache.get(workspaceDir);
1898
1898
  if (existing) return existing;
1899
1899
  const read2 = (path) => readFile3(join3(workspaceDir, path), "utf8");
@@ -1901,7 +1901,7 @@ function workspaceGraphs(workspaceDir, paths) {
1901
1901
  // No knownTables: a staged workspace may not carry migrations, and a filter
1902
1902
  // that silently drops every table is worse than an unfiltered one. Callers
1903
1903
  // with ground truth should build the graph themselves.
1904
- 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.") } })
1905
1905
  }))();
1906
1906
  cache.set(workspaceDir, built);
1907
1907
  return built;
@@ -1963,11 +1963,11 @@ async function read(context, request, options, policy, registry) {
1963
1963
  if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
1964
1964
  throw new TypeError("requested line range exceeds its bound");
1965
1965
  }
1966
- const paths = await registry.files(context.workspaceDir);
1967
- if (!paths.includes(path)) {
1966
+ const paths2 = await registry.files(context.workspaceDir);
1967
+ if (!paths2.includes(path)) {
1968
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.`);
1969
1969
  }
1970
- 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 }));
1971
1971
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
1972
1972
  const target = resolveCodePath(context.workspaceDir, path);
1973
1973
  const info = await stat(target);
@@ -1989,16 +1989,16 @@ async function list(context, request, options, policy, registry) {
1989
1989
  const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
1990
1990
  const maxEntries = optionalInteger(request.input.maxEntries) ?? 1e3;
1991
1991
  if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
1992
- const paths = await registry.files(context.workspaceDir);
1993
- 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 } : {} }));
1994
1994
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
1995
- const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
1995
+ const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
1996
1996
  if (!entries.length) {
1997
1997
  return response(request, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
1998
1998
  }
1999
- const truncated = entries.length < paths.length && entries.length === maxEntries;
2000
- const hint = !prefix && paths.length > 500 ? `
2001
- \u2026 ${paths.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
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.` : "";
2002
2002
  return response(
2003
2003
  request,
2004
2004
  true,
@@ -2016,10 +2016,10 @@ async function search(context, request, options, policy, registry) {
2016
2016
  const maxResults = optionalInteger(request.input.maxResults) ?? 100;
2017
2017
  if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
2018
2018
  const caseSensitive = request.input.caseSensitive === void 0 ? true : request.input.caseSensitive === true;
2019
- const paths = await registry.files(context.workspaceDir);
2020
- 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 } : {} }));
2021
2021
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
2022
- const matches = await searchWorkspace(context.workspaceDir, paths, {
2022
+ const matches = await searchWorkspace(context.workspaceDir, paths2, {
2023
2023
  query,
2024
2024
  maxResults,
2025
2025
  caseSensitive,
@@ -2041,8 +2041,8 @@ async function graphQuery(context, request, options, policy, registry) {
2041
2041
  selector: query
2042
2042
  }));
2043
2043
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
2044
- const paths = await registry.files(context.workspaceDir);
2045
- const graphs = await workspaceGraphs(context.workspaceDir, paths);
2044
+ const paths2 = await registry.files(context.workspaceDir);
2045
+ const graphs = await workspaceGraphs(context.workspaceDir, paths2);
2046
2046
  if (request.tool === "sandbox.overview") {
2047
2047
  return response(request, true, renderOverview(graphs, query || void 0));
2048
2048
  }
@@ -2106,16 +2106,16 @@ function toolFailureMessage(reason) {
2106
2106
  async function patch(context, request, options, policy, registry) {
2107
2107
  exactKeys(request.input, ["patch"]);
2108
2108
  const value = stringField(request.input, "patch");
2109
- const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
2110
- if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
2109
+ const paths2 = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
2110
+ if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
2111
2111
  throw new TypeError("patch targets a read-only reference source");
2112
2112
  }
2113
2113
  const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
2114
2114
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
2115
- await applyCodePatch(context.workspaceDir, value, paths);
2115
+ await applyCodePatch(context.workspaceDir, value, paths2);
2116
2116
  registry.invalidate(context.workspaceDir);
2117
2117
  forgetWorkspaceGraphs(context.workspaceDir);
2118
- return response(request, true, `Applied patch to ${paths.length} file(s).`, { paths });
2118
+ return response(request, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
2119
2119
  }
2120
2120
  async function recipe(context, request, options, recipes, policy) {
2121
2121
  exactKeys(request.input, ["recipeId"]);
@@ -2499,12 +2499,129 @@ import { createHash as createHash3 } from "crypto";
2499
2499
  async function appendCodeRuntimeEvent(control, command, event, refs) {
2500
2500
  const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
2501
2501
  refs.push(eventId);
2502
- const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
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;
2503
2504
  await control.appendSessionEvent(command.sessionId, eventId, bounded);
2504
2505
  }
2505
2506
  var digestRuntimeValue = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
2506
2507
  var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
2507
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
+
2508
2625
  // src/code-runtime-engine.ts
2509
2626
  var TheseusRuntimeEngine = class {
2510
2627
  constructor(options) {
@@ -2739,18 +2856,29 @@ var TheseusRuntimeEngine = class {
2739
2856
  return {
2740
2857
  execute: async (context, request) => {
2741
2858
  const startedAt = Date.now();
2859
+ const operationId = digestRuntimeValue(`${command.commandId}:${request.requestId}`);
2860
+ const startedPresentation = codeToolRequestPresentation(request);
2742
2861
  await this.#event(
2743
2862
  command,
2744
- { type: "tool", phase: "started", tool: request.tool },
2863
+ {
2864
+ type: "tool",
2865
+ phase: "started",
2866
+ tool: request.tool,
2867
+ operationId,
2868
+ ...startedPresentation ? { presentation: startedPresentation } : {}
2869
+ },
2745
2870
  active.conversationRefs
2746
2871
  ).catch(() => void 0);
2747
2872
  const response2 = await broker.execute(context, request);
2873
+ const completedPresentation = codeToolResultPresentation(request, response2);
2748
2874
  await this.#event(command, {
2749
2875
  type: "tool",
2750
2876
  phase: "completed",
2751
2877
  tool: request.tool,
2752
2878
  ok: response2.ok,
2753
- durationMs: Date.now() - startedAt
2879
+ durationMs: Date.now() - startedAt,
2880
+ operationId,
2881
+ ...completedPresentation ? { presentation: completedPresentation } : {}
2754
2882
  }, active.conversationRefs).catch(() => void 0);
2755
2883
  return response2;
2756
2884
  }
@@ -2826,4 +2954,4 @@ export {
2826
2954
  runGoal,
2827
2955
  TheseusRuntimeEngine
2828
2956
  };
2829
- //# sourceMappingURL=chunk-GYWQM76X.js.map
2957
+ //# sourceMappingURL=chunk-ISR434K7.js.map