DeepSeek Harness 启动与组合:Profile、Bundle、Patch 如何拼出运行时
JotaroRD

Profile 启动链路

模块定位

DeepSeek Harness 的启动系统解决一个核心问题:同一个产品如何在 Web、headless、demo、用户自定义 profile、外部插件之间复用能力,而不是复制多套入口代码。

源码入口主要在:

  • apps/cli/src/bin.ts
  • apps/cli/src/args.ts
  • apps/cli/src/profile-boot.ts
  • packages/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 是一个很薄的入口。它做三件事:

  1. 调用 parseDshArgs() 解析参数。
  2. 按 mode 动态导入对应 runner。
  3. 对 profile、plugin、dump-config 三类命令分发。

这意味着无关 mode 不会进入当前 dispatch path。例如 dsh plugin 不需要加载 Web 启动逻辑,--dump-config 可以离线渲染组合结果而不真正启动服务。

常见命令:

1
2
3
4
pnpm dsh web
pnpm dsh --profile headless "分析这个仓库"
pnpm dsh --profile web --dump-config
pnpm dsh plugin --profile web add <plugin>

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
2
3
4
5
6
flowchart LR
A["empty entry list"] --> B["bundle patches"]
B --> C["profile cordis.patch.yml"]
C --> D["$DSH_HOME/cordis.patch.yml"]
D --> E["--patch overlays"]
E --> F["Cordis Loader activates plugins"]

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 之上增加:

  • webserver
  • apiproxy
  • workspace 服务
  • projection cache
  • storage
  • frontend-static
  • client plugin roster
  • web-runtime glue 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.ts
  • apps/cli/src/args.ts
  • packages/boot/app-boot/README.md
  • packages/bundle/base/cordis.patch.yml
  • packages/bundle/web-app/cordis.patch.yml
  • docs/architecture.md#profiles-and-bundles