030 秒上手
如果你只有三分钟,读这一节就够了。剩下的章节按需查。
一句话理解
dsh 是 DeepSeek 官方的 Agent 运行时框架,不是普通 CLI。官方公式:
Model + Harness = Agent
模型只是其中一个插件;工具、UI、沙箱、记忆全都可以替换。
最小上手三步
装
npm i -g @deepseek-ai/dsh,确认 Node 22。
跑
在项目目录里执行 dsh web,浏览器打开 3080。
改
要加功能就写 patch 或装 bundle,不改源码。
三条铁律
- 先跑通再改造:先确认现有项目里
AGENTS.md/CLAUDE.md被读到。 - 装功能 = 写 patch:不是装插件、更不是改源码。
- 实验别碰真的
~/.dsh:用项目内DSH_HOME。
本机实测 在 0.1.5-rc.2 上真跑过、看过输出。官方文档 来自 npm 包内随附的 README / 源码。 经验判断 从多轮排障里总结,不一定对所有版本成立。dsh 迭代很快,动手前用文末的自查命令核一遍版本。
1先分清三条线,别装错包
市面上叫"DeepSeek 编程工具"的东西有三条完全不同的线,名字很像,血统不同。搞混了会浪费一整天。
| 名字 | 谁做的 | 形态 | 该不该用 |
|---|---|---|---|
Deep Codedeepcode-cli |
第三方开源项目,被 DeepSeek 官方 API 文档收录推荐 | 终端 CLI + VS Code 插件,轻量、开箱即用 | 想 5 分钟跑起来,选它 |
DSH@deepseek-ai/dsh |
DeepSeek 官方 | CLI + Web GUI + SDK,本体是 Agent 运行时 | 想当长期主力、接自己工具链,选它 |
社区魔改版<user>/deepseek-harness |
第三方 fork | 跟随上游,修 bug / 加功能 / 去限制 | 上游有硬 blocker、你能读 diff 时再考虑 |
官方包是 @deepseek-ai/dsh。社区里出现过 @deepopen/cli、@opendeep/cli 这类只差一两个字母的相近名,属于典型的高风险命名。不确定时先跑:
npm view @deepseek-ai/dsh version
npm view @deepseek-ai/dsh repository.url
npm view @deepseek-ai/dsh dist.tarball90% 的"我想改掉这个行为"都能用一份 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.json 的 dsh.profile.bundles 里按顺序列出。
Patch = 覆盖层
一个 YAML 顶层数组,按插件 id 改配置、禁用插件、插入新插件。你日常真正动手的地方。
配置树按这个顺序叠加(后者覆盖前者)
dsh.profile.bundles 里按顺序叠加,如 dsh-base → dsh-web-app~/.dsh/profiles/<name>/cordis.patch.yml,日常改这里~/.dsh/cordis.patch.yml,对所有 profile 生效,适合放机器级偏好--patch 覆盖层DSH_TELEMETRY_DISABLED 非空即关闭遥测最外层(home)patch 的优先级高于 profile 层。也就是说,~/.dsh/cordis.patch.yml 里的配置会盖掉 profiles/web/cordis.patch.yml 里的同 id 配置。这是官方源码 allPatches() 的顺序,网上有些笔记写反了。
启动器的参数规则
启动器只解析自己的 flag;遇到第一个不认识的 token 之后,全部原样交给 profile 里的 app。所以:
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 | 插件条目 | 独有内容 |
|---|---|---|
web | 152 | client-* / ui-*、directory-picker、agent-presets |
headless | 87 | headless-runner、headless-startup |
acp | 86 | Agent Client Protocol stdio |
数字会随版本变化,但"web 多出来的全是界面插件"这个结构不变。这就是"换 profile 换产品"的直观证据。
3安装与启动
目标:让 dsh 在你的普通终端和 GUI 环境里都能跑起来,然后跑通一个真实项目。
前置条件
dsh 的启动 shim 是 #!/usr/bin/env node,会跑在 PATH 里第一个 node 上。老 Node(如 16)直接 ESM SyntaxError,而且 dsh 内部再调 pnpm / corepack 时会继续命中旧 Node。本机实测:Node v22.22.2 正常。
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 换端口。
cd ~/Projects/my-project
dsh web
dsh --profile web --port 8080 --no-open
dsh --profile headless "这个仓库怎么跑测试" # 一次性任务,答案打印后退出确认项目指令被读到
在你现有项目里放 AGENTS.md 或 CLAUDE.md,然后问一句"你现在知道哪些项目规则",确认它读到了再往下走。
web app 的 flag(0.1.5-rc.2)
| flag | 作用 |
|---|---|
--host <host> | 绑定地址 |
--port <port> | 监听端口,默认 3080;0 表示让系统分配 |
--no-open | 不自动打开浏览器 |
--trusted-host <authority> | 额外允许的 host / host:port,可重复 |
--resume恢复会话在 Web UI 的会话列表里点。--resume 是终端类 app(如社区 TUI)的参数,别写到 web 上。想看 web app 自己的 flag,用 dsh --profile web --help。
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 上核过。
启动 / 退出
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看配置(不启动也能看)
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 与插件
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 |
# 会话是 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 有自己的文件沙箱。真实 ~/.dsh 在会话工作区之外,直接跑会报 EPERM: operation not permitted, mkdir '.../.dsh/profiles/...'。这不是 dsh 坏了,是策略在拦。实验用:
# 随便建一个实验目录,别在 ~/.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 长什么样
禁用插件
- id: timer
disabled: true改插件配置(合并)
- id: approval
config:
policy: never这里只为让 diff 明显。真实项目别关审批闸门,用官方入口 DSH_PERMISSION_MODE。
生效后怎么确认
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: truepatchReload:改完 patch 怎么生效live:监视 patch 文件,改完立即生效(本机 web profile 就是 live,会触发前端 HMR)。startup:只在启动时应用一次,改完要重启。插件装完则必须重启后端进程,只刷新浏览器不会加载新模块。
6从 Claude Code / Codex 迁移
dsh 主动兼容 Claude Code 和 Codex 的 hooks 与 MCP,迁移成本比想象中低。你已经有的 skills、记忆、MCP 大多能复用。
| 你要做的事 | Codex | Claude Code | DSH |
|---|---|---|---|
| 启动交互 | codex | claude | dsh web 或 dsh --profile <p> |
| 单次提问退出 | codex exec | claude -p | dsh --profile headless "..." |
| 继续上次会话 | codex resume | claude -c | Web UI 会话列表 |
| 项目指令文件 | AGENTS.md | CLAUDE.md | 两者都支持 |
| 审批 / 权限 | sandbox 模式 | permissions | dsh-fs-sandbox + permission presets |
| 扩展能力 | MCP | MCP / Skills / hooks | MCP / 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/skills | DSH_HOME 下的用户级 skill |
| 500 | ~/.agents/skills | 共享 agent 配置根 |
- 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。
如果你的 CC 配置里有 Notification / PermissionRequest / SessionEnd,桥接器不支持它们,可能抛错并拒绝整份配置。正确做法是筛一份子集到 ~/.dsh/hooks.json,再指过去。另外 CC hook 默认 timeout 是 10 分钟,建议收紧到 15s,避免某个 hook 挂住整个会话。
- id: hooks-claude-code
name: '@deepseek-ai/dsh-hooks-claude-code'
config:
configPath: /Users/you/.dsh/hooks.json
defaultTimeoutMs: 15000configPath 是 process 级、加载时只读一次,改完必须重启 dsh。
验证与回滚
# 拿一个只存在于 ~/.claude/skills 的自建 skill 去问,把 your-skill-name 换成你的
dsh --profile headless "你的可用 skill 列表里有没有 your-skill-name。不要调用工具。"
dsh --profile web --dump-config | grep -A5 'id: skill-filesystem'rm -f ~/.dsh/cordis.patch.yml ~/.dsh/AGENTS.md ~/.dsh/hooks.json
rm -rf ~/.dsh/skills # 只删软链,不动 ~/.claude/skills 本体cmux 给的是多路复用工位;dsh 的 web 形态是"一次一个会话"。但 dsh 内置了 dsh-tool-subagent、dsh-tool-workflow、dsh-tool-ralph,可以在单个会话内部做并行 fan-out 和 fresh-agent 迭代。把 cmux 用法下沉成 workflow 脚本,可以少一层进程管理。
7patch 与插件系统
在 dsh 里"装功能"不是装插件,是写 patch。理解这一句,你就理解了 dsh 和 Codex / Claude Code 最根本的区别。
一个 profile 由什么组成
{
"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 改配置(合并)
- id: approval
config:
policy: never禁用插件
- id: timer
disabled: true插入新插件(必须用 insert)
- insert:
- id: ui-shortcuts
name: dsh-client-ui-shortcuts直接写 id + name 会被当成"按 id 覆盖",报 not found。
命令行临时覆盖
dsh --profile web --patch ./extra.yml优先级最高,适合实验,不适合长期。
插件安装:pnpm 的真实脾气
dsh plugin --profile web add <pkg> 只是把参数转发给 profile 目录里的 pnpm。所以遇到的坑基本都是 pnpm 的坑。本机实测过的四类:
1 · pnpm 全局 store 在工作区外,被 dsh 沙箱拦住
症状:
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.yaml 的 allowBuilds,再重跑。
3 · isolated linker 导致子包找不到
症状:启动 dsh web 时报 Cannot find package '@linxin666/dsh-...'。根因是 pnpm 默认的 isolated 模式把部分子包嵌套收敛了。设 nodeLinker: hoisted 后重装。
4 · pnpm 11 的 minimumReleaseAge 门禁
包刚发布时会因安全策略回退到旧版本。把对应 scope 加进 minimumReleaseAgeExclude,确保拿到最新版。
本机在用的 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 脚本要格外警惕。
聚合包(如 @linxin666/dsh-web-all)省事,但它会一次带进 task-board / git-graph / remote-web-ui 等多个插件和 SSE 长连接,是 web 卡死的主要嫌疑。按需装单个子包,代价是你要自己管依赖。
8推荐起步插件
按"先补能力缺口,再谈体验"的顺序装。版本号是本机 09-22 的快照,会过期,装之前看下 npm。
第一梯队:补能力
| 插件 | 它解决什么 | 注意 |
|---|---|---|
@liustack/modsearch5.10.3 |
给没有原生联网的模型补 web 搜索 / X 搜索 / 抓网页,免注册免 key | 免费额度有限;抓取失败时看返回的 uncertainty |
@liustack/modlens3.26.2 |
给纯文本模型补看图能力,基于免费的 Antigravity CLI | 装完要确认 CLI 可用;图片路径要能被读取 |
dsh-context0.54.0 |
上下文面板:看清 context 由什么组成、怎么膨胀 | 长会话必备;配合压缩策略用 |
第二梯队:补效率
| 插件 | 它解决什么 | 注意 |
|---|---|---|
@changfenhuang/dsh-annotation1.4.10 |
选中回复文字批注,按回车带批注发送,模型逐条回应 | 讨论型任务很好用;注意批注块会进上下文 |
@linxin666/dsh-client-ui-task-board0.3.24 |
任务看板:卡片 + 真实会话执行 + Host cron 调度 + 休眠保护 | 有权限门,写类任务默认可能被跳过,见第 9 节 |
第三梯队:体验与外观
| 插件 | 它解决什么 | 注意 |
|---|---|---|
dshmarket1.50.0 |
Web 里的可视化插件市场,逛、搜、一键装 | toggle 插件会改 patch,触发 HMR,可能让页面卡一下 |
dsh-setting-restart1.0.0 |
设置页里的一键重启后端 | 本机实测它只往 stdout 打日志、不落盘;启动失败时可能无声无息,别把它当唯一手段 |
dsh-plugin-whale-fenggu1.1.0 |
DeepSeek 峰谷提醒浮窗,帮你把批量任务挪到谷段 | 纯前端;峰谷时段以官方价格页为准 |
@linxin666/dsh-client-ui-skin-center |
图形化皮肤中心,管理 ~/.dsh/skins |
外观插件不影响核心能力,但会增加前端体积 |
dsh-better-sidebar0.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。
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_MODE | sandbox | approval | 适合 |
|---|---|---|---|
read-only | read-only | ask | 只读调研、代码审查 |
workspace-write(默认) | workspace-write | ask | 绝大多数开发任务 |
danger-full-access | danger-full-access | never | 确实需要写工作区外,且你盯着 |
官方设计的入口是环境变量 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。
npm i -g @deepseek-ai/dsh
which dsh
npm config get prefix # 检查 <prefix>/bin 是否在 PATH写自动化脚本时不要假定 dsh 在 PATH:按 dsh → npx --no-install → npx -y 的顺序探测,找不到再报错提示用户安装。
B · 沙箱挡住 ~/.dsh(实测)
在 dsh 会话里直接跑 dsh --profile headless ...,会报:
EPERM: operation not permitted, mkdir '/Users/you/.dsh/profiles/headless'不是 dsh 坏了,是 dsh 的文件沙箱策略在拦。做实验用项目内 DSH_HOME:
export DSH_HOME="$PWD/.dsh-lab"
dsh --profile web --dump-default-config | head -40C · 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。
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 重下一遍,所以必然再卡一次。
按性价比的处理顺序:
- 减插件:优先停用/移除聚合包和 better-sidebar,目标 client bundle < 5 MB、SSE ≤ 3 条。
- 不要开多个 dsh 标签页;卡住时先关掉其他标签,再
Cmd+Shift+R。 - 降低热重载频率:避免 agent 输出时去插件市场 toggle;可把
patchReload改成startup。 - 重启插件的健壮性有缺口:只看旧 PID、不检查端口释放、不确认新进程起来、日志不落盘。别把它当唯一重启手段。
E · 右侧栏图片 / PDF 预览被顶掉(实测)
dsh-better-sidebar 用 priority: '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: false 且 fake-ip-filter 没有 localhost,开 TUN 后 localhost 会被解析成 198.18.x.x。表现是 curl 127.0.0.1:3080 通,但 curl localhost:3080 不通——这种问题最难查。
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.xG · 参数归属与 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)
你是运行在 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(另一段提示词)
请帮我把 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 十条纪律
- 先对齐目标再动手,不要拿别的项目的结论回答现在的问题。
- 分阶段汇报,长排查不要一路跑到底。
- 涉及 kill / 重启 / 改配置 / 清缓存,先停下问。
- 明确区分已验证与推测。
- 多步脚本类任务用
~/Projects/<name>/,不用临时目录。
- 子代理显式指定模型,别默认继承主模型。
- 装第三方插件前审代码、核对 scope。
- 不要装全家桶,按需装子插件。
- 改任何配置前先备份,改完必须 dump-config 验证。
- 把"怎么回滚"和"怎么验证"一起交付。
Agent 环境自检清单
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把"dump-config 能读到某插件"当成"插件已经生效"。实际上要 重启后端进程 才会加载新模块;只刷新浏览器不够。另一个是把"后端活着"当成"web 没问题"——本机卡死案例里后端一直是活的,问题在前端连接。
12术语、自查与参考
术语表
| 术语 | 一句话解释 |
|---|---|
| Harness | 调度上下文、工具、任务状态、反馈和边界的运行时;dsh 的本体 |
| Profile | 产品形态,如 web / headless / acp / sdk |
| Bundle | 一组插件的预设配置,是配置树的第一层 |
| Patch | YAML 顶层数组的覆盖层,按 id 改配置 / 禁用 / 插入插件 |
| cordis | dsh 底层用的插件框架;cordis.patch.yml 因此得名 |
| Agent preset | 改写首轮 prompt / 工具集 / 压缩行为的预设,社区可选装 |
| Skill | 可复用的任务说明,按目录 rank 扫描加载 |
| Hook | 会话 / 工具事件的拦截点,可桥接 Claude Code 的 hooks |
| ACP / SDK | 给自动化客户端用的两种 stdio 协议 profile |
| SSE / HMR | 服务端推送长连接 / 热模块重载,是 web 卡死的主要连接占用方 |
| pnpm | dsh 用来管理 profile 插件的包管理器;很多安装坑来自它 |
信息时效性:自己核一遍
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.yml | home 级 patch 的真实例子(skills + hooks 桥接) |
~/.dsh/AGENTS.md | 全局工作记忆与约定 |
不要一上来就研究怎么改 dsh。先用它把一个真实的小项目跑通,确认你的 AGENTS.md、skills、MCP 都能用;等你遇到第一个真实的不顺手,再去写第一份 patch。那时候你才看得懂配置树。