@within-7/minto 0.3.0 → 0.3.4

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 (84) hide show
  1. package/dist/commands/setup.js +40 -2
  2. package/dist/commands/setup.js.map +2 -2
  3. package/dist/components/SubagentProgress.js +10 -2
  4. package/dist/components/SubagentProgress.js.map +2 -2
  5. package/dist/constants/prompts.js +22 -1
  6. package/dist/constants/prompts.js.map +2 -2
  7. package/dist/entrypoints/cli.js +15 -9
  8. package/dist/entrypoints/cli.js.map +2 -2
  9. package/dist/permissions.js +121 -2
  10. package/dist/permissions.js.map +2 -2
  11. package/dist/screens/ResumeConversation.js +2 -0
  12. package/dist/screens/ResumeConversation.js.map +2 -2
  13. package/dist/services/taskStore.js +205 -0
  14. package/dist/services/taskStore.js.map +7 -0
  15. package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js +40 -3
  16. package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js.map +2 -2
  17. package/dist/tools/BashTool/BashTool.js +21 -4
  18. package/dist/tools/BashTool/BashTool.js.map +2 -2
  19. package/dist/tools/BashTool/prompt.js +6 -0
  20. package/dist/tools/BashTool/prompt.js.map +2 -2
  21. package/dist/tools/FileEditTool/FileEditTool.js +24 -9
  22. package/dist/tools/FileEditTool/FileEditTool.js.map +2 -2
  23. package/dist/tools/FileEditTool/prompt.js +4 -1
  24. package/dist/tools/FileEditTool/prompt.js.map +2 -2
  25. package/dist/tools/FileEditTool/utils.js +10 -4
  26. package/dist/tools/FileEditTool/utils.js.map +2 -2
  27. package/dist/tools/FileReadTool/FileReadTool.js +1 -1
  28. package/dist/tools/FileReadTool/FileReadTool.js.map +1 -1
  29. package/dist/tools/FileReadTool/prompt.js +16 -1
  30. package/dist/tools/FileReadTool/prompt.js.map +2 -2
  31. package/dist/tools/FileWriteTool/FileWriteTool.js +1 -1
  32. package/dist/tools/FileWriteTool/FileWriteTool.js.map +1 -1
  33. package/dist/tools/FileWriteTool/prompt.js +8 -1
  34. package/dist/tools/FileWriteTool/prompt.js.map +2 -2
  35. package/dist/tools/GlobTool/prompt.js +12 -1
  36. package/dist/tools/GlobTool/prompt.js.map +2 -2
  37. package/dist/tools/GrepTool/GrepTool.js +333 -65
  38. package/dist/tools/GrepTool/GrepTool.js.map +2 -2
  39. package/dist/tools/GrepTool/prompt.js +15 -8
  40. package/dist/tools/GrepTool/prompt.js.map +2 -2
  41. package/dist/tools/NotebookEditTool/NotebookEditTool.js +57 -45
  42. package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
  43. package/dist/tools/NotebookEditTool/prompt.js +1 -1
  44. package/dist/tools/NotebookEditTool/prompt.js.map +1 -1
  45. package/dist/tools/TaskCreateTool/TaskCreateTool.js +102 -0
  46. package/dist/tools/TaskCreateTool/TaskCreateTool.js.map +7 -0
  47. package/dist/tools/TaskCreateTool/prompt.js +47 -0
  48. package/dist/tools/TaskCreateTool/prompt.js.map +7 -0
  49. package/dist/tools/TaskGetTool/TaskGetTool.js +115 -0
  50. package/dist/tools/TaskGetTool/TaskGetTool.js.map +7 -0
  51. package/dist/tools/TaskGetTool/prompt.js +28 -0
  52. package/dist/tools/TaskGetTool/prompt.js.map +7 -0
  53. package/dist/tools/TaskListTool/TaskListTool.js +102 -0
  54. package/dist/tools/TaskListTool/TaskListTool.js.map +7 -0
  55. package/dist/tools/TaskListTool/prompt.js +27 -0
  56. package/dist/tools/TaskListTool/prompt.js.map +7 -0
  57. package/dist/tools/TaskStopTool/TaskStopTool.js +150 -0
  58. package/dist/tools/TaskStopTool/TaskStopTool.js.map +7 -0
  59. package/dist/tools/TaskStopTool/prompt.js +15 -0
  60. package/dist/tools/TaskStopTool/prompt.js.map +7 -0
  61. package/dist/tools/TaskTool/TaskTool.js +41 -1
  62. package/dist/tools/TaskTool/TaskTool.js.map +2 -2
  63. package/dist/tools/TaskUpdateTool/TaskUpdateTool.js +134 -0
  64. package/dist/tools/TaskUpdateTool/TaskUpdateTool.js.map +7 -0
  65. package/dist/tools/TaskUpdateTool/prompt.js +81 -0
  66. package/dist/tools/TaskUpdateTool/prompt.js.map +7 -0
  67. package/dist/tools/URLFetcherTool/prompt.js +1 -1
  68. package/dist/tools/URLFetcherTool/prompt.js.map +1 -1
  69. package/dist/tools.js +12 -0
  70. package/dist/tools.js.map +2 -2
  71. package/dist/utils/config.js.map +2 -2
  72. package/dist/utils/model.js +15 -2
  73. package/dist/utils/model.js.map +2 -2
  74. package/dist/utils/ripgrep.js +53 -1
  75. package/dist/utils/ripgrep.js.map +2 -2
  76. package/dist/utils/teamConfig.js +160 -0
  77. package/dist/utils/teamConfig.js.map +3 -3
  78. package/dist/utils/terminal.js +12 -0
  79. package/dist/utils/terminal.js.map +2 -2
  80. package/dist/utils/tooling/safeRender.js +13 -14
  81. package/dist/utils/tooling/safeRender.js.map +2 -2
  82. package/dist/version.js +2 -2
  83. package/dist/version.js.map +1 -1
  84. package/package.json +20 -28
@@ -7,16 +7,49 @@ import { FallbackToolUseRejectedMessage } from "../../components/FallbackToolUse
7
7
  import { CollapsibleHint } from "../../components/CollapsibleHint.js";
8
8
  import { getCwd } from "../../utils/state.js";
9
9
  import { getAbsolutePath, getAbsoluteAndRelativePaths } from "../../utils/file.js";
10
- import { ripGrepStreaming } from "../../utils/ripgrep.js";
10
+ import { ripGrepStreaming, ripGrepStreamingWithContent } from "../../utils/ripgrep.js";
11
11
  import { DESCRIPTION, TOOL_NAME_FOR_PROMPT } from "./prompt.js";
12
12
  import { hasReadPermission } from "../../utils/permissions/filesystem.js";
