news 2026/9/19 17:24:26

DeepSeek Harness 内置 ripgrep 搜索:glob/grep 工具从 bash 封装迁移到打包二进制直连的架构解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DeepSeek Harness 内置 ripgrep 搜索:glob/grep 工具从 bash 封装迁移到打包二进制直连的架构解析
  • 人工智能
  • AI Agent
  • Agent 框架
  • DeepSeek

【免费下载链接】deepseek-harness

DeepSeek Harness: Everything is a Plugin.

项目地址:https://gitcode.com/gh_mirrors/de/deepseek-harness
点击查看免费下载

本篇文章聚焦 DeepSeek Harness 中glob/grep两个文件发现与内容搜索工具的架构演进:它们从依赖宿主环境rg的 bash 执行缝(seam),迁移为通过ctx.subprocess直接派生随 npm 包分发的内置 ripgrep 二进制(@vscode/ripgrep)。你将理解这次决策的问题背景、纯 argv 直连的调用链、--no-config安全边界、懒解析的加载期失败模式设计、完整的SEARCH_*错误词汇表与配置参数默认值,并看到对应的源码、测试与部署组合证据。核心变更记录于仓库内的决策笔记 2026-08-01-packaged-ripgrep-search.md,对应实现位于@deepseek-ai/dsh-tool-fs-search包(tool-fs-search)。

一、问题:v1 的 bash 封装让系统rg成为宿主依赖

在本次变更之前,glob/grep工具通过 bash 执行缝运行,其设计记录在已被归档的 2026-07-09-bash-backed-grep-glob-discovery.md 中。当时的取舍是:不做ctx.fsprovider 方法(避免把本地产品便利变成通用文件系统后端契约),而是让工具走ctx.bash.resolve()ctx.bash.run()流程,用固定的rg命令模板执行搜索。这个 v1 方案换来的是三个明确代价:

  1. 系统rg成为宿主依赖:插件加载时通过command -v rg探测;在 Windows 和精简容器镜像上,rg默认不在PATH中,于是两个工具静默消失——部署方只能从加载期探测警告中发现问题,而模型侧根本没有机会发起搜索。
  2. 整个模型可见参数面被迫穿过一层 shell 引号辅助函数:因为 shell 位于工具与 ripgrep 之间,所有模型控制的值(patternpathinclude)都必须先做 POSIX 单引号转义,再拼进命令字符串。v1 笔记把这种耦合明确记录为权衡,并点名"如果 shell 字符串域被证明过于敏感,直接派生 ripgrep 就是合理的后续方案"。
  3. 职责重复:探测逻辑要在测试里写脚本模拟;执行器的超时分类与协作式工具超时策略(tool-call-timeout-policy)重复实现。

事实上 v1 笔记的备选方案中就已经写过:"直接从dsh-fs-local派生 ripgrep……它仍是合理的优化方向,如果 bash 封装的搜索被证明对 shell 字符串过于敏感。" 本次变更正是兑现了这个被显式延期的备选项:直接派生 ripgrep

二、决策:打包二进制 + 子进程缝直连

@deepseek-ai/dsh-tool-fs-search现在通过ctx.subprocess缝运行随包分发的ripgrep 二进制。该二进制来自 npm 依赖@vscode/ripgrep——它通过可选平台包(@vscode/ripgrep-<platform>-<arch>)为 darwin/linux/win32 × x64/arm64 交付可执行文件,见 package.json 中的"@vscode/ripgrep": "^1.18.0"

2.1 runRipgrep:纯 argv 向量直连

核心执行函数runRipgrep()位于 search-core.ts,其调用形态是:

ctx.subprocess.spawn({ argv: [await resolveRgPath(), '--no-config', ...argv], cwd: workdir, stdio: { stdin: 'ignore', stdout: { maxBytes: rawOutputMaxBytes }, stderr: { maxBytes: stderrMaxBytes }, }, graceMs, signal: exec.signal, })

