DeepSeek Harness 源码解析(三):源码目录、包依赖与文档地图

沿 read 工具寻找真正的文件读取实现,区分 DeepSeek Harness 的目录归属、包依赖与运行时服务关系,建立从文档、manifest、插件入口到具体实现的源码阅读路线。

Hex4C59 11 分钟阅读 更新于 2026-10-17

第二篇沿一次请求走过模型、工具和会话记录。读到工具执行时,你会遇到这样的代码:ctx.fs.readText(...)。工具已经叫作 read,执行函数也找到了,可这里的 fs 究竟是谁?它直接读取本机文件,还是把请求交给另一种后端?

在 DeepSeek Harness 中,声明“能读文件”的接口、实际读文件的实现、模型调用的工具,以及选择这些插件的配置,可能分属不同文件。找到一个调用点以后,需要继续追踪服务定义、提供者和装配关系:先看接口允许做什么,再找实现它的对象,最后确认应用配置选择了谁。目录名和依赖图分别提供一部分线索。

本文以文件读取为主线,建立三张用途不同的地图:代码归属在哪里、包之间依赖什么、运行时调用哪个服务。读者需要能阅读 TypeScript 的导入、函数和类继承,知道 package.json 的基本作用;不必先启动 dsh。本文与前两篇均分析 DeepSeek Harness 0.2.1-alpha.2,本次核对日期为 2026-10-14。

先分清三张地图回答的问题 #

假设要调整 read 返回内容的格式。你可以从 packages/fs/tool-fs/ 找到工具实现,但真正读取文件的能力由它取得的 ctx.fs 提供。tool-fs 需要文件系统服务,至于应用把哪一种实现装到这个位置,还要查看插件配置。

把这些关系分开,阅读路径就清楚了:

地图回答的问题文件读取的例子它没有直接回答什么
目录归属哪个代码范围负责这项能力fs、fs-local、tool-fs 同属 packages/fs/包是否互相依赖、应用加载了谁
包依赖一个包需要哪些代码或共享接口tool-fs 与 fs-local 都声明 dsh-fs 为 peer 依赖,要求使用方提供兼容的共享包服务实例如何装配、函数调用顺序
运行时服务关系消费方使用什么服务,谁提供它tool-fs 通过 ctx.fs 使用已装配的文件系统所有代码分支、实际执行结果

下面可以切换这三种视角。目录图只画文件系统家族的三个代表包;依赖图只保留它们与 dsh-fs 有关的两条 peer 依赖箭头;服务图则展示共享 base 配置所选择的文件系统提供者。它们分别依据目录说明、工具 manifest与base patch。省略的关系不代表不存在。

切换目录归属、包依赖与服务装配,比较同一项文件系统能力的三种关系 · 在独立窗口中查看

一次调用的顺序,需要另查执行函数和事件处理。包依赖图里出现 a → b,只能先按该图的定义理解;它没有说明每次操作都会调用 b,也没有证明某个提供者已被加载。

根目录先帮助你缩小搜索范围 #

