@odla-ai/harness 0.7.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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";
@@ -1358,6 +1358,7 @@ async function runCodeAgentAttempt(options) {
1358
1358
  model: "brokered",
1359
1359
  surface,
1360
1360
  ...options.recipeIds ? { recipeIds: options.recipeIds } : {},
1361
+ ...options.extraSkills ? { extraSkills: options.extraSkills } : {},
1361
1362
  ...options.maxSteps === void 0 ? {} : { maxSteps: options.maxSteps },
1362
1363
  ...options.budget ? { budget: options.budget } : {},
1363
1364
  ...options.signal ? { signal: options.signal } : {},
@@ -1464,7 +1465,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
1464
1465
  files(root) {
1465
1466
  const existing = cache2.get(root);
1466
1467
  if (existing) return existing;
1467
- const pending = enumerate(root, limit).then((paths) => Object.freeze(paths));
1468
+ const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
1468
1469
  cache2.set(root, pending);
1469
1470
  void pending.catch(() => {
1470
1471
  if (cache2.get(root) === pending) cache2.delete(root);
@@ -1477,7 +1478,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
1477
1478
  };
1478
1479
  }
1479
1480
  async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
1480
- const paths = [];
1481
+ const paths2 = [];
1481
1482
  const walk = async (directory) => {
1482
1483
  for (const entry of await readdir2(directory, { withFileTypes: true })) {
1483
1484
  if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
@@ -1491,26 +1492,26 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
1491
1492
  } catch {
1492
1493
  continue;
1493
1494
  }
1494
- paths.push(path);
1495
- if (paths.length > limit) throw new TypeError("workspace file registry exceeds its bound");
1495
+ paths2.push(path);
1496
+ if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
1496
1497
  }
1497
1498
  }
1498
1499
  };
1499
1500
  await walk(resolve4(root));
1500
- return paths.sort();
1501
+ return paths2.sort();
1501
1502
  }
1502
- function listWorkspace(paths, options = {}) {
1503
+ function listWorkspace(paths2, options = {}) {
1503
1504
  const max = options.maxEntries ?? 1e3;
1504
1505
  const prefix = options.prefix?.replace(/\/+$/, "");
1505
- const scoped = prefix ? paths.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths];
1506
+ const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
1506
1507
  return scoped.slice(0, max);
1507
1508
  }
