refactor(fs): resolve search workdir before building argv

This commit is contained in:
_Kerman authored and imccyu committed 2026-10-26 12:04:34 +08:00
1 parent dfaf756199
commit 12f63eaf89
5 files changed
+10 -6

No files matched your search

+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+4 -2
View File
@@ -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: [] }
@@ -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: [