From ae1650d689aa17dc7e72b0e8e9139c2cd2c0e363 Mon Sep 17 00:00:00 2001 From: can1357 Date: Sat, 27 Jun 2026 00:44:28 +0200 Subject: [PATCH] refactor: renamed search and find tools to grep and glob - Renamed the `find` and `search` tools to `glob` and `grep` respectively across the codebase to improve command clarity. - Implemented full-stack support for the renamed tools, including CLI arguments, system prompts, SDK exports, and tool registration. - Added automated migration logic in `settings` to transform legacy `find` and `search` configuration keys to their new equivalents. - Updated the `collab-web` renderer registry to ensure backwards compatibility with legacy tool outputs. --- docs/fs-scan-cache-architecture.md | 2 +- docs/settings.md | 2 +- docs/tools/bash.md | 6 +- docs/tools/edit.md | 4 +- docs/tools/{find.md => glob.md} | 28 ++--- docs/tools/{search.md => grep.md} | 38 +++--- docs/tools/search_tool_bm25.md | 2 +- packages/coding-agent/CHANGELOG.md | 5 + .../src/advisor/__tests__/advisor.test.ts | 12 +- .../coding-agent/src/advisor/advise-tool.ts | 2 +- packages/coding-agent/src/cli/args.ts | 5 +- packages/coding-agent/src/cli/flag-tables.ts | 11 +- .../src/cli/gallery-fixtures/fs.ts | 34 +++--- .../src/cli/gallery-fixtures/search.ts | 8 +- .../src/config/settings-schema.ts | 40 +++---- packages/coding-agent/src/config/settings.ts | 113 ++++++++++++++++++ packages/coding-agent/src/cursor.ts | 2 +- .../coding-agent/src/discovery/helpers.ts | 4 +- .../coding-agent/src/eval/js/tool-bridge.ts | 4 +- .../coding-agent/src/export/html/template.js | 4 +- .../src/extensibility/extensions/types.ts | 44 +++---- .../src/extensibility/hooks/types.ts | 22 ++-- .../plugins/legacy-pi-bundled-keys.ts | 6 +- .../plugins/legacy-pi-bundled-registry.ts | 21 ++-- packages/coding-agent/src/memories/index.ts | 2 +- .../src/modes/acp/acp-event-mapper.ts | 4 +- .../src/modes/components/tree-selector.ts | 10 +- .../src/prompts/advisor/system.md | 4 +- .../src/prompts/agents/designer.md | 2 +- .../src/prompts/agents/explore.md | 2 +- .../src/prompts/agents/librarian.md | 4 +- .../coding-agent/src/prompts/agents/plan.md | 4 +- .../src/prompts/agents/reviewer.md | 2 +- .../coding-agent/src/prompts/agents/task.md | 4 +- .../src/prompts/system/plan-mode-active.md | 6 +- .../src/prompts/system/project-prompt.md | 4 +- .../src/prompts/system/system-prompt.md | 12 +- .../coding-agent/src/prompts/tools/bash.md | 4 +- .../src/prompts/tools/checkpoint.md | 2 +- .../src/prompts/tools/{find.md => glob.md} | 2 +- .../src/prompts/tools/{search.md => grep.md} | 6 +- packages/coding-agent/src/sdk.ts | 30 +++-- .../coding-agent/src/session/agent-session.ts | 3 +- .../src/session/session-history-format.ts | 4 +- packages/coding-agent/src/task/index.ts | 6 +- packages/coding-agent/src/tools/bash.ts | 4 +- .../coding-agent/src/tools/builtin-names.ts | 28 ++++- .../src/tools/{find.ts => glob.ts} | 84 ++++++------- .../src/tools/{search.ts => grep.ts} | 64 +++++----- packages/coding-agent/src/tools/index.ts | 27 ++--- packages/coding-agent/src/tools/renderers.ts | 8 +- .../test/agent-session-acp-permission.test.ts | 10 ++ ...ion-auto-compaction-progress-guard.test.ts | 4 +- ...gent-session-auto-compaction-queue.test.ts | 4 +- .../test/bash-acp-terminal.test.ts | 4 +- .../test/bash-failure-result.test.ts | 4 +- .../coding-agent/test/checkpoint-rpc-qa.ts | 8 +- .../test/core/python-display.test.ts | 4 +- .../coding-agent/test/cursor-exec.test.ts | 8 +- .../test/discovery/agent-fields.test.ts | 12 +- .../test/edit/seen-line-guard.test.ts | 8 +- .../coding-agent/test/flag-tables.test.ts | 8 ++ .../event-controller-interrupt.test.ts | 2 +- .../test/sdk-tool-activation.test.ts | 29 ++++- .../test/session/session-dump-format.test.ts | 6 +- .../session/session-history-format.test.ts | 18 +-- .../test/settings-manager.test.ts | 63 ++++++++++ packages/coding-agent/test/shake.test.ts | 4 +- .../test/tool-discovery/initial-tools.test.ts | 27 +++-- .../test/tool-discovery/persistence.test.ts | 6 +- .../test/tool-discovery/tool-index.test.ts | 6 +- packages/coding-agent/test/tools.test.ts | 20 ++-- .../coding-agent/test/tools/approval.test.ts | 4 +- ...renderer.test.ts => glob-renderer.test.ts} | 8 +- ...hs.test.ts => glob-validate-paths.test.ts} | 12 +- ...rls.test.ts => grep-internal-urls.test.ts} | 54 ++++----- ...gex.test.ts => grep-invalid-regex.test.ts} | 4 +- ...-lists.test.ts => grep-path-lists.test.ts} | 100 ++++++++-------- ...renderer.test.ts => grep-renderer.test.ts} | 34 ++---- .../coding-agent/test/tools/index.test.ts | 26 +++- ...h-path.test.ts => multi-grep-path.test.ts} | 20 ++-- .../test/tools/multi-path-missing.test.ts | 16 +-- .../test/tools/report-tool-issue.test.ts | 22 ++-- .../test/tools/root-path-alias.test.ts | 4 +- .../test/tools/search-tool-bm25.test.ts | 14 +-- .../tools/task-agent-capabilities.test.ts | 2 +- .../test/tools/tool-output-hyperlinks.test.ts | 4 +- packages/collab-web/CHANGELOG.md | 6 +- packages/collab-web/scripts/fixture.ts | 68 ++++++++++- .../collab-web/src/tool-render/registry.ts | 16 +-- .../tool-render/tools/{find.tsx => glob.tsx} | 4 +- .../tools/{search.tsx => grep.tsx} | 8 +- packages/utils/src/dirs.ts | 6 +- 93 files changed, 887 insertions(+), 551 deletions(-) rename docs/tools/{find.md => glob.md} (89%) rename docs/tools/{search.md => grep.md} (85%) rename packages/coding-agent/src/prompts/tools/{find.md => glob.md} (90%) rename packages/coding-agent/src/prompts/tools/{search.md => grep.md} (69%) rename packages/coding-agent/src/tools/{find.ts => glob.ts} (92%) rename packages/coding-agent/src/tools/{search.ts => grep.ts} (97%) rename packages/coding-agent/test/tools/{find-renderer.test.ts => glob-renderer.test.ts} (80%) rename packages/coding-agent/test/tools/{find-validate-paths.test.ts => glob-validate-paths.test.ts} (95%) rename packages/coding-agent/test/tools/{search-internal-urls.test.ts => grep-internal-urls.test.ts} (91%) rename packages/coding-agent/test/tools/{search-invalid-regex.test.ts => grep-invalid-regex.test.ts} (90%) rename packages/coding-agent/test/tools/{search-path-lists.test.ts => grep-path-lists.test.ts} (91%) rename packages/coding-agent/test/tools/{search-renderer.test.ts => grep-renderer.test.ts} (91%) rename packages/coding-agent/test/tools/{multi-search-path.test.ts => multi-grep-path.test.ts} (94%) rename packages/collab-web/src/tool-render/tools/{find.tsx => glob.tsx} (94%) rename packages/collab-web/src/tool-render/tools/{search.tsx => grep.tsx} (89%) diff --git a/docs/fs-scan-cache-architecture.md b/docs/fs-scan-cache-architecture.md index f036af8c1..944b64c07 100644 --- a/docs/fs-scan-cache-architecture.md +++ b/docs/fs-scan-cache-architecture.md @@ -116,7 +116,7 @@ Current callers: - `@`-mention fuzzy file autocomplete enables cache (`fuzzyFind` with `cache: true`): - `packages/tui/src/autocomplete.ts` - Mutation flows invalidate through `packages/coding-agent/src/tools/fs-cache-invalidation.ts`. -- Tool-level search integration (`packages/coding-agent/src/tools/search.ts`) currently calls native `grep` with `cache: false`. +- Tool-level grep integration (`packages/coding-agent/src/tools/grep.ts`) currently calls native `grep` with `cache: false`. ## Invalidation contract diff --git a/docs/settings.md b/docs/settings.md index fd2b5f11f..82f5ebd8f 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -418,7 +418,7 @@ tools: | `tools.artifactTailBytes` | number | `20` | KB of tail kept inline on spill. | | `tools.artifactTailLines` | number | `500` | Max tail lines kept inline on spill. | -Individual built-in tools are toggled by their own keys, e.g. `bash.enabled`, `eval.py`, `eval.js`, `find.enabled`, `search.enabled`, `fetch.enabled`, `browser.enabled`, `astEdit.enabled`, `astGrep.enabled`, `web_search.enabled`, `inspect_image.enabled`. +Individual built-in tools are toggled by their own keys, e.g. `bash.enabled`, `eval.py`, `eval.js`, `glob.enabled`, `grep.enabled`, `fetch.enabled`, `browser.enabled`, `astEdit.enabled`, `astGrep.enabled`, `web_search.enabled`, `inspect_image.enabled`. ### Shell, eval, and LSP diff --git a/docs/tools/bash.md b/docs/tools/bash.md index 2a5afa7e7..f7dcc246d 100644 --- a/docs/tools/bash.md +++ b/docs/tools/bash.md @@ -96,7 +96,7 @@ Stdout and stderr are merged before the model sees them. Definite non-zero exit - Starts like a foreground managed job, then backgrounds it when it outlives the wait window. 6. Intercepted command - No subprocess created. - - Returns a `ToolError` pointing the model at `read`, `search`, `find`, `edit`, or `write`. + - Returns a `ToolError` pointing the model at `read`, `grep`, `glob`, `edit`, or `write`. ## Side Effects - Filesystem @@ -153,8 +153,8 @@ Stdout and stderr are merged before the model sees them. Definite non-zero exit - `checkBashInterception()` blocks only when the matching rule's `tool` name is present in `ctx.toolNames`; missing tools disable their corresponding rule. - Default interceptor rules come from `DEFAULT_BASH_INTERCEPTOR_RULES` in `packages/coding-agent/src/config/settings-schema.ts`: - `cat|head|tail|less|more` -> `read` - - `grep|rg|ripgrep|ag|ack` -> `search` - - `find|fd|locate` with name/type/glob flags -> `find` + - `grep|rg|ripgrep|ag|ack` -> `grep` + - `find|fd|locate` with name/type/glob flags -> `glob` - `sed -i`, `perl -i`, `awk -i inplace` -> `edit` - `echo|printf|cat <<` with redirection -> `write` - PTY mode is ignored in non-UI contexts and when `PI_NO_PTY=1` (gated by `canUseInteractiveBashPty()`); the tool falls back to non-PTY execution and appends a `pty requested but unavailable in this environment; ran without a terminal` notice. diff --git a/docs/tools/edit.md b/docs/tools/edit.md index ddbed5fb6..87b807780 100644 --- a/docs/tools/edit.md +++ b/docs/tools/edit.md @@ -22,7 +22,7 @@ | Field | Type | Required | Description | | --- | --- | --- | --- | -| `input` | `string` | Yes | One or more file sections. Anchored sections must start with `[PATH#TAG]`; `TAG` is the four-hex snapshot tag emitted by the latest `read`/`search`/`write`/successful `edit`. Optional `*** Begin Patch` / `*** End Patch` envelope is ignored if present. | +| `input` | `string` | Yes | One or more file sections. Anchored sections must start with `[PATH#TAG]`; `TAG` is the four-hex snapshot tag emitted by the latest `read`/`grep`/`write`/successful `edit`. Optional `*** Begin Patch` / `*** End Patch` envelope is ignored if present. | Patch language inside `input`: @@ -45,7 +45,7 @@ Patch language inside `input`: - There is no repeat row kind. To keep a line, leave it out of every range; split edits into multiple hunks when needed. - `-` rows are invalid. Literal text beginning with `-` or `+` must be written as `+-text` / `++text`. -Anchors come from `read`/`search` output. `read` emits a `[PATH#TAG]` header from the session snapshot store and lines as `LINE:TEXT`; copy the header into the edit section and copy only the line number into hunk headers. +Anchors come from `read`/`grep` output. `read` emits a `[PATH#TAG]` header from the session snapshot store and lines as `LINE:TEXT`; copy the header into the edit section and copy only the line number into hunk headers. ### Tolerated input shapes (lenient parsing) diff --git a/docs/tools/find.md b/docs/tools/glob.md similarity index 89% rename from docs/tools/find.md rename to docs/tools/glob.md index 73d475886..25e90e01f 100644 --- a/docs/tools/find.md +++ b/docs/tools/glob.md @@ -1,10 +1,10 @@ -# find +# glob -> Find filesystem paths by glob; use `search` when you need content matches instead of path matches. +> Find filesystem paths by glob; use `grep` when you need content matches instead of path matches. ## Source -- Entry: `packages/coding-agent/src/tools/find.ts` -- Model-facing prompt: `packages/coding-agent/src/prompts/tools/find.md` +- Entry: `packages/coding-agent/src/tools/glob.ts` +- Model-facing prompt: `packages/coding-agent/src/prompts/tools/glob.md` - Key collaborators: - `packages/coding-agent/src/tools/path-utils.ts` — normalize inputs; split base path vs glob. - `packages/coding-agent/src/tools/list-limit.ts` — apply result-count caps. @@ -41,8 +41,8 @@ The tool returns a single text block plus structured `details`. ## Flow -1. `FindTool.execute()` expands delimiter-flattened local `paths` entries with `expandDelimitedPathEntries(..., parseFindPattern)` unless custom operations are injected. The splitter validates candidate parts by statting their parsed base paths, keeps existing delimiter-containing paths intact, accepts comma/semicolon splits when at least one part resolves, and accepts whitespace splits only when every part resolves. -2. The tool normalizes each resulting entry with `normalizePathLikeInput()` and `/\\/g -> "/"` (`packages/coding-agent/src/tools/find.ts`). Empty normalized entries fail with `` `paths` must contain non-empty globs or paths ``. +1. `GlobTool.execute()` expands delimiter-flattened local `paths` entries with `expandDelimitedPathEntries(..., parseFindPattern)` unless custom operations are injected. The splitter validates candidate parts by statting their parsed base paths, keeps existing delimiter-containing paths intact, accepts comma/semicolon splits when at least one part resolves, and accepts whitespace splits only when every part resolves. +2. The tool normalizes each resulting entry with `normalizePathLikeInput()` and `/\\/g -> "/"` (`packages/coding-agent/src/tools/glob.ts`). Empty normalized entries fail with `` `paths` must contain non-empty globs or paths ``. 3. For multi-path local calls, `partitionExistingPaths(..., parseFindPattern)` (`packages/coding-agent/src/tools/path-utils.ts`) stats each base path. Missing entries are skipped; if all are missing, the tool throws `Path not found: ...`. Single missing paths still hard-fail. 4. The tool calls `resolveExplicitFindPatterns()` for multi-entry calls; it parses each entry into its own `(basePath, globPattern, hasGlob)` target so every path is walked as its own root (collapsing to a shared ancestor would scan unrelated siblings). Single-entry calls parse with `parseFindPattern()` directly. 5. `parseFindPattern()` determines `(basePath, globPattern, hasGlob)`: @@ -52,7 +52,7 @@ The tool returns a single text block plus structured `details`. 6. `resolveToCwd()` converts the base path to an absolute path under the session cwd. A resolved `/` is rejected with `Searching from root directory '/' is not allowed`. 7. `limit` defaults to `DEFAULT_LIMIT` (`200`), must be positive and finite, is floored, then clamped to `MAX_LIMIT` (`200`). `hidden` and `gitignore` both default to `true`. An internal timeout of `5` seconds (`5000` ms) is built via `AbortSignal.timeout(...)`. 8. Execution then branches: - - **Custom operations branch**: if `FindToolOptions.operations.glob` exists, the tool checks existence with `operations.exists()`, short-circuits exact-file inputs via `operations.stat()` when available, then calls `operations.glob(globPattern, searchPath, { ignore: ["**/node_modules/**", "**/.git/**"], limit })`. + - **Custom operations branch**: if `GlobToolOptions.operations.glob` exists, the tool checks existence with `operations.exists()`, short-circuits exact-file inputs via `operations.stat()` when available, then calls `operations.glob(globPattern, searchPath, { ignore: ["**/node_modules/**", "**/.git/**"], limit })`. - **Built-in local branch**: the tool stats each target's `searchPath`. Exact-file inputs return immediately. Directory inputs call `natives.glob()` with `hidden`, `maxResults: effectiveLimit`, `sortByMtime: true`, `gitignore: useGitignore`, `recursive: false` (recursion comes from the `**/` prefix `parseFindPattern()` adds), and the combined abort signal; multi-target calls run their globs concurrently. 9. In the local branch, optional `onMatch` callbacks convert each match to a cwd-relative display path and emit throttled progress updates. 10. After native glob returns, JS merges per-target results, deduplicates repeated display paths, and sorts the merged list by `mtime` descending before formatting paths. @@ -66,7 +66,7 @@ The tool returns a single text block plus structured `details`. - **Multi-path search**: multiple inputs resolved by `resolveExplicitFindPatterns()` into per-entry targets, each walked as its own root concurrently and merged afterwards. - **Partial multi-path search with missing inputs**: local multi-path calls skip missing base paths and surface them as `missingPaths` / `Skipped missing paths: ...`. - **Internal URL input**: supported when the internal router resolves the URL to a backing file. Internal URL globs are rejected. -- **Custom delegated search**: uses injected `FindOperations` instead of local fs + native glob. +- **Custom delegated search**: uses injected `GlobOperations` instead of local fs + native glob. ## Side Effects - Filesystem @@ -81,31 +81,31 @@ The tool returns a single text block plus structured `details`. - Local globbing is cancellable through the caller abort signal plus the internal timeout. ## Limits & Caps -- Default result limit: `200` (`DEFAULT_LIMIT` in `packages/coding-agent/src/tools/find.ts`). +- Default result limit: `200` (`DEFAULT_LIMIT` in `packages/coding-agent/src/tools/glob.ts`). - Maximum result limit: `200` (`MAX_LIMIT`); larger inputs are clamped. - Local glob timeout: fixed at `5000` ms. - Output byte cap: `50 * 1024` bytes (`DEFAULT_MAX_BYTES` in `packages/coding-agent/src/session/streaming-output.ts`). -- Default generic line cap in `truncateHead()` is `3000`, but `find` overrides `maxLines` to `Number.MAX_SAFE_INTEGER`, so byte size — not line count — is the practical output truncation cap. +- Default generic line cap in `truncateHead()` is `3000`, but `glob` overrides `maxLines` to `Number.MAX_SAFE_INTEGER`, so byte size — not line count — is the practical output truncation cap. - Streaming update throttle: `200` ms between `onUpdate` emissions. - Sort order: most recent `mtime` first in the built-in local branch and promised in the prompt. The tool re-sorts in JS even though native glob receives `sortByMtime: true` so native code can still stop early at `maxResults`. ## Errors -- User-facing `ToolError`s from `FindTool.execute()` include: +- User-facing `ToolError`s from `GlobTool.execute()` include: - `` `paths` must contain non-empty globs or paths `` - `Path not found: ...` - `Searching from root directory '/' is not allowed` - `Limit must be a positive number` - `Path is not a directory: ...` - - timeout result text is `find timed out after s; returning partial matches — narrow the pattern instead of retrying blindly` and is returned as a successful, truncated partial result rather than an error. + - timeout result text is `glob timed out after s; returning partial matches — narrow the pattern instead of retrying blindly` and is returned as a successful, truncated partial result rather than an error. - If the caller aborts, the local branch converts `AbortError` into `ToolAbortError`. - Non-`ENOENT` stat failures and other unexpected errors are rethrown. - Empty matches are not errors; they return the no-files text result. ## Notes -- Reach for `find` for filename / path discovery. Reach for `search` when the selection criterion is file contents or regex matches; `search` takes a `pattern` and returns anchored content matches, while `find` only returns matching paths (`packages/coding-agent/src/prompts/tools/find.md`, `packages/coding-agent/src/prompts/tools/search.md`). +- Reach for `glob` for filename / path discovery. Reach for `grep` when the selection criterion is file contents or regex matches; `grep` takes a `pattern` and returns anchored content matches, while `glob` only returns matching paths (`packages/coding-agent/src/prompts/tools/glob.md`, `packages/coding-agent/src/prompts/tools/grep.md`). - Bare top-level globs are made recursive. `*.ts` is parsed as base `.` plus glob `**/*.ts`; `src/*.ts` stays rooted at `src` with a non-recursive `*.ts` segment; `src/**/*.ts` preserves explicit recursion. - `.gitignore` defaults to enabled in the built-in local branch. Use `gitignore: false` to disable it for native traversal. - `hidden` defaults to `true`; hidden-file exclusion is opt-out, not opt-in. - Multi-path missing-input tolerance applies in both branches, but only the built-in local branch surfaces `missingPaths` / `Skipped missing paths: ...`. The custom-operations branch hard-fails a missing `searchPath` only for single-input calls; in multi-input calls a missing target silently contributes no results. -- The custom `FindOperations.glob()` hook receives `ignore` and `limit`, but not the `hidden` flag or an explicit `.gitignore` toggle. A remote delegate must account for that itself if it wants parity with the local branch. +- The custom `GlobOperations.glob()` hook receives `ignore` and `limit`, but not the `hidden` flag or an explicit `.gitignore` toggle. A remote delegate must account for that itself if it wants parity with the local branch. - Built-in local globbing does not force `fileType: File`; it can return files and directories from native glob. Directory outputs also occur through exact-path passthrough or custom delegates that return them. \ No newline at end of file diff --git a/docs/tools/search.md b/docs/tools/grep.md similarity index 85% rename from docs/tools/search.md rename to docs/tools/grep.md index a18f43fd1..1bc6f0177 100644 --- a/docs/tools/search.md +++ b/docs/tools/grep.md @@ -1,10 +1,10 @@ -# search +# grep -> Search file contents with a regex across files, directories, globs, and internal URLs. +> Grep file contents with a regex across files, directories, globs, and internal URLs. ## Source -- Entry: `packages/coding-agent/src/tools/search.ts` -- Model-facing prompt: `packages/coding-agent/src/prompts/tools/search.md` +- Entry: `packages/coding-agent/src/tools/grep.ts` +- Model-facing prompt: `packages/coding-agent/src/prompts/tools/grep.md` - Key collaborators: - `packages/coding-agent/src/tools/match-line-format.ts` — model-facing anchor formatting. - `packages/coding-agent/src/tools/path-utils.ts` — path normalization, glob splitting, internal URL resolution. @@ -20,11 +20,11 @@ | Field | Type | Required | Description | | --- | --- | --- | --- | -| `pattern` | `string` | Yes | Regex pattern. `search.ts` rejects whitespace-only input but otherwise preserves the pattern verbatim (leading/trailing whitespace is meaningful in regexes). The native matcher enables multiline only when the pattern text contains a literal newline or the two-character sequence `\\n`. The native layer auto-escapes braces that cannot be valid repetition quantifiers, so patterns like `${platform}` stay searchable (see Notes). | +| `pattern` | `string` | Yes | Regex pattern. `grep.ts` rejects whitespace-only input but otherwise preserves the pattern verbatim (leading/trailing whitespace is meaningful in regexes). The native matcher enables multiline only when the pattern text contains a literal newline or the two-character sequence `\\n`. The native layer auto-escapes braces that cannot be valid repetition quantifiers, so patterns like `${platform}` stay searchable (see Notes). | | `paths` | `string \| string[]` | No | One file path, directory path, glob-like path, archive member, internal URL, or an array of those. Omitted or empty defaults to `.` (the workspace root). Append a line-range selector such as `:50-100` or `:5-16,960-973` to a single file/archive/internal-resource input to constrain matches. Empty strings are rejected after trimming/quote stripping. Single entries accidentally joined with comma, semicolon, or whitespace are expanded only after existence validation; existing paths containing delimiters stay intact. Filesystem-backed internal URLs search their backing file; virtual internal resources search resolved text in memory. Internal URLs cannot contain glob characters. | | `case` | `boolean` | No | Case-sensitive search. Defaults to `true`. Passed to native `ignoreCase` or JS `RegExp` flags for virtual resources. | | `gitignore` | `boolean` | No | Respect `.gitignore` during directory scans. Defaults to `true`. Passed to native `gitignore`. | -| `skip` | `number` | No | File-page offset for multi-file results. Defaults to `0`; `search.ts` floors finite numbers and rejects negative or non-finite values. Single-file searches ignore it because they do not paginate by file. | +| `skip` | `number` | No | File-page offset for multi-file results. Defaults to `0`; `grep.ts` floors finite numbers and rejects negative or non-finite values. Single-file searches ignore it because they do not paginate by file. | ## Outputs The tool returns a single text block in `content[0].text` plus structured `details`. @@ -45,13 +45,13 @@ The tool returns a single text block in `content[0].text` plus structured `detai - No-match result text is `No matches found` (or `No more results (...)` when `skip` points past the last file page), optionally followed by skipped missing-path, unreadable-archive, or oversized-file notes. ## Flow -1. `SearchTool.execute()` validates and normalizes input in `packages/coding-agent/src/tools/search.ts`: +1. `GrepTool.execute()` validates and normalizes input in `packages/coding-agent/src/tools/grep.ts`: - rejects whitespace-only patterns while preserving the pattern verbatim; - defaults omitted or empty `paths` to `["."]` (the workspace root); - normalizes `skip` to a non-negative integer; - expands delimiter-flattened `paths` entries with `expandDelimitedPathEntries()`, keeping existing delimiter-containing paths intact, accepting comma/semicolon splits when at least one part resolves, and accepting whitespace splits only when every part resolves; - peels any line-range selector from each resulting entry; - - reads `search.contextBefore` and `search.contextAfter` from session settings (`1` and `3` by default); + - reads `grep.contextBefore` and `grep.contextAfter` from session settings (`1` and `3` by default); - enables multiline only when `pattern` contains `\n` or an actual newline. 2. Each `paths` entry is normalized with `normalizePathLikeInput()` again during shared scope resolution; this is a no-op for entries already normalized by delimiter expansion. 3. Archive member paths such as `bundle.zip:src/foo.ts` are materialized to temporary UTF-8 scratch files before native grep. Binary or non-UTF-8 archive members are reported as skipped/unreadable. @@ -66,7 +66,7 @@ The tool returns a single text block in `content[0].text` plus structured `detai - one entry: `parseSearchPath()` splits `basePath` and optional glob; - multiple entries: `resolveExplicitSearchPaths()` (via `resolveToolSearchScope()`) computes a common base directory, brace-union glob, exact-file list, or per-entry target list. Targets fan out when the common ancestor is not itself a requested scope, or when a plain-file entry would otherwise be demoted into a directory walk's glob union (`fanOutFileTargets`). 7. Line-range selectors are validated after path/archive/internal resolution. They are allowed only for single files, archive members, or virtual resources; glob/directory line-range selectors error. -8. `search.ts` stats the resolved base path to decide file vs directory behavior. +8. `grep.ts` stats the resolved base path to decide file vs directory behavior. 9. It calls native `grep()` from `@oh-my-pi/pi-natives` with: - `pattern`, `ignoreCase`, `multiline`, `gitignore`; - `hidden: true`; @@ -81,7 +81,7 @@ The tool returns a single text block in `content[0].text` plus structured `detai - `build_matcher()` sanitizes non-quantifier braces before regex compile; - if compile fails with unopened/unclosed-group errors, it retries after escaping previously unescaped parentheses; - directory scans use the grep pipeline described in `docs/natives-text-search-pipeline.md`. -11. Search dispatch differs by resolved path set: +11. Grep dispatch differs by resolved path set: - exact explicit files or fanned-out multi-targets: JS loops over targets, merges `grep()` results itself, and deduplicates overlapping targets by absolute path + line number; - single file/directory base: one `grep()` call handles native scanning. 12. Virtual internal resources are searched in JS with `RegExp`; archive scratch paths and virtual paths are remapped back to user-facing selectors before rendering. @@ -127,19 +127,19 @@ The tool returns a single text block in `content[0].text` plus structured `detai - Populates tool `details.meta` with truncation/limit metadata. - Background work / cancellation - Wrapped in `untilAborted(signal, ...)` at the JS level. - - `search.ts` passes the abort `signal` and `timeoutMs: SEARCH_GREP_TIMEOUT_MS` (`30_000`) into native `grep()`, so native scans are cancellable and time-bounded. + - `grep.ts` passes the abort `signal` and `timeoutMs: SEARCH_GREP_TIMEOUT_MS` (`30_000`) into native `grep()`, so native scans are cancellable and time-bounded. ## Limits & Caps -- File page limit: `20` files (`DEFAULT_FILE_LIMIT` in `packages/coding-agent/src/tools/search.ts`). +- File page limit: `20` files (`DEFAULT_FILE_LIMIT` in `packages/coding-agent/src/tools/grep.ts`). - Per-file match caps: `20` for multi-file scopes (`MULTI_FILE_PER_FILE_MATCHES`), `200` for single-file scopes (`SINGLE_FILE_MATCHES`). - Native/JS preselection cap: `2000` matches (`INTERNAL_TOTAL_CAP`). - Line truncation: `512` characters per emitted line (`DEFAULT_MAX_COLUMN` in `packages/coding-agent/src/session/streaming-output.ts`). Native grep marks truncated lines; JS reports `linesTruncated`. -- Final text truncation: `truncateHead()` default byte cap `50 * 1024` bytes (`DEFAULT_MAX_BYTES` in `packages/coding-agent/src/session/streaming-output.ts`). `search.ts` overrides `maxLines` to `Number.MAX_SAFE_INTEGER`, so normal search output is byte-capped, not line-capped. -- Context defaults: `search.contextBefore = 1`, `search.contextAfter = 3` in `packages/coding-agent/src/config/settings-schema.ts`. +- Final text truncation: `truncateHead()` default byte cap `50 * 1024` bytes (`DEFAULT_MAX_BYTES` in `packages/coding-agent/src/session/streaming-output.ts`). `grep.ts` overrides `maxLines` to `Number.MAX_SAFE_INTEGER`, so normal grep output is byte-capped, not line-capped. +- Context defaults: `grep.contextBefore = 1`, `grep.contextAfter = 3` in `packages/coding-agent/src/config/settings-schema.ts`. - Pagination: `skip` is a file-page offset for multi-file scopes. The result text says `Use skip= for the next page` when more files remain. - Native directory-scan cache: available in `grep.rs`, but this tool always sets `cache: false`. -- Native grep wall-clock budget: `30_000ms` per invocation (`SEARCH_GREP_TIMEOUT_MS` in `packages/coding-agent/src/tools/search.ts`); hitting it raises `Search timed out after 30s; ...`. -- Native per-file size cap: `4 * 1024 * 1024` bytes (`MAX_FILE_BYTES` in `crates/pi-natives/src/grep.rs`, mirrored as `NATIVE_GREP_MAX_FILE_BYTES` in `search.ts`). Oversized files are silently skipped by native grep; `search.ts` surfaces a `Skipped oversized file(s)` note (with names for explicit file targets, a count for directory scans). +- Native grep wall-clock budget: `30_000ms` per invocation (`SEARCH_GREP_TIMEOUT_MS` in `packages/coding-agent/src/tools/grep.ts`); hitting it raises `Grep timed out after 30s; ...`. +- Native per-file size cap: `4 * 1024 * 1024` bytes (`MAX_FILE_BYTES` in `crates/pi-natives/src/grep.rs`, mirrored as `NATIVE_GREP_MAX_FILE_BYTES` in `grep.ts`). Oversized files are silently skipped by native grep; `grep.ts` surfaces a `Skipped oversized file(s)` note (with names for explicit file targets, a count for directory scans). ## Errors - `Pattern must not be empty` when trimmed `pattern` is empty. @@ -151,14 +151,14 @@ The tool returns a single text block in `content[0].text` plus structured `detai - `Path not found: ...; pass each path as its own array element` when a filesystem-backed resolved base path is missing, or when every multi-path filesystem entry is missing (with an archive hint when unreadable archive members contributed). - Virtual internal URL regex compile failures are reported as `Invalid regex: ...` from JavaScript `RegExp`; filesystem-backed regex failures beginning with `regex` or `regex parse error` are normalized to `Invalid regex: ...`. - Multi-file native scans skip per-file open/search failures inside `grep.rs`; the scan continues with surviving files. -- ``Search timed out after 30s; narrow paths or pattern, or scope with `find` first`` when native grep hits `SEARCH_GREP_TIMEOUT_MS`. +- ``Grep timed out after 30s; narrow paths or pattern, or scope with `glob` first`` when native grep hits `SEARCH_GREP_TIMEOUT_MS`. ## Notes - The model-facing prompt documents Rust regex syntax (RE2-style; no lookaround or backreferences). Filesystem-backed searches use that native engine; virtual internal URL content is searched with JavaScript `RegExp`. - Native `build_matcher()` already auto-escapes braces that cannot be valid quantifiers, so patterns like `${platform}` become searchable instead of failing. Valid quantifiers like `a{2,4}` remain unchanged. - Native compile retry also escapes unescaped literal parentheses only after an unopened/unclosed-group parse error. It is a fallback, not a general parser mode. - Internal URLs are resolved before path existence checks. Backed resources become ordinary filesystem paths; virtual resources stay in memory and do not mint editable hashline anchors. -- `hidden:true` is hard-coded in `search.ts`; there is no model-facing flag to exclude dotfiles. +- `hidden:true` is hard-coded in `grep.ts`; there is no model-facing flag to exclude dotfiles. - `gitignore:false` only affects native directory traversal. It does not disable the tool's own path normalization or explicit-file handling. - When `paths` resolves to multiple exact files, each target uses the `2000` internal cap before JS grouping. -- The section tag in hashline mode is a four-hex opaque snapshot tag from the session snapshot store; `search` records whole-file snapshots when possible and prints bare line numbers beneath the header. +- The section tag in hashline mode is a four-hex opaque snapshot tag from the session snapshot store; `grep` records whole-file snapshots when possible and prints bare line numbers beneath the header. diff --git a/docs/tools/search_tool_bm25.md b/docs/tools/search_tool_bm25.md index b7c741425..d88b56257 100644 --- a/docs/tools/search_tool_bm25.md +++ b/docs/tools/search_tool_bm25.md @@ -113,6 +113,6 @@ - Built-in entries appear only in `"all"` mode and only for registry tools whose `loadMode === "discoverable"` and are not currently active. - Hidden/internal built-ins are intentionally excluded from the built-in corpus: `resolve`, `yield`, `report_finding`, `report_tool_issue` are called out in the `#collectDiscoverableBuiltinTools()` comment. - `DiscoverableToolSource` includes `"extension"` and `"custom"`, but `AgentSession.getDiscoverableTools()` currently assembles only built-in and MCP sources. -- On startup, `packages/coding-agent/src/sdk.ts` resolves `"auto"` after the full registry exists and injects `search_tool_bm25` when the count exceeds 40. It hides non-essential discoverable built-ins only in `tools.discoveryMode = "all"`. Tools whose class is marked as `loadMode === "essential"` (defaults are `read`, `bash`, `edit`, `write`, and `find`) are always active; they survive hiding regardless of configuration. `tools.essentialOverride` can be used to treat additional discoverable tools as essential (active on startup) or to explicitly specify the active essential list. +- On startup, `packages/coding-agent/src/sdk.ts` resolves `"auto"` after the full registry exists and injects `search_tool_bm25` when the count exceeds 40. It hides non-essential discoverable built-ins only in `tools.discoveryMode = "all"`. Tools whose class is marked as `loadMode === "essential"` (defaults are `read`, `bash`, `edit`, `write`, and `glob`) are always active; they survive hiding regardless of configuration. `tools.essentialOverride` can be used to treat additional discoverable tools as essential (active on startup) or to explicitly specify the active essential list. - Query tokenization is simple and deterministic: Unicode is NFKD-normalized, combining marks are dropped, acronym/camelCase and digit-to-capital boundaries are split, non-letter/non-number characters become spaces, tokens are lowercased, and only non-empty tokens survive. - Scores are rounded differently by surface: `details.tools[].score` keeps 6 decimals; the TUI line renders 3. diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 13d21db40..f19af90f7 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,12 +1,17 @@ # Changelog ## [Unreleased] + ### Added - Added support for file deletion and moving within file editing operations ### Changed +- Renamed `search` tool to `grep` +- Renamed `find` tool to `glob` +- Updated configuration keys and UI labels to reflect `grep` and `glob` tool naming +- Automatically migrated existing user settings for `search` and `find` to `grep` and `glob` configs - Changed the `inlineToolDescriptors` setting ("Inline Tool Descriptors") from a boolean to a three-way enum (`auto` | `on` | `off`), defaulting to `auto`. `auto` inlines tool descriptors into the system prompt (and strips them from provider tool schemas) only for Gemini models, leaving them in the schemas otherwise; `on`/`off` force the behavior regardless of model. Existing `true`/`false` configs migrate to `on`/`off`. - Replaced `as string | undefined` inline casts with `typeof` guards in the TUI usage renderer's account identity resolution (`formatAccountLabel`, `formatUnlimitedReportLabel`, reset-credits label, and unlimited-plan tier), so empty-string metadata values fall through to the next fallback instead of being displayed diff --git a/packages/coding-agent/src/advisor/__tests__/advisor.test.ts b/packages/coding-agent/src/advisor/__tests__/advisor.test.ts index 868d010f6..531dfea69 100644 --- a/packages/coding-agent/src/advisor/__tests__/advisor.test.ts +++ b/packages/coding-agent/src/advisor/__tests__/advisor.test.ts @@ -31,7 +31,7 @@ describe("advisor", () => { { type: "toolCall", id: "search-timeout", - name: "search", + name: "grep", arguments: { pattern: "needle", paths: ["packages/coding-agent/src"] }, }, ], @@ -40,7 +40,7 @@ describe("advisor", () => { { role: "toolResult", toolCallId: "search-timeout", - toolName: "search", + toolName: "grep", content: [{ type: "text", text: "timed out after 30s" }], isError: true, timestamp: 2, @@ -711,13 +711,13 @@ describe("advisor", () => { } as AgentMessage, { role: "assistant", - content: [{ type: "toolCall", id: "b", name: "search", arguments: { pattern: "y" } }], + content: [{ type: "toolCall", id: "b", name: "grep", arguments: { pattern: "y" } }], timestamp: 4, } as unknown as AgentMessage, { role: "toolResult", toolCallId: "b", - toolName: "search", + toolName: "grep", content: [{ type: "text", text: "ok" }], isError: false, timestamp: 5, @@ -1044,9 +1044,9 @@ describe("advisor", () => { describe("read-only tool allowlist", () => { it("selects only the investigation tools from a mixed toolset", () => { - const toolset = ["read", "edit", "search", "bash", "find", "write", "advise"]; + const toolset = ["read", "edit", "grep", "bash", "glob", "write", "advise"]; const selected = toolset.filter(name => ADVISOR_READONLY_TOOL_NAMES.has(name)); - expect(selected).toEqual(["read", "search", "find"]); + expect(selected).toEqual(["read", "grep", "glob"]); expect(ADVISOR_READONLY_TOOL_NAMES.has("edit")).toBe(false); expect(ADVISOR_READONLY_TOOL_NAMES.has("bash")).toBe(false); expect(ADVISOR_READONLY_TOOL_NAMES.has("write")).toBe(false); diff --git a/packages/coding-agent/src/advisor/advise-tool.ts b/packages/coding-agent/src/advisor/advise-tool.ts index 88ee1f79a..18c769371 100644 --- a/packages/coding-agent/src/advisor/advise-tool.ts +++ b/packages/coding-agent/src/advisor/advise-tool.ts @@ -137,7 +137,7 @@ export function deriveAdvisorTelemetry( * inspect the workspace before weighing in. Names match the primary session's * tool instances, which the advisor reuses. */ -export const ADVISOR_READONLY_TOOL_NAMES: ReadonlySet = new Set(["read", "search", "find"]); +export const ADVISOR_READONLY_TOOL_NAMES: ReadonlySet = new Set(["read", "grep", "glob"]); function advisorNoteDedupeKey(note: string): string { return note.trim().replace(/\s+/g, " "); diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index 364bb94ac..ea406e170 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -4,7 +4,7 @@ import { APP_NAME, CONFIG_DIR_NAME, logger } from "@oh-my-pi/pi-utils"; import chalk from "chalk"; import { CLI_THINKING_LEVELS, type ConfiguredThinkingLevel, parseCliThinkingLevel } from "../thinking"; -import { BUILTIN_TOOL_NAMES } from "../tools/builtin-names"; +import { BUILTIN_TOOL_NAMES, normalizeToolNames } from "../tools/builtin-names"; import { OPTIONAL_FLAGS, OPTIONAL_VALUE_FLAGS, @@ -90,6 +90,7 @@ const PARSE_DEPS: ParseDeps = { logger, parseThinking: parseCliThinkingLevel, builtinToolNames: BUILTIN_TOOL_NAMES, + normalizeToolNames, thinkingEfforts: CLI_THINKING_LEVELS, }; @@ -330,7 +331,7 @@ ${chalk.bold("Available Tools (default-enabled unless noted):")} edit - Edit files with find/replace write - Write files (creates/overwrites) grep - Search file contents - find - Find files by glob pattern + glob - Find files by glob pattern lsp - Language server protocol (code intelligence) python - Execute Python code (requires: ${APP_NAME} setup python) notebook - Edit Jupyter notebooks diff --git a/packages/coding-agent/src/cli/flag-tables.ts b/packages/coding-agent/src/cli/flag-tables.ts index fd315145e..e43fc5445 100644 --- a/packages/coding-agent/src/cli/flag-tables.ts +++ b/packages/coding-agent/src/cli/flag-tables.ts @@ -46,6 +46,7 @@ export interface ParseDeps { logger: { warn: (message: string, meta?: Record) => void }; parseThinking: (value: string | null | undefined) => ConfiguredThinkingLevel | undefined; builtinToolNames: readonly string[]; + normalizeToolNames: (values: Iterable) => string[]; thinkingEfforts: readonly string[]; } @@ -147,10 +148,12 @@ export const STRING_SETTERS: Record = { result.models = value.split(",").map(s => s.trim()); }, "--tools": (result, value, deps) => { - const names = value - .split(",") - .map(s => s.trim().toLowerCase()) - .filter(Boolean); + const names = deps.normalizeToolNames( + value + .split(",") + .map(s => s.trim()) + .filter(Boolean), + ); const valid: string[] = []; for (const name of names) { if (deps.builtinToolNames.includes(name)) { diff --git a/packages/coding-agent/src/cli/gallery-fixtures/fs.ts b/packages/coding-agent/src/cli/gallery-fixtures/fs.ts index d62389b59..c7ae7d137 100644 --- a/packages/coding-agent/src/cli/gallery-fixtures/fs.ts +++ b/packages/coding-agent/src/cli/gallery-fixtures/fs.ts @@ -1,17 +1,17 @@ // biome-ignore-all lint/suspicious/noTemplateCurlyInString: sample source-code strings (read fixtures) intentionally contain literal ${...}. -// Gallery fixtures for the filesystem tools (read, write, find). +// Gallery fixtures for the filesystem tools (read, write, glob). import { ReadToolGroupComponent } from "../../modes/components/read-tool-group"; import type { GalleryFixture, GalleryFixtureState, GalleryResult } from "./types"; const readSnippet = [ - "export const findToolRenderer = {", + "export const globToolRenderer = {", "\tinline: true,", - "\trenderCall(args: FindRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {", + "\trenderCall(args: GlobRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {", "\t\tconst meta: string[] = [];", "\t\tif (args.limit !== undefined) meta.push(`limit:${args.limit}`);", "", "\t\tconst text = renderStatusLine(", - '\t\t\t{ icon: "pending", title: "Find", description: formatFindRenderPaths(args.paths) || "*", meta },', + '\t\t\t{ icon: "pending", title: "Glob", description: formatGlobRenderPaths(args.paths) || "*", meta },', "\t\t\tuiTheme,", "\t\t);", "\t\treturn new Text(text, 0, 0);", @@ -99,22 +99,22 @@ export const fsFixtures: Record = { read: { label: "Read", // Streaming: path still being typed, selector not yet appended. - streamingArgs: { path: "packages/coding-agent/src/tools/find" }, - args: { path: "packages/coding-agent/src/tools/find.ts:437-448" }, + streamingArgs: { path: "packages/coding-agent/src/tools/glob" }, + args: { path: "packages/coding-agent/src/tools/glob.ts:437-448" }, result: { content: [ { type: "text", text: [ - "[packages/coding-agent/src/tools/find.ts#E48E]", - "437:export const findToolRenderer = {", + "[packages/coding-agent/src/tools/glob.ts#E48E]", + "437:export const globToolRenderer = {", "438:\tinline: true,", - "439:\trenderCall(args: FindRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {", + "439:\trenderCall(args: GlobRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {", "440:\t\tconst meta: string[] = [];", "441:\t\tif (args.limit !== undefined) meta.push(`limit:${args.limit}`);", "442:", "443:\t\tconst text = renderStatusLine(", - '444:\t\t\t{ icon: "pending", title: "Find", description: formatFindRenderPaths(args.paths) || "*", meta },', + '444:\t\t\t{ icon: "pending", title: "Glob", description: formatGlobRenderPaths(args.paths) || "*", meta },', "445:\t\t\tuiTheme,", "446:\t\t);", "447:\t\treturn new Text(text, 0, 0);", @@ -124,7 +124,7 @@ export const fsFixtures: Record = { ], details: { kind: "file", - resolvedPath: "/Users/dev/Projects/pi/packages/coding-agent/src/tools/find.ts", + resolvedPath: "/Users/dev/Projects/pi/packages/coding-agent/src/tools/glob.ts", contentType: "text/typescript", displayContent: { text: readSnippet, startLine: 437 }, }, @@ -134,7 +134,7 @@ export const fsFixtures: Record = { content: [ { type: "text", - text: "Error: ENOENT: no such file or directory, open 'packages/coding-agent/src/tools/find.ts'", + text: "Error: ENOENT: no such file or directory, open 'packages/coding-agent/src/tools/glob.ts'", }, ], }, @@ -179,8 +179,8 @@ export const fsFixtures: Record = { }, }, - find: { - label: "Find", + glob: { + label: "Glob", // Streaming: glob half-typed, no limit yet. streamingArgs: { paths: ["packages/coding-agent/src/tools/*-render"] }, args: { paths: ["packages/coding-agent/src/**/*.test.ts"], limit: 50 }, @@ -191,7 +191,7 @@ export const fsFixtures: Record = { text: [ "packages/coding-agent/src/tools/read.test.ts", "packages/coding-agent/src/tools/write.test.ts", - "packages/coding-agent/src/tools/find.test.ts", + "packages/coding-agent/src/tools/glob.test.ts", "packages/coding-agent/src/cli/gallery-cli.test.ts", "packages/coding-agent/src/edit/edit.test.ts", ].join("\n"), @@ -205,7 +205,7 @@ export const fsFixtures: Record = { files: [ "packages/coding-agent/src/cli/gallery-cli.test.ts", "packages/coding-agent/src/edit/edit.test.ts", - "packages/coding-agent/src/tools/find.test.ts", + "packages/coding-agent/src/tools/glob.test.ts", "packages/coding-agent/src/tools/read.test.ts", "packages/coding-agent/src/tools/write.test.ts", ], @@ -213,7 +213,7 @@ export const fsFixtures: Record = { }, errorResult: { isError: true, - content: [{ type: "text", text: "Find failed: invalid glob pattern '[unclosed'." }], + content: [{ type: "text", text: "Glob failed: invalid glob pattern '[unclosed'." }], details: { error: "invalid glob pattern '[unclosed'" }, }, }, diff --git a/packages/coding-agent/src/cli/gallery-fixtures/search.ts b/packages/coding-agent/src/cli/gallery-fixtures/search.ts index 2a0962070..241f930fa 100644 --- a/packages/coding-agent/src/cli/gallery-fixtures/search.ts +++ b/packages/coding-agent/src/cli/gallery-fixtures/search.ts @@ -1,9 +1,9 @@ -/** Gallery fixtures for the search tools (search, search_tool_bm25, ast_grep). */ +/** Gallery fixtures for the search tools (grep, search_tool_bm25, ast_grep). */ import type { GalleryFixture } from "./types"; export const searchFixtures: Record = { - search: { - label: "Search", + grep: { + label: "Grep", streamingArgs: { pattern: "useState", }, @@ -101,7 +101,7 @@ export const searchFixtures: Record = { limit: 5, total_tools: 142, activated_tools: ["docling_extract_tables", "docling_convert", "pdf_read_text"], - active_selected_tools: ["read", "search", "edit", "bash"], + active_selected_tools: ["read", "grep", "edit", "bash"], tools: [ { name: "docling_extract_tables", diff --git a/packages/coding-agent/src/config/settings-schema.ts b/packages/coding-agent/src/config/settings-schema.ts index e69d84b1a..4930e6440 100644 --- a/packages/coding-agent/src/config/settings-schema.ts +++ b/packages/coding-agent/src/config/settings-schema.ts @@ -289,13 +289,13 @@ export const DEFAULT_BASH_INTERCEPTOR_RULES: BashInterceptorRule[] = [ }, { pattern: "^\\s*(grep|rg|ripgrep|ag|ack)\\s+", - tool: "search", - message: "Use the `search` tool instead of grep/rg. It respects .gitignore and provides structured output.", + tool: "grep", + message: "Use the `grep` tool instead of grep/rg. It respects .gitignore and provides structured output.", }, { pattern: "^\\s*(find|fd|locate)\\s+.*(-name|-iname|-type|--type|-glob)", - tool: "find", - message: "Use the `find` tool instead of find/fd. It respects .gitignore and is faster for glob patterns.", + tool: "glob", + message: "Use the `glob` tool instead of find/fd. It respects .gitignore and is faster for glob patterns.", }, { pattern: "^\\s*sed\\s+(-i|--in-place)", @@ -3238,37 +3238,37 @@ export const SETTINGS_SCHEMA = { }, }, - // Search and AST tools - "find.enabled": { + // Grep, glob, and AST tools + "glob.enabled": { type: "boolean", default: true, ui: { tab: "tools", group: "Available Tools", - label: "Find", - description: "Enable the find tool for glob-based file lookup", + label: "Glob", + description: "Enable the glob tool for glob-based file lookup", }, }, - "search.enabled": { + "grep.enabled": { type: "boolean", default: true, ui: { tab: "tools", group: "Available Tools", - label: "Search", - description: "Enable the search tool for regex content search", + label: "Grep", + description: "Enable the grep tool for regex content search", }, }, - "search.contextBefore": { + "grep.contextBefore": { type: "number", default: 1, ui: { tab: "tools", - group: "Search & Browser", - label: "Search Context Before", - description: "Lines of context before each search match", + group: "Grep & Browser", + label: "Grep Context Before", + description: "Lines of context before each grep match", options: [ { value: "0", label: "0 lines" }, { value: "1", label: "1 line" }, @@ -3279,14 +3279,14 @@ export const SETTINGS_SCHEMA = { }, }, - "search.contextAfter": { + "grep.contextAfter": { type: "number", default: 3, ui: { tab: "tools", - group: "Search & Browser", - label: "Search Context After", - description: "Lines of context after each search match", + group: "Grep & Browser", + label: "Grep Context After", + description: "Lines of context after each grep match", options: [ { value: "0", label: "0 lines" }, { value: "1", label: "1 line" }, @@ -3618,7 +3618,7 @@ export const SETTINGS_SCHEMA = { group: "Discovery & MCP", label: "Essential Tools Override", description: - "Override the always-loaded built-in tools (default: read, bash, edit, write, find, eval). Leave empty to use defaults.", + "Override the always-loaded built-in tools (default: read, bash, edit, write, glob, eval). Leave empty to use defaults.", }, }, diff --git a/packages/coding-agent/src/config/settings.ts b/packages/coding-agent/src/config/settings.ts index ecb0f161a..096a820db 100644 --- a/packages/coding-agent/src/config/settings.ts +++ b/packages/coding-agent/src/config/settings.ts @@ -30,6 +30,7 @@ import type { ModelRole } from "../config/model-roles"; import { loadCapability } from "../discovery"; import { isLightTheme, setAutoThemeMapping, setColorBlindMode, setSymbolPreset } from "../modes/theme/theme"; import { AgentStorage } from "../session/agent-storage"; +import { normalizeToolName } from "../tools/builtin-names"; import { type EditMode, normalizeEditMode } from "../utils/edit-mode"; import { withFileLock } from "./file-lock"; import { @@ -996,6 +997,118 @@ export class Settings { delete raw["power.preventDisplaySleep"]; } + // Migration for renamed settings grep.* and glob.* from search.* and find.*: + // 1. Nested settings: find -> glob, search -> grep (per-property merge to avoid clobbering) + const ensureRawObject = (key: "glob" | "grep"): Record => { + const current = raw[key]; + if (current && typeof current === "object" && !Array.isArray(current)) { + return current as Record; + } + const created: Record = {}; + raw[key] = created; + return created; + }; + + if ("find" in raw) { + const findObj = raw.find; + if (findObj && typeof findObj === "object" && !Array.isArray(findObj)) { + const globObj = ensureRawObject("glob"); + for (const key of ["enabled"]) { + if (key in findObj && !(key in globObj)) { + globObj[key] = findObj[key]; + } + } + } + delete raw.find; + } + + if ("search" in raw) { + const searchObj = raw.search; + if (searchObj && typeof searchObj === "object" && !Array.isArray(searchObj)) { + const grepObj = ensureRawObject("grep"); + for (const key of ["enabled", "contextBefore", "contextAfter"]) { + if (key in searchObj && !(key in grepObj)) { + grepObj[key] = searchObj[key]; + } + } + } + delete raw.search; + } + + // 2. Flat settings keys: map them to the proper nested target so get/set resolves them correctly + if ("find.enabled" in raw) { + const globObj = ensureRawObject("glob"); + if (!("enabled" in globObj)) { + globObj.enabled = raw["find.enabled"]; + } + delete raw["find.enabled"]; + } + if ("search.enabled" in raw) { + const grepObj = ensureRawObject("grep"); + if (!("enabled" in grepObj)) { + grepObj.enabled = raw["search.enabled"]; + } + delete raw["search.enabled"]; + } + if ("search.contextBefore" in raw) { + const grepObj = ensureRawObject("grep"); + if (!("contextBefore" in grepObj)) { + grepObj.contextBefore = raw["search.contextBefore"]; + } + delete raw["search.contextBefore"]; + } + if ("search.contextAfter" in raw) { + const grepObj = ensureRawObject("grep"); + if (!("contextAfter" in grepObj)) { + grepObj.contextAfter = raw["search.contextAfter"]; + } + delete raw["search.contextAfter"]; + } + + // 3. Tool-name arrays use wire IDs too. Preserve user overrides across + // the rename without duplicating entries if they already added grep/glob. + const migrateToolNameList = (names: unknown): unknown => { + if (!Array.isArray(names)) return names; + const out: unknown[] = []; + const seen = new Set(); + for (const name of names) { + const migrated = typeof name === "string" ? normalizeToolName(name) : name; + if (typeof migrated === "string") { + if (seen.has(migrated)) continue; + seen.add(migrated); + } + out.push(migrated); + } + return out; + }; + const ensureToolsObject = (): Record => { + const current = raw.tools; + if (current && typeof current === "object" && !Array.isArray(current)) { + return current as Record; + } + const created: Record = {}; + raw.tools = created; + return created; + }; + const toolsObj = raw.tools as Record | undefined; + if (toolsObj && "essentialOverride" in toolsObj) { + toolsObj.essentialOverride = migrateToolNameList(toolsObj.essentialOverride); + } + if ("tools.essentialOverride" in raw) { + const nestedToolsObj = ensureToolsObject(); + if (!("essentialOverride" in nestedToolsObj)) { + nestedToolsObj.essentialOverride = migrateToolNameList(raw["tools.essentialOverride"]); + } + delete raw["tools.essentialOverride"]; + } + + // Also clean up any empty nested objects we might have created or left behind + if (raw.glob && typeof raw.glob === "object" && Object.keys(raw.glob).length === 0) { + delete raw.glob; + } + if (raw.grep && typeof raw.grep === "object" && Object.keys(raw.grep).length === 0) { + delete raw.grep; + } // readHashLines: removed. Hashline anchors are now driven solely by // edit.mode === "hashline"; the separate read toggle only ever produced // the incoherent "hashline edits without addressable anchors" state. diff --git a/packages/coding-agent/src/cursor.ts b/packages/coding-agent/src/cursor.ts index 36b900794..b63fed4dd 100644 --- a/packages/coding-agent/src/cursor.ts +++ b/packages/coding-agent/src/cursor.ts @@ -178,7 +178,7 @@ export class CursorExecHandlers implements ICursorExecHandlers { async grep(args: Parameters>[0]) { const toolCallId = decodeToolCallId(args.toolCallId); const searchPath = args.glob ? `${args.path || "."}/${args.glob}` : args.path || "."; - const toolResultMessage = await executeTool(this.options, "search", toolCallId, { + const toolResultMessage = await executeTool(this.options, "grep", toolCallId, { pattern: args.pattern, paths: [searchPath], case: args.caseInsensitive === true ? false : undefined, diff --git a/packages/coding-agent/src/discovery/helpers.ts b/packages/coding-agent/src/discovery/helpers.ts index c9ed0e170..95138d3ff 100644 --- a/packages/coding-agent/src/discovery/helpers.ts +++ b/packages/coding-agent/src/discovery/helpers.ts @@ -18,6 +18,7 @@ import { parseRuleConditionAndScope, type Rule, type RuleFrontmatter } from "../ import type { Skill, SkillFrontmatter } from "../capability/skill"; import type { LoadContext, LoadResult, SourceMeta } from "../capability/types"; import { parseThinkingLevel } from "../thinking"; +import { normalizeToolNames } from "../tools/builtin-names"; import { buildPluginDirRoot } from "./plugin-dir-roots"; @@ -246,7 +247,8 @@ export function parseAgentFields(frontmatter: Record): ParsedAg return null; } - let tools = parseArrayOrCSV(frontmatter.tools)?.map(tool => tool.toLowerCase()); + let tools = parseArrayOrCSV(frontmatter.tools); + if (tools) tools = normalizeToolNames(tools); // Subagents with explicit tool lists always need yield if (tools && !tools.includes("yield")) { diff --git a/packages/coding-agent/src/eval/js/tool-bridge.ts b/packages/coding-agent/src/eval/js/tool-bridge.ts index 60768763b..b2efc8c5f 100644 --- a/packages/coding-agent/src/eval/js/tool-bridge.ts +++ b/packages/coding-agent/src/eval/js/tool-bridge.ts @@ -88,9 +88,9 @@ function summarizeToolResult( path: record.path, count: details.matchCount ?? undefined, }); - case "find": + case "glob": return withError({ - op: "find", + op: "glob", pattern: record.pattern, count: details.fileCount ?? undefined, matches: Array.isArray(details.files) ? details.files.slice(0, 20) : undefined, diff --git a/packages/coding-agent/src/export/html/template.js b/packages/coding-agent/src/export/html/template.js index a0da25685..5c7796b55 100644 --- a/packages/coding-agent/src/export/html/template.js +++ b/packages/coding-agent/src/export/html/template.js @@ -433,10 +433,12 @@ const cmd = rawCmd.replace(/[\n\t]/g, ' ').trim().slice(0, 50); return `[bash: ${cmd}${rawCmd.length > 50 ? '...' : ''}]`; } + case 'search': case 'grep': return `[grep: /${args.pattern || ''}/ in ${shortenPath(String((args.paths || [args.path || '.']).join(', ')))}]`; case 'find': - return `[find: ${shortenPath(String((args.paths || [args.pattern || '.']).join(', ')))}]`; + case 'glob': + return `[glob: ${shortenPath(String((args.paths || [args.pattern || '.']).join(', ')))}]`; case 'ls': return `[ls: ${shortenPath(String(args.path || '.'))}]`; default: { diff --git a/packages/coding-agent/src/extensibility/extensions/types.ts b/packages/coding-agent/src/extensibility/extensions/types.ts index 247287ff8..b93535142 100644 --- a/packages/coding-agent/src/extensibility/extensions/types.ts +++ b/packages/coding-agent/src/extensibility/extensions/types.ts @@ -50,12 +50,12 @@ import type { ReadonlySessionManager, SessionManager } from "../../session/sessi import type { BashToolDetails, BashToolInput, - FindToolDetails, - FindToolInput, + GlobToolDetails, + GlobToolInput, + GrepToolDetails, + GrepToolInput, ReadToolDetails, ReadToolInput, - SearchToolDetails, - SearchToolInput, WriteToolInput, } from "../../tools"; import type { ApprovalMode } from "../../tools/approval"; @@ -697,14 +697,14 @@ export interface WriteToolCallEvent extends ToolCallEventBase { input: WriteToolInput; } -export interface SearchToolCallEvent extends ToolCallEventBase { - toolName: "search"; - input: SearchToolInput; +export interface GrepToolCallEvent extends ToolCallEventBase { + toolName: "grep"; + input: GrepToolInput; } -export interface FindToolCallEvent extends ToolCallEventBase { - toolName: "find"; - input: FindToolInput; +export interface GlobToolCallEvent extends ToolCallEventBase { + toolName: "glob"; + input: GlobToolInput; } export interface CustomToolCallEvent extends ToolCallEventBase { @@ -718,8 +718,8 @@ export type ToolCallEvent = | ReadToolCallEvent | EditToolCallEvent | WriteToolCallEvent - | SearchToolCallEvent - | FindToolCallEvent + | GrepToolCallEvent + | GlobToolCallEvent | CustomToolCallEvent; interface ToolResultEventBase { @@ -750,14 +750,14 @@ export interface WriteToolResultEvent extends ToolResultEventBase { details: undefined; } -export interface SearchToolResultEvent extends ToolResultEventBase { - toolName: "search"; - details: SearchToolDetails | undefined; +export interface GrepToolResultEvent extends ToolResultEventBase { + toolName: "grep"; + details: GrepToolDetails | undefined; } -export interface FindToolResultEvent extends ToolResultEventBase { - toolName: "find"; - details: FindToolDetails | undefined; +export interface GlobToolResultEvent extends ToolResultEventBase { + toolName: "glob"; + details: GlobToolDetails | undefined; } export interface CustomToolResultEvent extends ToolResultEventBase { @@ -771,8 +771,8 @@ export type ToolResultEvent = | ReadToolResultEvent | EditToolResultEvent | WriteToolResultEvent - | SearchToolResultEvent - | FindToolResultEvent + | GrepToolResultEvent + | GlobToolResultEvent | CustomToolResultEvent; /** @@ -799,8 +799,8 @@ export function isToolCallEventType(toolName: "bash", event: ToolCallEvent): eve export function isToolCallEventType(toolName: "read", event: ToolCallEvent): event is ReadToolCallEvent; export function isToolCallEventType(toolName: "edit", event: ToolCallEvent): event is EditToolCallEvent; export function isToolCallEventType(toolName: "write", event: ToolCallEvent): event is WriteToolCallEvent; -export function isToolCallEventType(toolName: "search", event: ToolCallEvent): event is SearchToolCallEvent; -export function isToolCallEventType(toolName: "find", event: ToolCallEvent): event is FindToolCallEvent; +export function isToolCallEventType(toolName: "grep", event: ToolCallEvent): event is GrepToolCallEvent; +export function isToolCallEventType(toolName: "glob", event: ToolCallEvent): event is GlobToolCallEvent; export function isToolCallEventType>( toolName: TName, event: ToolCallEvent, diff --git a/packages/coding-agent/src/extensibility/hooks/types.ts b/packages/coding-agent/src/extensibility/hooks/types.ts index 8233fe18f..e584a8d29 100644 --- a/packages/coding-agent/src/extensibility/hooks/types.ts +++ b/packages/coding-agent/src/extensibility/hooks/types.ts @@ -10,7 +10,7 @@ import type * as PiCodingAgent from "../../index"; import type { Theme } from "../../modes/theme/theme"; import type { HookMessage } from "../../session/messages"; import type { ReadonlySessionManager, SessionManager } from "../../session/session-manager"; -import type { BashToolDetails, FindToolDetails, ReadToolDetails, SearchToolDetails } from "../../tools"; +import type { BashToolDetails, GlobToolDetails, GrepToolDetails, ReadToolDetails } from "../../tools"; import type { AgentEndEvent, AgentStartEvent, @@ -352,16 +352,16 @@ export interface WriteToolResultEvent extends ToolResultEventBase { details: undefined; } -/** Tool result event for search tool */ -export interface SearchToolResultEvent extends ToolResultEventBase { - toolName: "search"; - details: SearchToolDetails | undefined; +/** Tool result event for grep tool */ +export interface GrepToolResultEvent extends ToolResultEventBase { + toolName: "grep"; + details: GrepToolDetails | undefined; } -/** Tool result event for find tool */ -export interface FindToolResultEvent extends ToolResultEventBase { - toolName: "find"; - details: FindToolDetails | undefined; +/** Tool result event for glob tool */ +export interface GlobToolResultEvent extends ToolResultEventBase { + toolName: "glob"; + details: GlobToolDetails | undefined; } /** Tool result event for custom/unknown tools */ @@ -380,8 +380,8 @@ export type ToolResultEvent = | ReadToolResultEvent | EditToolResultEvent | WriteToolResultEvent - | SearchToolResultEvent - | FindToolResultEvent + | GrepToolResultEvent + | GlobToolResultEvent | CustomToolResultEvent; /** diff --git a/packages/coding-agent/src/extensibility/plugins/legacy-pi-bundled-keys.ts b/packages/coding-agent/src/extensibility/plugins/legacy-pi-bundled-keys.ts index d565b986f..1e2db03d7 100644 --- a/packages/coding-agent/src/extensibility/plugins/legacy-pi-bundled-keys.ts +++ b/packages/coding-agent/src/extensibility/plugins/legacy-pi-bundled-keys.ts @@ -93,6 +93,7 @@ export const BUNDLED_PI_REGISTRY_KEYS: ReadonlySet = new Set([ "@oh-my-pi/pi-ai/usage/google-antigravity", "@oh-my-pi/pi-ai/usage/kimi", "@oh-my-pi/pi-ai/usage/minimax-code", + "@oh-my-pi/pi-ai/usage/ollama", "@oh-my-pi/pi-ai/usage/openai-codex-reset", "@oh-my-pi/pi-ai/usage/openai-codex", "@oh-my-pi/pi-ai/usage/opencode-go", @@ -352,6 +353,7 @@ export const BUNDLED_PI_REGISTRY_KEYS: ReadonlySet = new Set([ "@oh-my-pi/pi-coding-agent/config/append-only-context-mode", "@oh-my-pi/pi-coding-agent/config/config-file", "@oh-my-pi/pi-coding-agent/config/file-lock", + "@oh-my-pi/pi-coding-agent/config/inline-tool-descriptors-mode", "@oh-my-pi/pi-coding-agent/config/keybindings", "@oh-my-pi/pi-coding-agent/config/model-discovery", "@oh-my-pi/pi-coding-agent/config/model-registry", @@ -780,13 +782,14 @@ export const BUNDLED_PI_REGISTRY_KEYS: ReadonlySet = new Set([ "@oh-my-pi/pi-coding-agent/tools/eval", "@oh-my-pi/pi-coding-agent/tools/fetch", "@oh-my-pi/pi-coding-agent/tools/file-recorder", - "@oh-my-pi/pi-coding-agent/tools/find", "@oh-my-pi/pi-coding-agent/tools/fs-cache-invalidation", "@oh-my-pi/pi-coding-agent/tools/gh-cache-invalidation", "@oh-my-pi/pi-coding-agent/tools/gh-format", "@oh-my-pi/pi-coding-agent/tools/gh-renderer", "@oh-my-pi/pi-coding-agent/tools/gh", "@oh-my-pi/pi-coding-agent/tools/github-cache", + "@oh-my-pi/pi-coding-agent/tools/glob", + "@oh-my-pi/pi-coding-agent/tools/grep", "@oh-my-pi/pi-coding-agent/tools/grouped-file-output", "@oh-my-pi/pi-coding-agent/tools/image-gen", "@oh-my-pi/pi-coding-agent/tools/inspect-image-renderer", @@ -817,7 +820,6 @@ export const BUNDLED_PI_REGISTRY_KEYS: ReadonlySet = new Set([ "@oh-my-pi/pi-coding-agent/tools/resolve", "@oh-my-pi/pi-coding-agent/tools/review", "@oh-my-pi/pi-coding-agent/tools/search-tool-bm25", - "@oh-my-pi/pi-coding-agent/tools/search", "@oh-my-pi/pi-coding-agent/tools/sqlite-reader", "@oh-my-pi/pi-coding-agent/tools/ssh", "@oh-my-pi/pi-coding-agent/tools/todo", diff --git a/packages/coding-agent/src/extensibility/plugins/legacy-pi-bundled-registry.ts b/packages/coding-agent/src/extensibility/plugins/legacy-pi-bundled-registry.ts index bf71f60f2..53726bbf2 100644 --- a/packages/coding-agent/src/extensibility/plugins/legacy-pi-bundled-registry.ts +++ b/packages/coding-agent/src/extensibility/plugins/legacy-pi-bundled-registry.ts @@ -116,6 +116,7 @@ import * as bundledPiAiUsageGithubCopilot from "@oh-my-pi/pi-ai/usage/github-cop import * as bundledPiAiUsageGoogleAntigravity from "@oh-my-pi/pi-ai/usage/google-antigravity"; import * as bundledPiAiUsageKimi from "@oh-my-pi/pi-ai/usage/kimi"; import * as bundledPiAiUsageMinimaxCode from "@oh-my-pi/pi-ai/usage/minimax-code"; +import * as bundledPiAiUsageOllama from "@oh-my-pi/pi-ai/usage/ollama"; import * as bundledPiAiUsageOpenaiCodex from "@oh-my-pi/pi-ai/usage/openai-codex"; import * as bundledPiAiUsageOpenaiCodexReset from "@oh-my-pi/pi-ai/usage/openai-codex-reset"; import * as bundledPiAiUsageOpencodeGo from "@oh-my-pi/pi-ai/usage/opencode-go"; @@ -320,6 +321,7 @@ import * as bundledPiCodingAgentConfigApiKeyResolver from "@oh-my-pi/pi-coding-a import * as bundledPiCodingAgentConfigAppendOnlyContextMode from "@oh-my-pi/pi-coding-agent/config/append-only-context-mode"; import * as bundledPiCodingAgentConfigConfigFile from "@oh-my-pi/pi-coding-agent/config/config-file"; import * as bundledPiCodingAgentConfigFileLock from "@oh-my-pi/pi-coding-agent/config/file-lock"; +import * as bundledPiCodingAgentConfigInlineToolDescriptorsMode from "@oh-my-pi/pi-coding-agent/config/inline-tool-descriptors-mode"; import * as bundledPiCodingAgentConfigKeybindings from "@oh-my-pi/pi-coding-agent/config/keybindings"; import * as bundledPiCodingAgentConfigModelDiscovery from "@oh-my-pi/pi-coding-agent/config/model-discovery"; import * as bundledPiCodingAgentConfigModelRegistry from "@oh-my-pi/pi-coding-agent/config/model-registry"; @@ -782,13 +784,14 @@ import * as bundledPiCodingAgentToolsEvalBackends from "@oh-my-pi/pi-coding-agen import * as bundledPiCodingAgentToolsEvalRender from "@oh-my-pi/pi-coding-agent/tools/eval-render"; import * as bundledPiCodingAgentToolsFetch from "@oh-my-pi/pi-coding-agent/tools/fetch"; import * as bundledPiCodingAgentToolsFileRecorder from "@oh-my-pi/pi-coding-agent/tools/file-recorder"; -import * as bundledPiCodingAgentToolsFind from "@oh-my-pi/pi-coding-agent/tools/find"; import * as bundledPiCodingAgentToolsFsCacheInvalidation from "@oh-my-pi/pi-coding-agent/tools/fs-cache-invalidation"; import * as bundledPiCodingAgentToolsGh from "@oh-my-pi/pi-coding-agent/tools/gh"; import * as bundledPiCodingAgentToolsGhCacheInvalidation from "@oh-my-pi/pi-coding-agent/tools/gh-cache-invalidation"; import * as bundledPiCodingAgentToolsGhFormat from "@oh-my-pi/pi-coding-agent/tools/gh-format"; import * as bundledPiCodingAgentToolsGhRenderer from "@oh-my-pi/pi-coding-agent/tools/gh-renderer"; import * as bundledPiCodingAgentToolsGithubCache from "@oh-my-pi/pi-coding-agent/tools/github-cache"; +import * as bundledPiCodingAgentToolsGlob from "@oh-my-pi/pi-coding-agent/tools/glob"; +import * as bundledPiCodingAgentToolsGrep from "@oh-my-pi/pi-coding-agent/tools/grep"; import * as bundledPiCodingAgentToolsGroupedFileOutput from "@oh-my-pi/pi-coding-agent/tools/grouped-file-output"; import * as bundledPiCodingAgentToolsImageGen from "@oh-my-pi/pi-coding-agent/tools/image-gen"; import * as bundledPiCodingAgentToolsInspectImage from "@oh-my-pi/pi-coding-agent/tools/inspect-image"; @@ -818,7 +821,6 @@ import * as bundledPiCodingAgentToolsRenderers from "@oh-my-pi/pi-coding-agent/t import * as bundledPiCodingAgentToolsReportToolIssue from "@oh-my-pi/pi-coding-agent/tools/report-tool-issue"; import * as bundledPiCodingAgentToolsResolve from "@oh-my-pi/pi-coding-agent/tools/resolve"; import * as bundledPiCodingAgentToolsReview from "@oh-my-pi/pi-coding-agent/tools/review"; -import * as bundledPiCodingAgentToolsSearch from "@oh-my-pi/pi-coding-agent/tools/search"; import * as bundledPiCodingAgentToolsSearchToolBm25 from "@oh-my-pi/pi-coding-agent/tools/search-tool-bm25"; import * as bundledPiCodingAgentToolsSqliteReader from "@oh-my-pi/pi-coding-agent/tools/sqlite-reader"; import * as bundledPiCodingAgentToolsSsh from "@oh-my-pi/pi-coding-agent/tools/ssh"; @@ -1180,6 +1182,7 @@ export const BUNDLED_PI_REGISTRY: Readonly, "@oh-my-pi/pi-ai/usage/kimi": bundledPiAiUsageKimi as unknown as Readonly>, "@oh-my-pi/pi-ai/usage/minimax-code": bundledPiAiUsageMinimaxCode as unknown as Readonly>, + "@oh-my-pi/pi-ai/usage/ollama": bundledPiAiUsageOllama as unknown as Readonly>, "@oh-my-pi/pi-ai/usage/openai-codex-reset": bundledPiAiUsageOpenaiCodexReset as unknown as Readonly< Record >, @@ -1782,6 +1785,8 @@ export const BUNDLED_PI_REGISTRY: Readonly >, + "@oh-my-pi/pi-coding-agent/config/inline-tool-descriptors-mode": + bundledPiCodingAgentConfigInlineToolDescriptorsMode as unknown as Readonly>, "@oh-my-pi/pi-coding-agent/config/keybindings": bundledPiCodingAgentConfigKeybindings as unknown as Readonly< Record >, @@ -2828,9 +2833,6 @@ export const BUNDLED_PI_REGISTRY: Readonly >, - "@oh-my-pi/pi-coding-agent/tools/find": bundledPiCodingAgentToolsFind as unknown as Readonly< - Record - >, "@oh-my-pi/pi-coding-agent/tools/fs-cache-invalidation": bundledPiCodingAgentToolsFsCacheInvalidation as unknown as Readonly>, "@oh-my-pi/pi-coding-agent/tools/gh-cache-invalidation": @@ -2845,6 +2847,12 @@ export const BUNDLED_PI_REGISTRY: Readonly >, + "@oh-my-pi/pi-coding-agent/tools/glob": bundledPiCodingAgentToolsGlob as unknown as Readonly< + Record + >, + "@oh-my-pi/pi-coding-agent/tools/grep": bundledPiCodingAgentToolsGrep as unknown as Readonly< + Record + >, "@oh-my-pi/pi-coding-agent/tools/grouped-file-output": bundledPiCodingAgentToolsGroupedFileOutput as unknown as Readonly>, "@oh-my-pi/pi-coding-agent/tools/image-gen": bundledPiCodingAgentToolsImageGen as unknown as Readonly< @@ -2927,9 +2935,6 @@ export const BUNDLED_PI_REGISTRY: Readonly >, - "@oh-my-pi/pi-coding-agent/tools/search": bundledPiCodingAgentToolsSearch as unknown as Readonly< - Record - >, "@oh-my-pi/pi-coding-agent/tools/sqlite-reader": bundledPiCodingAgentToolsSqliteReader as unknown as Readonly< Record >, diff --git a/packages/coding-agent/src/memories/index.ts b/packages/coding-agent/src/memories/index.ts index 9532b5ff7..8d05b5a7c 100644 --- a/packages/coding-agent/src/memories/index.ts +++ b/packages/coding-agent/src/memories/index.ts @@ -559,7 +559,7 @@ function shouldPersistResponseItemForMemories(message: AgentMessage): boolean { } if (role !== "toolResult") return false; const toolName = (message as { toolName?: string }).toolName; - if (toolName === "bash" || toolName === "eval" || toolName === "read" || toolName === "search") { + if (toolName === "bash" || toolName === "eval" || toolName === "read" || toolName === "grep") { const text = extractMessageText(message); return text.length > 0 && text.length <= 32_000; } diff --git a/packages/coding-agent/src/modes/acp/acp-event-mapper.ts b/packages/coding-agent/src/modes/acp/acp-event-mapper.ts index c87f18abf..41d52fec5 100644 --- a/packages/coding-agent/src/modes/acp/acp-event-mapper.ts +++ b/packages/coding-agent/src/modes/acp/acp-event-mapper.ts @@ -143,8 +143,8 @@ export function mapToolKind(toolName: string): ToolKind { case "exec": case "eval": return "execute"; - case "search": - case "find": + case "grep": + case "glob": case "ast_grep": return "search"; case "web_search": diff --git a/packages/coding-agent/src/modes/components/tree-selector.ts b/packages/coding-agent/src/modes/components/tree-selector.ts index 2fd48fb21..2d0dffe60 100644 --- a/packages/coding-agent/src/modes/components/tree-selector.ts +++ b/packages/coding-agent/src/modes/components/tree-selector.ts @@ -15,8 +15,8 @@ import type { TreeFilterMode } from "../../config/settings-schema"; import { theme } from "../../modes/theme/theme"; import { matchesAppInterrupt, matchesSelectDown, matchesSelectUp } from "../../modes/utils/keybinding-matchers"; import type { SessionTreeNode } from "../../session/session-entries"; +import { toPathList } from "../../tools/grep"; import { shortenPath } from "../../tools/render-utils"; -import { toPathList } from "../../tools/search"; import { canonicalizeMessage } from "../../utils/thinking-display"; import { DynamicBorder } from "./dynamic-border"; import { centeredWindow, contentRowWidth, renderScrollableList } from "./selector-helpers"; @@ -740,7 +740,7 @@ class TreeList implements Component { .slice(0, 50); return `[bash: ${cmd}${rawCmd.length > 50 ? "..." : ""}]`; } - case "search": { + case "grep": { const pattern = String(args.pattern || ""); const searchPathsInput = typeof args.paths === "string" || Array.isArray(args.paths) @@ -750,11 +750,11 @@ class TreeList implements Component { : undefined; const paths = toPathList(searchPathsInput); const scope = paths.length > 0 ? paths.join(", ") : "."; - return `[search: /${pattern}/ in ${shortenPath(scope)}]`; + return `[grep: /${pattern}/ in ${shortenPath(scope)}]`; } - case "find": { + case "glob": { const paths = Array.isArray(args.paths) ? args.paths.join(", ") : String(args.pattern || "."); - return `[find: ${shortenPath(paths)}]`; + return `[glob: ${shortenPath(paths)}]`; } case "ls": { const path = shortenPath(String(args.path || ".")); diff --git a/packages/coding-agent/src/prompts/advisor/system.md b/packages/coding-agent/src/prompts/advisor/system.md index b79915bb5..e7e3c4c38 100644 --- a/packages/coding-agent/src/prompts/advisor/system.md +++ b/packages/coding-agent/src/prompts/advisor/system.md @@ -11,7 +11,7 @@ Your job is to offer that view before they sink work into the wrong direction. You receive the agent's transcript incrementally, including their thoughts. -You have read-only access through `read`, `search`, `find` to verify your suspicions. +You have read-only access through `read`, `grep`, `glob` to verify your suspicions. Keep exploration lean: - 2–3 tool calls per advise. - Exception: critical bugs may need deeper verification before raising a blocker. @@ -44,7 +44,7 @@ Cite only transcript evidence or tool output you personally inspected. Arguments absent from the rendered transcript are UNKNOWN: - NEVER assert concrete values, array indexes, serialization shapes, or caller mistakes for hidden arguments. - Hidden/omitted arguments + failure? Say what is observable; suggest inspecting the missing field. -- Example: if `search` times out and transcript only shows `pattern`, NEVER claim `paths[0]`, array flattening, or malformed `paths`. +- Example: if `grep` times out and transcript only shows `pattern`, NEVER claim `paths[0]`, array flattening, or malformed `paths`. Cite the exact instruction or risk. diff --git a/packages/coding-agent/src/prompts/agents/designer.md b/packages/coding-agent/src/prompts/agents/designer.md index 1e6dd0f90..72091b563 100644 --- a/packages/coding-agent/src/prompts/agents/designer.md +++ b/packages/coding-agent/src/prompts/agents/designer.md @@ -16,7 +16,7 @@ Implement and review UI designs. Edit files, create components, run commands whe Treat the design system as the foundation — UI built without one collapses into inconsistency. Work four phases in order: -1. **Token-first analysis (before any CSS/JSX/Svelte).** `search`/`read` for the design tokens (colors, spacing, typography, shadows, radii), theme files (CSS variables, Tailwind config, `theme.ts`), and shared primitives (Button, Card, Input, Layout). Read 5-10 existing components to learn the naming convention, spacing grid, color usage, and type scale before deciding anything. +1. **Token-first analysis (before any CSS/JSX/Svelte).** `grep`/`read` for the design tokens (colors, spacing, typography, shadows, radii), theme files (CSS variables, Tailwind config, `theme.ts`), and shared primitives (Button, Card, Input, Layout). Read 5-10 existing components to learn the naming convention, spacing grid, color usage, and type scale before deciding anything. 2. **No coherent system? Build the minimal one first.** Extract what exists, then define a palette, type scale, spacing scale (4px/8px base), radii/shadows/transitions, and primitive components — THEN implement the request against it. 3. **Compose with the system, never around it.** Colors → tokens/CSS variables, never hardcoded hex; spacing → scale values, never arbitrary px; type → scale steps; components → extend/compose existing primitives, not one-off div soup. Need something outside the system? Add the new token to the system first, then use it — never a one-off override. 4. **Verify before done.** Every color a token, every spacing on the scale, every component on the existing composition pattern, zero magic numbers — a designer would see consistency across old and new. Any "no" → not done. diff --git a/packages/coding-agent/src/prompts/agents/explore.md b/packages/coding-agent/src/prompts/agents/explore.md index f9a85c601..5ef518c67 100644 --- a/packages/coding-agent/src/prompts/agents/explore.md +++ b/packages/coding-agent/src/prompts/agents/explore.md @@ -1,7 +1,7 @@ --- name: explore description: Fast read-only codebase scout returning compressed context for handoff -tools: read, search, find, web_search +tools: read, grep, glob, web_search model: pi/smol thinking-level: medium read-summarize: false diff --git a/packages/coding-agent/src/prompts/agents/librarian.md b/packages/coding-agent/src/prompts/agents/librarian.md index a5aab26fd..d3bb1c40a 100644 --- a/packages/coding-agent/src/prompts/agents/librarian.md +++ b/packages/coding-agent/src/prompts/agents/librarian.md @@ -1,7 +1,7 @@ --- name: librarian description: Researches external libraries and APIs by reading source code. Returns definitive, source-verified answers. -tools: read, search, find, bash, lsp, web_search, ast_grep +tools: read, grep, glob, bash, lsp, web_search, ast_grep model: pi/smol thinking-level: minimal read-summarize: false @@ -86,7 +86,7 @@ You MUST operate as read-only on the user's project. You NEVER modify any projec ## 3. Investigate - Read `package.json`, `Cargo.toml`, or equivalent for version info and entry points. -- Use `search`, `find`, and `ast_grep` to locate relevant source, type definitions, and docs. Parallelize searches. +- Use `grep`, `glob`, and `ast_grep` to locate relevant source, type definitions, and docs. Parallelize searches. - Read the actual implementation — not just README examples. READMEs are aspirational; source code is truth. - For behavior questions: trace through the implementation. Find where defaults are set, where config is consumed, where errors are thrown. - Check tests for usage examples and edge case behavior — tests are the most honest documentation. diff --git a/packages/coding-agent/src/prompts/agents/plan.md b/packages/coding-agent/src/prompts/agents/plan.md index eb7dff98f..09c9fbb4e 100644 --- a/packages/coding-agent/src/prompts/agents/plan.md +++ b/packages/coding-agent/src/prompts/agents/plan.md @@ -1,7 +1,7 @@ --- name: plan description: Software architect for complex multi-file architectural decisions. NOT for simple tasks, single-file changes, or tasks completable in <5 tool calls. -tools: read, search, find, bash, lsp, web_search, ast_grep +tools: read, grep, glob, bash, lsp, web_search, ast_grep spawns: explore model: pi/plan, pi/slow thinking-level: high @@ -14,7 +14,7 @@ Analyze the codebase and the user's request. Produce a detailed implementation p 2. Identify ambiguities; list assumptions ## Phase 2: Explore -1. Find existing patterns via `search`/`find` +1. Find existing patterns via `grep`/`glob` 2. Read key files; understand architecture 3. Trace data flow through relevant paths 4. Identify types, interfaces, contracts diff --git a/packages/coding-agent/src/prompts/agents/reviewer.md b/packages/coding-agent/src/prompts/agents/reviewer.md index 826d31b7a..934dccd6a 100644 --- a/packages/coding-agent/src/prompts/agents/reviewer.md +++ b/packages/coding-agent/src/prompts/agents/reviewer.md @@ -1,7 +1,7 @@ --- name: reviewer description: "Code review specialist for quality/security analysis" -tools: read, search, find, bash, lsp, web_search, ast_grep, report_finding +tools: read, grep, glob, bash, lsp, web_search, ast_grep, report_finding spawns: explore model: pi/slow thinking-level: high diff --git a/packages/coding-agent/src/prompts/agents/task.md b/packages/coding-agent/src/prompts/agents/task.md index 4be5a9203..8f7e5e4ed 100644 --- a/packages/coding-agent/src/prompts/agents/task.md +++ b/packages/coding-agent/src/prompts/agents/task.md @@ -1,6 +1,6 @@ You are a worker agent for delegated tasks. -You have FULL access to all tools (edit, write, bash, search, read, etc.) and you MUST use them as needed to complete your task. +You have FULL access to all tools (edit, write, bash, grep, read, etc.) and you MUST use them as needed to complete your task. You MUST maintain hyperfocus on the assigned task. NEVER deviate from it. @@ -8,7 +8,7 @@ You MUST maintain hyperfocus on the assigned task. NEVER deviate from it. - You MUST finish only the assigned work and return the minimum useful result. Do not repeat what you have written to the filesystem. - You SHOULD make file edits, run commands, and create files when your task requires it. - You MUST be concise. You NEVER include filler, repetition, or tool transcripts. The user cannot see you. Your result is just the notes you are leaving for yourself. -- You SHOULD prefer narrow lookups (`search`/`find`), then read only the needed ranges. Ignore anything beyond your current scope. +- You SHOULD prefer narrow lookups (`grep`/`glob`), then read only the needed ranges. Ignore anything beyond your current scope. - AVOID full-file reads unless necessary. - You SHOULD prefer edits to existing files over creating new ones. - You NEVER create documentation files (*.md) unless explicitly requested. diff --git a/packages/coding-agent/src/prompts/system/plan-mode-active.md b/packages/coding-agent/src/prompts/system/plan-mode-active.md index c4e1481ef..dcb9bce64 100644 --- a/packages/coding-agent/src/prompts/system/plan-mode-active.md +++ b/packages/coding-agent/src/prompts/system/plan-mode-active.md @@ -37,7 +37,7 @@ Write each section together with its body — block ops need a multi-line sectio You eliminate unknowns by discovering facts, not by asking. -- **Discoverable facts** (file locations, current behavior, signatures, configs): you MUST find them yourself with `find`, `search`, `read`, or parallel `explore` subagents. Every path, symbol, signature, and behavior the plan states as fact MUST come from something you actually read this session. Anything you could not confirm you mark inline (`unverified — confirm first`); you NEVER present a guess as settled. Ask only when several real candidates survive exploration — then present them with a recommendation. +- **Discoverable facts** (file locations, current behavior, signatures, configs): you MUST find them yourself with `glob`, `grep`, `read`, or parallel `explore` subagents. Every path, symbol, signature, and behavior the plan states as fact MUST come from something you actually read this session. Anything you could not confirm you mark inline (`unverified — confirm first`); you NEVER present a guess as settled. Ask only when several real candidates survive exploration — then present them with a recommendation. - **Preferences and tradeoffs** (intent, UX, scope edges, performance-vs-simplicity): not derivable from code. Surface these early via `{{askToolName}}` with 2–4 mutually exclusive options and a recommended default. Left unanswered → proceed with the default and record it under Assumptions. Every question MUST change the plan or settle a load-bearing choice. Batch them. You NEVER ask what exploration answers, and you NEVER ask filler. @@ -57,7 +57,7 @@ Every question MUST change the plan or settle a load-bearing choice. Batch them. ## Workflow — iterative -1. **Explore** — use `find`/`search`/`read` to ground in the real code; hunt for existing functions, utilities, and conventions to reuse before proposing anything new. +1. **Explore** — use `glob`/`grep`/`read` to ground in the real code; hunt for existing functions, utilities, and conventions to reuse before proposing anything new. 2. **Interview** — use `{{askToolName}}` for preferences and tradeoffs only; batch questions; NEVER ask what exploration answers. 3. **Update** — revise the plan with `{{editToolName}}` as you learn. 4. **Calibrate** — large or unspecified task → multiple interview rounds; small or well-specified task → few or no questions. @@ -82,7 +82,7 @@ Write scannable markdown using these sections. Let depth track the change, not a - State the concrete edit — verb + exact target + the new behavior — NEVER just an area to "update" or "handle". - Name existing functions/utilities to reuse, with paths; introduce new code only with a one-line note that no existing equivalent was found. - For a new or changed symbol whose callers must fit it, or whose value is load-bearing (enum member, error/log string, config key, wire/JSON field), give the exact signature or literal. - - For a rename, signature change, or removal, list every callsite to update (or the exact `search` that returns exactly them) and what to delete — default to a clean cutover with no dead code or compatibility aliases. + - For a rename, signature change, or removal, list every callsite to update (or the exact `grep` that returns exactly them) and what to delete — default to a clean cutover with no dead code or compatibility aliases. - When rival patterns exist, name the one to copy and the one to avoid. - Specify the edge and failure handling for each new path (empty, missing, conflict, error), or state that none is needed and why. - **Critical files & anchors** — the ≤5 files that disambiguate non-obvious work, each as path + the symbol or region + a one-line reason. Line numbers are hints; the implementer re-reads before editing. Skip files already obvious from the Approach. diff --git a/packages/coding-agent/src/prompts/system/project-prompt.md b/packages/coding-agent/src/prompts/system/project-prompt.md index bd6bcffea..a08d45783 100644 --- a/packages/coding-agent/src/prompts/system/project-prompt.md +++ b/packages/coding-agent/src/prompts/system/project-prompt.md @@ -26,7 +26,7 @@ Before making changes within these directories, you MUST read: {{/if}} {{#ifAny contextFiles.length agentsMdSearch.files.length}} -The context files above are loaded automatically. You NEVER `search`/`find` for `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, or similar agent/context files — the relevant ones are already in your context; any others are noise. +The context files above are loaded automatically. You NEVER `grep`/`glob` for `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, or similar agent/context files — the relevant ones are already in your context; any others are noise. {{/ifAny}} {{#if includeWorkspaceTree}} @@ -35,7 +35,7 @@ The context files above are loaded automatically. You NEVER `search`/`find` for Working directory layout (sorted by mtime, recent first; depth ≤ 3): {{workspaceTree.rendered}} {{#if workspaceTree.truncated}} -(some entries elided to keep the tree short — use `find`/`read` to drill in) +(some entries elided to keep the tree short — use `glob`/`read` to drill in) {{/if}} {{/if}} diff --git a/packages/coding-agent/src/prompts/system/system-prompt.md b/packages/coding-agent/src/prompts/system/system-prompt.md index 4d8eb9c08..eb7447004 100644 --- a/packages/coding-agent/src/prompts/system/system-prompt.md +++ b/packages/coding-agent/src/prompts/system/system-prompt.md @@ -109,8 +109,8 @@ You MUST use the specialized tool over its shell equivalent: {{#has tools "edit"}}- Surgical edits → `{{toolRefs.edit}}`.{{/has}} {{#has tools "write"}}- Create or overwrite → `{{toolRefs.write}}`.{{/has}} {{#has tools "lsp"}}- Code intelligence → `{{toolRefs.lsp}}`.{{/has}} -{{#has tools "search"}}- Regex search → `{{toolRefs.search}}`, not `grep`, `rg`, or `awk`.{{/has}} -{{#has tools "find"}}- Globbing → `{{toolRefs.find}}`, not `ls **/*.ext` or `fd`.{{/has}} +{{#has tools "grep"}}- Regex search → `{{toolRefs.grep}}`, not `grep`, `rg`, or `awk`.{{/has}} +{{#has tools "glob"}}- Globbing → `{{toolRefs.glob}}`, not `ls **/*.ext` or `fd`.{{/has}} {{#has tools "eval"}}- Default for any compute: `{{toolRefs.eval}}` cells. Bash is the EXCEPTION — only single binary calls or short fact-computing pipelines (`wc -l`, `sort | uniq -c`, `diff`, checksums). The moment a command grows a loop, conditional, heredoc, `-e`/`-c` script, `$(…)` nesting, or >2 pipe stages, it's a program → `{{toolRefs.eval}}`. NEVER write multiline or inline-script bash.{{/has}} {{#has tools "bash"}}- `{{toolRefs.bash}}`: real binaries and short fact pipelines only. Commands shadowing the specialized tools above are blocked.{{/has}} {{#has tools "bash"}}- Litmus: one external-CLI call or short pipeline returning a count, frequency, set difference, or checksum → bash.{{#has tools "eval"}} Needs control flow, state, or fights shell quoting → `{{toolRefs.eval}}`.{{/has}} Merely moves, pages, or trims bytes a tool can fetch → use the tool.{{/has}} @@ -124,8 +124,8 @@ You MUST use the specialized tool over its shell equivalent: # Exploration You NEVER open a file hoping. Hope is not a strategy. - You MUST load only what's necessary; AVOID reading files or sections you don't need. -{{#has tools "search"}}- Use `{{toolRefs.search}}` to locate targets.{{/has}} -{{#has tools "find"}}- Use `{{toolRefs.find}}` to map structure.{{/has}} +{{#has tools "grep"}}- Use `{{toolRefs.grep}}` to locate targets.{{/has}} +{{#has tools "glob"}}- Use `{{toolRefs.glob}}` to map structure.{{/has}} {{#has tools "read"}}- Use `{{toolRefs.read}}` with offset/limit instead of whole-file reads.{{/has}} {{#has tools "task"}}- Use `{{toolRefs.task}}` to map unknown code instead of reading file after file yourself.{{/has}} @@ -141,7 +141,7 @@ You NEVER use search or manual edits for code intelligence when a language serve You SHOULD use syntax-aware tools before text hacks: {{#has tools "ast_grep"}}- `{{toolRefs.ast_grep}}` for structural discovery.{{/has}} {{#has tools "ast_edit"}}- `{{toolRefs.ast_edit}}` for codemods.{{/has}} -- Use `search` only for plain-text lookup when structure is irrelevant. +- Use `grep` only for plain-text lookup when structure is irrelevant. {{/ifAny}} # Delegation @@ -180,7 +180,7 @@ EXECUTION WORKFLOW - Fix problems at the source. Remove obsolete code—no leftover comments, aliases, or re-exports. - Prefer updating existing files over creating new ones. - Review changes from the user's perspective. -{{#has tools "search"}}- Search instead of guessing.{{/has}} +{{#has tools "grep"}}- Grep instead of guessing.{{/has}} {{#has tools "ask"}}- Ask before destructive commands or deleting code you didn't write.{{else}}- Don't run destructive git commands or delete code you didn't write.{{/has}} # 5. Verify diff --git a/packages/coding-agent/src/prompts/tools/bash.md b/packages/coding-agent/src/prompts/tools/bash.md index 3ca00cfd1..6bcef912a 100644 --- a/packages/coding-agent/src/prompts/tools/bash.md +++ b/packages/coding-agent/src/prompts/tools/bash.md @@ -29,8 +29,8 @@ Anything below → `eval` cell, not bash: - Bash invokes real binaries with simple args; it is NOT a scripting surface. Loops, conditionals, heredocs, inline interpreter scripts (`-e`/`-c`/`--eval`) when an eval runtime exists, several piped stages, or quote/JSON escaping mean you're writing a program → use `eval` cells: restartable, stateful, and free of shell-quoting traps. -- NEVER shell out to search content or files: `grep/rg` → `search`. -- NEVER use `ls` or `find` to list or locate files — `ls` → `read` (a directory path lists entries), `find` → the `find` tool (globbing). This is non-negotiable, even for a single quick listing. +- NEVER shell out to search content or files: `grep/rg` → `grep`. +- NEVER use `ls` or `find` to list or locate files — `ls` → `read` (a directory path lists entries), `find` → the `glob` tool (globbing). This is non-negotiable, even for a single quick listing. - Avoid head/tail/redirections: stderr already merged; long output auto-truncated, FULL capture kept at `artifact://`. diff --git a/packages/coding-agent/src/prompts/tools/checkpoint.md b/packages/coding-agent/src/prompts/tools/checkpoint.md index af04888c7..8eca2f59b 100644 --- a/packages/coding-agent/src/prompts/tools/checkpoint.md +++ b/packages/coding-agent/src/prompts/tools/checkpoint.md @@ -1,6 +1,6 @@ Creates a context checkpoint before exploratory work so you can later rewind and keep only a concise report. -Use this when you need to investigate with many intermediate tool calls (read/search/find/lsp/etc.) and want to minimize context cost afterward. +Use this when you need to investigate with many intermediate tool calls (read/grep/glob/lsp/etc.) and want to minimize context cost afterward. Rules: - You MUST call `rewind` before yielding after starting a checkpoint. diff --git a/packages/coding-agent/src/prompts/tools/find.md b/packages/coding-agent/src/prompts/tools/glob.md similarity index 90% rename from packages/coding-agent/src/prompts/tools/find.md rename to packages/coding-agent/src/prompts/tools/glob.md index 07d0c8b76..d9bb0a06e 100644 --- a/packages/coding-agent/src/prompts/tools/find.md +++ b/packages/coding-agent/src/prompts/tools/glob.md @@ -1,4 +1,4 @@ -Finds files and directories via fast pattern matching, any codebase size. +Globs files and directories via fast pattern matching, any codebase size. - `paths`: array of globs, files, or directories. diff --git a/packages/coding-agent/src/prompts/tools/search.md b/packages/coding-agent/src/prompts/tools/grep.md similarity index 69% rename from packages/coding-agent/src/prompts/tools/search.md rename to packages/coding-agent/src/prompts/tools/grep.md index 097d1a3fd..7f49be8a8 100644 --- a/packages/coding-agent/src/prompts/tools/search.md +++ b/packages/coding-agent/src/prompts/tools/grep.md @@ -1,4 +1,4 @@ -Searches files using regex. +Greps files using regex. - Rust regex (RE2-style) — no lookaround/backreferences; use line anchors or post-filters instead of (?!…)/(? -- MUST use built-in `search` for any content search. NEVER shell out to `grep`, `rg`, `ripgrep`, `ag`, `ack`, `git grep`, `awk`, `sed`-for-search, or any CLI search via Bash — not even for one match or a quick check. -- Open-ended search needing multiple rounds? MUST use the Task tool with the explore subagent, NOT chained `search` calls. +- MUST use built-in `grep` for any content search. NEVER shell out to `grep`, `rg`, `ripgrep`, `ag`, `ack`, `git grep`, `awk`, `sed`-for-search, or any CLI search via Bash — not even for one match or a quick check. +- Open-ended search needing multiple rounds? MUST use the Task tool with the explore subagent, NOT chained `grep` calls. diff --git a/packages/coding-agent/src/sdk.ts b/packages/coding-agent/src/sdk.ts index a3ab90953..41b402587 100644 --- a/packages/coding-agent/src/sdk.ts +++ b/packages/coding-agent/src/sdk.ts @@ -160,8 +160,9 @@ import { discoverStartupLspServers, EditTool, EvalTool, - FindTool, filterInitialToolsForDiscoveryAll, + GlobTool, + GrepTool, getSearchTools, HIDDEN_TOOLS, isImageProviderPreference, @@ -172,7 +173,6 @@ import { ReadTool, ResolveTool, renderSearchToolBm25Description, - SearchTool, SearchToolBm25Tool, setExcludedSearchProviders, setPreferredImageProvider, @@ -183,6 +183,7 @@ import { WriteTool, warmupLspServers, } from "./tools"; +import { normalizeToolName, normalizeToolNames } from "./tools/builtin-names"; import { ToolContextStore } from "./tools/context"; import { getImageGenTools } from "./tools/image-gen"; import { wrapToolWithMetaNotice } from "./tools/output-meta"; @@ -608,12 +609,12 @@ export { createTools, EditTool, EvalTool, - FindTool, + GlobTool, + GrepTool, HIDDEN_TOOLS, loadSshTool, ReadTool, ResolveTool, - SearchTool, type ToolSession, WebSearchTool, WriteTool, @@ -1491,9 +1492,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} hasUI: options.hasUI ?? false, enableLsp, get hasEditTool() { - const requestedToolNames = options.toolNames - ? [...new Set(options.toolNames.map(name => name.toLowerCase()))] - : undefined; + const requestedToolNames = options.toolNames ? normalizeToolNames(options.toolNames) : undefined; return !requestedToolNames || requestedToolNames.includes("edit"); }, skipPythonPreflight: options.skipPythonPreflight, @@ -2284,9 +2283,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} }; const toolNamesFromRegistry = Array.from(toolRegistry.keys()); - const explicitlyRequestedToolNames = options.toolNames - ? [...new Set(options.toolNames.map(name => name.toLowerCase()))] - : undefined; + const explicitlyRequestedToolNames = options.toolNames ? normalizeToolNames(options.toolNames) : undefined; // When `requireYieldTool` is set, the subagent's prompts and idle-reminders demand a // `yield` call to terminate. The tool registry already includes `yield` (see // `createTools`), but an explicit `toolNames` list would otherwise drop it from the @@ -2336,13 +2333,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} discoveryDefaultServers, ) : []; + const normalizeRenamedBuiltinToolName = normalizeToolName; let initialSelectedMCPToolNames: string[] = []; let defaultSelectedMCPToolNames: string[] = []; let initialToolNames = [...initialRequestedActiveToolNames]; if (mcpDiscoveryEnabled) { - const restoredSelectedMCPToolNames = existingSession.selectedMCPToolNames.filter(name => - toolRegistry.has(name), - ); + const restoredSelectedMCPToolNames = existingSession.selectedMCPToolNames + .map(normalizeRenamedBuiltinToolName) + .filter(name => toolRegistry.has(name)); defaultSelectedMCPToolNames = [ ...new Set([...discoveryDefaultServerToolNames, ...explicitlyRequestedMCPToolNames]), ]; @@ -2391,10 +2389,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} initialToolNames = filterInitialToolsForDiscoveryAll(initialToolNames, { loadModeOf: name => toolRegistry.get(name)?.loadMode, essentialNames: new Set(computeEssentialBuiltinNames(settings)), - explicitlyRequested: new Set(options.toolNames?.map(name => name.toLowerCase()) ?? []), + explicitlyRequested: new Set(options.toolNames ? normalizeToolNames(options.toolNames) : []), // Back-compat: persisted activations live under selectedMCPToolNames today (built-in // activation persistence is a follow-up). MCP names won't collide with built-in names. - restored: new Set(existingSession.selectedMCPToolNames), + restored: new Set(existingSession.selectedMCPToolNames.map(normalizeRenamedBuiltinToolName)), forceActive, }); } @@ -2626,7 +2624,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} } // Hard-isolated read-only toolset for the advisor (built unconditionally so - // it can be toggled at runtime). Fresh ReadTool/SearchTool/FindTool bound to a + // it can be toggled at runtime). Fresh ReadTool/GrepTool/GlobTool bound to a // DISTINCT ToolSession so the advisor's investigative reads never touch the // primary's snapshot, seen-lines, conflict, or summary caches (all keyed on // session identity). `cwd` stays dynamic; edit/yield capabilities are off. diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index cd0a03cab..2dca08968 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -271,6 +271,7 @@ import { selectDiscoverableToolNamesByServer, } from "../tool-discovery/tool-index"; import { assertEditableFile } from "../tools/auto-generated-guard"; +import { normalizeToolNames } from "../tools/builtin-names"; import type { CheckpointState } from "../tools/checkpoint"; import { outputMeta, wrapToolWithMetaNotice } from "../tools/output-meta"; import { normalizeLocalScheme, resolveToCwd } from "../tools/path-utils"; @@ -5163,7 +5164,7 @@ export class AgentSession { toolNames: string[], options?: { persistMCPSelection?: boolean; previousSelectedMCPToolNames?: string[] }, ): Promise { - toolNames = [...new Set(toolNames.map(name => name.toLowerCase()))]; + toolNames = normalizeToolNames(toolNames); const previousSelectedMCPToolNames = options?.previousSelectedMCPToolNames ?? this.getSelectedMCPToolNames(); const tools: AgentTool[] = []; const validToolNames: string[] = []; diff --git a/packages/coding-agent/src/session/session-history-format.ts b/packages/coding-agent/src/session/session-history-format.ts index b089037c9..20ab143b3 100644 --- a/packages/coding-agent/src/session/session-history-format.ts +++ b/packages/coding-agent/src/session/session-history-format.ts @@ -105,14 +105,14 @@ function primaryArg(name: string, args: Record | undefined): st if (note) return oneLine(note); if (severity) return oneLine(severity); } - if (name === "search") { + if (name === "grep") { const pattern = primaryArgValue(args.pattern); const paths = primaryArgValue(args.paths); if (pattern && paths) return oneLine(`${pattern} @ ${paths}`); if (pattern) return oneLine(pattern); if (paths) return oneLine(paths); } - if (name === "find") { + if (name === "glob") { const paths = primaryArgValue(args.paths); if (paths) return oneLine(paths); } diff --git a/packages/coding-agent/src/task/index.ts b/packages/coding-agent/src/task/index.ts index faef9d45e..0b235391d 100644 --- a/packages/coding-agent/src/task/index.ts +++ b/packages/coding-agent/src/task/index.ts @@ -136,8 +136,8 @@ export { // Fail-safe: any unknown tool makes the agent not read-only. export const READ_ONLY_TOOL_NAMES: ReadonlySet = new Set([ "read", - "search", - "find", + "grep", + "glob", "web_search", "ast_grep", "yield", @@ -1076,7 +1076,7 @@ export class TaskTool implements AgentTool = new Map([ + ["search", "grep"], + ["find", "glob"], +]); + +/** Return the canonical tool name for current and legacy built-in tool IDs. */ +export function normalizeToolName(name: string): string { + const normalized = name.toLowerCase(); + return LEGACY_BUILTIN_TOOL_NAME_ALIASES.get(normalized) ?? normalized; +} + +/** Normalize and deduplicate tool names while preserving first-seen order. */ +export function normalizeToolNames(names: Iterable): string[] { + const out: string[] = []; + const seen = new Set(); + for (const name of names) { + const normalized = normalizeToolName(name); + if (seen.has(normalized)) continue; + seen.add(normalized); + out.push(normalized); + } + return out; +} diff --git a/packages/coding-agent/src/tools/find.ts b/packages/coding-agent/src/tools/glob.ts similarity index 92% rename from packages/coding-agent/src/tools/find.ts rename to packages/coding-agent/src/tools/glob.ts index 70ded32ab..c28cea22a 100644 --- a/packages/coding-agent/src/tools/find.ts +++ b/packages/coding-agent/src/tools/glob.ts @@ -10,7 +10,7 @@ import { type } from "arktype"; import type { RenderResultOptions } from "../extensibility/custom-tools/types"; import { InternalUrlRouter } from "../internal-urls"; import type { Theme } from "../modes/theme/theme"; -import findDescription from "../prompts/tools/find.md" with { type: "text" }; +import globDescription from "../prompts/tools/glob.md" with { type: "text" }; import { type TruncationResult, truncateHead } from "../session/streaming-output"; import { Ellipsis, fileHyperlink, renderFileList, renderStatusLine, renderTreeList, truncateToWidth } from "../tui"; import type { ToolSession } from "."; @@ -47,13 +47,13 @@ const findSchema = type({ "limit?": type("number").describe("max results"), }); -export type FindToolInput = typeof findSchema.infer; +export type GlobToolInput = typeof findSchema.infer; const DEFAULT_LIMIT = 200; const MAX_LIMIT = 200; const DEFAULT_GLOB_TIMEOUT_MS = 5000; -export interface FindToolDetails { +export interface GlobToolDetails { truncation?: TruncationResult; resultLimitReached?: number; meta?: OutputMeta; @@ -76,7 +76,7 @@ export interface FindToolDetails { * Pluggable operations for the find tool. * Override these to delegate file search to remote systems (e.g., SSH). */ -export interface FindOperations { +export interface GlobOperations { /** Check if path exists */ exists: (absolutePath: string) => Promise | boolean; /** Optional stat for distinguishing files vs directories. */ @@ -87,28 +87,28 @@ export interface FindOperations { glob: (pattern: string, cwd: string, options: { ignore: string[]; limit: number }) => Promise | string[]; } -export interface FindToolOptions { +export interface GlobToolOptions { /** Custom operations for find. Default: local filesystem + rg */ - operations?: FindOperations; + operations?: GlobOperations; } -interface FindTarget { +interface GlobTarget { searchPath: string; globPattern: string; hasGlob: boolean; } -export class FindTool implements AgentTool { - readonly name = "find"; +export class GlobTool implements AgentTool { + readonly name = "glob"; readonly approval = "read" as const; readonly loadMode = "essential"; - readonly label = "Find"; + readonly label = "Glob"; readonly description: string; readonly parameters = findSchema; readonly examples: readonly ToolExample[] = [ { - caption: "Find files", + caption: "Glob files", call: { paths: ["src/**/*.ts"] }, }, { @@ -116,33 +116,33 @@ export class FindTool implements AgentTool { call: { paths: ["src/**/*.ts", "test/**/*.ts"] }, }, { - caption: "Find gitignored files like .env", + caption: "Glob gitignored files like .env", call: { paths: [".env*"], gitignore: false }, }, { - caption: "Find directories matching a name (returns both files and dirs; directories are suffixed with `/`)", + caption: "Glob directories matching a name (returns both files and dirs; directories are suffixed with `/`)", call: { paths: ["**/tests"] }, }, ]; readonly strict = true; - readonly #customOps?: FindOperations; + readonly #customOps?: GlobOperations; constructor( private readonly session: ToolSession, - options?: FindToolOptions, + options?: GlobToolOptions, ) { this.#customOps = options?.operations; - this.description = prompt.render(findDescription); + this.description = prompt.render(globDescription); } async execute( _toolCallId: string, params: typeof findSchema.infer, signal?: AbortSignal, - onUpdate?: AgentToolUpdateCallback, + onUpdate?: AgentToolUpdateCallback, _context?: AgentToolContext, - ): Promise> { + ): Promise> { const { paths, limit, hidden, gitignore } = params; return untilAborted(signal, async () => { @@ -194,7 +194,7 @@ export class FindTool implements AgentTool { const multiPattern = await resolveExplicitFindPatterns(effectivePatterns, this.session.cwd); const isSingle = !multiPattern; - const targets: FindTarget[] = multiPattern + const targets: GlobTarget[] = multiPattern ? multiPattern.targets.map(target => ({ searchPath: resolveToCwd(target.basePath, this.session.cwd), globPattern: target.globPattern, @@ -242,11 +242,11 @@ export class FindTool implements AgentTool { const buildResult = ( files: string[], opts?: { notice?: string; forceTruncated?: boolean }, - ): AgentToolResult => { + ): AgentToolResult => { const notice = opts?.notice; const forceTruncated = opts?.forceTruncated ?? false; if (files.length === 0) { - const details: FindToolDetails = { + const details: GlobToolDetails = { scopePath, fileCount: 0, files: [], @@ -272,7 +272,7 @@ export class FindTool implements AgentTool { const rawOutput = trailingNotes.length > 0 ? `${baseOutput}\n\n${trailingNotes.join("\n")}` : baseOutput; const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER }); - const details: FindToolDetails = { + const details: GlobToolDetails = { scopePath, fileCount: limited.length, files: limited, @@ -337,7 +337,7 @@ export class FindTool implements AgentTool { const now = Date.now(); if (now - lastUpdate < updateIntervalMs) return; lastUpdate = now; - const details: FindToolDetails = { + const details: GlobToolDetails = { scopePath, fileCount: onUpdateMatches.length, files: onUpdateMatches.slice(), @@ -362,7 +362,7 @@ export class FindTool implements AgentTool { }; let timedOut = false; - const runTarget = async (target: FindTarget): Promise> => { + const runTarget = async (target: GlobTarget): Promise> => { throwIfAborted(signal); let stat: fs.Stats; try { @@ -435,7 +435,7 @@ export class FindTool implements AgentTool { partial.sort((a, b) => b.m - a.m); const sortedPaths = partial.map(entry => entry.p); const seconds = timeoutMs % 1000 === 0 ? `${timeoutMs / 1000}` : (timeoutMs / 1000).toFixed(1); - const notice = `find timed out after ${seconds}s; returning ${sortedPaths.length} partial matches — narrow the pattern instead of retrying blindly`; + const notice = `glob timed out after ${seconds}s; returning ${sortedPaths.length} partial matches — narrow the pattern instead of retrying blindly`; return buildResult(sortedPaths, { notice, forceTruncated: true }); } @@ -461,33 +461,33 @@ export class FindTool implements AgentTool { // TUI Renderer // ============================================================================= -interface FindRenderArgs { +interface GlobRenderArgs { paths?: string | string[]; limit?: number; } -function formatFindRenderPaths(paths: FindRenderArgs["paths"]): string | undefined { +function formatGlobRenderPaths(paths: GlobRenderArgs["paths"]): string | undefined { return Array.isArray(paths) ? paths.join(", ") : paths; } const COLLAPSED_LIST_LIMIT = PREVIEW_LIMITS.COLLAPSED_ITEMS; -function findStatusIcon(uiTheme: Theme): string { +function globStatusIcon(uiTheme: Theme): string { return uiTheme.fg("toolTitle", uiTheme.symbol("icon.search")); } -export const findToolRenderer = { +export const globToolRenderer = { inline: true, - renderCall(args: FindRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component { + renderCall(args: GlobRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component { const meta: string[] = []; if (args.limit !== undefined) meta.push(`limit:${args.limit}`); const text = renderStatusLine( { icon: "pending", - title: "Find", + title: "Glob", titleColor: "toolTitle", - description: formatFindRenderPaths(args.paths) || "*", + description: formatGlobRenderPaths(args.paths) || "*", meta, }, uiTheme, @@ -496,10 +496,10 @@ export const findToolRenderer = { }, renderResult( - result: { content: Array<{ type: string; text?: string }>; details?: FindToolDetails; isError?: boolean }, + result: { content: Array<{ type: string; text?: string }>; details?: GlobToolDetails; isError?: boolean }, options: RenderResultOptions, uiTheme: Theme, - args?: FindRenderArgs, + args?: GlobRenderArgs, ): Component { const details = result.details; @@ -524,10 +524,10 @@ export const findToolRenderer = { const lines = textContent.split("\n").filter(l => l.trim()); const header = renderStatusLine( { - iconOverride: findStatusIcon(uiTheme), - title: "Find", + iconOverride: globStatusIcon(uiTheme), + title: "Glob", titleColor: "toolTitle", - description: formatFindRenderPaths(args?.paths), + description: formatGlobRenderPaths(args?.paths), meta: [formatCount("file", lines.length)], }, uiTheme, @@ -565,9 +565,9 @@ export const findToolRenderer = { const header = renderStatusLine( { icon: "warning", - title: "Find", + title: "Glob", titleColor: "toolTitle", - description: formatFindRenderPaths(args?.paths), + description: formatGlobRenderPaths(args?.paths), meta: ["0 files"], }, uiTheme, @@ -581,10 +581,10 @@ export const findToolRenderer = { if (truncated) meta.push(uiTheme.fg("warning", "truncated")); const header = renderStatusLine( { - ...(truncated ? { icon: "warning" as const } : { iconOverride: findStatusIcon(uiTheme) }), - title: "Find", + ...(truncated ? { icon: "warning" as const } : { iconOverride: globStatusIcon(uiTheme) }), + title: "Glob", titleColor: "toolTitle", - description: formatFindRenderPaths(args?.paths), + description: formatGlobRenderPaths(args?.paths), meta, }, uiTheme, diff --git a/packages/coding-agent/src/tools/search.ts b/packages/coding-agent/src/tools/grep.ts similarity index 97% rename from packages/coding-agent/src/tools/search.ts rename to packages/coding-agent/src/tools/grep.ts index 19471cd75..1147cecfd 100644 --- a/packages/coding-agent/src/tools/search.ts +++ b/packages/coding-agent/src/tools/grep.ts @@ -14,7 +14,7 @@ import type { LocalProtocolOptions } from "../internal-urls/local-protocol"; import { InternalUrlRouter } from "../internal-urls/router"; import type { InternalResource, ResolveContext } from "../internal-urls/types"; import type { Theme } from "../modes/theme/theme"; -import searchDescription from "../prompts/tools/search.md" with { type: "text" }; +import grepDescription from "../prompts/tools/grep.md" with { type: "text" }; import { DEFAULT_MAX_COLUMN, type TruncationResult, truncateHead, truncateLine } from "../session/streaming-output"; import { Ellipsis, @@ -77,7 +77,7 @@ const searchSchema = type({ .describe("files to skip before collecting results — use to paginate when the prior call hit the file limit"), }); -export type SearchToolInput = typeof searchSchema.infer; +export type GrepToolInput = typeof searchSchema.infer; export function toPathList(input: string | string[] | undefined): string[] { return typeof input === "string" ? [input] : (input ?? []); } @@ -110,14 +110,14 @@ const SEARCH_GREP_TIMEOUT_MS = 30_000; * line-range selector peeled off the trailing `:N-M` (or `:N+K`, `:N,M`, …) * chunk via {@link splitPathAndSel}. */ -interface SearchPathSpec { +interface GrepPathSpec { original: string; clean: string; ranges?: [LineRange, ...LineRange[]]; } -function parsePathSpecs(rawEntries: readonly string[]): SearchPathSpec[] { - const specs: SearchPathSpec[] = []; +function parsePathSpecs(rawEntries: readonly string[]): GrepPathSpec[] { + const specs: GrepPathSpec[] = []; for (const entry of rawEntries) { // Internal URLs (`artifact://`, `skill://`, …) use the URL-aware splitter, // which peels selector-shaped tails only for selector-capable schemes and @@ -572,7 +572,7 @@ async function expandVirtualInternalResource( } async function resolveInternalSearchInputs(opts: { - pathSpecs: readonly SearchPathSpec[]; + pathSpecs: readonly GrepPathSpec[]; resolvedPaths: string[]; cwd: string; settings: unknown; @@ -634,7 +634,7 @@ async function resolveInternalSearchInputs(opts: { }; } -export interface SearchToolDetails { +export interface GrepToolDetails { truncation?: TruncationResult; fileLimitReached?: number; perFileLimitReached?: number; @@ -666,19 +666,19 @@ export interface SearchToolDetails { type SearchParams = typeof searchSchema.infer; -export class SearchTool implements AgentTool { - readonly name = "search"; +export class GrepTool implements AgentTool { + readonly name = "grep"; readonly approval = "read" as const; - readonly label = "Search"; + readonly label = "Grep"; readonly loadMode = "discoverable"; - readonly summary = "Search file contents using ripgrep (fast text search)"; + readonly summary = "Grep file contents using ripgrep (fast regex search)"; readonly description: string; readonly parameters = searchSchema; readonly strict = true; constructor(private readonly session: ToolSession) { const displayMode = resolveFileDisplayMode(session); - this.description = prompt.render(searchDescription, { + this.description = prompt.render(grepDescription, { IS_HL_MODE: displayMode.hashLines, IS_LINE_NUMBER_MODE: !displayMode.hashLines && displayMode.lineNumbers, }); @@ -688,9 +688,9 @@ export class SearchTool implements AgentTool, + _onUpdate?: AgentToolUpdateCallback, _toolContext?: AgentToolContext, - ): Promise> { + ): Promise> { const { pattern, paths: rawPaths, case: caseSensitive, gitignore, skip } = params; return untilAborted(signal, async () => { @@ -773,8 +773,8 @@ export class SearchTool implements AgentTool Boolean(s)) .join("\n") || undefined; if (selectedMatches.length === 0) { - const details: SearchToolDetails = { + const details: GrepToolDetails = { scopePath, searchPath, cwd: this.session.cwd, @@ -1245,7 +1245,7 @@ export class SearchTool implements AgentTool 0) meta.push(`skip:${args.skip}`); const text = renderStatusLine( - { icon: "pending", title: "Search", titleColor: "toolTitle", description: args.pattern || "?", meta }, + { icon: "pending", title: "Grep", titleColor: "toolTitle", description: args.pattern || "?", meta }, uiTheme, ); return new Text(text, 1, 0); }, renderResult( - result: { content: Array<{ type: string; text?: string }>; details?: SearchToolDetails; isError?: boolean }, + result: { content: Array<{ type: string; text?: string }>; details?: GrepToolDetails; isError?: boolean }, options: RenderResultOptions, uiTheme: Theme, - args?: SearchRenderArgs, + args?: GrepRenderArgs, ): Component { const details = result.details; @@ -1491,8 +1491,8 @@ export const searchToolRenderer = { const description = args?.pattern ?? undefined; const header = renderStatusLine( { - iconOverride: searchStatusIcon(uiTheme), - title: "Search", + iconOverride: grepStatusIcon(uiTheme), + title: "Grep", titleColor: "toolTitle", description, meta: [formatCount("item", lines.length)], @@ -1536,7 +1536,7 @@ export const searchToolRenderer = { const scopeMeta = searchScopeMeta(details); if (scopeMeta) meta.push(scopeMeta); const header = renderStatusLine( - { icon: "warning", title: "Search", titleColor: "toolTitle", description: args?.pattern, meta }, + { icon: "warning", title: "Grep", titleColor: "toolTitle", description: args?.pattern, meta }, uiTheme, ); const lines = [header, formatEmptyMessage("No matches found", uiTheme)]; @@ -1552,8 +1552,8 @@ export const searchToolRenderer = { const description = args?.pattern ?? undefined; const header = renderStatusLine( { - ...(truncated ? { icon: "warning" as const } : { iconOverride: searchStatusIcon(uiTheme) }), - title: "Search", + ...(truncated ? { icon: "warning" as const } : { iconOverride: grepStatusIcon(uiTheme) }), + title: "Grep", titleColor: "toolTitle", description, meta, diff --git a/packages/coding-agent/src/tools/index.ts b/packages/coding-agent/src/tools/index.ts index d87fb1390..29e17fd0c 100644 --- a/packages/coding-agent/src/tools/index.ts +++ b/packages/coding-agent/src/tools/index.ts @@ -39,13 +39,14 @@ import { AstEditTool } from "./ast-edit"; import { AstGrepTool } from "./ast-grep"; import { BashTool } from "./bash"; import { BrowserTool } from "./browser"; -import type { BuiltinToolName } from "./builtin-names"; +import { type BuiltinToolName, normalizeToolNames } from "./builtin-names"; import { type CheckpointState, CheckpointTool, RewindTool } from "./checkpoint"; import { DebugTool } from "./debug"; import { EvalTool } from "./eval"; import { resolveEvalBackends } from "./eval-backends"; -import { FindTool } from "./find"; import { GithubTool } from "./gh"; +import { GlobTool } from "./glob"; +import { GrepTool } from "./grep"; import { InspectImageTool } from "./inspect-image"; import { IrcTool, isIrcEnabled } from "./irc"; import { JobTool } from "./job"; @@ -60,7 +61,6 @@ import { ReadTool } from "./read"; import { createReportToolIssueTool, isAutoQaEnabled } from "./report-tool-issue"; import { ResolveTool } from "./resolve"; import { reportFindingTool } from "./review"; -import { SearchTool } from "./search"; import { SearchToolBm25Tool } from "./search-tool-bm25"; import { loadSshTool } from "./ssh"; import { type TodoPhase, TodoTool } from "./todo"; @@ -82,8 +82,9 @@ export * from "./checkpoint"; export * from "./debug"; export * from "./eval"; export * from "./eval-backends"; -export * from "./find"; export * from "./gh"; +export * from "./glob"; +export * from "./grep"; export * from "./image-gen"; export * from "./inspect-image"; export * from "./irc"; @@ -98,7 +99,6 @@ export * from "./read"; export * from "./report-tool-issue"; export * from "./resolve"; export * from "./review"; -export * from "./search"; export * from "./search-tool-bm25"; export * from "./ssh"; export * from "./todo"; @@ -384,7 +384,7 @@ export const DEFAULT_ESSENTIAL_TOOL_NAMES: readonly string[] = [ "bash", "edit", "write", - "find", + "glob", "eval", ] as const; @@ -395,7 +395,7 @@ export const DEFAULT_ESSENTIAL_TOOL_NAMES: readonly string[] = [ */ export function computeEssentialBuiltinNames(settings: Settings): string[] { const override = settings.get("tools.essentialOverride") ?? []; - const cleaned = override.map(name => name.trim()).filter(Boolean); + const cleaned = normalizeToolNames(override.map(name => name.trim()).filter(Boolean)); if (cleaned.length > 0) { return cleaned.filter(name => name in BUILTIN_TOOLS); } @@ -449,8 +449,8 @@ export const BUILTIN_TOOLS: Record = { eval: s => new EvalTool(s), ssh: loadSshTool, github: GithubTool.createIf, - find: s => new FindTool(s), - search: s => new SearchTool(s), + glob: s => new GlobTool(s), + grep: s => new GrepTool(s), lsp: LspTool.createIf, inspect_image: s => new InspectImageTool(s), browser: s => new BrowserTool(s), @@ -487,8 +487,7 @@ export type ToolName = BuiltinToolName; export async function createTools(session: ToolSession, toolNames?: string[]): Promise { const includeYield = session.requireYieldTool === true; const enableLsp = session.enableLsp ?? true; - let requestedTools = - toolNames && toolNames.length > 0 ? [...new Set(toolNames.map(name => name.toLowerCase()))] : undefined; + let requestedTools = toolNames && toolNames.length > 0 ? normalizeToolNames(toolNames) : undefined; const goalEnabled = session.settings.get("goal.enabled"); const goalModeActive = goalEnabled && session.getGoalModeState?.()?.enabled === true; if (goalModeActive && requestedTools && !requestedTools.includes("goal")) { @@ -552,7 +551,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P // Auto-include AST counterparts when their text-based sibling is present if (requestedTools) { if ( - requestedTools.includes("search") && + requestedTools.includes("grep") && !requestedTools.includes("ast_grep") && session.settings.get("astGrep.enabled") ) { @@ -602,8 +601,8 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P if (name === "eval") return allowEval; if (name === "debug") return session.settings.get("debug.enabled"); if (name === "todo") return !includeYield && session.settings.get("todo.enabled"); - if (name === "find") return session.settings.get("find.enabled"); - if (name === "search") return session.settings.get("search.enabled"); + if (name === "glob") return session.settings.get("glob.enabled"); + if (name === "grep") return session.settings.get("grep.enabled"); if (name === "github") return session.settings.get("github.enabled"); if (name === "ast_grep") return session.settings.get("astGrep.enabled"); if (name === "ast_edit") return session.settings.get("astEdit.enabled"); diff --git a/packages/coding-agent/src/tools/renderers.ts b/packages/coding-agent/src/tools/renderers.ts index 39889af41..db308a3d8 100644 --- a/packages/coding-agent/src/tools/renderers.ts +++ b/packages/coding-agent/src/tools/renderers.ts @@ -18,15 +18,15 @@ import { bashToolRenderer } from "./bash"; import { browserToolRenderer } from "./browser/render"; import { debugToolRenderer } from "./debug"; import { evalToolRenderer } from "./eval-render"; -import { findToolRenderer } from "./find"; import { githubToolRenderer } from "./gh-renderer"; +import { globToolRenderer } from "./glob"; +import { grepToolRenderer } from "./grep"; import { inspectImageToolRenderer } from "./inspect-image-renderer"; import { ircToolRenderer } from "./irc"; import { jobToolRenderer } from "./job"; import { recallToolRenderer, reflectToolRenderer, retainToolRenderer } from "./memory-render"; import { readToolRenderer } from "./read"; import { resolveToolRenderer } from "./resolve"; -import { searchToolRenderer } from "./search"; import { searchToolBm25Renderer } from "./search-tool-bm25"; import { sshToolRenderer } from "./ssh"; import { todoToolRenderer } from "./todo"; @@ -75,8 +75,8 @@ export const toolRenderers: Record = { eval: evalToolRenderer as ToolRenderer, edit: editToolRenderer as ToolRenderer, apply_patch: editToolRenderer as ToolRenderer, - find: findToolRenderer as ToolRenderer, - search: searchToolRenderer as ToolRenderer, + glob: globToolRenderer as ToolRenderer, + grep: grepToolRenderer as ToolRenderer, lsp: lspToolRenderer as ToolRenderer, inspect_image: inspectImageToolRenderer as ToolRenderer, irc: ircToolRenderer as ToolRenderer, diff --git a/packages/coding-agent/test/agent-session-acp-permission.test.ts b/packages/coding-agent/test/agent-session-acp-permission.test.ts index c8c422f11..3bd17c243 100644 --- a/packages/coding-agent/test/agent-session-acp-permission.test.ts +++ b/packages/coding-agent/test/agent-session-acp-permission.test.ts @@ -785,3 +785,13 @@ it("read tool: requestPermission is never called for non-gated tools", async () expect(permissionSpy).toHaveBeenCalledTimes(0); expect(readTool.executeCalls).toBe(1); }); + +it("setActiveToolsByName normalizes legacy tool names", async () => { + const grepTool = makeFakeTool("grep"); + const globTool = makeFakeTool("glob"); + session = await createSession([grepTool, globTool]); + + await session.setActiveToolsByName(["Search", "find", "grep"]); + + expect(session.getActiveToolNames()).toEqual(["grep", "glob"]); +}); diff --git a/packages/coding-agent/test/agent-session-auto-compaction-progress-guard.test.ts b/packages/coding-agent/test/agent-session-auto-compaction-progress-guard.test.ts index 4f9a2d3b9..ff0e70b4d 100644 --- a/packages/coding-agent/test/agent-session-auto-compaction-progress-guard.test.ts +++ b/packages/coding-agent/test/agent-session-auto-compaction-progress-guard.test.ts @@ -448,7 +448,7 @@ describe("AgentSession auto-compaction progress guard", () => { const bigCallId = "call-big-useless"; sessionManager.appendMessage({ role: "assistant", - content: [{ type: "toolCall", id: bigCallId, name: "search", arguments: { pattern: "TODO" } }], + content: [{ type: "toolCall", id: bigCallId, name: "grep", arguments: { pattern: "TODO" } }], api: "anthropic-messages", provider: "anthropic", model: "claude-sonnet-4-5", @@ -466,7 +466,7 @@ describe("AgentSession auto-compaction progress guard", () => { sessionManager.appendMessage({ role: "toolResult", toolCallId: bigCallId, - toolName: "search", + toolName: "grep", content: [{ type: "text", text: "match line\n".repeat(20000) }], // ~40k+ tokens isError: false, useless: true, diff --git a/packages/coding-agent/test/agent-session-auto-compaction-queue.test.ts b/packages/coding-agent/test/agent-session-auto-compaction-queue.test.ts index 1bdc4415f..ffeec1d34 100644 --- a/packages/coding-agent/test/agent-session-auto-compaction-queue.test.ts +++ b/packages/coding-agent/test/agent-session-auto-compaction-queue.test.ts @@ -503,7 +503,7 @@ describe("AgentSession auto-compaction queue resume", () => { const bigCallId = "call-big-useless"; sessionManager.appendMessage({ role: "assistant", - content: [{ type: "toolCall", id: bigCallId, name: "search", arguments: { pattern: "TODO" } }], + content: [{ type: "toolCall", id: bigCallId, name: "grep", arguments: { pattern: "TODO" } }], api: "anthropic-messages", provider: "anthropic", model: "claude-sonnet-4-5", @@ -521,7 +521,7 @@ describe("AgentSession auto-compaction queue resume", () => { sessionManager.appendMessage({ role: "toolResult", toolCallId: bigCallId, - toolName: "search", + toolName: "grep", content: [{ type: "text", text: "match line\n".repeat(20000) }], // ~40k+ tokens isError: false, useless: true, diff --git a/packages/coding-agent/test/bash-acp-terminal.test.ts b/packages/coding-agent/test/bash-acp-terminal.test.ts index b9aad742f..50fd110ac 100644 --- a/packages/coding-agent/test/bash-acp-terminal.test.ts +++ b/packages/coding-agent/test/bash-acp-terminal.test.ts @@ -17,8 +17,8 @@ function makeSession(bridge: ClientBridge): ToolSession { if (key === "bashInterceptor.enabled") return false; if (key === "astGrep.enabled") return false; if (key === "astEdit.enabled") return false; - if (key === "search.enabled") return false; - if (key === "find.enabled") return false; + if (key === "grep.enabled") return false; + if (key === "glob.enabled") return false; return undefined; }, getBashInterceptorRules() { diff --git a/packages/coding-agent/test/bash-failure-result.test.ts b/packages/coding-agent/test/bash-failure-result.test.ts index 0e8523ebd..51bce014e 100644 --- a/packages/coding-agent/test/bash-failure-result.test.ts +++ b/packages/coding-agent/test/bash-failure-result.test.ts @@ -17,8 +17,8 @@ function makeSession(): ToolSession { if (key === "bash.stripTrailingHeadTail") return false; if (key === "astGrep.enabled") return false; if (key === "astEdit.enabled") return false; - if (key === "search.enabled") return false; - if (key === "find.enabled") return false; + if (key === "grep.enabled") return false; + if (key === "glob.enabled") return false; return undefined; }, getBashInterceptorRules() { diff --git a/packages/coding-agent/test/checkpoint-rpc-qa.ts b/packages/coding-agent/test/checkpoint-rpc-qa.ts index e4e6512b9..e9d192bab 100644 --- a/packages/coding-agent/test/checkpoint-rpc-qa.ts +++ b/packages/coding-agent/test/checkpoint-rpc-qa.ts @@ -120,7 +120,7 @@ async function main() { const hasCheckpoint = toolSequence.includes("checkpoint"); const hasRewind = toolSequence.includes("rewind"); - const hasFind = toolSequence.includes("find"); + const hasGlob = toolSequence.includes("glob"); const hasRead = toolSequence.includes("read"); const activeHasRewindReport = messages.some( @@ -132,7 +132,7 @@ async function main() { .map(message => message.toolName); const activeHasRewindResult = activeToolResults.includes("rewind"); - const activeHasFindResult = activeToolResults.includes("find"); + const activeHasGlobResult = activeToolResults.includes("glob"); const activeHasReadResult = activeToolResults.includes("read"); const rewindReportEntries = customMessages.filter(entry => entry.customType === "rewind-report"); @@ -163,7 +163,7 @@ async function main() { if (!hasCheckpoint || !hasRewind) { throw new Error("Agent did not execute both checkpoint and rewind."); } - if (!hasFind || !hasRead) { + if (!hasGlob || !hasRead) { throw new Error("Agent did not perform requested exploratory find/read inside checkpoint."); } if (!activeHasRewindReport) { @@ -172,7 +172,7 @@ async function main() { if (activeHasRewindResult) { throw new Error("Active context still contains rewind tool result; rewind did not prune it."); } - if (activeHasFindResult || activeHasReadResult) { + if (activeHasGlobResult || activeHasReadResult) { throw new Error("Active context still contains exploratory find/read tool results after rewind."); } if (rewindReportEntries.length === 0) { diff --git a/packages/coding-agent/test/core/python-display.test.ts b/packages/coding-agent/test/core/python-display.test.ts index a5f176c7b..cd4e1caef 100644 --- a/packages/coding-agent/test/core/python-display.test.ts +++ b/packages/coding-agent/test/core/python-display.test.ts @@ -4,10 +4,10 @@ import { renderKernelDisplay } from "@oh-my-pi/pi-coding-agent/eval/py/display"; describe("renderKernelDisplay (raw bundle shape)", () => { it("renders status events without text output", async () => { const { text, outputs } = await renderKernelDisplay({ - "application/x-omp-status": { op: "find", count: 12, pattern: "foo" }, + "application/x-omp-status": { op: "glob", count: 12, pattern: "foo" }, }); expect(text).toBe(""); - expect(outputs).toEqual([{ type: "status", event: { op: "find", count: 12, pattern: "foo" } }]); + expect(outputs).toEqual([{ type: "status", event: { op: "glob", count: 12, pattern: "foo" } }]); }); it("prefers text/markdown over text/plain", async () => { diff --git a/packages/coding-agent/test/cursor-exec.test.ts b/packages/coding-agent/test/cursor-exec.test.ts index d04f288c7..91bf8ebe6 100644 --- a/packages/coding-agent/test/cursor-exec.test.ts +++ b/packages/coding-agent/test/cursor-exec.test.ts @@ -4,7 +4,7 @@ import * as os from "node:os"; import * as path from "node:path"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { CursorExecHandlers } from "@oh-my-pi/pi-coding-agent/cursor"; -import { SearchTool, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; +import { GrepTool, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; function createTestSession(cwd: string, overrides: Partial = {}): ToolSession { return { @@ -19,16 +19,16 @@ function createTestSession(cwd: string, overrides: Partial = {}): T describe("CursorExecHandlers.grep bridge", () => { let cwd: string; - let searchTool: SearchTool; + let searchTool: GrepTool; let handlers: CursorExecHandlers; beforeEach(async () => { cwd = await fs.mkdtemp(path.join(os.tmpdir(), "cursor-exec-test-")); await Bun.write(path.join(cwd, "sample.txt"), "Hello World\nhello world\n"); - searchTool = new SearchTool(createTestSession(cwd)); + searchTool = new GrepTool(createTestSession(cwd)); handlers = new CursorExecHandlers({ cwd, - tools: new Map([["search", searchTool as any]]), + tools: new Map([["grep", searchTool as any]]), }); }); diff --git a/packages/coding-agent/test/discovery/agent-fields.test.ts b/packages/coding-agent/test/discovery/agent-fields.test.ts index 34371f7b2..64ad51d56 100644 --- a/packages/coding-agent/test/discovery/agent-fields.test.ts +++ b/packages/coding-agent/test/discovery/agent-fields.test.ts @@ -64,7 +64,17 @@ describe("parseAgentFields", () => { tools: ["Read", "Search"], }); - expect(fields?.tools).toEqual(["read", "search", "yield"]); + expect(fields?.tools).toEqual(["read", "grep", "yield"]); + }); + + test("maps legacy search and find tool names", () => { + const fields = parseAgentFields({ + name: "reviewer", + description: "desc", + tools: ["Find", "Glob", "Search", "Grep"], + }); + + expect(fields?.tools).toEqual(["glob", "grep", "yield"]); }); test("parses autoloadSkills from array frontmatter", () => { diff --git a/packages/coding-agent/test/edit/seen-line-guard.test.ts b/packages/coding-agent/test/edit/seen-line-guard.test.ts index df234afb6..476977340 100644 --- a/packages/coding-agent/test/edit/seen-line-guard.test.ts +++ b/packages/coding-agent/test/edit/seen-line-guard.test.ts @@ -7,7 +7,7 @@ import { type ExecuteHashlineSingleOptions, executeHashlineSingle } from "@oh-my import { canonicalSnapshotKey, getFileSnapshotStore } from "@oh-my-pi/pi-coding-agent/edit/file-snapshot-store"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; -import { SearchTool } from "@oh-my-pi/pi-coding-agent/tools/search"; +import { GrepTool } from "../../src/tools/grep"; function createSession(cwd: string): ToolSession { return { @@ -193,7 +193,7 @@ describe("search → edit seen-line guard", () => { getArtifactsDir: () => path.join(cwd, "artifacts"), allocateOutputArtifact: async () => ({ id: "artifact-1", path: path.join(cwd, "artifact-1.log") }), // Zero context so the seen set is exactly the matched lines. - settings: Settings.isolated({ "search.contextBefore": 0, "search.contextAfter": 0 }), + settings: Settings.isolated({ "grep.contextBefore": 0, "grep.contextAfter": 0 }), enableLsp: false, } as ToolSession; } @@ -204,7 +204,7 @@ describe("search → edit seen-line guard", () => { await Bun.write(file, `${lines.join("\n")}\n`); const session = searchSession(tmpDir); - const search = await new SearchTool(session).execute("s1", { pattern: "NEEDLE", paths: [file] }); + const search = await new GrepTool(session).execute("s1", { pattern: "NEEDLE", paths: [file] }); const tag = tagFromOutput(resultText(search)); const seen = getFileSnapshotStore(session).byHash(canonicalSnapshotKey(file), tag)?.seenLines; @@ -222,7 +222,7 @@ describe("search → edit seen-line guard", () => { await Bun.write(file, `${lines.join("\n")}\n`); const session = searchSession(tmpDir); - const search = await new SearchTool(session).execute("s1", { pattern: "NEEDLE", paths: [file] }); + const search = await new GrepTool(session).execute("s1", { pattern: "NEEDLE", paths: [file] }); const tag = tagFromOutput(resultText(search)); await expect(executeHashlineSingle(execOptions(`[code.txt#${tag}]\nSWAP 8.=8:\n+X`, session))).rejects.toThrow( diff --git a/packages/coding-agent/test/flag-tables.test.ts b/packages/coding-agent/test/flag-tables.test.ts index 386996f19..a48800c24 100644 --- a/packages/coding-agent/test/flag-tables.test.ts +++ b/packages/coding-agent/test/flag-tables.test.ts @@ -48,6 +48,14 @@ describe("OPTIONAL_VALUE_FLAGS table is honored by args.ts parseArgs", () => { } }); +describe("--tools legacy aliases", () => { + it("maps search and find to grep and glob", () => { + const result = parseArgs(["--tools", "search,find,grep"]); + + expect(result.tools).toEqual(["grep", "glob"]); + }); +}); + describe("OPTIONAL_FLAGS per-flag quirks", () => { it("treats empty string as bare resume for --resume", () => { const result = parseArgs(["--resume", ""]); diff --git a/packages/coding-agent/test/modes/controllers/event-controller-interrupt.test.ts b/packages/coding-agent/test/modes/controllers/event-controller-interrupt.test.ts index d539bf9fa..8ff6d0a37 100644 --- a/packages/coding-agent/test/modes/controllers/event-controller-interrupt.test.ts +++ b/packages/coding-agent/test/modes/controllers/event-controller-interrupt.test.ts @@ -38,7 +38,7 @@ function toolStartWithIntent(toolCallId: string, intent: string): AgentSessionEv return { type: "tool_execution_start", toolCallId, - toolName: "search", + toolName: "grep", args: {}, intent, } as unknown as AgentSessionEvent; diff --git a/packages/coding-agent/test/sdk-tool-activation.test.ts b/packages/coding-agent/test/sdk-tool-activation.test.ts index 7f0e55c12..cb388affb 100644 --- a/packages/coding-agent/test/sdk-tool-activation.test.ts +++ b/packages/coding-agent/test/sdk-tool-activation.test.ts @@ -140,7 +140,7 @@ describe("createAgentSession defaultInactive tool activation", () => { it("activates the yield tool when requireYieldTool is set and toolNames is explicit", async () => { // Regression for #1408: plan-mode subagents pass an explicit `toolNames` list - // (e.g. `["read", "search", "find", "lsp", "web_search"]`). Without this + // (e.g. `["read", "grep", "glob", "lsp", "web_search"]`). Without this // invariant, `yield` ended up registered but not active, and the model // could not satisfy the idle-reminder contract that demands a `yield` call. const tempDir = makeTempDir(); @@ -148,7 +148,7 @@ describe("createAgentSession defaultInactive tool activation", () => { const { session } = await createAgentSession({ ...baseOptions(tempDir), requireYieldTool: true, - toolNames: ["read", "search", "find", "web_search"], + toolNames: ["read", "grep", "glob", "web_search"], }); try { @@ -158,6 +158,27 @@ describe("createAgentSession defaultInactive tool activation", () => { } }); + it("normalizes legacy builtin toolNames before selecting the active SDK tools", async () => { + const tempDir = makeTempDir(); + + const { session } = await createAgentSession({ + ...baseOptions(tempDir), + toolNames: ["read", "search", "find"], + }); + + try { + const activeToolNames = session.getActiveToolNames(); + + expect(activeToolNames).toContain("read"); + expect(activeToolNames).toContain("grep"); + expect(activeToolNames).toContain("glob"); + expect(activeToolNames).not.toContain("search"); + expect(activeToolNames).not.toContain("find"); + } finally { + await session.dispose(); + } + }); + it("keeps the hidden resolve tool registered for plan mode even when no deferrable tool is requested", async () => { // Regression for #1428: plan mode submits its finalized plan via // `resolve { action: "apply" }` dispatched through a standing handler @@ -170,7 +191,7 @@ describe("createAgentSession defaultInactive tool activation", () => { const { session } = await createAgentSession({ ...baseOptions(tempDir), - toolNames: ["read", "search", "find", "web_search"], + toolNames: ["read", "grep", "glob", "web_search"], }); try { @@ -189,7 +210,7 @@ describe("createAgentSession defaultInactive tool activation", () => { const { session } = await createAgentSession({ ...baseOptions(tempDir), settings, - toolNames: ["read", "search", "find", "web_search"], + toolNames: ["read", "grep", "glob", "web_search"], }); try { diff --git a/packages/coding-agent/test/session/session-dump-format.test.ts b/packages/coding-agent/test/session/session-dump-format.test.ts index 48a24d4c3..39da8b671 100644 --- a/packages/coding-agent/test/session/session-dump-format.test.ts +++ b/packages/coding-agent/test/session/session-dump-format.test.ts @@ -82,8 +82,8 @@ describe("formatSessionDumpText tool parameters", () => { messages: [], tools: [ { - name: "find", - description: "Finds files.", + name: "glob", + description: "Globs files.", parameters: findSchema, examples: [{ call: { paths: ["src/**/*.ts"] } }], }, @@ -92,7 +92,7 @@ describe("formatSessionDumpText tool parameters", () => { expect(out).toContain("## Available Tools"); expect(out).toContain(""); - expect(out).toContain(''); + expect(out).toContain(''); }); it("omits the Available Tools section if inlineToolDescriptors is true", () => { diff --git a/packages/coding-agent/test/session/session-history-format.test.ts b/packages/coding-agent/test/session/session-history-format.test.ts index ba6f81e98..edb51fb08 100644 --- a/packages/coding-agent/test/session/session-history-format.test.ts +++ b/packages/coding-agent/test/session/session-history-format.test.ts @@ -106,7 +106,7 @@ describe("formatSessionHistoryMarkdown", () => { { role: "toolResult", toolCallId: "tc-orphan", - toolName: "search", + toolName: "grep", content: [{ type: "text", text: "one match" }], isError: false, timestamp: 1, @@ -122,8 +122,8 @@ describe("formatSessionHistoryMarkdown", () => { content: [ { type: "toolCall", - id: "tc-find", - name: "find", + id: "tc-glob", + name: "glob", arguments: { paths: ["packages/coding-agent/src/**/*.ts"] }, }, ], @@ -131,8 +131,8 @@ describe("formatSessionHistoryMarkdown", () => { }, { role: "toolResult", - toolCallId: "tc-find", - toolName: "find", + toolCallId: "tc-glob", + toolName: "glob", content: [{ type: "text", text: "session-history-format.ts" }], isError: false, timestamp: 2, @@ -150,8 +150,8 @@ describe("formatSessionHistoryMarkdown", () => { content: [ { type: "toolCall", - id: "tc-search", - name: "search", + id: "tc-grep", + name: "grep", arguments: { pattern: "PRIMARY_ARG_KEYS", paths: ["packages/coding-agent/src/session"] }, }, ], @@ -159,8 +159,8 @@ describe("formatSessionHistoryMarkdown", () => { }, { role: "toolResult", - toolCallId: "tc-search", - toolName: "search", + toolCallId: "tc-grep", + toolName: "grep", content: [{ type: "text", text: "timed out" }], isError: true, timestamp: 2, diff --git a/packages/coding-agent/test/settings-manager.test.ts b/packages/coding-agent/test/settings-manager.test.ts index 73dbc422b..8241fb9fc 100644 --- a/packages/coding-agent/test/settings-manager.test.ts +++ b/packages/coding-agent/test/settings-manager.test.ts @@ -511,6 +511,69 @@ describe("Settings", () => { expect(fs.readFileSync(path.join(agentDir, "last-changelog-version"), "utf8")).toBe("0.41.0"); }); + it("migrates legacy find and search settings to glob and grep", async () => { + await writeSettings({ + find: { enabled: false }, + search: { + enabled: false, + contextBefore: 2, + contextAfter: 5, + }, + }); + + const settings = await Settings.init({ cwd: projectDir, agentDir }); + + expect(settings.get("glob.enabled")).toBe(false); + expect(settings.get("grep.enabled")).toBe(false); + expect(settings.get("grep.contextBefore")).toBe(2); + expect(settings.get("grep.contextAfter")).toBe(5); + }); + + it("migrates flat legacy find and search settings keys to nested glob and grep", async () => { + await writeSettings({ + "find.enabled": false, + "search.enabled": false, + "search.contextBefore": 2, + "search.contextAfter": 5, + }); + + const settings = await Settings.init({ cwd: projectDir, agentDir }); + + expect(settings.get("glob.enabled")).toBe(false); + expect(settings.get("grep.enabled")).toBe(false); + expect(settings.get("grep.contextBefore")).toBe(2); + expect(settings.get("grep.contextAfter")).toBe(5); + }); + + it("does not clobber existing glob/grep settings when migrating legacy find/search ones", async () => { + await writeSettings({ + find: { enabled: false }, + glob: { enabled: true }, + search: { enabled: false }, + grep: { enabled: true }, + "find.enabled": false, + "glob.enabled": true, + "search.enabled": false, + "grep.enabled": true, + }); + + const settings = await Settings.init({ cwd: projectDir, agentDir }); + + expect(settings.get("glob.enabled")).toBe(true); + expect(settings.get("grep.enabled")).toBe(true); + }); + + it("migrates legacy tool names in persisted essential overrides", async () => { + await writeSettings({ + tools: { essentialOverride: ["read", "find", "search", "grep"] }, + "tools.essentialOverride": ["find", "search", "read"], + }); + + const settings = await Settings.init({ cwd: projectDir, agentDir }); + + expect(settings.get("tools.essentialOverride")).toEqual(["read", "glob", "grep"]); + }); + it("migrates from settings.json containing comments", async () => { const jsonPath = path.join(agentDir, "settings.json"); await fs.promises.writeFile( diff --git a/packages/coding-agent/test/shake.test.ts b/packages/coding-agent/test/shake.test.ts index 01c7aff07..d27f80ae5 100644 --- a/packages/coding-agent/test/shake.test.ts +++ b/packages/coding-agent/test/shake.test.ts @@ -363,7 +363,7 @@ describe("AgentSession shake", () => { const bigCallId = "call-big-useless-for-shake"; sessionManager.appendMessage({ role: "assistant", - content: [{ type: "toolCall", id: bigCallId, name: "search", arguments: { pattern: "TODO" } }], + content: [{ type: "toolCall", id: bigCallId, name: "grep", arguments: { pattern: "TODO" } }], ...apiInfo, stopReason: "toolUse", usage, @@ -372,7 +372,7 @@ describe("AgentSession shake", () => { sessionManager.appendMessage({ role: "toolResult", toolCallId: bigCallId, - toolName: "search", + toolName: "grep", content: [{ type: "text", text: "match line\n".repeat(20000) }], isError: false, useless: true, diff --git a/packages/coding-agent/test/tool-discovery/initial-tools.test.ts b/packages/coding-agent/test/tool-discovery/initial-tools.test.ts index fd3ca28f0..0547bf0ab 100644 --- a/packages/coding-agent/test/tool-discovery/initial-tools.test.ts +++ b/packages/coding-agent/test/tool-discovery/initial-tools.test.ts @@ -17,8 +17,8 @@ const allToolsSettings = Settings.isolated({ "astGrep.enabled": true, "astEdit.enabled": true, "debug.enabled": true, - "find.enabled": true, - "search.enabled": true, + "glob.enabled": true, + "grep.enabled": true, "github.enabled": true, "lsp.enabled": true, "inspect_image.enabled": true, @@ -98,8 +98,13 @@ describe("computeEssentialBuiltinNames", () => { }); it("respects tools.essentialOverride when provided", () => { - const settings = Settings.isolated({ "tools.essentialOverride": ["read", "find"] }); - expect(computeEssentialBuiltinNames(settings).sort()).toEqual(["find", "read"]); + const settings = Settings.isolated({ "tools.essentialOverride": ["read", "glob"] }); + expect(computeEssentialBuiltinNames(settings).sort()).toEqual(["glob", "read"]); + }); + + it("maps legacy essential override tool names", () => { + const settings = Settings.isolated({ "tools.essentialOverride": ["read", "find", "search", "glob"] }); + expect(computeEssentialBuiltinNames(settings).sort()).toEqual(["glob", "grep", "read"]); }); it("filters override entries that are not known built-in tools", () => { @@ -147,18 +152,18 @@ describe("filterInitialToolsForDiscoveryAll", () => { }; const base = { loadModeOf: (name: string): BuiltinToolLoadMode | undefined => loadModes[name], - essentialNames: new Set(["read", "bash", "edit", "write", "find"]), + essentialNames: new Set(["read", "bash", "edit", "write", "glob"]), explicitlyRequested: new Set(), restored: new Set(), forceActive: new Set(), }; it("hides non-essential discoverable built-ins", () => { - expect(filterInitialToolsForDiscoveryAll(["read", "edit", "todo", "search"], base)).toEqual(["read", "edit"]); + expect(filterInitialToolsForDiscoveryAll(["read", "edit", "todo", "grep"], base)).toEqual(["read", "edit"]); }); it("keeps discoverable tools required by a forced tool_choice (eager todo)", () => { - const result = filterInitialToolsForDiscoveryAll(["read", "todo", "search"], { + const result = filterInitialToolsForDiscoveryAll(["read", "todo", "grep"], { ...base, forceActive: new Set(["todo"]), }); @@ -166,15 +171,15 @@ describe("filterInitialToolsForDiscoveryAll", () => { }); it("keeps explicitly requested and restored discoverable tools", () => { - const result = filterInitialToolsForDiscoveryAll(["todo", "search"], { + const result = filterInitialToolsForDiscoveryAll(["todo", "grep"], { ...base, - explicitlyRequested: new Set(["search"]), + explicitlyRequested: new Set(["grep"]), restored: new Set(["todo"]), }); - expect([...result].sort()).toEqual(["search", "todo"]); + expect([...result].sort()).toEqual(["grep", "todo"]); }); it("never hides tools without a built-in loadMode (MCP/custom/extension)", () => { - expect(filterInitialToolsForDiscoveryAll(["mcp__server__tool", "search"], base)).toEqual(["mcp__server__tool"]); + expect(filterInitialToolsForDiscoveryAll(["mcp__server__tool", "grep"], base)).toEqual(["mcp__server__tool"]); }); }); diff --git a/packages/coding-agent/test/tool-discovery/persistence.test.ts b/packages/coding-agent/test/tool-discovery/persistence.test.ts index 791725fa3..3fcc276bb 100644 --- a/packages/coding-agent/test/tool-discovery/persistence.test.ts +++ b/packages/coding-agent/test/tool-discovery/persistence.test.ts @@ -8,8 +8,8 @@ import { describe("generic index: DiscoverableTool round-trip", () => { const tools: DiscoverableTool[] = [ { - name: "find", - label: "find", + name: "glob", + label: "glob", summary: "Find files matching a glob pattern", source: "builtin", schemaKeys: ["pattern", "path"], @@ -30,7 +30,7 @@ describe("generic index: DiscoverableTool round-trip", () => { expect(index.documents).toHaveLength(2); const findResults = searchDiscoverableTools(index, "find files", 3); - expect(findResults.some((r: any) => r.tool.name === "find")).toBe(true); + expect(findResults.some((r: any) => r.tool.name === "glob")).toBe(true); const ghResults = searchDiscoverableTools(index, "github search", 3); expect(ghResults.some((r: any) => r.tool.name === "mcp__gh_search")).toBe(true); diff --git a/packages/coding-agent/test/tool-discovery/tool-index.test.ts b/packages/coding-agent/test/tool-discovery/tool-index.test.ts index 920cd70ef..1b418d807 100644 --- a/packages/coding-agent/test/tool-discovery/tool-index.test.ts +++ b/packages/coding-agent/test/tool-discovery/tool-index.test.ts @@ -261,8 +261,8 @@ describe("BM25 search", () => { schemaKeys: ["channel", "text"], }, { - name: "find", - label: "find", + name: "glob", + label: "glob", summary: "Find files and directories matching a glob pattern", source: "builtin", schemaKeys: ["pattern", "path"], @@ -284,7 +284,7 @@ describe("BM25 search", () => { it("finds built-in tools too", () => { const results = searchDiscoverableTools(index, "find files", 5); - expect(results.some(r => r.tool.name === "find")).toBe(true); + expect(results.some(r => r.tool.name === "glob")).toBe(true); }); it("respects the limit", () => { diff --git a/packages/coding-agent/test/tools.test.ts b/packages/coding-agent/test/tools.test.ts index 7a4c2ec20..94a4e8eac 100644 --- a/packages/coding-agent/test/tools.test.ts +++ b/packages/coding-agent/test/tools.test.ts @@ -11,15 +11,15 @@ import { EditTool } from "@oh-my-pi/pi-coding-agent/edit"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { BashTool } from "@oh-my-pi/pi-coding-agent/tools/bash"; -import { FindTool } from "@oh-my-pi/pi-coding-agent/tools/find"; import { JobTool } from "@oh-my-pi/pi-coding-agent/tools/job"; import { wrapToolWithMetaNotice } from "@oh-my-pi/pi-coding-agent/tools/output-meta"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; -import { DEFAULT_FILE_LIMIT, MULTI_FILE_PER_FILE_MATCHES, SearchTool } from "@oh-my-pi/pi-coding-agent/tools/search"; import * as toolTimeouts from "@oh-my-pi/pi-coding-agent/tools/tool-timeouts"; import { WriteTool } from "@oh-my-pi/pi-coding-agent/tools/write"; import { unzip } from "@oh-my-pi/pi-coding-agent/utils/zip"; import { $which, removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; +import { GlobTool } from "../src/tools/glob"; +import { DEFAULT_FILE_LIMIT, GrepTool, MULTI_FILE_PER_FILE_MATCHES } from "../src/tools/grep"; // Helper to extract text from content blocks function getTextOutput(result: any): string { @@ -268,8 +268,8 @@ describe("Coding Agent Tools", () => { let writeTool: WriteTool; let editTool: EditTool; let bashTool: BashTool; - let searchTool: SearchTool; - let findTool: FindTool; + let searchTool: GrepTool; + let findTool: GlobTool; let originalEditVariant: string | undefined; beforeAll(async () => { @@ -294,8 +294,8 @@ describe("Coding Agent Tools", () => { writeTool = wrapToolWithMetaNotice(new WriteTool(session)); editTool = wrapToolWithMetaNotice(new EditTool(session)); bashTool = wrapToolWithMetaNotice(new BashTool(session)); - searchTool = wrapToolWithMetaNotice(new SearchTool(session)); - findTool = wrapToolWithMetaNotice(new FindTool(session)); + searchTool = wrapToolWithMetaNotice(new GrepTool(session)); + findTool = wrapToolWithMetaNotice(new GlobTool(session)); }); afterEach(() => { @@ -1691,9 +1691,9 @@ function b() { const content = ["before", "match one", "after", "middle", "match two", "after two"].join("\n"); fs.writeFileSync(testFile, content); - const contextSettings = Settings.isolated({ "search.contextBefore": 1, "search.contextAfter": 1 }); + const contextSettings = Settings.isolated({ "grep.contextBefore": 1, "grep.contextAfter": 1 }); const contextSearchTool = wrapToolWithMetaNotice( - new SearchTool(createTestToolSession(testDir, contextSettings)), + new GrepTool(createTestToolSession(testDir, contextSettings)), ); const result = await contextSearchTool.execute("test-call-12", { pattern: "match", @@ -1713,9 +1713,9 @@ function b() { const lines = Array.from({ length: 10 }, (_, idx) => (idx === 0 || idx === 5 ? "match" : `filler ${idx}`)); fs.writeFileSync(testFile, lines.join("\n")); - const noContextSettings = Settings.isolated({ "search.contextBefore": 0, "search.contextAfter": 0 }); + const noContextSettings = Settings.isolated({ "grep.contextBefore": 0, "grep.contextAfter": 0 }); const noContextSearchTool = wrapToolWithMetaNotice( - new SearchTool(createTestToolSession(testDir, noContextSettings)), + new GrepTool(createTestToolSession(testDir, noContextSettings)), ); const result = await noContextSearchTool.execute("test-call-12-gap", { pattern: "match", diff --git a/packages/coding-agent/test/tools/approval.test.ts b/packages/coding-agent/test/tools/approval.test.ts index 65a6e4d8b..ae6581e40 100644 --- a/packages/coding-agent/test/tools/approval.test.ts +++ b/packages/coding-agent/test/tools/approval.test.ts @@ -29,8 +29,8 @@ function createBashTool(): BashTool { case "bash.autoBackground.enabled": case "astGrep.enabled": case "astEdit.enabled": - case "search.enabled": - case "find.enabled": + case "grep.enabled": + case "glob.enabled": return false; case "bash.autoBackground.thresholdMs": return 60_000; diff --git a/packages/coding-agent/test/tools/find-renderer.test.ts b/packages/coding-agent/test/tools/glob-renderer.test.ts similarity index 80% rename from packages/coding-agent/test/tools/find-renderer.test.ts rename to packages/coding-agent/test/tools/glob-renderer.test.ts index 2ef48e353..1b804529a 100644 --- a/packages/coding-agent/test/tools/find-renderer.test.ts +++ b/packages/coding-agent/test/tools/glob-renderer.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from "bun:test"; import { getThemeByName } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; -import { findToolRenderer } from "@oh-my-pi/pi-coding-agent/tools/find"; import { sanitizeText } from "@oh-my-pi/pi-utils"; +import { globToolRenderer } from "../../src/tools/glob"; -describe("findToolRenderer", () => { - it("indents inline find output and avoids accent-colored success headers", async () => { +describe("globToolRenderer", () => { + it("indents inline glob output and avoids accent-colored success headers", async () => { const theme = await getThemeByName("dark"); expect(theme).toBeDefined(); const uiTheme = theme!; @@ -16,7 +16,7 @@ describe("findToolRenderer", () => { }, }; - const renderedLines = findToolRenderer + const renderedLines = globToolRenderer .renderResult(result as never, { expanded: true, isPartial: false }, uiTheme, { paths: "src/**/*.ts" }) .render(240); const plainLines = sanitizeText(renderedLines.join("\n")).split("\n"); diff --git a/packages/coding-agent/test/tools/find-validate-paths.test.ts b/packages/coding-agent/test/tools/glob-validate-paths.test.ts similarity index 95% rename from packages/coding-agent/test/tools/find-validate-paths.test.ts rename to packages/coding-agent/test/tools/glob-validate-paths.test.ts index 7590b0c7b..0fe58c82d 100644 --- a/packages/coding-agent/test/tools/find-validate-paths.test.ts +++ b/packages/coding-agent/test/tools/glob-validate-paths.test.ts @@ -4,7 +4,6 @@ import * as os from "node:os"; import * as path from "node:path"; import type { RenderResultOptions } from "@oh-my-pi/pi-coding-agent/extensibility/custom-tools/types"; import { getThemeByName, initTheme, type Theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; -import { findToolRenderer } from "@oh-my-pi/pi-coding-agent/tools/find"; import { expandDelimitedPathEntries, parseFindPattern, @@ -12,6 +11,7 @@ import { splitDelimitedPathEntry, } from "@oh-my-pi/pi-coding-agent/tools/path-utils"; import type { Component } from "@oh-my-pi/pi-tui"; +import { globToolRenderer } from "../../src/tools/glob"; let uiTheme: Theme; @@ -133,24 +133,24 @@ describe("delimited path expansion", () => { }); }); -describe("findToolRenderer", () => { +describe("globToolRenderer", () => { it("accepts a single string paths value before validation", async () => { const args = { paths: "src/**/*.ts" }; const renderings = [ - findToolRenderer.renderCall(args, renderOptions, uiTheme), - findToolRenderer.renderResult( + globToolRenderer.renderCall(args, renderOptions, uiTheme), + globToolRenderer.renderResult( { content: [{ type: "text", text: "src/index.ts\n" }] }, renderOptions, uiTheme, args, ), - findToolRenderer.renderResult( + globToolRenderer.renderResult( { content: [{ type: "text", text: "" }], details: { fileCount: 0, files: [] } }, renderOptions, uiTheme, args, ), - findToolRenderer.renderResult( + globToolRenderer.renderResult( { content: [{ type: "text", text: "src/index.ts" }], details: { fileCount: 1, files: ["src/index.ts"] } }, renderOptions, uiTheme, diff --git a/packages/coding-agent/test/tools/search-internal-urls.test.ts b/packages/coding-agent/test/tools/grep-internal-urls.test.ts similarity index 91% rename from packages/coding-agent/test/tools/search-internal-urls.test.ts rename to packages/coding-agent/test/tools/grep-internal-urls.test.ts index 957ca1999..7cfa1ffd3 100644 --- a/packages/coding-agent/test/tools/search-internal-urls.test.ts +++ b/packages/coding-agent/test/tools/grep-internal-urls.test.ts @@ -13,9 +13,9 @@ import { } from "@oh-my-pi/pi-coding-agent/internal-urls"; import { AgentRegistry } from "@oh-my-pi/pi-coding-agent/registry/agent-registry"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; -import { FindTool } from "@oh-my-pi/pi-coding-agent/tools/find"; import { ReadTool } from "@oh-my-pi/pi-coding-agent/tools/read"; -import { SearchTool } from "@oh-my-pi/pi-coding-agent/tools/search"; +import { GlobTool } from "../../src/tools/glob"; +import { GrepTool } from "../../src/tools/grep"; function getResultText(result: { content: Array<{ type: string; text?: string }> }): string { return result.content @@ -62,7 +62,7 @@ function registerVirtualDocs(docs: ReadonlyMap): void { InternalUrlRouter.instance().register(handler); } -describe("SearchTool internal URL resolution", () => { +describe("GrepTool internal URL resolution", () => { let tmpDir: string; let artifactsDir: string; @@ -100,7 +100,7 @@ describe("SearchTool internal URL resolution", () => { hasUI: false, getSessionFile: () => null, getSessionSpawns: () => "*", - settings: Settings.isolated({ "search.contextBefore": 0, "search.contextAfter": 0 }), + settings: Settings.isolated({ "grep.contextBefore": 0, "grep.contextAfter": 0 }), ...overrides, }; } @@ -160,8 +160,8 @@ describe("SearchTool internal URL resolution", () => { it("walks skill:// directory subpaths for search and find", async () => { await registerSkillDirectory(); const session = createSession({ hasEditTool: true }); - const searchTool = new SearchTool(session); - const findTool = new FindTool(session); + const searchTool = new GrepTool(session); + const findTool = new GlobTool(session); const searchResult = await searchTool.execute("test-search", { pattern: "deep needle", @@ -182,7 +182,7 @@ describe("SearchTool internal URL resolution", () => { await Bun.write(path.join(artifactsDir, "5.bash.log"), content); const session = createSession(); - const tool = new SearchTool(session); + const tool = new GrepTool(session); const result = await tool.execute("test-call", { pattern: "needle", @@ -198,7 +198,7 @@ describe("SearchTool internal URL resolution", () => { await Bun.write(path.join(artifactsDir, "3.python.log"), content); const session = createSession(); - const tool = new SearchTool(session); + const tool = new GrepTool(session); const result = await tool.execute("test-call", { pattern: "ERROR.*", @@ -216,7 +216,7 @@ describe("SearchTool internal URL resolution", () => { registerVirtualDocs(new Map([["doc.md", "alpha line\nneedle in virtual content\ngamma line\n"]])); const session = createSession(); - const tool = new SearchTool(session); + const tool = new GrepTool(session); const result = await tool.execute("test-call", { pattern: "needle", @@ -232,7 +232,7 @@ describe("SearchTool internal URL resolution", () => { registerVirtualDocs(new Map([["doc.md", "needle outside range\nmiddle line\nneedle inside range\n"]])); const session = createSession(); - const tool = new SearchTool(session); + const tool = new GrepTool(session); const result = await tool.execute("test-call", { pattern: "needle", @@ -246,21 +246,21 @@ describe("SearchTool internal URL resolution", () => { it("expands omp:// root to grep embedded documentation files", async () => { const session = createSession(); - const tool = new SearchTool(session); + const tool = new GrepTool(session); const result = await tool.execute("test-call", { - pattern: "Search file contents with a regex across files", + pattern: "Greps files using regex.", paths: ["omp://"], }); const text = getResultText(result); - expect(text).toContain("# omp://tools/search.md"); - expect(text).toContain("Search file contents with a regex across files"); + expect(text).toContain("# omp://tools/grep.md"); + expect(text).toContain("Greps files using regex."); }); it("expands omp://docs to grep embedded documentation files", async () => { const session = createSession(); - const tool = new SearchTool(session); + const tool = new GrepTool(session); const result = await tool.execute("test-call", { pattern: "Read files, directories, archives", @@ -274,7 +274,7 @@ describe("SearchTool internal URL resolution", () => { it("throws when internal URL has no sourcePath", async () => { const session = createSession(); - const tool = new SearchTool(session); + const tool = new GrepTool(session); expect(tool.execute("test-call", { pattern: "foo", paths: ["artifact://999"] })).rejects.toThrow( "Artifact 999 not found", @@ -285,7 +285,7 @@ describe("SearchTool internal URL resolution", () => { await Bun.write(path.join(tmpDir, "test.txt"), "hello world\n"); const session = createSession(); - const tool = new SearchTool(session); + const tool = new GrepTool(session); const result = await tool.execute("test-call", { pattern: "hello", @@ -300,7 +300,7 @@ describe("SearchTool internal URL resolution", () => { await Bun.write(path.join(tmpDir, "data.log"), "some data here\n"); const session = createSession(); - const tool = new SearchTool(session); + const tool = new GrepTool(session); const result = await tool.execute("test-call", { pattern: "data", @@ -316,7 +316,7 @@ describe("SearchTool internal URL resolution", () => { await Bun.write(path.join(artifactsDir, "9.bash.log"), content); const session = createSession({ hasEditTool: true }); - const tool = new SearchTool(session); + const tool = new GrepTool(session); const result = await tool.execute("test-call", { pattern: "needle", @@ -338,7 +338,7 @@ describe("SearchTool internal URL resolution", () => { LocalProtocolHandler.setOverride({ getArtifactsDir: () => artifactsDir, getSessionId: () => "session" }); const session = createSession(); - const tool = new FindTool(session); + const tool = new GlobTool(session); const result = await tool.execute("test-call", { paths: ["local://PLAN.md"], @@ -357,7 +357,7 @@ describe("SearchTool internal URL resolution", () => { const session = createSession({ hasEditTool: true }); const readResult = await new ReadTool(session).execute("test-read", { path: "local://notes" }); - const findResult = await new FindTool(session).execute("test-find", { + const findResult = await new GlobTool(session).execute("test-find", { paths: ["local://notes"], }); const dirResource = await InternalUrlRouter.instance().resolve("local://notes"); @@ -378,7 +378,7 @@ describe("SearchTool internal URL resolution", () => { LocalProtocolHandler.setOverride({ getArtifactsDir: () => artifactsDir, getSessionId: () => "session" }); const session = createSession({ hasEditTool: true }); - const tool = new SearchTool(session); + const tool = new GrepTool(session); const result = await tool.execute("test-call", { pattern: "needle", @@ -398,7 +398,7 @@ describe("SearchTool internal URL resolution", () => { await Bun.write(path.join(tmpDir, "mixed.txt"), "mixed needle line\n"); const session = createSession({ hasEditTool: true }); - const tool = new SearchTool(session); + const tool = new GrepTool(session); const result = await tool.execute("test-call", { pattern: "needle", @@ -414,7 +414,7 @@ describe("SearchTool internal URL resolution", () => { it("throws on nonexistent artifact ID", async () => { const session = createSession(); - const tool = new SearchTool(session); + const tool = new GrepTool(session); expect(tool.execute("test-call", { pattern: "foo", paths: ["artifact://999"] })).rejects.toThrow( "Artifact 999 not found", @@ -425,9 +425,9 @@ describe("SearchTool internal URL resolution", () => { registerVirtualDocs(new Map([["doc.md", "l1\nneedle a\nl3\nneedle b\nl5\nl6\nl7\nl8\n"]])); const session = createSession({ - settings: Settings.isolated({ "search.contextBefore": 1, "search.contextAfter": 3 }), + settings: Settings.isolated({ "grep.contextBefore": 1, "grep.contextAfter": 3 }), }); - const tool = new SearchTool(session); + const tool = new GrepTool(session); const result = await tool.execute("test-call", { pattern: "needle", @@ -453,7 +453,7 @@ describe("SearchTool internal URL resolution", () => { await Bun.write(path.join(tmpDir, "b.txt"), "needle in b\n"); const session = createSession(); - const tool = new SearchTool(session); + const tool = new GrepTool(session); const result = await tool.execute("test-call", { pattern: "needle", diff --git a/packages/coding-agent/test/tools/search-invalid-regex.test.ts b/packages/coding-agent/test/tools/grep-invalid-regex.test.ts similarity index 90% rename from packages/coding-agent/test/tools/search-invalid-regex.test.ts rename to packages/coding-agent/test/tools/grep-invalid-regex.test.ts index 001d74010..a139d82bd 100644 --- a/packages/coding-agent/test/tools/search-invalid-regex.test.ts +++ b/packages/coding-agent/test/tools/grep-invalid-regex.test.ts @@ -3,7 +3,7 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; -import { SearchTool, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; +import { GrepTool, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { ToolError } from "@oh-my-pi/pi-coding-agent/tools/tool-errors"; function createTestSession(cwd: string, overrides: Partial = {}): ToolSession { @@ -30,7 +30,7 @@ describe("search tool invalid regex handling", () => { }); it("wraps invalid regex pattern errors in a ToolError", async () => { - const tool = new SearchTool(createTestSession(cwd)); + const tool = new GrepTool(createTestSession(cwd)); let caught: unknown; try { diff --git a/packages/coding-agent/test/tools/search-path-lists.test.ts b/packages/coding-agent/test/tools/grep-path-lists.test.ts similarity index 91% rename from packages/coding-agent/test/tools/search-path-lists.test.ts rename to packages/coding-agent/test/tools/grep-path-lists.test.ts index f618da3c3..fa0348134 100644 --- a/packages/coding-agent/test/tools/search-path-lists.test.ts +++ b/packages/coding-agent/test/tools/grep-path-lists.test.ts @@ -19,8 +19,8 @@ import { AgentRegistry } from "@oh-my-pi/pi-coding-agent/registry/agent-registry import type { SessionEntry, SessionTreeNode } from "@oh-my-pi/pi-coding-agent/session/session-entries"; import { ToolChoiceQueue } from "@oh-my-pi/pi-coding-agent/session/tool-choice-queue"; import { createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; -import { searchToolRenderer } from "@oh-my-pi/pi-coding-agent/tools/search"; import { Text } from "@oh-my-pi/pi-tui"; +import { grepToolRenderer } from "../../src/tools/grep"; function createTestSession(cwd: string, overrides: Partial = {}): ToolSession { return { @@ -143,9 +143,9 @@ describe("tool path arrays", () => { it("search accepts explicit path arrays", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "search"); + const tool = tools.find(entry => entry.name === "grep"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing search tool"); + if (!tool) throw new Error("Missing grep tool"); const result = await tool.execute("search-path-array", { pattern: "shared-needle", @@ -165,9 +165,9 @@ describe("tool path arrays", () => { it("search expands delimited path entries", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "search"); + const tool = tools.find(entry => entry.name === "grep"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing search tool"); + if (!tool) throw new Error("Missing grep tool"); for (const [name, entry] of [ ["comma", "apps/grep.txt, packages/grep.txt"], @@ -192,9 +192,9 @@ describe("tool path arrays", () => { it("search keeps comma-delimited surviving entries when peers are missing", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "search"); + const tool = tools.find(entry => entry.name === "grep"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing search tool"); + if (!tool) throw new Error("Missing grep tool"); const result = await tool.execute("search-delimited-missing", { pattern: "shared-needle", @@ -213,9 +213,9 @@ describe("tool path arrays", () => { it("records hashline snapshots for matched files", async () => { const session = createTestSession(tempDir); const tools = await createTools(session); - const tool = tools.find(entry => entry.name === "search"); + const tool = tools.find(entry => entry.name === "grep"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing search tool"); + if (!tool) throw new Error("Missing grep tool"); const result = await tool.execute("search-records-snapshot", { pattern: "shared-needle", @@ -235,9 +235,9 @@ describe("tool path arrays", () => { it("search accepts a single string path through tool validation", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "search"); + const tool = tools.find(entry => entry.name === "grep"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing search tool"); + if (!tool) throw new Error("Missing grep tool"); const args = validateToolArguments(tool, { type: "toolCall", @@ -265,8 +265,8 @@ describe("tool path arrays", () => { await Bun.write(path.join(tmp, "apps", "[id]", "page.tsx"), "bracket-needle\n"); const tools = await createTools(createTestSession(tmp)); - const tool = tools.find(entry => entry.name === "search"); - if (!tool) throw new Error("Missing search tool"); + const tool = tools.find(entry => entry.name === "grep"); + if (!tool) throw new Error("Missing grep tool"); const single = await tool.execute("search-bracket-literal-single", { pattern: "bracket-needle", @@ -282,8 +282,8 @@ describe("tool path arrays", () => { await fs.rm(tmp, { recursive: true, force: true }); }); - it("search pending renderer accepts a single string path", () => { - const component = searchToolRenderer.renderCall( + it("grep pending renderer accepts a single string path", () => { + const component = grepToolRenderer.renderCall( { pattern: "space-needle", paths: "folder with spaces/" }, renderOptions, plainTheme, @@ -292,7 +292,7 @@ describe("tool path arrays", () => { expect(component).toBeInstanceOf(Text); expect((component as Text).getText()).toContain("in folder with spaces/"); }); - it("agent hub chat renders a single-string search path summary", async () => { + it("agent hub chat renders a single-string grep path summary", async () => { const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "search-path-lists-")); const sessionFile = await makeJsonlSessionFile(tmp, [ { type: "session", version: 3, id: "search-overlay-session", timestamp: new Date().toISOString() }, @@ -301,7 +301,7 @@ describe("tool path arrays", () => { id: "msg-user-1", parentId: null, timestamp: new Date().toISOString(), - message: { role: "user", content: "search", timestamp: 1 }, + message: { role: "user", content: "grep", timestamp: 1 }, }, { type: "message", @@ -314,7 +314,7 @@ describe("tool path arrays", () => { { type: "toolCall", id: "search-call-1", - name: "search", + name: "grep", arguments: { pattern: "space-needle", paths: "folder with spaces/" }, }, ], @@ -369,15 +369,15 @@ describe("tool path arrays", () => { const rendered = Bun.stripANSI(viewer.render(120).join("\n")); viewer.dispose(); - // The hub chat now renders through searchToolRenderer.renderCall; the + // The hub chat now renders through grepToolRenderer.renderCall; the // single-string `paths` arg shows up as the "in " scope meta on the // pending call line (a completed result merges the call line away). expect(rendered).toContain("in folder with spaces/"); await fs.rm(tmp, { recursive: true, force: true }); }); - it("tree selector renders a single-string search path summary", () => { - const root = makeMessageNode({ role: "user", content: "search", timestamp: 1 }); + it("tree selector renders a single-string grep path summary", () => { + const root = makeMessageNode({ role: "user", content: "grep", timestamp: 1 }); const assistant = makeMessageNode( { role: "assistant", @@ -385,7 +385,7 @@ describe("tool path arrays", () => { { type: "toolCall", id: "search-call-1", - name: "search", + name: "grep", arguments: { pattern: "space-needle", paths: "folder with spaces/" }, }, ], @@ -409,7 +409,7 @@ describe("tool path arrays", () => { { role: "toolResult", toolCallId: "search-call-1", - toolName: "search", + toolName: "grep", content: [{ type: "text", text: "note.txt" }], isError: false, timestamp: 3, @@ -427,9 +427,9 @@ describe("tool path arrays", () => { it("search keeps a single path that contains spaces", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "search"); + const tool = tools.find(entry => entry.name === "grep"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing search tool"); + if (!tool) throw new Error("Missing grep tool"); const result = await tool.execute("search-space-directory", { pattern: "space-needle", @@ -445,9 +445,9 @@ describe("tool path arrays", () => { it("search accepts quoted directory paths", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "search"); + const tool = tools.find(entry => entry.name === "grep"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing search tool"); + if (!tool) throw new Error("Missing grep tool"); const result = await tool.execute("search-quoted-path", { pattern: "shared-needle", @@ -464,9 +464,9 @@ describe("tool path arrays", () => { it("search formats absolute in-cwd paths relative to cwd", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "search"); + const tool = tools.find(entry => entry.name === "grep"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing search tool"); + if (!tool) throw new Error("Missing grep tool"); const absoluteAppsPath = path.join(tempDir, "apps"); const result = await tool.execute("search-absolute-in-cwd", { @@ -653,9 +653,9 @@ describe("tool path arrays", () => { it("find accepts explicit path arrays", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "find"); + const tool = tools.find(entry => entry.name === "glob"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing find tool"); + if (!tool) throw new Error("Missing glob tool"); const result = await tool.execute("find-path-array", { paths: ["apps/", "packages/", "phases/"], @@ -683,9 +683,9 @@ describe("tool path arrays", () => { it("find expands delimited path entries", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "find"); + const tool = tools.find(entry => entry.name === "glob"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing find tool"); + if (!tool) throw new Error("Missing glob tool"); for (const [name, entry] of [ ["comma", "apps/grep.txt, packages/grep.txt"], @@ -710,9 +710,9 @@ describe("tool path arrays", () => { it("find keeps comma-delimited surviving entries when peers are missing", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "find"); + const tool = tools.find(entry => entry.name === "glob"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing find tool"); + if (!tool) throw new Error("Missing glob tool"); const result = await tool.execute("find-delimited-missing", { paths: ["missing.txt, packages/grep.txt"], @@ -730,9 +730,9 @@ describe("tool path arrays", () => { it("find keeps a single path that contains spaces", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "find"); + const tool = tools.find(entry => entry.name === "glob"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing find tool"); + if (!tool) throw new Error("Missing glob tool"); const result = await tool.execute("find-space-directory", { paths: ["folder with spaces/"], @@ -748,9 +748,9 @@ describe("tool path arrays", () => { it("find accepts quoted directory patterns", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "find"); + const tool = tools.find(entry => entry.name === "glob"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing find tool"); + if (!tool) throw new Error("Missing glob tool"); const result = await tool.execute("find-quoted-pattern", { paths: ['"packages/"'], @@ -770,9 +770,9 @@ describe("tool path arrays", () => { try { await Bun.write(path.join(outsideDir, "outside.txt"), "outside\n"); const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "find"); + const tool = tools.find(entry => entry.name === "glob"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing find tool"); + if (!tool) throw new Error("Missing glob tool"); const result = await tool.execute("find-outside-cwd", { paths: [outsideDir], @@ -793,9 +793,9 @@ describe("tool path arrays", () => { it("grep accepts bare directory name arrays", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "search"); + const tool = tools.find(entry => entry.name === "grep"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing search tool"); + if (!tool) throw new Error("Missing grep tool"); const result = await tool.execute("grep-bare-path-array", { pattern: "shared-needle", @@ -821,9 +821,9 @@ describe("tool path arrays", () => { await Bun.write(path.join(tmp, "nested", "beta.txt"), "exact-needle nested beta\n"); const tools = await createTools(createTestSession(tmp)); - const tool = tools.find(entry => entry.name === "search"); + const tool = tools.find(entry => entry.name === "grep"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing search tool"); + if (!tool) throw new Error("Missing grep tool"); const result = await tool.execute("grep-exact-file-array", { pattern: "exact-needle", @@ -844,9 +844,9 @@ describe("tool path arrays", () => { it("grep renders only file headings that have child lines", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "search"); + const tool = tools.find(entry => entry.name === "grep"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing search tool"); + if (!tool) throw new Error("Missing grep tool"); const result = await tool.execute("grep-no-empty-headings", { pattern: "shared-needle", @@ -872,12 +872,12 @@ describe("tool path arrays", () => { const tools = await createTools( createTestSession(tmp, { - settings: Settings.isolated({ "search.contextBefore": 1, "search.contextAfter": 1 }), + settings: Settings.isolated({ "grep.contextBefore": 1, "grep.contextAfter": 1 }), }), ); - const tool = tools.find(entry => entry.name === "search"); + const tool = tools.find(entry => entry.name === "grep"); expect(tool).toBeDefined(); - if (!tool) throw new Error("Missing search tool"); + if (!tool) throw new Error("Missing grep tool"); const result = await tool.execute("grep-context-label", { pattern: "needle", diff --git a/packages/coding-agent/test/tools/search-renderer.test.ts b/packages/coding-agent/test/tools/grep-renderer.test.ts similarity index 91% rename from packages/coding-agent/test/tools/search-renderer.test.ts rename to packages/coding-agent/test/tools/grep-renderer.test.ts index 10b3bbac6..2b40b91a7 100644 --- a/packages/coding-agent/test/tools/search-renderer.test.ts +++ b/packages/coding-agent/test/tools/grep-renderer.test.ts @@ -3,8 +3,8 @@ import * as path from "node:path"; import * as url from "node:url"; import { resetSettingsForTest, Settings, settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { getThemeByName } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; -import { searchToolRenderer } from "@oh-my-pi/pi-coding-agent/tools/search"; import { sanitizeText } from "@oh-my-pi/pi-utils"; +import { grepToolRenderer } from "../../src/tools/grep"; function extractLinkUris(text: string): string[] { return [...text.matchAll(/\x1b\]8;[^;]*;([^\x1b]+)\x1b\\/g)].map(match => match[1]!); @@ -23,8 +23,8 @@ afterAll(() => { resetSettingsForTest(); }); -describe("searchToolRenderer", () => { - it("indents inline search output and avoids accent-colored success headers", async () => { +describe("grepToolRenderer", () => { + it("indents inline grep output and avoids accent-colored success headers", async () => { const theme = await getThemeByName("dark"); expect(theme).toBeDefined(); const uiTheme = theme!; @@ -37,7 +37,7 @@ describe("searchToolRenderer", () => { }, }; - const renderedLines = searchToolRenderer + const renderedLines = grepToolRenderer .renderResult(result as never, { expanded: true, isPartial: false }, uiTheme, { pattern: "needle" }) .render(240); const plainLines = sanitizeText(renderedLines.join("\n")).split("\n"); @@ -68,14 +68,9 @@ describe("searchToolRenderer", () => { }, }; - const collapsed = searchToolRenderer.renderResult( - result as never, - { expanded: false, isPartial: false }, - uiTheme, - { - pattern: "needle", - }, - ); + const collapsed = grepToolRenderer.renderResult(result as never, { expanded: false, isPartial: false }, uiTheme, { + pattern: "needle", + }); const renderedLines = sanitizeText(collapsed.render(200).join("\n")).split("\n"); const bodyLines = renderedLines.slice(1); @@ -111,12 +106,9 @@ describe("searchToolRenderer", () => { }, }; - const collapsed = searchToolRenderer.renderResult( - result as never, - { expanded: false, isPartial: false }, - uiTheme, - { pattern: "Flag" }, - ); + const collapsed = grepToolRenderer.renderResult(result as never, { expanded: false, isPartial: false }, uiTheme, { + pattern: "Flag", + }); const renderedLines = sanitizeText(collapsed.render(240).join("\n")).split("\n"); const bodyLines = renderedLines.slice(1); @@ -147,7 +139,7 @@ describe("searchToolRenderer", () => { }, }; - const rendered = searchToolRenderer + const rendered = grepToolRenderer .renderResult(result as never, { expanded: true, isPartial: false }, uiTheme, { pattern: "needle" }) .render(240) .join("\n"); @@ -178,7 +170,7 @@ describe("searchToolRenderer", () => { }, }; - const rendered = searchToolRenderer + const rendered = grepToolRenderer .renderResult(result as never, { expanded: true, isPartial: false }, uiTheme, { pattern: "needle" }) .render(240) .join("\n"); @@ -218,7 +210,7 @@ describe("searchToolRenderer", () => { const render = (expanded: boolean) => sanitizeText( - searchToolRenderer + grepToolRenderer .renderResult(result as never, { expanded, isPartial: false }, uiTheme, { pattern: "needle" }) .render(200) .join("\n"), diff --git a/packages/coding-agent/test/tools/index.test.ts b/packages/coding-agent/test/tools/index.test.ts index 537b4223b..eae16b79c 100644 --- a/packages/coding-agent/test/tools/index.test.ts +++ b/packages/coding-agent/test/tools/index.test.ts @@ -75,8 +75,8 @@ describe("createTools", () => { expect(names).toContain("read"); expect(names).toContain("edit"); expect(names).toContain("write"); - expect(names).toContain("search"); - expect(names).toContain("find"); + expect(names).toContain("grep"); + expect(names).toContain("glob"); expect(names).toContain("lsp"); expect(names).toContain("task"); expect(names).toContain("todo"); @@ -86,6 +86,20 @@ describe("createTools", () => { expect(names).not.toContain("vim"); }); + it("normalizes legacy explicit tool names", async () => { + const session = createTestSession({ + settings: createSettingsWithOverrides({ "astGrep.enabled": false }), + }); + const tools = await createTools(session, ["search", "find", "grep"]); + const names = tools.map(t => t.name); + + expect(names.filter(name => name === "grep")).toHaveLength(1); + expect(names).toContain("glob"); + expect(names).toContain("resolve"); + expect(names).not.toContain("search"); + expect(names).not.toContain("find"); + }); + it("includes bash and eval when both eval backends are allowed", async () => { const session = createTestSession({ settings: createSettingsWithOverrides({ @@ -197,8 +211,8 @@ describe("createTools", () => { it("filters disabled builtin tools by settings", async () => { const session = createTestSession({ settings: createSettingsWithOverrides({ - "find.enabled": false, - "search.enabled": false, + "glob.enabled": false, + "grep.enabled": false, "astGrep.enabled": false, "astEdit.enabled": false, "bash.enabled": false, @@ -211,8 +225,8 @@ describe("createTools", () => { const names = tools.map(t => t.name); expect(names).not.toContain("bash"); - expect(names).not.toContain("find"); - expect(names).not.toContain("search"); + expect(names).not.toContain("glob"); + expect(names).not.toContain("grep"); expect(names).not.toContain("ast_grep"); expect(names).not.toContain("ast_edit"); expect(names).not.toContain("web_search"); diff --git a/packages/coding-agent/test/tools/multi-search-path.test.ts b/packages/coding-agent/test/tools/multi-grep-path.test.ts similarity index 94% rename from packages/coding-agent/test/tools/multi-search-path.test.ts rename to packages/coding-agent/test/tools/multi-grep-path.test.ts index d7648185b..ca3e6a2a6 100644 --- a/packages/coding-agent/test/tools/multi-search-path.test.ts +++ b/packages/coding-agent/test/tools/multi-grep-path.test.ts @@ -58,8 +58,8 @@ describe.skipIf(isWindows)("search with omitted paths", () => { it("defaults to the workspace root when paths is omitted", async () => { const tools = await createTools(createTestSession(cwd)); - const tool = tools.find(entry => entry.name === "search"); - if (!tool) throw new Error("Missing search tool"); + const tool = tools.find(entry => entry.name === "grep"); + if (!tool) throw new Error("Missing grep tool"); // Callers that omit `paths` would otherwise be rejected at schema // validation with `paths: Invalid input` and never run. Omission must @@ -74,8 +74,8 @@ describe.skipIf(isWindows)("search with omitted paths", () => { it("defaults to the workspace root when paths is an empty array", async () => { const tools = await createTools(createTestSession(cwd)); - const tool = tools.find(entry => entry.name === "search"); - if (!tool) throw new Error("Missing search tool"); + const tool = tools.find(entry => entry.name === "grep"); + if (!tool) throw new Error("Missing grep tool"); const result = await tool.execute("search-empty-paths", { pattern: "default-needle", @@ -112,8 +112,8 @@ describe.skipIf(isWindows)("search across unrelated filesystem trees", () => { it("returns matches from both trees without rooting the scan at /", async () => { const tools = await createTools(createTestSession(cwd)); - const tool = tools.find(entry => entry.name === "search"); - if (!tool) throw new Error("Missing search tool"); + const tool = tools.find(entry => entry.name === "grep"); + if (!tool) throw new Error("Missing grep tool"); const start = performance.now(); const result = await tool.execute("search-cross-tree", { @@ -206,8 +206,8 @@ describe.skipIf(isWindows)("search with explicit walker-pruned file targets", () // explicit file into the walk's glob union silently returned 0 matches. // The file must be read directly as its own target. const tools = await createTools(createTestSession(repo)); - const tool = tools.find(entry => entry.name === "search"); - if (!tool) throw new Error("Missing search tool"); + const tool = tools.find(entry => entry.name === "grep"); + if (!tool) throw new Error("Missing grep tool"); const result = await tool.execute("search-git-config", { pattern: "followTags", @@ -223,8 +223,8 @@ describe.skipIf(isWindows)("search with explicit walker-pruned file targets", () await fs.mkdir(path.join(repo, "src"), { recursive: true }); await Bun.write(path.join(repo, "src", "a.ts"), "needle-dup\n"); const tools = await createTools(createTestSession(repo)); - const tool = tools.find(entry => entry.name === "search"); - if (!tool) throw new Error("Missing search tool"); + const tool = tools.find(entry => entry.name === "grep"); + if (!tool) throw new Error("Missing grep tool"); const result = await tool.execute("search-overlap", { pattern: "needle-dup", diff --git a/packages/coding-agent/test/tools/multi-path-missing.test.ts b/packages/coding-agent/test/tools/multi-path-missing.test.ts index ab7d3ad10..67450de2e 100644 --- a/packages/coding-agent/test/tools/multi-path-missing.test.ts +++ b/packages/coding-agent/test/tools/multi-path-missing.test.ts @@ -44,8 +44,8 @@ describe("multi-path tools tolerate missing entries", () => { it("search returns matches from existing paths and reports the missing one", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "search"); - if (!tool) throw new Error("Missing search tool"); + const tool = tools.find(entry => entry.name === "grep"); + if (!tool) throw new Error("Missing grep tool"); const result = await tool.execute("search-multi-missing", { pattern: "shared-needle", @@ -64,8 +64,8 @@ describe("multi-path tools tolerate missing entries", () => { it("search errors only when every path is missing", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "search"); - if (!tool) throw new Error("Missing search tool"); + const tool = tools.find(entry => entry.name === "grep"); + if (!tool) throw new Error("Missing grep tool"); const promise = tool.execute("search-all-missing", { pattern: "shared-needle", @@ -77,8 +77,8 @@ describe("multi-path tools tolerate missing entries", () => { it("find returns matches from existing globs and reports the missing one", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "find"); - if (!tool) throw new Error("Missing find tool"); + const tool = tools.find(entry => entry.name === "glob"); + if (!tool) throw new Error("Missing glob tool"); const result = await tool.execute("find-multi-missing", { paths: ["src/**/*.ts", "tests/**/*.ts"], @@ -98,8 +98,8 @@ describe("multi-path tools tolerate missing entries", () => { it("find errors only when every glob's base directory is missing", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "find"); - if (!tool) throw new Error("Missing find tool"); + const tool = tools.find(entry => entry.name === "glob"); + if (!tool) throw new Error("Missing glob tool"); const promise = tool.execute("find-all-missing", { paths: ["nope/**/*.ts", "also-nope/**/*.ts"], diff --git a/packages/coding-agent/test/tools/report-tool-issue.test.ts b/packages/coding-agent/test/tools/report-tool-issue.test.ts index 08bbf92c9..0853b044f 100644 --- a/packages/coding-agent/test/tools/report-tool-issue.test.ts +++ b/packages/coding-agent/test/tools/report-tool-issue.test.ts @@ -102,7 +102,7 @@ describe("flushGrievances", () => { }); it("skips network when consent is missing and leaves rows intact", async () => { - insertGrievance(db, "find", "weird ordering"); + insertGrievance(db, "glob", "weird ordering"); const fetchSpy = vi.fn(async () => new Response("unexpected", { status: 200 })); // `denied` is the user-facing kill switch for push. @@ -116,7 +116,7 @@ describe("flushGrievances", () => { }); it("skips network when endpoint is missing", async () => { - insertGrievance(db, "find", "weird ordering"); + insertGrievance(db, "glob", "weird ordering"); const fetchSpy = vi.fn(async () => new Response("unexpected", { status: 200 })); const result = await flushGrievances(db, pushSettings({ "dev.autoqaPush.endpoint": "" }), { @@ -138,7 +138,7 @@ describe("flushGrievances", () => { }); it("posts pending rows with bearer header and marks them pushed=1 on 200", async () => { - insertGrievance(db, "find", "weird ordering"); + insertGrievance(db, "glob", "weird ordering"); insertGrievance(db, "read", "selector ignored"); let capturedInput: string | URL | Request | undefined; @@ -170,7 +170,7 @@ describe("flushGrievances", () => { expect(typeof body.arch).toBe("string"); expect(body.installId).toBe("11111111-2222-3333-4444-555555555555"); expect(body.entries).toEqual([ - { id: 1, model: "test-model", version: "test-version", tool: "find", report: "weird ordering" }, + { id: 1, model: "test-model", version: "test-version", tool: "glob", report: "weird ordering" }, { id: 2, model: "test-model", version: "test-version", tool: "read", report: "selector ignored" }, ]); @@ -182,7 +182,7 @@ describe("flushGrievances", () => { }); it("omits the Authorization header when no token is configured", async () => { - insertGrievance(db, "find", "no token here"); + insertGrievance(db, "glob", "no token here"); let capturedInit: RequestInit | undefined; const fetchSpy = vi.fn(async (_input: string | URL | Request, init: RequestInit | undefined) => { capturedInit = init; @@ -199,7 +199,7 @@ describe("flushGrievances", () => { }); it("leaves rows unpushed on 5xx and reports failure", async () => { - insertGrievance(db, "find", "boom"); + insertGrievance(db, "glob", "boom"); const fetchSpy = vi.fn(async () => new Response("nope", { status: 500 })); const result = await flushGrievances(db, pushSettings(), { fetch: mockFetch(fetchSpy) }); @@ -211,7 +211,7 @@ describe("flushGrievances", () => { }); it("drains mid-flight inserts in a follow-up batch within the same loop", async () => { - insertGrievance(db, "find", "first"); + insertGrievance(db, "glob", "first"); const fetchEntered = Promise.withResolvers(); const releaseFirstFetch = Promise.withResolvers(); @@ -245,7 +245,7 @@ describe("flushGrievances", () => { }); it("collapses concurrent callers onto a single in-flight push", async () => { - insertGrievance(db, "find", "single-flight"); + insertGrievance(db, "glob", "single-flight"); const releaseFetch = Promise.withResolvers(); const fetchSpy = vi.fn(() => releaseFetch.promise); @@ -265,7 +265,7 @@ describe("flushGrievances", () => { }); it("skips the next push within the failure cooldown window", async () => { - insertGrievance(db, "find", "first"); + insertGrievance(db, "glob", "first"); const fetchSpy = vi.fn(async () => new Response("nope", { status: 500 })); const settings = pushSettings(); @@ -284,7 +284,7 @@ describe("flushGrievances", () => { // partial final one), exercising both the LIMIT semantics and the // "remainder smaller than batch" tail. const total = 127; - for (let i = 0; i < total; i++) insertGrievance(db, "find", `report-${i}`); + for (let i = 0; i < total; i++) insertGrievance(db, "glob", `report-${i}`); const seenBatchSizes: number[] = []; const fetchSpy = vi.fn(async (_input: string | URL | Request, init: RequestInit | undefined) => { @@ -309,7 +309,7 @@ describe("flushGrievances", () => { // rows stay flagged unpushed. const firstBatch = 50; const secondBatch = 10; - for (let i = 0; i < firstBatch + secondBatch; i++) insertGrievance(db, "find", `r-${i}`); + for (let i = 0; i < firstBatch + secondBatch; i++) insertGrievance(db, "glob", `r-${i}`); let call = 0; const fetchSpy = vi.fn(() => { diff --git a/packages/coding-agent/test/tools/root-path-alias.test.ts b/packages/coding-agent/test/tools/root-path-alias.test.ts index daf084bae..e40875a9b 100644 --- a/packages/coding-agent/test/tools/root-path-alias.test.ts +++ b/packages/coding-agent/test/tools/root-path-alias.test.ts @@ -64,7 +64,7 @@ describe("tool path root alias", () => { it("searches from cwd when path is slash", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "search"); + const tool = tools.find(entry => entry.name === "grep"); expect(tool).toBeDefined(); if (!tool) throw new Error("Missing search tool"); @@ -94,7 +94,7 @@ describe("tool path root alias", () => { it("finds from cwd when pattern is slash", async () => { const tools = await createTools(createTestSession(tempDir)); - const tool = tools.find(entry => entry.name === "find"); + const tool = tools.find(entry => entry.name === "glob"); expect(tool).toBeDefined(); if (!tool) throw new Error("Missing find tool"); diff --git a/packages/coding-agent/test/tools/search-tool-bm25.test.ts b/packages/coding-agent/test/tools/search-tool-bm25.test.ts index 9383fd3f0..510234aa2 100644 --- a/packages/coding-agent/test/tools/search-tool-bm25.test.ts +++ b/packages/coding-agent/test/tools/search-tool-bm25.test.ts @@ -194,8 +194,8 @@ describe("SearchToolBm25Tool", () => { it("discovers built-in tools when using the new tools.discoveryMode=all setting", async () => { const builtinTools: DiscoverableTool[] = [ - builtinTool("find", "Find files and directories matching a glob pattern"), - builtinTool("search", "Search file contents using ripgrep"), + builtinTool("glob", "Find files and directories matching a glob pattern"), + builtinTool("grep", "Search file contents using ripgrep"), ]; const allTools = [...discoverableTools, ...builtinTools]; const session = createSession(discoverableTools, { @@ -206,7 +206,7 @@ describe("SearchToolBm25Tool", () => { const result = await tool.execute("call-builtin", { query: "find files" }); const names = result.details?.tools.map(t => t.name) ?? []; - expect(names).toContain("find"); + expect(names).toContain("glob"); }); }); @@ -218,8 +218,8 @@ describe("renderSearchToolBm25Description", () => { it("lists discoverable built-in tool names alphabetically without leaking them into the MCP server line", () => { const rendered = renderSearchToolBm25Description([ builtinTool("write", "Create or overwrite a file"), - builtinTool("find", "Find files by name"), - builtinTool("search", "Search file contents"), + builtinTool("glob", "Find files by name"), + builtinTool("grep", "Search file contents"), mcpTool("mcp__github_create_issue", "github", "create_issue", "Create a GitHub issue", ["owner"]), mcpTool("mcp__slack_post_message", "slack", "post_message", "Post a message to Slack", ["channel"]), ]); @@ -233,7 +233,7 @@ describe("renderSearchToolBm25Description", () => { const mcpLine = lineWith(rendered, "Discoverable MCP servers"); expect(mcpLine).toBe("Discoverable MCP servers in this session: github (1 tool), slack (1 tool)."); expect(mcpLine).not.toContain("write"); - expect(mcpLine).not.toContain("find"); + expect(mcpLine).not.toContain("glob"); expect(rendered).toContain( "Discoverable MCP servers in this session: github (1 tool), slack (1 tool).\n" + "Discoverable built-in tools: find, search, write.\n" + @@ -255,7 +255,7 @@ describe("renderSearchToolBm25Description", () => { it("keeps built-ins counted in the total discoverable tools line", () => { const rendered = renderSearchToolBm25Description([ builtinTool("write", "Create or overwrite a file"), - builtinTool("find", "Find files by name"), + builtinTool("glob", "Find files by name"), mcpTool("mcp__slack_post_message", "slack", "post_message", "Post a message to Slack", ["channel"]), ]); diff --git a/packages/coding-agent/test/tools/task-agent-capabilities.test.ts b/packages/coding-agent/test/tools/task-agent-capabilities.test.ts index c187d83b4..6b71d100b 100644 --- a/packages/coding-agent/test/tools/task-agent-capabilities.test.ts +++ b/packages/coding-agent/test/tools/task-agent-capabilities.test.ts @@ -53,7 +53,7 @@ describe("task agent capability descriptions", () => { name: "read_scout", description: "Read-only scout", systemPrompt: "Scout the codebase.", - tools: ["read", "search", "find"], + tools: ["read", "grep", "glob"], source: "project", }, { diff --git a/packages/coding-agent/test/tools/tool-output-hyperlinks.test.ts b/packages/coding-agent/test/tools/tool-output-hyperlinks.test.ts index ebc0be5f0..47c35e50f 100644 --- a/packages/coding-agent/test/tools/tool-output-hyperlinks.test.ts +++ b/packages/coding-agent/test/tools/tool-output-hyperlinks.test.ts @@ -9,8 +9,8 @@ import { getThemeByName, initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { astGrepToolRenderer } from "@oh-my-pi/pi-coding-agent/tools/ast-grep"; import { ReadTool, readToolRenderer } from "@oh-my-pi/pi-coding-agent/tools/read"; -import { searchToolRenderer } from "@oh-my-pi/pi-coding-agent/tools/search"; import { WriteTool, writeToolRenderer } from "@oh-my-pi/pi-coding-agent/tools/write"; +import { grepToolRenderer } from "../../src/tools/grep"; // 1x1 PNG so the read tool takes its image branch. const TINY_PNG_BASE64 = @@ -128,7 +128,7 @@ describe("tool output OSC 8 file:// hyperlinks", () => { displayContent: ["# src/", "## interactive-mode.ts#abcd", "*12│const needle = true;"].join("\n"), }, }; - const rendered = searchToolRenderer + const rendered = grepToolRenderer .renderResult(result as never, { expanded: true, isPartial: false }, theme, { pattern: "needle" }) .render(240) .join("\n"); diff --git a/packages/collab-web/CHANGELOG.md b/packages/collab-web/CHANGELOG.md index b22b56fa0..4854505f3 100644 --- a/packages/collab-web/CHANGELOG.md +++ b/packages/collab-web/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog ## [Unreleased] +### Added + +- Added dedicated renderers for `glob` and `grep` tools to improve result readability +- Enabled display support for legacy `find` and `search` tool outputs via new renderers ## [16.1.23] - 2026-06-26 @@ -138,4 +142,4 @@ ### Security -- Hardened transcript Markdown rendering by escaping embedded HTML and allowing only safe link schemes +- Hardened transcript Markdown rendering by escaping embedded HTML and allowing only safe link schemes \ No newline at end of file diff --git a/packages/collab-web/scripts/fixture.ts b/packages/collab-web/scripts/fixture.ts index 4d59538c9..b290beb3b 100644 --- a/packages/collab-web/scripts/fixture.ts +++ b/packages/collab-web/scripts/fixture.ts @@ -227,6 +227,64 @@ export const fixtureEntries: SessionEntry[] = [ timestamp: NOW - 20 * MIN, }, }, + { + id: "e09-legacy-call", + parentId: "e09", + timestamp: iso(NOW - 19 * MIN), + type: "message", + message: { + role: "assistant", + content: [ + { type: "text", text: "Legacy transcript aliases should still render for old saved sessions." }, + { + type: "toolCall", + id: "legacy-search-01", + name: "search", + arguments: { pattern: "relay", paths: ["docs/collab.md"] }, + intent: "Legacy search alias sample", + }, + { + type: "toolCall", + id: "legacy-find-01", + name: "find", + arguments: { paths: ["docs/**/*.md"] }, + intent: "Legacy find alias sample", + }, + ], + model: fixtureModel.id, + usage: mkUsage(4_020, 54, 23_500, 0.014), + stopReason: "toolUse", + timestamp: NOW - 19 * MIN, + }, + }, + { + id: "e09-legacy-search-result", + parentId: "e09-legacy-call", + timestamp: iso(NOW - 19 * MIN + 1_000), + type: "message", + message: { + role: "toolResult", + toolCallId: "legacy-search-01", + toolName: "search", + content: [{ type: "text", text: "docs/collab.md:12:relay reconnect notes" }], + isError: false, + timestamp: NOW - 19 * MIN + 1_000, + }, + }, + { + id: "e09-legacy-find-result", + parentId: "e09-legacy-call", + timestamp: iso(NOW - 19 * MIN + 1_500), + type: "message", + message: { + role: "toolResult", + toolCallId: "legacy-find-01", + toolName: "find", + content: [{ type: "text", text: "docs/collab.md\ndocs/collab-protocol.md" }], + isError: false, + timestamp: NOW - 19 * MIN + 1_500, + }, + }, { id: "e10", parentId: "e09", @@ -318,11 +376,11 @@ export const fixtureAgents: AgentSnapshot[] = [ }, ]; -const PROBE_TOOLS = ["bash", "read", "search", "edit"] as const; +const PROBE_TOOLS = ["bash", "read", "grep", "edit"] as const; const PROBE_TOOL_ARGS: Record<(typeof PROBE_TOOLS)[number], string> = { bash: "bun test packages/coding-agent/test/collab --filter reconnect", read: "packages/coding-agent/src/collab/relay-client.ts:168-197", - search: "scheduleRetry|failFatal", + grep: "scheduleRetry|failFatal", edit: "packages/coding-agent/test/collab/reconnect.test.ts", }; @@ -396,8 +454,8 @@ const subagentTranscriptLines: unknown[] = [ { type: "toolCall", id: "sub-call-01", - name: "search", - arguments: { pattern: "40\\d\\d", path: "docs/collab.md" }, + name: "grep", + arguments: { pattern: "40\\d\\d", paths: ["docs/collab.md"] }, intent: "Finding close codes", }, ], @@ -415,7 +473,7 @@ const subagentTranscriptLines: unknown[] = [ message: { role: "toolResult", toolCallId: "sub-call-01", - toolName: "search", + toolName: "grep", content: [ { type: "text", diff --git a/packages/collab-web/src/tool-render/registry.ts b/packages/collab-web/src/tool-render/registry.ts index 95369aa45..a59ee1d87 100644 --- a/packages/collab-web/src/tool-render/registry.ts +++ b/packages/collab-web/src/tool-render/registry.ts @@ -1,7 +1,6 @@ /** - * Tool renderer registry. Keys are wire tool names; aliases map legacy or - * sibling tool names onto a shared renderer. Unknown tools fall back to the - * generic JSON renderer. + * Tool renderer registry. Keys are current wire tool names; aliases keep old + * transcript names renderable. Unknown tools fall back to the generic JSON renderer. */ import { genericRenderer } from "./generic"; import { askRenderer } from "./tools/ask"; @@ -13,10 +12,11 @@ import { debugRenderer } from "./tools/debug"; import { editRenderer } from "./tools/edit"; import { evalRenderer } from "./tools/eval"; import { fetchRenderer } from "./tools/fetch"; -import { findRenderer } from "./tools/find"; import { generateImageRenderer } from "./tools/generate-image"; import { githubRenderer } from "./tools/github"; +import { globRenderer } from "./tools/glob"; import { goalRenderer } from "./tools/goal"; +import { grepRenderer } from "./tools/grep"; import { inspectImageRenderer } from "./tools/inspect-image"; import { ircRenderer } from "./tools/irc"; import { jobRenderer } from "./tools/job"; @@ -28,7 +28,6 @@ import { readRenderer } from "./tools/read"; import { reportFindingRenderer } from "./tools/report-finding"; import { reportToolIssueRenderer } from "./tools/report-tool-issue"; import { resolveRenderer } from "./tools/resolve"; -import { searchRenderer } from "./tools/search"; import { searchBm25Renderer } from "./tools/search-bm25"; import { sshRenderer } from "./tools/ssh"; import { taskRenderer } from "./tools/task"; @@ -53,7 +52,8 @@ const RENDERERS: Record = { python: evalRenderer, notebook: evalRenderer, fetch: fetchRenderer, - find: findRenderer, + glob: globRenderer, + find: globRenderer, generate_image: generateImageRenderer, github: githubRenderer, goal: goalRenderer, @@ -71,8 +71,8 @@ const RENDERERS: Record = { report_finding: reportFindingRenderer, report_tool_issue: reportToolIssueRenderer, resolve: resolveRenderer, - search: searchRenderer, - grep: searchRenderer, + grep: grepRenderer, + search: grepRenderer, search_tool_bm25: searchBm25Renderer, ssh: sshRenderer, task: taskRenderer, diff --git a/packages/collab-web/src/tool-render/tools/find.tsx b/packages/collab-web/src/tool-render/tools/glob.tsx similarity index 94% rename from packages/collab-web/src/tool-render/tools/find.tsx rename to packages/collab-web/src/tool-render/tools/glob.tsx index c6f455d8a..a6007c18f 100644 --- a/packages/collab-web/src/tool-render/tools/find.tsx +++ b/packages/collab-web/src/tool-render/tools/glob.tsx @@ -1,4 +1,4 @@ -/** `find` — glob-based file finder; results are paths sorted by mtime. */ +/** `glob` (legacy `find`) — glob-based file finder; results are paths sorted by mtime. */ import type { ReactNode } from "react"; import { Badge, Badges, InvalidArg, Note, ResultText } from "../parts"; import type { ToolRenderer, ToolRenderProps } from "../types"; @@ -63,4 +63,4 @@ function Body({ args, result }: ToolRenderProps): ReactNode { ); } -export const findRenderer: ToolRenderer = { Summary, Body }; +export const globRenderer: ToolRenderer = { Summary, Body }; diff --git a/packages/collab-web/src/tool-render/tools/search.tsx b/packages/collab-web/src/tool-render/tools/grep.tsx similarity index 89% rename from packages/collab-web/src/tool-render/tools/search.tsx rename to packages/collab-web/src/tool-render/tools/grep.tsx index 6a395eb0b..7e4b890f8 100644 --- a/packages/collab-web/src/tool-render/tools/search.tsx +++ b/packages/collab-web/src/tool-render/tools/grep.tsx @@ -1,10 +1,10 @@ -/** `search` (alias `grep`) — ripgrep content search across workspace files. */ +/** `grep` (legacy `search`) — ripgrep content search across workspace files. */ import type { ReactNode } from "react"; import { Badge, Badges, InvalidArg, Note, ResultText } from "../parts"; import type { ToolRenderer, ToolRenderProps } from "../types"; import { detailsRecord, num, resultTextOf, shortenPath, str } from "../util"; -/** Search targets: modern `paths` (string | string[]) or legacy grep `path`. */ +/** Grep targets: current `paths` (string | string[]) or legacy `path`. */ function pathsOf(args: Record): string[] { const raw = args.paths ?? args.path; if (typeof raw === "string") return [shortenPath(raw)]; @@ -18,7 +18,7 @@ function pathsOf(args: Record): string[] { return ["."]; } -/** Flag badges covering both arg dialects (search: i/skip/gitignore; grep: glob/type/multiline). */ +/** Flag badges covering current and legacy arg dialects. */ function argBadges(args: Record): ReactNode[] { const badges: ReactNode[] = []; const glob = str(args.glob); @@ -82,4 +82,4 @@ function Body({ args, result }: ToolRenderProps): ReactNode { ); } -export const searchRenderer: ToolRenderer = { Summary, Body }; +export const grepRenderer: ToolRenderer = { Summary, Body }; diff --git a/packages/utils/src/dirs.ts b/packages/utils/src/dirs.ts index 4c78372f1..892576709 100644 --- a/packages/utils/src/dirs.ts +++ b/packages/utils/src/dirs.ts @@ -584,11 +584,7 @@ export function setWorktreesDir(dir: string | undefined): string | undefined { * ignored and resolution falls through. */ export function getWorktreesDir(): string { - return ( - resolveWorktreeBase(process.env.OMP_WORKTREE_DIR) ?? - worktreesDirOverride ?? - dirs.rootSubdir("wt", "data") - ); + return resolveWorktreeBase(process.env.OMP_WORKTREE_DIR) ?? worktreesDirOverride ?? dirs.rootSubdir("wt", "data"); } /** Get the SSH control socket directory (~/.omp/ssh-control). */