动手用 dshDeepSeek Harness 上手指南

动手用 dsh

DeepSeek Harness 的配置与使用分享:从装上跑通,到写 patch、接自己的工具链。整理自 2026-09-18 ~ 09-22 的真实配置与踩坑记录,也给 agent 留了一份速查。

基线 dsh 0.1.5-rc.2 Node v22.22.2 macOS 实测 含 4 个可跑 Lab 附 agent 速查

030 秒上手

如果你只有三分钟,读这一节就够了。剩下的章节按需查。

一句话理解

dsh 是 DeepSeek 官方的 Agent 运行时框架,不是普通 CLI。官方公式:

Model + Harness = Agent

模型只是其中一个插件;工具、UI、沙箱、记忆全都可以替换。

最小上手三步

npm i -g @deepseek-ai/dsh,确认 Node 22。

在项目目录里执行 dsh web,浏览器打开 3080。

要加功能就写 patch 或装 bundle,不改源码。

三条铁律

  1. 先跑通再改造:先确认现有项目里 AGENTS.md / CLAUDE.md 被读到。
  2. 装功能 = 写 patch:不是装插件、更不是改源码。
  3. 实验别碰真的 ~/.dsh:用项目内 DSH_HOME
本页的证据分级

本机实测 在 0.1.5-rc.2 上真跑过、看过输出。官方文档 来自 npm 包内随附的 README / 源码。 经验判断 从多轮排障里总结,不一定对所有版本成立。dsh 迭代很快,动手前用文末的自查命令核一遍版本。

1先分清三条线,别装错包

市面上叫"DeepSeek 编程工具"的东西有三条完全不同的线,名字很像,血统不同。搞混了会浪费一整天。

名字谁做的形态该不该用
Deep Code
deepcode-cli
第三方开源项目,被 DeepSeek 官方 API 文档收录推荐 终端 CLI + VS Code 插件,轻量、开箱即用 想 5 分钟跑起来,选它
DSH
@deepseek-ai/dsh
DeepSeek 官方 CLI + Web GUI + SDK,本体是 Agent 运行时 想当长期主力、接自己工具链,选它
社区魔改版
<user>/deepseek-harness
第三方 fork 跟随上游,修 bug / 加功能 / 去限制 上游有硬 blocker、你能读 diff 时再考虑
装之前核对 npm scope

官方包是 @deepseek-ai/dsh。社区里出现过 @deepopen/cli@opendeep/cli 这类只差一两个字母的相近名,属于典型的高风险命名。不确定时先跑:

bash · 核实官方包
npm view @deepseek-ai/dsh version
npm view @deepseek-ai/dsh repository.url
npm view @deepseek-ai/dsh dist.tarball
更稳的姿势:只写 patch,不 fork 整个 dsh

90% 的"我想改掉这个行为"都能用一份 YAML patch 解决。fork 之后每次上游更新你都要重新合并;patch 只需要重新验证一次。

2心智模型:一切皆是 patch

这是全文最重要的一节。理解配置树之后,dsh 的行为就不再神秘。

Profile = 产品形态

web / headless / acp / sdk / sdk-minimal。换 profile 就是换一整套产品,不是换开关。

Bundle = 插件组合包

一组插件的预设配置,比如 @deepseek-ai/dsh-base + @deepseek-ai/dsh-web-app。你在 package.jsondsh.profile.bundles 里按顺序列出。

Patch = 覆盖层

一个 YAML 顶层数组,按插件 id 改配置、禁用插件、插入新插件。你日常真正动手的地方。

配置树按这个顺序叠加(后者覆盖前者)

1
bundle 层
dsh.profile.bundles 里按顺序叠加,如 dsh-base → dsh-web-app
+
2
profile 自己的 patch
~/.dsh/profiles/<name>/cordis.patch.yml,日常改这里
+
3
home 级 patch(优先级高于 profile 层)
~/.dsh/cordis.patch.yml,对所有 profile 生效,适合放机器级偏好
+
4
命令行 --patch 覆盖层
临时实验用,可重复出现,按 argv 顺序叠加
+
5
telemetry 开关
DSH_TELEMETRY_DISABLED 非空即关闭遥测
常见误解:home patch 不是"默认值"

最外层(home)patch 的优先级高于 profile 层。也就是说,~/.dsh/cordis.patch.yml 里的配置会盖掉 profiles/web/cordis.patch.yml 里的同 id 配置。这是官方源码 allPatches() 的顺序,网上有些笔记写反了。

启动器的参数规则

启动器只解析自己的 flag;遇到第一个不认识的 token 之后,全部原样交给 profile 里的 app。所以:

bash · 归属要分清
dsh --help                    # 启动器自己的 help
dsh --profile web --help      # web app 的 help(--port 在这里)
dsh web --port 8080           # --port 属于 web app,不属于启动器
dsh --profile headless "跑测试"  # 之后全是任务文本

不同 profile 的体量差别

本机 0.1.5-rc.2 实测,--dump-default-config 里的插件条目数:

