生命不息,折腾不止。这个系列前前后后折腾了十几篇,今天把它们全压成一张可收藏的速查表——从「怎么装」到「半夜自动跑」一条龙查得到,前面零散的命令,看完这篇就串起来了。

从 8 月底接入中转站到现在,我们在 DeepSeek Harness(下称 dsh)这条线上折腾了十几次:装插件、写插件、接 MCP、自定义 Provider、Creator 自进化、权限、沙箱、Trajectory 回放,最后到无头自动化。内容不少,散在十几篇里,真要抄命令得翻半天。这一篇做收口:把命令、配置、环境变量、目录结构全摊成表,当字典用。建议直接收藏。

一、先记住一句话:dsh 是「profile 启动器」

很多人以为 dsh 是个聊天终端,不是。官方定位很明确——dsh 是 profile 启动器,本身不管交互,只负责把你交给某个 profile(一组插件的组合)去跑。整个 CLI 只有四个入口,记住这张表基本就够用了:

命令 干什么
dsh --profile <name> 启动 $DSH_HOME/profiles/<name> 这个 profile
dsh --profile headless "任务" 一次性任务:建会话 → 跑 → 打印最终回答 → 退出(completed 退 0,否则退 1)
dsh web dsh --profile web 的硬编码别名,大家天天用的 Web 界面
dsh plugin --profile <name> <参数> 插件管理,参数原样转发给 pnpm

几个缩写和细节:

  • dsh <name> 等价于 dsh --profile <name>(缩写名必须紧跟 dsh)。
  • 但 plugin 是保留命令,想启动一个恰好叫 plugin 的 profile,得写全 dsh --profile plugin。
  • web 和 headless 都是内置保留 profile 名,首次用会从随附模板初始化:web = base + web-app,headless = base + headless。

二、命令行速查表(收藏这一节就够)

Web 界面

1
2
3
4
dsh web                          # 默认 127.0.0.1:3080
dsh web --port 8080 # 换端口(传 0 让系统随便挑)
dsh web --host 127.0.0.1 # 绑 host;注意不支持 0.0.0.0
dsh web --trusted-host myhost:3080 # 放行其它 host 过浏览器信任墙(可重复)

注意:官方明确 --host 不支持 0.0.0.0,理由是那样会把「远程代码执行」暴露到公网。想给局域网用就配 --trusted-host,别硬绑 0.0.0.0。

无头(headless)

1
2
3
dsh --profile headless "审查 src/server.ts 的鉴权逻辑,给 3 条建议"
dsh --profile headless "跑一遍测试并汇总失败项" > /tmp/out.txt
dsh --profile headless --resume <session-id> # 恢复某个会话继续

任务文本是位置参数(不是 flag),写清楚、可执行,别含糊。

插件管理(转发给 pnpm)

1
2
3
4
5
6
7
dsh plugin --profile web add "github:owner/repo"   # 从 GitHub 装
dsh plugin --profile web add npm:@deepseek-ai/dsh # 从 npm 装
dsh plugin --profile web add link:/path/to/plugin # 本地目录装
dsh plugin --profile web remove <package> # 卸载
dsh plugin --profile web update [package] # 升级(一个或全部)
dsh plugin --profile web why <package> # 查它为什么被装上
dsh plugin --profile web ls # 列出

装第三方子代理 Provider(可选 bundle):

1
2
dsh plugin --profile web add @deepseek-ai/dsh-subagent-codex
dsh plugin --profile web add @deepseek-ai/dsh-subagent-claude-code

配置诊断与补丁

1
2
3
4
5
dsh web --dump-config          # 打印完整合成配置(含 provider 细节,别外传)
dsh web --dump-default-config # 不含你自定义覆盖的那份
dsh --profile tui --patch ./extra.yml # 叠加一层补丁(可重复,后层胜)
dsh --version # 看版本(写自动化时建议 pin 死版本)
dsh --help / dsh web --help # 各层帮助

三、东西都藏在 ~/.dsh,目录结构一目了然

dsh 的所有状态集中在一个「单根主目录」,默认 ~/.dsh,可用 DSH_HOME 重定向。优先级:显式配置路径 > $DSH_HOME > ~/.dsh。

路径 放什么
$DSH_HOME/profiles/<name>/ 每个 profile(一组插件的组合)
$DSH_HOME/settings.yaml 手写的模型/默认设置
$DSH_HOME/.env DEEPSEEK_API_KEY=... 等(环境层兜底)
$DSH_HOME/.credentials.yaml API 密钥
$DSH_HOME/storages/(或 sessions) 会话 / 轨迹存储

配置合成(effective tree)按这个顺序一层层叠,后写的一层胜:

  1. 空 root;
  2. profile manifest 里 dsh.profile.bundles 列出的每个 bundle patch;
  3. profile 自己的 cordis.patch.yml;
  4. 家目录级 $DSH_HOME/cordis.patch.yml(机器级偏好,盖过 profile 层);
  5. 命令行每个 --patch <路径>(按 argv 顺序)。

记住一个坑:patch 是「整行替换」而不是深合并——覆盖会替换掉目标行的完整值,别指望只改一个 key 还能保住同行的其它 key。