dsh 使用单仓库管理多个包。pnpm-workspace.yaml将 vendor/*、packages/*/*、apps/*、原生包和部分构建、文档项目纳入同一工作区。目录承担的职责不同,阅读时可以先用当前问题选择入口。

范围在固定版本中负责什么什么时候进入这里
apps/CLI、Web、Desktop 等应用入口与产品装配找启动入口、界面行为和应用配置
packages/按能力家族组织的 Harness 包找服务、工具、状态与实现
vendor/源码随仓库保存的 Cordis 及其基础库追踪框架的服务、事件和加载机制
docs/架构、子系统、开发与扩展文档建立边界,再定位源码
scripts/构建、生成、检查等仓库自动化解释生成物和验证规则的来源
native/原生源码与平台包追踪相关系统能力和发布边界
python/Python SDK 与携带 dsh 的运行时追踪 Python 调用、协议与运行时选择
benchmarks/、website/性能检查材料与文档站点检查基准方法或文档呈现方式

这是一张选入口的表,不是整个目录树。具体包表以 packages/README.md及各家族 README 为准,避免在文章里维护一份很快过期的完整清单。

两个容易跳错的入口值得先说明。

vendor/ 中的 Cordis 是项目保存的源码副本。它的 manifest 和本地修改记录列出了上游来源与修改。遇到服务访问或加载行为的问题,先读本仓库版本,才能知道应用使用了什么实现。

Python SDK 在这里通过标准输入输出上的 JSON-RPC 驱动配套 dsh 运行时;JSON-RPC 是以 JSON 消息表达远程调用的协议。Python 目录说明给出了客户端与运行时的分工。如果你要研究 Agent 的执行循环,仍需继续进入共享 Harness 代码。

一个能力家族里,包名分别表示什么角色 #

文件系统家族的 README先给出角色,再列包名。理解三个角色以后,才能判断该读哪一层。

服务定义描述使用者能够调用哪些方法。在 packages/fs/fs/ 中,FileSystem 定义文件系统能力,并以 fs 这个键注册为 Cordis 服务。应用装入具体实现后,工具就可以通过 ctx.fs 取得该服务,不必在调用处固定某个本地实现。

服务提供者实现接口。packages/fs/fs-local/ 中的 LocalFileSystem 处理本机文件;packages/fs/fs-sandbox/ 中的 SandboxedFileSystem 在它的基础上加入写入范围等策略。不同实现的行为和限制,要分别查看代码。

消费方使用服务完成自己的工作。packages/fs/tool-fs/ 注册模型可见的 read、write、edit 等工具,负责参数、读取窗口和结果格式;真正的文件访问交给 ctx.fs。

这三个角色是能力边界文档中的组织方式。原文称可替换的能力边界为 seam,你可以把它理解为消费方与具体实现之间约定好的接口。一个包也可能承担多个角色,不能只看命名猜完整关系。

此外,家族里还可以有独立策略插件。fs-observation-policy 通过 fs/* 事件记录已读状态并约束修改;工具没有把它声明为必需服务。家族 README 明确说明,移除这个策略会改变修改行为,工具仍可使用提供者。这意味着检查实际行为时,除了接口与实现,还要确认哪些策略参与了装配。

沿着 read 找到真正读取文件的代码 #

现在把问题缩小为:固定版本中,一次普通文本读取,从工具进入哪个文件系统实现?

工具入口声明依赖并注册能力 #

tool-fs/src/index.ts声明了所需服务。以下是一行源码节选,需要在插件环境中使用,不能独立运行:

export const inject = ['tools', 'fs', 'systemPrompt', 'workingDirectory']

apply() 验证读取限制等配置,然后调用各工具的注册函数。read 的主体位于 src/read.ts,经 ctx.tools.register() 注册名称、参数和执行函数。

这里可以区分两条线索:导入告诉你代码引用了什么;inject 告诉你插件激活时需要哪些服务。它们解释的是不同层次的依赖,不能把 package.json 中的每个 peer 名称都直接翻译成这份注入列表。

执行函数决定怎样调用接口 #

execute() 先解析参数,再交给 resolveRegularReadTarget()解析路径并检查目标是不是普通文件。这个辅助函数使用 ctx.fs.resolve() 和 ctx.fs.stat(),也会处理目标不存在的情况。

随后,读取方式由文件大小信息决定。以下节选保留了该分支,需要上下文中的变量与服务,不能独立运行:

const chunks = info.size === undefined || info.size >= caps.streamMinSize
  ? await ctx.fs.streamText(target, exec.signal)
  : [await ctx.fs.readText(target, exec.signal)]

大小未知或达到阈值时,工具按片段读取;否则一次读取全文。工具接着从读取内容中选出本次要返回的行,添加行号并限制返回大小,这就是“读取窗口”,随后返回结构化结果,并发出 fs/observed。源码中的读取分支说明:如果问题是“返回的行号或截断格式不对”,应继续读窗口与渲染代码;如果问题是“读到了哪个路径、底层怎样取内容”,应继续查文件系统服务。

服务定义给出契约,提供者落实读取 #

FileSystem继承 Cordis 的 Service,构造时使用 super(ctx, 'fs')。它定义了接口和共同词汇。

在 LocalFileSystem中,readText() 转给 readWholeText(),streamText() 转给 streamWholeText()。若还要查这些方法怎样从本机文件取得数据,就继续进入同目录的 fsio.ts。

到这里,我们已经找到了本地读取实现,但还没有确认某个应用装配的 ctx.fs 就是它。接口能由谁实现,与这次应用选择了谁,需要分别检查。

共享 base 选择的是带策略的提供者 #

packages/bundle/base/cordis.patch.yml包含 fs-sandbox 配置项,加载 @deepseek-ai/dsh-fs-sandbox。对应类继承 LocalFileSystem,本篇的文本读取方法沿用父类实现;写入和编辑则有自己的策略处理。

因此,在这个未被后续 patch 替换的装配片段中,消费方取得的服务来自带策略的提供者,读取代码仍可沿继承关系追到 fs-local。要说明实际启动配置,还必须继续检查 profile 的 bundle 顺序与覆盖层。

架构文档说明,profile 是一份有名字的应用组合配置;bundle 提供一组插件配置及配套代码,用户还可以用 patch 修改组合结果。因此,后加载的配置可能改变前面选择的服务提供者。它还指出 sdk-minimal 使用独立组合,并不应用共享 base。本文选取 base 解释追踪方法,不能据此宣称所有 dsh 应用都加载相同的文件系统提供者。

package.json 和依赖图应该怎样读 #

文件读取的例子已经说明了服务关系。现在回头看包的描述文件 manifest,就能解释它为什么没有直接告诉我们完整调用路径。

三种依赖字段有不同用途 #

tool-fs/package.json的 peerDependencies 包含 @deepseek-ai/dsh-fs、dsh-tools 等包,而 dsh-fs-local 出现在 devDependencies 中。这里没有一条 tool-fs 对 fs-local 的生产依赖把工具固定到本机实现。

dependencies 记录普通运行依赖,例如工具用来格式化差异的库;devDependencies 记录开发或测试所需依赖。peer 依赖通常表示需要由使用方提供兼容包;在 dsh 的包约定里,它还承担了共享服务接口的组织含义:扩展依赖服务定义,具体提供者由装配选择。

一个字段仍不能解释所有运行条件。某项 peer 依赖可能用于类型或可选能力,插件也可能在代码里进行条件注册。例如 tool-fs 的 read_image 还要等待附件服务。判断能力何时可用,需同时读 manifest、插件入口和相关服务访问。

官方 module graph 只画了 peer 边 #

docs/module-graph.md开头给出了精确口径:它展示 Harness 包之间的共享实例 peer 依赖,普通运行依赖与仅用于开发的关系没有画入。a → b 表示包 a 把包 b 声明为 peer。

生成器的包收集代码读取 packages/*/*/package.json,取出 peerDependencies,再筛选 @deepseek-ai/dsh- 前缀。这也解释了为什么图里没有把整个 vendor/ 和所有外部库展开。