Profile插件条目独有内容
web152client-* / ui-*、directory-picker、agent-presets
headless87headless-runner、headless-startup
acp86Agent Client Protocol stdio

数字会随版本变化,但"web 多出来的全是界面插件"这个结构不变。这就是"换 profile 换产品"的直观证据。

3安装与启动

目标:让 dsh 在你的普通终端和 GUI 环境里都能跑起来,然后跑通一个真实项目。

前置条件

用 Node 22,别用旧 Node

dsh 的启动 shim 是 #!/usr/bin/env node,会跑在 PATH 里第一个 node 上。老 Node(如 16)直接 ESM SyntaxError,而且 dsh 内部再调 pnpm / corepack 时会继续命中旧 Node。本机实测:Node v22.22.2 正常。

bash · 安装
node -v                              # 期望 v22.x
npm i -g @deepseek-ai/dsh
which -a dsh                         # 看会不会命中多个版本
dsh --version                        # 期望 0.1.5-rc.2 或更新
npm config get prefix                # 确认 <prefix>/bin 在 PATH 里

第一次启动

先 cd 到你的项目目录

dsh 把启动时所在的目录当作默认 workspace 根,沙箱的 workspaceRoot = process.cwd()。在哪个目录启动,哪里才是可写工作区。

启动 Web GUI

默认监听 127.0.0.1:3080,启动后终端会打印带 token 的 URL,浏览器打开即可。用 --no-open 可以不自动开浏览器,用 --port 换端口。

bash · 启动
cd ~/Projects/my-project
dsh web
dsh --profile web --port 8080 --no-open
dsh --profile headless "这个仓库怎么跑测试"   # 一次性任务,答案打印后退出

确认项目指令被读到

在你现有项目里放 AGENTS.mdCLAUDE.md,然后问一句"你现在知道哪些项目规则",确认它读到了再往下走。

web app 的 flag(0.1.5-rc.2)

flag作用
--host <host>绑定地址
--port <port>监听端口,默认 3080;0 表示让系统分配
--no-open不自动打开浏览器
--trusted-host <authority>额外允许的 host / host:port,可重复
web 没有 --resume

恢复会话在 Web UI 的会话列表里点。--resume 是终端类 app(如社区 TUI)的参数,别写到 web 上。想看 web app 自己的 flag,用 dsh --profile web --help

"终端里能用、别的 App 里用不了"的经典原因

GUI / daemon 启动的进程拿到的是精简 PATH,可能先命中 /usr/local/bin/node 这种旧版本(本机实测有 2022 年 pkg 残留的 v16.13.2)。解法是给 dsh 写一个 launcher:exec 绝对路径的 Node 22,并无条件把该 Node 的 bin 目录 prepend 到 PATH(dsh 派生的 pnpm / corepack 也才跟着用对 Node)。详见第 10 节。

4命令速查

打印出来贴在屏幕边。所有命令都在本机 0.1.5-rc.2 上核过。

启动 / 退出

bash · 启动
dsh web                                  # = dsh --profile web,默认 3080
dsh --profile web --port 8080 --no-open
dsh --profile headless "任务"             # 一次性:答案走 stdout,推理走 stderr
dsh --profile acp                        # Agent Client Protocol stdio
dsh --profile sdk                        # SDK JSON-RPC stdio

# 彻底重启 web(只刷新浏览器不会加载新插件)
kill $(lsof -tiTCP:3080 -sTCP:LISTEN)
dsh web

看配置(不启动也能看)

bash · 检查配置树
dsh --profile web --dump-default-config   # 只看 bundle 默认层
dsh --profile web --dump-config           # 叠加你的 patch 后的完整树
dsh --profile web --dump-config | grep -c '^- id:'   # 数插件条目,本机 web = 152
dsh --profile web --dump-config | grep -B2 -A6 'patched by'  # 看哪条 patch 生效了
--dump-config 有副作用

即使只是 dump,dsh 也会重写 profiles/<name>/cordis.yml(空根文件)。所以查看配置同样受 dsh 自己的文件沙箱限制;做实验用项目内 DSH_HOME

Profile 与插件

bash · profile / 插件
dsh --profile rescue --from-default-profile web   # 基于 web 模板建新 profile
dsh plugin --profile web add <npm-pkg>            # 转发给 profile 目录里的 pnpm
dsh plugin --profile web remove <npm-pkg>         # remove 会自动从 bundles 摘除
dsh --profile web --patch ./extra.yml             # 临时叠加一个覆盖层

找东西

你想找路径
全局工作记忆~/.dsh/AGENTS.md
home 级 patch~/.dsh/cordis.patch.yml
某个 profile 的 patch~/.dsh/profiles/<name>/cordis.patch.yml
profile 的插件清单 / 顺序~/.dsh/profiles/<name>/package.json
插件安装策略~/.dsh/profiles/<name>/pnpm-workspace.yaml
会话记录(zstd 压缩)~/.dsh/sessions/<workspace-slug>/session-*/session.v3.jsonl.zstd
按天 token / 成本~/.dsh/dsh-usage/usage-ledger.json
任务看板账本~/.dsh/task-board/ledger-v2.json
bash · 读会话记录
# 会话是 zstd 压缩的 JSONL,逐行一个事件
zstd -dc ~/.dsh/sessions/*/session-*/session.v3.jsonl.zstd | head
# 事件类型:user/message, assistant/message, tool/call, tool/result, turn/start ...

