zidane 3.0.2 → 3.1.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.
- package/README.md +72 -20
- package/dist/{agent-4zeSbdXy.d.ts → agent-Cq009tbG.d.ts} +134 -3
- package/dist/{chunk-D45PXTY2.js → chunk-3DUWP7YU.js} +65 -15
- package/dist/{chunk-2VM47IBI.js → chunk-ATMVSCGJ.js} +1 -1
- package/dist/{chunk-QFHGWKK3.js → chunk-EBSFBIP3.js} +428 -32
- package/dist/{chunk-2EQT4EHD.js → chunk-IUBBVF53.js} +4 -0
- package/dist/contexts.d.ts +3 -3
- package/dist/contexts.js +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.js +4 -4
- package/dist/mcp.d.ts +2 -2
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +3 -3
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +1 -1
- package/dist/{sandbox-CW72eLDP.d.ts → sandbox-CLghrTLi.d.ts} +1 -1
- package/dist/session/sqlite.d.ts +2 -2
- package/dist/session.d.ts +2 -2
- package/dist/{skills-use-DhxQaluD.d.ts → skills-use-Bi6Dklye.d.ts} +1 -1
- package/dist/skills.d.ts +3 -3
- package/dist/tools.d.ts +5 -5
- package/dist/tools.js +2 -2
- package/dist/{types-BpvTmawk.d.ts → types-vA1a_ZX7.d.ts} +11 -0
- package/dist/types.d.ts +4 -4
- package/dist/{validation-CYISGVTn.d.ts → validation-BeQD94ft.d.ts} +1 -1
- package/package.json +2 -2
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-TPXPVEH6.js";
|
|
9
9
|
import {
|
|
10
10
|
createProcessContext
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-IUBBVF53.js";
|
|
12
12
|
import {
|
|
13
13
|
connectMcpServers
|
|
14
14
|
} from "./chunk-R74LQKAM.js";
|
|
@@ -36,6 +36,175 @@ function countExactMatches(haystack, needle) {
|
|
|
36
36
|
}
|
|
37
37
|
return count;
|
|
38
38
|
}
|
|
39
|
+
var LEFT_SINGLE_CURLY_QUOTE = "\u2018";
|
|
40
|
+
var RIGHT_SINGLE_CURLY_QUOTE = "\u2019";
|
|
41
|
+
var LEFT_DOUBLE_CURLY_QUOTE = "\u201C";
|
|
42
|
+
var RIGHT_DOUBLE_CURLY_QUOTE = "\u201D";
|
|
43
|
+
function normalizeQuotes(str) {
|
|
44
|
+
return str.replaceAll(LEFT_SINGLE_CURLY_QUOTE, "'").replaceAll(RIGHT_SINGLE_CURLY_QUOTE, "'").replaceAll(LEFT_DOUBLE_CURLY_QUOTE, '"').replaceAll(RIGHT_DOUBLE_CURLY_QUOTE, '"');
|
|
45
|
+
}
|
|
46
|
+
var DESANITIZATIONS = [
|
|
47
|
+
["<fnr>", "<function_results>"],
|
|
48
|
+
["<n>", "<name>"],
|
|
49
|
+
["</n>", "</name>"],
|
|
50
|
+
["<o>", "<output>"],
|
|
51
|
+
["</o>", "</output>"],
|
|
52
|
+
["<e>", "<error>"],
|
|
53
|
+
["</e>", "</error>"],
|
|
54
|
+
["<s>", "<system>"],
|
|
55
|
+
["</s>", "</system>"],
|
|
56
|
+
["<r>", "<result>"],
|
|
57
|
+
["</r>", "</result>"],
|
|
58
|
+
["< META_START >", "<META_START>"],
|
|
59
|
+
["< META_END >", "<META_END>"],
|
|
60
|
+
["< EOT >", "<EOT>"],
|
|
61
|
+
["< META >", "<META>"],
|
|
62
|
+
["< SOS >", "<SOS>"],
|
|
63
|
+
["\n\nH:", "\n\nHuman:"],
|
|
64
|
+
["\n\nA:", "\n\nAssistant:"]
|
|
65
|
+
];
|
|
66
|
+
function desanitize(s) {
|
|
67
|
+
let out = s;
|
|
68
|
+
for (const [from, to] of DESANITIZATIONS)
|
|
69
|
+
out = out.replaceAll(from, to);
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
function resolveOldString(haystack, needle) {
|
|
73
|
+
const exact = countExactMatches(haystack, needle);
|
|
74
|
+
if (exact > 0)
|
|
75
|
+
return { actual: needle, occurrences: exact, via: "exact" };
|
|
76
|
+
const normNeedle = normalizeQuotes(needle);
|
|
77
|
+
const normFile = normalizeQuotes(haystack);
|
|
78
|
+
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
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const desan = desanitize(needle);
|
|
95
|
+
if (desan !== needle) {
|
|
96
|
+
const desanCount = countExactMatches(haystack, desan);
|
|
97
|
+
if (desanCount > 0)
|
|
98
|
+
return { actual: desan, occurrences: desanCount, via: "desanitize" };
|
|
99
|
+
}
|
|
100
|
+
const combo = desanitize(normNeedle);
|
|
101
|
+
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" };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function preserveQuoteStyle(actual, replacement) {
|
|
120
|
+
const hasDouble = actual.includes(LEFT_DOUBLE_CURLY_QUOTE) || actual.includes(RIGHT_DOUBLE_CURLY_QUOTE);
|
|
121
|
+
const hasSingle = actual.includes(LEFT_SINGLE_CURLY_QUOTE) || actual.includes(RIGHT_SINGLE_CURLY_QUOTE);
|
|
122
|
+
if (!hasDouble && !hasSingle)
|
|
123
|
+
return replacement;
|
|
124
|
+
let out = replacement;
|
|
125
|
+
if (hasDouble)
|
|
126
|
+
out = applyCurly(out, '"', LEFT_DOUBLE_CURLY_QUOTE, RIGHT_DOUBLE_CURLY_QUOTE, false);
|
|
127
|
+
if (hasSingle)
|
|
128
|
+
out = applyCurly(out, "'", LEFT_SINGLE_CURLY_QUOTE, RIGHT_SINGLE_CURLY_QUOTE, true);
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
function applyCurly(s, straight, left, right, contractionAware) {
|
|
132
|
+
const chars = [...s];
|
|
133
|
+
const result = [];
|
|
134
|
+
for (let i = 0; i < chars.length; i++) {
|
|
135
|
+
if (chars[i] !== straight) {
|
|
136
|
+
result.push(chars[i]);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (contractionAware) {
|
|
140
|
+
const prev = i > 0 ? chars[i - 1] : "";
|
|
141
|
+
const next = i < chars.length - 1 ? chars[i + 1] : "";
|
|
142
|
+
if (/\p{L}/u.test(prev) && /\p{L}/u.test(next)) {
|
|
143
|
+
result.push(right);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
result.push(isOpeningContext(chars, i) ? left : right);
|
|
148
|
+
}
|
|
149
|
+
return result.join("");
|
|
150
|
+
}
|
|
151
|
+
function isOpeningContext(chars, i) {
|
|
152
|
+
if (i === 0)
|
|
153
|
+
return true;
|
|
154
|
+
const prev = chars[i - 1];
|
|
155
|
+
return prev === " " || prev === " " || prev === "\n" || prev === "\r" || prev === "(" || prev === "[" || prev === "{" || prev === "\u2014" || prev === "\u2013";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/tools/path-suggest.ts
|
|
159
|
+
async function findSimilarFile(execution, handle, path) {
|
|
160
|
+
const slash = path.lastIndexOf("/");
|
|
161
|
+
const dir = slash === -1 ? "." : path.slice(0, slash) || "/";
|
|
162
|
+
const target = slash === -1 ? path : path.slice(slash + 1);
|
|
163
|
+
const dot = target.lastIndexOf(".");
|
|
164
|
+
const targetBase = dot === -1 ? target : target.slice(0, dot);
|
|
165
|
+
if (targetBase.length === 0)
|
|
166
|
+
return null;
|
|
167
|
+
let entries;
|
|
168
|
+
try {
|
|
169
|
+
entries = await execution.listFiles(handle, dir);
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
for (const entry of entries) {
|
|
174
|
+
if (entry === target)
|
|
175
|
+
continue;
|
|
176
|
+
const entryDot = entry.lastIndexOf(".");
|
|
177
|
+
const entryBase = entryDot === -1 ? entry : entry.slice(0, entryDot);
|
|
178
|
+
if (entryBase === targetBase)
|
|
179
|
+
return entry;
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
async function suggestionFor(execution, handle, path) {
|
|
184
|
+
const sibling = await findSimilarFile(execution, handle, path);
|
|
185
|
+
return sibling ? ` Did you mean ${sibling}?` : "";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/tools/read-state.ts
|
|
189
|
+
var STATE = /* @__PURE__ */ new WeakMap();
|
|
190
|
+
function getReadState(session) {
|
|
191
|
+
if (!session)
|
|
192
|
+
return void 0;
|
|
193
|
+
let map = STATE.get(session);
|
|
194
|
+
if (!map) {
|
|
195
|
+
map = /* @__PURE__ */ new Map();
|
|
196
|
+
STATE.set(session, map);
|
|
197
|
+
}
|
|
198
|
+
return map;
|
|
199
|
+
}
|
|
200
|
+
function hashContent(text) {
|
|
201
|
+
let h = 2166136261;
|
|
202
|
+
for (let i = 0; i < text.length; i++) {
|
|
203
|
+
h ^= text.charCodeAt(i);
|
|
204
|
+
h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
|
|
205
|
+
}
|
|
206
|
+
return h.toString(16).padStart(8, "0");
|
|
207
|
+
}
|
|
39
208
|
|
|
40
209
|
// src/tools/edit.ts
|
|
41
210
|
var edit = {
|
|
@@ -66,19 +235,41 @@ var edit = {
|
|
|
66
235
|
try {
|
|
67
236
|
original = await ctx.execution.readFile(ctx.handle, target);
|
|
68
237
|
} catch {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (
|
|
238
|
+
const hint = await suggestionFor(ctx.execution, ctx.handle, target);
|
|
239
|
+
return `Edit error: file not found: ${target}.${hint}`;
|
|
240
|
+
}
|
|
241
|
+
if (ctx.behavior?.requireReadBeforeEdit && ctx.session) {
|
|
242
|
+
const readState = getReadState(ctx.session);
|
|
243
|
+
const absKey = `${ctx.handle.cwd}::${target}`;
|
|
244
|
+
const prior = readState?.get(absKey);
|
|
245
|
+
if (!prior)
|
|
246
|
+
return `Edit error: ${target} has not been read in this session. Call read_file first so the edit applies against the current contents.`;
|
|
247
|
+
if (prior.contentHash !== hashContent(original))
|
|
248
|
+
return `Edit error: ${target} has changed on disk since the last read. Re-read the file before editing.`;
|
|
249
|
+
}
|
|
250
|
+
const match = resolveOldString(original, find);
|
|
251
|
+
if (!match) {
|
|
73
252
|
const preview = nearestMatchPreview(original, find);
|
|
74
253
|
return preview ? `Edit error: old_string not found in ${target}. Closest match in the file: ${preview}` : `Edit error: old_string not found in ${target}.`;
|
|
75
254
|
}
|
|
255
|
+
const { actual, occurrences, via } = match;
|
|
76
256
|
if (occurrences > 1 && !replaceAll)
|
|
77
257
|
return `Edit error: old_string appears ${occurrences} times in ${target}. Pass replace_all=true or expand old_string for uniqueness.`;
|
|
78
|
-
|
|
258
|
+
let styledReplacement = via === "desanitize" || via === "quotes+desanitize" ? desanitize(replacement) : replacement;
|
|
259
|
+
if (via === "quotes" || via === "quotes+desanitize")
|
|
260
|
+
styledReplacement = preserveQuoteStyle(actual, styledReplacement);
|
|
261
|
+
const updated = replaceAll ? original.split(actual).join(styledReplacement) : original.replace(actual, styledReplacement);
|
|
79
262
|
if (updated === original)
|
|
80
263
|
return `Edit error: replacement produced no change in ${target}.`;
|
|
81
264
|
await ctx.execution.writeFile(ctx.handle, target, updated);
|
|
265
|
+
if (ctx.session) {
|
|
266
|
+
const readState = getReadState(ctx.session);
|
|
267
|
+
const absKey = `${ctx.handle.cwd}::${target}`;
|
|
268
|
+
const prior = readState?.get(absKey);
|
|
269
|
+
if (readState && prior) {
|
|
270
|
+
readState.set(absKey, { ...prior, contentHash: hashContent(updated), mtimeMs: Date.now() });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
82
273
|
return `Edited ${target}: replaced ${occurrences} occurrence${occurrences === 1 ? "" : "s"}.`;
|
|
83
274
|
}
|
|
84
275
|
};
|
|
@@ -441,7 +632,17 @@ var multiEdit = {
|
|
|
441
632
|
try {
|
|
442
633
|
current = await ctx.execution.readFile(ctx.handle, target);
|
|
443
634
|
} catch {
|
|
444
|
-
|
|
635
|
+
const hint = await suggestionFor(ctx.execution, ctx.handle, target);
|
|
636
|
+
return `multi_edit error: file not found: ${target}.${hint}`;
|
|
637
|
+
}
|
|
638
|
+
if (ctx.behavior?.requireReadBeforeEdit && ctx.session) {
|
|
639
|
+
const readState = getReadState(ctx.session);
|
|
640
|
+
const absKey = `${ctx.handle.cwd}::${target}`;
|
|
641
|
+
const prior = readState?.get(absKey);
|
|
642
|
+
if (!prior)
|
|
643
|
+
return `multi_edit error: ${target} has not been read in this session. Call read_file first so the edits apply against the current contents.`;
|
|
644
|
+
if (prior.contentHash !== hashContent(current))
|
|
645
|
+
return `multi_edit error: ${target} has changed on disk since the last read. Re-read the file before editing.`;
|
|
445
646
|
}
|
|
446
647
|
let applied = 0;
|
|
447
648
|
for (let i = 0; i < steps.length; i++) {
|
|
@@ -455,24 +656,77 @@ var multiEdit = {
|
|
|
455
656
|
return `multi_edit error: edit #${i + 1} has empty old_string. Use write_file to fully replace a file.`;
|
|
456
657
|
if (find === replacement)
|
|
457
658
|
return `multi_edit error: edit #${i + 1} old_string and new_string are identical.`;
|
|
458
|
-
const
|
|
459
|
-
if (
|
|
659
|
+
const match = resolveOldString(current, find);
|
|
660
|
+
if (!match)
|
|
460
661
|
return `multi_edit error: edit #${i + 1} old_string not found in ${target}.`;
|
|
662
|
+
const { actual, occurrences, via } = match;
|
|
461
663
|
if (occurrences > 1 && !replaceAll)
|
|
462
664
|
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.`;
|
|
463
|
-
|
|
665
|
+
let styledReplacement = via === "desanitize" || via === "quotes+desanitize" ? desanitize(replacement) : replacement;
|
|
666
|
+
if (via === "quotes" || via === "quotes+desanitize")
|
|
667
|
+
styledReplacement = preserveQuoteStyle(actual, styledReplacement);
|
|
668
|
+
current = replaceAll ? current.split(actual).join(styledReplacement) : current.replace(actual, styledReplacement);
|
|
464
669
|
applied += occurrences;
|
|
465
670
|
}
|
|
466
671
|
await ctx.execution.writeFile(ctx.handle, target, current);
|
|
672
|
+
if (ctx.session) {
|
|
673
|
+
const readState = getReadState(ctx.session);
|
|
674
|
+
const absKey = `${ctx.handle.cwd}::${target}`;
|
|
675
|
+
const prior = readState?.get(absKey);
|
|
676
|
+
if (readState && prior) {
|
|
677
|
+
readState.set(absKey, { ...prior, contentHash: hashContent(current), mtimeMs: Date.now() });
|
|
678
|
+
}
|
|
679
|
+
}
|
|
467
680
|
return `Edited ${target}: applied ${steps.length} edit${steps.length === 1 ? "" : "s"} (${applied} replacement${applied === 1 ? "" : "s"}).`;
|
|
468
681
|
}
|
|
469
682
|
};
|
|
470
683
|
|
|
471
684
|
// src/tools/read-file.ts
|
|
685
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
686
|
+
|
|
687
|
+
// src/tools/binary-read.ts
|
|
472
688
|
import { Buffer } from "buffer";
|
|
689
|
+
function imageMediaTypeFor(path) {
|
|
690
|
+
const dot = path.lastIndexOf(".");
|
|
691
|
+
if (dot === -1)
|
|
692
|
+
return void 0;
|
|
693
|
+
const ext = path.slice(dot + 1).toLowerCase();
|
|
694
|
+
switch (ext) {
|
|
695
|
+
case "png":
|
|
696
|
+
return "image/png";
|
|
697
|
+
case "jpg":
|
|
698
|
+
case "jpeg":
|
|
699
|
+
return "image/jpeg";
|
|
700
|
+
case "gif":
|
|
701
|
+
return "image/gif";
|
|
702
|
+
case "webp":
|
|
703
|
+
return "image/webp";
|
|
704
|
+
default:
|
|
705
|
+
return void 0;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
async function readFileAsBase64(execution, handle, path) {
|
|
709
|
+
if (execution.readFileBinary) {
|
|
710
|
+
const bytes = await execution.readFileBinary(handle, path);
|
|
711
|
+
const b642 = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
|
|
712
|
+
return { base64: b642, byteLength: bytes.byteLength };
|
|
713
|
+
}
|
|
714
|
+
const cmd = `base64 < ${shellQuote2(path)}`;
|
|
715
|
+
const result = await execution.exec(handle, cmd);
|
|
716
|
+
if (result.exitCode !== 0)
|
|
717
|
+
throw new Error(`base64 read failed: ${result.stderr || `exit ${result.exitCode}`}`);
|
|
718
|
+
const b64 = result.stdout.replace(/\s+/g, "");
|
|
719
|
+
return { base64: b64, byteLength: Math.floor(b64.length * 3 / 4) };
|
|
720
|
+
}
|
|
721
|
+
function shellQuote2(s) {
|
|
722
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// src/tools/read-file.ts
|
|
473
726
|
var DEFAULT_LINE_LIMIT = 2e3;
|
|
474
727
|
var DEFAULT_BYTE_CAP = 65536;
|
|
475
728
|
var BINARY_PROBE_BYTES = 8e3;
|
|
729
|
+
var DEFAULT_IMAGE_BYTE_CAP = 5 * 1024 * 1024;
|
|
476
730
|
var readFile = {
|
|
477
731
|
spec: {
|
|
478
732
|
name: "read_file",
|
|
@@ -489,19 +743,52 @@ var readFile = {
|
|
|
489
743
|
}
|
|
490
744
|
},
|
|
491
745
|
async execute({ path, offset, limit, maxBytes }, ctx) {
|
|
746
|
+
const imgMedia = imageMediaTypeFor(path);
|
|
747
|
+
if (imgMedia) {
|
|
748
|
+
const sizeCap = maxBytes !== void 0 ? normalizeInteger(maxBytes, DEFAULT_IMAGE_BYTE_CAP) : DEFAULT_IMAGE_BYTE_CAP;
|
|
749
|
+
try {
|
|
750
|
+
const { base64, byteLength } = await readFileAsBase64(ctx.execution, ctx.handle, path);
|
|
751
|
+
if (sizeCap > 0 && byteLength > sizeCap) {
|
|
752
|
+
return `[image too large to inline: ${path}, ${byteLength} bytes (cap ${sizeCap}). Raise maxBytes, or use shell to inspect.]`;
|
|
753
|
+
}
|
|
754
|
+
const content = [
|
|
755
|
+
{ type: "text", text: `Image: ${path} (${byteLength} bytes, ${imgMedia})` },
|
|
756
|
+
{ type: "image", mediaType: imgMedia, data: base64 }
|
|
757
|
+
];
|
|
758
|
+
return content;
|
|
759
|
+
} catch (err) {
|
|
760
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
761
|
+
const hint = await suggestionFor(ctx.execution, ctx.handle, path);
|
|
762
|
+
return `Image read failed: ${path} \u2014 ${msg}.${hint}`;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
492
765
|
let raw;
|
|
493
766
|
try {
|
|
494
767
|
raw = await ctx.execution.readFile(ctx.handle, path);
|
|
495
768
|
} catch {
|
|
496
|
-
|
|
769
|
+
const hint = await suggestionFor(ctx.execution, ctx.handle, path);
|
|
770
|
+
return `File not found: ${path}.${hint}`;
|
|
771
|
+
}
|
|
772
|
+
const totalBytes = Buffer2.byteLength(raw);
|
|
773
|
+
const dedupEnabled = ctx.behavior?.dedupReads !== false;
|
|
774
|
+
const readState = dedupEnabled ? getReadState(ctx.session) : void 0;
|
|
775
|
+
const absKey = `${ctx.handle.cwd}::${path}`;
|
|
776
|
+
const offsetForKey = normalizeInteger(offset, 1);
|
|
777
|
+
const limitForKey = normalizeInteger(limit, DEFAULT_LINE_LIMIT);
|
|
778
|
+
const maxBytesForKey = normalizeInteger(maxBytes, DEFAULT_BYTE_CAP);
|
|
779
|
+
const currentHash = readState ? hashContent(raw) : "";
|
|
780
|
+
if (readState) {
|
|
781
|
+
const prior = readState.get(absKey);
|
|
782
|
+
if (prior && prior.contentHash === currentHash && prior.offset === offsetForKey && prior.limit === limitForKey && prior.maxBytes === maxBytesForKey) {
|
|
783
|
+
return `File ${path} unchanged since the previous read in this session \u2014 the prior result is still current.`;
|
|
784
|
+
}
|
|
497
785
|
}
|
|
498
|
-
const totalBytes = Buffer.byteLength(raw);
|
|
499
786
|
if (looksBinary(raw)) {
|
|
500
787
|
return `[binary file: ${path}, ${totalBytes} bytes; use shell with hexdump | xxd | od to inspect]`;
|
|
501
788
|
}
|
|
502
|
-
const offsetN =
|
|
503
|
-
const limitN =
|
|
504
|
-
const maxBytesN =
|
|
789
|
+
const offsetN = offsetForKey;
|
|
790
|
+
const limitN = limitForKey;
|
|
791
|
+
const maxBytesN = maxBytesForKey;
|
|
505
792
|
const lines = raw.split("\n");
|
|
506
793
|
const totalLines = lines.length;
|
|
507
794
|
const startIdx = Math.max(0, offsetN - 1);
|
|
@@ -512,7 +799,7 @@ var readFile = {
|
|
|
512
799
|
if (maxBytesN > 0) {
|
|
513
800
|
const truncatedSlice = [];
|
|
514
801
|
for (const line of slice) {
|
|
515
|
-
const lineBytes =
|
|
802
|
+
const lineBytes = Buffer2.byteLength(line) + 1;
|
|
516
803
|
if (bytesUsed + lineBytes > maxBytesN && truncatedSlice.length > 0) {
|
|
517
804
|
bytesCut = true;
|
|
518
805
|
break;
|
|
@@ -529,14 +816,14 @@ var readFile = {
|
|
|
529
816
|
}
|
|
530
817
|
let midLineCut = false;
|
|
531
818
|
if (maxBytesN > 0 && slice.length > 0) {
|
|
532
|
-
const bodyBytes =
|
|
819
|
+
const bodyBytes = Buffer2.byteLength(slice.join("\n"));
|
|
533
820
|
if (bodyBytes > maxBytesN) {
|
|
534
821
|
const lastIdx = slice.length - 1;
|
|
535
822
|
const lastLine = slice[lastIdx];
|
|
536
|
-
const otherBytes = lastIdx > 0 ?
|
|
823
|
+
const otherBytes = lastIdx > 0 ? Buffer2.byteLength(slice.slice(0, lastIdx).join("\n")) + 1 : 0;
|
|
537
824
|
const budgetForLast = Math.max(0, maxBytesN - otherBytes);
|
|
538
825
|
let cut = Math.min(lastLine.length, budgetForLast);
|
|
539
|
-
while (cut > 0 &&
|
|
826
|
+
while (cut > 0 && Buffer2.byteLength(lastLine.slice(0, cut)) > budgetForLast)
|
|
540
827
|
cut--;
|
|
541
828
|
slice[lastIdx] = lastLine.slice(0, cut);
|
|
542
829
|
midLineCut = true;
|
|
@@ -546,6 +833,15 @@ var readFile = {
|
|
|
546
833
|
const body = slice.join("\n");
|
|
547
834
|
const linesReturned = slice.length;
|
|
548
835
|
const lastLineRead = startIdx + linesReturned;
|
|
836
|
+
if (readState) {
|
|
837
|
+
readState.set(absKey, {
|
|
838
|
+
contentHash: currentHash,
|
|
839
|
+
offset: offsetN,
|
|
840
|
+
limit: limitN,
|
|
841
|
+
maxBytes: maxBytesN,
|
|
842
|
+
mtimeMs: Date.now()
|
|
843
|
+
});
|
|
844
|
+
}
|
|
549
845
|
const linesTruncated = endIdx < totalLines || bytesCut;
|
|
550
846
|
if (!linesTruncated && offsetN === 1)
|
|
551
847
|
return body;
|
|
@@ -589,7 +885,38 @@ function looksBinary(text) {
|
|
|
589
885
|
}
|
|
590
886
|
|
|
591
887
|
// src/tools/shell.ts
|
|
592
|
-
import { Buffer as
|
|
888
|
+
import { Buffer as Buffer3 } from "buffer";
|
|
889
|
+
|
|
890
|
+
// src/tools/shell-semantics.ts
|
|
891
|
+
var DEFAULT_SEMANTIC = (exitCode) => ({
|
|
892
|
+
isError: exitCode !== 0,
|
|
893
|
+
message: exitCode !== 0 ? `Command failed with exit code ${exitCode}` : void 0
|
|
894
|
+
});
|
|
895
|
+
var COMMAND_SEMANTICS = /* @__PURE__ */ new Map([
|
|
896
|
+
// grep / ripgrep: 0 = matches, 1 = no matches, ≥2 = error.
|
|
897
|
+
["grep", (exit) => ({ isError: exit >= 2, message: exit === 1 ? "No matches found" : void 0 })],
|
|
898
|
+
["rg", (exit) => ({ isError: exit >= 2, message: exit === 1 ? "No matches found" : void 0 })],
|
|
899
|
+
// diff: 0 = identical, 1 = differ, ≥2 = error.
|
|
900
|
+
["diff", (exit) => ({ isError: exit >= 2, message: exit === 1 ? "Files differ" : void 0 })],
|
|
901
|
+
// find: 0 = ok, 1 = some dirs inaccessible (warning), ≥2 = error.
|
|
902
|
+
["find", (exit) => ({ isError: exit >= 2, message: exit === 1 ? "Some directories were inaccessible" : void 0 })],
|
|
903
|
+
// test / [: 0 = condition true, 1 = condition false, ≥2 = error.
|
|
904
|
+
["test", (exit) => ({ isError: exit >= 2, message: exit === 1 ? "Condition is false" : void 0 })],
|
|
905
|
+
["[", (exit) => ({ isError: exit >= 2, message: exit === 1 ? "Condition is false" : void 0 })]
|
|
906
|
+
]);
|
|
907
|
+
function interpretShellResult(command, exitCode) {
|
|
908
|
+
const base = extractTrailingCommand(command);
|
|
909
|
+
const semantic = COMMAND_SEMANTICS.get(base) ?? DEFAULT_SEMANTIC;
|
|
910
|
+
return semantic(exitCode);
|
|
911
|
+
}
|
|
912
|
+
function extractTrailingCommand(command) {
|
|
913
|
+
const segments = command.split(/\|\||&&|[;|\n]/);
|
|
914
|
+
const last = segments[segments.length - 1]?.trim() ?? command;
|
|
915
|
+
const tokens = last.split(/\s+/).filter((t) => !/^[A-Z_]\w*=/i.test(t));
|
|
916
|
+
return tokens[0] ?? "";
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// src/tools/shell.ts
|
|
593
920
|
var DEFAULT_MAX_OUTPUT_BYTES = 8192;
|
|
594
921
|
var shell = {
|
|
595
922
|
spec: {
|
|
@@ -609,10 +936,19 @@ var shell = {
|
|
|
609
936
|
const execOpts = {};
|
|
610
937
|
if (typeof timeout === "number" && Number.isFinite(timeout) && timeout > 0)
|
|
611
938
|
execOpts.timeout = Math.max(1, Math.ceil(timeout / 1e3));
|
|
612
|
-
const
|
|
939
|
+
const cmd = command;
|
|
940
|
+
const result = await ctx.execution.exec(ctx.handle, cmd, execOpts);
|
|
613
941
|
const cap = normalizeCap(maxOutputBytes);
|
|
942
|
+
const semantic = interpretShellResult(cmd, result.exitCode);
|
|
614
943
|
if (result.exitCode === 0)
|
|
615
944
|
return truncateTail(result.stdout || "(no output)", cap);
|
|
945
|
+
if (!semantic.isError) {
|
|
946
|
+
const body = (result.stdout || result.stderr || "").trim();
|
|
947
|
+
const tail = truncateTail(body, cap);
|
|
948
|
+
const footer = semantic.message ? `
|
|
949
|
+
(${semantic.message})` : "";
|
|
950
|
+
return tail.length > 0 ? `${tail}${footer}` : semantic.message ?? "(no output)";
|
|
951
|
+
}
|
|
616
952
|
const combined = `${result.stdout}
|
|
617
953
|
${result.stderr}`.trim();
|
|
618
954
|
return `Exit code ${result.exitCode}
|
|
@@ -629,21 +965,21 @@ function normalizeCap(value) {
|
|
|
629
965
|
function truncateTail(text, cap) {
|
|
630
966
|
if (cap === 0)
|
|
631
967
|
return text;
|
|
632
|
-
const totalBytes =
|
|
968
|
+
const totalBytes = Buffer3.byteLength(text);
|
|
633
969
|
if (totalBytes <= cap)
|
|
634
970
|
return text;
|
|
635
971
|
let bytes = 0;
|
|
636
972
|
let charIdx = text.length;
|
|
637
973
|
while (charIdx > 0) {
|
|
638
974
|
const ch = text[charIdx - 1];
|
|
639
|
-
const chBytes =
|
|
975
|
+
const chBytes = Buffer3.byteLength(ch);
|
|
640
976
|
if (bytes + chBytes > cap)
|
|
641
977
|
break;
|
|
642
978
|
bytes += chBytes;
|
|
643
979
|
charIdx--;
|
|
644
980
|
}
|
|
645
981
|
const tail = text.slice(charIdx);
|
|
646
|
-
const droppedBytes = totalBytes -
|
|
982
|
+
const droppedBytes = totalBytes - Buffer3.byteLength(tail);
|
|
647
983
|
return `\u2026(${droppedBytes} bytes truncated from head)\u2026
|
|
648
984
|
${tail}`;
|
|
649
985
|
}
|
|
@@ -1101,6 +1437,43 @@ var IMAGE_OMITTED_MARKER = "[image omitted \u2014 model does not support vision]
|
|
|
1101
1437
|
function turnsToMessages(turns) {
|
|
1102
1438
|
return turns.filter((t) => t.role !== "system").map((t) => ({ role: t.role, content: t.content }));
|
|
1103
1439
|
}
|
|
1440
|
+
var COMPACTION_STUB = "[\u2026elided by client-side tail compaction; ask the user or re-run the tool to retrieve.]";
|
|
1441
|
+
function applyTailCompaction(messages, threshold, keepTurns) {
|
|
1442
|
+
if (messages.length === 0)
|
|
1443
|
+
return messages;
|
|
1444
|
+
let totalBytes = 0;
|
|
1445
|
+
for (const msg of messages) {
|
|
1446
|
+
for (const block of msg.content) {
|
|
1447
|
+
if (block.type === "tool_result")
|
|
1448
|
+
totalBytes += toolOutputByteLength(block.output);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
if (totalBytes <= threshold)
|
|
1452
|
+
return messages;
|
|
1453
|
+
const keep = Math.max(0, keepTurns);
|
|
1454
|
+
const cutoff = messages.length - keep;
|
|
1455
|
+
if (cutoff <= 0)
|
|
1456
|
+
return messages;
|
|
1457
|
+
let changed = false;
|
|
1458
|
+
const out = messages.slice();
|
|
1459
|
+
for (let i = 0; i < cutoff; i++) {
|
|
1460
|
+
const msg = out[i];
|
|
1461
|
+
let msgChanged = false;
|
|
1462
|
+
const newContent = msg.content.map((block) => {
|
|
1463
|
+
if (block.type !== "tool_result")
|
|
1464
|
+
return block;
|
|
1465
|
+
const existingBytes = toolOutputByteLength(block.output);
|
|
1466
|
+
if (existingBytes <= COMPACTION_STUB.length)
|
|
1467
|
+
return block;
|
|
1468
|
+
msgChanged = true;
|
|
1469
|
+
changed = true;
|
|
1470
|
+
return { ...block, output: COMPACTION_STUB };
|
|
1471
|
+
});
|
|
1472
|
+
if (msgChanged)
|
|
1473
|
+
out[i] = { ...msg, content: newContent };
|
|
1474
|
+
}
|
|
1475
|
+
return changed ? out : messages;
|
|
1476
|
+
}
|
|
1104
1477
|
function sanitizeStoredToolResults(provider, messages) {
|
|
1105
1478
|
if (provider.meta.capabilities?.vision !== false)
|
|
1106
1479
|
return messages;
|
|
@@ -1210,7 +1583,12 @@ async function executeTurn(ctx, turn) {
|
|
|
1210
1583
|
const turnId = await ctx.generateTurnId();
|
|
1211
1584
|
const canonicalMessages = turnsToMessages(ctx.turns);
|
|
1212
1585
|
const wireMessages = rewriteMessagesToWire(canonicalMessages, ctx.aliasMaps);
|
|
1213
|
-
|
|
1586
|
+
let sanitizedMessages = sanitizeStoredToolResults(ctx.provider, wireMessages);
|
|
1587
|
+
if (ctx.compactStrategy === "tail") {
|
|
1588
|
+
const threshold = typeof ctx.compactThreshold === "number" && ctx.compactThreshold > 0 ? ctx.compactThreshold : 131072;
|
|
1589
|
+
const keep = typeof ctx.compactKeepTurns === "number" && ctx.compactKeepTurns >= 0 ? ctx.compactKeepTurns : 4;
|
|
1590
|
+
sanitizedMessages = applyTailCompaction(sanitizedMessages, threshold, keep);
|
|
1591
|
+
}
|
|
1214
1592
|
const streamOptions = {
|
|
1215
1593
|
model: ctx.model,
|
|
1216
1594
|
system: ctx.system,
|
|
@@ -1337,6 +1715,17 @@ async function executeTurn(ctx, turn) {
|
|
|
1337
1715
|
}
|
|
1338
1716
|
return { ended: true, turnId, usage: result.usage };
|
|
1339
1717
|
}
|
|
1718
|
+
if (canonicalToolCalls.length === 0 && result.usage.finishReason === "pause") {
|
|
1719
|
+
const continueMsg = ctx.provider.userMessage("Please continue.");
|
|
1720
|
+
ctx.turns.push({
|
|
1721
|
+
id: await ctx.generateTurnId(),
|
|
1722
|
+
runId: ctx.runId,
|
|
1723
|
+
role: continueMsg.role,
|
|
1724
|
+
content: continueMsg.content,
|
|
1725
|
+
createdAt: Date.now()
|
|
1726
|
+
});
|
|
1727
|
+
return { ended: false, turnId, usage: result.usage };
|
|
1728
|
+
}
|
|
1340
1729
|
const toolResults = ctx.toolExecution === "parallel" ? await executeToolsParallel(ctx, canonicalToolCalls, turnId) : await executeToolsSequential(ctx, canonicalToolCalls, turnId);
|
|
1341
1730
|
const toolResultMsg = ctx.provider.toolResultsMessage(toolResults);
|
|
1342
1731
|
ctx.turns.push({
|
|
@@ -1479,15 +1868,16 @@ async function executeSingleTool(ctx, call, turnId) {
|
|
|
1479
1868
|
output = await toolDef.execute(effectiveInput, toolCtx);
|
|
1480
1869
|
} catch (err) {
|
|
1481
1870
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
1482
|
-
|
|
1871
|
+
const errorCtx = {
|
|
1483
1872
|
turnId,
|
|
1484
1873
|
callId,
|
|
1485
1874
|
name: call.name,
|
|
1486
1875
|
displayName,
|
|
1487
1876
|
input: effectiveInput,
|
|
1488
1877
|
error
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1878
|
+
};
|
|
1879
|
+
await ctx.hooks.callHook("tool:error", errorCtx);
|
|
1880
|
+
output = errorCtx.result ?? `Tool error: ${error.message}`;
|
|
1491
1881
|
isError = true;
|
|
1492
1882
|
}
|
|
1493
1883
|
const transformCtx = {
|
|
@@ -1690,7 +2080,10 @@ function resolveBehavior(agentBehavior, runBehavior) {
|
|
|
1690
2080
|
thinkingBudget: runBehavior?.thinkingBudget ?? agentBehavior?.thinkingBudget,
|
|
1691
2081
|
schema: runBehavior?.schema ?? agentBehavior?.schema,
|
|
1692
2082
|
cache: runBehavior?.cache ?? agentBehavior?.cache ?? true,
|
|
1693
|
-
toolOutputBudget: runBehavior?.toolOutputBudget ?? agentBehavior?.toolOutputBudget
|
|
2083
|
+
toolOutputBudget: runBehavior?.toolOutputBudget ?? agentBehavior?.toolOutputBudget,
|
|
2084
|
+
compactStrategy: runBehavior?.compactStrategy ?? agentBehavior?.compactStrategy ?? "off",
|
|
2085
|
+
compactThreshold: runBehavior?.compactThreshold ?? agentBehavior?.compactThreshold,
|
|
2086
|
+
compactKeepTurns: runBehavior?.compactKeepTurns ?? agentBehavior?.compactKeepTurns
|
|
1694
2087
|
};
|
|
1695
2088
|
}
|
|
1696
2089
|
function createAgent({ provider, name: agentName, system: agentSystem, tools: agentTools, toolAliases, behavior: agentBehavior, execution, mcpServers, session, skills: agentSkills, mcpConnector, eager }) {
|
|
@@ -1813,7 +2206,7 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
|
|
|
1813
2206
|
}
|
|
1814
2207
|
const thinking = options.thinking ?? "off";
|
|
1815
2208
|
const model = options.model ?? provider.meta.defaultModel;
|
|
1816
|
-
const { toolExecution, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget } = resolveBehavior(agentBehavior, options.behavior);
|
|
2209
|
+
const { toolExecution, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, compactStrategy, compactThreshold, compactKeepTurns } = resolveBehavior(agentBehavior, options.behavior);
|
|
1817
2210
|
let system = options.system || agentSystem || "You are a helpful assistant.";
|
|
1818
2211
|
if (skillsCatalog) {
|
|
1819
2212
|
system = `${system}
|
|
@@ -1949,6 +2342,9 @@ ${skillsCatalog}`;
|
|
|
1949
2342
|
schema,
|
|
1950
2343
|
cache,
|
|
1951
2344
|
toolOutputBudget,
|
|
2345
|
+
compactStrategy,
|
|
2346
|
+
compactThreshold,
|
|
2347
|
+
compactKeepTurns,
|
|
1952
2348
|
runStartMs
|
|
1953
2349
|
});
|
|
1954
2350
|
const finalStats = {
|
|
@@ -2393,7 +2789,7 @@ function createSpawnTool(options = {}) {
|
|
|
2393
2789
|
var spawn = createSpawnTool();
|
|
2394
2790
|
|
|
2395
2791
|
// src/tools/write-file.ts
|
|
2396
|
-
import { Buffer as
|
|
2792
|
+
import { Buffer as Buffer4 } from "buffer";
|
|
2397
2793
|
var writeFile = {
|
|
2398
2794
|
spec: {
|
|
2399
2795
|
name: "write_file",
|
|
@@ -2415,7 +2811,7 @@ var writeFile = {
|
|
|
2415
2811
|
existing = await ctx.execution.readFile(ctx.handle, targetPath);
|
|
2416
2812
|
} catch {
|
|
2417
2813
|
}
|
|
2418
|
-
const bytes =
|
|
2814
|
+
const bytes = Buffer4.byteLength(targetContent);
|
|
2419
2815
|
if (existing === targetContent)
|
|
2420
2816
|
return `No change needed: ${targetPath} already at target state (${bytes} bytes).`;
|
|
2421
2817
|
await ctx.execution.writeFile(ctx.handle, targetPath, targetContent);
|
|
@@ -176,6 +176,10 @@ function createProcessContext(config) {
|
|
|
176
176
|
async readFile(handle, path) {
|
|
177
177
|
return readFile(resolve(handle.cwd, path), "utf-8");
|
|
178
178
|
},
|
|
179
|
+
async readFileBinary(handle, path) {
|
|
180
|
+
const buf = await readFile(resolve(handle.cwd, path));
|
|
181
|
+
return new Uint8Array(buf);
|
|
182
|
+
},
|
|
179
183
|
async writeFile(handle, path, content) {
|
|
180
184
|
const fullPath = resolve(handle.cwd, path);
|
|
181
185
|
await mkdir(dirname(fullPath), { recursive: true });
|
package/dist/contexts.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { S as SpawnConfig, b as ExecutionContext } from './types-
|
|
2
|
-
export { C as ContextCapabilities, a as ContextType, E as ExecResult, c as ExecutionHandle } from './types-
|
|
3
|
-
export { S as SandboxProvider, c as createSandboxContext } from './sandbox-
|
|
1
|
+
import { S as SpawnConfig, b as ExecutionContext } from './types-vA1a_ZX7.js';
|
|
2
|
+
export { C as ContextCapabilities, a as ContextType, E as ExecResult, c as ExecutionHandle } from './types-vA1a_ZX7.js';
|
|
3
|
+
export { S as SandboxProvider, c as createSandboxContext } from './sandbox-CLghrTLi.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Docker execution context.
|