关键点:

  • 无 shell 层:工具与 ripgrep 之间不再有 shell,模型提供的每个值都是 argv 数组中的一个普通元素,不存在引号转义边界。singleQuote辅助函数及其 shell 派生测试随这次变更一并删除。
  • collect 模式输出:stdout/stderr 按"诊断尾部"形状收集,不产生 spill 文件——工具从不读取原始 spill 路径;如果 stdout 是有损读取(超出rawOutputMaxBytes预算仍未完整保留),搜索以SEARCH_RAW_OUTPUT_OVERFLOW失败,而不是解析一份静默截断的流(completeStdout实现了这条规则)。
  • 超时与终止graceMs(terminate 升级的宽限期)与exec.signal原样转发给子进程缝;协作式工具调用超时由@deepseek-ai/dsh-tool-call-timeout-policy通过 abort 信号触发,子进程缝的 terminate 升级负责硬杀,工具最终上报SEARCH_ABORTED

2.2 rgPath 懒解析:把"缺二进制"从加载期错误变成调用期错误

rgPath的解析被设计为首次调用时懒解析、进程内记忆化resolveRgPath(),同一文件内)。动机很明确:@vscode/ripgrep在模块求值阶段就会解析其平台包,如果用静态 import,一个缺失或损坏的平台包(例如--omit=optional安装、部分安装)会直接变成Loader 组合失败——而"把加载期失败模式移除"正是这次变更要解决的核心目标之一。

懒解析让失败降级为:第一次搜索调用时报SEARCH_FAILED(启动失败),而不是插件加载失败。源码注释明确写着"Resolving at the call boundary keeps a missing or corrupt binary at the first search call asSEARCH_FAILED, rather than failing the Loader composition."。单文件运行时(pkg 打包)无法从虚拟文件系统派生原生辅助程序,因此resolveRgPath还额外支持可执行文件的-rg侧边车(sidecar)路径。

@vscode/ripgrep没有自带类型声明,包内用 ripgrep.d.ts 声明了最小的模块面:命名导出rgPath: string

2.3 注册无条件化:删除探测与条件注册

v1 的加载期command -v rg探测、条件注册逻辑以及 "rg not found" 警告全部删除。现在注册是无条件的,因为二进制总是可用(它是 npm 依赖)。包注入的服务也从['tools', 'systemPrompt', 'bash']变为['tools', 'systemPrompt', 'subprocess'],见 index.ts。spillStore仍然通过ctx.get('spillStore')机会式读取,因为格式化结果的 spill 是可选的。

2.4 工作目录归属工具

执行工作目录为会话头中的 cwd(exec.agent.session.header.cwd),缺失时回退到process.cwd()。由于不再有执行器配置可"默认穿透",工作目录回退逻辑由工具自己持有。返回路径相对解析后的工作目录展示(toWorkdirRelative),且仅在"工作目录与文件系统read根为同一工作区"的共置部署中可被后续读取——这是 v1 就记录的部署要求,本次未做运行时校验。

三、执行细节与安全边界

3.1 --no-config:阻断配置注入的任意命令预处理器

派生是不受限制的(普通ctx.subprocess调用,而非受沙箱配置约束的派生),因此每次调用都在 argv 前强制加上--no-config。原因:宿主若设置了RIPGREP_CONFIG_PATH(或二进制旁存在rg.conf),ripgrep 会读取该配置并可能注入一个--pre预处理器——为每个匹配文件执行任意命令。--no-config保证没有任何配置文件(因此也没有预处理器)能到达搜索过程。这在 search-core.ts 的runRipgrepJSDoc 中有明确说明。

3.2 glob 的 argv 模板

buildGlobCommand(glob.ts)构建固定模板:

