@oh-my-pi/pi-coding-agent 9.1.1 → 9.2.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/package.json +8 -9
- package/src/cli/grep-cli.ts +152 -0
- package/src/config/settings-manager.ts +15 -0
- package/src/lsp/render.ts +2 -0
- package/src/main.ts +12 -0
- package/src/modes/components/bash-execution.ts +1 -2
- package/src/modes/components/python-execution.ts +1 -2
- package/src/modes/components/settings-defs.ts +14 -0
- package/src/modes/theme/theme.ts +2 -0
- package/src/prompts/tools/find.md +2 -1
- package/src/sdk.ts +1 -0
- package/src/tools/find.ts +58 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.2.0",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -74,17 +74,17 @@
|
|
|
74
74
|
"scripts": {
|
|
75
75
|
"check": "tsgo -p tsconfig.json",
|
|
76
76
|
"format-prompts": "bun scripts/format-prompts.ts",
|
|
77
|
-
"build:binary": "bun build --compile ./src/cli.ts --outfile dist/omp",
|
|
77
|
+
"build:binary": "cd ../.. && bun build --compile --define OMP_COMPILED=true --root . ./packages/coding-agent/src/cli.ts ./packages/natives/src/grep/worker.ts ./packages/natives/src/html/worker.ts ./packages/natives/src/image/worker.ts --outfile packages/coding-agent/dist/omp",
|
|
78
78
|
"generate-template": "bun scripts/generate-template.ts",
|
|
79
79
|
"test": "bun test"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@oh-my-pi/omp-stats": "9.
|
|
83
|
-
"@oh-my-pi/pi-agent-core": "9.
|
|
84
|
-
"@oh-my-pi/pi-ai": "9.
|
|
85
|
-
"@oh-my-pi/pi-natives": "9.
|
|
86
|
-
"@oh-my-pi/pi-tui": "9.
|
|
87
|
-
"@oh-my-pi/pi-utils": "9.
|
|
82
|
+
"@oh-my-pi/omp-stats": "9.2.0",
|
|
83
|
+
"@oh-my-pi/pi-agent-core": "9.2.0",
|
|
84
|
+
"@oh-my-pi/pi-ai": "9.2.0",
|
|
85
|
+
"@oh-my-pi/pi-natives": "9.2.0",
|
|
86
|
+
"@oh-my-pi/pi-tui": "9.2.0",
|
|
87
|
+
"@oh-my-pi/pi-utils": "9.2.0",
|
|
88
88
|
"@openai/agents": "^0.4.4",
|
|
89
89
|
"@sinclair/typebox": "^0.34.48",
|
|
90
90
|
"ajv": "^8.17.1",
|
|
@@ -97,7 +97,6 @@
|
|
|
97
97
|
"nanoid": "^5.1.6",
|
|
98
98
|
"node-html-parser": "^7.0.2",
|
|
99
99
|
"smol-toml": "^1.6.0",
|
|
100
|
-
"strip-ansi": "^7.1.2",
|
|
101
100
|
"zod": "^4.3.6"
|
|
102
101
|
},
|
|
103
102
|
"devDependencies": {
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grep CLI command handlers.
|
|
3
|
+
*
|
|
4
|
+
* Handles `omp grep` subcommand for testing grep tool on Windows.
|
|
5
|
+
*/
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import { grep } from "@oh-my-pi/pi-natives";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import { APP_NAME } from "../config";
|
|
10
|
+
|
|
11
|
+
export interface GrepCommandArgs {
|
|
12
|
+
pattern: string;
|
|
13
|
+
path: string;
|
|
14
|
+
glob?: string;
|
|
15
|
+
limit: number;
|
|
16
|
+
context: number;
|
|
17
|
+
mode: "content" | "filesWithMatches" | "count";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parse grep subcommand arguments.
|
|
22
|
+
* Returns undefined if not a grep command.
|
|
23
|
+
*/
|
|
24
|
+
export function parseGrepArgs(args: string[]): GrepCommandArgs | undefined {
|
|
25
|
+
if (args.length === 0 || args[0] !== "grep") {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const result: GrepCommandArgs = {
|
|
30
|
+
pattern: "",
|
|
31
|
+
path: ".",
|
|
32
|
+
limit: 20,
|
|
33
|
+
context: 2,
|
|
34
|
+
mode: "content",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const positional: string[] = [];
|
|
38
|
+
|
|
39
|
+
for (let i = 1; i < args.length; i++) {
|
|
40
|
+
const arg = args[i];
|
|
41
|
+
if (arg === "--glob" || arg === "-g") {
|
|
42
|
+
result.glob = args[++i];
|
|
43
|
+
} else if (arg === "--limit" || arg === "-l") {
|
|
44
|
+
result.limit = parseInt(args[++i], 10);
|
|
45
|
+
} else if (arg === "--context" || arg === "-C") {
|
|
46
|
+
result.context = parseInt(args[++i], 10);
|
|
47
|
+
} else if (arg === "--files" || arg === "-f") {
|
|
48
|
+
result.mode = "filesWithMatches";
|
|
49
|
+
} else if (arg === "--count" || arg === "-c") {
|
|
50
|
+
result.mode = "count";
|
|
51
|
+
} else if (!arg.startsWith("-")) {
|
|
52
|
+
positional.push(arg);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (positional.length >= 1) {
|
|
57
|
+
result.pattern = positional[0];
|
|
58
|
+
}
|
|
59
|
+
if (positional.length >= 2) {
|
|
60
|
+
result.path = positional[1];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function runGrepCommand(cmd: GrepCommandArgs): Promise<void> {
|
|
67
|
+
if (!cmd.pattern) {
|
|
68
|
+
console.error(chalk.red("Error: Pattern is required"));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const searchPath = path.resolve(cmd.path);
|
|
73
|
+
console.log(chalk.dim(`Searching in: ${searchPath}`));
|
|
74
|
+
console.log(chalk.dim(`Pattern: ${cmd.pattern}`));
|
|
75
|
+
console.log(chalk.dim(`Mode: ${cmd.mode}, Limit: ${cmd.limit}, Context: ${cmd.context}`));
|
|
76
|
+
|
|
77
|
+
console.log("");
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const result = await grep({
|
|
81
|
+
pattern: cmd.pattern,
|
|
82
|
+
path: searchPath,
|
|
83
|
+
glob: cmd.glob,
|
|
84
|
+
mode: cmd.mode,
|
|
85
|
+
maxCount: cmd.limit,
|
|
86
|
+
context: cmd.mode === "content" ? cmd.context : undefined,
|
|
87
|
+
hidden: true,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
console.log(chalk.green(`Total matches: ${result.totalMatches}`));
|
|
91
|
+
console.log(chalk.green(`Files with matches: ${result.filesWithMatches}`));
|
|
92
|
+
console.log(chalk.green(`Files searched: ${result.filesSearched}`));
|
|
93
|
+
if (result.limitReached) {
|
|
94
|
+
console.log(chalk.yellow(`Limit reached: true`));
|
|
95
|
+
}
|
|
96
|
+
console.log("");
|
|
97
|
+
|
|
98
|
+
for (const match of result.matches) {
|
|
99
|
+
const displayPath = match.path.replace(/\\/g, "/");
|
|
100
|
+
|
|
101
|
+
if (cmd.mode === "content") {
|
|
102
|
+
if (match.contextBefore) {
|
|
103
|
+
for (const ctx of match.contextBefore) {
|
|
104
|
+
console.log(chalk.dim(`${displayPath}-${ctx.lineNumber}- ${ctx.line}`));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
console.log(`${chalk.cyan(displayPath)}:${chalk.yellow(String(match.lineNumber))}: ${match.line}`);
|
|
108
|
+
if (match.contextAfter) {
|
|
109
|
+
for (const ctx of match.contextAfter) {
|
|
110
|
+
console.log(chalk.dim(`${displayPath}-${ctx.lineNumber}- ${ctx.line}`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
console.log("");
|
|
114
|
+
} else if (cmd.mode === "count") {
|
|
115
|
+
console.log(`${chalk.cyan(displayPath)}: ${match.matchCount ?? 0} matches`);
|
|
116
|
+
} else {
|
|
117
|
+
console.log(chalk.cyan(displayPath));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch (err) {
|
|
121
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function printGrepHelp(): void {
|
|
127
|
+
console.log(`${chalk.bold(`${APP_NAME} grep`)} - Test grep tool
|
|
128
|
+
|
|
129
|
+
${chalk.bold("Usage:")}
|
|
130
|
+
${APP_NAME} grep <pattern> [path] [options]
|
|
131
|
+
|
|
132
|
+
${chalk.bold("Arguments:")}
|
|
133
|
+
pattern Regex pattern to search for
|
|
134
|
+
path Directory or file to search (default: .)
|
|
135
|
+
|
|
136
|
+
${chalk.bold("Options:")}
|
|
137
|
+
-g, --glob <pattern> Filter files by glob pattern
|
|
138
|
+
-l, --limit <n> Max matches (default: 20)
|
|
139
|
+
-C, --context <n> Context lines (default: 2)
|
|
140
|
+
-f, --files Output file names only
|
|
141
|
+
-c, --count Output match counts per file
|
|
142
|
+
-h, --help Show this help
|
|
143
|
+
|
|
144
|
+
${chalk.bold("Environment:")}
|
|
145
|
+
OMP_GREP_WORKERS=0 Disable worker pool (use single-threaded mode)
|
|
146
|
+
|
|
147
|
+
${chalk.bold("Examples:")}
|
|
148
|
+
${APP_NAME} grep "import" src/
|
|
149
|
+
${APP_NAME} grep "TODO" . --glob "*.ts"
|
|
150
|
+
${APP_NAME} grep "function" --files
|
|
151
|
+
`);
|
|
152
|
+
}
|
|
@@ -90,10 +90,12 @@ export interface ExaSettings {
|
|
|
90
90
|
|
|
91
91
|
export type WebSearchProviderOption = "auto" | "exa" | "perplexity" | "anthropic";
|
|
92
92
|
export type ImageProviderOption = "auto" | "gemini" | "openrouter";
|
|
93
|
+
export type KimiApiFormatOption = "openai" | "anthropic";
|
|
93
94
|
|
|
94
95
|
export interface ProviderSettings {
|
|
95
96
|
webSearch?: WebSearchProviderOption; // default: "auto" (exa > perplexity > anthropic)
|
|
96
97
|
image?: ImageProviderOption; // default: "auto" (openrouter > gemini)
|
|
98
|
+
kimiApiFormat?: KimiApiFormatOption; // default: "anthropic" (use Anthropic-compatible API for Kimi, more stable)
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
export interface BashInterceptorRule {
|
|
@@ -1349,6 +1351,19 @@ export class SettingsManager {
|
|
|
1349
1351
|
await this.save();
|
|
1350
1352
|
}
|
|
1351
1353
|
|
|
1354
|
+
getKimiApiFormat(): KimiApiFormatOption {
|
|
1355
|
+
return this.settings.providers?.kimiApiFormat ?? "anthropic";
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
async setKimiApiFormat(format: KimiApiFormatOption): Promise<void> {
|
|
1359
|
+
if (!this.globalSettings.providers) {
|
|
1360
|
+
this.globalSettings.providers = {};
|
|
1361
|
+
}
|
|
1362
|
+
this.globalSettings.providers.kimiApiFormat = format;
|
|
1363
|
+
this.markModified("providers", "kimiApiFormat");
|
|
1364
|
+
await this.save();
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1352
1367
|
getBashInterceptorEnabled(): boolean {
|
|
1353
1368
|
return this.settings.bashInterceptor?.enabled ?? DEFAULT_BASH_INTERCEPTOR_SETTINGS.enabled;
|
|
1354
1369
|
}
|
package/src/lsp/render.ts
CHANGED
|
@@ -306,6 +306,8 @@ function highlightCode(codeText: string, language: string, theme: Theme): string
|
|
|
306
306
|
type: theme.getFgAnsi("syntaxType"),
|
|
307
307
|
operator: theme.getFgAnsi("syntaxOperator"),
|
|
308
308
|
punctuation: theme.getFgAnsi("syntaxPunctuation"),
|
|
309
|
+
inserted: theme.getFgAnsi("toolDiffAdded"),
|
|
310
|
+
deleted: theme.getFgAnsi("toolDiffRemoved"),
|
|
309
311
|
};
|
|
310
312
|
return nativeHighlightCode(codeText, validLang, colors).split("\n");
|
|
311
313
|
} catch {
|
package/src/main.ts
CHANGED
|
@@ -14,6 +14,7 @@ import chalk from "chalk";
|
|
|
14
14
|
import { type Args, parseArgs, printHelp } from "./cli/args";
|
|
15
15
|
import { parseConfigArgs, printConfigHelp, runConfigCommand } from "./cli/config-cli";
|
|
16
16
|
import { processFileArguments } from "./cli/file-processor";
|
|
17
|
+
import { parseGrepArgs, printGrepHelp, runGrepCommand } from "./cli/grep-cli";
|
|
17
18
|
import { parseJupyterArgs, printJupyterHelp, runJupyterCommand } from "./cli/jupyter-cli";
|
|
18
19
|
import { listModels } from "./cli/list-models";
|
|
19
20
|
import { parsePluginArgs, printPluginHelp, runPluginCommand } from "./cli/plugin-cli";
|
|
@@ -553,6 +554,17 @@ export async function main(args: string[]) {
|
|
|
553
554
|
return;
|
|
554
555
|
}
|
|
555
556
|
|
|
557
|
+
// Handle grep subcommand (for testing grep tool)
|
|
558
|
+
const grepCmd = parseGrepArgs(args);
|
|
559
|
+
if (grepCmd) {
|
|
560
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
561
|
+
printGrepHelp();
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
await runGrepCommand(grepCmd);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
|
|
556
568
|
// Handle commit subcommand
|
|
557
569
|
const commitCmd = parseCommitArgs(args);
|
|
558
570
|
if (commitCmd) {
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* Component for displaying bash command execution with streaming output.
|
|
3
3
|
*/
|
|
4
4
|
import { Container, Loader, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
5
|
-
import stripAnsi from "strip-ansi";
|
|
6
5
|
import { getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
7
6
|
import type { TruncationMeta } from "../../tools/output-meta";
|
|
8
7
|
import { formatSize } from "../../tools/truncate";
|
|
@@ -188,7 +187,7 @@ export class BashExecutionComponent extends Container {
|
|
|
188
187
|
private normalizeOutput(text: string): string {
|
|
189
188
|
// Strip ANSI codes and normalize line endings
|
|
190
189
|
// Note: binary data is already sanitized in tui-renderer.ts executeBashCommand
|
|
191
|
-
return
|
|
190
|
+
return Bun.stripANSI(text).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
192
191
|
}
|
|
193
192
|
|
|
194
193
|
private setOutput(output: string): void {
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Shares the same kernel session as the agent's Python tool.
|
|
4
4
|
*/
|
|
5
5
|
import { Container, Loader, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
6
|
-
import stripAnsi from "strip-ansi";
|
|
7
6
|
import { getSymbolTheme, highlightCode, theme } from "../../modes/theme/theme";
|
|
8
7
|
import type { TruncationMeta } from "../../tools/output-meta";
|
|
9
8
|
import { formatSize } from "../../tools/truncate";
|
|
@@ -172,7 +171,7 @@ export class PythonExecutionComponent extends Container {
|
|
|
172
171
|
}
|
|
173
172
|
|
|
174
173
|
private normalizeOutput(text: string): string {
|
|
175
|
-
return
|
|
174
|
+
return Bun.stripANSI(text).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
176
175
|
}
|
|
177
176
|
|
|
178
177
|
private setOutput(output: string): void {
|
|
@@ -11,6 +11,7 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
|
11
11
|
import { TERMINAL_INFO } from "@oh-my-pi/pi-tui";
|
|
12
12
|
import type {
|
|
13
13
|
ImageProviderOption,
|
|
14
|
+
KimiApiFormatOption,
|
|
14
15
|
NotificationMethod,
|
|
15
16
|
PythonKernelMode,
|
|
16
17
|
PythonToolMode,
|
|
@@ -416,6 +417,19 @@ export const SETTINGS_DEFS: SettingDef[] = [
|
|
|
416
417
|
{ value: "openrouter", label: "OpenRouter", description: "Use OpenRouter (requires OPENROUTER_API_KEY)" },
|
|
417
418
|
],
|
|
418
419
|
},
|
|
420
|
+
{
|
|
421
|
+
id: "kimiApiFormat",
|
|
422
|
+
tab: "tools",
|
|
423
|
+
type: "submenu",
|
|
424
|
+
label: "Kimi API format",
|
|
425
|
+
description: "API format for Kimi Code provider",
|
|
426
|
+
get: sm => sm.getKimiApiFormat(),
|
|
427
|
+
set: (sm, v) => sm.setKimiApiFormat(v as KimiApiFormatOption),
|
|
428
|
+
getOptions: () => [
|
|
429
|
+
{ value: "openai", label: "OpenAI", description: "Use OpenAI-compatible API (api.kimi.com)" },
|
|
430
|
+
{ value: "anthropic", label: "Anthropic", description: "Use Anthropic-compatible API (api.moonshot.ai)" },
|
|
431
|
+
],
|
|
432
|
+
},
|
|
419
433
|
|
|
420
434
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
421
435
|
// Display tab - Visual/UI settings
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -2049,6 +2049,8 @@ function getHighlightColors(t: Theme): NativeHighlightColors {
|
|
|
2049
2049
|
type: t.getFgAnsi("syntaxType"),
|
|
2050
2050
|
operator: t.getFgAnsi("syntaxOperator"),
|
|
2051
2051
|
punctuation: t.getFgAnsi("syntaxPunctuation"),
|
|
2052
|
+
inserted: t.getFgAnsi("toolDiffAdded"),
|
|
2053
|
+
deleted: t.getFgAnsi("toolDiffRemoved"),
|
|
2052
2054
|
};
|
|
2053
2055
|
}
|
|
2054
2056
|
return cachedHighlightColors;
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
Fast file pattern matching that works with any codebase size.
|
|
4
4
|
|
|
5
5
|
<instruction>
|
|
6
|
-
-
|
|
6
|
+
- Pattern includes the search path: `src/**/*.ts`, `lib/*.json`, `**/*.md`
|
|
7
|
+
- Simple patterns like `*.ts` automatically search recursively from cwd
|
|
7
8
|
- Includes hidden files by default (use `hidden: false` to exclude)
|
|
8
9
|
- Speculatively perform multiple searches in parallel when potentially useful
|
|
9
10
|
</instruction>
|
package/src/sdk.ts
CHANGED
|
@@ -1115,6 +1115,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1115
1115
|
followUpMode: settingsManager.getFollowUpMode(),
|
|
1116
1116
|
interruptMode: settingsManager.getInterruptMode(),
|
|
1117
1117
|
thinkingBudgets: settingsManager.getThinkingBudgets(),
|
|
1118
|
+
kimiApiFormat: settingsManager.getKimiApiFormat(),
|
|
1118
1119
|
getToolContext: tc => toolContextStore.getContext(tc),
|
|
1119
1120
|
getApiKey: async () => {
|
|
1120
1121
|
const currentModel = agent.state.model;
|
package/src/tools/find.ts
CHANGED
|
@@ -22,8 +22,7 @@ import { toolResult } from "./tool-result";
|
|
|
22
22
|
import { type TruncationResult, truncateHead } from "./truncate";
|
|
23
23
|
|
|
24
24
|
const findSchema = Type.Object({
|
|
25
|
-
pattern: Type.String({ description: "Glob pattern, e.g. '*.ts', '
|
|
26
|
-
path: Type.Optional(Type.String({ description: "Directory to search (default: cwd)" })),
|
|
25
|
+
pattern: Type.String({ description: "Glob pattern, e.g. '*.ts', 'src/**/*.json', 'lib/*.tsx'" }),
|
|
27
26
|
hidden: Type.Optional(Type.Boolean({ description: "Include hidden files and directories (default: true)" })),
|
|
28
27
|
limit: Type.Optional(Type.Number({ description: "Max results (default: 1000)" })),
|
|
29
28
|
});
|
|
@@ -31,6 +30,51 @@ const findSchema = Type.Object({
|
|
|
31
30
|
const DEFAULT_LIMIT = 1000;
|
|
32
31
|
const GLOB_TIMEOUT_MS = 5000;
|
|
33
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Parse a pattern to extract the base directory path and glob pattern.
|
|
35
|
+
* Examples:
|
|
36
|
+
* "src/app/**\/*.tsx" → { basePath: "src/app", globPattern: "**\/*.tsx" }
|
|
37
|
+
* "src/app/*.tsx" → { basePath: "src/app", globPattern: "*.tsx" }
|
|
38
|
+
* "*.ts" → { basePath: ".", globPattern: "**\/*.ts" }
|
|
39
|
+
* "**\/*.json" → { basePath: ".", globPattern: "**\/*.json" }
|
|
40
|
+
* "/abs/path/**\/*.ts" → { basePath: "/abs/path", globPattern: "**\/*.ts" }
|
|
41
|
+
*/
|
|
42
|
+
function parsePatternPath(pattern: string): { basePath: string; globPattern: string } {
|
|
43
|
+
// Find the first segment containing glob characters
|
|
44
|
+
const segments = pattern.split("/");
|
|
45
|
+
const globChars = ["*", "?", "[", "{"];
|
|
46
|
+
|
|
47
|
+
let firstGlobIndex = -1;
|
|
48
|
+
for (let i = 0; i < segments.length; i++) {
|
|
49
|
+
if (globChars.some(c => segments[i].includes(c))) {
|
|
50
|
+
firstGlobIndex = i;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// No glob characters found - treat as literal path with implicit **/*
|
|
56
|
+
if (firstGlobIndex === -1) {
|
|
57
|
+
// Pattern is a directory path like "src/app" - search recursively in it
|
|
58
|
+
return { basePath: pattern, globPattern: "**/*" };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Glob starts at first segment - no base path
|
|
62
|
+
if (firstGlobIndex === 0) {
|
|
63
|
+
// Simple pattern like "*.ts" needs **/ prefix for recursive search
|
|
64
|
+
const needsRecursive = !pattern.startsWith("**/");
|
|
65
|
+
return {
|
|
66
|
+
basePath: ".",
|
|
67
|
+
globPattern: needsRecursive ? `**/${pattern}` : pattern,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Split at the glob boundary
|
|
72
|
+
const basePath = segments.slice(0, firstGlobIndex).join("/");
|
|
73
|
+
const globPattern = segments.slice(firstGlobIndex).join("/");
|
|
74
|
+
|
|
75
|
+
return { basePath, globPattern };
|
|
76
|
+
}
|
|
77
|
+
|
|
34
78
|
export interface FindToolDetails {
|
|
35
79
|
truncation?: TruncationResult;
|
|
36
80
|
resultLimitReached?: number;
|
|
@@ -81,10 +125,19 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
81
125
|
_onUpdate?: AgentToolUpdateCallback<FindToolDetails>,
|
|
82
126
|
_context?: AgentToolContext,
|
|
83
127
|
): Promise<AgentToolResult<FindToolDetails>> {
|
|
84
|
-
const { pattern,
|
|
128
|
+
const { pattern, limit, hidden } = params;
|
|
85
129
|
|
|
86
130
|
return untilAborted(signal, async () => {
|
|
87
|
-
|
|
131
|
+
// Parse pattern to extract base directory and glob pattern
|
|
132
|
+
// e.g., "src/app/**/*.tsx" → basePath: "src/app", globPattern: "**/*.tsx"
|
|
133
|
+
// e.g., "*.ts" → basePath: ".", globPattern: "**/*.ts"
|
|
134
|
+
const normalizedPattern = pattern.trim().replace(/\\/g, "/");
|
|
135
|
+
if (!normalizedPattern) {
|
|
136
|
+
throw new ToolError("Pattern must not be empty");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const { basePath, globPattern } = parsePatternPath(normalizedPattern);
|
|
140
|
+
const searchPath = resolveToCwd(basePath, this.session.cwd);
|
|
88
141
|
|
|
89
142
|
if (searchPath === "/") {
|
|
90
143
|
throw new ToolError("Searching from root directory '/' is not allowed");
|
|
@@ -94,10 +147,6 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
94
147
|
const relative = path.relative(this.session.cwd, searchPath).replace(/\\/g, "/");
|
|
95
148
|
return relative.length === 0 ? "." : relative;
|
|
96
149
|
})();
|
|
97
|
-
const normalizedPattern = pattern.trim();
|
|
98
|
-
if (!normalizedPattern) {
|
|
99
|
-
throw new ToolError("Pattern must not be empty");
|
|
100
|
-
}
|
|
101
150
|
|
|
102
151
|
const rawLimit = limit ?? DEFAULT_LIMIT;
|
|
103
152
|
const effectiveLimit = Number.isFinite(rawLimit) ? Math.floor(rawLimit) : Number.NaN;
|
|
@@ -105,7 +154,6 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
105
154
|
throw new ToolError("Limit must be a positive number");
|
|
106
155
|
}
|
|
107
156
|
const includeHidden = hidden ?? true;
|
|
108
|
-
const globPattern = normalizedPattern.replace(/\\/g, "/");
|
|
109
157
|
|
|
110
158
|
// If custom operations provided with glob, use that instead of fd
|
|
111
159
|
if (this.customOps?.glob) {
|
|
@@ -113,7 +161,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
113
161
|
throw new ToolError(`Path not found: ${searchPath}`);
|
|
114
162
|
}
|
|
115
163
|
|
|
116
|
-
const results = await this.customOps.glob(
|
|
164
|
+
const results = await this.customOps.glob(globPattern, searchPath, {
|
|
117
165
|
ignore: ["**/node_modules/**", "**/.git/**"],
|
|
118
166
|
limit: effectiveLimit,
|
|
119
167
|
});
|
|
@@ -279,8 +327,6 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
279
327
|
|
|
280
328
|
interface FindRenderArgs {
|
|
281
329
|
pattern: string;
|
|
282
|
-
path?: string;
|
|
283
|
-
sortByMtime?: boolean;
|
|
284
330
|
limit?: number;
|
|
285
331
|
}
|
|
286
332
|
|
|
@@ -290,8 +336,6 @@ export const findToolRenderer = {
|
|
|
290
336
|
inline: true,
|
|
291
337
|
renderCall(args: FindRenderArgs, uiTheme: Theme): Component {
|
|
292
338
|
const meta: string[] = [];
|
|
293
|
-
if (args.path) meta.push(`in ${args.path}`);
|
|
294
|
-
if (args.sortByMtime) meta.push("sort:mtime");
|
|
295
339
|
if (args.limit !== undefined) meta.push(`limit:${args.limit}`);
|
|
296
340
|
|
|
297
341
|
const text = renderStatusLine(
|