5四个 Lab:把配置树玩一遍

下面四个实验可以在任意本地目录复现,设计原则是绝不碰真实的 ~/.dsh:在一个临时目录里建一个假的 DSH_HOME,跑完整个删掉。

为什么必须用假 DSH_HOME

dsh 有自己的文件沙箱。真实 ~/.dsh 在会话工作区之外,直接跑会报 EPERM: operation not permitted, mkdir '.../.dsh/profiles/...'。这不是 dsh 坏了,是策略在拦。实验用:

bash · 假 DSH_HOME
# 随便建一个实验目录,别在 ~/.dsh 里折腾
mkdir -p ~/dsh-lab && cd ~/dsh-lab
export DSH_HOME="$PWD/.dsh-home"

# Lab 1:看 bundle 默认层
dsh --profile web --dump-default-config > default.yml
grep -c '^- id:' default.yml

# Lab 2:写一个 patch 再叠加(见下面的写法)
dsh --profile web --patch ./disable-timer.yml --dump-config | grep -B2 -A4 'patched by'
Lab做什么你会学到
1 dump 默认配置树,数插件条目和分层标记 一个 profile 到底装了哪些插件、每个插件都有 id
2 叠加 patches/disable-timer.yml patch 如何按 id 精确禁用插件,输出里的 patched by 长什么样
3 叠加 patches/relax-approval.yml 插件配置怎么改;config 是合并而不是整体替换
4 对比 web / headless / acp profile 是"换一整套产品形态",不是换开关

patch 长什么样

禁用插件

yml · disable-timer
- id: timer
  disabled: true

改插件配置(合并)

yml · relax-approval
- id: approval
  config:
    policy: never

这里只为让 diff 明显。真实项目别关审批闸门,用官方入口 DSH_PERMISSION_MODE

生效后怎么确认

bash · 看 patched by
dsh --profile web --dump-config | grep -B2 -A6 'patched by'

# 期望看到类似:
# == @deepseek-ai/dsh-base, patched by /path/to/disable-timer.yml
- id: timer
  name: '@deepseek-ai/cordis-plugin-timer'
  disabled: true
patchReload:改完 patch 怎么生效

live:监视 patch 文件,改完立即生效(本机 web profile 就是 live,会触发前端 HMR)。startup:只在启动时应用一次,改完要重启。插件装完则必须重启后端进程,只刷新浏览器不会加载新模块。

6从 Claude Code / Codex 迁移

dsh 主动兼容 Claude Code 和 Codex 的 hooks 与 MCP,迁移成本比想象中低。你已经有的 skills、记忆、MCP 大多能复用。

你要做的事CodexClaude CodeDSH
启动交互codexclaudedsh webdsh --profile <p>
单次提问退出codex execclaude -pdsh --profile headless "..."
继续上次会话codex resumeclaude -cWeb UI 会话列表
项目指令文件AGENTS.mdCLAUDE.md两者都支持
审批 / 权限sandbox 模式permissionsdsh-fs-sandbox + permission presets
扩展能力MCPMCP / Skills / hooksMCP / Skills / hooks 全能读 + patch
看最终配置--dsh --profile <p> --dump-config
最省事的迁移路径

先别改任何东西,直接用 dsh 跑你现有的项目,确认 AGENTS.md / CLAUDE.md / MCP 配置被正常读取,再考虑写 patch。

三层修复:skills / 记忆 / hooks

1. skills:DSH 默认不扫 ~/.claude/skills

DSH 的用户级 skill 根目录按 rank 扫描。两边目录会漂移,所以要把 ~/.claude/skills 显式加进去:

Rank路径说明
300自定义 customSkillDirs你手动插入的目录,排在旧副本之前
400~/.dsh/skillsDSH_HOME 下的用户级 skill
500~/.agents/skills共享 agent 配置根
yml · 让 DSH 读 Claude Code 的 skills
- id: skill-filesystem
  config:
    customSkillDirs:
      - /Users/you/.claude/skills

注意:DSH 刻意不支持递归发现 **/SKILL.md,只扫目录本身,所以必须指到 skills 这一层。

2. 记忆:AGENTS.md 才是入口