所以它适合寻找共享接口和潜在影响范围。改了 dsh-fs 的契约,可以沿反向关系查看有哪些包声明依赖它;至于具体哪些路径受影响,还要逐个检查消费代码。若要计算安装或打包所需的完整依赖集合,必须考虑其他字段和发布入口,这张图的口径不足以回答。

服务相关的另一份图是 docs/capability-seams.md。它组织服务定义、已知实现与直接消费方,适合从 ctx.fs 这样的键反查角色。要确认某次启动的具体实例,仍需回到装配配置。

文档地图按当前问题选择入口 #

源码和文档应该互相校对。dsh 的包 README 往往包含用途、配置、源码地图、模型可见行为和限制;子系统文档则把多个包共享的契约集中解释。读文档时,先找它负责回答哪一类问题。

你要确认什么优先入口读完后继续检查
项目怎样组合,运行系统如何分工架构文档配置与插件入口
服务、事件、插件如何工作Cordis primer本仓库 vendor/cordis/ 实现
一个家族包含哪些角色packages/<家族>/README.md相关包 README
某个包支持哪些参数、输出和扩展位置该包 README 与 src/对应函数及相关测试
多包共享什么协议或状态规则docs/subsystems/定义方与各个消费方
包之间共享哪些接口docs/module-graph.mdmanifest 与调用点
谁定义、实现和使用某服务docs/capability-seams.mdprofile、bundle 与 patch
怎样准备环境、构建与验证开发指南当前脚本和检查入口

