模块定位
DeepSeek Harness 的启动系统解决一个核心问题:同一个产品如何在 Web、headless、demo、用户自定义 profile、外部插件之间复用能力,而不是复制多套入口代码。
源码入口主要在:
apps/cli/src/bin.tsapps/cli/src/args.tsapps/cli/src/profile-boot.tspackages/boot/app-boot/packages/bundle/base/packages/bundle/web-app/packages/bundle/headless/
最重要的事实是:dsh 不直接手写装配所有服务。它解析命令后选择 profile,profile 声明 bundle,bundle 提供 Cordis patch,最终由 Cordis Loader 激活插件树。
从命令到插件树
apps/cli/src/bin.ts 是一个很薄的入口。它做三件事:
- 调用
parseDshArgs()解析参数。 - 按 mode 动态导入对应 runner。
- 对 profile、plugin、dump-config 三类命令分发。
这意味着无关 mode 不会进入当前 dispatch path。例如 dsh plugin 不需要加载 Web 启动逻辑,--dump-config 可以离线渲染组合结果而不真正启动服务。
常见命令:
1 | pnpm dsh web |
Profile 是什么
Profile 是用户机器上的一套运行组合,目录在:
1 | $DSH_HOME/profiles/<name> |
$DSH_HOME 默认是 ~/.dsh。一个 profile 至少包含:
package.json:声明dsh.profile.bundles和外部插件依赖。cordis.patch.yml:用户自己的 per-profile patch。
内置的 web 和 headless profile 首次使用时会自动初始化。其他 profile 通常通过 dsh plugin 管理。
Bundle 是什么
Bundle 是一组可发布的 Cordis 配置层。它通过自己的 package.json 里的 dsh.bundle.patch 指向 patch 文件。
关键 bundle:
| Bundle | 职责 |
|---|---|
@deepseek-ai/dsh-base |
基础 Agent runtime:模型、工具、持久化、设置、权限、FS、Shell、sandbox、subagent provider |
@deepseek-ai/dsh-web-app |
浏览器界面:webserver、API proxy、workspace、client plugin roster、静态前端 |
@deepseek-ai/dsh-headless |
一次性运行任务并输出最终答案 |
启动时的组合顺序是:
1 | flowchart LR |
Patch 匹配 entry id 后替换整个 config。这不是 deep merge,所以覆盖配置时必须重写要保留的字段。
app-boot 做了什么
packages/boot/app-boot 是启动公共层。它负责:
- 解析 config/profile/bundle 路径。
- 加载
.env。 - 初始化内置 profile。
- 组合 patch layers。
- 挂载
cordis:include和cordis:group。 - 等待 Loader settle。
- 把未加载、未激活、失败插件转成清晰启动错误。
- 对长驻进程监听用户 patch 变化。
- 在 Web 模式下加入 harness source prompt section。
它还有一个很重要的失败处理:如果启动过程中某个插件已经把终端切到 raw mode,而另一个插件失败,boot() 会释放已经挂载的部分树,避免用户 shell 被留下异常状态。
Web 模式特殊在哪
dsh web 本质上是 --profile web。web-app bundle 在 base 之上增加:
webserverapiproxy- workspace 服务
- projection cache
- storage
- frontend-static
- client plugin roster
web-runtimeglue plugin
Web app 参数放在 launcher mode 后面:
1 | pnpm dsh web --port 8080 |
这些参数由 web bundle 里的 startup provider 消费,而不是由 CLI launcher 自己解释。
常见坑
第一,修改 profile patch 时不要以为是 deep merge。它替换整段 config,漏字段就等于删除字段。
第二,bare plugin specifier 的解析和安装位置有关。生产安装和源码运行都有各自的 module resolution 约束。
第三,--dump-config 是定位问题的第一工具。不要猜某个 plugin 有没有挂载,直接 dump 当前 profile。
第四,Windows 和 POSIX 的 shell 栈不同,但注册同一个 service key。覆盖时必须保证只启用一个 shell family。
继续读源码
apps/cli/src/bin.tsapps/cli/src/args.tspackages/boot/app-boot/README.mdpackages/bundle/base/cordis.patch.ymlpackages/bundle/web-app/cordis.patch.ymldocs/architecture.md#profiles-and-bundles