DSH 的 dsh-agent-instructions 只认 $DSH_HOME/AGENTS.md 和项目里的 AGENTS.md / CLAUDE.md,不认 ~/.claude/projects/*/memory/。把记忆索引生成/软链到 ~/.dsh/AGENTS.md

3. hooks:桥接器只支持这些事件

dsh-hooks-claude-code 支持:SessionStart / UserPromptSubmit / PreToolUse / PostToolUse / Stop / SubagentStop

不要直接把 configPath 指向 ~/.claude/settings.json

如果你的 CC 配置里有 Notification / PermissionRequest / SessionEnd,桥接器不支持它们,可能抛错并拒绝整份配置。正确做法是筛一份子集到 ~/.dsh/hooks.json,再指过去。另外 CC hook 默认 timeout 是 10 分钟,建议收紧到 15s,避免某个 hook 挂住整个会话。

yml · hooks 桥接
- id: hooks-claude-code
  name: '@deepseek-ai/dsh-hooks-claude-code'
  config:
    configPath: /Users/you/.dsh/hooks.json
    defaultTimeoutMs: 15000

configPath 是 process 级、加载时只读一次,改完必须重启 dsh。

验证与回滚

bash · 验证 skill 是否被读到
# 拿一个只存在于 ~/.claude/skills 的自建 skill 去问,把 your-skill-name 换成你的
dsh --profile headless "你的可用 skill 列表里有没有 your-skill-name。不要调用工具。"
dsh --profile web --dump-config | grep -A5 'id: skill-filesystem'
bash · 回滚
rm -f ~/.dsh/cordis.patch.yml ~/.dsh/AGENTS.md ~/.dsh/hooks.json
rm -rf ~/.dsh/skills          # 只删软链,不动 ~/.claude/skills 本体
从 cmux 迁过来的人注意

cmux 给的是多路复用工位;dsh 的 web 形态是"一次一个会话"。但 dsh 内置了 dsh-tool-subagentdsh-tool-workflowdsh-tool-ralph,可以在单个会话内部做并行 fan-out 和 fresh-agent 迭代。把 cmux 用法下沉成 workflow 脚本,可以少一层进程管理。

7patch 与插件系统

在 dsh 里"装功能"不是装插件,是写 patch。理解这一句,你就理解了 dsh 和 Codex / Claude Code 最根本的区别。

一个 profile 由什么组成

json · ~/.dsh/profiles/web/package.json
{
  "dependencies": { /* 树外插件装在这里 */ },
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app",
        "@liustack/modsearch",
        "dshmarket"
      ],
      "patchReload": "live"   // 或 "startup"
    }
  }
}

组合包先从 dsh 安装目录解析(@deepseek-ai/dsh-*),再从 profile 自己的 node_modules 解析。pnpm 会把树外插件装进 profiles/<name>/node_modules

patch 的四种用法

按 id 改配置(合并)

yml
- id: approval
  config:
    policy: never

禁用插件

yml
- id: timer
  disabled: true

插入新插件(必须用 insert)

yml
- insert:
    - id: ui-shortcuts
      name: dsh-client-ui-shortcuts

直接写 id + name 会被当成"按 id 覆盖",报 not found。

命令行临时覆盖

bash
dsh --profile web --patch ./extra.yml

优先级最高,适合实验,不适合长期。

插件安装:pnpm 的真实脾气

dsh plugin --profile web add <pkg> 只是把参数转发给 profile 目录里的 pnpm。所以遇到的坑基本都是 pnpm 的坑。本机实测过的四类:

1 · pnpm 全局 store 在工作区外,被 dsh 沙箱拦住

症状:

text
Failed to write cafs: Failed to create the parent directory at
"/Users/you/Library/pnpm/store/v11/files/..": Operation not permitted
dsh: pnpm failed in profile directory /Users/you/.dsh/profiles/web

根因:dsh 自己的文件沙箱只放行会话工作区,pnpm 全局 store 在工作区外。处理:让用户在自己的普通终端跑安装命令;或把 pnpm 的 store-dir 指到工作区内;不要反复重试。

2 · ERR_PNPM_IGNORED_BUILDS:构建脚本被拦

pnpm 10+ 默认不执行第三方依赖的 build / prepare 脚本。终端会打印需要放行的 key,把它们写进 profile 的 pnpm-workspace.yamlallowBuilds,再重跑。

3 · isolated linker 导致子包找不到

症状:启动 dsh web 时报 Cannot find package '@linxin666/dsh-...'。根因是 pnpm 默认的 isolated 模式把部分子包嵌套收敛了。设 nodeLinker: hoisted 后重装。

4 · pnpm 11 的 minimumReleaseAge 门禁

包刚发布时会因安全策略回退到旧版本。把对应 scope 加进 minimumReleaseAgeExclude,确保拿到最新版。

本机在用的 pnpm-workspace.yaml

yml · ~/.dsh/profiles/web/pnpm-workspace.yaml
packages:
  - .

# pnpm 默认 isolated 模式会让部分子包嵌套收敛;hoisted 更稳
nodeLinker: hoisted
autoInstallPeers: false

# 第三方包的构建脚本默认被拦;false = 明确不执行(本项目不需要)
allowBuilds:
  cloudflared: false
  cpu-features: false
  node-pty: false
  ssh2: false

# 绕过 pnpm 11 对新发布包的时间门禁
minimumReleaseAgeExclude:
  - '@linxin666/*'
第三方插件有供应链风险

社区插件能访问你的文件、凭据和网络。帮人装之前先让它审代码;安装前核对 npm scope;优先选有仓库、有 README、能看懂 diff 的包。看到 postinstall 脚本要格外警惕。