13
13
  const inputSchema = z.strictObject({
14
14
  pattern: z.string().describe("The regular expression pattern to search for in file contents"),
15
15
  path: z.string().optional().describe(
16
- "The directory to search in. Defaults to the current working directory."
16
+ "File or directory to search in (rg PATH). Defaults to current working directory."
17
17
  ),
18
+ // Backward compatible: 'include' is mapped to 'glob'
18
19
  include: z.string().optional().describe(
19
- 'File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")'
20
+ 'File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}") - DEPRECATED: use glob instead'
21
+ ),
22
+ glob: z.string().optional().describe(
23
+ 'Glob pattern to filter files (e.g. "*.js", "*.{ts,tsx}") - maps to rg --glob'
24
+ ),
25
+ type: z.string().optional().describe(
26
+ "File type to search (rg --type). Common types: js, py, rust, go, java, etc. More efficient than include for standard file types."
27
+ ),
28
+ output_mode: z.enum(["content", "files_with_matches", "count"]).optional().describe(
29
+ 'Output mode: "content" shows matching lines, "files_with_matches" shows file paths (default), "count" shows match counts'
30
+ ),
31
+ multiline: z.boolean().optional().describe(
32
+ "Enable multiline mode where . matches newlines and patterns can span lines (rg -U --multiline-dotall). Default: false."
33
+ ),
34
+ context: z.number().optional().describe(
35
+ 'Number of lines to show before and after each match (rg -C). Requires output_mode: "content", ignored otherwise.'
36
+ ),
37
+ "-A": z.number().optional().describe(
38
+ 'Number of lines to show after each match (rg -A). Requires output_mode: "content", ignored otherwise.'
39
+ ),
40
+ "-B": z.number().optional().describe(
41
+ 'Number of lines to show before each match (rg -B). Requires output_mode: "content", ignored otherwise.'
42
+ ),
43
+ "-C": z.number().optional().describe("Alias for context."),
44
+ "-n": z.boolean().optional().describe(
45
+ 'Show line numbers in output (rg -n). Requires output_mode: "content", ignored otherwise. Defaults to true.'
46
+ ),
47
+ "-i": z.boolean().optional().describe("Case insensitive search (rg -i)"),
48
+ head_limit: z.number().optional().describe(
49
+ 'Limit output to first N lines/entries, equivalent to "| head -N". Works across all output modes: content (limits output lines), files_with_matches (limits file paths), count (limits count entries). Defaults to 0 (unlimited).'
50
+ ),
51
+ offset: z.number().optional().describe(
52
+ 'Skip first N lines/entries before applying head_limit, equivalent to "| tail -n +N | head -N". Works across all output modes. Defaults to 0.'
20
53
  )
21
54
  });
22
55
  const MAX_RESULTS = 100;
