deepseek-harness 学习手册
动手实验室Lab 01 · 跑起来 + 读懂你机器上的插件树
30 min

跑起来 + 读懂你机器上的插件树

把仓库跑起来,然后用 --dump-config 把「一切皆插件」这句话变成你屏幕上的一棵可读的树。

最后核对于 v0.1.1-rc.2 · commit b150a551b8 · 2026-08-22

这一课只做一件事:把「一切皆插件」这句话,变成你屏幕上一棵可读的树。

做完你会有两样东西:一份你自己机器上的插件清单,以及一双能看懂 --dump-config 每一行来自哪一层的眼睛。后面四个 Lab 全都建立在这双眼睛上。

1 先跑起来

terminal
$ export DEEPSEEK_API_KEY=...
$ npx @deepseek-ai/dsh web

默认在 http://127.0.0.1:3080 起 Web UI,并在本地启动时打开默认浏览器。走 SSH 的话它只打印 URL——因为转发地址是 SSH 客户端或编辑器在管。不想开浏览器就加 --no-open

第一次跑发生了什么:webheadless 这两个 profile 会从出厂模板自动初始化,落在 $DSH_HOME/profiles/<name>$DSH_HOME 没设就是 ~/.dsh)。其它名字的 profile 必须用 dsh plugin 显式创建,否则会大声失败并提示你该跑哪条命令。

2 把插件树打印出来

这条命令不启动 agent,只打印生效的插件树:

terminal
$ dsh --profile web --dump-config

输出是一份可加载的 YAML 文档,每一段前面有一行 # == 注释,写明这一段来自哪个文件、被哪些 patch 层改过。大概长这样:

--dump-config 输出(节选)yaml
# == @deepseek-ai/dsh-base
- id: web
  name: '@deepseek-ai/dsh-web'
  config:
    searchProvider: deepseek-official      ← 记住这一行,Lab 2 要换它

- id: web-search-deepseek
  name: '@deepseek-ai/dsh-web-search-deepseek'
  config:
    apiKeyEnv: DEEPSEEK_API_KEY

- id: tool-web
  name: '@deepseek-ai/dsh-tool-web'
  config:
    fetch: false                        # 抓取默认关掉,理由写在补丁文件里
    searchTimeoutMs: 60000

这三行取自 packages/bundle/base/cordis.patch.yml 的真实内容(核对于 commit b150a55)。你机器上的顺序和字段可能不同。

3 读懂它:四个问题

拿着你的输出,回答这四个问题。能全部答上来,这一课的目的就达到了。

  1. agent loop 在哪一行?找找 agent-loop。它跟 tool-bashweb 排在同一层,没有任何特殊待遇——这就是「无特权核心」的字面意思。
  2. 哪些行来自 dsh-base,哪些来自 dsh-web-app# == 注释。web profile 的 bundle 列表是 base + web-app
  3. 有没有一行是被后面的层改过的?如果有,注释里会写出改它的每一个 overlay 的标签。
  4. 有没有带 disabled: !!js … 的行?有的话你多半在看 shell 那一组——base bundle 用 disabled: !!js process.platform === 'win32' 把 bash 和 pwsh 两套按平台各挂一套。!!js 在 dump 里是原样保留、不求值的。

4 对比两条 dump 命令

terminal
$ dsh --profile web --dump-default-config   # 只有 bundle 层
$ dsh --profile web --dump-config           # 再叠上 profile / home / --patch

刚装完的机器上这两份应该几乎一样——因为你还没写任何补丁。做完 Lab 2 再跑一次,差异就是你自己那一层。

为什么可以信任这份 dump

它不是一个「大概是这样」的诊断工具。renderConfigDump 走的是 include 自己的解析器和 patch 算法entryListSchema / applyEntryPatches),所以结果等同于 boot() 真正挂载的东西。另外它不会运行任何 app 命令行 provider,所以它显示的是「app 参数被解析之前」的组合树——反过来,一个带着 app 参数的 dump 调用会被拒绝。

5 顺手看一眼你的 profile 目录

terminal
$ ls -a ~/.dsh/profiles/web/
package.json      cordis.patch.yml      node_modules

package.json 里有两样东西:树外插件的 dependencies,和 profile manifest dsh.profile(里面是有序的 bundles 列表)。cordis.patch.yml 就是你的补丁层——现在多半是空的。

一个会咬人的细节:这个文件空着或者只有注释会抛错(它解析出来是「什么都没有」,而不是「一个空列表」)。要停用这一层,写 []

对比即教学

同样这件事,在别家要几步?

dsh 1 条命令,打出完整、可加载、标了来源的组合树。它跟真实启动共用同一套解析与 patch 算法。
OpenCode / Codex 读配置 能看到自己写的配置文件,但「最终生效的完整组合」通常不是一个可打印的产物。
待核对
Claude Code 分散 settings、hooks、MCP、skills 各有各的查看方式;没有统一的「这就是我现在的全部组合」视图。
待核对
这个差异的根源不在工具好坏,在于 dsh 的「组合」本身就是一份数据——它当然可以被打印出来。别家的组合分散在若干个子系统里,打印它需要先发明一个统一模型。
下一课

Lab 2 · 零 fork 换掉一个 provider——把你刚认出来的那行 searchProvider: deepseek-official 换掉。20 分钟。

目录

本页

Lab 01 · 跑起来 + 读懂你机器上的插件树