@kolisachint/hoocode-agent 0.4.74 → 0.4.75

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 (106) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/cli/args.d.ts +0 -2
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +0 -11
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-frontmatter.d.ts.map +1 -1
  7. package/dist/core/agent-frontmatter.js +2 -2
  8. package/dist/core/agent-frontmatter.js.map +1 -1
  9. package/dist/core/agent-session-services.d.ts +0 -1
  10. package/dist/core/agent-session-services.d.ts.map +1 -1
  11. package/dist/core/agent-session-services.js +0 -1
  12. package/dist/core/agent-session-services.js.map +1 -1
  13. package/dist/core/agent-session.d.ts +1 -17
  14. package/dist/core/agent-session.d.ts.map +1 -1
  15. package/dist/core/agent-session.js +3 -198
  16. package/dist/core/agent-session.js.map +1 -1
  17. package/dist/core/bash-executor.d.ts.map +1 -1
  18. package/dist/core/bash-executor.js +7 -4
  19. package/dist/core/bash-executor.js.map +1 -1
  20. package/dist/core/compaction/utils.d.ts.map +1 -1
  21. package/dist/core/compaction/utils.js +8 -5
  22. package/dist/core/compaction/utils.js.map +1 -1
  23. package/dist/core/model-categories.d.ts +28 -0
  24. package/dist/core/model-categories.d.ts.map +1 -0
  25. package/dist/core/model-categories.js +51 -0
  26. package/dist/core/model-categories.js.map +1 -0
  27. package/dist/core/model-registry.d.ts +0 -6
  28. package/dist/core/model-registry.d.ts.map +1 -1
  29. package/dist/core/model-registry.js +0 -32
  30. package/dist/core/model-registry.js.map +1 -1
  31. package/dist/core/sdk.d.ts +0 -6
  32. package/dist/core/sdk.d.ts.map +1 -1
  33. package/dist/core/sdk.js +0 -1
  34. package/dist/core/sdk.js.map +1 -1
  35. package/dist/core/settings-manager.d.ts +14 -0
  36. package/dist/core/settings-manager.d.ts.map +1 -1
  37. package/dist/core/settings-manager.js.map +1 -1
  38. package/dist/core/subagent-pool-instance.d.ts.map +1 -1
  39. package/dist/core/subagent-pool-instance.js +9 -1
  40. package/dist/core/subagent-pool-instance.js.map +1 -1
  41. package/dist/core/subagent-pool.d.ts +5 -0
  42. package/dist/core/subagent-pool.d.ts.map +1 -1
  43. package/dist/core/subagent-pool.js +7 -1
  44. package/dist/core/subagent-pool.js.map +1 -1
  45. package/dist/core/tools/edit-diff.d.ts +3 -0
  46. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  47. package/dist/core/tools/edit-diff.js +35 -3
  48. package/dist/core/tools/edit-diff.js.map +1 -1
  49. package/dist/core/tools/edit.d.ts.map +1 -1
  50. package/dist/core/tools/edit.js +7 -1
  51. package/dist/core/tools/edit.js.map +1 -1
  52. package/dist/core/tools/glob.d.ts +42 -0
  53. package/dist/core/tools/glob.d.ts.map +1 -0
  54. package/dist/core/tools/glob.js +448 -0
  55. package/dist/core/tools/glob.js.map +1 -0
  56. package/dist/core/tools/grep.d.ts.map +1 -1
  57. package/dist/core/tools/grep.js +4 -1
  58. package/dist/core/tools/grep.js.map +1 -1
  59. package/dist/core/tools/index.d.ts +4 -1
  60. package/dist/core/tools/index.d.ts.map +1 -1
  61. package/dist/core/tools/index.js +13 -1
  62. package/dist/core/tools/index.js.map +1 -1
  63. package/dist/core/tools/output-accumulator.d.ts +3 -0
  64. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  65. package/dist/core/tools/output-accumulator.js +8 -1
  66. package/dist/core/tools/output-accumulator.js.map +1 -1
  67. package/dist/core/tools/output-compression.d.ts +53 -0
  68. package/dist/core/tools/output-compression.d.ts.map +1 -0
  69. package/dist/core/tools/output-compression.js +392 -0
  70. package/dist/core/tools/output-compression.js.map +1 -0
  71. package/dist/core/tools/read.d.ts.map +1 -1
  72. package/dist/core/tools/read.js +4 -1
  73. package/dist/core/tools/read.js.map +1 -1
  74. package/dist/main.d.ts.map +1 -1
  75. package/dist/main.js +0 -1
  76. package/dist/main.js.map +1 -1
  77. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  78. package/dist/modes/interactive/components/bash-execution.js +2 -2
  79. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  80. package/dist/modes/interactive/components/task-panel.d.ts.map +1 -1
  81. package/dist/modes/interactive/components/task-panel.js +10 -26
  82. package/dist/modes/interactive/components/task-panel.js.map +1 -1
  83. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  84. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  85. package/examples/extensions/sandbox/package.json +1 -1
  86. package/examples/extensions/with-deps/package.json +1 -1
  87. package/package.json +4 -4
  88. package/templates/agents/explore.md +1 -1
  89. package/templates/agents/general-purpose.md +1 -1
  90. package/templates/agents/plan.md +1 -1
  91. package/dist/core/routing/local-inference.d.ts +0 -128
  92. package/dist/core/routing/local-inference.d.ts.map +0 -1
  93. package/dist/core/routing/local-inference.js +0 -145
  94. package/dist/core/routing/local-inference.js.map +0 -1
  95. package/dist/core/routing/metrics.d.ts +0 -27
  96. package/dist/core/routing/metrics.d.ts.map +0 -1
  97. package/dist/core/routing/metrics.js +0 -35
  98. package/dist/core/routing/metrics.js.map +0 -1
  99. package/dist/core/routing/mlx-server.d.ts +0 -43
  100. package/dist/core/routing/mlx-server.d.ts.map +0 -1
  101. package/dist/core/routing/mlx-server.js +0 -115
  102. package/dist/core/routing/mlx-server.js.map +0 -1
  103. package/dist/core/routing/tool-result-prompts.d.ts +0 -26
  104. package/dist/core/routing/tool-result-prompts.d.ts.map +0 -1
  105. package/dist/core/routing/tool-result-prompts.js +0 -45
  106. package/dist/core/routing/tool-result-prompts.js.map +0 -1
