DeepSeek Harness 到底有多少个包:49 是数错了,真实是 219 个

2026-08-16

先把结论摆在前面:截至 2026-08-16 我们采集的快照 47f9438 里,deepseek-harnesspackages/ 下有 49 个目录,但真正的 npm 包有 219 个。这两个数字都对,只是指的不是一回事。把 49 写成「包数」,是本仓库最容易踩的一个数字坑。

需要先说清楚的限定:这个仓库建立于 2026-08-13,我们采集时距建仓只有三天;根 package.json 的版本是 0.1.0-rc.5,README 自述处于 developer preview,并明写「THERE WILL BE COMPATIBILITY-BREAKING CHANGES」。所以下面所有的目录结构、包名规则、数字,都可能随时变。本文只负责把那一天的快照数清楚,以及告诉你怎么在自己 clone 的仓库里重数一遍。

数错发生在哪一步

packages/ 一打开,ls 出来是 acpapiattachmentbootbundleclientcore……这一排看起来就是包名的东西,49 个。以 monorepo 的常识,packages/<name>/ 一层一个包,是最常见的布局,于是「49 个包」就被写下来了。

问题出在工作区定义上。pnpm-workspace.yamlpackages: 列表实读为这几项:vendor/*packages/*/*native/landlock-runnative/landlock-run/packages/*apps/*websiteexamplespython/sdk-runtime

注意第二项是 packages/*/*,两个星号中间隔着一层斜杠。这意味着 pnpm 在扫工作区成员时,根本不会把 packages/acp/ 本身当作一个包,它扫的是 packages/acp/acp/ 这一层。

我们逐个检查过:packages/ 下这 49 个目录没有一个带 package.json。它们在这个仓库的术语里叫组(group),只是一层分类目录。真正的包在第二层,共 219 个目录,219 个全部有 package.json,缺失数为 0

这条规则在仓库里其实写死过两次,只是位置不显眼。根 AGENTS.md:13 用一行把它写成布局约定:

packages/    @deepseek-ai/dsh-<pkg> workspaces at packages/<group>/<pkg>/

packages/README.md:9 写的是另一半:「Groups hold packages/<group>/<pkg>/; names stay @deepseek-ai/dsh-<pkg>. Group READMEs own package/ctx-key maps.」——组目录的职责是持有包,以及在组 README 里维护包与 ctx key 的对照表;它自己不是包。

自己数一遍

不用装任何东西,clone 下来在仓库根目录跑一段 Python 就够了。这是本文所有数字的口径:

python -c "
import os,glob
pkgs=[p for p in sorted(os.listdir('packages')) if os.path.isdir(os.path.join('packages',p))]
print('dir count:',len(pkgs))                      # 49
subs=[s for s in sorted(glob.glob('packages/*/*')) if os.path.isdir(s)]
print('packages/*/* dir count:',len(subs))         # 219
withpj=[s for s in subs if os.path.exists(os.path.join(s,'package.json'))]
print('with package.json:',len(withpj))            # 219
"

三行输出分别是 49 / 219 / 219。第三行是关键:只要第二层目录数和「带 package.json 的第二层目录数」相等,就说明这一层才是包所在层,没有半成品目录混在里面。

顺手可以再核一件事——219 个包的 nameversion 是否齐整:

python -c "
import json,glob,collections
vers=collections.Counter(); bad=[]
for f in glob.glob('packages/*/*/package.json'):
    j=json.load(open(f,encoding='utf-8')); vers[j['version']]+=1
    if not j['name'].startswith('@deepseek-ai/dsh-'): bad.append((f,j['name']))
print(dict(vers), 'bad:', len(bad))
"

我们跑出来是 {'0.1.0-rc.5': 219} bad: 0:219 个包的版本号全部0.1.0-rc.5,与根 package.json 完全一致,一个例外都没有;219 个包的 name 全部@deepseek-ai/dsh- 开头,scope 统一。这与根 AGENTS.md:100 的约定一致:「Every npm package is @deepseek-ai/dsh-<name>」。

顺带说一句:工作区成员不止 packages/