比如“编辑前为何必须先读取文件”,应进入文件系统子系统文档和观察策略;“读取结果为什么截断”则先查工具的配置与窗口构造。两者同属文件系统家族,负责行为的代码范围却不同。

文档还会链接设计记录、故障复盘和内部维护说明。这些材料能解释某项决策当时的约束;当前行为仍以固定版本的契约和实现为依据。测试文件可以帮助发现失败条件,但读过断言只说明知道它检查什么,不能报告为运行通过。

用只读检索复核这条路线 #

下面的命令从 dsh 仓库根目录执行,只读取源码,不安装依赖,不构建、不启动应用。需要 Git 和 ripgrep(命令名为 rg);本次已在固定基线上执行。

先确认版本并列出代表包文件:

git rev-parse HEAD
rg --files packages/fs/fs packages/fs/fs-local packages/fs/tool-fs

版本应对应 d743267388641bc76f17c45ce8b4c231aed1d32c。文件列表中能找到各包 manifest、README 和 src/;此命令用于定位,文件数量不作为稳定结论。

再从消费方追到服务定义与实现:

rg -n 'export const inject|ctx\.fs\.(readText|streamText)' \
  packages/fs/tool-fs/src/index.ts packages/fs/tool-fs/src/read.ts

rg -n 'class FileSystem|class LocalFileSystem|readText\(|streamText\(' \
  packages/fs/fs/src/index.ts packages/fs/fs-local/src/index.ts

rg -n 'class SandboxedFileSystem|fs-sandbox' \
  packages/fs/fs-sandbox/src/index.ts packages/bundle/base/cordis.patch.yml

第一组会找到工具需要的服务和文本读取调用;第二组定位接口、提供者与读取方法;第三组定位继承关系和 base 中的提供者配置。它们串起的是源码定位路线,没有触发一次实际文件读取。

最后确认包关系与图的生成口径:

rg -n '"peerDependencies"|"devDependencies"|"@deepseek-ai/dsh-fs"|"@deepseek-ai/dsh-fs-local"' \
  packages/fs/tool-fs/package.json packages/fs/fs-local/package.json

rg -n 'peerDependencies|startsWith' scripts/package-graph.ts

如果检索没有结果,先核对版本、文件位置与正则,不要直接判定能力被删除。若要进一步执行测试或启动应用,还需按当前开发文档准备环境;本篇没有做这部分验证。

带着一个问题继续读 #

现在,你已经可以从 read 找到工具参数与窗口处理,沿 ctx.fs 找到接口和本地实现,再用 base 配置解释提供者的选择。换一个问题时,也可以沿同一方法查角色和装配,只把具体入口替换成目标能力。

下一篇将专门研究 CLI、Web、Desktop 与 SDK 的入口和组合关系:应用怎样选择 profile,bundle 层如何叠加,后续 patch 又怎样改变插件树。本篇到这里保留一个明确条件:源码里存在某个提供者,还不足以说明某次启动使用了它。

本次分析以 DeepSeek Harness 0.2.1-alpha.2 的源码与文档为依据,完成只读检索和引用核对。没有运行 dsh、真实模型请求或项目行为测试,也没有验证跨平台隔离效果;上面的读取过程是静态分析,不是运行轨迹。

延伸阅读 #

一起讨论

选中正文中的文字,可以针对这段内容发起讨论。

正在读取讨论…

在 GitHub 查看与管理讨论 ↗

文章讨论