@zqagent/core 0.1.30 → 0.2.5

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.
Files changed (86) hide show
  1. package/CONTRIBUTING.md +65 -0
  2. package/LICENSE +190 -0
  3. package/README.md +102 -146
  4. package/README.oss.en.md +161 -0
  5. package/README.oss.md +165 -0
  6. package/README.oss.zh-TW.md +165 -0
  7. package/SECURITY.md +51 -0
  8. package/dist/{ToolRegistry-BmvPxgMr.d.cts → ToolRegistry-C3pTFiaJ.d.cts} +13 -4
  9. package/dist/{ToolRegistry-BmvPxgMr.d.ts → ToolRegistry-C3pTFiaJ.d.ts} +13 -4
  10. package/dist/cli/index.cjs +17799 -2062
  11. package/dist/cli/index.cjs.map +1 -1
  12. package/dist/cli/index.js +16729 -985
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/{explore-OI6htHN1.d.cts → explore-D4lCKXbA.d.ts} +160 -19
  15. package/dist/{explore-Pmhyk4gv.d.ts → explore-Dj1j_GyN.d.cts} +160 -19
  16. package/dist/index.cjs +3626 -453
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +337 -27
  19. package/dist/index.d.ts +337 -27
  20. package/dist/index.js +3617 -452
  21. package/dist/index.js.map +1 -1
  22. package/dist/server/index.cjs +14029 -1482
  23. package/dist/server/index.cjs.map +1 -1
  24. package/dist/server/index.js +14036 -1482
  25. package/dist/server/index.js.map +1 -1
  26. package/dist/tools/ask.cjs +14 -0
  27. package/dist/tools/ask.cjs.map +1 -1
  28. package/dist/tools/ask.d.cts +8 -2
  29. package/dist/tools/ask.d.ts +8 -2
  30. package/dist/tools/ask.js +13 -0
  31. package/dist/tools/ask.js.map +1 -1
  32. package/dist/tools/diff.cjs +59 -30
  33. package/dist/tools/diff.cjs.map +1 -1
  34. package/dist/tools/diff.d.cts +1 -1
  35. package/dist/tools/diff.d.ts +1 -1
  36. package/dist/tools/diff.js +59 -30
  37. package/dist/tools/diff.js.map +1 -1
  38. package/dist/tools/directories.d.cts +1 -1
  39. package/dist/tools/directories.d.ts +1 -1
  40. package/dist/tools/explore.cjs +1050 -309
  41. package/dist/tools/explore.cjs.map +1 -1
  42. package/dist/tools/explore.d.cts +2 -2
  43. package/dist/tools/explore.d.ts +2 -2
  44. package/dist/tools/explore.js +1057 -309
  45. package/dist/tools/explore.js.map +1 -1
  46. package/dist/tools/files.cjs +58 -43
  47. package/dist/tools/files.cjs.map +1 -1
  48. package/dist/tools/files.d.cts +10 -4
  49. package/dist/tools/files.d.ts +10 -4
  50. package/dist/tools/files.js +56 -42
  51. package/dist/tools/files.js.map +1 -1
  52. package/dist/tools/git.cjs +816 -63
  53. package/dist/tools/git.cjs.map +1 -1
  54. package/dist/tools/git.d.cts +200 -2
  55. package/dist/tools/git.d.ts +200 -2
  56. package/dist/tools/git.js +788 -62
  57. package/dist/tools/git.js.map +1 -1
  58. package/dist/tools/media.cjs +1191 -0
  59. package/dist/tools/media.cjs.map +1 -0
  60. package/dist/tools/media.d.cts +31 -0
  61. package/dist/tools/media.d.ts +31 -0
  62. package/dist/tools/media.js +1159 -0
  63. package/dist/tools/media.js.map +1 -0
  64. package/dist/tools/plan.d.cts +1 -1
  65. package/dist/tools/plan.d.ts +1 -1
  66. package/dist/tools/search.cjs +513 -33
  67. package/dist/tools/search.cjs.map +1 -1
  68. package/dist/tools/search.d.cts +12 -1
  69. package/dist/tools/search.d.ts +12 -1
  70. package/dist/tools/search.js +508 -33
  71. package/dist/tools/search.js.map +1 -1
  72. package/dist/tools/shell.cjs +631 -41
  73. package/dist/tools/shell.cjs.map +1 -1
  74. package/dist/tools/shell.d.cts +37 -4
  75. package/dist/tools/shell.d.ts +37 -4
  76. package/dist/tools/shell.js +626 -40
  77. package/dist/tools/shell.js.map +1 -1
  78. package/dist/tools/todo.d.cts +1 -1
  79. package/dist/tools/todo.d.ts +1 -1
  80. package/dist/tools/web.cjs +51 -18
  81. package/dist/tools/web.cjs.map +1 -1
  82. package/dist/tools/web.d.cts +1 -1
  83. package/dist/tools/web.d.ts +1 -1
  84. package/dist/tools/web.js +51 -18
  85. package/dist/tools/web.js.map +1 -1
  86. package/package.json +43 -8
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/tools/search.ts
@@ -23,46 +33,516 @@ __export(search_exports, {
23
33
  registerSearchTool: () => registerSearchTool
24
34
  });