1508
- async function searchWorkspace(root, paths, options) {
1509
+ async function searchWorkspace(root, paths2, options) {
1509
1510
  options.signal?.throwIfAborted();
1510
1511
  if (!options.query) throw new TypeError("search query must be a non-empty string");
1511
1512
  const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
1512
1513
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
1513
- const scoped = listWorkspace(paths, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths.length });
1514
+ const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
1514
1515
  if (scoped.length === 0) return [];
1515
1516
  try {
1516
1517
  return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
@@ -1520,11 +1521,11 @@ async function searchWorkspace(root, paths, options) {
1520
1521
  }
1521
1522
  }
1522
1523
  var MAX_NATIVE_ARG_BYTES = 96 * 1024;
1523
- async function nativeSearch(root, paths, options) {
1524
+ async function nativeSearch(root, paths2, options) {
1524
1525
  const batches = [];
1525
1526
  let batch = [];
1526
1527
  let bytes = 0;
1527
- for (const path of paths) {
1528
+ for (const path of paths2) {
1528
1529
  const size = Buffer.byteLength(path) + 1;
1529
1530
  if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
1530
1531
  batches.push(batch);
@@ -1543,7 +1544,7 @@ async function nativeSearch(root, paths, options) {
1543
1544
  }
1544
1545
  return matches;
1545
1546
  }
1546
- function nativeSearchBatch(root, paths, options, remaining) {
1547
+ function nativeSearchBatch(root, paths2, options, remaining) {
1547
1548
  return new Promise((resolveMatches, reject) => {
1548
1549
  const args = [
1549
1550
  "--fixed-strings",
@@ -1556,7 +1557,7 @@ function nativeSearchBatch(root, paths, options, remaining) {
1556
1557
  options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
1557
1558
  "--",
1558
1559
  options.query,
1559
- ...paths
1560
+ ...paths2
1560
1561
  ];
1561
1562
  const child = spawn3("rg", args, {
1562
1563
  cwd: root,
@@ -1754,16 +1755,16 @@ function createCodePolicyGate(options) {
1754
1755
  }
1755
1756
  };
1756
1757
  }
1757
- function directoryPrefixes(paths) {
1758
+ function directoryPrefixes(paths2) {
1758
1759
  const prefixes = /* @__PURE__ */ new Set(["."]);
1759
- for (const path of paths) {
1760
+ for (const path of paths2) {
1760
1761
  const parts = path.split("/");
1761
1762
  for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
1762
1763
  }
1763
1764
  return [...prefixes].sort();
1764
1765
  }
1765
- async function safePrefix(base, paths, prefix) {
1766
- const prefixes = directoryPrefixes(paths);
1766
+ async function safePrefix(base, paths2, prefix) {
1767
+ const prefixes = directoryPrefixes(paths2);
1767
1768
  const conversions = await conversionRegistry(
1768
1769
  [await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
1769
1770
  { "code.prefixes.v1": prefixes }
@@ -1893,7 +1894,7 @@ import {
1893
1894
  } from "@odla-ai/graph";
1894
1895
  import { buildCodeGraph, FILE, IMPORTS, PACKAGE, READS, SYMBOL, WRITES } from "@odla-ai/graph/code";
1895
1896
  var cache = /* @__PURE__ */ new Map();
1896
- function workspaceGraphs(workspaceDir, paths) {
1897
+ function workspaceGraphs(workspaceDir, paths2) {
1897
1898
  const existing = cache.get(workspaceDir);
1898
1899
  if (existing) return existing;
1899
1900
  const read2 = (path) => readFile3(join3(workspaceDir, path), "utf8");
@@ -1901,7 +1902,7 @@ function workspaceGraphs(workspaceDir, paths) {
1901
1902
  // No knownTables: a staged workspace may not carry migrations, and a filter
1902
1903
  // that silently drops every table is worse than an unfiltered one. Callers
1903
1904
  // with ground truth should build the graph themselves.
1904
- graph: await buildCodeGraph({ paths, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
1905
+ graph: await buildCodeGraph({ paths: paths2, read: read2, data: { ignore: (path) => path.includes(".generated.") } })
1905
1906
  }))();
1906
1907
  cache.set(workspaceDir, built);
1907
1908
  return built;
@@ -1963,11 +1964,11 @@ async function read(context, request, options, policy, registry) {
1963
1964
  if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
1964
1965
  throw new TypeError("requested line range exceeds its bound");
1965
1966
  }
1966
- const paths = await registry.files(context.workspaceDir);
1967
- if (!paths.includes(path)) {
1967
+ const paths2 = await registry.files(context.workspaceDir);
1968
+ if (!paths2.includes(path)) {
1968
1969
  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
1970
  }
1970
- const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
1971
+ const allowed = await policy.read(policyContext(context, request, options, { paths: paths2, path, startLine, endLine }));
1971
1972
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
1972
1973
  const target = resolveCodePath(context.workspaceDir, path);
1973
1974
  const info = await stat(target);
@@ -1989,16 +1990,16 @@ async function list(context, request, options, policy, registry) {
1989
1990
  const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
1990
1991
  const maxEntries = optionalInteger(request.input.maxEntries) ?? 1e3;
1991
1992
  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 } : {} }));
1993
+ const paths2 = await registry.files(context.workspaceDir);
1994
+ const allowed = await policy.list(policyContext(context, request, options, { paths: paths2, ...prefix ? { prefix } : {} }));
1994
1995
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
1995
- const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
1996
+ const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
1996
1997
  if (!entries.length) {
1997
1998
  return response(request, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
1998
1999
  }
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.` : "";
2000
+ const truncated = entries.length < paths2.length && entries.length === maxEntries;
2001
+ const hint = !prefix && paths2.length > 500 ? `
2002
+ \u2026 ${paths2.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
2002
2003
  return response(
2003
2004
  request,
2004
2005
  true,
@@ -2016,10 +2017,10 @@ async function search(context, request, options, policy, registry) {
2016
2017
  const maxResults = optionalInteger(request.input.maxResults) ?? 100;
2017
2018
  if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
2018
2019
  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 } : {} }));
2020
+ const paths2 = await registry.files(context.workspaceDir);
2021
+ const allowed = await policy.search(policyContext(context, request, options, { paths: paths2, query, ...prefix ? { prefix } : {} }));
2021
2022
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
2022
- const matches = await searchWorkspace(context.workspaceDir, paths, {
2023
+ const matches = await searchWorkspace(context.workspaceDir, paths2, {
2023
2024
  query,
2024
2025
  maxResults,
2025
2026
  caseSensitive,
@@ -2041,8 +2042,8 @@ async function graphQuery(context, request, options, policy, registry) {
2041
2042
  selector: query
2042
2043
  }));
2043
2044
  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);
2045
+ const paths2 = await registry.files(context.workspaceDir);
2046
+ const graphs = await workspaceGraphs(context.workspaceDir, paths2);
2046
2047
  if (request.tool === "sandbox.overview") {
2047
2048
  return response(request, true, renderOverview(graphs, query || void 0));
2048
2049
  }
@@ -2106,16 +2107,16 @@ function toolFailureMessage(reason) {
2106
2107
  async function patch(context, request, options, policy, registry) {
2107
2108
  exactKeys(request.input, ["patch"]);
2108
2109
  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}/`)))) {
2110
+ const paths2 = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
2111
+ if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
2111
2112
  throw new TypeError("patch targets a read-only reference source");
2112
2113
  }
2113
2114
  const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
2114
2115
  if (!allowed) return response(request, false, "tool denied by CaMeL policy");
2115
- await applyCodePatch(context.workspaceDir, value, paths);
2116
+ await applyCodePatch(context.workspaceDir, value, paths2);
2116
2117
  registry.invalidate(context.workspaceDir);
2117
2118
  forgetWorkspaceGraphs(context.workspaceDir);
2118
- return response(request, true, `Applied patch to ${paths.length} file(s).`, { paths });
2119
+ return response(request, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
2119
2120
  }
2120
2121
  async function recipe(context, request, options, recipes, policy) {
2121
2122
  exactKeys(request.input, ["recipeId"]);
@@ -2323,6 +2324,18 @@ function assertBudget(budget) {
2323
2324
  }
2324
2325
  }
2325
2326
 
2327
+ // src/code-runtime-session-skills.ts
2328
+ async function sessionSkillsFor(options, command) {
2329
+ try {
2330
+ return await options.sessionSkills?.(command) ?? [];
2331
+ } catch (cause) {
2332
+ options.onDiagnostic?.(
2333
+ `session skills unavailable, continuing with code tools only: ${cause instanceof Error ? cause.message : String(cause)}`
2334
+ );
2335
+ return [];
2336
+ }
2337
+ }
2338
+
2326
2339
  // src/code-runtime-broker.ts
2327
2340
  function createCodeRuntimeToolBroker(input, lease, role) {
2328
2341
  const broker = createCodeToolBroker({
@@ -2499,12 +2512,129 @@ import { createHash as createHash3 } from "crypto";
2499
2512
  async function appendCodeRuntimeEvent(control, command, event, refs) {
2500
2513
  const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
2501
2514
  refs.push(eventId);
2502
- const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
2515
+ const attributed = { ...event, interactionId: command.commandId };
2516
+ const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
2503
2517
  await control.appendSessionEvent(command.sessionId, eventId, bounded);
2504
2518
  }
2505
2519
  var digestRuntimeValue = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
2506
2520
  var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
2507
2521
 
2522
+ // src/code-tool-presentation.ts
2523
+ var text = (value, maximum) => {
2524
+ if (typeof value !== "string") return void 0;
2525
+ const bounded = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
2526
+ return bounded ? bounded.slice(0, maximum) : void 0;
2527
+ };
2528
+ var integer2 = (value) => Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : void 0;
2529
+ var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2530
+ var excerpt = (value, tail = false) => {
2531
+ if (typeof value !== "string") return void 0;
2532
+ const safe = value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
2533
+ const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
2534
+ if (!source) return void 0;
2535
+ const lines = source.split("\n").filter((line) => line.trim()).map((line) => line.slice(0, 240));
2536
+ const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
2537
+ return text(selected.join("\n"), 2400);
2538
+ };
2539
+ var paths = (value) => {
2540
+ if (!Array.isArray(value)) return void 0;
2541
+ const items = value.flatMap((item) => {
2542
+ const path = text(item, 1024);
2543
+ return path ? [path] : [];
2544
+ }).slice(0, 12);
2545
+ return items.length ? items : void 0;
2546
+ };
2547
+ function patchStats(value) {
2548
+ if (typeof value !== "string") return {};
2549
+ let additions = 0;
2550
+ let deletions = 0;
2551
+ for (const line of value.slice(0, 262144).split("\n")) {
2552
+ if (line.startsWith("+++") || line.startsWith("---")) continue;
2553
+ if (line.startsWith("+")) additions += 1;
2554
+ else if (line.startsWith("-")) deletions += 1;
2555
+ }
2556
+ return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
2557
+ }
2558
+ function searchResults(value) {
2559
+ if (typeof value !== "string") return void 0;
2560
+ const results = value.split("\n").flatMap((line) => {
2561
+ const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line);
2562
+ if (!match) return [];
2563
+ const lineNumber = Number(match[2]);
2564
+ const itemText = text(match[3], 240);
2565
+ if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
2566
+ return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
2567
+ }).slice(0, 5);
2568
+ return results.length ? results : void 0;
2569
+ }
2570
+ function codeToolRequestPresentation(request) {
2571
+ const input = request.input;
2572
+ if (request.tool === "sandbox.read") {
2573
+ const path = text(input.path, 1024);
2574
+ if (!path) return void 0;
2575
+ const startLine = integer2(input.startLine);
2576
+ const endLine = integer2(input.endLine);
2577
+ return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
2578
+ }
2579
+ if (request.tool === "sandbox.list") {
2580
+ const scope = text(input.prefix, 1024);
2581
+ return { kind: "list", ...scope ? { scope } : {} };
2582
+ }
2583
+ if (request.tool === "sandbox.search" || request.tool === "sandbox.overview" || request.tool === "sandbox.where_is" || request.tool === "sandbox.who_imports" || request.tool === "sandbox.who_touches") {
2584
+ const query = text(input.query, 512);
2585
+ const scope = request.tool === "sandbox.search" ? text(input.prefix, 1024) : void 0;
2586
+ if (request.tool === "sandbox.search" && !query) return void 0;
2587
+ return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
2588
+ }
2589
+ if (request.tool === "sandbox.apply_patch") {
2590
+ return { kind: "patch", ...patchStats(input.patch) };
2591
+ }
2592
+ const recipeId = text(input.recipeId, 120);
2593
+ return recipeId ? { kind: "recipe", recipeId } : void 0;
2594
+ }
2595
+ function codeToolResultPresentation(request, response2) {
2596
+ const started = codeToolRequestPresentation(request);
2597
+ if (!started || !response2.ok) return started;
2598
+ const details = record3(response2.details);
2599
+ if (started.kind === "read") {
2600
+ return {
2601
+ ...started,
2602
+ ...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
2603
+ ...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
2604
+ ...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
2605
+ };
2606
+ }
2607
+ if (started.kind === "list") {
2608
+ 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);
2609
+ return {
2610
+ ...started,
2611
+ ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
2612
+ ...listed.length ? { paths: listed } : {}
2613
+ };
2614
+ }
2615
+ if (started.kind === "query") {
2616
+ const results = request.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
2617
+ const resultExcerpt = request.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
2618
+ return {
2619
+ ...started,
2620
+ ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
2621
+ ...results ? { results } : {},
2622
+ ...resultExcerpt ? { excerpt: resultExcerpt } : {}
2623
+ };
2624
+ }
2625
+ if (started.kind === "patch") {
2626
+ return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
2627
+ }
2628
+ const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
2629
+ return {
2630
+ ...started,
2631
+ ...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
2632
+ ...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
2633
+ ...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
2634
+ ...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
2635
+ };
2636
+ }
2637
+
2508
2638
  // src/code-runtime-engine.ts
2509
2639
  var TheseusRuntimeEngine = class {
2510
2640
  constructor(options) {
@@ -2699,6 +2829,7 @@ var TheseusRuntimeEngine = class {
2699
2829
  event: (event) => this.#event(command, event, active.conversationRefs)
2700
2830
  });
2701
2831
  await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
2832
+ const extraSkills = await sessionSkillsFor(this.options, command);
2702
2833
  const result = await this.#attempt({
2703
2834
  inference,
2704
2835
  broker,
@@ -2706,7 +2837,8 @@ var TheseusRuntimeEngine = class {
2706
2837
  workspaceDir: active.workspace.workspaceDir,
2707
2838
  prompt: metadata.prompt,
2708
2839
  signal: active.abort.signal,
2709
- recipeIds: this.options.recipes.map((recipe2) => recipe2.id)
2840
+ recipeIds: this.options.recipes.map((recipe2) => recipe2.id),
2841
+ ...extraSkills.length ? { extraSkills } : {}
2710
2842
  });
2711
2843
  const closing = result.finalText.trim();
2712
2844
  const completed = result.status === "completed" && Boolean(closing);
@@ -2739,18 +2871,29 @@ var TheseusRuntimeEngine = class {
2739
2871
  return {
2740
2872
  execute: async (context, request) => {
2741
2873
  const startedAt = Date.now();
2874
+ const operationId = digestRuntimeValue(`${command.commandId}:${request.requestId}`);
2875
+ const startedPresentation = codeToolRequestPresentation(request);
2742
2876
  await this.#event(
2743
2877
  command,
2744
- { type: "tool", phase: "started", tool: request.tool },
2878
+ {
2879
+ type: "tool",
2880
+ phase: "started",
2881
+ tool: request.tool,
2882
+ operationId,
2883
+ ...startedPresentation ? { presentation: startedPresentation } : {}
2884
+ },
2745
2885
  active.conversationRefs
2746
2886
  ).catch(() => void 0);
2747
2887
  const response2 = await broker.execute(context, request);
2888
+ const completedPresentation = codeToolResultPresentation(request, response2);
2748
2889
  await this.#event(command, {
2749
2890
  type: "tool",
2750
2891
  phase: "completed",
2751
2892
  tool: request.tool,
2752
2893
  ok: response2.ok,
2753
- durationMs: Date.now() - startedAt
2894
+ durationMs: Date.now() - startedAt,
2895
+ operationId,
2896
+ ...completedPresentation ? { presentation: completedPresentation } : {}
2754
2897
  }, active.conversationRefs).catch(() => void 0);
2755
2898
  return response2;
2756
2899
  }
@@ -2826,4 +2969,4 @@ export {
2826
2969
  runGoal,
2827
2970
  TheseusRuntimeEngine
2828
2971
  };
2829
- //# sourceMappingURL=chunk-GYWQM76X.js.map
2972
+ //# sourceMappingURL=chunk-IWVGSWY6.js.map