--files --glob=<pattern> --sort=modified --no-ignore --hidden --glob=!**/<vcs> --glob=!**/<vcs>/** (对 .git/.svn/.hg/.bzr/.jj/.sl 各一对) [-- <path>]

细节值得展开:

  • --files只列文件、不列目录;--sort=modified保证修改时间序;--no-ignore --hidden覆盖被忽略与隐藏文件。
  • VCS 元数据排除是双 glob 对:每个 VCS 目录名同时生成裸形式--glob=!**/.git(遍历时剪枝该目录)和内容形式--glob=!**/.git/**(当搜索根就是或位于该目录内部时,裸形式对根前缀路径永不匹配,内容形式仍然排除内部)。GLOB_VCS_EXCLUDES = ['.git', '.svn', '.hg', '.bzr', '.jj', '.sl']
  • 搜索根放在--之后,前导连字符的路径永远不会被解析成 flag。

3.3 grep 的 argv 模板与 --json 解析

buildGrepCommand(grep.ts)构建固定的面向行的rg --json命令:

--json --regexp=<pattern> [--glob=<include>] [-- <path>]

选择--json是为了让文件路径、行号、行文本的解析不依赖冒号切分(规避路径含冒号/带格式路径时的歧义)。parseGrepMatches只消费match记录,begin/end/context/summary属于传输帧而跳过;非法 JSON 或缺字段的 match 记录被判定为SEARCH_FAILED;行的 UTF-8 解码失败(ripgrep 发送 base64bytes而非text)时用占位符(line is not valid UTF-8)代替预览,而不是让整个搜索失败。

include校验前置且严格(validateInclude):必须是一个正向 glob,拒绝空串、拒绝!前缀的否定模式、拒绝逗号分隔列表(花括号内的逗号合法,如*.{ts,tsx})。

3.4 退出码语义仍归工具所有

  • 退出码0:成功且有结果;
  • 退出码1:成功的空搜索(noMatches置真);
  • 其他:分类进既有SEARCH_*词汇表。

由于没有 shell 层,退出码 127 或 "command not found" 文本不可能出现——启动失败在 spawn 时就以 rejection 呈现。classifyRunFailure通过 stderr 尾文本中的/regex parse error|error parsing glob/i区分SEARCH_INVALID_PATTERN,其余归SEARCH_FAILED

四、配置参数与默认值

包级Config(index.ts)中,sampleOverCapGlobResults必需项(无默认值,部署必须显式选择超限分页的排序契约),其余字段均有默认值;所有计数/字节/毫秒字段都经assertPositiveInteger校验,graceMs还受MAX_TIMER_DELAY_MS上限约束。完整表格如下(同时见于包 README.md 与生成的 config-catalog.md):

配置键默认值含义
sampleOverCapGlobResults无(必需)true:超限glob页按顶层条目跨目录抽样;false:保留修改时间序头部
globMaxResults100单次glob调用内联展示的最大路径数(对应源码GLOB_MAX_RESULTS
grepMaxMatches250单次grep调用内联保留的最大扁平匹配数(GREP_MAX_MATCHES
grepMaxLineBytes2000每条匹配行预览的字节上限,切分保留 UTF-8 边界(GREP_MAX_LINE_BYTES
rawOutputMaxBytes20000000工具将解析的完整原始rgstdout 上限;超出报SEARCH_RAW_OUTPUT_OVERFLOWRAW_OUTPUT_MAX_BYTES
timeoutMs30000两个工具定义上的协作式调用预算,经exec.signal强制执行(SEARCH_TIMEOUT_MS
graceMs3000子进程缝在timeoutMs之外给予的 terminate 升级宽限(SEARCH_GRACE_MS
stderrMaxBytes65536rgstderr 诊断尾部预算(SEARCH_STDERR_MAX_BYTES
searchMetaMaxBytes65536单次搜索序列化presentationMeta的字节上限,尾部组/路径超过即丢弃(SEARCH_META_MAX_BYTES

其中rawOutputMaxBytesglobMaxResults/grepMaxMatches分别对应 Claude Code 搜索工具的 20 MB 原始缓冲与 100/250 的内联上限,属于双层预算(原始收集 + 内联页)的设计参照,而非模式化 schema 先例。grep在 v1 不暴露case_insensitivehead_limitoffsetcount、多行、上下文行、输出模式或文件类型过滤——需要上下文的模型用read读匹配文件,需要后续结果的模型跟随返回的 spill 定位器提示。

五、组合方式与部署

5.1 最小组合

需要先挂载一个ctx.subprocess后端;无需宿主rg,也无需文件系统 provider(spill 后端可选,用于让超限结果完整可恢复):

- name: '@deepseek-ai/dsh-subprocess-local' - name: '@deepseek-ai/dsh-tool-fs-search' config: sampleOverCapGlobResults: false - name: '@deepseek-ai/dsh-spill-local'

5.2 内置工具花名册:TUI/Web 的固定成员

因为dsh-tool-fs-search派生打包二进制并无条件注册两个工具,glob/grep从"宿主依赖决定是否出现"变成固定成员:TUI 与 Web 两个已发布界面共享同一工具花名册,共享底座 base/cordis.patch.yml 中即可看到tool-fs-search挂载(sampleOverCapGlobResults: false)。这一花名册决策记录在 2026-07-31-even-out-shipped-tool-rosters.md:两个界面端到端测试把glob/grep断言为无条件固定成员,而不是宿主相关的一对工具。

5.3 快照场景:真实二进制 + 固定 mtime

fs-glob-sampling快照场景现在执行真实的打包二进制,针对一个预先准备的、用固定 mtime 钉住--sort=modified顺序的工作区——取代了原来通过注入PATH提供的rg替身(替身是 POSIX 专用的,因为其展示路径带/分隔符,会话日志对比无法归一化)。证据见 snapshots/session/fs-glob-sampling/cordis.yml(sampleOverCapGlobResults: trueglobMaxResults: 4)与 snapshots/session/fs-glob-sampling/snapshot.yml(scenario: fs-glob-samplingplatform: posixworkspace.setup: fixed-search-mtimes)。

六、失败模式与错误词汇表

搜索失败使用包所有的HarnessError子类SearchError(带稳定错误码与cause链),而非FsErrorCode——因为这些工具是 spawn 支撑的发现操作,不是ctx.fsprovider 操作。词汇表(search-core.ts):

错误码含义
SEARCH_INVALID_PATTERNripgrep 拒绝了正则或 glob
SEARCH_FAILED搜索无法运行或输出无法解析(启动失败、目标不可达、信号杀死、畸形--json
SEARCH_RAW_OUTPUT_OVERFLOW原始输出超过rawOutputMaxBytes,或在请求的 stdout 预算内仍保持截断
SEARCH_ABORTED协作式工具超时或调用方取消

错误码通过工具注册表的isError结果的{ name, code }暴露,重试/权限/UI 层可以分支处理而无需解析消息文本。加载期失败模式也随本次变更改变:子进程缝损坏时,失败从"插件加载失败(探测所致)"变为"首次搜索调用报SEARCH_FAILED";二进制缺失则变成"带打包路径的启动失败",而不是 PATH 问题。

七、测试与验证

测试覆盖是本次重构可信度的直接证据:

  • 真实集成套件(integration.spec.ts):dsh-subprocess-local+打包二进制,通过ctx.tools.execute()走真实世界——磁盘上真实文件被发现与搜索、敌意模式保持惰性、真实rgstderr 分类进SEARCH_*。它此前在缺少系统rg时自我跳过,现在每个平台都能跑
  • 敌意模式惰性测试grep传入'$(touch pwned)',断言结果是No matches found且 canary 文件不存在——因为模式只是 argv 普通元素,世界未被触碰。这被注释明确钉为"已发布契约",未来任何重新引入 shell 包裹的改动都必须守住。
  • 懒解析失败路径(rg-path.spec.ts):mock@vscode/ripgrep在求值时抛错,证明缺失/损坏平台包(--omit=optional、部分安装)表现为逐调用的SEARCH_FAILED,且解析记忆化后每次调用都一致失败。
  • 真实加载路径守卫(load-path.spec.ts):命名空间插件如果误加export default apply,会被 Loader 的unwrapExports折叠、丢掉inject(即 postmortem 0001)。该测试用真实Loader.prototype.unwrapExports解包模块并挂载,验证name/inject/Config/apply形状与无inject错误启动。
  • 夹具的 Windows 可重放性:集成套件夹具删掉了一个 Windows 无法表示的"文件名,保证套件在所有平台可重放。

八、备选方案与取舍记录

决策笔记记录了三组被拒的备选方案,作为后续提案必须超越的基准证据:

  1. 保留 bash 缝与探测、仅把rg文档化为必需宿主依赖——拒绝:宿主依赖恰恰是本次要消除的失败;Windows 支持是本次工作的目标,文档化的需求仍然是需求。
  2. rgPath可注入(配置字段或环境变量覆盖)——拒绝:它新增一个唯一的消费方是测试钩子的公开部署面;真实二进制已足够确定,直接用夹具 mtime 钉住即可——"打包二进制就是部署,测试就该测它"。
  3. 切换到纯 JS glob/搜索引擎(如picomatch/tinyglobby——拒绝:依赖交换审计(2026-07-26-dependency-swaps-rejected-by-nih-audit.md)基于"不存在满足条件的 glob 引擎"证据已否决该方向;ripgrep 语义(--sort=modified、VCS 剪枝、JSON 传输、正则方言)就是工具契约。

九、迁移后果与连锁影响

  • 平台覆盖:发现工具在打包二进制覆盖的每个平台(darwin/linux/win32 × x64/arm64)上无需宿主安装即可工作;Node 部署接收@vscode/ripgrep平台包,Python SDK 单文件运行时则把目标平台原生二进制复制为-rg侧边车。
  • 攻击面消失:shell 字符串注入面不复存在,敌意模式是惰性 argv 元素,由现在能在 Windows 上运行的集成套件钉住。
  • 溢出路径改变形状:旧 bash 路径继承 bash-local 常开的 spill,可能遗留一个未被读取的多 MB 临时文件;子进程缝现在无 spill 收集,溢出是纯错误(SEARCH_RAW_OUTPUT_OVERFLOW,"narrow pattern, path, or include and retry"),零内容返回。
  • 第三方声明生成器 bug 被暴露并修复@vscode/ripgrep新依赖让THIRD_PARTY_NOTICES.md生成器的一个潜在缺陷显形——Node 的fs.globSync返回 OS 原生分隔符,Windows 上带/后缀的 dev 区前缀永不匹配,导致 dev 专用包被错误分档为 runtime;生成器现在在摄入时归一化 manifest 路径,声明平台无关。同时@vscode/ripgrep的 MIT 行进入 runtime 档,pnpm 11 截断的虚拟存储目录名需要元数据查找中的内容扫描回退。
  • 夹具调整:集成套件夹具去掉 Windows 无法表示的文件名,套件在所有平台可重放。

十、小结

从 bash 封装到打包 ripgrep 直连,@deepseek-ai/dsh-tool-fs-search的这次架构决策围绕一条主线:把"系统级前置条件"变成"包内资产",把"加载期失败"变成"调用期失败",把"shell 字符串域"变成"纯 argv 向量域"。配套的懒解析、--no-config安全前缀、SEARCH_*错误词汇表、双层输出预算(原始收集 + 内联页 + 格式化 spill 恢复)与跨平台集成测试,共同构成了一套可在任何受支持平台上零安装运行、行为确定、可测试的文件发现与内容搜索能力。若需继续深入,可阅读 filesystem 子系统文档、subprocess 能力文档 与 tool-fs(read/write/edit 工具) 了解与搜索工具配套的完整文件访问链路。

  • 人工智能
  • AI Agent
  • Agent 框架
  • DeepSeek

【免费下载链接】deepseek-harness

DeepSeek Harness: Everything is a Plugin.

项目地址:https://gitcode.com/gh_mirrors/de/deepseek-harness
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 17:24:23

HybridCLR打包报错全解析:从原理到解决方案

开头直接上结论&#xff1a;HybridCLR这套热更新方案&#xff0c;是目前Unity圈子里把“原生C#热更”做到最彻底的一个。它跟Lua方案不是一回事&#xff0c;也跟ILRuntime那种解释器方案有本质区别&#xff0c;它是在IL2CPP的AOT流程之上&#xff0c;补了一套基于解释执行的补充…

作者头像 李华
网站建设 2026/9/19 17:24:18

改进聚类算法在道路事故多发路段鉴别中的应用

简介&#xff1a;这是一篇发表于《武汉理工大学学报》的学术论文&#xff0c;面向交通安全管理与智能交通研究者、研究生和道路工程师&#xff0c;针对事故多发路段鉴别中阈值选择难的问题&#xff0c;提出改进DBSCAN聚类算法。算法结合累计频率曲线法自适应选取最小密度点&…

作者头像 李华
网站建设 2026/9/19 17:22:22

DeepSeek接入IDEA全指南:Continue插件配置与AI编程实战

作为一个常年泡在IDEA里的Java开发者&#xff0c;我一直在找一款真正能融入日常编码的AI辅助工具。DeepSeek背后是深度求索出的开源大模型&#xff0c;API调用价格便宜到近乎白菜价&#xff0c;而且接口直接兼容OpenAI格式&#xff0c;这意味着IDEA生态里几乎所有的AI插件都能无…

作者头像 李华
网站建设 2026/9/19 17:18:33

开放世界信息抽取中LLM不确定性澄清机制:QDrawer解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 17:17:25

ChatGLM3-6B LoRA 微调实战:基于 PEFT 构建甄嬛风格个性化对话模型

ChatGLM3-6B LoRA 微调实战&#xff1a;基于 PEFT 构建甄嬛风格个性化对话模型 【免费下载链接】self-llm 《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调&#xff08;全参数/Lora&#xff09;、部署国内外开源大模型&#xff08;LLM&#xff09;/多模态大…

作者头像 李华