回头看 pnpm-workspace.yaml 那份列表,packages/*/* 只是其中一项。同一份列表里还有 vendor/*apps/*websiteexamplespython/sdk-runtime,以及 native/landlock-runnative/landlock-run/packages/* 这一对。所以「这个仓库有多少个工作区包」和「packages/ 下有多少个包」也不是同一个问题——后者是 219,前者还要把上面这些加进去。

这份文件里另外几项配置也值得顺手记一下,都是实读值:linkWorkspacePackagestrueoverrides@deepseek-ai/cosmokit 指向 link:vendor/cosmokit@deepseek-ai/schemastery 指向 link:vendor/schemasterypeerDependencyRules.allowedVersions.typescript'>=5 <7'allowBuilds 白名单显式允许 esbuildlefthooknode-ptykoffi 等,同时显式拒绝 @google/genaiprotobufjsnode-addon-require-builtinpatchedDependencies 里有一条 node-pty@1.1.0 的补丁。这些都是配置里写着的值,不代表你装起来会遇到什么,也别拿它们去推断构建行为。

另外一个能帮你摆正比例的数字,同样以 2026-08-16 的快照为准:apps/clipackage.jsondependencies 共 61 个,devDependencies 14 个,peerDependencies 0 个。61 个 dependencies 里非 @deepseek-ai/* 的只有 3 个(commanderjs-yamlnode-addon-require-builtin),其余 58 个中含 5 个 vendored 的 Cordis 包与 53 个 @deepseek-ai/dsh-* 包。也就是说 219 个包里被 CLI 直接依赖的是 53 个,不是全部——这一点需要提醒:我们没有把这 53 个与 packages/bundle/base/cordis.patch.yml 里实际的 plugin 行做交叉核对,所以不能说「CLI 依赖的包就等于启动时装载的插件」。

219 这个数字在别处留下的印子

如果你怀疑 219 是不是我们数法特殊,仓库自己生成的文档里可以交叉对上。

docs/module-graph.mdscripts/gen-module-graph.ts 生成的模块依赖图,首行注释写着「do not edit by hand」,在 2026-08-16 的这份快照里全文 1638 行。我们在里面数出:subgraph group_* 49 个pkg_*["…"] 节点 219 个,文件后半段那张「Package | Group | Depends on」表有 219 行数据行。把 219 个真实包名去掉 @deepseek-ai/dsh- 前缀后与这 219 个节点比对,全部命中,不匹配数为 0

也就是说,49 与 219 在这张生成图里是分工明确的两个层级:49 个 subgraph 是分组框,219 个节点是包。

图里还有一个数字挺有意思:入度(被依赖次数)第一名是 invariants218。219 减去它自己正好是 218,对应 packages/AGENTS.md:18 的一句约定「Every package owns ./invariant.」——数字上是吻合的。排在后面的依次是 session 80、llm 78、agent 58、tools 43。

数错「层」之后,还会顺手推错一件事

把 49 当成包数,通常还会连带产生第二个错觉:包名的尾段就是目录名。这一条在本仓库也不成立。

packages/host/*packages/client/* 这两组的包名要带组前缀,而目录名不带:host/apiproxy 的包名是 @deepseek-ai/dsh-host-apiproxyclient/runtime 的包名是 @deepseek-ai/dsh-client-runtime。这条规则写在 .agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md:74,原文明确写「The package-name tail therefore ≠ the directory name」。

还有跨组不同名的:test-support/client-runtime 的包名是 @deepseek-ai/dsh-client-test-runtime(不是 dsh-test-support-client-runtime);interaction/commands 的包名是 @deepseek-ai/dsh-commands,不带 interaction- 前缀。

实用后果很直接:想装某个包或在 package.json 里写依赖时,不能拿目录路径拼包名,要去那一层的 package.json 里读 name

219 也不是所有事情的分母

数清楚层级之后,还得知道另外几个数字不等于 219,免得下一次又对不上。

  • 组表是 47 行,不是 49 行。 packages/README.md:11-59 的「Group | Role | Release expectation」表共 47 行数据行,表内 47 个组名都对应真实目录、无多余项;与 49 个真实组目录比对,mcp/runtime-diagnostics/ 两组不在表里。同一文件 :61 写着「New packages join existing groups; new groups update their README and this table.」两处不一致,以我们实读的快照为准。
  • 49 个组里有 48 个有组 README。 唯一没有组 README.md 的是 runtime-diagnostics/;而 219 个子包则全部README.mdpackages/README.md:9 那句「Group READMEs own package/ctx-key maps」与这一处的实况不一致,同样只陈述、不延伸。
  • 配置目录里列了 105 个包,不是 219 个。 docs/config-catalog.md 共 3151 行,## 二级标题 108 个,其中 105 个是形如 ## \@deepseek-ai/dsh-xxx` 的包名节,另外 3 个是分类小节:Loadable plugins with no configSeam packages (not directly loadable)Library packages (no plugin entry)`。剩下的包归入那 3 节,我们没有逐个数每节里各有多少个。
  • 强制小节是 218/219。 packages/README.md:69 要求每个包 README 带 ## Known Limitations and Deferred Work 小节,我们逐个 grep 219 个包 README:218 个有,缺的那个是 packages/util/brand/README.md。该文件同时提到有一份 allowlist 在 scripts/verify-package-readme-limitations.ts,我们没有去读它的内容,所以不知道这个包是否已在白名单里。
  • AGENTS.md 的布局清单也不是 49 项。 AGENTS.md:11-55 那段 text 围栏里缩进两格的组名共 34 个,其中 self-modification/:35)与 support/:46)在 packages/ 下都没有同名目录;另有 17 个真实组目录没有出现在这段清单里。该段末尾 :57 把完整分组指向了 packages/README.md

这几条只是想说明一件事:同一个仓库里,「包」这个词在不同文档里被数了好几遍,口径各不相同。 引用哪一个数字,就得说清楚它是从哪儿数的。

规模感放在最后

如果你要的只是一个体量印象,截至 2026-08-16 的快照:49 个组、219 个子包,packages/.ts.tsx 文件共 2,240 个、496,340 行(递归统计,排除 node_modules/dist/lib/.turbo),其中路径含 /src/ 的 227,637 行、含 /tests/ 的 268,040 行。

组与组之间体量差得很远:client 组 39 个子包、137,889 行,是最大的一块;session 13 个、subagent 11 个、shell 9 个、corehost 各 8 个;另一头,identity 组只有 1 个子包 4 个文件 248 行,runtime-diagnostics 组 1 个子包 3 个文件 540 行。

所以「49」和「219」都别单独拿去用。要讲分层结构就说 49 个组,要讲发布单元、依赖关系、版本号就说 219 个包——中间隔着 pnpm-workspace.yaml 里那一个 *

延伸阅读


本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的 架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-16,对应仓库快照 47f9438(版本 0.1.0-rc.5)。 本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目, 因此不涉及界面外观、操作手感与运行速度的任何描述。 该仓库建立于 2026-08-13,README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更, 文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。