diff --git a/packages/fs/tool-fs-search/README.i18n.yaml b/packages/fs/tool-fs-search/README.i18n.yaml index 8e1717db1a..caaf3bd524 100644 --- a/packages/fs/tool-fs-search/README.i18n.yaml +++ b/packages/fs/tool-fs-search/README.i18n.yaml @@ -42,8 +42,8 @@ en: 5a30f023dd86c345 zh: 558b6e80c62648ba /deepseek-ai-dsh-tool-fs-search/understand-the-implementation/how-a-search-runs: - en: 3806f88eb3ac869a - zh: 688a978647fa5aca + en: 47f4c091ab3c0a71 + zh: 1afaab48629b6b39 /deepseek-ai-dsh-tool-fs-search/understand-the-implementation/native-mode-benchmark: en: 031654f59e63d07e zh: a3b488fb1880d2f5 diff --git a/packages/fs/tool-fs-search/README.md b/packages/fs/tool-fs-search/README.md index 942da7f24a..7b32971bb2 100644 --- a/packages/fs/tool-fs-search/README.md +++ b/packages/fs/tool-fs-search/README.md @@ -107,7 +107,7 @@ Local workspace discovery is naturally a process-backed `rg` workflow, and putti Without an explicit `path`, `glob` narrows traversal for patterns such as `packages/core/src/*.ts` when every directory component is a plain relative name that exactly matches an existing non-symlink directory outside VCS metadata. A prefix consisting only of `-` retains workspace traversal because ripgrep interprets that argument as standard input. Other patterns retain workspace traversal; matching, modification-time ordering, and the logical result root stay unchanged. -Each call resolves the packaged binary (`@vscode/ripgrep`, or the executable's `-rg` sidecar in a pkg single-file runtime), prepends `--no-config` so a host `RIPGREP_CONFIG_PATH` cannot inject a `--pre` preprocessor into the unconfined spawn, and passes every model-controlled value as a plain argv element — no shell layer exists, so no quoting applies. Collect-mode budgets bound complete stdout and a stderr tail; a lossy stdout read fails as `SEARCH_RAW_OUTPUT_OVERFLOW` rather than parsing a silently-partial stream. The tools never read a raw spill path. +Each call resolves its working directory once before constructing argv, then resolves the packaged binary (`@vscode/ripgrep`, or the executable's `-rg` sidecar in a pkg single-file runtime), prepends `--no-config` so a host `RIPGREP_CONFIG_PATH` cannot inject a `--pre` preprocessor into the unconfined spawn, and passes every model-controlled value as a plain argv element — no shell layer exists, so no quoting applies. Collect-mode budgets bound complete stdout and a stderr tail; a lossy stdout read fails as `SEARCH_RAW_OUTPUT_OVERFLOW` rather than parsing a silently-partial stream. The tools never read a raw spill path. ### Native-mode benchmark diff --git a/packages/fs/tool-fs-search/README.zh.md b/packages/fs/tool-fs-search/README.zh.md index 9ce05936f3..7b5082082d 100644 --- a/packages/fs/tool-fs-search/README.zh.md +++ b/packages/fs/tool-fs-search/README.zh.md @@ -107,7 +107,7 @@ Node 部署在受支持的 macOS、Linux 与 Windows 目标上获得 `@vscode/ri 未显式提供 `path` 时,如果目录部分的每一项都是普通相对名称,且精确匹配已存在的非符号链接目录、不属于 VCS 元数据目录,`glob` 会为 `packages/core/src/*.ts` 这样的模式缩小遍历范围。仅由 `-` 组成的前缀仍遍历工作区,因为 ripgrep 将该参数解释为标准输入。其他模式仍遍历工作区;匹配规则、修改时间排序和结果的逻辑根目录保持不变。 -每次调用解析打包二进制(`@vscode/ripgrep`,或 pkg 单文件运行时中可执行程序的 `-rg` 伴随文件),前置 `--no-config`,使宿主的 `RIPGREP_CONFIG_PATH` 无法向不受约束的 spawn 注入 `--pre` 预处理器,并把每个模型控制的值作为普通 argv 元素传入——不存在 shell 层,因此不涉及 shell 引号处理。collect 模式预算限制完整 stdout 与 stderr 尾部;lossy stdout 读取以 `SEARCH_RAW_OUTPUT_OVERFLOW` 失败,而不是解析静默不完整的流。工具从不读取原始 spill 路径。 +每次调用在构造 argv 前解析一次工作目录,然后解析打包二进制(`@vscode/ripgrep`,或 pkg 单文件运行时中可执行程序的 `-rg` 伴随文件),前置 `--no-config`,使宿主的 `RIPGREP_CONFIG_PATH` 无法向不受约束的 spawn 注入 `--pre` 预处理器,并把每个模型控制的值作为普通 argv 元素传入——不存在 shell 层,因此不涉及 shell 引号处理。collect 模式预算限制完整 stdout 与 stderr 尾部;lossy stdout 读取以 `SEARCH_RAW_OUTPUT_OVERFLOW` 失败,而不是解析静默不完整的流。工具从不读取原始 spill 路径。 ### Native 模式 benchmark diff --git a/packages/fs/tool-fs-search/src/glob.ts b/packages/fs/tool-fs-search/src/glob.ts index dcc4389857..5fa59567c9 100644 --- a/packages/fs/tool-fs-search/src/glob.ts +++ b/packages/fs/tool-fs-search/src/glob.ts @@ -15,7 +15,7 @@ import { join, sep } from 'node:path' import { defineTool } from '@deepseek-ai/dsh-tools' import type { GenericCallView, SearchResultView, ToolResult } from '@deepseek-ai/dsh-tools' import type { SpillRef } from '@deepseek-ai/dsh-spill' -import { runRipgrep, toWorkdirRelative, trySaveFormattedResult } from './search-core.ts' +import { resolveSearchWorkdir, runRipgrep, toWorkdirRelative, trySaveFormattedResult } from './search-core.ts' import { globSearchMeta, searchViewFromMeta } from './presentation.ts' import { acceptedDirectCallValue } from './direct-call.ts' @@ -364,7 +364,9 @@ export function applyGlobTool(ctx: Context, caps: GlobToolCaps): void { }, async execute(args, exec) { const input = parseGlobArgs(args) - const run = await runRipgrep(ctx, exec, 'glob', workdir => scopedGlobCommand(input, workdir), caps.rawOutputMaxBytes, caps.graceMs, caps.stderrMaxBytes) + const workdir = await resolveSearchWorkdir(ctx, exec, 'glob') + const argv = await scopedGlobCommand(input, workdir) + const run = await runRipgrep(ctx, exec, 'glob', workdir, argv, caps.rawOutputMaxBytes, caps.graceMs, caps.stderrMaxBytes) const root = input.path === undefined ? '.' : toWorkdirRelative(input.path, run.workdir) if (run.noMatches) return { root, paths: [] } diff --git a/packages/fs/tool-fs-search/tests/integration.spec.ts b/packages/fs/tool-fs-search/tests/integration.spec.ts index c6e57dfa93..83405a5b72 100644 --- a/packages/fs/tool-fs-search/tests/integration.spec.ts +++ b/packages/fs/tool-fs-search/tests/integration.spec.ts @@ -88,7 +88,9 @@ describe('search tools over the real subprocess service + the packaged rg', () = await writeFile(join(dir, '.gitignore'), 'packages/\n') await utimes(join(dir, 'packages', 'core', 'src', 'old.ts'), new Date(2000, 0, 1), new Date(2000, 0, 1)) const spawn = vi.spyOn(ctx.subprocess, 'spawn') + const ensure = vi.spyOn(ctx.workingDirectory, 'ensure') const result = await call('glob', { pattern: 'packages/core/src/*.ts' }, agent()) + expect(ensure).toHaveBeenCalledTimes(1) expect(result.isError).toBe(false) if (result.isError) throw new Error('expected glob success') expect(result.value).toEqual({ root: '.', paths: [