25
35
  module.exports = __toCommonJS(search_exports);
26
- var import_child_process = require("child_process");
36
+
37
+ // src/tools/execAsync.ts
38
+ var import_node_child_process2 = require("child_process");
39
+
40
+ // src/tools/killTree.ts
41
+ var import_node_child_process = require("child_process");
42
+ function killTree(pid) {
43
+ if (!pid) return;
44
+ if (process.platform === "win32") {
45
+ try {
46
+ (0, import_node_child_process.spawnSync)("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true });
47
+ } catch {
48
+ }
49
+ return;
50
+ }
51
+ try {
52
+ process.kill(-pid, "SIGKILL");
53
+ } catch {
54
+ try {
55
+ process.kill(pid, "SIGKILL");
56
+ } catch {
57
+ }
58
+ }
59
+ }
60
+
61
+ // src/tools/execAsync.ts
62
+ var liveChildren = /* @__PURE__ */ new Set();
63
+ var exitCleanupInstalled = false;
64
+ function trackChild(pid) {
65
+ if (!pid) return;
66
+ liveChildren.add(pid);
67
+ if (exitCleanupInstalled) return;
68
+ exitCleanupInstalled = true;
69
+ process.once("exit", () => {
70
+ for (const p of liveChildren) killTree(p);
71
+ });
72
+ }
73
+ var DEFAULT_MAX_BUFFER = 1024 * 1024 * 5;
74
+ var KILL_GRACE_MS = 1e3;
75
+ function makeSink(maxBuffer) {
76
+ const chunks = [];
77
+ let len = 0;
78
+ let over = false;
79
+ return {
80
+ push(chunk) {
81
+ if (len >= maxBuffer) {
82
+ over = true;
83
+ return;
84
+ }
85
+ const room = maxBuffer - len;
86
+ if (chunk.length > room) {
87
+ chunks.push(chunk.subarray(0, room));
88
+ len = maxBuffer;
89
+ over = true;
90
+ return;
91
+ }
92
+ chunks.push(chunk);
93
+ len += chunk.length;
94
+ },
95
+ done: () => Buffer.concat(chunks),
96
+ over: () => over
97
+ };
98
+ }
99
+ function execAsync(file, argv, opts = {}) {
100
+ return new Promise((resolve2) => {
101
+ const maxBuffer = opts.maxBuffer ?? DEFAULT_MAX_BUFFER;
102
+ const out = makeSink(maxBuffer);
103
+ const err = makeSink(maxBuffer);
104
+ if (opts.signal?.aborted) {
105
+ resolve2({
106
+ status: null,
107
+ stdout: Buffer.alloc(0),
108
+ stderr: Buffer.alloc(0),
109
+ timedOut: false,
110
+ truncated: false,
111
+ aborted: true
112
+ });
113
+ return;
114
+ }
115
+ let child;
116
+ try {
117
+ child = (0, import_node_child_process2.spawn)(file, argv, {
118
+ ...opts.cwd !== void 0 ? { cwd: opts.cwd } : {},
119
+ ...opts.env !== void 0 ? { env: opts.env } : {},
120
+ // 自成进程组(POSIX):超时/中断时用 killTree 一次收掉整棵进程树
121
+ detached: process.platform !== "win32",
122
+ stdio: [opts.input === void 0 ? "ignore" : "pipe", "pipe", "pipe"],
123
+ windowsHide: true
124
+ });
125
+ } catch (e) {
126
+ resolve2({
127
+ status: null,
128
+ stdout: Buffer.alloc(0),
129
+ stderr: Buffer.alloc(0),
130
+ error: e,
131
+ timedOut: false,
132
+ truncated: false
133
+ });
134
+ return;
135
+ }
136
+ trackChild(child.pid);
137
+ let settled = false;
138
+ let timedOut = false;
139
+ let aborted = false;
140
+ let spawnError;
141
+ let timer = null;
142
+ let graceTimer = null;
143
+ const onAbort = () => {
144
+ aborted = true;
145
+ killAndSettle();
146
+ };
147
+ const finish = (status) => {
148
+ if (settled) return;
149
+ settled = true;
150
+ if (timer) clearTimeout(timer);
151
+ if (graceTimer) clearTimeout(graceTimer);
152
+ if (child.pid !== void 0) liveChildren.delete(child.pid);
153
+ opts.signal?.removeEventListener("abort", onAbort);
154
+ const result = {
155
+ status,
156
+ stdout: out.done(),
157
+ stderr: err.done(),
158
+ timedOut,
159
+ truncated: out.over() || err.over()
160
+ };
161
+ if (aborted) result.aborted = true;
162
+ if (spawnError) result.error = spawnError;
163
+ resolve2(result);
164
+ };
165
+ const killAndSettle = () => {
166
+ killTree(child.pid);
167
+ if (settled || graceTimer) return;
168
+ graceTimer = setTimeout(() => {
169
+ child.stdout?.destroy();
170
+ child.stderr?.destroy();
171
+ finish(null);
172
+ }, KILL_GRACE_MS);
173
+ };
174
+ if (opts.timeoutMs !== void 0 && opts.timeoutMs > 0) {
175
+ timer = setTimeout(() => {
176
+ timedOut = true;
177
+ killAndSettle();
178
+ }, opts.timeoutMs);
179
+ }
180
+ opts.signal?.addEventListener("abort", onAbort, { once: true });
181
+ child.stdout?.on("data", (c) => out.push(c));
182
+ child.stderr?.on("data", (c) => err.push(c));
183
+ child.on("error", (e) => {
184
+ spawnError = e;
185
+ if (child.pid === void 0) finish(null);
186
+ });
187
+ if (opts.input !== void 0 && child.stdin) {
188
+ child.stdin.on("error", () => {
189
+ });
190
+ child.stdin.end(opts.input);
191
+ }
192
+ child.on("close", (code) => finish(code));
193
+ });
194
+ }
195
+
196
+ // src/tools/shellRuntime.ts
197
+ var path = __toESM(require("path"), 1);
198
+ var WINDOWS_SUFFIXES = [".exe", ".cmd", ".bat", ".com", ""];
199
+ function pathApi(platform) {
200
+ return platform === "win32" ? path.win32 : path.posix;
201
+ }
202
+ function joinPath(platform, ...parts) {
203
+ return pathApi(platform).join(...parts);
204
+ }
205
+ function findExecutable(name, ctx) {
206
+ if (name.includes("/") || name.includes("\\")) {
207
+ return ctx.fileExists(name) ? name : null;
208
+ }
209
+ const suffixes = ctx.platform === "win32" ? WINDOWS_SUFFIXES : [""];
210
+ const delimiter = ctx.platform === "win32" ? ";" : ":";
211
+ const dirs = (ctx.env.PATH ?? "").split(delimiter).filter((d) => d.length > 0);
212
+ for (const dir of dirs) {
213
+ for (const suffix of suffixes) {
214
+ const candidate = joinPath(ctx.platform, dir, name + suffix);
215
+ if (ctx.fileExists(candidate)) return candidate;
216
+ }
217
+ }
218
+ return null;
219
+ }
220
+
221
+ // src/tools/shellBackground.ts
222
+ var import_node_child_process3 = require("child_process");
223
+ var DEFAULT_BUFFER_LIMIT = 64 * 1024;
224
+ var RingBuffer = class {
225
+ constructor(limit = DEFAULT_BUFFER_LIMIT) {
226
+ this.limit = limit;
227
+ }
228
+ limit;
229
+ chunks = [];
230
+ size = 0;
231
+ add(chunk) {
232
+ if (!chunk) return;
233
+ this.chunks.push(chunk);
234
+ this.size += chunk.length;
235
+ while (this.size > this.limit && this.chunks.length > 1) {
236
+ this.size -= this.chunks.shift().length;
237
+ }
238
+ if (this.size > this.limit) {
239
+ const joined = this.chunks.join("");
240
+ this.chunks = [joined.slice(-this.limit)];
241
+ this.size = this.chunks[0].length;
242
+ }
243
+ }
244
+ get() {
245
+ const joined = this.chunks.join("");
246
+ return joined.length > this.limit ? joined.slice(-this.limit) : joined;
247
+ }
248
+ };
249
+ var ShellTaskManager = class {
250
+ tasks = /* @__PURE__ */ new Map();
251
+ seq = 0;
252
+ cleanupInstalled = false;
253
+ /** 启动一个后台任务,立即返回快照(不等退出) */
254
+ start(opts) {
255
+ this.installCleanup();
256
+ const id = `shell-${++this.seq}-${Date.now().toString(36)}`;
257
+ const child = (0, import_node_child_process3.spawn)(opts.file, opts.argv, {
258
+ cwd: opts.cwd,
259
+ env: opts.env,
260
+ detached: process.platform !== "win32",
261
+ windowsHide: true,
262
+ stdio: ["ignore", "pipe", "pipe"]
263
+ });
264
+ const task = {
265
+ id,
266
+ command: opts.command,
267
+ scope: opts.scope ?? null,
268
+ child,
269
+ stdout: new RingBuffer(),
270
+ stderr: new RingBuffer(),
271
+ status: "running",
272
+ exitCode: null,
273
+ signal: null,
274
+ startedAt: Date.now(),
275
+ endedAt: null
276
+ };
277
+ this.tasks.set(id, task);
278
+ child.stdout?.on("data", (d) => task.stdout.add(d.toString("utf-8")));
279
+ child.stderr?.on("data", (d) => task.stderr.add(d.toString("utf-8")));
280
+ child.on("error", (err) => task.stderr.add(`[spawn error] ${err.message}
281
+ `));
282
+ child.on("exit", (code, signal) => {
283
+ task.exitCode = code;
284
+ task.signal = signal;
285
+ task.endedAt = Date.now();
286
+ if (task.status !== "killed") task.status = "exited";
287
+ });
288
+ child.unref();
289
+ return this.snapshot(task);
290
+ }
291
+ /** 读取任务输出与状态 */
292
+ output(taskId) {
293
+ const task = this.tasks.get(taskId);
294
+ if (!task) return { error: `\u672A\u77E5\u7684\u540E\u53F0\u4EFB\u52A1: ${taskId}` };
295
+ return this.snapshot(task);
296
+ }
297
+ /** 终止任务(进程树)并返回快照;传 scope 时只允许终止该归属下的任务 */
298
+ kill(taskId, scope) {
299
+ const task = this.tasks.get(taskId);
300
+ if (!task) return { error: `\u672A\u77E5\u7684\u540E\u53F0\u4EFB\u52A1: ${taskId}` };
301
+ if (scope !== void 0 && task.scope !== scope) {
302
+ return { error: `\u672A\u77E5\u7684\u540E\u53F0\u4EFB\u52A1: ${taskId}` };
303
+ }
304
+ if (task.status === "running") {
305
+ task.status = "killed";
306
+ task.endedAt = Date.now();
307
+ killTree(task.child.pid);
308
+ }
309
+ return this.snapshot(task);
310
+ }
311
+ /** 列出任务快照(调试/展示用);传 scope 时只返回该归属下的任务 */
312
+ list(scope) {
313
+ const all = [...this.tasks.values()];
314
+ return (scope === void 0 ? all : all.filter((t) => t.scope === scope)).map(
315
+ (t) => this.snapshot(t)
316
+ );
317
+ }
318
+ /** 终止全部任务(进程退出时调用) */
319
+ disposeAll() {
320
+ for (const task of this.tasks.values()) {
321
+ if (task.status === "running") {
322
+ task.status = "killed";
323
+ killTree(task.child.pid);
324
+ }
325
+ }
326
+ }
327
+ snapshot(task) {
328
+ return {
329
+ task_id: task.id,
330
+ command: task.command,
331
+ status: task.status,
332
+ running: task.status === "running",
333
+ exit_code: task.exitCode,
334
+ signal: task.signal,
335
+ stdout: task.stdout.get(),
336
+ stderr: task.stderr.get(),
337
+ duration_ms: (task.endedAt ?? Date.now()) - task.startedAt
338
+ };
339
+ }
340
+ installCleanup() {
341
+ if (this.cleanupInstalled) return;
342
+ this.cleanupInstalled = true;
343
+ process.once("exit", () => this.disposeAll());
344
+ }
345
+ };
346
+
347
+ // src/tools/shell.ts
348
+ var MAX_BUFFER = 1024 * 1024 * 5;
349
+ function decodeOutput(buf) {
350
+ if (!buf || buf.length === 0) return "";
351
+ try {
352
+ return new TextDecoder("utf-8", { fatal: true }).decode(buf);
353
+ } catch {
354
+ for (const enc of ["gbk", "big5", "shift_jis", "latin1"]) {
355
+ try {
356
+ return new TextDecoder(enc).decode(buf);
357
+ } catch {
358
+ }
359
+ }
360
+ return buf.toString("latin1");
361
+ }
362
+ }
363
+ var shellTasks = new ShellTaskManager();
364
+
365
+ // src/tools/nodeGrep.ts
366
+ var fs = __toESM(require("fs"), 1);
367
+ var path2 = __toESM(require("path"), 1);
368
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
369
+ ".git",
370
+ "node_modules",
371
+ ".svn",
372
+ ".hg",
373
+ "dist",
374
+ "build",
375
+ ".next",
376
+ ".cache"
377
+ ]);
378
+ function looksBinary(buf) {
379
+ const n = Math.min(buf.length, 8192);
380
+ for (let i = 0; i < n; i++) if (buf[i] === 0) return true;
381
+ return false;
382
+ }
383
+ function nodeGrep(opts) {
384
+ const maxMatches = opts.maxMatches ?? 50;
385
+ const maxFileBytes = opts.maxFileBytes ?? 1024 * 1024;
386
+ let regex;
387
+ try {
388
+ regex = new RegExp(opts.pattern);
389
+ } catch (err) {
390
+ throw new Error(`\u65E0\u6548\u7684\u6B63\u5219\u8868\u8FBE\u5F0F: ${opts.pattern}\uFF08${String(err)}\uFF09`);
391
+ }
392
+ const target = path2.resolve(opts.workspace, opts.target);
393
+ if (!fs.existsSync(target)) {
394
+ throw new Error(`\u641C\u7D22\u8DEF\u5F84\u4E0D\u5B58\u5728: ${opts.target}`);
395
+ }
396
+ const matches = [];
397
+ const scanFile = (file) => {
398
+ let stat2;
399
+ try {
400
+ stat2 = fs.statSync(file);
401
+ } catch {
402
+ return false;
403
+ }
404
+ if (!stat2.isFile() || stat2.size > maxFileBytes) return false;
405
+ let buf;
406
+ try {
407
+ buf = fs.readFileSync(file);
408
+ } catch {
409
+ return false;
410
+ }
411
+ if (looksBinary(buf)) return false;
412
+ const rel = path2.relative(opts.workspace, file) || path2.basename(file);
413
+ const lines = buf.toString("utf-8").split("\n");
414
+ for (let i = 0; i < lines.length; i++) {
415
+ const raw = lines[i].replace(/\r$/, "");
416
+ if (regex.test(raw)) {
417
+ matches.push({ file: rel, line: i + 1, text: raw.trim().slice(0, 200) });
418
+ if (matches.length >= maxMatches) return true;
419
+ }
420
+ }
421
+ return false;
422
+ };
423
+ const stat = fs.statSync(target);
424
+ if (stat.isFile()) {
425
+ scanFile(target);
426
+ return matches;
427
+ }
428
+ const stack = [target];
429
+ while (stack.length > 0) {
430
+ const dir = stack.pop();
431
+ let entries;
432
+ try {
433
+ entries = fs.readdirSync(dir, { withFileTypes: true });
434
+ } catch {
435
+ continue;
436
+ }
437
+ for (const entry of entries) {
438
+ const full = path2.join(dir, entry.name);
439
+ if (entry.isDirectory()) {
440
+ if (!SKIP_DIRS.has(entry.name)) stack.push(full);
441
+ } else if (entry.isFile()) {
442
+ if (scanFile(full)) return matches;
443
+ }
444
+ }
445
+ }
446
+ return matches;
447
+ }
448
+
449
+ // src/tools/ripgrep.ts
450
+ var fs2 = __toESM(require("fs"), 1);
451
+ var os = __toESM(require("os"), 1);
452
+ var path3 = __toESM(require("path"), 1);
453
+ var import_node_module = require("module");
454
+ var cached;
455
+ function extractBundledRipgrep() {
456
+ try {
457
+ const req = typeof require === "function" ? require : (0, import_node_module.createRequire)(path3.join(process.cwd(), "__zqagent__.js"));
458
+ const sea = req("node:sea");
459
+ if (!sea?.isSea?.() || typeof sea.getAsset !== "function") return null;
460
+ const asset = sea.getAsset("rg");
461
+ if (!asset) return null;
462
+ const dir = fs2.mkdtempSync(path3.join(os.tmpdir(), "zqagent-rg-"));
463
+ const file = path3.join(dir, process.platform === "win32" ? "rg.exe" : "rg");
464
+ fs2.writeFileSync(file, Buffer.from(asset));
465
+ fs2.chmodSync(file, 493);
466
+ return file;
467
+ } catch {
468
+ return null;
469
+ }
470
+ }
471
+ function resolveRipgrep() {
472
+ if (cached !== void 0) return cached;
473
+ const explicit = (process.env.ZQAGENT_RG ?? "").trim();
474
+ if (explicit && fs2.existsSync(explicit)) {
475
+ cached = explicit;
476
+ return cached;
477
+ }
478
+ const bundled = extractBundledRipgrep();
479
+ if (bundled) {
480
+ cached = bundled;
481
+ return cached;
482
+ }
483
+ cached = findExecutable("rg", {
484
+ platform: process.platform,
485
+ env: process.env,
486
+ fileExists: (p) => {
487
+ try {
488
+ return fs2.statSync(p).isFile();
489
+ } catch {
490
+ return false;
491
+ }
492
+ }
493
+ });
494
+ return cached;
495
+ }
496
+
497
+ // src/tools/search.ts
498
+ function parseRgOutput(result) {
499
+ return result.split("\n").map((l) => l.replace(/\r$/, "")).filter((l) => l.length > 0).slice(0, 50).map((line) => {
500
+ const m = /^(.+?):(\d+):(.*)$/.exec(line);
501
+ if (!m) return { file: line.trim(), line: 0, text: "" };
502
+ return { file: m[1], line: Number(m[2]), text: m[3].trim().slice(0, 200) };
503
+ });
504
+ }
27
505
  function makeGrep(workspace) {
28
- return function grep(args) {
506
+ return async function grep(args) {
29
507
  const pattern = String(args.pattern ?? "");
30
508
  const filePath = String(args.path ?? ".");
31
509
  if (!pattern.trim()) {
32
510
  return { error: "\u641C\u7D22\u6A21\u5F0F\u4E0D\u80FD\u4E3A\u7A7A" };
33
511
  }
34
- const execOpts = {
35
- cwd: workspace,
36
- encoding: "utf-8",
37
- timeout: 1e4,
38
- maxBuffer: 1024 * 1024 * 2
39
- };
40
- try {
41
- let result;
42
- try {
43
- result = (0, import_child_process.execFileSync)(
44
- "rg",
45
- ["--line-number", "--no-heading", "--max-count", "50", pattern, filePath],
46
- execOpts
47
- ).toString();
48
- } catch {
49
- result = (0, import_child_process.execFileSync)(
50
- "grep",
51
- ["-rn", "--max-count", "50", pattern, filePath],
52
- execOpts
53
- ).toString();
512
+ const rg = resolveRipgrep();
513
+ if (rg) {
514
+ const res = await execAsync(
515
+ rg,
516
+ [
517
+ // --with-filename:单文件模式下 rg 默认不输出文件名前缀,会导致
518
+ // "行号:内容" 被误当成 "文件:行号:内容" 解析而错位;显式要求带文件名。
519
+ "--with-filename",
520
+ "--line-number",
521
+ "--no-heading",
522
+ "--max-count",
523
+ "50",
524
+ pattern,
525
+ filePath
526
+ ],
527
+ {
528
+ cwd: workspace,
529
+ timeoutMs: 1e4,
530
+ maxBuffer: 1024 * 1024 * 2
531
+ }
532
+ );
533
+ if (res.status === 0) {
534
+ const matches = parseRgOutput(decodeOutput(res.stdout));
535
+ return { query: pattern, engine: "ripgrep", matches, count: matches.length };
54
536
  }
55
- const lines = result.trim().split("\n").slice(0, 50);
56
- const matches = lines.map((line) => {
57
- const [file, lineno, ...rest] = line.split(":");
58
- return { file, line: Number(lineno), text: rest.join(":").trim().slice(0, 200) };
59
- });
60
- return { query: pattern, matches, count: matches.length };
61
- } catch (exc) {
62
- if (exc instanceof Error && "status" in exc && exc.status === 1) {
63
- return { query: pattern, matches: [], count: 0 };
537
+ if (res.status === 1) {
538
+ return { query: pattern, engine: "ripgrep", matches: [], count: 0 };
64
539
  }
65
- return { error: `\u641C\u7D22\u5931\u8D25: ${String(exc)}` };
540
+ }
541
+ try {
542
+ const matches = nodeGrep({ pattern, target: filePath, workspace });
543
+ return { query: pattern, engine: "node", matches, count: matches.length };
544
+ } catch (exc) {
545
+ return { error: `\u641C\u7D22\u5931\u8D25: ${String(exc instanceof Error ? exc.message : exc)}` };
66
546
  }
67
547
  };
68
548
  }
@@ -75,7 +555,7 @@ function registerSearchTool(registry, workspace) {
75
555
  type: "function",
76
556
  function: {
77
557
  name: "grep",
78
- description: "\u5728\u6587\u4EF6\u4E2D\u641C\u7D22\u6587\u672C\u6A21\u5F0F\u3002\u8FD4\u56DE\u5339\u914D\u7684\u6587\u4EF6\u540D\u3001\u884C\u53F7\u548C\u5185\u5BB9\u3002\u4F18\u5148\u4F7F\u7528 ripgrep (rg) \u4EE5\u83B7\u5F97\u66F4\u597D\u7684\u6027\u80FD\u3002",
558
+ description: "\u5728\u6587\u4EF6\u4E2D\u641C\u7D22\u6587\u672C\u6A21\u5F0F\uFF08\u652F\u6301\u5B8C\u6574 JS \u6B63\u5219\u8BED\u6CD5\uFF09\u3002\u8FD4\u56DE\u5339\u914D\u7684\u6587\u4EF6\u540D\u3001\u884C\u53F7\u548C\u5185\u5BB9\uFF0C\u6700\u591A 50 \u6761\u3002\u4F18\u5148\u4F7F\u7528 ripgrep (rg) \u4EE5\u83B7\u5F97\u66F4\u597D\u7684\u6027\u80FD\uFF1Brg \u4E0D\u53EF\u7528\u65F6\u81EA\u52A8\u56DE\u9000\u5230\u5185\u7F6E\u626B\u63CF\u5668\u3002",
79
559
  parameters: {
80
560
  type: "object",
81
561
  properties: {