@@ -0,0 +1,448 @@
1
+ import { createInterface } from "node:readline";
2
+ import { Text } from "@kolisachint/hoocode-tui";
3
+ import { spawn } from "child_process";
4
+ import { existsSync } from "fs";
5
+ import path from "path";
6
+ import { Type } from "typebox";
7
+ import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
8
+ import { ensureTool } from "../../utils/tools-manager.js";
9
+ import { resolveToCwd } from "./path-utils.js";
10
+ import { getTextOutput, invalidArgText, shortenPath, str } from "./render-utils.js";
11
+ import { wrapToolDefinition } from "./tool-definition-wrapper.js";
12
+ import { DEFAULT_MAX_BYTES, formatSize, truncateHead } from "./truncate.js";
13
+ function toPosixPath(value) {
14
+ return value.split(path.sep).join("/");
15
+ }
16
+ const globSchema = Type.Object({
17
+ patterns: Type.Union([Type.String(), Type.Array(Type.String())], {
18
+ description: "One or more glob patterns to match files (OR logic), e.g. '*.ts', '**/*.json', or ['src/**/*.ts', 'test/**/*.ts']",
19
+ }),
20
+ path: Type.Optional(Type.String({ description: "Root directory to search from (default: current directory)" })),
21
+ exclude: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())], {
22
+ description: "Additional exclusion patterns, e.g. '**/*.test.ts' or ['**/dist/**', '**/build/**']",
23
+ })),
24
+ type: Type.Optional(Type.Union([Type.Literal("f"), Type.Literal("d"), Type.Literal("l")], {
25
+ description: "Filter by file type: 'f' for files, 'd' for directories, 'l' for symlinks (default: 'f')",
26
+ })),
27
+ depth: Type.Optional(Type.Number({ description: "Maximum directory depth to search" })),
28
+ limit: Type.Optional(Type.Number({ description: "Maximum number of results (default: 2000)" })),
29
+ compress: Type.Optional(Type.Boolean({ description: "Compress output by grouping files (default: true)" })),
30
+ });
31
+ const DEFAULT_LIMIT = 2000;
32
+ const defaultGlobOperations = {
33
+ exists: existsSync,
34
+ };
35
+ /**
36
+ * Compress a list of file paths by grouping files in the same directory.
37
+ *
38
+ * Compression rules:
39
+ * - If a directory has >=3 entries all sharing the same extension: fold into `dir/{stem1,stem2,stem3}.ext`
40
+ * - If >=3 entries with mixed extensions: sub-group by extension; fold sub-groups >=3
41
+ * - If directory has >6 total files: summarize as `dir/ [N files]`
42
+ * - 1-2 entries: emit verbatim
43
+ */
44
+ function compressPaths(paths) {
45
+ if (paths.length === 0)
46
+ return "";
47
+ // Sort paths for stable output
48
+ const sorted = [...paths].sort();
49
+ // Group by directory
50
+ const dirMap = new Map();
51
+ for (const p of sorted) {
52
+ const dir = path.dirname(p);
53
+ const base = path.basename(p);
54
+ if (!dirMap.has(dir)) {
55
+ dirMap.set(dir, []);
56
+ }
57
+ dirMap.get(dir).push(base);
58
+ }
59
+ const result = [];
60
+ for (const [dir, files] of dirMap) {
61
+ if (files.length <= 2) {
62
+ // 1-2 entries: emit verbatim
63
+ for (const f of files) {
64
+ result.push(dir === "." ? f : `${dir}/${f}`);
65
+ }
66
+ continue;
67
+ }
68
+ // Check if all files share the same extension
69
+ const extMap = new Map();
70
+ for (const f of files) {
71
+ const ext = path.extname(f);
72
+ const stem = path.basename(f, ext);
73
+ if (!extMap.has(ext)) {
74
+ extMap.set(ext, []);
75
+ }
76
+ extMap.get(ext).push(stem);
77
+ }
78
+ // If all files have the same extension and >=3, fold
79
+ if (extMap.size === 1 && files.length >= 3) {
80
+ const [ext, stems] = extMap.entries().next().value;
81
+ const displayDir = dir === "." ? "" : `${dir}/`;
82
+ result.push(`${displayDir}{${stems.join(",")}}${ext}`);
83
+ continue;
84
+ }
85
+ // Mixed extensions: try to sub-group
86
+ let anyFolded = false;
87
+ const subResults = [];
88
+ for (const [ext, stems] of extMap) {
89
+ if (stems.length >= 3) {
90
+ // Fold this sub-group
91
+ const displayDir = dir === "." ? "" : `${dir}/`;
92
+ subResults.push(`${displayDir}{${stems.join(",")}}${ext}`);
93
+ anyFolded = true;
94
+ }
95
+ else {
96
+ // Emit verbatim
97
+ for (const stem of stems) {
98
+ subResults.push(dir === "." ? `${stem}${ext}` : `${dir}/${stem}${ext}`);
99
+ }
100
+ }
101
+ }
102
+ if (anyFolded || files.length > 6) {
103
+ result.push(...subResults);
104
+ }
105
+ else {
106
+ // Too mixed, emit verbatim
107
+ for (const f of files) {
108
+ result.push(dir === "." ? f : `${dir}/${f}`);
109
+ }
110
+ }
111
+ }
112
+ return result.join("\n");
113
+ }
114
+ function formatGlobCall(args, theme) {
115
+ const patterns = args?.patterns;
116
+ const patternStr = Array.isArray(patterns) ? patterns.join(", ") : (str(patterns) ?? "");
117
+ const rawPath = str(args?.path);
118
+ const displayPath = rawPath !== null ? shortenPath(rawPath || ".") : null;
119
+ const type = args?.type;
120
+ const depth = args?.depth;
121
+ const limit = args?.limit;
122
+ const invalidArg = invalidArgText(theme);
123
+ let text = theme.fg("toolTitle", theme.bold("glob")) +
124
+ " " +
125
+ (patternStr === "" ? invalidArg : theme.fg("accent", patternStr)) +
126
+ theme.fg("toolOutput", ` in ${displayPath === null ? invalidArg : displayPath}`);
127
+ if (type && type !== "f") {
128
+ const typeLabel = type === "d" ? "dirs" : type === "l" ? "symlinks" : type;
129
+ text += theme.fg("toolOutput", ` (${typeLabel})`);
130
+ }
131
+ if (depth !== undefined) {
132
+ text += theme.fg("toolOutput", ` (depth ${depth})`);
133
+ }
134
+ if (limit !== undefined) {
135
+ text += theme.fg("toolOutput", ` (limit ${limit})`);
136
+ }
137
+ return text;
138
+ }
139
+ function formatGlobResult(result, options, theme, showImages) {
140
+ const output = getTextOutput(result, showImages).trim();
141
+ let text = "";
142
+ if (output) {
143
+ const lines = output.split("\n");
144
+ const maxLines = options.expanded ? lines.length : 25;
145
+ const displayLines = lines.slice(0, maxLines);
146
+ const remaining = lines.length - maxLines;
147
+ text += `\n${displayLines.map((line) => theme.fg("toolOutput", line)).join("\n")}`;
148
+ if (remaining > 0) {
149
+ text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("app.tools.expand", "to expand")})`;
150
+ }
151
+ }
152
+ // Stats footer
153
+ const totalMatches = result.details?.totalMatches;
154
+ const uniqueDirectories = result.details?.uniqueDirectories;
155
+ if (totalMatches !== undefined && uniqueDirectories !== undefined) {
156
+ text += `\n${theme.fg("muted", `[${totalMatches} matches, ${uniqueDirectories} dirs]`)}`;
157
+ }
158
+ // Truncation/limit warnings
159
+ const resultLimit = result.details?.resultLimitReached;
160
+ const truncation = result.details?.truncation;
161
+ if (resultLimit || truncation?.truncated) {
162
+ const warnings = [];
163
+ if (resultLimit) {
164
+ warnings.push(`${resultLimit} results limit reached. Use limit=${resultLimit * 2} for more`);
165
+ }
166
+ if (truncation?.truncated) {
167
+ warnings.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);
168
+ }
169
+ text += `\n${theme.fg("warning", `[Truncated: ${warnings.join(". ")}]`)}`;
170
+ }
171
+ return text;
172
+ }
173
+ export function createGlobToolDefinition(cwd, options) {
174
+ const customOps = options?.operations;
175
+ return {
176
+ name: "glob",
177
+ label: "glob",
178
+ description: `Search for files matching one or more glob patterns. Supports OR logic across patterns. Returns matching file paths grouped and compressed for readability. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} results or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first).`,
179
+ promptSnippet: "Search for files by glob pattern (supports multiple patterns, respects .gitignore)",
180
+ parameters: globSchema,
181
+ async execute(_toolCallId, { patterns: rawPatterns, path: searchDir, exclude: rawExclude, type, depth, limit, compress = true, }, signal, _onUpdate, _ctx) {
182
+ return new Promise((resolve, reject) => {
183
+ if (signal?.aborted) {
184
+ reject(new Error("Operation aborted"));
185
+ return;
186
+ }
187
+ let settled = false;
188
+ let stopChild;
189
+ const settle = (fn) => {
190
+ if (settled)
191
+ return;
192
+ settled = true;
193
+ signal?.removeEventListener("abort", onAbort);
194
+ stopChild = undefined;
195
+ fn();
196
+ };
197
+ const onAbort = () => {
198
+ stopChild?.();
199
+ settle(() => reject(new Error("Operation aborted")));
200
+ };
201
+ signal?.addEventListener("abort", onAbort, { once: true });
202
+ (async () => {
203
+ try {
204
+ const searchPath = resolveToCwd(searchDir || ".", cwd);
205
+ const effectiveLimit = limit ?? DEFAULT_LIMIT;
206
+ const ops = customOps ?? defaultGlobOperations;
207
+ // Normalize patterns to array
208
+ const patterns = Array.isArray(rawPatterns) ? rawPatterns : [rawPatterns];
209
+ // Normalize exclude to array
210
+ const excludePatterns = rawExclude ? (Array.isArray(rawExclude) ? rawExclude : [rawExclude]) : [];
211
+ // Always exclude node_modules and .git
212
+ const allIgnore = ["**/node_modules/**", "**/.git/**", ...excludePatterns];
213
+ // Build type filter for fd
214
+ const typeFilter = type === "d" ? "d" : type === "l" ? "l" : "f";
215
+ // If custom operations provide glob(), use that instead of fd.
216
+ if (customOps?.glob) {
217
+ if (!(await ops.exists(searchPath))) {
218
+ settle(() => reject(new Error(`Path not found: ${searchPath}`)));
219
+ return;
220
+ }
221
+ if (signal?.aborted) {
222
+ settle(() => reject(new Error("Operation aborted")));
223
+ return;
224
+ }
225
+ const results = await ops.glob(patterns, searchPath, {
226
+ ignore: allIgnore,
227
+ limit: effectiveLimit,
228
+ type: typeFilter,
229
+ });
230
+ if (signal?.aborted) {
231
+ settle(() => reject(new Error("Operation aborted")));
232
+ return;
233
+ }
234
+ if (results.length === 0) {
235
+ settle(() => resolve({
236
+ content: [{ type: "text", text: "No files found matching patterns" }],
237
+ details: undefined,
238
+ }));
239
+ return;
240
+ }
241
+ // Relativize paths against the search root
242
+ const relativized = results.map((p) => {
243
+ if (p.startsWith(searchPath))
244
+ return toPosixPath(p.slice(searchPath.length + 1));
245
+ return toPosixPath(path.relative(searchPath, p));
246
+ });
247
+ // Deduplicate
248
+ const unique = [...new Set(relativized)];
249
+ const totalMatches = unique.length;
250
+ const resultLimitReached = totalMatches >= effectiveLimit;
251
+ const truncated = unique.slice(0, effectiveLimit);
252
+ // Compress output
253
+ let resultOutput;
254
+ if (compress) {
255
+ resultOutput = compressPaths(truncated);
256
+ }
257
+ else {
258
+ resultOutput = truncated.join("\n");
259
+ }
260
+ // Apply byte-level truncation
261
+ const truncationResult = truncateHead(resultOutput, {
262
+ maxLines: Number.MAX_SAFE_INTEGER,
263
+ });
264
+ resultOutput = truncationResult.content;
265
+ // Calculate unique directories
266
+ const uniqueDirs = new Set(truncated.map((p) => path.dirname(p)));
267
+ const details = {
268
+ totalMatches,
269
+ uniqueDirectories: uniqueDirs.size,
270
+ };
271
+ const notices = [];
272
+ if (resultLimitReached) {
273
+ notices.push(`${effectiveLimit} results limit reached. Use limit=${effectiveLimit * 2} for more`);
274
+ details.resultLimitReached = effectiveLimit;
275
+ }
276
+ if (truncationResult.truncated) {
277
+ notices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);
278
+ details.truncation = truncationResult;
279
+ }
280
+ if (notices.length > 0) {
281
+ resultOutput += `\n\n[${notices.join(". ")}]`;
282
+ }
283
+ settle(() => resolve({
284
+ content: [{ type: "text", text: resultOutput }],
285
+ details: Object.keys(details).length > 0 ? details : undefined,
286
+ }));
287
+ return;
288
+ }
289
+ // Default implementation uses fd.
290
+ const fdPath = await ensureTool("fd", true);
291
+ if (signal?.aborted) {
292
+ settle(() => reject(new Error("Operation aborted")));
293
+ return;
294
+ }
295
+ if (!fdPath) {
296
+ settle(() => reject(new Error("fd unavailable and could not be downloaded — use the bash tool to run find instead")));
297
+ return;
298
+ }
299
+ // Run all patterns in parallel
300
+ const allResults = [];
301
+ const runPattern = async (pattern) => {
302
+ const args = [
303
+ "--glob",
304
+ "--color=never",
305
+ "--hidden",
306
+ "--no-require-git",
307
+ "--max-results",
308
+ String(effectiveLimit),
309
+ ];
310
+ // Add type filter
311
+ if (typeFilter) {
312
+ args.push("--type", typeFilter);
313
+ }
314
+ // Add exclusions
315
+ for (const excl of allIgnore) {
316
+ args.push("--exclude", excl);
317
+ }
318
+ // Add depth
319
+ if (depth !== undefined) {
320
+ args.push("--max-depth", String(depth));
321
+ }
322
+ // Handle path-containing patterns
323
+ let effectivePattern = pattern;
324
+ if (pattern.includes("/")) {
325
+ args.push("--full-path");
326
+ if (!pattern.startsWith("/") && !pattern.startsWith("**/") && pattern !== "**") {
327
+ effectivePattern = `**/${pattern}`;
328
+ }
329
+ }
330
+ args.push("--", effectivePattern, searchPath);
331
+ return new Promise((res, rej) => {
332
+ const child = spawn(fdPath, args, { stdio: ["ignore", "pipe", "pipe"] });
333
+ const rl = createInterface({ input: child.stdout });
334
+ const lines = [];
335
+ const cleanup = () => {
336
+ rl.close();
337
+ };
338
+ rl.on("line", (line) => {
339
+ lines.push(line);
340
+ });
341
+ child.on("error", (error) => {
342
+ cleanup();
343
+ rej(new Error(`Failed to run fd: ${error.message}`));
344
+ });
345
+ child.on("close", () => {
346
+ cleanup();
347
+ res(lines);
348
+ });
349
+ });
350
+ };
351
+ // Execute all patterns in parallel
352
+ const patternResults = await Promise.all(patterns.map(runPattern));
353
+ // Merge and deduplicate results
354
+ const seen = new Set();
355
+ for (const results of patternResults) {
356
+ for (const rawLine of results) {
357
+ const line = rawLine.replace(/\r$/, "").trim();
358
+ if (!line)
359
+ continue;
360
+ let relativePath;
361
+ if (line.startsWith(searchPath)) {
362
+ relativePath = line.slice(searchPath.length + 1);
363
+ }
364
+ else {
365
+ relativePath = path.relative(searchPath, line);
366
+ }
367
+ const posixPath = toPosixPath(relativePath);
368
+ if (!seen.has(posixPath)) {
369
+ seen.add(posixPath);
370
+ allResults.push(posixPath);
371
+ }
372
+ }
373
+ }
374
+ if (allResults.length === 0) {
375
+ settle(() => resolve({
376
+ content: [{ type: "text", text: "No files found matching patterns" }],
377
+ details: undefined,
378
+ }));
379
+ return;
380
+ }
381
+ // Sort results for stable output
382
+ allResults.sort();
383
+ const totalMatches = allResults.length;
384
+ const resultLimitReached = totalMatches >= effectiveLimit;
385
+ const truncated = allResults.slice(0, effectiveLimit);
386
+ // Compress output
387
+ let resultOutput;
388
+ if (compress) {
389
+ resultOutput = compressPaths(truncated);
390
+ }
391
+ else {
392
+ resultOutput = truncated.join("\n");
393
+ }
394
+ // Apply byte-level truncation
395
+ const truncationResult = truncateHead(resultOutput, {
396
+ maxLines: Number.MAX_SAFE_INTEGER,
397
+ });
398
+ resultOutput = truncationResult.content;
399
+ // Calculate unique directories
400
+ const uniqueDirs = new Set(truncated.map((p) => path.dirname(p)));
401
+ const details = {
402
+ totalMatches,
403
+ uniqueDirectories: uniqueDirs.size,
404
+ };
405
+ const notices = [];
406
+ if (resultLimitReached) {
407
+ notices.push(`${effectiveLimit} results limit reached. Use limit=${effectiveLimit * 2} for more`);
408
+ details.resultLimitReached = effectiveLimit;
409
+ }
410
+ if (truncationResult.truncated) {
411
+ notices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);
412
+ details.truncation = truncationResult;
413
+ }
414
+ if (notices.length > 0) {
415
+ resultOutput += `\n\n[${notices.join(". ")}]`;
416
+ }
417
+ settle(() => resolve({
418
+ content: [{ type: "text", text: resultOutput }],
419
+ details: Object.keys(details).length > 0 ? details : undefined,
420
+ }));
421
+ }
422
+ catch (e) {
423
+ if (signal?.aborted) {
424
+ settle(() => reject(new Error("Operation aborted")));
425
+ return;
426
+ }
427
+ const error = e instanceof Error ? e : new Error(String(e));
428
+ settle(() => reject(error));
429
+ }
430
+ })();
431
+ });
432
+ },
433
+ renderCall(args, theme, context) {
434
+ const text = context.lastComponent ?? new Text("", 0, 0);
435
+ text.setText(formatGlobCall(args, theme));
436
+ return text;
437
+ },
438
+ renderResult(result, options, theme, context) {
439
+ const text = context.lastComponent ?? new Text("", 0, 0);
440
+ text.setText(formatGlobResult(result, options, theme, context.showImages));
441
+ return text;
442
+ },
443
+ };
444
+ }
445
+ export function createGlobTool(cwd, options) {
446
+ return wrapToolDefinition(createGlobToolDefinition(cwd, options));
447
+ }
448
+ //# sourceMappingURL=glob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.js","sourceRoot":"","sources":["../../../src/core/tools/glob.ts"],"names":[],"mappings":"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","sourcesContent":["import { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { spawn } from \"child_process\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.js\";\nimport { ensureTool } from \"../../utils/tools-manager.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from \"./truncate.js\";\n\nfunction toPosixPath(value: string): string {\n\treturn value.split(path.sep).join(\"/\");\n}\n\nconst globSchema = Type.Object({\n\tpatterns: Type.Union([Type.String(), Type.Array(Type.String())], {\n\t\tdescription:\n\t\t\t\"One or more glob patterns to match files (OR logic), e.g. '*.ts', '**/*.json', or ['src/**/*.ts', 'test/**/*.ts']\",\n\t}),\n\tpath: Type.Optional(Type.String({ description: \"Root directory to search from (default: current directory)\" })),\n\texclude: Type.Optional(\n\t\tType.Union([Type.String(), Type.Array(Type.String())], {\n\t\t\tdescription: \"Additional exclusion patterns, e.g. '**/*.test.ts' or ['**/dist/**', '**/build/**']\",\n\t\t}),\n\t),\n\ttype: Type.Optional(\n\t\tType.Union([Type.Literal(\"f\"), Type.Literal(\"d\"), Type.Literal(\"l\")], {\n\t\t\tdescription: \"Filter by file type: 'f' for files, 'd' for directories, 'l' for symlinks (default: 'f')\",\n\t\t}),\n\t),\n\tdepth: Type.Optional(Type.Number({ description: \"Maximum directory depth to search\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results (default: 2000)\" })),\n\tcompress: Type.Optional(Type.Boolean({ description: \"Compress output by grouping files (default: true)\" })),\n});\n\nexport type GlobToolInput = Static<typeof globSchema>;\n\nconst DEFAULT_LIMIT = 2000;\n\nexport interface GlobToolDetails {\n\ttruncation?: TruncationResult;\n\tresultLimitReached?: number;\n\ttotalMatches?: number;\n\tuniqueDirectories?: number;\n}\n\n/**\n * Pluggable operations for the glob tool.\n * Override these to delegate file search to remote systems (for example SSH).\n */\nexport interface GlobOperations {\n\t/** Check if path exists */\n\texists: (absolutePath: string) => Promise<boolean> | boolean;\n\t/** Optional custom glob resolver (used for SSH/remote) */\n\tglob?: (\n\t\tpatterns: string[],\n\t\tcwd: string,\n\t\toptions: { ignore: string[]; limit: number; type?: string },\n\t) => Promise<string[]> | string[];\n}\n\nconst defaultGlobOperations: GlobOperations = {\n\texists: existsSync,\n};\n\nexport interface GlobToolOptions {\n\t/** Custom operations for glob. Default: local filesystem plus fd */\n\toperations?: GlobOperations;\n}\n\n/**\n * Compress a list of file paths by grouping files in the same directory.\n *\n * Compression rules:\n * - If a directory has >=3 entries all sharing the same extension: fold into `dir/{stem1,stem2,stem3}.ext`\n * - If >=3 entries with mixed extensions: sub-group by extension; fold sub-groups >=3\n * - If directory has >6 total files: summarize as `dir/ [N files]`\n * - 1-2 entries: emit verbatim\n */\nfunction compressPaths(paths: string[]): string {\n\tif (paths.length === 0) return \"\";\n\n\t// Sort paths for stable output\n\tconst sorted = [...paths].sort();\n\n\t// Group by directory\n\tconst dirMap = new Map<string, string[]>();\n\tfor (const p of sorted) {\n\t\tconst dir = path.dirname(p);\n\t\tconst base = path.basename(p);\n\t\tif (!dirMap.has(dir)) {\n\t\t\tdirMap.set(dir, []);\n\t\t}\n\t\tdirMap.get(dir)!.push(base);\n\t}\n\n\tconst result: string[] = [];\n\n\tfor (const [dir, files] of dirMap) {\n\t\tif (files.length <= 2) {\n\t\t\t// 1-2 entries: emit verbatim\n\t\t\tfor (const f of files) {\n\t\t\t\tresult.push(dir === \".\" ? f : `${dir}/${f}`);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Check if all files share the same extension\n\t\tconst extMap = new Map<string, string[]>();\n\t\tfor (const f of files) {\n\t\t\tconst ext = path.extname(f);\n\t\t\tconst stem = path.basename(f, ext);\n\t\t\tif (!extMap.has(ext)) {\n\t\t\t\textMap.set(ext, []);\n\t\t\t}\n\t\t\textMap.get(ext)!.push(stem);\n\t\t}\n\n\t\t// If all files have the same extension and >=3, fold\n\t\tif (extMap.size === 1 && files.length >= 3) {\n\t\t\tconst [ext, stems] = extMap.entries().next().value!;\n\t\t\tconst displayDir = dir === \".\" ? \"\" : `${dir}/`;\n\t\t\tresult.push(`${displayDir}{${stems.join(\",\")}}${ext}`);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Mixed extensions: try to sub-group\n\t\tlet anyFolded = false;\n\t\tconst subResults: string[] = [];\n\n\t\tfor (const [ext, stems] of extMap) {\n\t\t\tif (stems.length >= 3) {\n\t\t\t\t// Fold this sub-group\n\t\t\t\tconst displayDir = dir === \".\" ? \"\" : `${dir}/`;\n\t\t\t\tsubResults.push(`${displayDir}{${stems.join(\",\")}}${ext}`);\n\t\t\t\tanyFolded = true;\n\t\t\t} else {\n\t\t\t\t// Emit verbatim\n\t\t\t\tfor (const stem of stems) {\n\t\t\t\t\tsubResults.push(dir === \".\" ? `${stem}${ext}` : `${dir}/${stem}${ext}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (anyFolded || files.length > 6) {\n\t\t\tresult.push(...subResults);\n\t\t} else {\n\t\t\t// Too mixed, emit verbatim\n\t\t\tfor (const f of files) {\n\t\t\t\tresult.push(dir === \".\" ? f : `${dir}/${f}`);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result.join(\"\\n\");\n}\n\nfunction formatGlobCall(\n\targs: { patterns?: string | string[]; path?: string; type?: string; depth?: number; limit?: number } | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst patterns = args?.patterns;\n\tconst patternStr = Array.isArray(patterns) ? patterns.join(\", \") : (str(patterns) ?? \"\");\n\tconst rawPath = str(args?.path);\n\tconst displayPath = rawPath !== null ? shortenPath(rawPath || \".\") : null;\n\tconst type = args?.type;\n\tconst depth = args?.depth;\n\tconst limit = args?.limit;\n\tconst invalidArg = invalidArgText(theme);\n\n\tlet text =\n\t\ttheme.fg(\"toolTitle\", theme.bold(\"glob\")) +\n\t\t\" \" +\n\t\t(patternStr === \"\" ? invalidArg : theme.fg(\"accent\", patternStr)) +\n\t\ttheme.fg(\"toolOutput\", ` in ${displayPath === null ? invalidArg : displayPath}`);\n\n\tif (type && type !== \"f\") {\n\t\tconst typeLabel = type === \"d\" ? \"dirs\" : type === \"l\" ? \"symlinks\" : type;\n\t\ttext += theme.fg(\"toolOutput\", ` (${typeLabel})`);\n\t}\n\tif (depth !== undefined) {\n\t\ttext += theme.fg(\"toolOutput\", ` (depth ${depth})`);\n\t}\n\tif (limit !== undefined) {\n\t\ttext += theme.fg(\"toolOutput\", ` (limit ${limit})`);\n\t}\n\treturn text;\n}\n\nfunction formatGlobResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: GlobToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result, showImages).trim();\n\tlet text = \"\";\n\tif (output) {\n\t\tconst lines = output.split(\"\\n\");\n\t\tconst maxLines = options.expanded ? lines.length : 25;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t}\n\t}\n\n\t// Stats footer\n\tconst totalMatches = result.details?.totalMatches;\n\tconst uniqueDirectories = result.details?.uniqueDirectories;\n\tif (totalMatches !== undefined && uniqueDirectories !== undefined) {\n\t\ttext += `\\n${theme.fg(\"muted\", `[${totalMatches} matches, ${uniqueDirectories} dirs]`)}`;\n\t}\n\n\t// Truncation/limit warnings\n\tconst resultLimit = result.details?.resultLimitReached;\n\tconst truncation = result.details?.truncation;\n\tif (resultLimit || truncation?.truncated) {\n\t\tconst warnings: string[] = [];\n\t\tif (resultLimit) {\n\t\t\twarnings.push(`${resultLimit} results limit reached. Use limit=${resultLimit * 2} for more`);\n\t\t}\n\t\tif (truncation?.truncated) {\n\t\t\twarnings.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t}\n\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${warnings.join(\". \")}]`)}`;\n\t}\n\treturn text;\n}\n\nexport function createGlobToolDefinition(\n\tcwd: string,\n\toptions?: GlobToolOptions,\n): ToolDefinition<typeof globSchema, GlobToolDetails | undefined> {\n\tconst customOps = options?.operations;\n\treturn {\n\t\tname: \"glob\",\n\t\tlabel: \"glob\",\n\t\tdescription: `Search for files matching one or more glob patterns. Supports OR logic across patterns. Returns matching file paths grouped and compressed for readability. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} results or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first).`,\n\t\tpromptSnippet: \"Search for files by glob pattern (supports multiple patterns, respects .gitignore)\",\n\t\tparameters: globSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{\n\t\t\t\tpatterns: rawPatterns,\n\t\t\t\tpath: searchDir,\n\t\t\t\texclude: rawExclude,\n\t\t\t\ttype,\n\t\t\t\tdepth,\n\t\t\t\tlimit,\n\t\t\t\tcompress = true,\n\t\t\t}: {\n\t\t\t\tpatterns: string | string[];\n\t\t\t\tpath?: string;\n\t\t\t\texclude?: string | string[];\n\t\t\t\ttype?: \"f\" | \"d\" | \"l\";\n\t\t\t\tdepth?: number;\n\t\t\t\tlimit?: number;\n\t\t\t\tcompress?: boolean;\n\t\t\t},\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet settled = false;\n\t\t\t\tlet stopChild: (() => void) | undefined;\n\t\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\tstopChild = undefined;\n\t\t\t\t\tfn();\n\t\t\t\t};\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tstopChild?.();\n\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t};\n\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst searchPath = resolveToCwd(searchDir || \".\", cwd);\n\t\t\t\t\t\tconst effectiveLimit = limit ?? DEFAULT_LIMIT;\n\t\t\t\t\t\tconst ops = customOps ?? defaultGlobOperations;\n\n\t\t\t\t\t\t// Normalize patterns to array\n\t\t\t\t\t\tconst patterns = Array.isArray(rawPatterns) ? rawPatterns : [rawPatterns];\n\n\t\t\t\t\t\t// Normalize exclude to array\n\t\t\t\t\t\tconst excludePatterns = rawExclude ? (Array.isArray(rawExclude) ? rawExclude : [rawExclude]) : [];\n\n\t\t\t\t\t\t// Always exclude node_modules and .git\n\t\t\t\t\t\tconst allIgnore = [\"**/node_modules/**\", \"**/.git/**\", ...excludePatterns];\n\n\t\t\t\t\t\t// Build type filter for fd\n\t\t\t\t\t\tconst typeFilter = type === \"d\" ? \"d\" : type === \"l\" ? \"l\" : \"f\";\n\n\t\t\t\t\t\t// If custom operations provide glob(), use that instead of fd.\n\t\t\t\t\t\tif (customOps?.glob) {\n\t\t\t\t\t\t\tif (!(await ops.exists(searchPath))) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst results = await ops.glob!(patterns, searchPath, {\n\t\t\t\t\t\t\t\tignore: allIgnore,\n\t\t\t\t\t\t\t\tlimit: effectiveLimit,\n\t\t\t\t\t\t\t\ttype: typeFilter,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (results.length === 0) {\n\t\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No files found matching patterns\" }],\n\t\t\t\t\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Relativize paths against the search root\n\t\t\t\t\t\t\tconst relativized = results.map((p) => {\n\t\t\t\t\t\t\t\tif (p.startsWith(searchPath)) return toPosixPath(p.slice(searchPath.length + 1));\n\t\t\t\t\t\t\t\treturn toPosixPath(path.relative(searchPath, p));\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t// Deduplicate\n\t\t\t\t\t\t\tconst unique = [...new Set(relativized)];\n\n\t\t\t\t\t\t\tconst totalMatches = unique.length;\n\t\t\t\t\t\t\tconst resultLimitReached = totalMatches >= effectiveLimit;\n\t\t\t\t\t\t\tconst truncated = unique.slice(0, effectiveLimit);\n\n\t\t\t\t\t\t\t// Compress output\n\t\t\t\t\t\t\tlet resultOutput: string;\n\t\t\t\t\t\t\tif (compress) {\n\t\t\t\t\t\t\t\tresultOutput = compressPaths(truncated);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tresultOutput = truncated.join(\"\\n\");\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Apply byte-level truncation\n\t\t\t\t\t\t\tconst truncationResult = truncateHead(resultOutput, {\n\t\t\t\t\t\t\t\tmaxLines: Number.MAX_SAFE_INTEGER,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tresultOutput = truncationResult.content;\n\n\t\t\t\t\t\t\t// Calculate unique directories\n\t\t\t\t\t\t\tconst uniqueDirs = new Set(truncated.map((p) => path.dirname(p)));\n\n\t\t\t\t\t\t\tconst details: GlobToolDetails = {\n\t\t\t\t\t\t\t\ttotalMatches,\n\t\t\t\t\t\t\t\tuniqueDirectories: uniqueDirs.size,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tconst notices: string[] = [];\n\t\t\t\t\t\t\tif (resultLimitReached) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`${effectiveLimit} results limit reached. Use limit=${effectiveLimit * 2} for more`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.resultLimitReached = effectiveLimit;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (truncationResult.truncated) {\n\t\t\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\t\t\tdetails.truncation = truncationResult;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (notices.length > 0) {\n\t\t\t\t\t\t\t\tresultOutput += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: resultOutput }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Default implementation uses fd.\n\t\t\t\t\t\tconst fdPath = await ensureTool(\"fd\", true);\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!fdPath) {\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\treject(\n\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t\"fd unavailable and could not be downloaded — use the bash tool to run find instead\",\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Run all patterns in parallel\n\t\t\t\t\t\tconst allResults: string[] = [];\n\n\t\t\t\t\t\tconst runPattern = async (pattern: string): Promise<string[]> => {\n\t\t\t\t\t\t\tconst args: string[] = [\n\t\t\t\t\t\t\t\t\"--glob\",\n\t\t\t\t\t\t\t\t\"--color=never\",\n\t\t\t\t\t\t\t\t\"--hidden\",\n\t\t\t\t\t\t\t\t\"--no-require-git\",\n\t\t\t\t\t\t\t\t\"--max-results\",\n\t\t\t\t\t\t\t\tString(effectiveLimit),\n\t\t\t\t\t\t\t];\n\n\t\t\t\t\t\t\t// Add type filter\n\t\t\t\t\t\t\tif (typeFilter) {\n\t\t\t\t\t\t\t\targs.push(\"--type\", typeFilter);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Add exclusions\n\t\t\t\t\t\t\tfor (const excl of allIgnore) {\n\t\t\t\t\t\t\t\targs.push(\"--exclude\", excl);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Add depth\n\t\t\t\t\t\t\tif (depth !== undefined) {\n\t\t\t\t\t\t\t\targs.push(\"--max-depth\", String(depth));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Handle path-containing patterns\n\t\t\t\t\t\t\tlet effectivePattern = pattern;\n\t\t\t\t\t\t\tif (pattern.includes(\"/\")) {\n\t\t\t\t\t\t\t\targs.push(\"--full-path\");\n\t\t\t\t\t\t\t\tif (!pattern.startsWith(\"/\") && !pattern.startsWith(\"**/\") && pattern !== \"**\") {\n\t\t\t\t\t\t\t\t\teffectivePattern = `**/${pattern}`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\targs.push(\"--\", effectivePattern, searchPath);\n\n\t\t\t\t\t\t\treturn new Promise<string[]>((res, rej) => {\n\t\t\t\t\t\t\t\tconst child = spawn(fdPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\t\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\t\t\t\tconst lines: string[] = [];\n\n\t\t\t\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\t\t\t\trl.close();\n\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\t\t\t\tlines.push(line);\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\t\t\trej(new Error(`Failed to run fd: ${error.message}`));\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tchild.on(\"close\", () => {\n\t\t\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\t\t\tres(lines);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Execute all patterns in parallel\n\t\t\t\t\t\tconst patternResults = await Promise.all(patterns.map(runPattern));\n\n\t\t\t\t\t\t// Merge and deduplicate results\n\t\t\t\t\t\tconst seen = new Set<string>();\n\t\t\t\t\t\tfor (const results of patternResults) {\n\t\t\t\t\t\t\tfor (const rawLine of results) {\n\t\t\t\t\t\t\t\tconst line = rawLine.replace(/\\r$/, \"\").trim();\n\t\t\t\t\t\t\t\tif (!line) continue;\n\n\t\t\t\t\t\t\t\tlet relativePath: string;\n\t\t\t\t\t\t\t\tif (line.startsWith(searchPath)) {\n\t\t\t\t\t\t\t\t\trelativePath = line.slice(searchPath.length + 1);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\trelativePath = path.relative(searchPath, line);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst posixPath = toPosixPath(relativePath);\n\n\t\t\t\t\t\t\t\tif (!seen.has(posixPath)) {\n\t\t\t\t\t\t\t\t\tseen.add(posixPath);\n\t\t\t\t\t\t\t\t\tallResults.push(posixPath);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (allResults.length === 0) {\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No files found matching patterns\" }],\n\t\t\t\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Sort results for stable output\n\t\t\t\t\t\tallResults.sort();\n\n\t\t\t\t\t\tconst totalMatches = allResults.length;\n\t\t\t\t\t\tconst resultLimitReached = totalMatches >= effectiveLimit;\n\t\t\t\t\t\tconst truncated = allResults.slice(0, effectiveLimit);\n\n\t\t\t\t\t\t// Compress output\n\t\t\t\t\t\tlet resultOutput: string;\n\t\t\t\t\t\tif (compress) {\n\t\t\t\t\t\t\tresultOutput = compressPaths(truncated);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresultOutput = truncated.join(\"\\n\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Apply byte-level truncation\n\t\t\t\t\t\tconst truncationResult = truncateHead(resultOutput, {\n\t\t\t\t\t\t\tmaxLines: Number.MAX_SAFE_INTEGER,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tresultOutput = truncationResult.content;\n\n\t\t\t\t\t\t// Calculate unique directories\n\t\t\t\t\t\tconst uniqueDirs = new Set(truncated.map((p) => path.dirname(p)));\n\n\t\t\t\t\t\tconst details: GlobToolDetails = {\n\t\t\t\t\t\t\ttotalMatches,\n\t\t\t\t\t\t\tuniqueDirectories: uniqueDirs.size,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst notices: string[] = [];\n\t\t\t\t\t\tif (resultLimitReached) {\n\t\t\t\t\t\t\tnotices.push(`${effectiveLimit} results limit reached. Use limit=${effectiveLimit * 2} for more`);\n\t\t\t\t\t\t\tdetails.resultLimitReached = effectiveLimit;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (truncationResult.truncated) {\n\t\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\t\tdetails.truncation = truncationResult;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (notices.length > 0) {\n\t\t\t\t\t\t\tresultOutput += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: resultOutput }],\n\t\t\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst error = e instanceof Error ? e : new Error(String(e));\n\t\t\t\t\t\tsettle(() => reject(error));\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGlobCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGlobResult(result as any, options, theme, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createGlobTool(cwd: string, options?: GlobToolOptions): AgentTool<typeof globSchema> {\n\treturn wrapToolDefinition(createGlobToolDefinition(cwd, options));\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../../src/core/tools/grep.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAKjE,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AAItF,OAAO,EAIN,KAAK,gBAAgB,EAGrB,MAAM,eAAe,CAAC;AAEvB,QAAA,MAAM,UAAU;;;;;;;;EAYd,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAGtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,mEAAmE;IACnE,WAAW,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAClE,2CAA2C;IAC3C,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;CAC7D;AAOD,MAAM,WAAW,eAAe;IAC/B,yEAAyE;IACzE,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B;AAyDD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CAqQhE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG","sourcesContent":["import { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { spawn } from \"child_process\";\nimport { readFileSync, statSync } from \"fs\";\nimport path from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.js\";\nimport { ensureTool } from \"../../utils/tools-manager.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tformatSize,\n\tGREP_MAX_LINE_LENGTH,\n\ttype TruncationResult,\n\ttruncateHead,\n\ttruncateLine,\n} from \"./truncate.js\";\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Number({ description: \"Number of lines to show before and after each match (default: 0)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nexport type GrepToolInput = Static<typeof grepSchema>;\nconst DEFAULT_LIMIT = 100;\n\nexport interface GrepToolDetails {\n\ttruncation?: TruncationResult;\n\tmatchLimitReached?: number;\n\tlinesTruncated?: boolean;\n}\n\n/**\n * Pluggable operations for the grep tool.\n * Override these to delegate search to remote systems (for example SSH).\n */\nexport interface GrepOperations {\n\t/** Check if path is a directory. Throws if path does not exist. */\n\tisDirectory: (absolutePath: string) => Promise<boolean> | boolean;\n\t/** Read file contents for context lines */\n\treadFile: (absolutePath: string) => Promise<string> | string;\n}\n\nconst defaultGrepOperations: GrepOperations = {\n\tisDirectory: (p) => statSync(p).isDirectory(),\n\treadFile: (p) => readFileSync(p, \"utf-8\"),\n};\n\nexport interface GrepToolOptions {\n\t/** Custom operations for grep. Default: local filesystem plus ripgrep */\n\toperations?: GrepOperations;\n}\n\nfunction formatGrepCall(\n\targs: { pattern: string; path?: string; glob?: string; limit?: number } | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst pattern = str(args?.pattern);\n\tconst rawPath = str(args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath || \".\") : null;\n\tconst glob = str(args?.glob);\n\tconst limit = args?.limit;\n\tconst invalidArg = invalidArgText(theme);\n\tlet text =\n\t\ttheme.fg(\"toolTitle\", theme.bold(\"grep\")) +\n\t\t\" \" +\n\t\t(pattern === null ? invalidArg : theme.fg(\"accent\", `/${pattern || \"\"}/`)) +\n\t\ttheme.fg(\"toolOutput\", ` in ${path === null ? invalidArg : path}`);\n\tif (glob) text += theme.fg(\"toolOutput\", ` (${glob})`);\n\tif (limit !== undefined) text += theme.fg(\"toolOutput\", ` limit ${limit}`);\n\treturn text;\n}\n\nfunction formatGrepResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: GrepToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result, showImages).trim();\n\tlet text = \"\";\n\tif (output) {\n\t\tconst lines = output.split(\"\\n\");\n\t\tconst maxLines = options.expanded ? lines.length : 15;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t}\n\t}\n\n\tconst matchLimit = result.details?.matchLimitReached;\n\tconst truncation = result.details?.truncation;\n\tconst linesTruncated = result.details?.linesTruncated;\n\tif (matchLimit || truncation?.truncated || linesTruncated) {\n\t\tconst warnings: string[] = [];\n\t\tif (matchLimit) warnings.push(`${matchLimit} matches limit`);\n\t\tif (truncation?.truncated) warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\tif (linesTruncated) warnings.push(\"some lines truncated\");\n\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`)}`;\n\t}\n\treturn text;\n}\n\nexport function createGrepToolDefinition(\n\tcwd: string,\n\toptions?: GrepToolOptions,\n): ToolDefinition<typeof grepSchema, GrepToolDetails | undefined> {\n\tconst customOps = options?.operations;\n\treturn {\n\t\tname: \"grep\",\n\t\tlabel: \"grep\",\n\t\tdescription: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} matches or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Long lines are truncated to ${GREP_MAX_LINE_LENGTH} chars.`,\n\t\tpromptSnippet: \"Search file contents for patterns (respects .gitignore)\",\n\t\tparameters: grepSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{\n\t\t\t\tpattern,\n\t\t\t\tpath: searchDir,\n\t\t\t\tglob,\n\t\t\t\tignoreCase,\n\t\t\t\tliteral,\n\t\t\t\tcontext,\n\t\t\t\tlimit,\n\t\t\t}: {\n\t\t\t\tpattern: string;\n\t\t\t\tpath?: string;\n\t\t\t\tglob?: string;\n\t\t\t\tignoreCase?: boolean;\n\t\t\t\tliteral?: boolean;\n\t\t\t\tcontext?: number;\n\t\t\t\tlimit?: number;\n\t\t\t},\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\t\tif (!settled) {\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\tfn();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rgPath = await ensureTool(\"rg\", true);\n\t\t\t\t\t\tif (!rgPath) {\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\treject(\n\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t\"ripgrep (rg) unavailable and could not be downloaded — use the bash tool to run grep/rg instead\",\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst searchPath = resolveToCwd(searchDir || \".\", cwd);\n\t\t\t\t\t\tconst ops = customOps ?? defaultGrepOperations;\n\t\t\t\t\t\tlet isDirectory: boolean;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tisDirectory = await ops.isDirectory(searchPath);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst contextValue = context && context > 0 ? context : 0;\n\t\t\t\t\t\tconst effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);\n\t\t\t\t\t\tconst formatPath = (filePath: string): string => {\n\t\t\t\t\t\t\tif (isDirectory) {\n\t\t\t\t\t\t\t\tconst relative = path.relative(searchPath, filePath);\n\t\t\t\t\t\t\t\tif (relative && !relative.startsWith(\"..\")) {\n\t\t\t\t\t\t\t\t\treturn relative.replace(/\\\\/g, \"/\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn path.basename(filePath);\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst fileCache = new Map<string, string[]>();\n\t\t\t\t\t\tconst getFileLines = async (filePath: string): Promise<string[]> => {\n\t\t\t\t\t\t\tlet lines = fileCache.get(filePath);\n\t\t\t\t\t\t\tif (!lines) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst content = await ops.readFile(filePath);\n\t\t\t\t\t\t\t\t\tlines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\tlines = [];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfileCache.set(filePath, lines);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn lines;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\t\t\t\t\t\tif (ignoreCase) args.push(\"--ignore-case\");\n\t\t\t\t\t\tif (literal) args.push(\"--fixed-strings\");\n\t\t\t\t\t\tif (glob) args.push(\"--glob\", glob);\n\t\t\t\t\t\targs.push(\"--\", pattern, searchPath);\n\n\t\t\t\t\t\tconst child = spawn(rgPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\t\tlet matchCount = 0;\n\t\t\t\t\t\tlet matchLimitReached = false;\n\t\t\t\t\t\tlet linesTruncated = false;\n\t\t\t\t\t\tlet aborted = false;\n\t\t\t\t\t\tlet killedDueToLimit = false;\n\t\t\t\t\t\tconst outputLines: string[] = [];\n\n\t\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\t\trl.close();\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst stopChild = (dueToLimit = false) => {\n\t\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\t\tkilledDueToLimit = dueToLimit;\n\t\t\t\t\t\t\t\tchild.kill();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\t\tstopChild();\n\t\t\t\t\t\t};\n\t\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst formatBlock = async (filePath: string, lineNumber: number): Promise<string[]> => {\n\t\t\t\t\t\t\tconst relativePath = formatPath(filePath);\n\t\t\t\t\t\t\tconst lines = await getFileLines(filePath);\n\t\t\t\t\t\t\tif (!lines.length) return [`${relativePath}:${lineNumber}: (unable to read file)`];\n\t\t\t\t\t\t\tconst block: string[] = [];\n\t\t\t\t\t\t\tconst start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;\n\t\t\t\t\t\t\tconst end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;\n\t\t\t\t\t\t\tfor (let current = start; current <= end; current++) {\n\t\t\t\t\t\t\t\tconst lineText = lines[current - 1] ?? \"\";\n\t\t\t\t\t\t\t\tconst sanitized = lineText.replace(/\\r/g, \"\");\n\t\t\t\t\t\t\t\tconst isMatchLine = current === lineNumber;\n\t\t\t\t\t\t\t\t// Truncate long lines so grep output stays compact.\n\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\tif (isMatchLine) block.push(`${relativePath}:${current}: ${truncatedText}`);\n\t\t\t\t\t\t\t\telse block.push(`${relativePath}-${current}- ${truncatedText}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn block;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Collect matches during streaming, then format them after rg exits.\n\t\t\t\t\t\tconst matches: Array<{ filePath: string; lineNumber: number; lineText?: string }> = [];\n\t\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\t\tif (!line.trim() || matchCount >= effectiveLimit) return;\n\t\t\t\t\t\t\tlet event: any;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tevent = JSON.parse(line);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (event.type === \"match\") {\n\t\t\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t\t\t\tconst filePath = event.data?.path?.text;\n\t\t\t\t\t\t\t\tconst lineNumber = event.data?.line_number;\n\t\t\t\t\t\t\t\tconst lineText = event.data?.lines?.text;\n\t\t\t\t\t\t\t\tif (filePath && typeof lineNumber === \"number\")\n\t\t\t\t\t\t\t\t\tmatches.push({ filePath, lineNumber, lineText });\n\t\t\t\t\t\t\t\tif (matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\t\t\tmatchLimitReached = true;\n\t\t\t\t\t\t\t\t\tstopChild(true);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));\n\t\t\t\t\t\t});\n\t\t\t\t\t\tchild.on(\"close\", async (code) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!killedDueToLimit && code !== 0 && code !== 1) {\n\t\t\t\t\t\t\t\tconst errorMsg = stderr.trim() || `ripgrep exited with code ${code}`;\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (matchCount === 0) {\n\t\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: \"No matches found\" }], details: undefined }),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Format matches after streaming finishes so custom readFile() backends can be async.\n\t\t\t\t\t\t\tfor (const match of matches) {\n\t\t\t\t\t\t\t\tif (contextValue === 0 && match.lineText !== undefined) {\n\t\t\t\t\t\t\t\t\tconst relativePath = formatPath(match.filePath);\n\t\t\t\t\t\t\t\t\tconst sanitized = match.lineText\n\t\t\t\t\t\t\t\t\t\t.replace(/\\r\\n/g, \"\\n\")\n\t\t\t\t\t\t\t\t\t\t.replace(/\\r/g, \"\")\n\t\t\t\t\t\t\t\t\t\t.replace(/\\n$/, \"\");\n\t\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\t\toutputLines.push(`${relativePath}:${match.lineNumber}: ${truncatedText}`);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst block = await formatBlock(match.filePath, match.lineNumber);\n\t\t\t\t\t\t\t\t\toutputLines.push(...block);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst rawOutput = outputLines.join(\"\\n\");\n\t\t\t\t\t\t\t// Apply byte truncation. There is no line limit here because the match limit already capped rows.\n\t\t\t\t\t\t\tconst truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });\n\t\t\t\t\t\t\tlet output = truncation.content;\n\t\t\t\t\t\t\tconst details: GrepToolDetails = {};\n\t\t\t\t\t\t\t// Build actionable notices for truncation and match limits.\n\t\t\t\t\t\t\tconst notices: string[] = [];\n\t\t\t\t\t\t\tif (matchLimitReached) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`${effectiveLimit} matches limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.matchLimitReached = effectiveLimit;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\t\t\tdetails.truncation = truncation;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (linesTruncated) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`Some lines truncated to ${GREP_MAX_LINE_LENGTH} chars. Use read tool to see full lines`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.linesTruncated = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (notices.length > 0) output += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: output }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsettle(() => reject(err as Error));\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepResult(result as any, options, theme, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createGrepTool(cwd: string, options?: GrepToolOptions): AgentTool<typeof grepSchema> {\n\treturn wrapToolDefinition(createGrepToolDefinition(cwd, options));\n}\n"]}
1
+ {"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../../src/core/tools/grep.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAKjE,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AAKtF,OAAO,EAIN,KAAK,gBAAgB,EAGrB,MAAM,eAAe,CAAC;AAEvB,QAAA,MAAM,UAAU;;;;;;;;EAYd,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAGtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,mEAAmE;IACnE,WAAW,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAClE,2CAA2C;IAC3C,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;CAC7D;AAOD,MAAM,WAAW,eAAe;IAC/B,yEAAyE;IACzE,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B;AAyDD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CAuQhE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG","sourcesContent":["import { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { spawn } from \"child_process\";\nimport { readFileSync, statSync } from \"fs\";\nimport path from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.js\";\nimport { ensureTool } from \"../../utils/tools-manager.js\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.js\";\nimport { compressGrepOutput } from \"./output-compression.js\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"./render-utils.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tformatSize,\n\tGREP_MAX_LINE_LENGTH,\n\ttype TruncationResult,\n\ttruncateHead,\n\ttruncateLine,\n} from \"./truncate.js\";\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Number({ description: \"Number of lines to show before and after each match (default: 0)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nexport type GrepToolInput = Static<typeof grepSchema>;\nconst DEFAULT_LIMIT = 100;\n\nexport interface GrepToolDetails {\n\ttruncation?: TruncationResult;\n\tmatchLimitReached?: number;\n\tlinesTruncated?: boolean;\n}\n\n/**\n * Pluggable operations for the grep tool.\n * Override these to delegate search to remote systems (for example SSH).\n */\nexport interface GrepOperations {\n\t/** Check if path is a directory. Throws if path does not exist. */\n\tisDirectory: (absolutePath: string) => Promise<boolean> | boolean;\n\t/** Read file contents for context lines */\n\treadFile: (absolutePath: string) => Promise<string> | string;\n}\n\nconst defaultGrepOperations: GrepOperations = {\n\tisDirectory: (p) => statSync(p).isDirectory(),\n\treadFile: (p) => readFileSync(p, \"utf-8\"),\n};\n\nexport interface GrepToolOptions {\n\t/** Custom operations for grep. Default: local filesystem plus ripgrep */\n\toperations?: GrepOperations;\n}\n\nfunction formatGrepCall(\n\targs: { pattern: string; path?: string; glob?: string; limit?: number } | undefined,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n): string {\n\tconst pattern = str(args?.pattern);\n\tconst rawPath = str(args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath || \".\") : null;\n\tconst glob = str(args?.glob);\n\tconst limit = args?.limit;\n\tconst invalidArg = invalidArgText(theme);\n\tlet text =\n\t\ttheme.fg(\"toolTitle\", theme.bold(\"grep\")) +\n\t\t\" \" +\n\t\t(pattern === null ? invalidArg : theme.fg(\"accent\", `/${pattern || \"\"}/`)) +\n\t\ttheme.fg(\"toolOutput\", ` in ${path === null ? invalidArg : path}`);\n\tif (glob) text += theme.fg(\"toolOutput\", ` (${glob})`);\n\tif (limit !== undefined) text += theme.fg(\"toolOutput\", ` limit ${limit}`);\n\treturn text;\n}\n\nfunction formatGrepResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: GrepToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: typeof import(\"../../modes/interactive/theme/theme.js\").theme,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result, showImages).trim();\n\tlet text = \"\";\n\tif (output) {\n\t\tconst lines = output.split(\"\\n\");\n\t\tconst maxLines = options.expanded ? lines.length : 15;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")})`;\n\t\t}\n\t}\n\n\tconst matchLimit = result.details?.matchLimitReached;\n\tconst truncation = result.details?.truncation;\n\tconst linesTruncated = result.details?.linesTruncated;\n\tif (matchLimit || truncation?.truncated || linesTruncated) {\n\t\tconst warnings: string[] = [];\n\t\tif (matchLimit) warnings.push(`${matchLimit} matches limit`);\n\t\tif (truncation?.truncated) warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\tif (linesTruncated) warnings.push(\"some lines truncated\");\n\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`)}`;\n\t}\n\treturn text;\n}\n\nexport function createGrepToolDefinition(\n\tcwd: string,\n\toptions?: GrepToolOptions,\n): ToolDefinition<typeof grepSchema, GrepToolDetails | undefined> {\n\tconst customOps = options?.operations;\n\treturn {\n\t\tname: \"grep\",\n\t\tlabel: \"grep\",\n\t\tdescription: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} matches or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Long lines are truncated to ${GREP_MAX_LINE_LENGTH} chars.`,\n\t\tpromptSnippet: \"Search file contents for patterns (respects .gitignore)\",\n\t\tparameters: grepSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{\n\t\t\t\tpattern,\n\t\t\t\tpath: searchDir,\n\t\t\t\tglob,\n\t\t\t\tignoreCase,\n\t\t\t\tliteral,\n\t\t\t\tcontext,\n\t\t\t\tlimit,\n\t\t\t}: {\n\t\t\t\tpattern: string;\n\t\t\t\tpath?: string;\n\t\t\t\tglob?: string;\n\t\t\t\tignoreCase?: boolean;\n\t\t\t\tliteral?: boolean;\n\t\t\t\tcontext?: number;\n\t\t\t\tlimit?: number;\n\t\t\t},\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\t\tif (!settled) {\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\tfn();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rgPath = await ensureTool(\"rg\", true);\n\t\t\t\t\t\tif (!rgPath) {\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\treject(\n\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t\"ripgrep (rg) unavailable and could not be downloaded — use the bash tool to run grep/rg instead\",\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst searchPath = resolveToCwd(searchDir || \".\", cwd);\n\t\t\t\t\t\tconst ops = customOps ?? defaultGrepOperations;\n\t\t\t\t\t\tlet isDirectory: boolean;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tisDirectory = await ops.isDirectory(searchPath);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst contextValue = context && context > 0 ? context : 0;\n\t\t\t\t\t\tconst effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);\n\t\t\t\t\t\tconst formatPath = (filePath: string): string => {\n\t\t\t\t\t\t\tif (isDirectory) {\n\t\t\t\t\t\t\t\tconst relative = path.relative(searchPath, filePath);\n\t\t\t\t\t\t\t\tif (relative && !relative.startsWith(\"..\")) {\n\t\t\t\t\t\t\t\t\treturn relative.replace(/\\\\/g, \"/\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn path.basename(filePath);\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst fileCache = new Map<string, string[]>();\n\t\t\t\t\t\tconst getFileLines = async (filePath: string): Promise<string[]> => {\n\t\t\t\t\t\t\tlet lines = fileCache.get(filePath);\n\t\t\t\t\t\t\tif (!lines) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst content = await ops.readFile(filePath);\n\t\t\t\t\t\t\t\t\tlines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\tlines = [];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfileCache.set(filePath, lines);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn lines;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\t\t\t\t\t\tif (ignoreCase) args.push(\"--ignore-case\");\n\t\t\t\t\t\tif (literal) args.push(\"--fixed-strings\");\n\t\t\t\t\t\tif (glob) args.push(\"--glob\", glob);\n\t\t\t\t\t\targs.push(\"--\", pattern, searchPath);\n\n\t\t\t\t\t\tconst child = spawn(rgPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\t\tlet matchCount = 0;\n\t\t\t\t\t\tlet matchLimitReached = false;\n\t\t\t\t\t\tlet linesTruncated = false;\n\t\t\t\t\t\tlet aborted = false;\n\t\t\t\t\t\tlet killedDueToLimit = false;\n\t\t\t\t\t\tconst outputLines: string[] = [];\n\n\t\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\t\trl.close();\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst stopChild = (dueToLimit = false) => {\n\t\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\t\tkilledDueToLimit = dueToLimit;\n\t\t\t\t\t\t\t\tchild.kill();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\t\tstopChild();\n\t\t\t\t\t\t};\n\t\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst formatBlock = async (filePath: string, lineNumber: number): Promise<string[]> => {\n\t\t\t\t\t\t\tconst relativePath = formatPath(filePath);\n\t\t\t\t\t\t\tconst lines = await getFileLines(filePath);\n\t\t\t\t\t\t\tif (!lines.length) return [`${relativePath}:${lineNumber}: (unable to read file)`];\n\t\t\t\t\t\t\tconst block: string[] = [];\n\t\t\t\t\t\t\tconst start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;\n\t\t\t\t\t\t\tconst end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;\n\t\t\t\t\t\t\tfor (let current = start; current <= end; current++) {\n\t\t\t\t\t\t\t\tconst lineText = lines[current - 1] ?? \"\";\n\t\t\t\t\t\t\t\tconst sanitized = lineText.replace(/\\r/g, \"\");\n\t\t\t\t\t\t\t\tconst isMatchLine = current === lineNumber;\n\t\t\t\t\t\t\t\t// Truncate long lines so grep output stays compact.\n\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\tif (isMatchLine) block.push(`${relativePath}:${current}: ${truncatedText}`);\n\t\t\t\t\t\t\t\telse block.push(`${relativePath}-${current}- ${truncatedText}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn block;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Collect matches during streaming, then format them after rg exits.\n\t\t\t\t\t\tconst matches: Array<{ filePath: string; lineNumber: number; lineText?: string }> = [];\n\t\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\t\tif (!line.trim() || matchCount >= effectiveLimit) return;\n\t\t\t\t\t\t\tlet event: any;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tevent = JSON.parse(line);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (event.type === \"match\") {\n\t\t\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t\t\t\tconst filePath = event.data?.path?.text;\n\t\t\t\t\t\t\t\tconst lineNumber = event.data?.line_number;\n\t\t\t\t\t\t\t\tconst lineText = event.data?.lines?.text;\n\t\t\t\t\t\t\t\tif (filePath && typeof lineNumber === \"number\")\n\t\t\t\t\t\t\t\t\tmatches.push({ filePath, lineNumber, lineText });\n\t\t\t\t\t\t\t\tif (matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\t\t\tmatchLimitReached = true;\n\t\t\t\t\t\t\t\t\tstopChild(true);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));\n\t\t\t\t\t\t});\n\t\t\t\t\t\tchild.on(\"close\", async (code) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!killedDueToLimit && code !== 0 && code !== 1) {\n\t\t\t\t\t\t\t\tconst errorMsg = stderr.trim() || `ripgrep exited with code ${code}`;\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (matchCount === 0) {\n\t\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: \"No matches found\" }], details: undefined }),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Format matches after streaming finishes so custom readFile() backends can be async.\n\t\t\t\t\t\t\tfor (const match of matches) {\n\t\t\t\t\t\t\t\tif (contextValue === 0 && match.lineText !== undefined) {\n\t\t\t\t\t\t\t\t\tconst relativePath = formatPath(match.filePath);\n\t\t\t\t\t\t\t\t\tconst sanitized = match.lineText\n\t\t\t\t\t\t\t\t\t\t.replace(/\\r\\n/g, \"\\n\")\n\t\t\t\t\t\t\t\t\t\t.replace(/\\r/g, \"\")\n\t\t\t\t\t\t\t\t\t\t.replace(/\\n$/, \"\");\n\t\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\t\toutputLines.push(`${relativePath}:${match.lineNumber}: ${truncatedText}`);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst block = await formatBlock(match.filePath, match.lineNumber);\n\t\t\t\t\t\t\t\t\toutputLines.push(...block);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst rawOutput = outputLines.join(\"\\n\");\n\t\t\t\t\t\t\t// Apply lossless compression then byte truncation.\n\t\t\t\t\t\t\tconst compressedOutput = compressGrepOutput(rawOutput);\n\t\t\t\t\t\t\t// Apply byte truncation. There is no line limit here because the match limit already capped rows.\n\t\t\t\t\t\t\tconst truncation = truncateHead(compressedOutput, { maxLines: Number.MAX_SAFE_INTEGER });\n\t\t\t\t\t\t\tlet output = truncation.content;\n\t\t\t\t\t\t\tconst details: GrepToolDetails = {};\n\t\t\t\t\t\t\t// Build actionable notices for truncation and match limits.\n\t\t\t\t\t\t\tconst notices: string[] = [];\n\t\t\t\t\t\t\tif (matchLimitReached) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`${effectiveLimit} matches limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.matchLimitReached = effectiveLimit;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\t\t\tdetails.truncation = truncation;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (linesTruncated) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`Some lines truncated to ${GREP_MAX_LINE_LENGTH} chars. Use read tool to see full lines`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.linesTruncated = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (notices.length > 0) output += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: output }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsettle(() => reject(err as Error));\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepResult(result as any, options, theme, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createGrepTool(cwd: string, options?: GrepToolOptions): AgentTool<typeof grepSchema> {\n\treturn wrapToolDefinition(createGrepToolDefinition(cwd, options));\n}\n"]}