@oh-my-pi/pi-coding-agent 13.6.2 → 13.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/package.json +7 -7
- package/src/cli/grep-cli.ts +9 -1
- package/src/commands/grep.ts +2 -0
- package/src/config/settings-schema.ts +1 -0
- package/src/extensibility/extensions/types.ts +8 -0
- package/src/main.ts +19 -27
- package/src/modes/components/countdown-timer.ts +39 -10
- package/src/modes/components/hook-input.ts +7 -1
- package/src/modes/components/hook-selector.ts +30 -5
- package/src/modes/components/mcp-add-wizard.ts +8 -2
- package/src/modes/components/status-line/presets.ts +2 -2
- package/src/modes/components/status-line/segments.ts +12 -0
- package/src/modes/components/status-line/token-rate.ts +66 -0
- package/src/modes/components/status-line/types.ts +1 -0
- package/src/modes/components/status-line-segment-editor.ts +1 -0
- package/src/modes/components/status-line.ts +38 -1
- package/src/modes/controllers/extension-ui-controller.ts +21 -0
- package/src/modes/controllers/mcp-command-controller.ts +4 -2
- package/src/modes/rpc/rpc-mode.ts +25 -18
- package/src/modes/rpc/rpc-types.ts +1 -1
- package/src/prompts/tools/grep.md +1 -0
- package/src/prompts/tools/hashline.md +41 -139
- package/src/session/session-manager.ts +50 -0
- package/src/tools/ask.ts +237 -75
- package/src/tools/grep.ts +6 -1
package/src/tools/ask.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
19
19
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
20
20
|
import { TERMINAL, Text } from "@oh-my-pi/pi-tui";
|
|
21
|
-
import {
|
|
21
|
+
import { untilAborted } from "@oh-my-pi/pi-utils";
|
|
22
22
|
import { type Static, Type } from "@sinclair/typebox";
|
|
23
23
|
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
24
24
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
@@ -95,6 +95,14 @@ function addRecommendedSuffix(labels: string[], recommendedIndex?: number): stri
|
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
function getAutoSelectionOnTimeout(optionLabels: string[], recommended?: number): string[] {
|
|
99
|
+
if (optionLabels.length === 0) return [];
|
|
100
|
+
if (typeof recommended === "number" && recommended >= 0 && recommended < optionLabels.length) {
|
|
101
|
+
return [optionLabels[recommended]];
|
|
102
|
+
}
|
|
103
|
+
return [optionLabels[0]];
|
|
104
|
+
}
|
|
105
|
+
|
|
98
106
|
/** Strip "(Recommended)" suffix from a label */
|
|
99
107
|
function stripRecommendedSuffix(label: string): string {
|
|
100
108
|
return label.endsWith(RECOMMENDED_SUFFIX) ? label.slice(0, -RECOMMENDED_SUFFIX.length) : label;
|
|
@@ -107,15 +115,43 @@ function stripRecommendedSuffix(label: string): string {
|
|
|
107
115
|
interface SelectionResult {
|
|
108
116
|
selectedOptions: string[];
|
|
109
117
|
customInput?: string;
|
|
118
|
+
timedOut: boolean;
|
|
119
|
+
navigation?: "back" | "forward";
|
|
120
|
+
cancelled?: boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface NavigationControls {
|
|
124
|
+
allowBack: boolean;
|
|
125
|
+
allowForward: boolean;
|
|
126
|
+
progressText?: string;
|
|
127
|
+
}
|
|
128
|
+
interface AskSingleQuestionOptions {
|
|
129
|
+
recommended?: number;
|
|
130
|
+
timeout?: number;
|
|
131
|
+
signal?: AbortSignal;
|
|
132
|
+
initialSelection?: Pick<SelectionResult, "selectedOptions" | "customInput">;
|
|
133
|
+
navigation?: NavigationControls;
|
|
110
134
|
}
|
|
111
135
|
|
|
112
136
|
interface UIContext {
|
|
113
137
|
select(
|
|
114
138
|
prompt: string,
|
|
115
139
|
options: string[],
|
|
116
|
-
options_?: {
|
|
140
|
+
options_?: {
|
|
141
|
+
initialIndex?: number;
|
|
142
|
+
timeout?: number;
|
|
143
|
+
signal?: AbortSignal;
|
|
144
|
+
outline?: boolean;
|
|
145
|
+
onTimeout?: () => void;
|
|
146
|
+
onLeft?: () => void;
|
|
147
|
+
onRight?: () => void;
|
|
148
|
+
helpText?: string;
|
|
149
|
+
},
|
|
150
|
+
): Promise<string | undefined>;
|
|
151
|
+
input(
|
|
152
|
+
prompt: string,
|
|
153
|
+
options_?: { signal?: AbortSignal; timeout?: number; onTimeout?: () => void },
|
|
117
154
|
): Promise<string | undefined>;
|
|
118
|
-
input(prompt: string, options_?: { signal?: AbortSignal }): Promise<string | undefined>;
|
|
119
155
|
}
|
|
120
156
|
|
|
121
157
|
async function askSingleQuestion(
|
|
@@ -123,17 +159,75 @@ async function askSingleQuestion(
|
|
|
123
159
|
question: string,
|
|
124
160
|
optionLabels: string[],
|
|
125
161
|
multi: boolean,
|
|
126
|
-
|
|
127
|
-
signal?: AbortSignal,
|
|
162
|
+
options: AskSingleQuestionOptions = {},
|
|
128
163
|
): Promise<SelectionResult> {
|
|
164
|
+
const { recommended, timeout, signal, initialSelection, navigation } = options;
|
|
129
165
|
const doneLabel = getDoneOptionLabel();
|
|
130
|
-
let selectedOptions
|
|
131
|
-
let customInput
|
|
166
|
+
let selectedOptions = [...(initialSelection?.selectedOptions ?? [])];
|
|
167
|
+
let customInput = initialSelection?.customInput;
|
|
168
|
+
let timedOut = false;
|
|
132
169
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
170
|
+
const selectOption = async (
|
|
171
|
+
prompt: string,
|
|
172
|
+
optionsToShow: string[],
|
|
173
|
+
initialIndex?: number,
|
|
174
|
+
): Promise<{ choice: string | undefined; timedOut: boolean; navigation?: "back" | "forward" }> => {
|
|
175
|
+
let timeoutTriggered = false;
|
|
176
|
+
const onTimeout = () => {
|
|
177
|
+
timeoutTriggered = true;
|
|
178
|
+
};
|
|
179
|
+
let navigationAction: "back" | "forward" | undefined;
|
|
180
|
+
const helpText = navigation
|
|
181
|
+
? "up/down navigate enter select ←/→ question esc cancel"
|
|
182
|
+
: "up/down navigate enter select esc cancel";
|
|
183
|
+
const dialogOptions = {
|
|
184
|
+
initialIndex,
|
|
185
|
+
timeout,
|
|
186
|
+
signal,
|
|
187
|
+
outline: true,
|
|
188
|
+
onTimeout,
|
|
189
|
+
helpText,
|
|
190
|
+
onLeft: navigation?.allowBack
|
|
191
|
+
? () => {
|
|
192
|
+
navigationAction = "back";
|
|
193
|
+
}
|
|
194
|
+
: undefined,
|
|
195
|
+
onRight: navigation?.allowForward
|
|
196
|
+
? () => {
|
|
197
|
+
navigationAction = "forward";
|
|
198
|
+
}
|
|
199
|
+
: undefined,
|
|
200
|
+
};
|
|
201
|
+
const startMs = Date.now();
|
|
202
|
+
const choice = signal
|
|
203
|
+
? await untilAborted(signal, () => ui.select(prompt, optionsToShow, dialogOptions))
|
|
204
|
+
: await ui.select(prompt, optionsToShow, dialogOptions);
|
|
205
|
+
if (!timeoutTriggered && choice === undefined && typeof timeout === "number") {
|
|
206
|
+
timeoutTriggered = Date.now() - startMs >= timeout;
|
|
207
|
+
}
|
|
208
|
+
return { choice, timedOut: timeoutTriggered, navigation: navigationAction };
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const promptForInput = async (): Promise<{ input: string | undefined; timedOut: boolean }> => {
|
|
212
|
+
let inputTimedOut = false;
|
|
213
|
+
const onTimeout = () => {
|
|
214
|
+
inputTimedOut = true;
|
|
215
|
+
};
|
|
216
|
+
const input = signal
|
|
217
|
+
? await untilAborted(signal, () => ui.input("Enter your response:", { signal, timeout, onTimeout }))
|
|
218
|
+
: await ui.input("Enter your response:", { signal, timeout, onTimeout });
|
|
219
|
+
return { input, timedOut: inputTimedOut };
|
|
220
|
+
};
|
|
136
221
|
|
|
222
|
+
const promptWithProgress = navigation?.progressText ? `${question} (${navigation.progressText})` : question;
|
|
223
|
+
if (multi) {
|
|
224
|
+
const selected = new Set<string>(selectedOptions);
|
|
225
|
+
let cursorIndex = Math.min(Math.max(recommended ?? 0, 0), Math.max(optionLabels.length - 1, 0));
|
|
226
|
+
const firstSelected = selectedOptions[0];
|
|
227
|
+
if (firstSelected) {
|
|
228
|
+
const selectedIndex = optionLabels.indexOf(firstSelected);
|
|
229
|
+
if (selectedIndex >= 0) cursorIndex = selectedIndex;
|
|
230
|
+
}
|
|
137
231
|
while (true) {
|
|
138
232
|
const opts: string[] = [];
|
|
139
233
|
|
|
@@ -142,38 +236,41 @@ async function askSingleQuestion(
|
|
|
142
236
|
opts.push(`${checkbox} ${opt}`);
|
|
143
237
|
}
|
|
144
238
|
|
|
145
|
-
|
|
146
|
-
if (selected.size > 0) {
|
|
239
|
+
if (!navigation?.allowForward && selected.size > 0) {
|
|
147
240
|
opts.push(doneLabel);
|
|
148
241
|
}
|
|
149
242
|
opts.push(OTHER_OPTION);
|
|
150
243
|
|
|
151
244
|
const prefix = selected.size > 0 ? `(${selected.size} selected) ` : "";
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
245
|
+
const {
|
|
246
|
+
choice,
|
|
247
|
+
timedOut: selectTimedOut,
|
|
248
|
+
navigation: arrowNavigation,
|
|
249
|
+
} = await selectOption(`${prefix}${promptWithProgress}`, opts, cursorIndex);
|
|
250
|
+
|
|
251
|
+
if (arrowNavigation) {
|
|
252
|
+
return { selectedOptions: Array.from(selected), customInput, timedOut, navigation: arrowNavigation };
|
|
253
|
+
}
|
|
254
|
+
if (choice === undefined) {
|
|
255
|
+
if (selectTimedOut) {
|
|
256
|
+
timedOut = true;
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
return { selectedOptions: Array.from(selected), customInput, timedOut, cancelled: true };
|
|
260
|
+
}
|
|
261
|
+
if (choice === doneLabel) break;
|
|
167
262
|
|
|
168
263
|
if (choice === OTHER_OPTION) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
264
|
+
if (selectTimedOut) {
|
|
265
|
+
timedOut = true;
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
const inputResult = await promptForInput();
|
|
269
|
+
if (inputResult.input) customInput = inputResult.input;
|
|
270
|
+
if (inputResult.timedOut) timedOut = true;
|
|
173
271
|
break;
|
|
174
272
|
}
|
|
175
273
|
|
|
176
|
-
// Find which index was selected and update cursor position
|
|
177
274
|
const selectedIdx = opts.indexOf(choice);
|
|
178
275
|
if (selectedIdx >= 0) {
|
|
179
276
|
cursorIndex = selectedIdx;
|
|
@@ -194,35 +291,65 @@ async function askSingleQuestion(
|
|
|
194
291
|
selected.add(opt);
|
|
195
292
|
}
|
|
196
293
|
}
|
|
294
|
+
|
|
295
|
+
if (selectTimedOut) {
|
|
296
|
+
timedOut = true;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
197
299
|
}
|
|
198
300
|
selectedOptions = Array.from(selected);
|
|
199
301
|
} else {
|
|
200
302
|
const displayLabels = addRecommendedSuffix(optionLabels, recommended);
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
303
|
+
const optionsWithNavigation = [...displayLabels, OTHER_OPTION];
|
|
304
|
+
|
|
305
|
+
let initialIndex = recommended;
|
|
306
|
+
const previouslySelected = selectedOptions[0];
|
|
307
|
+
if (previouslySelected) {
|
|
308
|
+
const selectedIndex = optionLabels.indexOf(previouslySelected);
|
|
309
|
+
if (selectedIndex >= 0) initialIndex = selectedIndex;
|
|
310
|
+
} else if (customInput) {
|
|
311
|
+
initialIndex = displayLabels.length;
|
|
312
|
+
}
|
|
313
|
+
if (initialIndex !== undefined) {
|
|
314
|
+
const maxIndex = Math.max(optionsWithNavigation.length - 1, 0);
|
|
315
|
+
initialIndex = Math.max(0, Math.min(initialIndex, maxIndex));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const {
|
|
319
|
+
choice,
|
|
320
|
+
timedOut: selectTimedOut,
|
|
321
|
+
navigation: arrowNavigation,
|
|
322
|
+
} = await selectOption(promptWithProgress, optionsWithNavigation, initialIndex);
|
|
323
|
+
timedOut = selectTimedOut;
|
|
324
|
+
|
|
325
|
+
if (arrowNavigation) {
|
|
326
|
+
return { selectedOptions, customInput, timedOut, navigation: arrowNavigation };
|
|
327
|
+
}
|
|
328
|
+
if (choice === undefined) {
|
|
329
|
+
if (!timedOut) {
|
|
330
|
+
return { selectedOptions, customInput, timedOut, cancelled: true };
|
|
331
|
+
}
|
|
332
|
+
} else if (choice === OTHER_OPTION) {
|
|
333
|
+
if (!selectTimedOut) {
|
|
334
|
+
const inputResult = await promptForInput();
|
|
335
|
+
if (inputResult.input) customInput = inputResult.input;
|
|
336
|
+
if (inputResult.timedOut) timedOut = true;
|
|
337
|
+
}
|
|
338
|
+
selectedOptions = [];
|
|
339
|
+
} else {
|
|
221
340
|
selectedOptions = [stripRecommendedSuffix(choice)];
|
|
341
|
+
customInput = undefined;
|
|
342
|
+
}
|
|
343
|
+
if (navigation?.allowForward) {
|
|
344
|
+
return { selectedOptions, customInput, timedOut, navigation: "forward" };
|
|
222
345
|
}
|
|
223
346
|
}
|
|
224
347
|
|
|
225
|
-
|
|
348
|
+
if (timedOut && selectedOptions.length === 0 && !customInput) {
|
|
349
|
+
selectedOptions = getAutoSelectionOnTimeout(optionLabels, recommended);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return { selectedOptions, customInput, timedOut };
|
|
226
353
|
}
|
|
227
354
|
|
|
228
355
|
function formatQuestionResult(result: QuestionResult): string {
|
|
@@ -309,28 +436,29 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
309
436
|
};
|
|
310
437
|
}
|
|
311
438
|
|
|
312
|
-
const askQuestion = async (
|
|
439
|
+
const askQuestion = async (
|
|
440
|
+
q: AskParams["questions"][number],
|
|
441
|
+
options?: { previous?: QuestionResult; navigation?: NavigationControls },
|
|
442
|
+
) => {
|
|
313
443
|
const optionLabels = q.options.map(o => o.label);
|
|
314
|
-
const timeoutSignal = timeout == null ? undefined : AbortSignal.timeout(timeout);
|
|
315
|
-
const questionSignal = ptree.combineSignals(signal, timeoutSignal);
|
|
316
444
|
try {
|
|
317
|
-
const { selectedOptions, customInput } = await askSingleQuestion(
|
|
445
|
+
const { selectedOptions, customInput, navigation, cancelled, timedOut } = await askSingleQuestion(
|
|
318
446
|
ui,
|
|
319
447
|
q.question,
|
|
320
448
|
optionLabels,
|
|
321
449
|
q.multi ?? false,
|
|
322
|
-
|
|
323
|
-
|
|
450
|
+
{
|
|
451
|
+
recommended: q.recommended,
|
|
452
|
+
timeout: timeout ?? undefined,
|
|
453
|
+
signal,
|
|
454
|
+
initialSelection: options?.previous,
|
|
455
|
+
navigation: options?.navigation,
|
|
456
|
+
},
|
|
324
457
|
);
|
|
325
|
-
return { optionLabels, selectedOptions, customInput, timedOut
|
|
458
|
+
return { optionLabels, selectedOptions, customInput, navigation, cancelled, timedOut };
|
|
326
459
|
} catch (error) {
|
|
327
460
|
if (error instanceof Error && error.name === "AbortError") {
|
|
328
|
-
|
|
329
|
-
throw new ToolAbortError("Ask input was cancelled");
|
|
330
|
-
}
|
|
331
|
-
if (timeoutSignal?.aborted) {
|
|
332
|
-
return { optionLabels, selectedOptions: [], customInput: undefined, timedOut: true };
|
|
333
|
-
}
|
|
461
|
+
throw new ToolAbortError("Ask input was cancelled");
|
|
334
462
|
}
|
|
335
463
|
throw error;
|
|
336
464
|
}
|
|
@@ -338,9 +466,9 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
338
466
|
|
|
339
467
|
if (params.questions.length === 1) {
|
|
340
468
|
const [q] = params.questions;
|
|
341
|
-
const { optionLabels, selectedOptions, customInput, timedOut } = await askQuestion(q);
|
|
469
|
+
const { optionLabels, selectedOptions, customInput, cancelled, timedOut } = await askQuestion(q);
|
|
342
470
|
|
|
343
|
-
if (!timedOut && selectedOptions.length === 0 && !customInput) {
|
|
471
|
+
if (!timedOut && (cancelled || (selectedOptions.length === 0 && !customInput))) {
|
|
344
472
|
context.abort();
|
|
345
473
|
throw new ToolAbortError("Ask tool was cancelled by the user");
|
|
346
474
|
}
|
|
@@ -366,25 +494,59 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
366
494
|
return { content: [{ type: "text" as const, text: responseText }], details };
|
|
367
495
|
}
|
|
368
496
|
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const
|
|
497
|
+
const resultsByIndex: Array<QuestionResult | undefined> = Array.from({ length: params.questions.length });
|
|
498
|
+
let questionIndex = 0;
|
|
499
|
+
while (questionIndex < params.questions.length) {
|
|
500
|
+
const q = params.questions[questionIndex]!;
|
|
501
|
+
const previous = resultsByIndex[questionIndex];
|
|
502
|
+
const navigation: NavigationControls = {
|
|
503
|
+
allowBack: questionIndex > 0,
|
|
504
|
+
allowForward: true,
|
|
505
|
+
progressText: `${questionIndex + 1}/${params.questions.length}`,
|
|
506
|
+
};
|
|
507
|
+
const {
|
|
508
|
+
optionLabels,
|
|
509
|
+
selectedOptions,
|
|
510
|
+
customInput,
|
|
511
|
+
navigation: navAction,
|
|
512
|
+
cancelled,
|
|
513
|
+
timedOut,
|
|
514
|
+
} = await askQuestion(q, { previous, navigation });
|
|
373
515
|
|
|
374
|
-
if (
|
|
516
|
+
if (cancelled && !timedOut) {
|
|
375
517
|
context.abort();
|
|
376
518
|
throw new ToolAbortError("Ask tool was cancelled by the user");
|
|
377
519
|
}
|
|
378
|
-
|
|
520
|
+
|
|
521
|
+
resultsByIndex[questionIndex] = {
|
|
379
522
|
id: q.id,
|
|
380
523
|
question: q.question,
|
|
381
524
|
options: optionLabels,
|
|
382
525
|
multi: q.multi ?? false,
|
|
383
526
|
selectedOptions,
|
|
384
527
|
customInput,
|
|
385
|
-
}
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
if (navAction === "back") {
|
|
531
|
+
questionIndex = Math.max(0, questionIndex - 1);
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
questionIndex += 1;
|
|
386
536
|
}
|
|
387
537
|
|
|
538
|
+
const results = resultsByIndex.map((result, index) => {
|
|
539
|
+
if (result) return result;
|
|
540
|
+
const q = params.questions[index]!;
|
|
541
|
+
return {
|
|
542
|
+
id: q.id,
|
|
543
|
+
question: q.question,
|
|
544
|
+
options: q.options.map(o => o.label),
|
|
545
|
+
multi: q.multi ?? false,
|
|
546
|
+
selectedOptions: [],
|
|
547
|
+
};
|
|
548
|
+
});
|
|
549
|
+
|
|
388
550
|
const details: AskToolDetails = { results };
|
|
389
551
|
const responseLines = results.map(formatQuestionResult);
|
|
390
552
|
const responseText = `User answers:\n${responseLines.join("\n")}`;
|
package/src/tools/grep.ts
CHANGED
|
@@ -30,6 +30,7 @@ const grepSchema = Type.Object({
|
|
|
30
30
|
pre: Type.Optional(Type.Number({ description: "Lines of context before matches" })),
|
|
31
31
|
post: Type.Optional(Type.Number({ description: "Lines of context after matches" })),
|
|
32
32
|
multiline: Type.Optional(Type.Boolean({ description: "Enable multiline matching" })),
|
|
33
|
+
gitignore: Type.Optional(Type.Boolean({ description: "Respect .gitignore files during search (default: true)" })),
|
|
33
34
|
limit: Type.Optional(Type.Number({ description: "Limit output to first N matches (default: 20)" })),
|
|
34
35
|
offset: Type.Optional(Type.Number({ description: "Skip first N entries before applying limit (default: 0)" })),
|
|
35
36
|
});
|
|
@@ -77,7 +78,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
77
78
|
_onUpdate?: AgentToolUpdateCallback<GrepToolDetails>,
|
|
78
79
|
_toolContext?: AgentToolContext,
|
|
79
80
|
): Promise<AgentToolResult<GrepToolDetails>> {
|
|
80
|
-
const { pattern, path: searchDir, glob, type, i, pre, post, multiline, limit, offset } = params;
|
|
81
|
+
const { pattern, path: searchDir, glob, type, i, gitignore, pre, post, multiline, limit, offset } = params;
|
|
81
82
|
|
|
82
83
|
return untilAborted(signal, async () => {
|
|
83
84
|
const normalizedPattern = pattern.trim();
|
|
@@ -101,6 +102,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
101
102
|
const normalizedContextBefore = pre ?? defaultContextBefore;
|
|
102
103
|
const normalizedContextAfter = post ?? defaultContextAfter;
|
|
103
104
|
const ignoreCase = i ?? false;
|
|
105
|
+
const useGitignore = gitignore ?? true;
|
|
104
106
|
const patternHasNewline = normalizedPattern.includes("\n") || normalizedPattern.includes("\\n");
|
|
105
107
|
const effectiveMultiline = multiline ?? patternHasNewline;
|
|
106
108
|
|
|
@@ -162,6 +164,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
162
164
|
ignoreCase,
|
|
163
165
|
multiline: effectiveMultiline,
|
|
164
166
|
hidden: true,
|
|
167
|
+
gitignore: useGitignore,
|
|
165
168
|
cache: false,
|
|
166
169
|
maxCount: internalLimit,
|
|
167
170
|
offset: normalizedOffset > 0 ? normalizedOffset : undefined,
|
|
@@ -368,6 +371,7 @@ interface GrepRenderArgs {
|
|
|
368
371
|
glob?: string;
|
|
369
372
|
type?: string;
|
|
370
373
|
i?: boolean;
|
|
374
|
+
gitignore?: boolean;
|
|
371
375
|
pre?: number;
|
|
372
376
|
post?: number;
|
|
373
377
|
multiline?: boolean;
|
|
@@ -385,6 +389,7 @@ export const grepToolRenderer = {
|
|
|
385
389
|
if (args.glob) meta.push(`glob:${args.glob}`);
|
|
386
390
|
if (args.type) meta.push(`type:${args.type}`);
|
|
387
391
|
if (args.i) meta.push("case:insensitive");
|
|
392
|
+
if (args.gitignore === false) meta.push("gitignore:false");
|
|
388
393
|
if (args.pre !== undefined && args.pre > 0) {
|
|
389
394
|
meta.push(`pre:${args.pre}`);
|
|
390
395
|
}
|