装 bundle 还是装单个子插件

聚合包(如 @linxin666/dsh-web-all)省事,但它会一次带进 task-board / git-graph / remote-web-ui 等多个插件和 SSE 长连接,是 web 卡死的主要嫌疑。按需装单个子包,代价是你要自己管依赖。

8推荐起步插件

按"先补能力缺口,再谈体验"的顺序装。版本号是本机 09-22 的快照,会过期,装之前看下 npm。

第一梯队:补能力

插件它解决什么注意
@liustack/modsearch
5.10.3
给没有原生联网的模型补 web 搜索 / X 搜索 / 抓网页,免注册免 key 免费额度有限;抓取失败时看返回的 uncertainty
@liustack/modlens
3.26.2
给纯文本模型补看图能力,基于免费的 Antigravity CLI 装完要确认 CLI 可用;图片路径要能被读取
dsh-context
0.54.0
上下文面板:看清 context 由什么组成、怎么膨胀 长会话必备;配合压缩策略用

第二梯队:补效率

插件它解决什么注意
@changfenhuang/dsh-annotation
1.4.10
选中回复文字批注,按回车带批注发送,模型逐条回应 讨论型任务很好用;注意批注块会进上下文
@linxin666/dsh-client-ui-task-board
0.3.24
任务看板:卡片 + 真实会话执行 + Host cron 调度 + 休眠保护 有权限门,写类任务默认可能被跳过,见第 9 节

第三梯队:体验与外观

插件它解决什么注意
dshmarket
1.50.0
Web 里的可视化插件市场,逛、搜、一键装 toggle 插件会改 patch,触发 HMR,可能让页面卡一下
dsh-setting-restart
1.0.0
设置页里的一键重启后端 本机实测它只往 stdout 打日志、不落盘;启动失败时可能无声无息,别把它当唯一手段
dsh-plugin-whale-fenggu
1.1.0
DeepSeek 峰谷提醒浮窗,帮你把批量任务挪到谷段 纯前端;峰谷时段以官方价格页为准
@linxin666/dsh-client-ui-skin-center 图形化皮肤中心,管理 ~/.dsh/skins 外观插件不影响核心能力,但会增加前端体积
dsh-better-sidebar
0.19.1
类 VS Code 右侧栏(explorer / editor / terminal / git / browser) 已知冲突:它以 extension 优先级接管 dsh-resource://file/**,会盖掉内置图片 / PDF 预览。图片显示异常先停它
一条最小可用清单

如果你只想先跑顺,装 modsearch + modlens 就够用了。等真的觉得"上下文看不见"再上 dsh-context不要一上来装全家桶,web 卡死的代价比少一个功能大得多。

自己写一个插件:最短路径

本机有一个真实的最小例子:Codex 风格快捷键插件 dsh-client-ui-shortcuts,浏览器侧插件本体只有一个 lib/client.js,用 insert 挂进 web profile。

bash · 把自己写的插件挂进 web
dsh plugin --profile web add /path/to/your-plugin
# 然后在 profiles/web/cordis.patch.yml 里 insert:
# - insert:
#     - id: your-plugin
#       name: your-plugin-name
# 重启 dsh web,刷新浏览器

9日常使用范式

这部分不是命令,是习惯。从最近几天的真实 session 里总结出来的,能少走很多弯路。

会话与工作区:一个项目一个会话

  • dsh 把启动目录当 workspace 根,沙箱也只放行这个目录。开新会话前先 cd 到正确的仓库。
  • 跨项目的事不要塞进同一个会话;上下文会互相污染,权限也容易给错。
  • 会话有磁盘持久化(~/.dsh/sessions/.../session.v3.jsonl.zstd),重启 dsh 后可以在 UI 里恢复。

权限:先想清楚这次要给多大

DSH_PERMISSION_MODEsandboxapproval适合
read-onlyread-onlyask只读调研、代码审查
workspace-write(默认)workspace-writeask绝大多数开发任务
danger-full-accessdanger-full-accessnever确实需要写工作区外,且你盯着
默认策略就够用,不要图省事写 patch 关审批

官方设计的入口是环境变量 DSH_PERMISSION_MODE,三档语义自洽。直接 patch approval.policy: never 等于关掉闸门,只在明确知道后果时用。

峰谷批量:把"要跑很久但不急"的任务攒起来

DeepSeek 有峰谷计价。一个很实用的工作流是:平时只做讨论和拆任务,把可无人值守的任务写成看板卡片,挂 cron 到谷段批量执行,第二天看结果。

先讨论,再落卡

复杂任务先对齐目标和验收标准,再写成卡片。卡片里放 prompt、workspace、期望产物。

按"能不能无人值守"分类

A 类纯核对 / 算力型可以全自动;B 类能出草稿但要人拍板;C 类只能本人做,别排进去。

提前过权限门

看板默认权限是 read-only,写文件的卡权限高于它时会被拒或跳过。谷段跑之前一张张确认。

挂 cron,别守夜

