注册 `config_doctor` 工具,检查 harness 自身配置里那些会静默通过启动的问题:patch 整体替换 config 而丢失的字段、指向不存在 entry id 的 patch、工具重名冲突。只读。
安装
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:asdf17128/dsh-doctor
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
你的 dsh 配置里有一些设置已经悄悄失效了。这个工具告诉你是哪些。
English | 中文
npx github:asdf17128/dsh-doctor
一条命令、十秒钟、只读。零配置、零注册、零依赖。
为什么你改的设置没生效
dsh 有两个行为会正常启动、退出码 0,所以没有任何东西提醒你:
patch 会整体替换一个条目的 config。 你只改了一个字段,同一条目下所有你没
重写的字段全部从启动的树里消失,插件于是跑在你从没选过的默认值上。
config:
fallbackMaxWords: 12
- fallbackMaxBytes: 40 ← 静默消失
- maxTitleBytes: 80 ← 静默消失
entry id 打错等于没写。 写成 agent-defualt-model,dsh 往 stderr 打一行就照常
启动。用 Web UI 起的话你根本看不见那一行——只会觉得「我明明改了怎么没反应」。
升级改了 entry id 之后这两种情况都会变多,而且都要等到行为漂移了几周才被发现。
效果
dsh-doctor · profile web · 130 entries (25 disabled)
✗ patch on "session-title" dropped 2 default config fields config-clobber
@deepseek-ai/dsh-session-title
dsh replaces an entry's whole config when a patch targets it. These fields
were in the shipped defaults but are missing from the tree that boots, so
the plugin now runs without them.
- fallbackMaxBytes: 40
- maxTitleBytes: 80
fix Restate them in your patch for "session-title":
fallbackMaxBytes: 40
maxTitleBytes: 80
✗ patch targets "agent-defualt-model", which is not in the composed tree dead-patch
~/.dsh/profiles/web/cordis.patch.yml patches an entry id that does not
exist, so dsh prints one stderr warning and boots without it. Everything
in that patch is inert.
fix Did you mean "agent-default-model"? Rename the id, or delete the
patch block if the plugin is gone.
2 error
作为 dsh 插件使用
装进 profile 后,dsh-doctor 会注册一个 config_doctor 工具,让 agent 能检查
它自己正跑在什么配置上:
dsh plugin --profile web add dsh-doctor
然后直接问它「我改的 session-title 怎么没生效」,它会从合成树里给答案而不是猜。
只读;--fix 保持只在 CLI 里可用——改写你的 patch 文件不该由 agent 在一轮对话里做。
检查项
| 规则 | 级别 | 检出什么 |
|---|---|---|
config-clobber |
error | patch 因为没重写而丢掉的默认配置字段 |
dead-patch |
error | patch 指向树里不存在的 entry id(带拼写纠正建议) |
tool-collision |
error | 两个已挂载插件注册了同名工具——dsh 会直接拒绝启动 |
plugin-not-mounted |
warn | 装进 profile 但根本没被加载的插件 |
plugin-stale |
warn | 超过 180 天没发新版的第三方插件 |
entry-removed |
warn | 被你的 patch 层移除的官方条目 |
entry-toggled / entry-added |
info | 与官方 profile 的其他差异,让改动可见 |
explain 模式
配置健康的人只会看到「no problems found」,这句话没告诉他任何东西。--explain
用来回答「我这套 dsh 到底装了什么」:
Your harness: 130 entries, 103 active, 25 disabled, 2 conditional
Web UI 32
Tools 18 (16 off)
Sessions & history 11
Agent loop 5 (1 off)
...
Conditional (2) — enablement is decided at mount time, not here
bash-sandbox !!js process.platform === 'win32'
pwsh-sandbox !!js process.platform !== 'win32'
disabled: 是 !!js 表达式的条目会被单独列为 conditional,而不是压成布尔值——
它到底开不开取决于启动时的机器,本工具不会执行你的配置去猜这个答案。
用法
npx dsh-doctor # 检查 web profile
npx dsh-doctor --explain # 描述这棵树,而不是检查它
npx dsh-doctor --profile headless # 指定 profile
npx dsh-doctor --verbose # 显示 info 级别提示
npx dsh-doctor --json # 机器可读输出
npx dsh-doctor --fix # 自动把被抹掉的字段补回去
npx dsh-doctor --offline # 跳过 npm registry 查询
npx dsh-doctor --quiet # 只在有问题时输出
退出码:0 正常或仅有警告 · 1 至少一个 error · 2 无法检查。
适合放进 CI,或者在升级 dsh 前跑一次:
npx dsh-doctor --quiet || echo "升级 dsh 前先检查一下你的 patch"
--fix
--fix 会把 patch 丢掉的字段按官方默认值补回同一个 config: 块:
- id: session-title
config:
fallbackMaxWords: 12
+ fallbackMaxBytes: 40
+ maxTitleBytes: 80
只在这个块内部改动——注释、顺序、其他条目全部逐字节不变——写之前先在旁边生成
.bak。嵌套路径会列出来让你手动处理而不是猜着写;dead-patch 永远不自动修,
因为"改名还是删掉"是你的判断。
原理
直接复用 dsh 自己的合成结果:
dsh --profile <p> --dump-config—— 真正启动的树(bundles → profile patch → home patch → overlay)dsh --profile <p> --dump-default-config—— 去掉你的用户层之后的同一棵树
所有结论都来自这两者的差异,所以能把问题归因到你自己的 patch,而不是上游默认值。此外还会读取 profile 的 package.json 和各层 cordis.patch.yml。
任何情况下都不会加载插件,也不会执行配置里的 !!js 表达式。
关于"只读"这件事有两个必须说清的例外:
--fix是会写的,这是设计如此——只动被标记的那个config:块,写前留.bak。- 合成 profile 是 dsh 自己的动作,而 dsh 在一个 profile 首次被使用时会落地模板。
所以如果你的
$DSH_HOME里还没有任何 profile,跑一次会留下profiles/<name>/——是 dsh 建的不是本工具建的,但指向全新目录前值得知道。
安装与卸载
当 CLI 用不需要安装,npx dsh-doctor 直接跑。
当插件用:
dsh plugin --profile web add github:asdf17128/dsh-doctor # 安装
dsh plugin --profile web remove dsh-doctor # 卸载
卸载后 config_doctor 工具消失,不留任何残留——这个插件从不写入你的 Harness home。
兼容性
基于 @deepseek-ai/dsh 0.1.0-rc.5 验证。dsh 处于 developer preview,会有破坏性
变更;检查逻辑读的是 --dump-config 的输出,所以那个格式一旦变动会最先受影响。
如果新版 dsh 下报出奇怪结果,提 issue 我来定位差异。
环境要求
Node 18+,以及一个可用的 dsh(优先用本地 node_modules/.bin/dsh,否则用 PATH 上的)。
为什么做这个
dsh 处于 developer preview,会有破坏性变更;「一切皆插件」意味着你的树是一叠 patch 层,而分层规则在一个方向上格外不留情——上面那些失败模式全是静默的。这个工具负责把它们说出来。
行为基于 @deepseek-ai/dsh 0.1.0-rc.5 实测验证。
参与
欢迎提 issue 和 PR,尤其欢迎带复现步骤的新检查规则。npm test 跑测试。
许可
MIT
链接
同类插件
yjh051108/dsh-routing-suite★ 6995
一个仓库三件套:DSH 插件包的运行时注入器(注入、热重载、卸载、开发侧挂区一键转正、路由自愈,外带设置页插件管理:列出、卸载、拖入文件夹内化)、任务感知的思维模式路由 agent 预设(router-standard / router-spec / router-react)、以及分级两级任务协议(commit_star / lock_stage / revise_do / edit_plan / mark_task / redteam_verdict 六个工具,任务状态落盘)。注入器实现直接在库内,安装的是它自己的行为而不是一份依赖清单。
strukto-ai/mirage#dsh★ 3667
把文件系统与 bash 提供者换成 mirage 虚拟工作区:文件工具与 shell 命令作用于挂载的资源(RAM、S3、Redis、Slack、Gmail、Notion、Postgres)而非宿主磁盘,支持按挂载点设置读/写/执行模式、按命令选择沙箱(进程内 monty、pyodide、quickjs;远程 docker、e2b、daytona),并可在虚拟终端中安装 CLI(git、gh、slack、linear、ntn、gws,或自行注册的程序树)作为命令头词。
hust-open-atom-club/oh-dsh★ 325
社区发行版:TUI、桌面端与 Web UI 统一体验,分层安装、一步到位。
weijiafu14/pi2dsh★ 208
Pi Host ABI 兼容引擎:装一次之后,npm 上的 Pi 扩展原包经 `dsh plugin add <pi-package>` 直接作为 DSH 原生插件挂载。已在官方 DSH 上端到端验证 pi-mcp-adapter(完整 MCP 管理面:OAuth、resources、prompts、MCP Apps、elicitation、sampling)、@tintinweb/pi-subagents、pi-code、pi-hermes-memory、pi-background-tasks;`pi2dsh inspect` 在安装前报告一个包的兼容情况。
lire1131/dsh-undo-savepoint★ 167
DSH 撤销/回退系统:配置变更自动存档,一键撤销/恢复/回退到任意版本,支持 WebUI 与离线 CLI/GUI 工具(DSH 启动失败也能救)。
Fishquito7/dsh-skill-mcp-panel★ 158
在 DSH Web 设置中管理技能与 MCP 服务器:技能卡片热启停、工作区作用域、分组、批量迁移与拖拽导入,以及 stdio/HTTP MCP 增删改查、连接测试、密钥脱敏,并附带统一 dsh-panel 命令行。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。