zidane 3.2.0 → 3.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,9 +3,9 @@ import {
3
3
  createSkillActivationState,
4
4
  installAllowedToolsGate,
5
5
  interpolateShellCommands,
6
- resolveSkillsWithCleanup,
6
+ resolveSkills,
7
7
  validateResourcePath
8
- } from "./chunk-J4ZOSNSH.js";
8
+ } from "./chunk-X3VOTPVM.js";
9
9
  import {
10
10
  createProcessContext
11
11
  } from "./chunk-UD25QF3H.js";
@@ -69,6 +69,26 @@ function desanitize(s) {
69
69
  out = out.replaceAll(from, to);
70
70
  return out;
71
71
  }
72
+ var LINE_NUMBER_PREFIX_RE = /^[ \t]*\d{1,9}[\t|\u2192]/gm;
73
+ function stripLineNumberPrefixes(s) {
74
+ return s.replace(LINE_NUMBER_PREFIX_RE, "");
75
+ }
76
+ function locateAndCount(haystack, normFile, target, via) {
77
+ const idx = normFile.indexOf(target);
78
+ if (idx === -1)
79
+ return null;
80
+ const actual = haystack.slice(idx, idx + target.length);
81
+ let occ = 0;
82
+ let cursor = 0;
83
+ while (true) {
84
+ const next = normFile.indexOf(target, cursor);
85
+ if (next === -1)
86
+ break;
87
+ occ++;
88
+ cursor = next + target.length;
89
+ }
90
+ return { actual, occurrences: occ, via };
91
+ }
72
92
  function resolveOldString(haystack, needle) {
73
93
  const exact = countExactMatches(haystack, needle);
74
94
  if (exact > 0)
@@ -76,20 +96,9 @@ function resolveOldString(haystack, needle) {
76
96
  const normNeedle = normalizeQuotes(needle);
77
97
  const normFile = normalizeQuotes(haystack);
78
98
  if (normNeedle !== needle || normFile !== haystack) {
79
- const idx = normFile.indexOf(normNeedle);
80
- if (idx !== -1) {
81
- const actual = haystack.slice(idx, idx + normNeedle.length);
82
- let occ = 0;
83
- let cursor = 0;
84
- while (true) {
85
- const next = normFile.indexOf(normNeedle, cursor);
86
- if (next === -1)
87
- break;
88
- occ++;
89
- cursor = next + normNeedle.length;
90
- }
91
- return { actual, occurrences: occ, via: "quotes" };
92
- }
99
+ const m = locateAndCount(haystack, normFile, normNeedle, "quotes");
100
+ if (m)
101
+ return m;
93
102
  }
94
103
  const desan = desanitize(needle);
95
104
  if (desan !== needle) {
@@ -99,23 +108,34 @@ function resolveOldString(haystack, needle) {
99
108
  }
100
109
  const combo = desanitize(normNeedle);
101
110
  if (combo !== needle) {
102
- const idx = normFile.indexOf(combo);
103
- if (idx !== -1) {
104
- const actual = haystack.slice(idx, idx + combo.length);
105
- let occ = 0;
106
- let cursor = 0;
107
- while (true) {
108
- const next = normFile.indexOf(combo, cursor);
109
- if (next === -1)
110
- break;
111
- occ++;
112
- cursor = next + combo.length;
113
- }
114
- return { actual, occurrences: occ, via: "quotes+desanitize" };
111
+ const m = locateAndCount(haystack, normFile, combo, "quotes+desanitize");
112
+ if (m)
113
+ return m;
114
+ }
115
+ const stripped = stripLineNumberPrefixes(needle);
116
+ if (stripped !== needle && stripped.trim().length > 0) {
117
+ const count = countExactMatches(haystack, stripped);
118
+ if (count > 0)
119
+ return { actual: stripped, occurrences: count, via: "line-numbers" };
120
+ const strippedNorm = normalizeQuotes(stripped);
121
+ if (strippedNorm !== stripped || normFile !== haystack) {
122
+ const m = locateAndCount(haystack, normFile, strippedNorm, "quotes+line-numbers");
123
+ if (m)
124
+ return m;
115
125
  }
116
126
  }
117
127
  return null;
118
128
  }
129
+ function styleReplacementForVia(replacement, via, actual) {
130
+ let out = replacement;
131
+ if (via === "desanitize" || via === "quotes+desanitize")
132
+ out = desanitize(out);
133
+ if (via === "line-numbers" || via === "quotes+line-numbers")
134
+ out = stripLineNumberPrefixes(out);
135
+ if (via === "quotes" || via === "quotes+desanitize" || via === "quotes+line-numbers")
136
+ out = preserveQuoteStyle(actual, out);
137
+ return out;
138
+ }
119
139
  function preserveQuoteStyle(actual, replacement) {
120
140
  const hasDouble = actual.includes(LEFT_DOUBLE_CURLY_QUOTE) || actual.includes(RIGHT_DOUBLE_CURLY_QUOTE);
121
141
  const hasSingle = actual.includes(LEFT_SINGLE_CURLY_QUOTE) || actual.includes(RIGHT_SINGLE_CURLY_QUOTE);
@@ -221,7 +241,7 @@ function hashContent(text) {
221
241
  var edit = {
222
242
  spec: {
223
243
  name: "edit",
224
- description: "Replace exact `old_string` with `new_string` in a file. Fails if `old_string` is not unique unless `replace_all: true`. Prefer over `write_file` for surgical changes \u2014 preserves the rest of the file.",
244
+ description: "Replace exact `old_string` with `new_string` in a file. Fails if `old_string` is not unique unless `replace_all: true`. Prefer over `write_file` for surgical changes \u2014 preserves the rest of the file. Tolerates `read_file` line-number prefixes (`<N>\\t\u2026`, `<N>|\u2026`, or `<N>\u2192\u2026`) in `old_string` / `new_string` \u2014 they are stripped before matching/writing, so you can paste a numbered chunk verbatim.",
225
245
  inputSchema: {
226
246
  type: "object",
227
247
  properties: {
@@ -266,9 +286,7 @@ var edit = {
266
286
  const { actual, occurrences, via } = match;
267
287
  if (occurrences > 1 && !replaceAll)
268
288
  return `Edit error: old_string appears ${occurrences} times in ${target}. Pass replace_all=true or expand old_string for uniqueness.`;
269
- let styledReplacement = via === "desanitize" || via === "quotes+desanitize" ? desanitize(replacement) : replacement;
270
- if (via === "quotes" || via === "quotes+desanitize")
271
- styledReplacement = preserveQuoteStyle(actual, styledReplacement);
289
+ const styledReplacement = styleReplacementForVia(replacement, via, actual);
272
290
  const updated = replaceAll ? original.split(actual).join(styledReplacement) : original.replace(actual, styledReplacement);
273
291
  if (updated === original)
274
292
  return `Edit error: replacement produced no change in ${target}.`;
@@ -285,7 +303,8 @@ var edit = {
285
303
  }
286
304
  };
287
305
  function nearestMatchPreview(haystack, needle) {
288
- const needleFirstLine = needle.split("\n")[0];
306
+ const normalizedNeedle = stripLineNumberPrefixes(needle);
307
+ const needleFirstLine = normalizedNeedle.split("\n")[0];
289
308
  if (needleFirstLine.length < 3)
290
309
  return null;
291
310
  const lines = haystack.split("\n");
@@ -312,6 +331,8 @@ function sharedPrefixLength(a, b) {
312
331
  }
313
332
 
314
333
  // src/tools/glob.ts
334
+ import { stat } from "fs/promises";
335
+ import { resolve } from "path";
315
336
  var DEFAULT_LIMIT = 1e3;
316
337
  var SAFE_GLOB_PATTERN_RE = /^[\w./*?[\]{}!,^@+-]+$/;
317
338
  async function globInProcess(pattern, cwd, limit) {
@@ -338,7 +359,7 @@ async function globViaShell(pattern, ctx, limit) {
338
359
  var glob = {
339
360
  spec: {
340
361
  name: "glob",
341
- description: "Match files by glob pattern (supports **, *, ?). Relative to the execution context cwd. Returns a newline-separated list of matching paths, sorted.",
362
+ description: "Match files by glob pattern (supports **, *, ?). Relative to the execution context cwd. By default each row is `<path>\\t<size-bytes>\\t<mtime-iso>`; set `metadata: false` for a plain newline-separated list of paths. Always sorted.",
342
363
  inputSchema: {
343
364
  type: "object",
344
365
  properties: {
@@ -349,17 +370,34 @@ var glob = {
349
370
  limit: {
350
371
  type: "number",
351
372
  description: `Maximum number of matches to return. Default: ${DEFAULT_LIMIT}.`
373
+ },
374
+ metadata: {
375
+ type: "boolean",
376
+ description: "Append size (bytes) and mtime (ISO) per row, tab-separated. Default: true. In-process only \u2014 non-process execution contexts always return paths."
352
377
  }
353
378
  },
354
379
  required: ["pattern"]
355
380
  }
356
381
  },
357
- async execute({ pattern, limit }, ctx) {
382
+ async execute({ pattern, limit, metadata }, ctx) {
358
383
  const pat = pattern;
359
384
  const max = typeof limit === "number" && limit > 0 ? limit : DEFAULT_LIMIT;
385
+ const wantMetadata = metadata !== false;
360
386
  try {
361
387
  const entries = ctx.execution.type === "process" ? await globInProcess(pat, ctx.handle.cwd, max) : await globViaShell(pat, ctx, max);
362
- return entries.length > 0 ? entries.join("\n") : "(no matches)";
388
+ if (entries.length === 0)
389
+ return "(no matches)";
390
+ if (!wantMetadata || ctx.execution.type !== "process")
391
+ return entries.join("\n");
392
+ const rows = await Promise.all(entries.map(async (rel) => {
393
+ try {
394
+ const s = await stat(resolve(ctx.handle.cwd, rel));
395
+ return `${rel} ${s.size} ${new Date(s.mtimeMs).toISOString()}`;
396
+ } catch {
397
+ return `${rel} `;
398
+ }
399
+ }));
400
+ return rows.join("\n");
363
401
  } catch (err) {
364
402
  const message = err instanceof Error ? err.message : String(err);
365
403
  return `Glob error: ${message}`;
@@ -538,8 +576,8 @@ async function enumerateFiles(input, ctx) {
538
576
  const root = input.path ?? ".";
539
577
  if (input.path && !input.path.includes("*") && !input.path.includes("?")) {
540
578
  try {
541
- const stat = await ctx.execution.exec(ctx.handle, `test -f ${shellQuote(input.path)} && echo file || echo dir`);
542
- if (stat.stdout.trim() === "file")
579
+ const stat2 = await ctx.execution.exec(ctx.handle, `test -f ${shellQuote(input.path)} && echo file || echo dir`);
580
+ if (stat2.stdout.trim() === "file")
543
581
  return [input.path];
544
582
  } catch {
545
583
  }
@@ -619,7 +657,7 @@ var listFiles = {
619
657
  var multiEdit = {
620
658
  spec: {
621
659
  name: "multi_edit",
622
- description: "Apply a sequential list of edits to a file atomically. Each edit operates on the result of the previous edit. All edits must succeed for any to be written. Prefer this over multiple `edit` calls when several non-overlapping changes are needed in the same file.",
660
+ description: "Apply a sequential list of edits to a file atomically. Each edit operates on the result of the previous edit. All edits must succeed for any to be written. Prefer this over multiple `edit` calls when several non-overlapping changes are needed in the same file. Each step tolerates `read_file` line-number prefixes (`<N>\\t\u2026`, `<N>|\u2026`, or `<N>\u2192\u2026`) in `old_string` / `new_string`.",
623
661
  inputSchema: {
624
662
  type: "object",
625
663
  properties: {
@@ -680,9 +718,7 @@ var multiEdit = {
680
718
  const { actual, occurrences, via } = match;
681
719
  if (occurrences > 1 && !replaceAll)
682
720
  return `multi_edit error: edit #${i + 1} old_string appears ${occurrences} times. Pass replace_all=true on this edit or expand old_string for uniqueness.`;
683
- let styledReplacement = via === "desanitize" || via === "quotes+desanitize" ? desanitize(replacement) : replacement;
684
- if (via === "quotes" || via === "quotes+desanitize")
685
- styledReplacement = preserveQuoteStyle(actual, styledReplacement);
721
+ const styledReplacement = styleReplacementForVia(replacement, via, actual);
686
722
  current = replaceAll ? current.split(actual).join(styledReplacement) : current.replace(actual, styledReplacement);
687
723
  applied += occurrences;
688
724
  }
@@ -781,19 +817,20 @@ var DEFAULT_IMAGE_BYTE_CAP = 5 * 1024 * 1024;
781
817
  var readFile = {
782
818
  spec: {
783
819
  name: "read_file",
784
- description: "Read a file by path. Returns lines [offset..offset+limit). Default offset=1, limit=2000. A trailing footer explains how to read the rest when truncated. Binary files return a short marker rather than mojibake.",
820
+ description: "Read a file by path. Returns lines [offset..offset+limit). Default offset=1, limit=2000. Each line is prefixed with its 1-indexed line number followed by a tab (e.g. `42\\tconst foo = bar`); the prefix is metadata, not part of the file. Mirrors Claude Code's `cat -n`-style compact output for token efficiency. A trailing footer explains how to read the rest when truncated. Binary files return a short marker rather than mojibake.",
785
821
  inputSchema: {
786
822
  type: "object",
787
823
  properties: {
788
824
  path: { type: "string", description: "Relative file path." },
789
825
  offset: { type: "integer", description: "1-indexed line number to start from. Default: 1." },
790
826
  limit: { type: "integer", description: "Max lines to return. Default: 2000. Set 0 for unlimited." },
791
- maxBytes: { type: "integer", description: "Hard byte cap regardless of line count. Default: 65536. Set 0 for unlimited." }
827
+ maxBytes: { type: "integer", description: "Hard byte cap on file content read, regardless of line count. Default: 65536. Set 0 for unlimited. The rendered output may be slightly larger than this cap when `lineNumbers` is on (each line carries a `<N>\\t` prefix)." },
828
+ lineNumbers: { type: "boolean", description: "Prefix each line with its 1-indexed line number. Default: true. Override the agent-wide `behavior.readLineNumbers` for this call." }
792
829
  },
793
830
  required: ["path"]
794
831
  }
795
832
  },
796
- async execute({ path, offset, limit, maxBytes }, ctx) {
833
+ async execute({ path, offset, limit, maxBytes, lineNumbers }, ctx) {
797
834
  const imgMedia = imageMediaTypeFor(path);
798
835
  if (imgMedia) {
799
836
  const sizeCap = maxBytes !== void 0 ? normalizeInteger(maxBytes, DEFAULT_IMAGE_BYTE_CAP) : DEFAULT_IMAGE_BYTE_CAP;
@@ -827,10 +864,11 @@ var readFile = {
827
864
  const offsetForKey = normalizeInteger(offset, 1);
828
865
  const limitForKey = normalizeInteger(limit, DEFAULT_LINE_LIMIT);
829
866
  const maxBytesForKey = normalizeInteger(maxBytes, DEFAULT_BYTE_CAP);
867
+ const showLineNumbers = typeof lineNumbers === "boolean" ? lineNumbers : ctx.behavior?.readLineNumbers ?? true;
830
868
  const currentHash = readState ? hashContent(raw) : "";
831
869
  if (readState) {
832
870
  const prior = readState.get(absKey);
833
- if (prior && prior.contentHash === currentHash && prior.offset === offsetForKey && prior.limit === limitForKey && prior.maxBytes === maxBytesForKey) {
871
+ if (prior && prior.contentHash === currentHash && prior.offset === offsetForKey && prior.limit === limitForKey && prior.maxBytes === maxBytesForKey && prior.lineNumbers === showLineNumbers) {
834
872
  return `File ${path} unchanged since the previous read in this session \u2014 the prior result is still current.`;
835
873
  }
836
874
  }
@@ -845,10 +883,10 @@ var readFile = {
845
883
  const startIdx = Math.max(0, offsetN - 1);
846
884
  const endIdx = limitN > 0 ? Math.min(totalLines, startIdx + limitN) : totalLines;
847
885
  let slice = lines.slice(startIdx, endIdx);
848
- let bytesUsed = 0;
849
886
  let bytesCut = false;
850
887
  if (maxBytesN > 0) {
851
888
  const truncatedSlice = [];
889
+ let bytesUsed = 0;
852
890
  for (const line of slice) {
853
891
  const lineBytes = Buffer2.byteLength(line) + 1;
854
892
  if (bytesUsed + lineBytes > maxBytesN && truncatedSlice.length > 0) {
@@ -881,15 +919,16 @@ var readFile = {
881
919
  bytesCut = true;
882
920
  }
883
921
  }
884
- const body = slice.join("\n");
885
922
  const linesReturned = slice.length;
886
923
  const lastLineRead = startIdx + linesReturned;
924
+ const body = showLineNumbers ? slice.map((line, i) => `${startIdx + i + 1} ${line}`).join("\n") : slice.join("\n");
887
925
  if (readState) {
888
926
  readState.set(absKey, {
889
927
  contentHash: currentHash,
890
928
  offset: offsetN,
891
929
  limit: limitN,
892
930
  maxBytes: maxBytesN,
931
+ lineNumbers: showLineNumbers,
893
932
  mtimeMs: Date.now()
894
933
  });
895
934
  }
@@ -957,37 +996,54 @@ var DEFAULT_MAX_OUTPUT_BYTES = 8192;
957
996
  var shell = {
958
997
  spec: {
959
998
  name: "shell",
960
- description: "Execute a shell command in the project root and return its combined stdout/stderr. Output is tail-priority truncated at 8 KB by default; errors and exit-code summaries live in the tail. Set maxOutputBytes=0 to disable truncation.",
999
+ description: "Execute a shell command in the project root and return its combined stdout/stderr. Output is tail-priority truncated at 8 KB by default; errors and exit-code summaries live in the tail. By default each call appends a `(exit N, Nms)` footer and surfaces non-empty stderr in a separate section even on success \u2014 set `metadata: false` to return only stdout. Set maxOutputBytes=0 to disable truncation.",
961
1000
  inputSchema: {
962
1001
  type: "object",
963
1002
  properties: {
964
1003
  command: { type: "string", description: "Shell command to run." },
965
1004
  timeout: { type: "integer", description: "Per-call timeout in milliseconds." },
966
- maxOutputBytes: { type: "integer", description: "Truncate combined stdout+stderr beyond this many bytes. Default: 8192. Set 0 for unlimited." }
1005
+ maxOutputBytes: { type: "integer", description: "Truncate combined stdout+stderr beyond this many bytes. Default: 8192. Set 0 for unlimited." },
1006
+ metadata: { type: "boolean", description: "Append `(exit N, Nms)` footer and surface non-empty stderr on success. Default: true." }
967
1007
  },
968
1008
  required: ["command"]
969
1009
  }
970
1010
  },
971
- async execute({ command, timeout, maxOutputBytes }, ctx) {
1011
+ async execute({ command, timeout, maxOutputBytes, metadata }, ctx) {
972
1012
  const execOpts = {};
973
1013
  if (typeof timeout === "number" && Number.isFinite(timeout) && timeout > 0)
974
1014
  execOpts.timeout = Math.max(1, Math.ceil(timeout / 1e3));
975
1015
  const cmd = command;
1016
+ const wantMetadata = metadata !== false;
1017
+ const startedAt = Date.now();
976
1018
  const result = await ctx.execution.exec(ctx.handle, cmd, execOpts);
1019
+ const durationMs = Date.now() - startedAt;
977
1020
  const cap = normalizeCap(maxOutputBytes);
978
1021
  const semantic = interpretShellResult(cmd, result.exitCode);
979
- if (result.exitCode === 0)
980
- return truncateTail(result.stdout || "(no output)", cap);
1022
+ if (result.exitCode === 0) {
1023
+ const stdoutTail = truncateTail(result.stdout || "(no output)", cap);
1024
+ if (!wantMetadata)
1025
+ return stdoutTail;
1026
+ const stderrTrimmed = result.stderr.trim();
1027
+ const stderrSection = stderrTrimmed ? `
1028
+ [stderr]
1029
+ ${truncateTail(stderrTrimmed, Math.min(cap, 2048))}` : "";
1030
+ return `${stdoutTail}${stderrSection}
1031
+ (exit 0, ${durationMs}ms)`;
1032
+ }
981
1033
  if (!semantic.isError) {
982
1034
  const body = (result.stdout || result.stderr || "").trim();
983
1035
  const tail = truncateTail(body, cap);
984
- const footer = semantic.message ? `
1036
+ const semanticFooter = semantic.message ? `
985
1037
  (${semantic.message})` : "";
986
- return tail.length > 0 ? `${tail}${footer}` : semantic.message ?? "(no output)";
1038
+ const timingFooter = wantMetadata ? `
1039
+ (exit ${result.exitCode}, ${durationMs}ms)` : "";
1040
+ const head = tail.length > 0 ? tail : semantic.message ?? "(no output)";
1041
+ return `${head}${semanticFooter}${timingFooter}`;
987
1042
  }
988
1043
  const combined = `${result.stdout}
989
1044
  ${result.stderr}`.trim();
990
- return `Exit code ${result.exitCode}
1045
+ const header = wantMetadata ? `Exit code ${result.exitCode} (${durationMs}ms)` : `Exit code ${result.exitCode}`;
1046
+ return `${header}
991
1047
  ${truncateTail(combined, cap)}`;
992
1048
  }
993
1049
  };
@@ -1568,6 +1624,74 @@ function applyTailCompaction(messages, threshold, keepTurns) {
1568
1624
  }
1569
1625
  return changed ? out : messages;
1570
1626
  }
1627
+ var STALE_READ_STUB = "[\u2026elided: file edited later in this run; re-read if still needed.]";
1628
+ function applyStaleReadElision(messages) {
1629
+ if (messages.length === 0)
1630
+ return messages;
1631
+ const resultByCallId = /* @__PURE__ */ new Map();
1632
+ for (const msg of messages) {
1633
+ for (const block of msg.content) {
1634
+ if (block.type === "tool_result" && typeof block.output === "string")
1635
+ resultByCallId.set(block.callId, block.output);
1636
+ }
1637
+ }
1638
+ const maxMutationIdxByPath = /* @__PURE__ */ new Map();
1639
+ const readCallInfo = /* @__PURE__ */ new Map();
1640
+ for (let i = 0; i < messages.length; i++) {
1641
+ for (const block of messages[i].content) {
1642
+ if (block.type !== "tool_call")
1643
+ continue;
1644
+ const path = block.input.path;
1645
+ if (typeof path !== "string")
1646
+ continue;
1647
+ if (block.name === "read_file") {
1648
+ readCallInfo.set(block.id, { path, msgIdx: i });
1649
+ continue;
1650
+ }
1651
+ const isEdit = block.name === "edit" || block.name === "multi_edit";
1652
+ const isWrite = block.name === "write_file";
1653
+ if (!isEdit && !isWrite)
1654
+ continue;
1655
+ const result = resultByCallId.get(block.id);
1656
+ if (typeof result !== "string")
1657
+ continue;
1658
+ const succeeded = isEdit ? result.startsWith("Edited ") : result.startsWith("Created ") || result.startsWith("Updated ");
1659
+ if (!succeeded)
1660
+ continue;
1661
+ const prior = maxMutationIdxByPath.get(path);
1662
+ if (prior === void 0 || i > prior)
1663
+ maxMutationIdxByPath.set(path, i);
1664
+ }
1665
+ }
1666
+ if (maxMutationIdxByPath.size === 0)
1667
+ return messages;
1668
+ const staleCallIds = /* @__PURE__ */ new Set();
1669
+ for (const [callId, info] of readCallInfo) {
1670
+ const lastMutationIdx = maxMutationIdxByPath.get(info.path);
1671
+ if (typeof lastMutationIdx === "number" && info.msgIdx < lastMutationIdx)
1672
+ staleCallIds.add(callId);
1673
+ }
1674
+ if (staleCallIds.size === 0)
1675
+ return messages;
1676
+ let changed = false;
1677
+ const out = messages.slice();
1678
+ for (let i = 0; i < out.length; i++) {
1679
+ const msg = out[i];
1680
+ let msgChanged = false;
1681
+ const newContent = msg.content.map((block) => {
1682
+ if (block.type !== "tool_result" || !staleCallIds.has(block.callId))
1683
+ return block;
1684
+ if (block.output === STALE_READ_STUB)
1685
+ return block;
1686
+ msgChanged = true;
1687
+ changed = true;
1688
+ return { ...block, output: STALE_READ_STUB };
1689
+ });
1690
+ if (msgChanged)
1691
+ out[i] = { ...msg, content: newContent };
1692
+ }
1693
+ return changed ? out : messages;
1694
+ }
1571
1695
  function sanitizeStoredToolResults(provider, messages) {
1572
1696
  if (provider.meta.capabilities?.vision !== false)
1573
1697
  return messages;
@@ -1675,7 +1799,9 @@ function wrapProviderError(err, ctx) {
1675
1799
  }
1676
1800
  async function executeTurn(ctx, turn) {
1677
1801
  const turnId = await ctx.generateTurnId();
1678
- const canonicalMessages = turnsToMessages(ctx.turns);
1802
+ let canonicalMessages = turnsToMessages(ctx.turns);
1803
+ if (ctx.elideStaleReads === true)
1804
+ canonicalMessages = applyStaleReadElision(canonicalMessages);
1679
1805
  const wireMessages = rewriteMessagesToWire(canonicalMessages, ctx.aliasMaps);
1680
1806
  let sanitizedMessages = sanitizeStoredToolResults(ctx.provider, wireMessages);
1681
1807
  if (ctx.compactStrategy === "tail") {
@@ -2092,26 +2218,13 @@ async function executeToolsParallel(ctx, toolCalls, turnId) {
2092
2218
  }
2093
2219
 
2094
2220
  // src/prompt.ts
2095
- function canonicalizePrompt(prompt, legacyImages) {
2221
+ function canonicalizePrompt(prompt) {
2096
2222
  if (prompt === void 0)
2097
2223
  return void 0;
2098
2224
  if (typeof prompt === "string") {
2099
- const hasImages = legacyImages && legacyImages.length > 0;
2100
- if (prompt.length === 0 && !hasImages)
2225
+ if (prompt.length === 0)
2101
2226
  return void 0;
2102
- const parts = [];
2103
- if (prompt.length > 0)
2104
- parts.push({ type: "text", text: prompt });
2105
- if (hasImages) {
2106
- for (const img of legacyImages) {
2107
- parts.push({
2108
- type: "image",
2109
- mediaType: img.source.media_type,
2110
- data: img.source.data
2111
- });
2112
- }
2113
- }
2114
- return parts;
2227
+ return [{ type: "text", text: prompt }];
2115
2228
  }
2116
2229
  if (prompt.length === 0)
2117
2230
  return void 0;
@@ -2311,7 +2424,9 @@ function resolveBehavior(agentBehavior, runBehavior) {
2311
2424
  dedupReads: runBehavior?.dedupReads ?? agentBehavior?.dedupReads,
2312
2425
  dedupTools: runBehavior?.dedupTools ?? agentBehavior?.dedupTools,
2313
2426
  requireReadBeforeEdit: runBehavior?.requireReadBeforeEdit ?? agentBehavior?.requireReadBeforeEdit,
2314
- toolBudgets: runBehavior?.toolBudgets ?? agentBehavior?.toolBudgets
2427
+ toolBudgets: runBehavior?.toolBudgets ?? agentBehavior?.toolBudgets,
2428
+ readLineNumbers: runBehavior?.readLineNumbers ?? agentBehavior?.readLineNumbers,
2429
+ elideStaleReads: runBehavior?.elideStaleReads ?? agentBehavior?.elideStaleReads
2315
2430
  };
2316
2431
  }
2317
2432
  function createAgent({ provider, name: agentName, system: agentSystem, tools: agentTools, toolAliases, behavior: agentBehavior, execution, mcpServers, session, skills: agentSkills, mcpConnector, eager }) {
@@ -2374,8 +2489,8 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2374
2489
  options.signal.addEventListener("abort", onExternalAbort, { once: true });
2375
2490
  }
2376
2491
  }
2377
- idlePromise = new Promise((resolve) => {
2378
- idleResolve = resolve;
2492
+ idlePromise = new Promise((resolve2) => {
2493
+ idleResolve = resolve2;
2379
2494
  });
2380
2495
  const childrenStats = [];
2381
2496
  const unregisterSpawnHook = hooks.hook("spawn:complete", (ctx) => {
@@ -2404,7 +2519,7 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2404
2519
  await warmup();
2405
2520
  }
2406
2521
  if (!skillsDisabled && skillsConfig && !resolvedSkills) {
2407
- const bundle = await resolveSkillsWithCleanup(skillsConfig);
2522
+ const bundle = await resolveSkills(skillsConfig);
2408
2523
  resolvedSkills = bundle.skills;
2409
2524
  skillsCleanup = bundle.cleanup;
2410
2525
  await hooks.callHook("skills:resolve", { skills: resolvedSkills });
@@ -2439,7 +2554,7 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2439
2554
  const thinking = options.thinking ?? "off";
2440
2555
  const model = options.model ?? provider.meta.defaultModel;
2441
2556
  const resolvedBehavior = resolveBehavior(agentBehavior, options.behavior);
2442
- const { toolExecution, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, compactStrategy, compactThreshold, compactKeepTurns, thinkingDecay, dedupTools, toolBudgets } = resolvedBehavior;
2557
+ const { toolExecution, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, compactStrategy, compactThreshold, compactKeepTurns, thinkingDecay, dedupTools, toolBudgets, elideStaleReads } = resolvedBehavior;
2443
2558
  let system = options.system || agentSystem || "You are a helpful assistant.";
2444
2559
  if (skillsCatalog) {
2445
2560
  system = `${system}
@@ -2488,7 +2603,7 @@ ${skillsCatalog}`;
2488
2603
  if (options.system) {
2489
2604
  await hooks.callHook("system:before", { system: options.system });
2490
2605
  }
2491
- const promptParts = canonicalizePrompt(options.prompt, options.images);
2606
+ const promptParts = canonicalizePrompt(options.prompt);
2492
2607
  if (promptParts) {
2493
2608
  const promptMsg = buildPromptMessage(provider, promptParts);
2494
2609
  turns.push({
@@ -2591,6 +2706,7 @@ ${skillsCatalog}`;
2591
2706
  compactStrategy,
2592
2707
  compactThreshold,
2593
2708
  compactKeepTurns,
2709
+ ...elideStaleReads !== void 0 ? { elideStaleReads } : {},
2594
2710
  ...thinkingDecay !== void 0 ? { thinkingDecay } : {},
2595
2711
  runStartMs,
2596
2712
  runToolCounts: {}
@@ -2838,9 +2954,9 @@ async function raceWithTimeout(task, timeoutMs) {
2838
2954
  return task;
2839
2955
  let timer;
2840
2956
  try {
2841
- return await new Promise((resolve, reject) => {
2957
+ return await new Promise((resolve2, reject) => {
2842
2958
  timer = setTimeout(() => reject(new SpawnTimeoutError(timeoutMs)), timeoutMs);
2843
- task.then(resolve, reject);
2959
+ task.then(resolve2, reject);
2844
2960
  });
2845
2961
  } finally {
2846
2962
  if (timer)
@@ -3044,7 +3160,6 @@ function createSpawnTool(options = {}) {
3044
3160
  }
3045
3161
  };
3046
3162
  }
3047
- var spawn = createSpawnTool();
3048
3163
 
3049
3164
  // src/tools/write-file.ts
3050
3165
  import { Buffer as Buffer4 } from "buffer";
@@ -3092,6 +3207,5 @@ export {
3092
3207
  readFile,
3093
3208
  shell,
3094
3209
  createSpawnTool,
3095
- spawn,
3096
3210
  writeFile
3097
3211
  };
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { d as AgentHooks } from './agent-CE2jhpNE.js';
2
- export { ac as ActivationVia, ad as ActiveSkill, A as Agent, a as AgentAbortedError, b as AgentBehavior, c as AgentContextExceededError, e as AgentOptions, f as AgentProviderError, g as AgentRunOptions, h as AgentStats, i as AgentToolNotAllowedError, j as AnthropicParams, C as CONTEXT_EXCEEDED_MESSAGE_PATTERNS, k as CerebrasParams, m as ClassifiedError, n as ClassifiedErrorKind, o as CreateSessionOptions, ae as DeactivationReason, af as FileMapAdapter, ag as FileMapStoreOptions, I as ImageContent, M as McpConnection, p as McpServerConfig, q as McpToolHookContext, O as OAuthRefreshHookContext, ah as OpenAICompatAuthHeader, ai as OpenAICompatHttpError, aj as OpenAICompatParams, r as OpenAIParams, s as OpenRouterParams, P as PromptDocumentPart, t as PromptImagePart, u as PromptPart, v as PromptTextPart, w as Provider, x as ProviderCapabilities, R as RemoteStoreOptions, y as RunHookMap, S as Session, z as SessionContentBlock, B as SessionData, D as SessionEndStatus, E as SessionHookContext, F as SessionMessage, G as SessionRun, H as SessionStore, J as SessionTurn, ak as SkillActivationState, al as SkillActivationStateOptions, K as SkillConfig, am as SkillDiagnostic, L as SkillResource, an as SkillSource, N as SkillsConfig, Q as SpawnHookContext, T as StreamCallbacks, U as StreamHookContext, V as StreamOptions, W as ThinkingLevel, X as ToolCall, Y as ToolContext, Z as ToolDef, _ as ToolExecutionMode, $ as ToolHookContext, a0 as ToolMap, a1 as ToolResult, a2 as ToolResultContent, a3 as ToolResultImageContent, a4 as ToolResultTextContent, a5 as ToolSpec, a6 as TurnFinishReason, a7 as TurnResult, a8 as TurnUsage, ao as anthropic, ap as autoDetectAndConvert, aq as cerebras, ar as classifyOpenAICompatError, as as connectMcpServers, at as createAgent, au as createFileMapStore, av as createMemoryStore, aw as createRemoteStore, ax as createSession, ay as createSkillActivationState, az as fromAnthropic, aA as fromOpenAI, aB as loadSession, aC as mapOAIFinishReason, a9 as matchesContextExceeded, aD as normalizeMcpBlocks, aE as normalizeMcpServers, aF as openai, aG as openaiCompat, aH as openrouter, aI as resultToString, aJ as toAnthropic, aK as toOpenAI, aL as toTypedError, aa as toolOutputByteLength, ab as toolResultToText } from './agent-CE2jhpNE.js';
1
+ import { d as AgentHooks } from './agent-DqEkutk4.js';
2
+ export { ab as ActivationVia, ac as ActiveSkill, A as Agent, a as AgentAbortedError, b as AgentBehavior, c as AgentContextExceededError, e as AgentOptions, f as AgentProviderError, g as AgentRunOptions, h as AgentStats, i as AgentToolNotAllowedError, j as AnthropicParams, C as CONTEXT_EXCEEDED_MESSAGE_PATTERNS, k as CerebrasParams, m as ClassifiedError, n as ClassifiedErrorKind, o as CreateSessionOptions, ad as DeactivationReason, ae as FileMapAdapter, af as FileMapStoreOptions, M as McpConnection, p as McpServerConfig, q as McpToolHookContext, O as OAuthRefreshHookContext, ag as OpenAICompatAuthHeader, ah as OpenAICompatHttpError, ai as OpenAICompatParams, r as OpenAIParams, s as OpenRouterParams, P as PromptDocumentPart, t as PromptImagePart, u as PromptPart, v as PromptTextPart, w as Provider, x as ProviderCapabilities, R as RemoteStoreOptions, y as RunHookMap, S as Session, z as SessionContentBlock, B as SessionData, D as SessionEndStatus, E as SessionHookContext, F as SessionMessage, G as SessionRun, H as SessionStore, I as SessionTurn, aj as SkillActivationState, ak as SkillActivationStateOptions, J as SkillConfig, al as SkillDiagnostic, K as SkillResource, am as SkillSource, L as SkillsConfig, N as SpawnHookContext, Q as StreamCallbacks, T as StreamHookContext, U as StreamOptions, V as ThinkingLevel, W as ToolCall, X as ToolContext, Y as ToolDef, Z as ToolExecutionMode, _ as ToolHookContext, $ as ToolMap, a0 as ToolResult, a1 as ToolResultContent, a2 as ToolResultImageContent, a3 as ToolResultTextContent, a4 as ToolSpec, a5 as TurnFinishReason, a6 as TurnResult, a7 as TurnUsage, an as anthropic, ao as autoDetectAndConvert, ap as cerebras, aq as classifyOpenAICompatError, ar as connectMcpServers, as as createAgent, at as createFileMapStore, au as createMemoryStore, av as createRemoteStore, aw as createSession, ax as createSkillActivationState, ay as fromAnthropic, az as fromOpenAI, aA as loadSession, aB as mapOAIFinishReason, a8 as matchesContextExceeded, aC as normalizeMcpBlocks, aD as normalizeMcpServers, aE as openai, aF as openaiCompat, aG as openrouter, aH as resultToString, aI as toAnthropic, aJ as toOpenAI, aK as toTypedError, a9 as toolOutputByteLength, aa as toolResultToText } from './agent-DqEkutk4.js';
3
3
  export { createDockerContext, createProcessContext } from './contexts.js';
4
4
  export { S as SandboxProvider, c as createSandboxContext } from './sandbox-CLghrTLi.js';
5
5
  export { C as ContextCapabilities, a as ContextType, E as ExecResult, b as ExecutionContext, c as ExecutionHandle, S as SpawnConfig } from './types-vA1a_ZX7.js';
6
6
  export { Preset, basic, basicTools, definePreset } from './presets.js';
7
7
  export { IMPLICITLY_ALLOWED_SKILL_TOOLS, SkillValidationIssue, SkillValidationResult, SourcedScanPath, buildCatalog, defineSkill, discoverSkills, installAllowedToolsGate, interpolateShellCommands, isToolAllowedByUnion, matchesAllowedTool, parseAllowedToolPattern, parseSkillFile, resolveSkills, validateResourcePath, validateSkillForWrite, validateSkillName, writeSkillToDisk, writeSkillsToDisk } from './skills.js';
8
- export { S as SkillsReadToolOptions, a as SkillsRunScriptToolOptions, b as SkillsUseToolOptions, c as createSkillsReadTool, d as createSkillsRunScriptTool, e as createSkillsUseTool, f as edit, g as glob, h as grep, m as multiEdit } from './skills-use-CqOKEN56.js';
9
- export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, V as ValidationResult, c as createInteractionTool, b as createSpawnTool, s as spawn, v as validateToolArgs } from './validation-DlIURVGV.js';
8
+ export { S as SkillsReadToolOptions, a as SkillsRunScriptToolOptions, b as SkillsUseToolOptions, c as createSkillsReadTool, d as createSkillsRunScriptTool, e as createSkillsUseTool, f as edit, g as glob, h as grep, m as multiEdit } from './skills-use-WbOh6TuS.js';
9
+ export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, V as ValidationResult, c as createInteractionTool, b as createSpawnTool, v as validateToolArgs } from './validation-DTbkLXbd.js';
10
10
  import { Hookable } from 'hookable';
11
11
  import '@modelcontextprotocol/sdk/client/index.js';
12
12
 
package/dist/index.js CHANGED
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  defineSkill
3
- } from "./chunk-YPU6KVL6.js";
3
+ } from "./chunk-LIVB5W4B.js";
4
4
  import {
5
5
  anthropic,
6
6
  cerebras,
7
7
  openai,
8
8
  openrouter
9
- } from "./chunk-AUBXCLUC.js";
9
+ } from "./chunk-W57VY6DJ.js";
10
10
  import {
11
11
  basicTools,
12
12
  basic_default,
13
13
  definePreset
14
- } from "./chunk-HPTCF3EX.js";
14
+ } from "./chunk-3D5Q527Y.js";
15
15
  import {
16
16
  createAgent,
17
17
  createInteractionTool,
@@ -23,9 +23,8 @@ import {
23
23
  glob,
24
24
  grep,
25
25
  multiEdit,
26
- spawn,
27
26
  validateToolArgs
28
- } from "./chunk-6JIVVEQQ.js";
27
+ } from "./chunk-Z2E5QN5X.js";
29
28
  import {
30
29
  IMPLICITLY_ALLOWED_SKILL_TOOLS,
31
30
  buildCatalog,
@@ -43,7 +42,7 @@ import {
43
42
  validateSkillName,
44
43
  writeSkillToDisk,
45
44
  writeSkillsToDisk
46
- } from "./chunk-J4ZOSNSH.js";
45
+ } from "./chunk-X3VOTPVM.js";
47
46
  import {
48
47
  createDockerContext,
49
48
  createProcessContext,
@@ -76,7 +75,7 @@ import {
76
75
  openaiCompat,
77
76
  toAnthropic,
78
77
  toOpenAI
79
- } from "./chunk-QX7TDFD4.js";
78
+ } from "./chunk-4ILGBQ23.js";
80
79
  import {
81
80
  AgentAbortedError,
82
81
  AgentContextExceededError,
@@ -255,7 +254,6 @@ export {
255
254
  parseSkillFile,
256
255
  resolveSkills,
257
256
  resultToString,
258
- spawn,
259
257
  toAnthropic,
260
258
  toOpenAI,
261
259
  toTypedError,
package/dist/mcp.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import 'hookable';
2
- export { M as McpConnection, p as McpServerConfig, as as connectMcpServers, aD as normalizeMcpBlocks, aE as normalizeMcpServers, aI as resultToString } from './agent-CE2jhpNE.js';
2
+ export { M as McpConnection, p as McpServerConfig, ar as connectMcpServers, aC as normalizeMcpBlocks, aD as normalizeMcpServers, aH as resultToString } from './agent-DqEkutk4.js';
3
3
  import '@modelcontextprotocol/sdk/client/index.js';
4
4
  import './types-vA1a_ZX7.js';
package/dist/presets.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Z as ToolDef, e as AgentOptions } from './agent-CE2jhpNE.js';
1
+ import { Y as ToolDef, e as AgentOptions } from './agent-DqEkutk4.js';
2
2
  import 'hookable';
3
3
  import './types-vA1a_ZX7.js';
4
4
  import '@modelcontextprotocol/sdk/client/index.js';
package/dist/presets.js CHANGED
@@ -2,9 +2,9 @@ import {
2
2
  basicTools,
3
3
  basic_default,
4
4
  definePreset
5
- } from "./chunk-HPTCF3EX.js";
6
- import "./chunk-6JIVVEQQ.js";
7
- import "./chunk-J4ZOSNSH.js";
5
+ } from "./chunk-3D5Q527Y.js";
6
+ import "./chunk-Z2E5QN5X.js";
7
+ import "./chunk-X3VOTPVM.js";
8
8
  import "./chunk-UD25QF3H.js";
9
9
  import "./chunk-7H34OFDA.js";
10
10
  import "./chunk-JH6IAAFA.js";