@oh-my-pi/pi-coding-agent 14.3.0 → 14.4.1
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 +98 -1
- package/package.json +7 -7
- package/src/autoresearch/prompt.md +1 -1
- package/src/commit/agentic/prompts/analyze-file.md +1 -1
- package/src/config/model-registry.ts +67 -15
- package/src/config/prompt-templates.ts +5 -5
- package/src/config/settings-schema.ts +4 -4
- package/src/cursor.ts +3 -8
- package/src/discovery/helpers.ts +3 -3
- package/src/edit/diff.ts +50 -47
- package/src/edit/index.ts +86 -57
- package/src/edit/line-hash.ts +743 -24
- package/src/edit/modes/apply-patch.ts +0 -9
- package/src/edit/modes/atom.ts +893 -0
- package/src/edit/modes/chunk.ts +14 -24
- package/src/edit/modes/hashline.ts +193 -146
- package/src/edit/modes/patch.ts +5 -9
- package/src/edit/modes/replace.ts +6 -11
- package/src/edit/renderer.ts +14 -10
- package/src/edit/streaming.ts +50 -16
- package/src/exec/bash-executor.ts +2 -4
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +4 -12
- package/src/extensibility/custom-tools/types.ts +2 -0
- package/src/extensibility/custom-tools/wrapper.ts +2 -1
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/lsp/defaults.json +142 -652
- package/src/lsp/index.ts +1 -1
- package/src/mcp/render.ts +1 -8
- package/src/modes/components/assistant-message.ts +4 -0
- package/src/modes/components/diff.ts +23 -14
- package/src/modes/components/footer.ts +21 -16
- package/src/modes/components/session-selector.ts +3 -3
- package/src/modes/components/settings-defs.ts +6 -1
- package/src/modes/components/todo-reminder.ts +1 -8
- package/src/modes/components/tool-execution.ts +1 -4
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/print-mode.ts +8 -0
- package/src/prompts/agents/librarian.md +1 -1
- package/src/prompts/agents/reviewer.md +4 -4
- package/src/prompts/ci-green-request.md +1 -1
- package/src/prompts/review-request.md +1 -1
- package/src/prompts/system/subagent-system-prompt.md +3 -3
- package/src/prompts/system/subagent-yield-reminder.md +11 -0
- package/src/prompts/system/system-prompt.md +3 -0
- package/src/prompts/tools/ask.md +3 -2
- package/src/prompts/tools/ast-edit.md +16 -20
- package/src/prompts/tools/ast-grep.md +19 -24
- package/src/prompts/tools/atom.md +87 -0
- package/src/prompts/tools/chunk-edit.md +37 -161
- package/src/prompts/tools/debug.md +4 -5
- package/src/prompts/tools/exit-plan-mode.md +4 -5
- package/src/prompts/tools/find.md +4 -8
- package/src/prompts/tools/github.md +18 -0
- package/src/prompts/tools/grep.md +4 -5
- package/src/prompts/tools/hashline.md +22 -89
- package/src/prompts/tools/{gemini-image.md → image-gen.md} +1 -1
- package/src/prompts/tools/inspect-image.md +6 -6
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/patch.md +12 -19
- package/src/prompts/tools/python.md +3 -2
- package/src/prompts/tools/read-chunk.md +2 -3
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/ssh.md +8 -17
- package/src/prompts/tools/todo-write.md +54 -41
- package/src/sdk.ts +14 -9
- package/src/session/agent-session.ts +25 -2
- package/src/session/session-manager.ts +4 -1
- package/src/task/executor.ts +43 -48
- package/src/task/render.ts +11 -13
- package/src/tools/ask.ts +7 -7
- package/src/tools/ast-edit.ts +45 -41
- package/src/tools/ast-grep.ts +77 -85
- package/src/tools/bash.ts +8 -9
- package/src/tools/browser.ts +32 -30
- package/src/tools/calculator.ts +4 -4
- package/src/tools/cancel-job.ts +1 -1
- package/src/tools/checkpoint.ts +2 -2
- package/src/tools/debug.ts +41 -37
- package/src/tools/exit-plan-mode.ts +1 -1
- package/src/tools/find.ts +4 -4
- package/src/tools/gh-renderer.ts +12 -4
- package/src/tools/gh.ts +509 -697
- package/src/tools/grep.ts +116 -131
- package/src/tools/{gemini-image.ts → image-gen.ts} +459 -60
- package/src/tools/index.ts +14 -32
- package/src/tools/inspect-image.ts +3 -3
- package/src/tools/json-tree.ts +114 -114
- package/src/tools/match-line-format.ts +8 -7
- package/src/tools/notebook.ts +8 -7
- package/src/tools/poll-tool.ts +2 -1
- package/src/tools/python.ts +9 -23
- package/src/tools/read.ts +32 -25
- package/src/tools/render-mermaid.ts +1 -1
- package/src/tools/render-utils.ts +18 -0
- package/src/tools/renderers.ts +2 -2
- package/src/tools/report-tool-issue.ts +3 -2
- package/src/tools/resolve.ts +1 -1
- package/src/tools/review.ts +12 -10
- package/src/tools/search-tool-bm25.ts +2 -4
- package/src/tools/ssh.ts +4 -4
- package/src/tools/todo-write.ts +172 -147
- package/src/tools/vim.ts +14 -15
- package/src/tools/write.ts +4 -4
- package/src/tools/{submit-result.ts → yield.ts} +11 -13
- package/src/utils/edit-mode.ts +2 -1
- package/src/utils/file-display-mode.ts +10 -5
- package/src/utils/git.ts +9 -5
- package/src/utils/shell-snapshot.ts +2 -3
- package/src/vim/render.ts +4 -4
- package/src/prompts/system/subagent-submit-reminder.md +0 -11
- package/src/prompts/tools/gh-issue-view.md +0 -11
- package/src/prompts/tools/gh-pr-checkout.md +0 -12
- package/src/prompts/tools/gh-pr-diff.md +0 -12
- package/src/prompts/tools/gh-pr-push.md +0 -12
- package/src/prompts/tools/gh-pr-view.md +0 -11
- package/src/prompts/tools/gh-repo-view.md +0 -11
- package/src/prompts/tools/gh-run-watch.md +0 -12
- package/src/prompts/tools/gh-search-issues.md +0 -11
- package/src/prompts/tools/gh-search-prs.md +0 -11
package/src/tools/grep.ts
CHANGED
|
@@ -19,29 +19,31 @@ import { createFileRecorder } from "./file-recorder";
|
|
|
19
19
|
import { formatMatchLine } from "./match-line-format";
|
|
20
20
|
import { formatFullOutputReference, type OutputMeta } from "./output-meta";
|
|
21
21
|
import {
|
|
22
|
-
combineSearchGlobs,
|
|
23
22
|
hasGlobPathChars,
|
|
24
23
|
normalizePathLikeInput,
|
|
25
24
|
parseSearchPath,
|
|
26
25
|
resolveMultiSearchPath,
|
|
27
26
|
resolveToCwd,
|
|
28
27
|
} from "./path-utils";
|
|
29
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
formatCodeFrameLine,
|
|
30
|
+
formatCount,
|
|
31
|
+
formatEmptyMessage,
|
|
32
|
+
formatErrorMessage,
|
|
33
|
+
PREVIEW_LIMITS,
|
|
34
|
+
} from "./render-utils";
|
|
30
35
|
import { ToolError } from "./tool-errors";
|
|
31
36
|
import { toolResult } from "./tool-result";
|
|
32
37
|
|
|
33
38
|
const grepSchema = Type.Object({
|
|
34
|
-
pattern: Type.String({ description: "
|
|
35
|
-
path: Type.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
gitignore: Type.Optional(Type.Boolean({ description: "Respect .gitignore files during search", default: true })),
|
|
43
|
-
limit: Type.Optional(Type.Number({ description: "Limit output to first N matches", default: 20 })),
|
|
44
|
-
offset: Type.Optional(Type.Number({ description: "Skip first N entries before applying limit", default: 0 })),
|
|
39
|
+
pattern: Type.String({ description: "regex pattern", examples: ["function\\s+\\w+", "TODO"] }),
|
|
40
|
+
path: Type.String({
|
|
41
|
+
description: "file, directory, glob, comma-separated paths, or internal URL to search",
|
|
42
|
+
examples: ["src/", "src/foo.ts", "src/**/*.ts"],
|
|
43
|
+
}),
|
|
44
|
+
i: Type.Optional(Type.Boolean({ description: "case-insensitive search", default: false })),
|
|
45
|
+
gitignore: Type.Optional(Type.Boolean({ description: "respect gitignore", default: true })),
|
|
46
|
+
skip: Type.Optional(Type.Number({ description: "matches to skip", default: 0 })),
|
|
45
47
|
});
|
|
46
48
|
|
|
47
49
|
export type GrepToolInput = Static<typeof grepSchema>;
|
|
@@ -61,6 +63,10 @@ export interface GrepToolDetails {
|
|
|
61
63
|
fileMatches?: Array<{ path: string; count: number }>;
|
|
62
64
|
truncated?: boolean;
|
|
63
65
|
error?: string;
|
|
66
|
+
/** Pre-formatted text for the user-visible TUI render. Mirrors the model-facing
|
|
67
|
+
* `result.text` lines but uses a `│` gutter and `*` to mark match lines (vs space for
|
|
68
|
+
* context). The TUI uses this directly so it never parses model-facing hashline anchors. */
|
|
69
|
+
displayContent?: string;
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
type GrepParams = Static<typeof grepSchema>;
|
|
@@ -88,7 +94,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
88
94
|
_onUpdate?: AgentToolUpdateCallback<GrepToolDetails>,
|
|
89
95
|
_toolContext?: AgentToolContext,
|
|
90
96
|
): Promise<AgentToolResult<GrepToolDetails>> {
|
|
91
|
-
const { pattern, path: searchDir,
|
|
97
|
+
const { pattern, path: searchDir, i, gitignore, skip } = params;
|
|
92
98
|
|
|
93
99
|
return untilAborted(signal, async () => {
|
|
94
100
|
const normalizedPattern = pattern.trim();
|
|
@@ -97,25 +103,16 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
97
103
|
throw new ToolError("Pattern must not be empty");
|
|
98
104
|
}
|
|
99
105
|
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
102
|
-
throw new ToolError("
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const rawLimit = limit === undefined ? undefined : Number.isFinite(limit) ? Math.floor(limit) : Number.NaN;
|
|
106
|
-
if (rawLimit !== undefined && (!Number.isFinite(rawLimit) || rawLimit < 0)) {
|
|
107
|
-
throw new ToolError("Limit must be a non-negative number");
|
|
106
|
+
const normalizedSkip = skip === undefined ? 0 : Number.isFinite(skip) ? Math.floor(skip) : Number.NaN;
|
|
107
|
+
if (normalizedSkip < 0 || !Number.isFinite(normalizedSkip)) {
|
|
108
|
+
throw new ToolError("Skip must be a non-negative number");
|
|
108
109
|
}
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
const defaultContextBefore = this.session.settings.get("grep.contextBefore");
|
|
112
|
-
const defaultContextAfter = this.session.settings.get("grep.contextAfter");
|
|
113
|
-
const normalizedContextBefore = pre ?? defaultContextBefore;
|
|
114
|
-
const normalizedContextAfter = post ?? defaultContextAfter;
|
|
110
|
+
const normalizedContextBefore = this.session.settings.get("grep.contextBefore");
|
|
111
|
+
const normalizedContextAfter = this.session.settings.get("grep.contextAfter");
|
|
115
112
|
const ignoreCase = i ?? false;
|
|
116
113
|
const useGitignore = gitignore ?? true;
|
|
117
114
|
const patternHasNewline = normalizedPattern.includes("\n") || normalizedPattern.includes("\\n");
|
|
118
|
-
const effectiveMultiline =
|
|
115
|
+
const effectiveMultiline = patternHasNewline;
|
|
119
116
|
|
|
120
117
|
const useHashLines = resolveFileDisplayMode(this.session).hashLines;
|
|
121
118
|
const formatScopePath = (targetPath: string): string => {
|
|
@@ -125,41 +122,36 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
125
122
|
let searchPath: string;
|
|
126
123
|
let scopePath: string;
|
|
127
124
|
let exactFilePaths: string[] | undefined;
|
|
128
|
-
let globFilter
|
|
125
|
+
let globFilter: string | undefined;
|
|
126
|
+
const rawPath = normalizePathLikeInput(searchDir);
|
|
127
|
+
if (rawPath.length === 0) {
|
|
128
|
+
throw new ToolError("`path` must be a non-empty path or glob");
|
|
129
|
+
}
|
|
129
130
|
const internalRouter = this.session.internalRouter;
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (!resource.sourcePath) {
|
|
138
|
-
throw new ToolError(`Cannot grep internal URL without a backing file: ${rawPath}`);
|
|
139
|
-
}
|
|
140
|
-
searchPath = resource.sourcePath;
|
|
141
|
-
scopePath = formatScopePath(searchPath);
|
|
142
|
-
} else {
|
|
143
|
-
const multiSearchPath = await resolveMultiSearchPath(rawPath, this.session.cwd, globFilter);
|
|
144
|
-
if (multiSearchPath) {
|
|
145
|
-
searchPath = multiSearchPath.basePath;
|
|
146
|
-
globFilter = multiSearchPath.exactFilePaths ? undefined : multiSearchPath.glob;
|
|
147
|
-
exactFilePaths = multiSearchPath.exactFilePaths;
|
|
148
|
-
scopePath = multiSearchPath.scopePath;
|
|
149
|
-
} else {
|
|
150
|
-
const parsedPath = parseSearchPath(rawPath);
|
|
151
|
-
searchPath = resolveToCwd(parsedPath.basePath, this.session.cwd);
|
|
152
|
-
if (parsedPath.glob) {
|
|
153
|
-
globFilter = combineSearchGlobs(parsedPath.glob, globFilter);
|
|
154
|
-
}
|
|
155
|
-
scopePath = formatScopePath(searchPath);
|
|
156
|
-
}
|
|
131
|
+
if (internalRouter?.canHandle(rawPath)) {
|
|
132
|
+
if (hasGlobPathChars(rawPath)) {
|
|
133
|
+
throw new ToolError(`Glob patterns are not supported for internal URLs: ${rawPath}`);
|
|
134
|
+
}
|
|
135
|
+
const resource = await internalRouter.resolve(rawPath);
|
|
136
|
+
if (!resource.sourcePath) {
|
|
137
|
+
throw new ToolError(`Cannot grep internal URL without a backing file: ${rawPath}`);
|
|
157
138
|
}
|
|
139
|
+
searchPath = resource.sourcePath;
|
|
140
|
+
scopePath = formatScopePath(searchPath);
|
|
158
141
|
} else {
|
|
159
|
-
|
|
160
|
-
|
|
142
|
+
const multiSearchPath = await resolveMultiSearchPath(rawPath, this.session.cwd, globFilter);
|
|
143
|
+
if (multiSearchPath) {
|
|
144
|
+
searchPath = multiSearchPath.basePath;
|
|
145
|
+
globFilter = multiSearchPath.exactFilePaths ? undefined : multiSearchPath.glob;
|
|
146
|
+
exactFilePaths = multiSearchPath.exactFilePaths;
|
|
147
|
+
scopePath = multiSearchPath.scopePath;
|
|
148
|
+
} else {
|
|
149
|
+
const parsedPath = parseSearchPath(rawPath);
|
|
150
|
+
searchPath = resolveToCwd(parsedPath.basePath, this.session.cwd);
|
|
151
|
+
globFilter = parsedPath.glob;
|
|
152
|
+
scopePath = formatScopePath(searchPath);
|
|
153
|
+
}
|
|
161
154
|
}
|
|
162
|
-
|
|
163
155
|
let isDirectory: boolean;
|
|
164
156
|
try {
|
|
165
157
|
const stat = await Bun.file(searchPath).stat();
|
|
@@ -170,7 +162,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
170
162
|
}
|
|
171
163
|
|
|
172
164
|
const effectiveOutputMode = GrepOutputMode.Content;
|
|
173
|
-
const effectiveLimit =
|
|
165
|
+
const effectiveLimit = DEFAULT_MATCH_LIMIT;
|
|
174
166
|
const internalLimit = Math.min(effectiveLimit * 5, 2000);
|
|
175
167
|
|
|
176
168
|
// Run grep
|
|
@@ -184,7 +176,6 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
184
176
|
{
|
|
185
177
|
pattern: normalizedPattern,
|
|
186
178
|
path: exactFilePath,
|
|
187
|
-
type: type?.trim() || undefined,
|
|
188
179
|
ignoreCase,
|
|
189
180
|
multiline: effectiveMultiline,
|
|
190
181
|
hidden: true,
|
|
@@ -201,7 +192,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
201
192
|
const relativeFilePath = path.relative(searchPath, exactFilePath).replace(/\\/g, "/");
|
|
202
193
|
matches.push(...fileResult.matches.map(match => ({ ...match, path: relativeFilePath })));
|
|
203
194
|
}
|
|
204
|
-
const offsetMatches = matches.slice(
|
|
195
|
+
const offsetMatches = matches.slice(normalizedSkip);
|
|
205
196
|
result = {
|
|
206
197
|
matches: offsetMatches,
|
|
207
198
|
totalMatches: offsetMatches.length,
|
|
@@ -215,14 +206,13 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
215
206
|
pattern: normalizedPattern,
|
|
216
207
|
path: searchPath,
|
|
217
208
|
glob: globFilter,
|
|
218
|
-
type: type?.trim() || undefined,
|
|
219
209
|
ignoreCase,
|
|
220
210
|
multiline: effectiveMultiline,
|
|
221
211
|
hidden: true,
|
|
222
212
|
gitignore: useGitignore,
|
|
223
213
|
cache: false,
|
|
224
214
|
maxCount: internalLimit,
|
|
225
|
-
offset:
|
|
215
|
+
offset: normalizedSkip > 0 ? normalizedSkip : undefined,
|
|
226
216
|
contextBefore: normalizedContextBefore,
|
|
227
217
|
contextAfter: normalizedContextAfter,
|
|
228
218
|
maxColumns: DEFAULT_MAX_COLUMN,
|
|
@@ -281,6 +271,8 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
281
271
|
? roundRobinSelect(result.matches, effectiveLimit)
|
|
282
272
|
: result.matches.slice(0, effectiveLimit);
|
|
283
273
|
const matchLimitReached = result.matches.length > effectiveLimit;
|
|
274
|
+
const nextSkip = normalizedSkip + selectedMatches.length;
|
|
275
|
+
const limitMessage = `Result limit reached; narrow path or use skip=${nextSkip}.`;
|
|
284
276
|
const { record: recordFile, list: fileList } = createFileRecorder();
|
|
285
277
|
const fileMatchCounts = new Map<string, number>();
|
|
286
278
|
if (selectedMatches.length === 0) {
|
|
@@ -333,7 +325,6 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
333
325
|
if (fileMatches.length === 0) {
|
|
334
326
|
return renderedLines;
|
|
335
327
|
}
|
|
336
|
-
const lineWidth = fileMatches[0]?.fileLineCount.toString().length ?? 1;
|
|
337
328
|
const matchesByChunk = new Map<string, ChunkedGrepMatch[]>();
|
|
338
329
|
for (const match of fileMatches) {
|
|
339
330
|
const chunkKey = match.chunkPath ?? "";
|
|
@@ -352,7 +343,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
352
343
|
renderedLines.push(anchor);
|
|
353
344
|
}
|
|
354
345
|
for (const match of chunkMatches) {
|
|
355
|
-
renderedLines.push(` ${match.lineNumber
|
|
346
|
+
renderedLines.push(` ${match.lineNumber}|${match.line}`);
|
|
356
347
|
fileMatchCounts.set(relativePath, (fileMatchCounts.get(relativePath) ?? 0) + 1);
|
|
357
348
|
}
|
|
358
349
|
}
|
|
@@ -398,6 +389,9 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
398
389
|
outputLines.push(...renderChunkedMatchesForFile(relativePath));
|
|
399
390
|
}
|
|
400
391
|
}
|
|
392
|
+
if (matchLimitReached || result.limitReached) {
|
|
393
|
+
outputLines.push("", limitMessage);
|
|
394
|
+
}
|
|
401
395
|
const rawOutput = outputLines.join("\n");
|
|
402
396
|
const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
403
397
|
const truncated = Boolean(matchLimitReached || result.limitReached || truncation.truncated);
|
|
@@ -415,52 +409,49 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
415
409
|
resultLimitReached: result.limitReached ? internalLimit : undefined,
|
|
416
410
|
};
|
|
417
411
|
if (truncation.truncated) details.truncation = truncation;
|
|
418
|
-
const resultBuilder = toolResult(details)
|
|
419
|
-
.text(truncation.content)
|
|
420
|
-
.limits({
|
|
421
|
-
matchLimit: matchLimitReached ? effectiveLimit : undefined,
|
|
422
|
-
resultLimit: result.limitReached ? internalLimit : undefined,
|
|
423
|
-
});
|
|
412
|
+
const resultBuilder = toolResult(details).text(truncation.content);
|
|
424
413
|
if (truncation.truncated) {
|
|
425
414
|
resultBuilder.truncation(truncation, { direction: "head" });
|
|
426
415
|
}
|
|
427
416
|
return resultBuilder.done();
|
|
428
417
|
}
|
|
429
|
-
const
|
|
430
|
-
|
|
418
|
+
const displayLines: string[] = [];
|
|
419
|
+
const renderMatchesForFile = (relativePath: string): { model: string[]; display: string[] } => {
|
|
420
|
+
const modelOut: string[] = [];
|
|
421
|
+
const displayOut: string[] = [];
|
|
431
422
|
const fileMatches = matchesByFile.get(relativePath) ?? [];
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
lineNumbers.push(ctx.lineNumber);
|
|
437
|
-
}
|
|
423
|
+
const lineNumberWidth = fileMatches.reduce((width, match) => {
|
|
424
|
+
let nextWidth = Math.max(width, String(match.lineNumber).length);
|
|
425
|
+
for (const ctx of match.contextBefore ?? []) {
|
|
426
|
+
nextWidth = Math.max(nextWidth, String(ctx.lineNumber).length);
|
|
438
427
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
lineNumbers.push(ctx.lineNumber);
|
|
442
|
-
}
|
|
428
|
+
for (const ctx of match.contextAfter ?? []) {
|
|
429
|
+
nextWidth = Math.max(nextWidth, String(ctx.lineNumber).length);
|
|
443
430
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
431
|
+
return nextWidth;
|
|
432
|
+
}, 0);
|
|
433
|
+
for (const match of fileMatches) {
|
|
434
|
+
const pushLine = (lineNumber: number, line: string, isMatch: boolean) => {
|
|
435
|
+
modelOut.push(formatMatchLine(lineNumber, line, isMatch, { useHashLines }));
|
|
436
|
+
displayOut.push(formatCodeFrameLine(isMatch ? "*" : " ", lineNumber, line, lineNumberWidth));
|
|
437
|
+
};
|
|
447
438
|
if (match.contextBefore) {
|
|
448
439
|
for (const ctx of match.contextBefore) {
|
|
449
|
-
|
|
440
|
+
pushLine(ctx.lineNumber, ctx.line, false);
|
|
450
441
|
}
|
|
451
442
|
}
|
|
452
|
-
|
|
443
|
+
pushLine(match.lineNumber, match.line, true);
|
|
453
444
|
if (match.truncated) {
|
|
454
445
|
linesTruncated = true;
|
|
455
446
|
}
|
|
456
447
|
if (match.contextAfter) {
|
|
457
448
|
for (const ctx of match.contextAfter) {
|
|
458
|
-
|
|
449
|
+
pushLine(ctx.lineNumber, ctx.line, false);
|
|
459
450
|
}
|
|
460
451
|
}
|
|
461
452
|
fileMatchCounts.set(relativePath, (fileMatchCounts.get(relativePath) ?? 0) + 1);
|
|
462
453
|
}
|
|
463
|
-
return
|
|
454
|
+
return { model: modelOut, display: displayOut };
|
|
464
455
|
};
|
|
465
456
|
if (isDirectory) {
|
|
466
457
|
const filesByDirectory = new Map<string, string[]>();
|
|
@@ -474,36 +465,47 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
474
465
|
for (const [directory, directoryFiles] of filesByDirectory) {
|
|
475
466
|
if (directory === ".") {
|
|
476
467
|
for (const relativePath of directoryFiles) {
|
|
477
|
-
const
|
|
478
|
-
if (
|
|
468
|
+
const rendered = renderMatchesForFile(relativePath);
|
|
469
|
+
if (rendered.model.length === 0) continue;
|
|
479
470
|
if (outputLines.length > 0) {
|
|
480
471
|
outputLines.push("");
|
|
472
|
+
displayLines.push("");
|
|
481
473
|
}
|
|
482
|
-
|
|
483
|
-
outputLines.push(...
|
|
474
|
+
const header = `# ${path.basename(relativePath)}`;
|
|
475
|
+
outputLines.push(header, ...rendered.model);
|
|
476
|
+
displayLines.push(header, ...rendered.display);
|
|
484
477
|
}
|
|
485
478
|
continue;
|
|
486
479
|
}
|
|
487
480
|
const renderedFiles = directoryFiles
|
|
488
|
-
.map(relativePath => ({ relativePath,
|
|
489
|
-
.filter(file => file.
|
|
481
|
+
.map(relativePath => ({ relativePath, rendered: renderMatchesForFile(relativePath) }))
|
|
482
|
+
.filter(file => file.rendered.model.length > 0);
|
|
490
483
|
if (renderedFiles.length === 0) continue;
|
|
491
484
|
if (outputLines.length > 0) {
|
|
492
485
|
outputLines.push("");
|
|
486
|
+
displayLines.push("");
|
|
493
487
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
488
|
+
const dirHeader = `# ${directory}`;
|
|
489
|
+
outputLines.push(dirHeader);
|
|
490
|
+
displayLines.push(dirHeader);
|
|
491
|
+
for (const { relativePath, rendered } of renderedFiles) {
|
|
492
|
+
const fileHeader = `## └─ ${path.basename(relativePath)}`;
|
|
493
|
+
outputLines.push(fileHeader, ...rendered.model);
|
|
494
|
+
displayLines.push(fileHeader, ...rendered.display);
|
|
498
495
|
}
|
|
499
496
|
}
|
|
500
497
|
} else {
|
|
501
498
|
for (const relativePath of fileList) {
|
|
502
|
-
|
|
499
|
+
const rendered = renderMatchesForFile(relativePath);
|
|
500
|
+
outputLines.push(...rendered.model);
|
|
501
|
+
displayLines.push(...rendered.display);
|
|
503
502
|
}
|
|
504
503
|
}
|
|
505
504
|
if (hasContextLines && outputLines.length > 0) {
|
|
506
|
-
outputLines.unshift("[grep] match lines use '
|
|
505
|
+
outputLines.unshift("[grep] match lines use '>'; context lines use ':'.");
|
|
506
|
+
}
|
|
507
|
+
if (matchLimitReached || result.limitReached) {
|
|
508
|
+
outputLines.push("", limitMessage);
|
|
507
509
|
}
|
|
508
510
|
const rawOutput = outputLines.join("\n");
|
|
509
511
|
const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
@@ -521,16 +523,13 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
521
523
|
truncated,
|
|
522
524
|
matchLimitReached: matchLimitReached ? effectiveLimit : undefined,
|
|
523
525
|
resultLimitReached: result.limitReached ? internalLimit : undefined,
|
|
526
|
+
displayContent: displayLines.join("\n"),
|
|
524
527
|
};
|
|
525
528
|
if (truncation.truncated) details.truncation = truncation;
|
|
526
529
|
if (linesTruncated) details.linesTruncated = true;
|
|
527
530
|
const resultBuilder = toolResult(details)
|
|
528
531
|
.text(output)
|
|
529
|
-
.limits({
|
|
530
|
-
matchLimit: matchLimitReached ? effectiveLimit : undefined,
|
|
531
|
-
resultLimit: result.limitReached ? internalLimit : undefined,
|
|
532
|
-
columnMax: linesTruncated ? DEFAULT_MAX_COLUMN : undefined,
|
|
533
|
-
});
|
|
532
|
+
.limits({ columnMax: linesTruncated ? DEFAULT_MAX_COLUMN : undefined });
|
|
534
533
|
if (truncation.truncated) {
|
|
535
534
|
resultBuilder.truncation(truncation, { direction: "head" });
|
|
536
535
|
}
|
|
@@ -546,15 +545,9 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
546
545
|
interface GrepRenderArgs {
|
|
547
546
|
pattern: string;
|
|
548
547
|
path?: string;
|
|
549
|
-
glob?: string;
|
|
550
|
-
type?: string;
|
|
551
548
|
i?: boolean;
|
|
552
549
|
gitignore?: boolean;
|
|
553
|
-
|
|
554
|
-
post?: number;
|
|
555
|
-
multiline?: boolean;
|
|
556
|
-
limit?: number;
|
|
557
|
-
offset?: number;
|
|
550
|
+
skip?: number;
|
|
558
551
|
}
|
|
559
552
|
|
|
560
553
|
const COLLAPSED_TEXT_LIMIT = PREVIEW_LIMITS.COLLAPSED_LINES * 2;
|
|
@@ -564,19 +557,9 @@ export const grepToolRenderer = {
|
|
|
564
557
|
renderCall(args: GrepRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
565
558
|
const meta: string[] = [];
|
|
566
559
|
if (args.path) meta.push(`in ${args.path}`);
|
|
567
|
-
if (args.glob) meta.push(`glob:${args.glob}`);
|
|
568
|
-
if (args.type) meta.push(`type:${args.type}`);
|
|
569
560
|
if (args.i) meta.push("case:insensitive");
|
|
570
561
|
if (args.gitignore === false) meta.push("gitignore:false");
|
|
571
|
-
if (args.
|
|
572
|
-
meta.push(`pre:${args.pre}`);
|
|
573
|
-
}
|
|
574
|
-
if (args.post !== undefined && args.post > 0) {
|
|
575
|
-
meta.push(`post:${args.post}`);
|
|
576
|
-
}
|
|
577
|
-
if (args.multiline) meta.push("multiline");
|
|
578
|
-
if (args.limit !== undefined && args.limit > 0) meta.push(`limit:${args.limit}`);
|
|
579
|
-
if (args.offset !== undefined && args.offset > 0) meta.push(`offset:${args.offset}`);
|
|
562
|
+
if (args.skip !== undefined && args.skip > 0) meta.push(`skip:${args.skip}`);
|
|
580
563
|
|
|
581
564
|
const text = renderStatusLine(
|
|
582
565
|
{ icon: "pending", title: "Grep", description: args.pattern || "?", meta },
|
|
@@ -601,7 +584,7 @@ export const grepToolRenderer = {
|
|
|
601
584
|
const hasDetailedData = details?.matchCount !== undefined || details?.fileCount !== undefined;
|
|
602
585
|
|
|
603
586
|
if (!hasDetailedData) {
|
|
604
|
-
const textContent = result.content?.find(c => c.type === "text")?.text;
|
|
587
|
+
const textContent = result.details?.displayContent ?? result.content?.find(c => c.type === "text")?.text;
|
|
605
588
|
if (!textContent || textContent === "No matches found") {
|
|
606
589
|
return new Text(formatEmptyMessage("No matches found", uiTheme), 0, 0);
|
|
607
590
|
}
|
|
@@ -664,7 +647,7 @@ export const grepToolRenderer = {
|
|
|
664
647
|
uiTheme,
|
|
665
648
|
);
|
|
666
649
|
|
|
667
|
-
const textContent = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
650
|
+
const textContent = result.details?.displayContent ?? result.content?.find(c => c.type === "text")?.text ?? "";
|
|
668
651
|
const rawLines = textContent.split("\n");
|
|
669
652
|
const hasSeparators = rawLines.some(line => line.trim().length === 0);
|
|
670
653
|
const matchGroups: string[][] = [];
|
|
@@ -688,9 +671,11 @@ export const grepToolRenderer = {
|
|
|
688
671
|
}
|
|
689
672
|
}
|
|
690
673
|
|
|
674
|
+
const renderedMatchLimit = details?.matchLimitReached ?? limits?.matchLimit?.reached;
|
|
675
|
+
const renderedResultLimit = details?.resultLimitReached ?? limits?.resultLimit?.reached;
|
|
691
676
|
const truncationReasons: string[] = [];
|
|
692
|
-
if (
|
|
693
|
-
if (
|
|
677
|
+
if (renderedMatchLimit) truncationReasons.push(`first ${renderedMatchLimit} matches`);
|
|
678
|
+
if (renderedResultLimit) truncationReasons.push(`first ${renderedResultLimit} results`);
|
|
694
679
|
if (truncation) truncationReasons.push(truncation.truncatedBy === "lines" ? "line limit" : "size limit");
|
|
695
680
|
if (limits?.columnTruncated) truncationReasons.push(`line length ${limits.columnTruncated.maxColumn}`);
|
|
696
681
|
if (truncation?.artifactId) truncationReasons.push(formatFullOutputReference(truncation.artifactId));
|