@@ -44,9 +77,43 @@ const GrepTool = {
44
77
  async prompt() {
45
78
  return DESCRIPTION;
46
79
  },
47
- renderToolUseMessage({ pattern, path, include }, { verbose }) {
80
+ renderToolUseMessage(input, { verbose }) {
81
+ const {
82
+ pattern,
83
+ path,
84
+ include,
85
+ glob,
86
+ type,
87
+ output_mode,
88
+ multiline,
89
+ context,
90
+ "-i": caseInsensitive
91
+ } = input;
48
92
  const { absolutePath, relativePath } = getAbsoluteAndRelativePaths(path);
49
- return `pattern: "${pattern}"${relativePath || verbose ? `, path: "${verbose ? absolutePath : relativePath}"` : ""}${include ? `, include: "${include}"` : ""}`;
93
+ const effectiveGlob = glob || include;
94
+ const parts = [`pattern: "${pattern}"`];
95
+ if (relativePath || verbose) {
96
+ parts.push(`path: "${verbose ? absolutePath : relativePath}"`);
97
+ }
98
+ if (effectiveGlob) {
99
+ parts.push(`glob: "${effectiveGlob}"`);
100
+ }
101
+ if (type) {
102
+ parts.push(`type: "${type}"`);
103
+ }
104
+ if (output_mode && output_mode !== "files_with_matches") {
105
+ parts.push(`mode: "${output_mode}"`);
106
+ }
107
+ if (multiline) {
108
+ parts.push("multiline");
109
+ }
110
+ if (context) {
111
+ parts.push(`context: ${context}`);
112
+ }
113
+ if (caseInsensitive === false) {
114
+ parts.push("case-sensitive");
115
+ }
116
+ return parts.join(", ");
50
117
  },
51
118
  renderToolUseRejectedMessage() {
52
119
  return /* @__PURE__ */ React.createElement(FallbackToolUseRejectedMessage, null);
@@ -59,77 +126,278 @@ const GrepTool = {
59
126
  output = output;
60
127
  }
61
128
  const numFiles = output?.numFiles ?? 0;
129
+ const numMatches = output?.numMatches ?? 0;
62
130
  const durationMs = output?.durationMs ?? 0;
63
- const showExpandHint = !verbose && numFiles > 5;
64
- return /* @__PURE__ */ React.createElement(Box, { justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React.createElement(Box, { flexDirection: "row" }, /* @__PURE__ */ React.createElement(Text, null, "\xA0\xA0\u23BF \xA0Found "), /* @__PURE__ */ React.createElement(Text, { bold: true }, numFiles, " "), /* @__PURE__ */ React.createElement(Text, null, numFiles === 0 || numFiles > 1 ? "files" : "file"), showExpandHint && /* @__PURE__ */ React.createElement(CollapsibleHint, { canExpand: true })), /* @__PURE__ */ React.createElement(Cost, { costUSD: 0, durationMs, debug: false }));
65
- },
66
- renderResultForAssistant({ numFiles, filenames }) {
67
- if (numFiles === 0) {
68
- return "No files found";
131
+ const outputMode = output?.outputMode ?? "files_with_matches";
132
+ if (outputMode === "content") {
133
+ const showExpandHint = !verbose && numMatches > 10;
134
+ return /* @__PURE__ */ React.createElement(Box, { justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React.createElement(Box, { flexDirection: "row" }, /* @__PURE__ */ React.createElement(Text, null, "\xA0\xA0\u23BF \xA0Found "), /* @__PURE__ */ React.createElement(Text, { bold: true }, numMatches, " "), /* @__PURE__ */ React.createElement(Text, null, numMatches === 1 ? "match" : "matches", " in "), /* @__PURE__ */ React.createElement(Text, { bold: true }, numFiles, " "), /* @__PURE__ */ React.createElement(Text, null, numFiles === 1 ? "file" : "files"), showExpandHint && /* @__PURE__ */ React.createElement(CollapsibleHint, { canExpand: true })), /* @__PURE__ */ React.createElement(Cost, { costUSD: 0, durationMs, debug: false }));
135
+ } else if (outputMode === "count") {
136
+ const showExpandHint = !verbose && numFiles > 5;
137
+ return /* @__PURE__ */ React.createElement(Box, { justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React.createElement(Box, { flexDirection: "row" }, /* @__PURE__ */ React.createElement(Text, null, "\xA0\xA0\u23BF \xA0Counted "), /* @__PURE__ */ React.createElement(Text, { bold: true }, numMatches, " "), /* @__PURE__ */ React.createElement(Text, null, numMatches === 1 ? "match" : "matches", " in "), /* @__PURE__ */ React.createElement(Text, { bold: true }, numFiles, " "), /* @__PURE__ */ React.createElement(Text, null, numFiles === 1 ? "file" : "files"), showExpandHint && /* @__PURE__ */ React.createElement(CollapsibleHint, { canExpand: true })), /* @__PURE__ */ React.createElement(Cost, { costUSD: 0, durationMs, debug: false }));
138
+ } else {
139
+ const showExpandHint = !verbose && numFiles > 5;
140
+ return /* @__PURE__ */ React.createElement(Box, { justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React.createElement(Box, { flexDirection: "row" }, /* @__PURE__ */ React.createElement(Text, null, "\xA0\xA0\u23BF \xA0Found "), /* @__PURE__ */ React.createElement(Text, { bold: true }, numFiles, " "), /* @__PURE__ */ React.createElement(Text, null, numFiles === 0 || numFiles > 1 ? "files" : "file"), showExpandHint && /* @__PURE__ */ React.createElement(CollapsibleHint, { canExpand: true })), /* @__PURE__ */ React.createElement(Cost, { costUSD: 0, durationMs, debug: false }));
69
141
  }
70
- let result = `Found ${numFiles} file${numFiles === 1 ? "" : "s"}
142
+ },
143
+ renderResultForAssistant(output) {
144
+ const { numFiles, filenames, content, counts, numMatches, outputMode } = output;
145
+ if (outputMode === "content") {
146
+ if (!content || numMatches === 0) {
147
+ return "No matches found";
148
+ }
149
+ let result = content;
150
+ if (numMatches && numMatches > MAX_RESULTS) {
151
+ result += "\n(Results are truncated. Consider using a more specific path or pattern.)";
152
+ }
153
+ return result;
154
+ } else if (outputMode === "count") {
155
+ if (!counts || counts.length === 0) {
156
+ return "No matches found";
157
+ }
158
+ const totalMatches = counts.reduce((sum, c) => sum + c.count, 0);
159
+ let result = `Found ${totalMatches} match${totalMatches === 1 ? "" : "es"} in ${numFiles} file${numFiles === 1 ? "" : "s"}
160
+ `;
161
+ result += counts.slice(0, MAX_RESULTS).map((c) => `${c.file}:${c.count}`).join("\n");
162
+ if (counts.length > MAX_RESULTS) {
163
+ result += "\n(Results are truncated. Consider using a more specific path or pattern.)";
164
+ }
165
+ return result;
166
+ } else {
167
+ if (numFiles === 0) {
168
+ return "No files found";
169
+ }
170
+ let result = `Found ${numFiles} file${numFiles === 1 ? "" : "s"}
71
171
  ${filenames.slice(0, MAX_RESULTS).join("\n")}`;
72
- if (numFiles > MAX_RESULTS) {
73
- result += "\n(Results are truncated. Consider using a more specific path or pattern.)";
172
+ if (numFiles > MAX_RESULTS) {
173
+ result += "\n(Results are truncated. Consider using a more specific path or pattern.)";
174
+ }
175
+ return result;
74
176
  }
75
- return result;
76
177
  },
77
- async *call({ pattern, path, include }, { abortController }) {
178
+ async *call(input, { abortController }) {
179
+ const {
180
+ pattern,
181
+ path,
182
+ include,
183
+ glob,
184
+ type,
185
+ output_mode = "files_with_matches",
186
+ multiline,
187
+ context,
188
+ "-A": afterContext,
189
+ "-B": beforeContext,
190
+ "-C": contextAlias,
191
+ "-n": lineNumbers = true,
192
+ "-i": caseInsensitive = true,
193
+ head_limit = 0,
194
+ offset = 0
195
+ } = input;
78
196
  const start = Date.now();
79
197
  const absolutePath = getAbsolutePath(path) || getCwd();
80
- const args = ["-li", pattern];
81
- if (include) {
82
- args.push("--glob", include);
83
- }
84
- const matchedFiles = [];
85
- let lastProgressUpdate = 0;
86
- const PROGRESS_UPDATE_INTERVAL = 100;
87
- for await (const chunk of ripGrepStreaming(
88
- args,
89
- absolutePath,
90
- abortController.signal
91
- )) {
92
- if (chunk.type === "match") {
93
- matchedFiles.push(chunk.file);
94
- const now = Date.now();
95
- if (now - lastProgressUpdate >= PROGRESS_UPDATE_INTERVAL) {
96
- lastProgressUpdate = now;
97
- yield {
98
- type: "progress",
99
- content: {
100
- type: "streaming",
101
- toolName: "Search",
102
- stdout: `Searching... Found ${matchedFiles.length} file${matchedFiles.length === 1 ? "" : "s"}`,
103
- stderr: "",
104
- isStreaming: true
198
+ const effectiveGlob = glob || include;
199
+ const effectiveContext = context ?? contextAlias;
200
+ const args = [];
201
+ if (caseInsensitive) {
202
+ args.push("-i");
203
+ }
204
+ if (multiline) {
205
+ args.push("-U", "--multiline-dotall");
206
+ }
207
+ if (type) {
208
+ args.push("--type", type);
209
+ }
210
+ if (effectiveGlob) {
211
+ args.push("--glob", effectiveGlob);
212
+ }
213
+ if (output_mode === "files_with_matches") {
214
+ args.push("-l");
215
+ } else if (output_mode === "count") {
216
+ args.push("-c");
217
+ } else if (output_mode === "content") {
218
+ if (lineNumbers) {
219
+ args.push("-n");
220
+ }
221
+ if (effectiveContext !== void 0 && effectiveContext > 0) {
222
+ args.push("-C", String(effectiveContext));
223
+ } else {
224
+ if (beforeContext !== void 0 && beforeContext > 0) {
225
+ args.push("-B", String(beforeContext));
226
+ }
227
+ if (afterContext !== void 0 && afterContext > 0) {
228
+ args.push("-A", String(afterContext));
229
+ }
230
+ }
231
+ }
232
+ args.push(pattern);
233
+ if (output_mode === "content") {
234
+ const contentLines = [];
235
+ const filesWithMatches = /* @__PURE__ */ new Set();
236
+ let matchCount = 0;
237
+ let lastProgressUpdate = 0;
238
+ const PROGRESS_UPDATE_INTERVAL = 100;
239
+ for await (const chunk of ripGrepStreamingWithContent(
240
+ args,
241
+ absolutePath,
242
+ abortController.signal
243
+ )) {
244
+ if (chunk.type === "line") {
245
+ contentLines.push(chunk.line);
246
+ const fileMatch = chunk.line.match(/^([^:]+):(\d+):/);
247
+ if (fileMatch) {
248
+ filesWithMatches.add(fileMatch[1]);
249
+ matchCount++;
250
+ }
251
+ const now = Date.now();
252
+ if (now - lastProgressUpdate >= PROGRESS_UPDATE_INTERVAL) {
253
+ lastProgressUpdate = now;
254
+ yield {
255
+ type: "progress",
256
+ content: {
257
+ type: "streaming",
258
+ toolName: "Search",
259
+ stdout: `Searching... Found ${matchCount} match${matchCount === 1 ? "" : "es"} in ${filesWithMatches.size} file${filesWithMatches.size === 1 ? "" : "s"}`,
260
+ stderr: "",
261
+ isStreaming: true
262
+ }
263
+ };
264
+ }
265
+ }
266
+ }
267
+ let resultLines = contentLines;
268
+ if (offset > 0) {
269
+ resultLines = resultLines.slice(offset);
270
+ }
271
+ if (head_limit > 0) {
272
+ resultLines = resultLines.slice(0, head_limit);
273
+ }
274
+ const output = {
275
+ filenames: Array.from(filesWithMatches),
276
+ content: resultLines.join("\n"),
277
+ numMatches: matchCount,
278
+ numFiles: filesWithMatches.size,
279
+ durationMs: Date.now() - start,
280
+ outputMode: "content"
281
+ };
282
+ yield {
283
+ type: "result",
284
+ resultForAssistant: this.renderResultForAssistant(output),
285
+ data: output
286
+ };
287
+ } else if (output_mode === "count") {
288
+ const counts = [];
289
+ let lastProgressUpdate = 0;
290
+ const PROGRESS_UPDATE_INTERVAL = 100;
291
+ for await (const chunk of ripGrepStreamingWithContent(
292
+ args,
293
+ absolutePath,
294
+ abortController.signal
295
+ )) {
296
+ if (chunk.type === "line") {
297
+ const match = chunk.line.match(/^(.+):(\d+)$/);
298
+ if (match) {
299
+ counts.push({ file: match[1], count: parseInt(match[2], 10) });
300
+ const now = Date.now();
301
+ if (now - lastProgressUpdate >= PROGRESS_UPDATE_INTERVAL) {
302
+ lastProgressUpdate = now;
303
+ yield {
304
+ type: "progress",
305
+ content: {
306
+ type: "streaming",
307
+ toolName: "Search",
308
+ stdout: `Searching... Found matches in ${counts.length} file${counts.length === 1 ? "" : "s"}`,
309
+ stderr: "",
310
+ isStreaming: true
311
+ }
312
+ };
105
313
  }
106
- };
314
+ }
107
315
  }
108
316
  }
317
+ counts.sort((a, b) => {
318
+ const countDiff = b.count - a.count;
319
+ if (countDiff !== 0) return countDiff;
320
+ return a.file.localeCompare(b.file);
321
+ });
322
+ let resultCounts = counts;
323
+ if (offset > 0) {
324
+ resultCounts = resultCounts.slice(offset);
325
+ }
326
+ if (head_limit > 0) {
327
+ resultCounts = resultCounts.slice(0, head_limit);
328
+ }
329
+ const totalMatches = resultCounts.reduce((sum, c) => sum + c.count, 0);
330
+ const output = {
331
+ filenames: resultCounts.map((c) => c.file),
332
+ counts: resultCounts,
333
+ numMatches: totalMatches,
334
+ numFiles: resultCounts.length,
335
+ durationMs: Date.now() - start,
336
+ outputMode: "count"
337
+ };
338
+ yield {
339
+ type: "result",
340
+ resultForAssistant: this.renderResultForAssistant(output),
341
+ data: output
342
+ };
343
+ } else {
344
+ const matchedFiles = [];
345
+ let lastProgressUpdate = 0;
346
+ const PROGRESS_UPDATE_INTERVAL = 100;
347
+ for await (const chunk of ripGrepStreaming(
348
+ args,
349
+ absolutePath,
350
+ abortController.signal
351
+ )) {
352
+ if (chunk.type === "match") {
353
+ matchedFiles.push(chunk.file);
354
+ const now = Date.now();
355
+ if (now - lastProgressUpdate >= PROGRESS_UPDATE_INTERVAL) {
356
+ lastProgressUpdate = now;
357
+ yield {
358
+ type: "progress",
359
+ content: {
360
+ type: "streaming",
361
+ toolName: "Search",
362
+ stdout: `Searching... Found ${matchedFiles.length} file${matchedFiles.length === 1 ? "" : "s"}`,
363
+ stderr: "",
364
+ isStreaming: true
365
+ }
366
+ };
367
+ }
368
+ }
369
+ }
370
+ const stats = await Promise.all(
371
+ matchedFiles.map((_) => stat(_).catch(() => null))
372
+ );
373
+ let matches = matchedFiles.map((file, i) => [file, stats[i]]).filter(([, s]) => s !== null).sort((a, b) => {
374
+ if (process.env.NODE_ENV === "test") {
375
+ return a[0].localeCompare(b[0]);
376
+ }
377
+ const timeComparison = (b[1]?.mtimeMs ?? 0) - (a[1]?.mtimeMs ?? 0);
378
+ if (timeComparison === 0) {
379
+ return a[0].localeCompare(b[0]);
380
+ }
381
+ return timeComparison;
382
+ }).map((_) => _[0]);
383
+ if (offset > 0) {
384
+ matches = matches.slice(offset);
385
+ }
386
+ if (head_limit > 0) {
387
+ matches = matches.slice(0, head_limit);
388
+ }
389
+ const output = {
390
+ filenames: matches,
391
+ durationMs: Date.now() - start,
392
+ numFiles: matches.length,
393
+ outputMode: "files_with_matches"
394
+ };
395
+ yield {
396
+ type: "result",
397
+ resultForAssistant: this.renderResultForAssistant(output),
398
+ data: output
399
+ };
109
400
  }
110
- const stats = await Promise.all(
111
- matchedFiles.map((_) => stat(_).catch(() => null))
112
- );
113
- const matches = matchedFiles.map((file, i) => [file, stats[i]]).filter(([, s]) => s !== null).sort((a, b) => {
114
- if (process.env.NODE_ENV === "test") {
115
- return a[0].localeCompare(b[0]);
116
- }
117
- const timeComparison = (b[1]?.mtimeMs ?? 0) - (a[1]?.mtimeMs ?? 0);
118
- if (timeComparison === 0) {
119
- return a[0].localeCompare(b[0]);
120
- }
121
- return timeComparison;
122
- }).map((_) => _[0]);
123
- const output = {
124
- filenames: matches,
125
- durationMs: Date.now() - start,
126
- numFiles: matches.length
127
- };
128
- yield {
129
- type: "result",
130
- resultForAssistant: this.renderResultForAssistant(output),
131
- data: output
132
- };
133
401
  }
134
402
  };
135
403
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/tools/GrepTool/GrepTool.tsx"],
4
- "sourcesContent": ["import { stat } from 'fs/promises'\nimport { Box, Text } from 'ink'\nimport React from 'react'\nimport { z } from 'zod'\nimport { Cost } from '@components/Cost'\nimport { FallbackToolUseRejectedMessage } from '@components/FallbackToolUseRejectedMessage'\nimport { CollapsibleHint } from '@components/CollapsibleHint'\nimport { Tool } from '@tool'\nimport { getCwd } from '@utils/state'\nimport { getAbsolutePath, getAbsoluteAndRelativePaths } from '@utils/file'\nimport { ripGrepStreaming, RipGrepStreamYield } from '@utils/ripgrep'\nimport { DESCRIPTION, TOOL_NAME_FOR_PROMPT } from './prompt'\nimport { hasReadPermission } from '@utils/permissions/filesystem'\n\nconst inputSchema = z.strictObject({\n pattern: z\n .string()\n .describe('The regular expression pattern to search for in file contents'),\n path: z\n .string()\n .optional()\n .describe(\n 'The directory to search in. Defaults to the current working directory.',\n ),\n include: z\n .string()\n .optional()\n .describe(\n 'File pattern to include in the search (e.g. \"*.js\", \"*.{ts,tsx}\")',\n ),\n})\n\nconst MAX_RESULTS = 100\n\ntype Input = typeof inputSchema\ntype Output = {\n durationMs: number\n numFiles: number\n filenames: string[]\n}\n\nexport const GrepTool = {\n name: TOOL_NAME_FOR_PROMPT,\n async description() {\n return DESCRIPTION\n },\n userFacingName() {\n return 'Search'\n },\n inputSchema,\n isReadOnly() {\n return true\n },\n isConcurrencySafe() {\n return true // GrepTool is read-only, safe for concurrent execution\n },\n async isEnabled() {\n return true\n },\n needsPermissions({ path }) {\n return !hasReadPermission(path || getCwd())\n },\n async prompt() {\n return DESCRIPTION\n },\n renderToolUseMessage({ pattern, path, include }, { verbose }) {\n const { absolutePath, relativePath } = getAbsoluteAndRelativePaths(path)\n return `pattern: \"${pattern}\"${relativePath || verbose ? `, path: \"${verbose ? absolutePath : relativePath}\"` : ''}${include ? `, include: \"${include}\"` : ''}`\n },\n renderToolUseRejectedMessage() {\n return <FallbackToolUseRejectedMessage />\n },\n renderToolResultMessage(output, { verbose }) {\n // Guard against undefined or null output\n if (!output) {\n return (\n <Box justifyContent=\"space-between\" width=\"100%\">\n <Box flexDirection=\"row\">\n <Text>&nbsp;&nbsp;\u23BF &nbsp;Search completed</Text>\n </Box>\n </Box>\n )\n }\n\n // Handle string content for backward compatibility\n if (typeof output === 'string') {\n // Convert string to Output type using tmpDeserializeOldLogResult if needed\n output = output as unknown as Output\n }\n\n const numFiles = output?.numFiles ?? 0\n const durationMs = output?.durationMs ?? 0\n // Show expand hint when there are many files and not in verbose mode\n const showExpandHint = !verbose && numFiles > 5\n\n return (\n <Box justifyContent=\"space-between\" width=\"100%\">\n <Box flexDirection=\"row\">\n <Text>&nbsp;&nbsp;\u23BF &nbsp;Found </Text>\n <Text bold>{numFiles} </Text>\n <Text>{numFiles === 0 || numFiles > 1 ? 'files' : 'file'}</Text>\n {showExpandHint && <CollapsibleHint canExpand={true} />}\n </Box>\n <Cost costUSD={0} durationMs={durationMs} debug={false} />\n </Box>\n )\n },\n renderResultForAssistant({ numFiles, filenames }) {\n if (numFiles === 0) {\n return 'No files found'\n }\n let result = `Found ${numFiles} file${numFiles === 1 ? '' : 's'}\\n${filenames.slice(0, MAX_RESULTS).join('\\n')}`\n if (numFiles > MAX_RESULTS) {\n result +=\n '\\n(Results are truncated. Consider using a more specific path or pattern.)'\n }\n return result\n },\n async *call({ pattern, path, include }, { abortController }) {\n const start = Date.now()\n const absolutePath = getAbsolutePath(path) || getCwd()\n\n const args = ['-li', pattern]\n if (include) {\n args.push('--glob', include)\n }\n\n // Collect matches with streaming progress updates\n const matchedFiles: string[] = []\n let lastProgressUpdate = 0\n const PROGRESS_UPDATE_INTERVAL = 100 // Update every 100ms\n\n for await (const chunk of ripGrepStreaming(\n args,\n absolutePath,\n abortController.signal,\n )) {\n if (chunk.type === 'match') {\n matchedFiles.push(chunk.file)\n\n // Yield progress update periodically for real-time feedback\n const now = Date.now()\n if (now - lastProgressUpdate >= PROGRESS_UPDATE_INTERVAL) {\n lastProgressUpdate = now\n yield {\n type: 'progress',\n content: {\n type: 'streaming',\n toolName: 'Search',\n stdout: `Searching... Found ${matchedFiles.length} file${matchedFiles.length === 1 ? '' : 's'}`,\n stderr: '',\n isStreaming: true,\n },\n }\n }\n }\n }\n\n // Sort by modification time\n const stats = await Promise.all(\n matchedFiles.map(_ => stat(_).catch(() => null)),\n )\n const matches = matchedFiles\n .map((file, i) => [file, stats[i]] as const)\n .filter(([, s]) => s !== null)\n .sort((a, b) => {\n if (process.env.NODE_ENV === 'test') {\n // In tests, we always want to sort by filename, so that results are deterministic\n return a[0].localeCompare(b[0])\n }\n const timeComparison = (b[1]?.mtimeMs ?? 0) - (a[1]?.mtimeMs ?? 0)\n if (timeComparison === 0) {\n // Sort by filename as a tiebreaker\n return a[0].localeCompare(b[0])\n }\n return timeComparison\n })\n .map(_ => _[0])\n\n const output = {\n filenames: matches,\n durationMs: Date.now() - start,\n numFiles: matches.length,\n }\n\n yield {\n type: 'result',\n resultForAssistant: this.renderResultForAssistant(output),\n data: output,\n }\n },\n} satisfies Tool<Input, Output>\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { stat } from 'fs/promises'\nimport { Box, Text } from 'ink'\nimport React from 'react'\nimport { z } from 'zod'\nimport { Cost } from '@components/Cost'\nimport { FallbackToolUseRejectedMessage } from '@components/FallbackToolUseRejectedMessage'\nimport { CollapsibleHint } from '@components/CollapsibleHint'\nimport { Tool } from '@tool'\nimport { getCwd } from '@utils/state'\nimport { getAbsolutePath, getAbsoluteAndRelativePaths } from '@utils/file'\nimport { ripGrepStreaming, ripGrepStreamingWithContent } from '@utils/ripgrep'\nimport { DESCRIPTION, TOOL_NAME_FOR_PROMPT } from './prompt'\nimport { hasReadPermission } from '@utils/permissions/filesystem'\n\nconst inputSchema = z.strictObject({\n pattern: z\n .string()\n .describe('The regular expression pattern to search for in file contents'),\n path: z\n .string()\n .optional()\n .describe(\n 'File or directory to search in (rg PATH). Defaults to current working directory.',\n ),\n // Backward compatible: 'include' is mapped to 'glob'\n include: z\n .string()\n .optional()\n .describe(\n 'File pattern to include in the search (e.g. \"*.js\", \"*.{ts,tsx}\") - DEPRECATED: use glob instead',\n ),\n glob: z\n .string()\n .optional()\n .describe(\n 'Glob pattern to filter files (e.g. \"*.js\", \"*.{ts,tsx}\") - maps to rg --glob',\n ),\n type: z\n .string()\n .optional()\n .describe(\n 'File type to search (rg --type). Common types: js, py, rust, go, java, etc. More efficient than include for standard file types.',\n ),\n output_mode: z\n .enum(['content', 'files_with_matches', 'count'])\n .optional()\n .describe(\n 'Output mode: \"content\" shows matching lines, \"files_with_matches\" shows file paths (default), \"count\" shows match counts',\n ),\n multiline: z\n .boolean()\n .optional()\n .describe(\n 'Enable multiline mode where . matches newlines and patterns can span lines (rg -U --multiline-dotall). Default: false.',\n ),\n context: z\n .number()\n .optional()\n .describe(\n 'Number of lines to show before and after each match (rg -C). Requires output_mode: \"content\", ignored otherwise.',\n ),\n '-A': z\n .number()\n .optional()\n .describe(\n 'Number of lines to show after each match (rg -A). Requires output_mode: \"content\", ignored otherwise.',\n ),\n '-B': z\n .number()\n .optional()\n .describe(\n 'Number of lines to show before each match (rg -B). Requires output_mode: \"content\", ignored otherwise.',\n ),\n '-C': z.number().optional().describe('Alias for context.'),\n '-n': z\n .boolean()\n .optional()\n .describe(\n 'Show line numbers in output (rg -n). Requires output_mode: \"content\", ignored otherwise. Defaults to true.',\n ),\n '-i': z.boolean().optional().describe('Case insensitive search (rg -i)'),\n head_limit: z\n .number()\n .optional()\n .describe(\n 'Limit output to first N lines/entries, equivalent to \"| head -N\". Works across all output modes: content (limits output lines), files_with_matches (limits file paths), count (limits count entries). Defaults to 0 (unlimited).',\n ),\n offset: z\n .number()\n .optional()\n .describe(\n 'Skip first N lines/entries before applying head_limit, equivalent to \"| tail -n +N | head -N\". Works across all output modes. Defaults to 0.',\n ),\n})\n\nconst MAX_RESULTS = 100\n\ntype Input = typeof inputSchema\ntype OutputMode = 'content' | 'files_with_matches' | 'count'\n\ntype Output = {\n durationMs: number\n numFiles: number\n filenames: string[]\n // For content mode\n content?: string\n numMatches?: number\n // For count mode\n counts?: Array<{ file: string; count: number }>\n // Output mode used\n outputMode: OutputMode\n}\n\nexport const GrepTool = {\n name: TOOL_NAME_FOR_PROMPT,\n async description() {\n return DESCRIPTION\n },\n userFacingName() {\n return 'Search'\n },\n inputSchema,\n isReadOnly() {\n return true\n },\n isConcurrencySafe() {\n return true // Grep is read-only, safe for concurrent execution\n },\n async isEnabled() {\n return true\n },\n needsPermissions({ path }) {\n return !hasReadPermission(path || getCwd())\n },\n async prompt() {\n return DESCRIPTION\n },\n renderToolUseMessage(input, { verbose }) {\n const {\n pattern,\n path,\n include,\n glob,\n type,\n output_mode,\n multiline,\n context,\n '-i': caseInsensitive,\n } = input\n const { absolutePath, relativePath } = getAbsoluteAndRelativePaths(path)\n const effectiveGlob = glob || include\n\n const parts: string[] = [`pattern: \"${pattern}\"`]\n if (relativePath || verbose) {\n parts.push(`path: \"${verbose ? absolutePath : relativePath}\"`)\n }\n if (effectiveGlob) {\n parts.push(`glob: \"${effectiveGlob}\"`)\n }\n if (type) {\n parts.push(`type: \"${type}\"`)\n }\n if (output_mode && output_mode !== 'files_with_matches') {\n parts.push(`mode: \"${output_mode}\"`)\n }\n if (multiline) {\n parts.push('multiline')\n }\n if (context) {\n parts.push(`context: ${context}`)\n }\n if (caseInsensitive === false) {\n parts.push('case-sensitive')\n }\n\n return parts.join(', ')\n },\n renderToolUseRejectedMessage() {\n return <FallbackToolUseRejectedMessage />\n },\n renderToolResultMessage(output, { verbose }) {\n // Guard against undefined or null output\n if (!output) {\n return (\n <Box justifyContent=\"space-between\" width=\"100%\">\n <Box flexDirection=\"row\">\n <Text>&nbsp;&nbsp;\u23BF &nbsp;Search completed</Text>\n </Box>\n </Box>\n )\n }\n\n // Handle string content for backward compatibility\n if (typeof output === 'string') {\n // Convert string to Output type using tmpDeserializeOldLogResult if needed\n output = output as unknown as Output\n }\n\n const numFiles = output?.numFiles ?? 0\n const numMatches = output?.numMatches ?? 0\n const durationMs = output?.durationMs ?? 0\n const outputMode = output?.outputMode ?? 'files_with_matches'\n\n // Different display based on output mode\n if (outputMode === 'content') {\n const showExpandHint = !verbose && numMatches > 10\n return (\n <Box justifyContent=\"space-between\" width=\"100%\">\n <Box flexDirection=\"row\">\n <Text>&nbsp;&nbsp;\u23BF &nbsp;Found </Text>\n <Text bold>{numMatches} </Text>\n <Text>{numMatches === 1 ? 'match' : 'matches'} in </Text>\n <Text bold>{numFiles} </Text>\n <Text>{numFiles === 1 ? 'file' : 'files'}</Text>\n {showExpandHint && <CollapsibleHint canExpand={true} />}\n </Box>\n <Cost costUSD={0} durationMs={durationMs} debug={false} />\n </Box>\n )\n } else if (outputMode === 'count') {\n const showExpandHint = !verbose && numFiles > 5\n return (\n <Box justifyContent=\"space-between\" width=\"100%\">\n <Box flexDirection=\"row\">\n <Text>&nbsp;&nbsp;\u23BF &nbsp;Counted </Text>\n <Text bold>{numMatches} </Text>\n <Text>{numMatches === 1 ? 'match' : 'matches'} in </Text>\n <Text bold>{numFiles} </Text>\n <Text>{numFiles === 1 ? 'file' : 'files'}</Text>\n {showExpandHint && <CollapsibleHint canExpand={true} />}\n </Box>\n <Cost costUSD={0} durationMs={durationMs} debug={false} />\n </Box>\n )\n } else {\n // files_with_matches (default)\n const showExpandHint = !verbose && numFiles > 5\n return (\n <Box justifyContent=\"space-between\" width=\"100%\">\n <Box flexDirection=\"row\">\n <Text>&nbsp;&nbsp;\u23BF &nbsp;Found </Text>\n <Text bold>{numFiles} </Text>\n <Text>{numFiles === 0 || numFiles > 1 ? 'files' : 'file'}</Text>\n {showExpandHint && <CollapsibleHint canExpand={true} />}\n </Box>\n <Cost costUSD={0} durationMs={durationMs} debug={false} />\n </Box>\n )\n }\n },\n renderResultForAssistant(output: Output) {\n const { numFiles, filenames, content, counts, numMatches, outputMode } =\n output\n\n if (outputMode === 'content') {\n if (!content || numMatches === 0) {\n return 'No matches found'\n }\n let result = content\n if (numMatches && numMatches > MAX_RESULTS) {\n result +=\n '\\n(Results are truncated. Consider using a more specific path or pattern.)'\n }\n return result\n } else if (outputMode === 'count') {\n if (!counts || counts.length === 0) {\n return 'No matches found'\n }\n const totalMatches = counts.reduce((sum, c) => sum + c.count, 0)\n let result = `Found ${totalMatches} match${totalMatches === 1 ? '' : 'es'} in ${numFiles} file${numFiles === 1 ? '' : 's'}\\n`\n result += counts\n .slice(0, MAX_RESULTS)\n .map(c => `${c.file}:${c.count}`)\n .join('\\n')\n if (counts.length > MAX_RESULTS) {\n result +=\n '\\n(Results are truncated. Consider using a more specific path or pattern.)'\n }\n return result\n } else {\n // files_with_matches (default)\n if (numFiles === 0) {\n return 'No files found'\n }\n let result = `Found ${numFiles} file${numFiles === 1 ? '' : 's'}\\n${filenames.slice(0, MAX_RESULTS).join('\\n')}`\n if (numFiles > MAX_RESULTS) {\n result +=\n '\\n(Results are truncated. Consider using a more specific path or pattern.)'\n }\n return result\n }\n },\n async *call(input, { abortController }) {\n const {\n pattern,\n path,\n include,\n glob,\n type,\n output_mode = 'files_with_matches',\n multiline,\n context,\n '-A': afterContext,\n '-B': beforeContext,\n '-C': contextAlias,\n '-n': lineNumbers = true,\n '-i': caseInsensitive = true,\n head_limit = 0,\n offset = 0,\n } = input\n\n const start = Date.now()\n const absolutePath = getAbsolutePath(path) || getCwd()\n\n // Use glob if provided, fall back to include for backward compatibility\n const effectiveGlob = glob || include\n\n // Determine effective context value (context takes precedence over -C alias)\n const effectiveContext = context ?? contextAlias\n\n // Build ripgrep arguments based on output mode\n const args: string[] = []\n\n // Case sensitivity\n if (caseInsensitive) {\n args.push('-i')\n }\n\n // Multiline mode\n if (multiline) {\n args.push('-U', '--multiline-dotall')\n }\n\n // File type filter\n if (type) {\n args.push('--type', type)\n }\n\n // Glob filter\n if (effectiveGlob) {\n args.push('--glob', effectiveGlob)\n }\n\n // Output mode specific flags\n if (output_mode === 'files_with_matches') {\n args.push('-l')\n } else if (output_mode === 'count') {\n args.push('-c')\n } else if (output_mode === 'content') {\n // For content mode, add context and line number flags\n if (lineNumbers) {\n args.push('-n')\n }\n if (effectiveContext !== undefined && effectiveContext > 0) {\n args.push('-C', String(effectiveContext))\n } else {\n if (beforeContext !== undefined && beforeContext > 0) {\n args.push('-B', String(beforeContext))\n }\n if (afterContext !== undefined && afterContext > 0) {\n args.push('-A', String(afterContext))\n }\n }\n }\n\n // Add the pattern\n args.push(pattern)\n\n // Different processing based on output mode\n if (output_mode === 'content') {\n // Content mode: collect full output with context\n const contentLines: string[] = []\n const filesWithMatches = new Set<string>()\n let matchCount = 0\n let lastProgressUpdate = 0\n const PROGRESS_UPDATE_INTERVAL = 100\n\n for await (const chunk of ripGrepStreamingWithContent(\n args,\n absolutePath,\n abortController.signal,\n )) {\n if (chunk.type === 'line') {\n contentLines.push(chunk.line)\n // Track files (lines starting with filename:linenum:)\n const fileMatch = chunk.line.match(/^([^:]+):(\\d+):/)\n if (fileMatch) {\n filesWithMatches.add(fileMatch[1])\n matchCount++\n }\n\n // Progress update\n const now = Date.now()\n if (now - lastProgressUpdate >= PROGRESS_UPDATE_INTERVAL) {\n lastProgressUpdate = now\n yield {\n type: 'progress',\n content: {\n type: 'streaming',\n toolName: 'Search',\n stdout: `Searching... Found ${matchCount} match${matchCount === 1 ? '' : 'es'} in ${filesWithMatches.size} file${filesWithMatches.size === 1 ? '' : 's'}`,\n stderr: '',\n isStreaming: true,\n },\n }\n }\n }\n }\n\n // Apply offset and head_limit\n let resultLines = contentLines\n if (offset > 0) {\n resultLines = resultLines.slice(offset)\n }\n if (head_limit > 0) {\n resultLines = resultLines.slice(0, head_limit)\n }\n\n const output: Output = {\n filenames: Array.from(filesWithMatches),\n content: resultLines.join('\\n'),\n numMatches: matchCount,\n numFiles: filesWithMatches.size,\n durationMs: Date.now() - start,\n outputMode: 'content',\n }\n\n yield {\n type: 'result',\n resultForAssistant: this.renderResultForAssistant(output),\n data: output,\n }\n } else if (output_mode === 'count') {\n // Count mode: collect file:count pairs\n const counts: Array<{ file: string; count: number }> = []\n let lastProgressUpdate = 0\n const PROGRESS_UPDATE_INTERVAL = 100\n\n for await (const chunk of ripGrepStreamingWithContent(\n args,\n absolutePath,\n abortController.signal,\n )) {\n if (chunk.type === 'line') {\n // Format: filename:count\n const match = chunk.line.match(/^(.+):(\\d+)$/)\n if (match) {\n counts.push({ file: match[1], count: parseInt(match[2], 10) })\n\n // Progress update\n const now = Date.now()\n if (now - lastProgressUpdate >= PROGRESS_UPDATE_INTERVAL) {\n lastProgressUpdate = now\n yield {\n type: 'progress',\n content: {\n type: 'streaming',\n toolName: 'Search',\n stdout: `Searching... Found matches in ${counts.length} file${counts.length === 1 ? '' : 's'}`,\n stderr: '',\n isStreaming: true,\n },\n }\n }\n }\n }\n }\n\n // Sort by count descending, then by filename\n counts.sort((a, b) => {\n const countDiff = b.count - a.count\n if (countDiff !== 0) return countDiff\n return a.file.localeCompare(b.file)\n })\n\n // Apply offset and head_limit\n let resultCounts = counts\n if (offset > 0) {\n resultCounts = resultCounts.slice(offset)\n }\n if (head_limit > 0) {\n resultCounts = resultCounts.slice(0, head_limit)\n }\n\n const totalMatches = resultCounts.reduce((sum, c) => sum + c.count, 0)\n\n const output: Output = {\n filenames: resultCounts.map(c => c.file),\n counts: resultCounts,\n numMatches: totalMatches,\n numFiles: resultCounts.length,\n durationMs: Date.now() - start,\n outputMode: 'count',\n }\n\n yield {\n type: 'result',\n resultForAssistant: this.renderResultForAssistant(output),\n data: output,\n }\n } else {\n // files_with_matches mode (default): list matching files\n const matchedFiles: string[] = []\n let lastProgressUpdate = 0\n const PROGRESS_UPDATE_INTERVAL = 100\n\n for await (const chunk of ripGrepStreaming(\n args,\n absolutePath,\n abortController.signal,\n )) {\n if (chunk.type === 'match') {\n matchedFiles.push(chunk.file)\n\n // Yield progress update periodically for real-time feedback\n const now = Date.now()\n if (now - lastProgressUpdate >= PROGRESS_UPDATE_INTERVAL) {\n lastProgressUpdate = now\n yield {\n type: 'progress',\n content: {\n type: 'streaming',\n toolName: 'Search',\n stdout: `Searching... Found ${matchedFiles.length} file${matchedFiles.length === 1 ? '' : 's'}`,\n stderr: '',\n isStreaming: true,\n },\n }\n }\n }\n }\n\n // Sort by modification time\n const stats = await Promise.all(\n matchedFiles.map(_ => stat(_).catch(() => null)),\n )\n let matches = matchedFiles\n .map((file, i) => [file, stats[i]] as const)\n .filter(([, s]) => s !== null)\n .sort((a, b) => {\n if (process.env.NODE_ENV === 'test') {\n // In tests, we always want to sort by filename, so that results are deterministic\n return a[0].localeCompare(b[0])\n }\n const timeComparison = (b[1]?.mtimeMs ?? 0) - (a[1]?.mtimeMs ?? 0)\n if (timeComparison === 0) {\n // Sort by filename as a tiebreaker\n return a[0].localeCompare(b[0])\n }\n return timeComparison\n })\n .map(_ => _[0])\n\n // Apply offset and head_limit\n if (offset > 0) {\n matches = matches.slice(offset)\n }\n if (head_limit > 0) {\n matches = matches.slice(0, head_limit)\n }\n\n const output: Output = {\n filenames: matches,\n durationMs: Date.now() - start,\n numFiles: matches.length,\n outputMode: 'files_with_matches',\n }\n\n yield {\n type: 'result',\n resultForAssistant: this.renderResultForAssistant(output),\n data: output,\n }\n }\n },\n} satisfies Tool<Input, Output>\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,12 +1,19 @@
1
- const TOOL_NAME_FOR_PROMPT = "GrepTool";
1
+ const TOOL_NAME_FOR_PROMPT = "Grep";
2
2
  const DESCRIPTION = `
3
- - Fast content search tool that works with any codebase size
4
- - Searches file contents using regular expressions
5
- - Supports full regex syntax (eg. "log.*Error", "function\\s+\\w+", etc.)
6
- - Filter files by pattern with the include parameter (eg. "*.js", "*.{ts,tsx}")
7
- - Returns matching file paths sorted by modification time
8
- - Use this tool when you need to find files containing specific patterns
9
- - When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead
3
+ A powerful search tool built on ripgrep
4
+
5
+ Usage:
6
+ - ALWAYS use Grep for search tasks. NEVER invoke \`grep\` or \`rg\` as a Bash command. The Grep tool has been optimized for correct permissions and access.
7
+ - Supports full regex syntax (e.g., "log.*Error", "function\\s+\\w+")
8
+ - Filter files with glob parameter (e.g., "*.js", "**/*.tsx") or type parameter (e.g., "js", "py", "rust")
9
+ - Output modes: "content" shows matching lines, "files_with_matches" shows only file paths (default), "count" shows match counts
10
+ - Use Task tool for open-ended searches requiring multiple rounds
11
+ - Pattern syntax: Uses ripgrep (not grep) - literal braces need escaping (use \`interface\\{\\}\` to find \`interface{}\` in Go code)
12
+ - Multiline matching: By default patterns match within single lines only. For cross-line patterns like \`struct \\{[\\s\\S]*?field\`, use \`multiline: true\`
13
+ - Context options: Use -A (lines after), -B (lines before), or -C/context (lines before and after) to see surrounding code. Requires output_mode: "content".
14
+ - Line numbers: Use -n to show line numbers (default: true for content mode)
15
+ - Case sensitivity: Use -i for case-insensitive search
16
+ - Pagination: Use head_limit to limit results, offset to skip initial results
10
17
  `;
11
18
  export {
12
19
  DESCRIPTION,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/tools/GrepTool/prompt.ts"],
4
- "sourcesContent": ["export const TOOL_NAME_FOR_PROMPT = 'GrepTool'\n\nexport const DESCRIPTION = `\n- Fast content search tool that works with any codebase size\n- Searches file contents using regular expressions\n- Supports full regex syntax (eg. \"log.*Error\", \"function\\\\s+\\\\w+\", etc.)\n- Filter files by pattern with the include parameter (eg. \"*.js\", \"*.{ts,tsx}\")\n- Returns matching file paths sorted by modification time\n- Use this tool when you need to find files containing specific patterns\n- When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead\n`\n"],
5
- "mappings": "AAAO,MAAM,uBAAuB;AAE7B,MAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;",
4
+ "sourcesContent": ["export const TOOL_NAME_FOR_PROMPT = 'Grep'\n\nexport const DESCRIPTION = `\nA powerful search tool built on ripgrep\n\n Usage:\n - ALWAYS use Grep for search tasks. NEVER invoke \\`grep\\` or \\`rg\\` as a Bash command. The Grep tool has been optimized for correct permissions and access.\n - Supports full regex syntax (e.g., \"log.*Error\", \"function\\\\s+\\\\w+\")\n - Filter files with glob parameter (e.g., \"*.js\", \"**/*.tsx\") or type parameter (e.g., \"js\", \"py\", \"rust\")\n - Output modes: \"content\" shows matching lines, \"files_with_matches\" shows only file paths (default), \"count\" shows match counts\n - Use Task tool for open-ended searches requiring multiple rounds\n - Pattern syntax: Uses ripgrep (not grep) - literal braces need escaping (use \\`interface\\\\{\\\\}\\` to find \\`interface{}\\` in Go code)\n - Multiline matching: By default patterns match within single lines only. For cross-line patterns like \\`struct \\\\{[\\\\s\\\\S]*?field\\`, use \\`multiline: true\\`\n - Context options: Use -A (lines after), -B (lines before), or -C/context (lines before and after) to see surrounding code. Requires output_mode: \"content\".\n - Line numbers: Use -n to show line numbers (default: true for content mode)\n - Case sensitivity: Use -i for case-insensitive search\n - Pagination: Use head_limit to limit results, offset to skip initial results\n`\n"],
5
+ "mappings": "AAAO,MAAM,uBAAuB;AAE7B,MAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;",
6
6
  "names": []
7
7
  }