内置 bundle 有这几个(永远从当前 dsh 安装里解析):@deepseek-ai/dsh-base、dsh-web-app、dsh-headless、dsh-sdk-app、dsh-sdk-minimal、dsh-acp-app。

四、权限和沙箱:三个档位一句话记

沙箱三档(ctx.sandbox,只管文件写)

档位 允许什么
read-only 完全不写;POSIX 后端额外放行 /dev/null
workspace-write 写限于会话工作区根 + 平台临时目录(默认档);不限制网络和进程可见性
danger-full-access 零隔离,啥都能碰

后端按平台落地:Linux = bwrap / Landlock,macOS = Seatbelt,Windows = ACL。老版本 Landlock ABI 和 Windows ACL 只能「部分强制」,别把每个后端当成一样硬。

权限预设(沙箱档 + 审批策略打包)

预设 沙箱 审批 适用
workspace-write workspace-write ask 工厂默认,日常用
read-only read-only ask 只读任务
danger-full-access danger-full-access never CI / 全自动批量

审批策略真正的枚举只有 ask(先问)和 never(直接执行)两个。切档:会话里发 /permission(裸命令查当前值,带参数切);或用环境变量 DSH_PERMISSION_MODE 在启动时覆盖。

关键一条:预设只是「档位」,不是沙箱本身——设成 danger 也不会绕过沙箱,命令照样过 ctx.sandbox.confine。而且 fail-closed:没有应答者(比如 headless 里没人点弹窗)时,ask 一律按拒绝处理。所以无人值守场景要么 danger-full-access,要么自己配个终端应答器,否则会被卡死。

五、环境变量速查表

变量 作用
DSH_HOME 主目录,默认 ~/.dsh
DEEPSEEK_API_KEY 官方 provider 凭据(也可放 ~/.dsh/.env)
DEEPSEEK_BASE_URL 把模型调用指到任意 OpenAI 兼容端点
DSH_PERMISSION_MODE 覆盖进程级权限预设(新会话默认档)
DSH_TOOLS_MODE native / code / both(原生工具调用 vs Code Mode)
DSH_TELEMETRY_MODE FULL / FEEDBACK_ONLY(遥测策略)
DSH_TELEMETRY_DISABLED 任意非空值硬禁用遥测(优先级最高)
DSH_MODEL / DSH_SYSTEM_PROMPT Python SDK 示例里覆盖模型名 / 系统提示

自定义 provider 那套:apiKeyEnv 默认取 DEEPSEEK_API_KEY,base URL 在落到官方地址前会先看 DEEPSEEK_BASE_URL。所以想接任意 OpenAI 兼容模型(或中转站,比如 ai.aklibk.com 这类多模型 + 按量便宜的服务),往往设这两个变量就够——具体接入套路在《接入中转站》那篇已经写过,这里不重复。

六、自动化契约:退出码 + headless

headless 是自动化(cron / CI / shell)的正主,靠退出码决定脚本走向:

退出码 含义
0 最终 turn 结束原因是 completed(正常完成)
1 没到 completed,或 runner 本身挂了
130 收到第一个 SIGINT(Ctrl+C)后优雅关闭

两个必须记住的坑:

  • 退出码 0 只证明「跑完了」,不证明文件真生成了、测试真过了——外部效果要自己另外验证。
  • SIGTERM 被当正常停止,任何情况都退 0。用 systemd/CI 超时去杀它,和「正常完成」在退出码上分不出来,得另看输出。

最朴素的包装:

1
2
3
4
5
6
7
8
#!/usr/bin/env bash
set -euo pipefail
if dsh --profile headless "跑一遍测试并汇总失败项"; then
echo "Agent 任务完成。"
else
echo "Agent 任务没跑完。" >&2
exit 1
fi

配上 cron 和从 CI secret 注入的 DEEPSEEK_API_KEY,就能做到「写完睡觉、明早收结果」。

七、一句话总结 + 高频坑速记

  • 模型:dsh 是 profile 启动器,不是聊天终端;web / headless 是内置 profile,plugin 是保留命令。
  • 装:dsh plugin --profile <name> add <specifier>,specifier 支持 github: / npm: / link:。
  • 看:dsh web --dump-config 看合成配置,但里面有 provider 细节,别外传。
  • 管:沙箱三档 read-only / workspace-write / danger-full-access;预设 = 沙箱 + 审批(ask / never)。
  • 跑:dsh --profile headless "任务",退出码 0 / 1 / 130,SIGTERM 恒 0。
  • 安全:--host 0.0.0.0 被官方禁掉(会暴露 RCE);无人值守的 ask 会 fail-closed 卡死。
  • 踩坑:旧教程里的 dsh run 已移除,一次性任务统一用 dsh --profile headless "任务"。

十几篇连载到这里收个口,这张表建议收藏,后面折腾别的框架时回来一查一个准。需要更细的某一块(比如 Trajectory 的事件流、Creator 的七个 cordis_* 工具),回对应那篇翻,本篇只做索引。

生命不息,折腾不止。下一篇我们开新坑,起一个「MCP 实战」系列:第一课先把「MCP 到底解决什么问题、它凭啥不是又一个 API 规范」讲透,再手把手跑通你人生中第一个 MCP Server,让任意 Agent 客户端都能调用你本地暴露出来的工具。