看板支持多段 cron,时区 Asia/Shanghai。跑完早上看 ~/.dsh/dsh-usage/usage-ledger.json 的成本。

交互习惯:分阶段,不要让它一路跑到底

真实教训

本机一次排障里,agent 一路查到底、没有阶段性汇报,用户中途完全不知道进展。默认约定应该是:每个阶段先汇报再继续;涉及 kill / 重启 / 改配置 / 清缓存,先停下来问。

  • 让它先给计划再执行;计划里写清验收标准和回滚方式。
  • 讨论型任务用批注插件(dsh-annotation),比来回打字快。
  • 需要并行时用 dsh-tool-subagent / dsh-tool-workflow,给子代理显式指定模型,别默认继承主模型。
  • 长任务中途用 dsh-context 看上下文占用,及时压缩或开新会话。

成本意识

  • 默认模型 deepseek-flash + 高推理档,本机日常够用;重活再换更强模型。
  • 每天看一次 ~/.dsh/dsh-usage/usage-ledger.json,按 provider / model / 天统计 token 和成本。
  • 把"搜索 + 抓取 + 批量核对"这类 token 密集型任务放到谷段。
  • 缓存命中(cacheReadTokens)通常占大头,重复问同一个大上下文会明显更便宜。

10踩坑与排查

先看速查表,再展开细节。每条都标了是实测还是推断。

症状根因处理
command not found: dsh通过 npx 跑时 dsh 只存在于那一次的 npx 缓存 PATH全局装:npm i -g @deepseek-ai/dsh,确认 <prefix>/bin 在 PATH
EPERM ... mkdir '~/.dsh/...'dsh 自己的文件沙箱用项目内 DSH_HOME;或在普通终端跑
终端能用,GUI / 别的 App 里 dsh 无输出GUI 的 PATH 命中旧 Node(如 v16)launcher 钉死 Node 22 + 无条件 prepend 其 bin
dsh plugin add 失败pnpm store 越界 / 构建脚本被拦 / isolated / 发布门禁见第 7 节,按终端提示逐条处理
Web 用着用着卡死,几分钟又自己好大量 client bundle + SSE 占满 Chrome 同源 6 连接减 UI 插件、别开多标签、Cmd+Shift+R、考虑 startup 重载
右侧栏图片 / PDF 预览变代码dsh-better-sidebar 以 extension 优先级接管了文件预览停用该插件,恢复内置 preview
开 TUN 后 dsh API 超时Clash 全局模式把直连流量丢到海外节点TUN + 规则模式,给 DeepSeek / Moonshot 域名加 DIRECT
TUN 下 localhost:3080 连不上,127.0.0.1 正常fake-ip 把 localhost 解析成 198.18.x.x把 localhost 加进 fake-ip-filter
--dump-config 也报权限错它同样会重写 profiles/<p>/cordis.yml用项目内 DSH_HOME
装了社区包结果不是官方的相近名抢注认准 @deepseek-ai/dsh,装前 npm view
A · dsh 不在 PATH:npx 的会话专属 PATH 陷阱

npx @deepseek-ai/dsh 时,dsh 只存在于 npx 缓存目录(~/.npm/_npx/<hash>/node_modules/.bin/),那是那一次会话专属的 PATH。你在自己的终端里敲 dsh 会 command not found。

bash · 修法
npm i -g @deepseek-ai/dsh
which dsh
npm config get prefix     # 检查 <prefix>/bin 是否在 PATH

写自动化脚本时不要假定 dsh 在 PATH:按 dshnpx --no-installnpx -y 的顺序探测,找不到再报错提示用户安装。

