十分钟跑通 DeepSeek Harness:npx、Web UI 与 dump-config
专栏:DeepSeek Harness · 第 2 / 14 篇上一篇我们给了整个专栏的学习路径。按照路线,第一阶段的目标只有一个:不读任何核心代码,先建立直觉。这篇带你把 dsh 跑起来。
dsh 的命令面
前置条件只有 Node(要求 ^22.19.0 || >=24.0.0)。直接:
npx @deepseek-ai/dsh web
首次运行会下载包,然后默认在 http://127.0.0.1:3080 起 Web UI(本机启动还会自动开浏览器;通过 SSH 启动时只打印 URL——因为它判断出浏览器不在你眼前)。加 --no-open 可以只起服务不开浏览器。
dsh --help 的真实输出值得逐行读,因为它就是产品形态的目录:
Usage: dsh [options] [command] [args...]
dsh: boot a DeepSeek Harness profile — an ordered stack of plugin-bundle patch
layers under your own overrides.
Arguments:
args arguments for the booted profile's app
Options:
-V, --version output the version number
--profile <name> the profile under $DSH_HOME/profiles to boot
--patch <path> extra patch-list overlay applied after the profile
layer (repeatable)
--dump-config print the composed profile tree and exit
--dump-default-config print the profile tree without its user layer or
--patch overlays and exit
Commands:
web [options] [args...] boot the web profile (alias of --profile web)
plugin [options] [args...] manage a profile's plugins by forwarding the
remaining arguments to pnpm
Examples:
dsh --profile web boot the web profile
dsh --profile headless "run the tests" answer one task, print result, exit
注意 help 里的第一句自我介绍:「an ordered stack of plugin-bundle patch layers under your own overrides」——启动一个 dsh,就是按序叠加插件组合层。这句话是第 4、5 篇的伏笔,先记住。
三种值得体验的启动方式
1. Web(dsh web):完整的浏览器 GUI。开几个会话、让它读写文件、观察审批卡片弹出的时机,看一遍工具调用的展示形态。
2. headless(一次性运行):
dsh --profile headless "用一句话介绍你自己"
无界面、不开端口、不留后台进程:任务文本是唯一的位置参数,最终答案写 stdout,推理过程以 dsh: reasoning: 前缀流到 stderr。退出码有明确语义——最终轮次以 completed 结束 → 0;aborted、error 或根本没跑出轮次 → 1。这让它天然适合脚本和 CI。
3. dump(不启动的体检):
dsh --profile web --dump-default-config | head -40
我在这台机器上跑的真实输出(节选):
# == @deepseek-ai/dsh-base
- id: timer
name: '@deepseek-ai/cordis-plugin-timer'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: hmr
name: '@deepseek-ai/cordis-plugin-hmr'
config:
root:
- .
disabled: true
# == @deepseek-ai/dsh-base
- id: llm
name: '@deepseek-ai/dsh-llm'
- id: session
name: '@deepseek-ai/dsh-session'
- id: session-title
name: '@deepseek-ai/dsh-session-title'
config:
fallbackMaxWords: 5
...
这份 YAML 就是「运行中的 dsh」的全部:一个插件条目列表,每行有 id(稳定标识)和 name(npm 包名),config 是该插件的环境变量求值后的配置,# == 注释标出每一行来自哪一层、被谁改过。web profile 的默认树共 162 个条目。把这份输出当导游——之后读源码时遇到的每个包,都能在这里找到它的行。
--dump-config 与 --dump-default-config 的区别:前者包含你的用户配置层和 --patch 覆盖(反映「实际会跑成什么样」),后者只打印发行版自带层(用于用户配置损坏时的诊断)。两者互斥,且都不接受应用参数——因为 dump 不会真正启动,无法反映应用参数的效果,打印一棵会误导的树不如直接报错。
启动背后发生了什么
先看一张入门视角的结构图——一个运行中的 dsh 由什么组成(后续每篇都会填细节):
不展开(第 5 篇细讲),但先给一张全景时序图,你以后每次 dsh web 心里都能过一遍它:
几个值得留意的细节:
- 启动器只管组装,应用自己管自己。
dshbin 只认识--profile/--patch/--dump-*这几个自己的 flag;第一个不认识的 token 之后的全部参数原样交给被启动的应用。所以dsh --profile web --help打印的是 web 应用的帮助。 - 环境变量有出处。
.env文件里的敏感变量不能覆盖继承环境(PATH、DSH_*等只允许来自继承层),代理变量例外。整个进程用一份冻结快照,插件拿到的是不可变的环境。 - 退出是设计过的:SIGTERM → 优雅退出(码 0),SIGINT → 130;第一次信号开始优雅关闭(上限 5 秒),第二次信号强制走。
体验时最值得做的一件事:改一个设置(比如模型),然后 dsh --profile web --dump-config | grep -A5 <相关 id> 看哪一行变了。配置即全部的世界里,学会 diff 配置树就学会了排除一半的问题。
你体验到的功能 ↔ 源码模块对照
| 你刚才用到的 | 对应源码位置 | 详解篇章 |
|---|---|---|
| Web 界面 | packages/client/(浏览器侧)+ packages/host/(宿主 API) | 第 13 篇 |
| 会话历史 | packages/core/session/(仅追加事件日志) | 第 6 篇 |
| 模型切换 | packages/llm/(适配器 seam) | 第 11 篇 |
| 工具执行与审批 | packages/core/tools/ + packages/interaction/ | 第 9、12 篇 |
| 162 行配置树 | packages/bundle/ + packages/boot/app-boot/ | 第 5 篇 |
dump-config 的每一行都能在这张表里找到归宿——后续篇章就是把这张表逐行读透。
常见小坑
常见小坑
- SSH 里启动不弹浏览器是特性不是 bug;
--no-open显式关掉。 --patch是可重复的单值参数(--patch a.yml --patch b.yml),按顺序叠加。- 遥测开关
DSH_TELEMETRY_DISABLED设成任何非空值(包括0、false)都是禁用——它只判断非空。 - web 应用会拒绝
--host 0.0.0.0(安全考虑,想对外暴露请走反向代理 + 认证)。
下一篇我们把这 162 行配置树背后的仓库真正构建起来。