@oh-my-pi/pi-coding-agent 14.5.14 → 14.6.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 +49 -0
- package/package.json +7 -7
- package/src/autoresearch/command-resume.md +5 -8
- package/src/autoresearch/git.ts +41 -51
- package/src/autoresearch/helpers.ts +43 -359
- package/src/autoresearch/index.ts +281 -273
- package/src/autoresearch/prompt-setup.md +43 -0
- package/src/autoresearch/prompt.md +52 -193
- package/src/autoresearch/resume-message.md +2 -8
- package/src/autoresearch/state.ts +59 -166
- package/src/autoresearch/storage.ts +687 -0
- package/src/autoresearch/tools/init-experiment.ts +201 -290
- package/src/autoresearch/tools/log-experiment.ts +304 -517
- package/src/autoresearch/tools/run-experiment.ts +117 -296
- package/src/autoresearch/tools/update-notes.ts +116 -0
- package/src/autoresearch/types.ts +16 -66
- package/src/cli/list-models.ts +66 -0
- package/src/config/settings-schema.ts +1 -1
- package/src/config/settings.ts +20 -1
- package/src/cursor.ts +1 -1
- package/src/edit/index.ts +9 -31
- package/src/edit/line-hash.ts +70 -43
- package/src/edit/modes/hashline.lark +26 -0
- package/src/edit/modes/hashline.ts +898 -1099
- package/src/edit/modes/patch.ts +0 -7
- package/src/edit/modes/replace.ts +0 -4
- package/src/edit/renderer.ts +22 -20
- package/src/edit/streaming.ts +8 -28
- package/src/eval/eval.lark +24 -30
- package/src/eval/js/context-manager.ts +5 -162
- package/src/eval/js/prelude.txt +0 -12
- package/src/eval/parse.ts +129 -129
- package/src/eval/py/prelude.py +1 -219
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +2 -2
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/main.ts +18 -3
- package/src/modes/components/session-observer-overlay.ts +5 -2
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tree-selector.ts +4 -5
- package/src/modes/components/welcome.ts +11 -1
- package/src/modes/controllers/command-controller.ts +2 -6
- package/src/modes/controllers/event-controller.ts +7 -5
- package/src/modes/controllers/extension-ui-controller.ts +3 -15
- package/src/modes/controllers/input-controller.ts +0 -1
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +5 -7
- package/src/prompts/system/system-prompt.md +14 -38
- package/src/prompts/tools/ast-edit.md +8 -8
- package/src/prompts/tools/ast-grep.md +10 -10
- package/src/prompts/tools/eval.md +13 -31
- package/src/prompts/tools/find.md +2 -1
- package/src/prompts/tools/hashline.md +66 -57
- package/src/prompts/tools/search.md +2 -2
- package/src/session/agent-session.ts +1 -1
- package/src/session/session-manager.ts +17 -13
- package/src/tools/ast-edit.ts +141 -44
- package/src/tools/ast-grep.ts +112 -36
- package/src/tools/eval.ts +2 -53
- package/src/tools/find.ts +16 -15
- package/src/tools/gh-renderer.ts +184 -59
- package/src/tools/path-utils.ts +36 -196
- package/src/tools/search.ts +56 -35
- package/src/utils/edit-mode.ts +2 -11
- package/src/utils/file-display-mode.ts +1 -1
- package/src/utils/git.ts +59 -24
- package/src/utils/session-color.ts +0 -12
- package/src/utils/title-generator.ts +22 -38
- package/src/autoresearch/apply-contract-to-state.ts +0 -24
- package/src/autoresearch/contract.ts +0 -288
- package/src/edit/modes/atom.lark +0 -29
- package/src/edit/modes/atom.ts +0 -1773
- package/src/prompts/tools/atom.md +0 -150
package/src/tools/ast-grep.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
1
2
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
3
|
import { type AstFindMatch, astGrep } from "@oh-my-pi/pi-natives";
|
|
3
4
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
hasGlobPathChars,
|
|
20
21
|
normalizePathLikeInput,
|
|
21
22
|
parseSearchPath,
|
|
22
|
-
|
|
23
|
+
resolveExplicitSearchPaths,
|
|
23
24
|
resolveToCwd,
|
|
24
25
|
} from "./path-utils";
|
|
25
26
|
import {
|
|
@@ -37,13 +38,71 @@ import { toolResult } from "./tool-result";
|
|
|
37
38
|
|
|
38
39
|
const astGrepSchema = Type.Object({
|
|
39
40
|
pat: Type.String({ description: "ast pattern", examples: ["console.log($$$)"] }),
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
paths: Type.Array(Type.String({ description: "file, directory, glob, or internal URL to search" }), {
|
|
42
|
+
minItems: 1,
|
|
43
|
+
description: "files, directories, globs, or internal URLs to search",
|
|
44
|
+
examples: [["src/"], ["src/foo.ts"], ["src/**/*.ts"], ["src/", "packages/"]],
|
|
43
45
|
}),
|
|
44
46
|
skip: Type.Optional(Type.Number({ description: "matches to skip", default: 0 })),
|
|
45
47
|
});
|
|
46
48
|
|
|
49
|
+
async function runMultiTargetAstGrep(
|
|
50
|
+
targets: Array<{ basePath: string; glob?: string }>,
|
|
51
|
+
options: { patterns: string[]; commonBasePath: string; skip: number; limit: number; signal?: AbortSignal },
|
|
52
|
+
): Promise<{
|
|
53
|
+
matches: AstFindMatch[];
|
|
54
|
+
totalMatches: number;
|
|
55
|
+
filesWithMatches: number;
|
|
56
|
+
filesSearched: number;
|
|
57
|
+
limitReached: boolean;
|
|
58
|
+
parseErrors?: string[];
|
|
59
|
+
}> {
|
|
60
|
+
const aggregatedMatches: AstFindMatch[] = [];
|
|
61
|
+
const parseErrors: string[] = [];
|
|
62
|
+
let totalMatches = 0;
|
|
63
|
+
let filesSearched = 0;
|
|
64
|
+
let limitReached = false;
|
|
65
|
+
for (const target of targets) {
|
|
66
|
+
const targetResult = await astGrep({
|
|
67
|
+
patterns: options.patterns,
|
|
68
|
+
path: target.basePath,
|
|
69
|
+
glob: target.glob,
|
|
70
|
+
offset: 0,
|
|
71
|
+
limit: options.skip + options.limit + 1,
|
|
72
|
+
includeMeta: true,
|
|
73
|
+
signal: options.signal,
|
|
74
|
+
});
|
|
75
|
+
totalMatches += targetResult.totalMatches;
|
|
76
|
+
filesSearched += targetResult.filesSearched;
|
|
77
|
+
limitReached = limitReached || targetResult.limitReached;
|
|
78
|
+
if (targetResult.parseErrors) parseErrors.push(...targetResult.parseErrors);
|
|
79
|
+
for (const match of targetResult.matches) {
|
|
80
|
+
const absolute = path.resolve(target.basePath, match.path);
|
|
81
|
+
const rebased = path.relative(options.commonBasePath, absolute).replace(/\\/g, "/");
|
|
82
|
+
aggregatedMatches.push({ ...match, path: rebased });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
aggregatedMatches.sort((left, right) => {
|
|
86
|
+
const pathCmp = left.path.localeCompare(right.path);
|
|
87
|
+
if (pathCmp !== 0) return pathCmp;
|
|
88
|
+
if (left.startLine !== right.startLine) return left.startLine - right.startLine;
|
|
89
|
+
if (left.startColumn !== right.startColumn) return left.startColumn - right.startColumn;
|
|
90
|
+
if (left.byteStart !== right.byteStart) return left.byteStart - right.byteStart;
|
|
91
|
+
return left.byteEnd - right.byteEnd;
|
|
92
|
+
});
|
|
93
|
+
const visible = aggregatedMatches.slice(options.skip);
|
|
94
|
+
const paged = visible.slice(0, options.limit);
|
|
95
|
+
const filesWithMatches = new Set(aggregatedMatches.map(match => match.path)).size;
|
|
96
|
+
return {
|
|
97
|
+
matches: paged,
|
|
98
|
+
totalMatches,
|
|
99
|
+
filesWithMatches,
|
|
100
|
+
filesSearched,
|
|
101
|
+
limitReached: limitReached || visible.length > options.limit,
|
|
102
|
+
parseErrors: parseErrors.length > 0 ? parseErrors : undefined,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
47
106
|
export interface AstGrepToolDetails {
|
|
48
107
|
matchCount: number;
|
|
49
108
|
fileCount: number;
|
|
@@ -88,15 +147,21 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
88
147
|
throw new ToolError("skip must be a non-negative number");
|
|
89
148
|
}
|
|
90
149
|
const formatScopePath = (targetPath: string): string => formatPathRelativeToCwd(targetPath, this.session.cwd);
|
|
91
|
-
let searchPath: string
|
|
92
|
-
let scopePath: string
|
|
150
|
+
let searchPath: string;
|
|
151
|
+
let scopePath: string;
|
|
93
152
|
let globFilter: string | undefined;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
153
|
+
let multiTargets: Array<{ basePath: string; glob?: string }> | undefined;
|
|
154
|
+
const rawPaths = params.paths.map(normalizePathLikeInput);
|
|
155
|
+
if (rawPaths.some(rawPath => rawPath.length === 0)) {
|
|
156
|
+
throw new ToolError("`paths` must contain non-empty paths or globs");
|
|
97
157
|
}
|
|
98
158
|
const internalRouter = this.session.internalRouter;
|
|
99
|
-
|
|
159
|
+
const resolvedPathInputs: string[] = [];
|
|
160
|
+
for (const rawPath of rawPaths) {
|
|
161
|
+
if (!internalRouter?.canHandle(rawPath)) {
|
|
162
|
+
resolvedPathInputs.push(rawPath);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
100
165
|
if (hasGlobPathChars(rawPath)) {
|
|
101
166
|
throw new ToolError(`Glob patterns are not supported for internal URLs: ${rawPath}`);
|
|
102
167
|
}
|
|
@@ -104,23 +169,25 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
104
169
|
if (!resource.sourcePath) {
|
|
105
170
|
throw new ToolError(`Cannot search internal URL without backing file: ${rawPath}`);
|
|
106
171
|
}
|
|
107
|
-
|
|
172
|
+
resolvedPathInputs.push(resource.sourcePath);
|
|
173
|
+
}
|
|
174
|
+
if (resolvedPathInputs.length === 1) {
|
|
175
|
+
const parsedPath = parseSearchPath(resolvedPathInputs[0] ?? ".");
|
|
176
|
+
searchPath = resolveToCwd(parsedPath.basePath, this.session.cwd);
|
|
177
|
+
globFilter = parsedPath.glob;
|
|
108
178
|
scopePath = formatScopePath(searchPath);
|
|
109
179
|
} else {
|
|
110
|
-
const multiSearchPath = await
|
|
111
|
-
if (multiSearchPath) {
|
|
112
|
-
|
|
113
|
-
globFilter = multiSearchPath.glob;
|
|
114
|
-
scopePath = multiSearchPath.scopePath;
|
|
115
|
-
} else {
|
|
116
|
-
const parsedPath = parseSearchPath(rawPath);
|
|
117
|
-
searchPath = resolveToCwd(parsedPath.basePath, this.session.cwd);
|
|
118
|
-
globFilter = parsedPath.glob;
|
|
119
|
-
scopePath = formatScopePath(searchPath);
|
|
180
|
+
const multiSearchPath = await resolveExplicitSearchPaths(resolvedPathInputs, this.session.cwd, globFilter);
|
|
181
|
+
if (!multiSearchPath) {
|
|
182
|
+
throw new ToolError("`paths` must contain at least one path or glob");
|
|
120
183
|
}
|
|
184
|
+
searchPath = multiSearchPath.basePath;
|
|
185
|
+
globFilter = multiSearchPath.targets ? undefined : multiSearchPath.glob;
|
|
186
|
+
multiTargets = multiSearchPath.targets;
|
|
187
|
+
scopePath = multiSearchPath.scopePath;
|
|
121
188
|
}
|
|
122
189
|
|
|
123
|
-
const resolvedSearchPath = searchPath
|
|
190
|
+
const resolvedSearchPath = searchPath;
|
|
124
191
|
scopePath = scopePath ?? formatScopePath(resolvedSearchPath);
|
|
125
192
|
let isDirectory: boolean;
|
|
126
193
|
try {
|
|
@@ -130,14 +197,23 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
130
197
|
throw new ToolError(`Path not found: ${scopePath}`);
|
|
131
198
|
}
|
|
132
199
|
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
200
|
+
const DEFAULT_AST_LIMIT = 50;
|
|
201
|
+
const result = multiTargets
|
|
202
|
+
? await runMultiTargetAstGrep(multiTargets, {
|
|
203
|
+
patterns,
|
|
204
|
+
commonBasePath: resolvedSearchPath,
|
|
205
|
+
skip,
|
|
206
|
+
limit: DEFAULT_AST_LIMIT,
|
|
207
|
+
signal,
|
|
208
|
+
})
|
|
209
|
+
: await astGrep({
|
|
210
|
+
patterns,
|
|
211
|
+
path: resolvedSearchPath,
|
|
212
|
+
glob: globFilter,
|
|
213
|
+
offset: skip,
|
|
214
|
+
includeMeta: true,
|
|
215
|
+
signal,
|
|
216
|
+
});
|
|
141
217
|
|
|
142
218
|
const normalizedParseErrors = (result.parseErrors ?? []).map(error => {
|
|
143
219
|
const parseError = error.match(/^.+: (.+: parse error \(syntax tree contains error nodes\))$/);
|
|
@@ -172,7 +248,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
172
248
|
|
|
173
249
|
if (result.matches.length === 0) {
|
|
174
250
|
const noMatchMessage = dedupedParseErrors.length
|
|
175
|
-
? "No matches found. Parse issues mean the query may be mis-scoped; narrow `
|
|
251
|
+
? "No matches found. Parse issues mean the query may be mis-scoped; narrow `paths` before concluding absence."
|
|
176
252
|
: "No matches found";
|
|
177
253
|
const parseMessage = dedupedParseErrors.length
|
|
178
254
|
? `\n${formatParseErrors(dedupedParseErrors).join("\n")}`
|
|
@@ -238,7 +314,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
238
314
|
displayContent: displayLines.join("\n"),
|
|
239
315
|
};
|
|
240
316
|
if (result.limitReached) {
|
|
241
|
-
outputLines.push("", "Result limit reached; narrow
|
|
317
|
+
outputLines.push("", "Result limit reached; narrow paths or increase limit.");
|
|
242
318
|
}
|
|
243
319
|
if (dedupedParseErrors.length) {
|
|
244
320
|
outputLines.push("", ...formatParseErrors(dedupedParseErrors));
|
|
@@ -255,7 +331,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
255
331
|
|
|
256
332
|
interface AstGrepRenderArgs {
|
|
257
333
|
pat?: string;
|
|
258
|
-
|
|
334
|
+
paths?: string[];
|
|
259
335
|
skip?: number;
|
|
260
336
|
}
|
|
261
337
|
|
|
@@ -265,7 +341,7 @@ export const astGrepToolRenderer = {
|
|
|
265
341
|
inline: true,
|
|
266
342
|
renderCall(args: AstGrepRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
267
343
|
const meta: string[] = [];
|
|
268
|
-
if (args.
|
|
344
|
+
if (args.paths?.length) meta.push(`in ${args.paths.join(", ")}`);
|
|
269
345
|
if (args.skip !== undefined && args.skip > 0) meta.push(`skip:${args.skip}`);
|
|
270
346
|
|
|
271
347
|
const description = args.pat ?? "?";
|
|
@@ -299,7 +375,7 @@ export const astGrepToolRenderer = {
|
|
|
299
375
|
const header = renderStatusLine({ icon: "warning", title: "AST Grep", description, meta }, uiTheme);
|
|
300
376
|
const lines = [header, formatEmptyMessage("No matches found", uiTheme)];
|
|
301
377
|
if (details?.parseErrors?.length) {
|
|
302
|
-
lines.push(uiTheme.fg("warning", "Query may be mis-scoped; narrow `
|
|
378
|
+
lines.push(uiTheme.fg("warning", "Query may be mis-scoped; narrow `paths` before concluding absence"));
|
|
303
379
|
const capped = details.parseErrors.slice(0, PARSE_ERRORS_LIMIT);
|
|
304
380
|
for (const err of capped) {
|
|
305
381
|
lines.push(uiTheme.fg("warning", ` - ${err}`));
|
|
@@ -351,7 +427,7 @@ export const astGrepToolRenderer = {
|
|
|
351
427
|
|
|
352
428
|
const extraLines: string[] = [];
|
|
353
429
|
if (limitReached) {
|
|
354
|
-
extraLines.push(uiTheme.fg("warning", "limit reached; narrow
|
|
430
|
+
extraLines.push(uiTheme.fg("warning", "limit reached; narrow paths or increase limit"));
|
|
355
431
|
}
|
|
356
432
|
if (details?.parseErrors?.length) {
|
|
357
433
|
const total = details.parseErrors.length;
|
package/src/tools/eval.ts
CHANGED
|
@@ -26,7 +26,7 @@ export const EVAL_DEFAULT_PREVIEW_LINES = 10;
|
|
|
26
26
|
|
|
27
27
|
export const evalSchema = Type.Object({
|
|
28
28
|
input: Type.String({
|
|
29
|
-
description: "
|
|
29
|
+
description: "eval input as a sequence of `===== <info> =====` cell headers followed by code",
|
|
30
30
|
}),
|
|
31
31
|
});
|
|
32
32
|
export type EvalToolParams = Static<typeof evalSchema>;
|
|
@@ -250,7 +250,7 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
250
250
|
let previousRuntimeLanguage: EvalLanguage | undefined;
|
|
251
251
|
const cells: ResolvedEvalCell[] = [];
|
|
252
252
|
for (const cell of parsedInput.cells) {
|
|
253
|
-
const requested = cell.languageOrigin === "
|
|
253
|
+
const requested = cell.languageOrigin === "header" ? cell.language : (previousRuntimeLanguage ?? undefined);
|
|
254
254
|
const resolved = await resolveBackend(session, requested, cell.code);
|
|
255
255
|
previousRuntimeLanguage = resolved.backend.id;
|
|
256
256
|
cells.push({
|
|
@@ -600,12 +600,6 @@ function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
|
|
|
600
600
|
pwd: "icon.folder",
|
|
601
601
|
mkdir: "icon.folder",
|
|
602
602
|
tree: "icon.folder",
|
|
603
|
-
stat: "icon.folder",
|
|
604
|
-
find: "icon.file",
|
|
605
|
-
grep: "icon.file",
|
|
606
|
-
rgrep: "icon.file",
|
|
607
|
-
glob: "icon.file",
|
|
608
|
-
sed: "icon.file",
|
|
609
603
|
git_status: "icon.git",
|
|
610
604
|
git_diff: "icon.git",
|
|
611
605
|
git_log: "icon.git",
|
|
@@ -642,19 +636,6 @@ function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
|
|
|
642
636
|
parts.push(`${data.files} file${(data.files as number) !== 1 ? "s" : ""}`);
|
|
643
637
|
parts.push(`${data.chars} chars`);
|
|
644
638
|
break;
|
|
645
|
-
case "find":
|
|
646
|
-
case "glob":
|
|
647
|
-
parts.push(`${data.count} match${(data.count as number) !== 1 ? "es" : ""}`);
|
|
648
|
-
if (data.pattern) parts.push(`for "${truncateToWidth(String(data.pattern), 20)}"`);
|
|
649
|
-
break;
|
|
650
|
-
case "grep":
|
|
651
|
-
parts.push(`${data.count} match${(data.count as number) !== 1 ? "es" : ""}`);
|
|
652
|
-
if (data.path) parts.push(`in ${shortenPath(String(data.path))}`);
|
|
653
|
-
break;
|
|
654
|
-
case "rgrep":
|
|
655
|
-
parts.push(`${data.count} match${(data.count as number) !== 1 ? "es" : ""}`);
|
|
656
|
-
if (data.pattern) parts.push(`for "${truncateToWidth(String(data.pattern), 20)}"`);
|
|
657
|
-
break;
|
|
658
639
|
case "ls":
|
|
659
640
|
parts.push(`${data.count} entr${(data.count as number) !== 1 ? "ies" : "y"}`);
|
|
660
641
|
break;
|
|
@@ -667,18 +648,6 @@ function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
|
|
|
667
648
|
parts.push(`${data.count} variable${(data.count as number) !== 1 ? "s" : ""}`);
|
|
668
649
|
}
|
|
669
650
|
break;
|
|
670
|
-
case "stat":
|
|
671
|
-
if (data.is_dir) {
|
|
672
|
-
parts.push("directory");
|
|
673
|
-
} else {
|
|
674
|
-
parts.push(`${data.size} bytes`);
|
|
675
|
-
}
|
|
676
|
-
if (data.path) parts.push(shortenPath(String(data.path)));
|
|
677
|
-
break;
|
|
678
|
-
case "sed":
|
|
679
|
-
parts.push(`${data.count} replacement${(data.count as number) !== 1 ? "s" : ""}`);
|
|
680
|
-
if (data.path) parts.push(`in ${shortenPath(String(data.path))}`);
|
|
681
|
-
break;
|
|
682
651
|
case "git_status":
|
|
683
652
|
if (data.clean) {
|
|
684
653
|
parts.push("clean");
|
|
@@ -759,29 +728,9 @@ function formatStatusEventExpanded(event: EvalStatusEvent, theme: Theme): string
|
|
|
759
728
|
};
|
|
760
729
|
|
|
761
730
|
switch (op) {
|
|
762
|
-
case "find":
|
|
763
|
-
case "glob":
|
|
764
|
-
if (data.matches) addItems(data.matches as unknown[], m => String(m));
|
|
765
|
-
break;
|
|
766
731
|
case "ls":
|
|
767
732
|
if (data.items) addItems(data.items as unknown[], m => String(m));
|
|
768
733
|
break;
|
|
769
|
-
case "grep":
|
|
770
|
-
if (data.hits) {
|
|
771
|
-
addItems(data.hits as unknown[], h => {
|
|
772
|
-
const hit = h as { line: number; text: string };
|
|
773
|
-
return `${hit.line}: ${truncateToWidth(hit.text, 60)}`;
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
break;
|
|
777
|
-
case "rgrep":
|
|
778
|
-
if (data.hits) {
|
|
779
|
-
addItems(data.hits as unknown[], h => {
|
|
780
|
-
const hit = h as { file: string; line: number; text: string };
|
|
781
|
-
return `${shortenPath(hit.file)}:${hit.line}: ${truncateToWidth(hit.text, 50)}`;
|
|
782
|
-
});
|
|
783
|
-
}
|
|
784
|
-
break;
|
|
785
734
|
case "env":
|
|
786
735
|
if (data.keys) addItems(data.keys as unknown[], k => String(k), 10);
|
|
787
736
|
break;
|
package/src/tools/find.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
formatPathRelativeToCwd,
|
|
28
28
|
normalizePathLikeInput,
|
|
29
29
|
parseFindPattern,
|
|
30
|
-
|
|
30
|
+
resolveExplicitFindPatterns,
|
|
31
31
|
resolveToCwd,
|
|
32
32
|
} from "./path-utils";
|
|
33
33
|
import { formatCount, formatEmptyMessage, formatErrorMessage, PREVIEW_LIMITS } from "./render-utils";
|
|
@@ -35,9 +35,10 @@ import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
|
|
|
35
35
|
import { toolResult } from "./tool-result";
|
|
36
36
|
|
|
37
37
|
const findSchema = Type.Object({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
paths: Type.Array(Type.String({ description: "glob including search path" }), {
|
|
39
|
+
minItems: 1,
|
|
40
|
+
description: "globs including search paths",
|
|
41
|
+
examples: [["src/**/*.ts"], ["lib/*.json"], ["apps/", "packages/"], ["*.ts"]],
|
|
41
42
|
}),
|
|
42
43
|
hidden: Type.Optional(Type.Boolean({ description: "include hidden files", default: true })),
|
|
43
44
|
limit: Type.Optional(Type.Number({ description: "max results", default: 1000 })),
|
|
@@ -104,17 +105,17 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
104
105
|
onUpdate?: AgentToolUpdateCallback<FindToolDetails>,
|
|
105
106
|
_context?: AgentToolContext,
|
|
106
107
|
): Promise<AgentToolResult<FindToolDetails>> {
|
|
107
|
-
const {
|
|
108
|
+
const { paths, limit, hidden } = params;
|
|
108
109
|
|
|
109
110
|
return untilAborted(signal, async () => {
|
|
110
111
|
const formatScopePath = (targetPath: string): string => formatPathRelativeToCwd(targetPath, this.session.cwd);
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
throw new ToolError("
|
|
112
|
+
const normalizedPatterns = paths.map(input => normalizePathLikeInput(input).replace(/\\/g, "/"));
|
|
113
|
+
if (normalizedPatterns.some(pattern => pattern.length === 0)) {
|
|
114
|
+
throw new ToolError("`paths` must contain non-empty globs or paths");
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
const multiPattern = await
|
|
117
|
-
const parsedPattern = multiPattern ? null : parseFindPattern(
|
|
117
|
+
const multiPattern = await resolveExplicitFindPatterns(normalizedPatterns, this.session.cwd);
|
|
118
|
+
const parsedPattern = multiPattern ? null : parseFindPattern(normalizedPatterns[0] ?? ".");
|
|
118
119
|
const hasGlob = multiPattern ? true : (parsedPattern?.hasGlob ?? false);
|
|
119
120
|
const globPattern = multiPattern?.globPattern ?? parsedPattern?.globPattern ?? "**/*";
|
|
120
121
|
const searchPath = resolveToCwd(multiPattern?.basePath ?? parsedPattern?.basePath ?? ".", this.session.cwd);
|
|
@@ -292,7 +293,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
292
293
|
// =============================================================================
|
|
293
294
|
|
|
294
295
|
interface FindRenderArgs {
|
|
295
|
-
|
|
296
|
+
paths?: string[];
|
|
296
297
|
limit?: number;
|
|
297
298
|
}
|
|
298
299
|
|
|
@@ -305,7 +306,7 @@ export const findToolRenderer = {
|
|
|
305
306
|
if (args.limit !== undefined) meta.push(`limit:${args.limit}`);
|
|
306
307
|
|
|
307
308
|
const text = renderStatusLine(
|
|
308
|
-
{ icon: "pending", title: "Find", description: args.
|
|
309
|
+
{ icon: "pending", title: "Find", description: args.paths?.join(", ") || "*", meta },
|
|
309
310
|
uiTheme,
|
|
310
311
|
);
|
|
311
312
|
return new Text(text, 0, 0);
|
|
@@ -342,7 +343,7 @@ export const findToolRenderer = {
|
|
|
342
343
|
{
|
|
343
344
|
icon: "success",
|
|
344
345
|
title: "Find",
|
|
345
|
-
description: args?.
|
|
346
|
+
description: args?.paths?.join(", "),
|
|
346
347
|
meta: [formatCount("file", lines.length)],
|
|
347
348
|
},
|
|
348
349
|
uiTheme,
|
|
@@ -381,7 +382,7 @@ export const findToolRenderer = {
|
|
|
381
382
|
|
|
382
383
|
if (fileCount === 0) {
|
|
383
384
|
const header = renderStatusLine(
|
|
384
|
-
{ icon: "warning", title: "Find", description: args?.
|
|
385
|
+
{ icon: "warning", title: "Find", description: args?.paths?.join(", "), meta: ["0 files"] },
|
|
385
386
|
uiTheme,
|
|
386
387
|
);
|
|
387
388
|
return new Text([header, formatEmptyMessage("No files found", uiTheme)].join("\n"), 0, 0);
|
|
@@ -390,7 +391,7 @@ export const findToolRenderer = {
|
|
|
390
391
|
if (details?.scopePath) meta.push(`in ${details.scopePath}`);
|
|
391
392
|
if (truncated) meta.push(uiTheme.fg("warning", "truncated"));
|
|
392
393
|
const header = renderStatusLine(
|
|
393
|
-
{ icon: truncated ? "warning" : "success", title: "Find", description: args?.
|
|
394
|
+
{ icon: truncated ? "warning" : "success", title: "Find", description: args?.paths?.join(", "), meta },
|
|
394
395
|
uiTheme,
|
|
395
396
|
);
|
|
396
397
|
|