B · 沙箱挡住 ~/.dsh(实测

在 dsh 会话里直接跑 dsh --profile headless ...,会报:

text
EPERM: operation not permitted, mkdir '/Users/you/.dsh/profiles/headless'

不是 dsh 坏了,是 dsh 的文件沙箱策略在拦。做实验用项目内 DSH_HOME:

bash
export DSH_HOME="$PWD/.dsh-lab"
dsh --profile web --dump-default-config | head -40
C · GUI / daemon 环境命中旧 Node(实测

一个真实案例:某个桌面 App 用 3 秒超时探测 dsh --version,报 "found, but could not verify its version"。根因是它的 daemon PATH 以 /usr/local/bin 开头,那里有 2022 年 pkg 装残留的 Node v16.13.2;dsh 的 shim 用 #!/usr/bin/env node,于是 ESM SyntaxError、exit 1、stdout 为空。

更隐蔽的是第二层:给 dsh 钉死 Node 22 还不够,dsh 内部 spawnSync("pnpm") 时,pnpm 是 corepack 的 node shim,会再次用 PATH 里的旧 Node,报 TypeError: URL.canParse is not a function

sh · launcher 的核心逻辑
NODE_DIR="/path/to/node22/bin"
NODE="$NODE_DIR/node"
DSH_ENTRY="/path/to/node22/lib/node_modules/@deepseek-ai/dsh/lib/bin.js"

PATH="$NODE_DIR:$PATH"      # 必须无条件 prepend,即使 PATH 末尾已有该目录
export PATH
exec "$NODE" "$DSH_ENTRY" "$@"

注意那个"无条件":如果写成"PATH 里已有该目录就跳过 prepend",而 daemon 的 PATH 末尾本来就有该目录,就会跳过、问题依旧。

D · Web 卡死:16.6 MB 前端 bundle + Chrome 6 连接(实测

症状:web 页面假死几分钟又自己恢复,反复出现,看起来像"重启插件有问题"。

实测证据(2026-09-21):

  • 后端进程活着,事件循环空闲,调度正常——不是后端挂了。
  • 首页一次加载 62 个 client bundle,约 16.6 MB 未压缩
  • Chrome 到 127.0.0.1:3080 恰好 6 条 ESTABLISHED——Chrome 对同 host:port 的 HTTP/1.1 连接上限。
  • 其中一条服务端 Send-Q 堆到约 7.9 MB,客户端 Recv-Q 约 16.7 MB;5 秒后再测归零——正是"卡住又自己恢复"的形状。

机制:UI 插件多 + SSE 长连接(HMR、task-board、git-graph、remote-web-ui)占满 6 条连接,普通请求排队;浏览器解析 16.6 MB JS 时又停止读网络,服务端发送队列迅速堆积。重启 / HMR 会让所有连接同时断、同时重连,并把 bundle 重下一遍,所以必然再卡一次。

按性价比的处理顺序:

  1. 减插件:优先停用/移除聚合包和 better-sidebar,目标 client bundle < 5 MB、SSE ≤ 3 条。
  2. 不要开多个 dsh 标签页;卡住时先关掉其他标签,再 Cmd+Shift+R
  3. 降低热重载频率:避免 agent 输出时去插件市场 toggle;可把 patchReload 改成 startup
  4. 重启插件的健壮性有缺口:只看旧 PID、不检查端口释放、不确认新进程起来、日志不落盘。别把它当唯一重启手段。
E · 右侧栏图片 / PDF 预览被顶掉(实测

dsh-better-sidebarpriority: 'extension' 注册了 dsh-resource://file/**,写得很直白:它比内置的 text preview(fallback)优先级高,所以每个文件都被它接管,图片走它自己的 viewer。

排查时先分两层:服务端媒体路由是否 200、文件是否正常;如果都正常,那就是前端被插件顶掉了。停用 better-sidebar 即可验证。

F · TUN 模式与 DSH 共存:别开全局(实测

Node 默认不读系统代理,也不读 HTTP_PROXY,所以不开 TUN 时 DSH 是直连的,Clash 模式伤不到它。一开 TUN,TUN 在 L3 接管所有流量,Node 的"免代理豁免"消失,DSH 请求开始完全受 Clash 模式支配。

配置DSH 请求走向结果
不开 TUN直连正常
TUN + 规则模式按规则走 DIRECT正常
TUN + 全局模式忽略规则,丢到海外节点延迟暴涨 / 超时 / 重置

正解:TUN + 规则模式,永远不要开全局;给 api.deepseek.com / api.moonshot.cn 明确加 DIRECT 规则。

附带的隐藏 bug:localhost 被解析成假 IP

如果 DNS 配置里 use-system-hosts: falsefake-ip-filter 没有 localhost,开 TUN 后 localhost 会被解析成 198.18.x.x。表现是 curl 127.0.0.1:3080 通,但 curl localhost:3080 不通——这种问题最难查。

bash · 验证
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3080   # 401 = 服务活着
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3080    # 哨兵:不能是 000
dscacheutil -q host -a name localhost                            # 不应出现 198.18.x.x
G · 参数归属与 dump 副作用

dsh --profile web --help 显示的是 web app 的 help;启动器自己的 help 用 dsh --help--port / --resume 都属于 app 参数,启动器只解析到第一个不认识的 token 为止。

--dump-config 即使只是查看,也会尝试写 profiles/<name>/cordis.yml,所以同样受沙箱限制。

11给 agent 用

这一节是给"要帮你配置 dsh"的 agent 看的。人类可以直接把整段提示词复制给你的 agent。

配套文件(同目录)
  • dsh.agent.md:精简机读版,包含路径、命令、patch、权限、踩坑表、开场提示词。
  • dsh.agent.json:结构化机读版,包含 commands / configLayerOrder / pitfalls / verifyChecklist。
  • 本页 index.html:人类可读的完整版。

开场提示词(复制给 agent)

text · dsh onboarding prompt
你是运行在 DeepSeek Harness (dsh) 里的 agent。
先做三件事,再回答我:
1. 读 ~/.dsh/AGENTS.md 和当前项目根的 AGENTS.md / CLAUDE.md;
2. 跑 dsh --version,以及 dsh --profile web --dump-config | grep -c '^- id:',确认版本与配置树规模;
3. 用一段话复述 dsh 的配置分层顺序(bundle 层 → profile patch → home patch → --patch 覆盖层,后者覆盖前者)。

之后所有涉及改 ~/.dsh、装插件、重启服务的操作,遵守:
- 先给计划并等我确认;每个阶段先汇报再继续;
- 先备份要改的文件(package.json / cordis.patch.yml / pnpm-workspace.yaml);
- 改完用 dsh --profile web --dump-config 验证 patch 是否挂上,再重启,再验证一次;
- 明确区分「实测结论」和「推测」;
- 不要盲目装第三方插件,先审代码并核对 npm scope。

让 agent 帮你配置 dsh(另一段提示词)

text · configure dsh
请帮我把 dsh 配置到可用状态,遵守以下约束:
1. 先只读排查:node -v、which -a node dsh pnpm、dsh --version、dsh web --port 0 --no-open 起一次。
2. 不要改 ~/.dsh 里的任何文件,除非我给你明确许可;需要实验时用项目内 DSH_HOME。
3. 装插件用 dsh plugin --profile web add <pkg>。如果失败,把完整报错贴给我,并按顺序检查:
   - pnpm store 是否在工作区外被沙箱拦(Operation not permitted);
   - 是否需要 allowBuilds;
   - 是否需要 nodeLinker: hoisted;
   - 是否撞上 pnpm 的 minimumReleaseAge。
4. 每装完一个插件:备份 package.json 和 cordis.patch.yml,dump-config 验证,再重启后端。
5. 最后给我一张「改了什么 / 怎么回滚 / 怎么验证」的清单。

Agent 十条纪律

  1. 先对齐目标再动手,不要拿别的项目的结论回答现在的问题。
  2. 分阶段汇报,长排查不要一路跑到底。
  3. 涉及 kill / 重启 / 改配置 / 清缓存,先停下问。
  4. 明确区分已验证与推测。
  5. 多步脚本类任务用 ~/Projects/<name>/,不用临时目录。
  1. 子代理显式指定模型,别默认继承主模型。
  2. 装第三方插件前审代码、核对 scope。
  3. 不要装全家桶,按需装子插件。
  4. 改任何配置前先备份,改完必须 dump-config 验证。
  5. 把"怎么回滚"和"怎么验证"一起交付。

Agent 环境自检清单

bash · 逐条跑
node -v                                  # 期望 v22.x
which -a node dsh pnpm                   # 看 PATH 顺序,GUI 环境尤其要查
dsh --version                            # 期望 0.1.5-rc.2 或更新
dsh web --port 0 --no-open               # 能起来说明后端正常,Ctrl+C 退出
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3080
dscacheutil -q host -a name localhost    # 不应出现 198.18.x.x
dsh --profile web --dump-config | grep -c '^- id:'   # 本机实测 152
agent 最容易犯的错

把"dump-config 能读到某插件"当成"插件已经生效"。实际上要 重启后端进程 才会加载新模块;只刷新浏览器不够。另一个是把"后端活着"当成"web 没问题"——本机卡死案例里后端一直是活的,问题在前端连接。

12术语、自查与参考

术语表

术语一句话解释
Harness调度上下文、工具、任务状态、反馈和边界的运行时;dsh 的本体
Profile产品形态,如 web / headless / acp / sdk
Bundle一组插件的预设配置,是配置树的第一层
PatchYAML 顶层数组的覆盖层,按 id 改配置 / 禁用 / 插入插件
cordisdsh 底层用的插件框架;cordis.patch.yml 因此得名
Agent preset改写首轮 prompt / 工具集 / 压缩行为的预设,社区可选装
Skill可复用的任务说明,按目录 rank 扫描加载
Hook会话 / 工具事件的拦截点,可桥接 Claude Code 的 hooks
ACP / SDK给自动化客户端用的两种 stdio 协议 profile
SSE / HMR服务端推送长连接 / 热模块重载,是 web 卡死的主要连接占用方
pnpmdsh 用来管理 profile 插件的包管理器;很多安装坑来自它

信息时效性:自己核一遍

bash · 核实版本与来源
npm view @deepseek-ai/dsh version
npm view @deepseek-ai/dsh repository.url
dsh --version
dsh --profile web --dump-config | grep -c '^- id:'

本页基线是 0.1.5-rc.2,实测日期 2026-09-18 ~ 09-22。这个领域迭代很快,数字和 flag 都可能变;结论里"结构"比"数字"更耐用。

参考与出处

来源内容
npm 包内 README.zh.md官方对 profile / bundle / patch / 命令语法 / 叠加顺序的定义
本机 dsh 实验笔记4 个 Lab、迁移配置源、一页速查、踩坑记录
本机排障记录GUI 环境旧 Node 问题的诊断思路与 launcher 写法
本机网络实测TUN / 规则 / 全局模式与 DSH 共存
~/.dsh/cordis.patch.ymlhome 级 patch 的真实例子(skills + hooks 桥接)
~/.dsh/AGENTS.md全局工作记忆与约定
分享的最后一句

不要一上来就研究怎么改 dsh。先用它把一个真实的小项目跑通,确认你的 AGENTS.md、skills、MCP 都能用;等你遇到第一个真实的不顺手,再去写第一份 patch。那时候你才看得懂配置树。