为什么 DSH 升级后插件会失效

别名:插件失效、升级炸插件、DSH 升级后插件没了、dsh-loader 挡不住什么

从层栈与补丁的绑定关系解释升级为什么会打断已装插件:命中行 id 的补丁、被整段替换的 config、客户端入口图、以及 home 层压过 profile 层这四类断裂,并说清兼容层 dsh-loader 能扛哪一段、扛不住哪一段。

升级为什么能「炸」插件

照直觉,升级 DSH 像是在换软件版本,插件是独立装的 npm 包,两边应该互不打扰。实际不是:插件不是挂在 DSH 上的功能开关,而是一层补丁。启动时 DSH 把 profile 里那串 bundle 的补丁依次叠到一棵空树上(层序与覆盖规则见 concept/1),插件那一层要么 insert 新行、要么按 id 改已有行的顶层键。于是「插件能不能用」取决于补丁能不能命中它要改的那些东西——而行的 id、行上的键、插件注入的服务,全都是 DSH 自己的实现细节,会随版本移动。

失效因此有两副面孔:看得见的(启动报错、插件整行不见了)与看不见的(插件还在,配置丢了、界面没出现)。下面四类断裂分别对应它们。

一、按 id 命中的补丁,目标不见了

补丁列表里只有两类操作:id + insert(往某个分组里插行)与 id + 其它键(改已有行的顶层键)。两者都靠 id 找目标:

  • 找不到目标的补丁只告警、不报错,被跳过。这条设计对启动稳定性是好事,对插件是坏事——升级把某个行 id 改掉之后,插件那层会被安静地跳过,界面上什么都不显示,日志里只有一行告警。
  • 列在 dsh.profile.bundles 里、却没有 dsh.bundle 声明的包会直接抛错(declares no dsh.bundle in its package.json),这是配置错误而不是兼容问题:多半是你手改过 profile 的 package.json。

要做的事:升级后养成看启动输出的习惯;dsh --profile <name> --dump-config 会打印组合后的树,相对 --dump-default-config(不叠用户层与 --patch)就能看出「包带的」与「你或插件改的」差在哪。

二、config 是整段替换,不是深合并

这是最容易在升级后以「插件在、功能没了」的形式暴露的一类。补丁写 config: {...} 时,目标行的整个 config 被换掉,不是逐键合并。旧版本里这样写没事,是因为当时该行的 config 只有那么几个键;升级后新版本给这一行补了必需的键,插件那层那份「只写自己关心的键」的 config 就把它们一并抹掉了。

要做的事:看到配置项莫名回到默认值或功能半残,先怀疑它。--dump-config 打出来的就是替换后的结果,照着新版默认值把要保留的键补全。

三、客户端半端是另一条链,宿主修好了它也不一定在

一个插件可以两半,宿主侧在 Node 进程里、客户端侧在浏览器里(见 concept/4)。客户端半端不是自己跑起来的:服务端扫描已经加载的插件行,把每个客户端半端变成入口图里的一行,序列化进页面 <head>;某个包解析不出 ./client 产物时聚合报错会连带说出它在找哪个路径。

两条推论:

  • 宿主那一行没挂上(第一类断裂),客户端入口就不会出现在图里——你在界面上看不到任何东西,但宿主日志里可能只有一个告警。排查看两处,不要只看一处。
  • 声明里的 inject 是一串包名,升级改了客户端包的拆分或命名,组合阶段就会直接抛错。这类修复点是 package.json 的 dsh.client,不是插件逻辑。

四、home 层压过 profile 层,改了却像没改

层序是固定的:bundle 层(按 dsh.profile.bundles 顺序)→ profile 的 cordis.patch.yml → $DSH_HOME/cordis.patch.yml → --patch 覆盖 → 遥测开关。注意第三个:机器级的 home 层比每个 profile 自己的层更高,这是有意的设计(机器偏好对所有 profile 生效)。

升级后最典型的踩法是:为了修插件,你在某个 profile 的 cordis.patch.yml 里改了配置,结果没生效——因为 $DSH_HOME/cordis.patch.yml 里有一条更早留下的同名补丁压在上面。「我改了没用」在 DSH 里通常不是缓存问题,而是层序问题。

dsh-loader 能挡哪一段、挡不住哪一段

dsh-loader 的定位是版本感知的运行时兼容层:让第三方插件在 DSH 本体升级、内部包版本变动之后,不改源码继续挂上。把它当作第一道缓冲是对的,但要清楚边界:

失效类型dsh-loader 管到吗为什么
内部包版本变动、老签名继续被调用管得到(它的本职)这正是「兼容 shim」要吸收的差异
补丁命中的行 id 变了管不到补丁是数据,shim 不改写别人的补丁文件;命不中就是命不中
config 整段替换把新键抹掉管不到这是补丁语义,不是加载器能修的
客户端入口图与 inject 声明断裂管不到客户端半端的解析与签名在另一个包里
home 层压过 profile 层管不到层序是契约,不是兼容问题
dsh-loader 自己没跟上新版 DSH管不到自己它同样是装在 profile 里的一个包(见 concept/2),也一样要等更新

它自己的可靠性也有实况可看:市场账本里唯一声明了它的整合包是「更好的侧边栏」(hxh230802.better-sidebar,manifest v4),dependencies 里写的是 "@dsh-plugin/dsh-loader": "1.3.3-dev.33218986978"——-dev.<一串数字> 这种版本形态说明上游在发开发版。把「长期稳定」押在一个发开发版的兼容层上,是有风险的;它支持的 DSH 版本区间本轮也没能核实(见 dsh-loader 的 npm 页面 的「未核实」一节)。

一个具体的插件长什么样

拿 plugin/2 当样本:它的 cordis.patch.yml 只有一行 insert,把宿主插件按 id dspack-host 插进树里;它的客户端半端靠 package.json 的 dsh.client 声明与 exports["./client"] 挂进页面。看懂了这个结构,就明白升级要动的是什么:不是它的 lib/host.js,而是那行 id: dspack-host 所依赖的服务名(connection / webServer / skills)与它写的 config 键。这层结构与自写插件一模一样,写法见 tutorial/2。

升级之后按什么顺序查

  1. 看启动输出:被跳过的补丁只告警,别漏掉。
  2. 对比两棵树:--dump-default-config 与 --dump-config,定位差异落在哪一层。
  3. 最小化实验:把可疑改动单独写成一个 --patch 文件叠上去。它按 argv 顺序排在最后,能立刻确认「是补丁本身的问题」还是「被更高的层压住了」。
  4. 最后才动插件源码:上面三类断裂大多不需要改插件;真要改,改的也多半是声明(dsh.bundle / dsh.client / patch 文件),不是逻辑。

「装插件时到底发生了什么」——依赖怎么解析、落点在哪、为什么 GitHub 来源要先授权构建——见 tutorial/3;层序与 config 替换语义的权威出处见 concept/1。

相关教程

谁引用了这一条

信息表

appliesTo
dsh 0.1.0-rc.6(实测,本机 @deepseek-ai/dsh 版本);更高版本未核实
category
ops.upgrade
difficulty
intermediate
origin
original
plugins
[object Object]、[object Object]
prereq
concept/1、concept/2
related
concept/4、tutorial/3
updatedAt
2026-10-02

被 2 篇教程引用 · 被 6 条词条引用

最后更新 2026-10-02