diff --git a/docs/tools/search.md b/docs/tools/search.md index f481bf9d8..cb87d1982 100644 --- a/docs/tools/search.md +++ b/docs/tools/search.md @@ -21,7 +21,7 @@ | Field | Type | Required | Description | | --- | --- | --- | --- | | `pattern` | `string` | Yes | Regex pattern. `search.ts` trims it and rejects empty input. The native matcher enables multiline only when the pattern text contains a literal newline or the two-character sequence `\\n`. The model prompt explicitly documents literal-brace escaping such as ``interface\\{\\}``, although the native layer also auto-escapes braces that cannot be valid repetition quantifiers. | -| `paths` | `string[]` | Yes | One or more file paths, directory paths, glob-like paths, or internal URLs. Empty strings are rejected after trimming/quote stripping. Internal URLs must resolve to a backing file and cannot contain glob characters. | +| `paths` | `string \| string[]` | Yes | One file path, directory path, glob-like path, internal URL, or an array of those. Empty strings are rejected after trimming/quote stripping. Internal URLs must resolve to a backing file and cannot contain glob characters. | | `i` | `boolean` | No | Case-insensitive search. Defaults to `false`. Passed to native `ignoreCase`. | | `gitignore` | `boolean` | No | Respect `.gitignore` during directory scans. Defaults to `true`. Passed to native `gitignore`. | | `skip` | `number` | No | Global match offset. Defaults to `0`. `search.ts` floors finite numbers and rejects negative or non-finite values. | @@ -49,7 +49,8 @@ The tool returns a single text block in `content[0].text` plus structured `detai - trims `pattern`, rejects empty patterns; - normalizes `skip` to a non-negative integer; - reads `search.contextBefore` and `search.contextAfter` from session settings (`1` and `3` by default); - - enables multiline only when `pattern` contains `\n` or an actual newline. + - enables multiline only when `pattern` contains `\n` or an actual newline; + - wraps a single string `paths` value into a one-element list before path resolution. 2. Each `paths` entry is normalized with `normalizePathLikeInput()`. 3. Internal URLs are resolved through `session.internalRouter`: - glob metacharacters (`*`, `?`, `[`, `{`) are rejected for internal URLs; diff --git a/packages/coding-agent/src/prompts/tools/search.md b/packages/coding-agent/src/prompts/tools/search.md index d96a26d3e..3753b88b8 100644 --- a/packages/coding-agent/src/prompts/tools/search.md +++ b/packages/coding-agent/src/prompts/tools/search.md @@ -2,8 +2,8 @@ Searches files using powerful regex matching. - Supports Rust regex syntax (RE2-style — no lookaround or backreferences). Use line anchors or post-filters instead of (?!…)/(? diff --git a/packages/coding-agent/src/tools/index.ts b/packages/coding-agent/src/tools/index.ts index 5060d8ace..9d0509424 100644 --- a/packages/coding-agent/src/tools/index.ts +++ b/packages/coding-agent/src/tools/index.ts @@ -139,6 +139,8 @@ export interface ToolSession { requireYieldTool?: boolean; /** Task recursion depth (0 = top-level, 1 = first child, etc.) */ taskDepth?: number; + /** Get shared eval executor session ID. Subagents inherit this to share JS/Python state. */ + getEvalSessionId?: () => string | null; /** Get session file */ getSessionFile: () => string | null; /** Get eval kernel owner ID for session-scoped retained-kernel cleanup. */ diff --git a/packages/coding-agent/src/tools/search.ts b/packages/coding-agent/src/tools/search.ts index 377d35b1c..6a8310b6e 100644 --- a/packages/coding-agent/src/tools/search.ts +++ b/packages/coding-agent/src/tools/search.ts @@ -9,11 +9,12 @@ import { prompt, untilAborted } from "@oh-my-pi/pi-utils"; import * as z from "zod/v4"; import { getFileReadCache } from "../edit/file-read-cache"; import type { RenderResultOptions } from "../extensibility/custom-tools/types"; -import { computeFileHash, formatHashlineHeader } from "../hashline/hash"; +import { formatHashlineHeader, getHashlineSyntax } from "../hashline/hash"; import type { Theme } from "../modes/theme/theme"; import searchDescription from "../prompts/tools/search.md" with { type: "text" }; import { DEFAULT_MAX_COLUMN, type TruncationResult, truncateHead } from "../session/streaming-output"; import { Ellipsis, fileHyperlink, renderStatusLine, renderTreeList, truncateToWidth } from "../tui"; +import { resolveEditMode } from "../utils/edit-mode"; import { resolveFileDisplayMode } from "../utils/file-display-mode"; import type { ToolSession } from "."; import { @@ -39,13 +40,13 @@ import { import { ToolError } from "./tool-errors"; import { toolResult } from "./tool-result"; +const searchPathEntrySchema = z.string().describe("file, directory, glob, or internal URL to search"); const searchSchema = z .object({ pattern: z.string().describe("regex pattern"), paths: z - .array(z.string().describe("file, directory, glob, or internal URL to search")) - .min(1) - .describe("files, directories, globs, or internal URLs to search"), + .union([searchPathEntrySchema, z.array(searchPathEntrySchema).min(1)]) + .describe("file, directory, glob, internal URL, or array of those to search"), i: z.boolean().optional().describe("case-insensitive search"), gitignore: z.boolean().optional().describe("respect gitignore"), skip: z @@ -237,7 +238,7 @@ export class SearchTool implements AgentTool, _toolContext?: AgentToolContext, ): Promise> { - const { pattern, paths, i, gitignore, skip } = params; + const { pattern, paths: rawPaths, i, gitignore, skip } = params; return untilAborted(signal, async () => { const normalizedPattern = pattern.trim(); @@ -249,6 +250,7 @@ export class SearchTool implements AgentTool(); + const hashlineSyntax = getHashlineSyntax(resolveEditMode(this.session)); if (baseDisplayMode.hashLines) { for (const relativePath of fileList) { if (archiveDisplaySet.has(relativePath)) continue; @@ -493,7 +496,7 @@ export class SearchTool implements AgentTool