@cryer/star-cli 0.3.12 → 0.3.14
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 +17 -5
- package/README.zh-CN.md +15 -5
- package/dist/{chunk-LSYGHLRU.js → chunk-2ISBRSBA.js} +479 -296
- package/dist/chunk-2ISBRSBA.js.map +1 -0
- package/dist/{chunk-F5RWZMQY.js → chunk-4HANB3DJ.js} +458 -221
- package/dist/chunk-4HANB3DJ.js.map +1 -0
- package/dist/chunk-4IRY44TG.js +0 -0
- package/dist/{chunk-Q5FJN4LN.js → chunk-54KH4JSO.js} +213 -125
- package/dist/chunk-54KH4JSO.js.map +1 -0
- package/dist/{chunk-MYHD3SZB.js → chunk-CECMCDZG.js} +43 -30
- package/dist/chunk-CECMCDZG.js.map +1 -0
- package/dist/{chunk-MVAMPVTA.js → chunk-KILMI3S5.js} +13 -5
- package/dist/{chunk-MVAMPVTA.js.map → chunk-KILMI3S5.js.map} +1 -1
- package/dist/{chunk-2GAUAYCU.js → chunk-NRLGGA7L.js} +507 -175
- package/dist/chunk-NRLGGA7L.js.map +1 -0
- package/dist/loop-M64D5YII.js +17 -0
- package/dist/main.js +177 -131
- package/dist/main.js.map +1 -1
- package/dist/{provider-EWMUPBLH.js → provider-3Z4B7RMF.js} +2 -2
- package/dist/{repl-SSCURTAH.js → repl-N2H7JDSH.js} +617 -327
- package/dist/repl-N2H7JDSH.js.map +1 -0
- package/dist/{tools-BH55BVIS.js → tools-5RKM7ZI3.js} +3 -3
- package/package.json +16 -25
- package/dist/chunk-2GAUAYCU.js.map +0 -1
- package/dist/chunk-F5RWZMQY.js.map +0 -1
- package/dist/chunk-LSYGHLRU.js.map +0 -1
- package/dist/chunk-MYHD3SZB.js.map +0 -1
- package/dist/chunk-Q5FJN4LN.js.map +0 -1
- package/dist/loop-BYXBNFFY.js +0 -17
- package/dist/repl-SSCURTAH.js.map +0 -1
- /package/dist/{loop-BYXBNFFY.js.map → loop-M64D5YII.js.map} +0 -0
- /package/dist/{provider-EWMUPBLH.js.map → provider-3Z4B7RMF.js.map} +0 -0
- /package/dist/{tools-BH55BVIS.js.map → tools-5RKM7ZI3.js.map} +0 -0
package/dist/chunk-4IRY44TG.js
CHANGED
|
File without changes
|
|
@@ -18,11 +18,12 @@ import {
|
|
|
18
18
|
nextSnapshotId,
|
|
19
19
|
probeImageDimensions,
|
|
20
20
|
pushSnapshot,
|
|
21
|
+
realpathForCheck,
|
|
21
22
|
sharedTodoStore,
|
|
22
23
|
starHome,
|
|
23
24
|
walkFiles,
|
|
24
25
|
webFetchTool
|
|
25
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-2ISBRSBA.js";
|
|
26
27
|
|
|
27
28
|
// src/tools/fs/edit.ts
|
|
28
29
|
import { readFile, stat, writeFile } from "fs/promises";
|
|
@@ -41,6 +42,31 @@ function countOccurrences(haystack, needle) {
|
|
|
41
42
|
function normalizeEol(text) {
|
|
42
43
|
return text.replace(/\r\n/g, "\n");
|
|
43
44
|
}
|
|
45
|
+
function normalizedOffsets(text) {
|
|
46
|
+
const offsets = [0];
|
|
47
|
+
for (let i = 0; i < text.length; i++) {
|
|
48
|
+
if (text[i] === "\r" && text[i + 1] === "\n") i++;
|
|
49
|
+
offsets.push(i + 1);
|
|
50
|
+
}
|
|
51
|
+
return offsets;
|
|
52
|
+
}
|
|
53
|
+
function replacePreservingEol(raw, normalized, offsets, oldString, newString, replaceAll, useCrlf) {
|
|
54
|
+
const replacement = useCrlf ? newString.replace(/\n/g, "\r\n") : newString;
|
|
55
|
+
let normalizedFrom = 0;
|
|
56
|
+
let rawFrom = 0;
|
|
57
|
+
let updated = "";
|
|
58
|
+
while (true) {
|
|
59
|
+
const match = normalized.indexOf(oldString, normalizedFrom);
|
|
60
|
+
if (match < 0) break;
|
|
61
|
+
const rawStart = offsets[match] ?? raw.length;
|
|
62
|
+
const rawEnd = offsets[match + oldString.length] ?? raw.length;
|
|
63
|
+
updated += raw.slice(rawFrom, rawStart) + replacement;
|
|
64
|
+
rawFrom = rawEnd;
|
|
65
|
+
normalizedFrom = match + oldString.length;
|
|
66
|
+
if (!replaceAll) break;
|
|
67
|
+
}
|
|
68
|
+
return updated + raw.slice(rawFrom);
|
|
69
|
+
}
|
|
44
70
|
var schema = z.object({
|
|
45
71
|
path: z.string().describe("File path, absolute or relative to the working directory"),
|
|
46
72
|
old_string: z.string().min(1).describe("Exact text to replace"),
|
|
@@ -54,6 +80,10 @@ var editFileTool = {
|
|
|
54
80
|
parameters: schema,
|
|
55
81
|
async execute(args, ctx) {
|
|
56
82
|
const filePath = path.resolve(ctx.cwd, args.path);
|
|
83
|
+
const resolvedPath = await realpathForCheck(filePath);
|
|
84
|
+
if (isSensitivePath(filePath) || resolvedPath !== null && isSensitivePath(resolvedPath)) {
|
|
85
|
+
return { content: `Refused to edit sensitive file: ${args.path}`, isError: true };
|
|
86
|
+
}
|
|
57
87
|
let content;
|
|
58
88
|
let readMtimeMs;
|
|
59
89
|
try {
|
|
@@ -76,7 +106,8 @@ var editFileTool = {
|
|
|
76
106
|
let working = content;
|
|
77
107
|
let oldString = args.old_string;
|
|
78
108
|
let newString = args.new_string;
|
|
79
|
-
let
|
|
109
|
+
let offsets = null;
|
|
110
|
+
let useCrlf = false;
|
|
80
111
|
let count = countOccurrences(working, oldString);
|
|
81
112
|
if (count === 0) {
|
|
82
113
|
const normalizedContent = normalizeEol(content);
|
|
@@ -85,7 +116,8 @@ var editFileTool = {
|
|
|
85
116
|
if (normalizedCount > 0) {
|
|
86
117
|
const crlfCount = countOccurrences(content, "\r\n");
|
|
87
118
|
const loneLfCount = countOccurrences(content.replace(/\r\n/g, ""), "\n");
|
|
88
|
-
|
|
119
|
+
useCrlf = crlfCount > 0 && crlfCount >= loneLfCount;
|
|
120
|
+
offsets = normalizedOffsets(content);
|
|
89
121
|
working = normalizedContent;
|
|
90
122
|
oldString = normalizedOld;
|
|
91
123
|
newString = normalizeEol(newString);
|
|
@@ -101,10 +133,15 @@ var editFileTool = {
|
|
|
101
133
|
isError: true
|
|
102
134
|
};
|
|
103
135
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
136
|
+
const updated = offsets ? replacePreservingEol(
|
|
137
|
+
content,
|
|
138
|
+
working,
|
|
139
|
+
offsets,
|
|
140
|
+
oldString,
|
|
141
|
+
newString,
|
|
142
|
+
args.replace_all === true,
|
|
143
|
+
useCrlf
|
|
144
|
+
) : args.replace_all ? working.split(oldString).join(newString) : working.replace(oldString, () => newString);
|
|
108
145
|
try {
|
|
109
146
|
const now = await stat(filePath).catch(() => null);
|
|
110
147
|
if (!now || now.mtimeMs !== readMtimeMs) {
|
|
@@ -175,13 +212,17 @@ var globTool = {
|
|
|
175
212
|
const prefixSegments = literalDirPrefix(args.pattern);
|
|
176
213
|
const walkRoot = prefixSegments.length > 0 ? path2.join(root, ...prefixSegments) : root;
|
|
177
214
|
const prefix = prefixSegments.length > 0 ? `${prefixSegments.join("/")}/` : "";
|
|
178
|
-
|
|
215
|
+
let matched = [];
|
|
216
|
+
const { aborted } = await walkFiles(walkRoot, void 0, {
|
|
179
217
|
ignore: createIgnorePredicate(ctx.cwd),
|
|
180
218
|
signal: ctx.abortSignal,
|
|
181
219
|
symlinkBoundary: ctx.cwd,
|
|
182
|
-
nestedIgnore: true
|
|
220
|
+
nestedIgnore: true,
|
|
221
|
+
collect: false,
|
|
222
|
+
onBatch: (files) => {
|
|
223
|
+
matched = matched.concat(files.filter((f) => matchesGlob(args.pattern, `${prefix}${f.rel}`))).sort((a, b) => b.mtimeMs - a.mtimeMs || (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0)).slice(0, MAX_RESULTS);
|
|
224
|
+
}
|
|
183
225
|
});
|
|
184
|
-
const matched = files.filter((f) => matchesGlob(args.pattern, `${prefix}${f.rel}`)).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, MAX_RESULTS);
|
|
185
226
|
if (matched.length === 0) {
|
|
186
227
|
return {
|
|
187
228
|
content: aborted ? `Search interrupted before any files matched pattern: ${args.pattern}` : `No files matched pattern: ${args.pattern}`
|
|
@@ -231,44 +272,13 @@ var grepTool = {
|
|
|
231
272
|
return { content: `Invalid regular expression: ${err.message}`, isError: true };
|
|
232
273
|
}
|
|
233
274
|
const root = path3.resolve(ctx.cwd, args.path ?? ".");
|
|
234
|
-
let files;
|
|
235
275
|
let interrupted = false;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const resolved = await realpath(root).catch(() => null);
|
|
240
|
-
if (isSensitivePath(root) || resolved !== null && isSensitivePath(resolved)) {
|
|
241
|
-
return {
|
|
242
|
-
content: `Refused to grep sensitive file: ${args.path ?? "."}`,
|
|
243
|
-
isError: true
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
files = [{ abs: root, rel: path3.basename(root), mtimeMs: st.mtimeMs }];
|
|
247
|
-
} else {
|
|
248
|
-
const walked = await walkFiles(root, void 0, {
|
|
249
|
-
ignore: createIgnorePredicate(ctx.cwd),
|
|
250
|
-
withMtime: false,
|
|
251
|
-
signal: ctx.abortSignal,
|
|
252
|
-
symlinkBoundary: ctx.cwd,
|
|
253
|
-
nestedIgnore: true
|
|
254
|
-
});
|
|
255
|
-
interrupted = walked.aborted;
|
|
256
|
-
files = walked.files.filter(
|
|
257
|
-
(f) => !isSensitivePath(f.abs) && (f.resolved === void 0 || !isSensitivePath(f.resolved))
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
} catch {
|
|
261
|
-
return { content: `Path not found: ${args.path ?? "."}`, isError: true };
|
|
262
|
-
}
|
|
263
|
-
if (args.glob) {
|
|
264
|
-
const glob = args.glob;
|
|
265
|
-
files = files.filter((f) => matchesGlob(glob, f.rel));
|
|
266
|
-
}
|
|
276
|
+
const out = [];
|
|
277
|
+
let truncated = false;
|
|
278
|
+
let skippedLarge = 0;
|
|
267
279
|
const scanFile = async (file) => {
|
|
268
280
|
const st = await stat2(file.abs).catch(() => null);
|
|
269
|
-
if (!st)
|
|
270
|
-
return null;
|
|
271
|
-
}
|
|
281
|
+
if (!st) return null;
|
|
272
282
|
if (st.size > MAX_FILE_BYTES) {
|
|
273
283
|
skippedLarge += 1;
|
|
274
284
|
return null;
|
|
@@ -279,12 +289,10 @@ var grepTool = {
|
|
|
279
289
|
} catch {
|
|
280
290
|
return null;
|
|
281
291
|
}
|
|
282
|
-
if (buf.includes(0))
|
|
283
|
-
return null;
|
|
284
|
-
}
|
|
292
|
+
if (buf.includes(0)) return null;
|
|
285
293
|
const hits = [];
|
|
286
294
|
const lines = buf.toString("utf8").split("\n");
|
|
287
|
-
for (let i = 0; i < lines.length; i
|
|
295
|
+
for (let i = 0; i < lines.length && hits.length < MAX_MATCHES; i += 1) {
|
|
288
296
|
const line = lines[i] ?? "";
|
|
289
297
|
if (re.test(line)) {
|
|
290
298
|
const shown = line.length > MAX_LINE_CHARS ? `${line.slice(0, MAX_LINE_CHARS)}\u2026` : line;
|
|
@@ -293,29 +301,55 @@ var grepTool = {
|
|
|
293
301
|
}
|
|
294
302
|
return hits;
|
|
295
303
|
};
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
outer: for (let i = 0; i < files.length; i += WALK_CONCURRENCY) {
|
|
300
|
-
if (ctx.abortSignal?.aborted) {
|
|
301
|
-
interrupted = true;
|
|
302
|
-
break;
|
|
303
|
-
}
|
|
304
|
-
const chunk = await Promise.all(
|
|
305
|
-
files.slice(i, i + WALK_CONCURRENCY).map((file) => scanFile(file))
|
|
304
|
+
const scanBatch = async (batch) => {
|
|
305
|
+
const files = batch.filter(
|
|
306
|
+
(f) => !isSensitivePath(f.abs) && (f.resolved === void 0 || !isSensitivePath(f.resolved)) && (!args.glob || matchesGlob(args.glob, f.rel))
|
|
306
307
|
);
|
|
307
|
-
for (
|
|
308
|
-
if (
|
|
309
|
-
|
|
308
|
+
outer: for (let i = 0; i < files.length; i += WALK_CONCURRENCY) {
|
|
309
|
+
if (ctx.abortSignal?.aborted) {
|
|
310
|
+
interrupted = true;
|
|
311
|
+
return false;
|
|
310
312
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
313
|
+
const chunk = await Promise.all(
|
|
314
|
+
files.slice(i, i + WALK_CONCURRENCY).map((file) => scanFile(file))
|
|
315
|
+
);
|
|
316
|
+
for (const hits of chunk) {
|
|
317
|
+
for (const hit of hits ?? []) {
|
|
318
|
+
out.push(hit);
|
|
319
|
+
if (out.length >= MAX_MATCHES) {
|
|
320
|
+
truncated = true;
|
|
321
|
+
break outer;
|
|
322
|
+
}
|
|
316
323
|
}
|
|
317
324
|
}
|
|
318
325
|
}
|
|
326
|
+
return !truncated && !ctx.abortSignal?.aborted;
|
|
327
|
+
};
|
|
328
|
+
try {
|
|
329
|
+
const st = await stat2(root);
|
|
330
|
+
if (st.isFile()) {
|
|
331
|
+
const resolved = await realpath(root).catch(() => null);
|
|
332
|
+
if (isSensitivePath(root) || resolved !== null && isSensitivePath(resolved)) {
|
|
333
|
+
return {
|
|
334
|
+
content: `Refused to grep sensitive file: ${args.path ?? "."}`,
|
|
335
|
+
isError: true
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
await scanBatch([{ abs: root, rel: path3.basename(root), mtimeMs: st.mtimeMs }]);
|
|
339
|
+
} else {
|
|
340
|
+
const walked = await walkFiles(root, void 0, {
|
|
341
|
+
ignore: createIgnorePredicate(ctx.cwd),
|
|
342
|
+
withMtime: false,
|
|
343
|
+
signal: ctx.abortSignal,
|
|
344
|
+
symlinkBoundary: ctx.cwd,
|
|
345
|
+
nestedIgnore: true,
|
|
346
|
+
collect: false,
|
|
347
|
+
onBatch: scanBatch
|
|
348
|
+
});
|
|
349
|
+
interrupted = walked.aborted;
|
|
350
|
+
}
|
|
351
|
+
} catch {
|
|
352
|
+
return { content: `Path not found: ${args.path ?? "."}`, isError: true };
|
|
319
353
|
}
|
|
320
354
|
const skippedNote = skippedLarge > 0 ? `(skipped ${skippedLarge} file(s) larger than 5MB)` : null;
|
|
321
355
|
if (out.length === 0) {
|
|
@@ -578,38 +612,82 @@ var codeOutlineTool = {
|
|
|
578
612
|
// src/tools/fs/read.ts
|
|
579
613
|
import { open, realpath as realpath3, stat as stat4 } from "fs/promises";
|
|
580
614
|
import path5 from "path";
|
|
615
|
+
import { StringDecoder } from "string_decoder";
|
|
581
616
|
import { z as z5 } from "zod";
|
|
582
617
|
var DEFAULT_LIMIT = 2e3;
|
|
583
618
|
var MAX_CHARS = 100 * 1024;
|
|
584
|
-
var MAX_READ_BYTES = 1024 * 1024;
|
|
585
619
|
var BINARY_PROBE_BYTES2 = 8192;
|
|
586
|
-
|
|
587
|
-
const len = buf.length - buf.length % 2;
|
|
588
|
-
const swapped = Buffer.allocUnsafe(len);
|
|
589
|
-
for (let k = 0; k < len; k += 2) {
|
|
590
|
-
swapped[k] = buf[k + 1] ?? 0;
|
|
591
|
-
swapped[k + 1] = buf[k] ?? 0;
|
|
592
|
-
}
|
|
593
|
-
return swapped.toString("utf16le");
|
|
594
|
-
}
|
|
595
|
-
function decodeTextBuffer(buf) {
|
|
596
|
-
if (buf.length >= 2 && buf[0] === 255 && buf[1] === 254) {
|
|
597
|
-
return { text: buf.subarray(2).toString("utf16le") };
|
|
598
|
-
}
|
|
599
|
-
if (buf.length >= 2 && buf[0] === 254 && buf[1] === 255) {
|
|
600
|
-
return { text: decodeUtf16be(buf.subarray(2)) };
|
|
601
|
-
}
|
|
602
|
-
if (buf.subarray(0, BINARY_PROBE_BYTES2).includes(0)) {
|
|
603
|
-
return { binary: true };
|
|
604
|
-
}
|
|
605
|
-
return { text: buf.toString("utf8") };
|
|
606
|
-
}
|
|
620
|
+
var READ_CHUNK_BYTES = 64 * 1024;
|
|
607
621
|
var schema5 = z5.object({
|
|
608
622
|
path: z5.string().describe("File path, absolute or relative to the working directory"),
|
|
609
623
|
offset: z5.number().int().positive().optional().describe("1-based line number to start reading from"),
|
|
610
624
|
limit: z5.number().int().positive().optional().describe("Maximum number of lines to read (default 2000)")
|
|
611
625
|
});
|
|
612
626
|
var READ_CACHE_MAX_ENTRIES = 200;
|
|
627
|
+
async function readWindow(handle, size, start, limit) {
|
|
628
|
+
const probe = Buffer.alloc(Math.min(size, BINARY_PROBE_BYTES2));
|
|
629
|
+
const { bytesRead: probeBytes } = await handle.read(probe, 0, probe.length, 0);
|
|
630
|
+
const head = probe.subarray(0, probeBytes);
|
|
631
|
+
const utf16le = head[0] === 255 && head[1] === 254;
|
|
632
|
+
const utf16be = head[0] === 254 && head[1] === 255;
|
|
633
|
+
if (!utf16le && !utf16be && head.includes(0)) return { binary: true };
|
|
634
|
+
const decoder = new StringDecoder(utf16le || utf16be ? "utf16le" : "utf8");
|
|
635
|
+
const requestedEnd = start - 1 + limit;
|
|
636
|
+
const result = {
|
|
637
|
+
totalLines: 1,
|
|
638
|
+
out: [],
|
|
639
|
+
lastShown: start - 1,
|
|
640
|
+
truncatedBySize: false
|
|
641
|
+
};
|
|
642
|
+
let lineNumber = 1;
|
|
643
|
+
let outputSize = 0;
|
|
644
|
+
let pending = "";
|
|
645
|
+
let beRemainder;
|
|
646
|
+
const showLine = (raw) => {
|
|
647
|
+
if (lineNumber >= start && lineNumber <= requestedEnd && !result.truncatedBySize) {
|
|
648
|
+
const numbered = `${lineNumber} ${raw.replace(/\r$/, "")}`;
|
|
649
|
+
if (outputSize + numbered.length > MAX_CHARS) {
|
|
650
|
+
result.truncatedBySize = true;
|
|
651
|
+
} else {
|
|
652
|
+
result.out.push(numbered);
|
|
653
|
+
outputSize += numbered.length;
|
|
654
|
+
result.lastShown = lineNumber;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
const accept = (text) => {
|
|
659
|
+
const parts = `${pending}${text}`.split("\n");
|
|
660
|
+
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
661
|
+
showLine(parts[i] ?? "");
|
|
662
|
+
lineNumber += 1;
|
|
663
|
+
}
|
|
664
|
+
pending = parts[parts.length - 1] ?? "";
|
|
665
|
+
};
|
|
666
|
+
let position = utf16le || utf16be ? 2 : 0;
|
|
667
|
+
while (position < size) {
|
|
668
|
+
const length = Math.min(READ_CHUNK_BYTES, size - position);
|
|
669
|
+
const { bytesRead, buffer } = await handle.read(Buffer.alloc(length), 0, length, position);
|
|
670
|
+
if (bytesRead === 0) break;
|
|
671
|
+
position += bytesRead;
|
|
672
|
+
let chunk = buffer.subarray(0, bytesRead);
|
|
673
|
+
if (utf16be) {
|
|
674
|
+
const combined = beRemainder === void 0 ? chunk : Buffer.concat([Buffer.from([beRemainder]), chunk]);
|
|
675
|
+
const pairedLength = combined.length - combined.length % 2;
|
|
676
|
+
const swapped = Buffer.allocUnsafe(pairedLength);
|
|
677
|
+
for (let i = 0; i < pairedLength; i += 2) {
|
|
678
|
+
swapped[i] = combined[i + 1] ?? 0;
|
|
679
|
+
swapped[i + 1] = combined[i] ?? 0;
|
|
680
|
+
}
|
|
681
|
+
beRemainder = pairedLength < combined.length ? combined[combined.length - 1] : void 0;
|
|
682
|
+
chunk = swapped;
|
|
683
|
+
}
|
|
684
|
+
accept(decoder.write(chunk));
|
|
685
|
+
}
|
|
686
|
+
accept(decoder.end());
|
|
687
|
+
showLine(pending);
|
|
688
|
+
result.totalLines = lineNumber;
|
|
689
|
+
return result;
|
|
690
|
+
}
|
|
613
691
|
function createReadFileTool() {
|
|
614
692
|
const cache = /* @__PURE__ */ new Map();
|
|
615
693
|
return {
|
|
@@ -643,55 +721,33 @@ function createReadFileTool() {
|
|
|
643
721
|
};
|
|
644
722
|
}
|
|
645
723
|
}
|
|
646
|
-
const bytesToRead = Math.min(st.size, MAX_READ_BYTES);
|
|
647
|
-
const fileCapped = st.size > MAX_READ_BYTES;
|
|
648
724
|
const handle = await open(filePath, "r").catch(() => null);
|
|
649
725
|
if (!handle) {
|
|
650
726
|
return { content: `Failed to read ${args.path}`, isError: true };
|
|
651
727
|
}
|
|
652
|
-
let
|
|
728
|
+
let decoded;
|
|
653
729
|
try {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
bytesToRead,
|
|
658
|
-
0
|
|
659
|
-
);
|
|
660
|
-
buf = buffer.subarray(0, bytesRead);
|
|
730
|
+
decoded = await readWindow(handle, st.size, start, limit);
|
|
731
|
+
} catch {
|
|
732
|
+
return { content: `Failed to read ${args.path}`, isError: true };
|
|
661
733
|
} finally {
|
|
662
734
|
await handle.close().catch(() => {
|
|
663
735
|
});
|
|
664
736
|
}
|
|
665
|
-
const decoded = decodeTextBuffer(buf);
|
|
666
737
|
if ("binary" in decoded) {
|
|
667
738
|
return { content: `Cannot read binary file: ${args.path}`, isError: true };
|
|
668
739
|
}
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
return { content: `(offset ${start} beyond end of file; file has ${totalDesc} lines)` };
|
|
673
|
-
}
|
|
674
|
-
const end = Math.min(lines.length, start - 1 + limit);
|
|
675
|
-
const out = [];
|
|
676
|
-
let size = 0;
|
|
677
|
-
let truncatedBySize = false;
|
|
678
|
-
for (let i = start - 1; i < end; i++) {
|
|
679
|
-
const line = (lines[i] ?? "").replace(/\r$/, "");
|
|
680
|
-
const numbered = `${i + 1} ${line}`;
|
|
681
|
-
if (size + numbered.length > MAX_CHARS) {
|
|
682
|
-
truncatedBySize = true;
|
|
683
|
-
break;
|
|
684
|
-
}
|
|
685
|
-
out.push(numbered);
|
|
686
|
-
size += numbered.length;
|
|
740
|
+
const { totalLines, out, lastShown, truncatedBySize } = decoded;
|
|
741
|
+
if (start > totalLines) {
|
|
742
|
+
return { content: `(offset ${start} beyond end of file; file has ${totalLines} lines)` };
|
|
687
743
|
}
|
|
688
|
-
const
|
|
744
|
+
const end = Math.min(totalLines, start - 1 + limit);
|
|
689
745
|
if (truncatedBySize) {
|
|
690
746
|
out.push(
|
|
691
|
-
`... (truncated: output exceeds 100KB, showing lines ${start}-${lastShown} of ${
|
|
747
|
+
`... (truncated: output exceeds 100KB, showing lines ${start}-${lastShown} of ${totalLines})`
|
|
692
748
|
);
|
|
693
|
-
} else if (end <
|
|
694
|
-
out.push(`... (truncated: showing lines ${start}-${end} of ${
|
|
749
|
+
} else if (end < totalLines) {
|
|
750
|
+
out.push(`... (truncated: showing lines ${start}-${end} of ${totalLines})`);
|
|
695
751
|
}
|
|
696
752
|
if (out.length > 0) {
|
|
697
753
|
if (cache.size >= READ_CACHE_MAX_ENTRIES) cache.clear();
|
|
@@ -699,7 +755,7 @@ function createReadFileTool() {
|
|
|
699
755
|
cache.set(cacheKey, {
|
|
700
756
|
mtimeMs: st.mtimeMs,
|
|
701
757
|
size: st.size,
|
|
702
|
-
totalLines
|
|
758
|
+
totalLines,
|
|
703
759
|
// Extend the remembered range when the new read overlaps or abuts
|
|
704
760
|
// the previous one; disjoint ranges replace it.
|
|
705
761
|
start: prev && start <= prev.end + 1 ? Math.min(prev.start, start) : start,
|
|
@@ -811,6 +867,10 @@ var writeFileTool = {
|
|
|
811
867
|
parameters: schema7,
|
|
812
868
|
async execute(args, ctx) {
|
|
813
869
|
const filePath = path7.resolve(ctx.cwd, args.path);
|
|
870
|
+
const resolvedPath = await realpathForCheck(filePath);
|
|
871
|
+
if (isSensitivePath(filePath) || resolvedPath !== null && isSensitivePath(resolvedPath)) {
|
|
872
|
+
return { content: `Refused to write sensitive file: ${args.path}`, isError: true };
|
|
873
|
+
}
|
|
814
874
|
if (args.content.includes("\0")) {
|
|
815
875
|
return {
|
|
816
876
|
content: `Refused to write ${args.path}: content contains NUL bytes (write_file is text-only)`,
|
|
@@ -825,6 +885,13 @@ var writeFileTool = {
|
|
|
825
885
|
};
|
|
826
886
|
}
|
|
827
887
|
const snapshot = await captureSnapshot(filePath, "write_file", ctx.snapshotContext);
|
|
888
|
+
if (snapshot.existed && snapshot.content === null) {
|
|
889
|
+
const reason = snapshot.contentTooLarge ? "existing content is over the 5MB snapshot limit" : "existing content could not be read";
|
|
890
|
+
return {
|
|
891
|
+
content: `Refused to overwrite ${args.path}: ${reason}, and no restorable snapshot is available`,
|
|
892
|
+
isError: true
|
|
893
|
+
};
|
|
894
|
+
}
|
|
828
895
|
try {
|
|
829
896
|
await mkdir(path7.dirname(filePath), { recursive: true });
|
|
830
897
|
await writeFile2(filePath, args.content, "utf8");
|
|
@@ -1146,6 +1213,7 @@ function debugStreamLog(event, data) {
|
|
|
1146
1213
|
|
|
1147
1214
|
// src/tools/web/search.ts
|
|
1148
1215
|
var TIMEOUT_MS = 15e3;
|
|
1216
|
+
var MAX_BODY_BYTES = 2 * 1024 * 1024;
|
|
1149
1217
|
var DEFAULT_MAX_RESULTS = 5;
|
|
1150
1218
|
var MAX_RESULTS_LIMIT = 10;
|
|
1151
1219
|
var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
|
@@ -1268,6 +1336,26 @@ var SEARCH_SOURCES = [
|
|
|
1268
1336
|
parse: parseBingResults
|
|
1269
1337
|
}
|
|
1270
1338
|
];
|
|
1339
|
+
async function readLimitedBody(res) {
|
|
1340
|
+
if (!res.body) return "";
|
|
1341
|
+
const reader = res.body.getReader();
|
|
1342
|
+
const decoder = new TextDecoder();
|
|
1343
|
+
let bytes = 0;
|
|
1344
|
+
let text = "";
|
|
1345
|
+
try {
|
|
1346
|
+
while (bytes < MAX_BODY_BYTES) {
|
|
1347
|
+
const { done, value } = await reader.read();
|
|
1348
|
+
if (done) break;
|
|
1349
|
+
bytes += value.byteLength;
|
|
1350
|
+
text += decoder.decode(value, { stream: true });
|
|
1351
|
+
}
|
|
1352
|
+
text += decoder.decode();
|
|
1353
|
+
} finally {
|
|
1354
|
+
await reader.cancel().catch(() => {
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
return text;
|
|
1358
|
+
}
|
|
1271
1359
|
var webSearchTool = {
|
|
1272
1360
|
name: "web_search",
|
|
1273
1361
|
description: "Search the web and return a numbered list of results with title, URL and snippet. Uses DuckDuckGo with automatic fallback to DuckDuckGo Lite and Bing when a source fails or returns nothing. No API key required. Use web_fetch to read a result's full page.",
|
|
@@ -1307,7 +1395,7 @@ var webSearchTool = {
|
|
|
1307
1395
|
}
|
|
1308
1396
|
let html;
|
|
1309
1397
|
try {
|
|
1310
|
-
html = await res
|
|
1398
|
+
html = await readLimitedBody(res);
|
|
1311
1399
|
} catch (err) {
|
|
1312
1400
|
const message = err instanceof Error ? err.message : String(err);
|
|
1313
1401
|
failures.push(`${source.name}: failed to read search response: ${message}`);
|
|
@@ -1415,4 +1503,4 @@ export {
|
|
|
1415
1503
|
ToolRegistry,
|
|
1416
1504
|
createDefaultRegistry
|
|
1417
1505
|
};
|
|
1418
|
-
//# sourceMappingURL=chunk-
|
|
1506
|
+
//# sourceMappingURL=chunk-54KH4JSO.js.map
|