← 返回列表
⚠ 装前注意
DSH 插件市场:搜索 GitHub dsh-plugin 话题插件,一键安装到本地 dshagent 工具 +…
基本兼容但装前注意:未发布到 npm registry,仅可从源码安装 · 最近上游提交 2026/8/27 · 已提供中文文档
DSH 插件市场:搜索 GitHub dsh-plugin 话题插件,一键安装到本地 dsh(agent 工具 + 设置页插件市场 tab)
综合分
34
GitHub 分
34
用户评分
—
★ Stars
4
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add 1e0zj/dsh-plugin-mall未发布到 npm registry,仅可从源码安装,改用 GitHub 源安装
数据截至 2026/9/19(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
安装兼容性检查⚠ 装前注意
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✗npm 包@1e0zj/dsh-plugin-mall(未发布到 npm,仅可源码安装)
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
未发布到 npm registry,仅可从源码安装
验证方式:npm registry 存在性 + package.json 静态校验 · 最后验证 2026/9/19 01:12:04
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-app-boot@deepseek-ai/dsh-tools@deepseek-ai/schemastery用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
dsh-plugin-mall
一个面向 DeepSeek Harness(dsh)的开放插件市场:搜索所有带有 topic:dsh-plugin 标签的 GitHub 仓库,自动验证哪些是真正的 dsh 插件,一键安装和更新。
中文说明 · 安装 · 为什么还要另一个市场 · 路线图
两个界面:dsh Web UI 中的 Settings → Plugins → Marketplace 标签页,以及可在任意会话中使用的五个 agent 工具。
为什么还要另一个市场?
精选列表只展示已经过审核并合并的内容。这个市场从构造上就是开放的:任何带有 topic:dsh-plugin 标签的仓库在推送的那一刻就可被发现——无需提交,无需审批队列。为了让这种开放性保持可用:
- 自动验证——每个搜索结果的 package.json 都会被抓取(jsDelivr/raw 双源 CDN,不消耗 API 配额),并检查是否存在官方的 dsh.bundle / dsh.client 清单。通过验证的插件会获得绿色徽章;默认的“仅已验证”视图会过滤掉约 73% 的话题噪声(空仓库以及蹭标签的无关项目)。
- 浏览时兼容性徽章——在你点击任何内容之前,每张卡片还会根据你的 profile 进行静态扫描:声明的冲突、互斥组、loader-id 冲突(来自仓库的 patch 文件)、候选插件的 patch 会在你当前运行的 profile 中关闭或重新配置的行、宿主模块遮蔽,以及 peer/Node/OS 版本范围。卡片会相应地显示 适配 / 有风险 / 冲突 / 适配未知——仅供参考;安装预检仍是强制关卡。
- 防抢注——只有当 registry 条目的 repository URL 指回同一个 GitHub 仓库时,安装才会优先使用 npm tarball;其他任何情况都会回退到显式的 github: 规格。
- npm 优先安装——registry tarball 比整仓库 GitHub 下载更小,并且带有完整性校验。查找会遵循 pnpm 实际安装所用的 registry(profile .npmrc → pnpm config get registry → npmjs),因此使用镜像的用户会保持 npm 优先,而不会静默回退到整仓库克隆。
- 更新管理——已安装插件会与 registry 的 latest 进行比较;每个插件都可一键更新。
- 冲突防护——每次安装都会先运行一次隔离的预检:候选插件在禁用脚本的情况下安装到一个一次性目录中,并对照实时 profile 扫描 loader-id 冲突、重复挂载、宿主模块遮蔽以及版本/操作系统/对等依赖范围。补丁层不是逐条读取,而是按照加载器应用它的方式来组合——profile 会在包含和不包含候选插件层的情况下分别组装,位置与 dsh 应用它的位置一致,然后对两棵结果树进行 diff。每次测试运行都会将这种组合与加载器自身的 applyEntryPatches 进行比对,覆盖固定形态和 300 个生成的补丁组合,因此“dsh 会启动成什么样”不是猜测。报告出来的是实际会发生变化的内容:禁用、重新启用或替换他人的行配置会发出警告并点名这些行,超过十行这样的行时,候选插件就是竞争性组合而非新增,会被阻止;而一个本身就是另一个前门的 bundle(自带 bin、终端对等依赖、没有浏览器部分)一旦开启或关闭行,或重写保护行,就会被阻止——指纹用于佐证,绝不会单独作为阻止依据。硬冲突会被阻止,警告需要显式确认。profile 的承重文件会在 pnpm 触碰它们之前被快照,并在失败时恢复。待处理的安装会在下次启动时解决,无论你如何启动:此插件在加载时就会执行恢复。这一证明比听起来更窄——它意味着加载器到达了 marketplace 的位置,而不是整棵树都组合成功(#24)。通过 guard launch 启动会在此基础上增加一个宽限窗口,同时覆盖几秒后崩溃的插件,以及 marketplace 之后某个条目中的 import/apply 失败(参见启动保护)。
- 弹性 — 限流断路器,GitHub 的 1000 条结果搜索窗口被优雅处理,当 pnpm 缺失时通过 corepack enable pnpm 自愈,一键重启 dsh(仅限回环,allowRestart: false 可禁用)。传出 Host 只有在磁盘上的 helper 明确确认当前交接协议并在稳定窗口内保持存活后才会退出;缺失、过期或不兼容的 helper 会让现有 Web 服务继续运行。后继者随后等待传出 host 消失后再绑定——启动到一个已被占用的端口会被解读为“待处理的插件使 dsh 崩溃”,并回滚一个本来没问题的安装。在从交互式终端启动的 Windows 上,重启会在一个可见的控制台窗口中运行(cmd /c start;被包装的 argv 以 JSON 计划文件的形式传递,绝不通过 cmd 命令行):输出同时分流到窗口和日志,并且关闭窗口会终止整个 guard/dsh 树(关闭事件完全绕过控制台输入处理,因此它总是有效)。窗口中的 Ctrl+C 会被尽力处理——实际测试表明,dsh 内部的某个组件可能会将控制台切换到原始模式,此后该控制台上的任何进程都完全收不到 Ctrl+C;关闭窗口才是可靠的停止方式。服务/计划任务/无 TTY 的 Windows 以及所有其他平台保持完全后台行为——没有多余的控制台,不会重新打开浏览器(发起请求的页面仍然在那里,并会自行重新连接);无论重启打印出什么,都会落到 /guard/restart-.log。
安装
从 npm 安装
dsh plugin --profile web add @1e0zj/dsh-plugin-mall
从 GitHub 安装
dsh plugin --profile web add github:1e0zj/dsh-plugin-mall
本地开发——目前不可用,见下方说明
dsh plugin --profile web add link:C:\path\to\dsh-plugin-mall
安装后重启 dsh。
link: 目前无法设置,原因在本插件的上游。在
link: 下,Node 从项目的真实路径加载,因此裸导入必须从项目自己的
node_modules 解析——这需要先在项目内运行 npm install。而这会失败:框架包的 registry dist-tags 目前横跨两条
发布列车(dsh-tools 的 latest 仍是 0.0.1-rc.x,而 dsh-app-boot 处于
0.1.0-rc.x),它们的 peer 范围不相交,npm 会以 ERESOLVE 停止。
两个逃生舱都无济于事——--legacy-peer-deps 会跳过 peer,因此裸导入仍然
无法解析,而 --force 会在项目内植入一份混合列车的框架副本,link: 随后会加载它而不是宿主机的框架,导致
开发说明中描述的完全相同的重复模块崩溃。在这些 dist-tags 对齐之前,
请针对本地 tarball 进行开发——npm pack,然后用 file:
规格安装 .tgz(配方见下方安装部分)。不要手动覆盖
node_modules 下的文件:它们被硬链接到 pnpm 的全局存储中,之后任何
pnpm add/remove 会重建依赖树并无论如何都会恢复它们。
重新打包并再次运行 add 什么也不会做:file: 规范与版本都没有变化,因此 pnpm 认为它已经安装,永远不会比较 tarball 的字节。命令成功,package.json 看起来正常,而 node_modules 仍然保留着之前的构建。务必在 add 之前先 remove,或者给测试构建一个独立的版本号。然后彻底重启 dsh —— 安装是在仍在执行旧代码的宿主进程中运行的。
启动保护(guard CLI)
已知问题 —— 自 v0.3.4 以来的每个版本,包括 v0.4.17(#24): 一次普通的 dsh web 启动可能会在市场插件加载时立即提交并删除待处理的快照,而此时后续的加载器条目尚未完成加载。降级并不是变通办法 —— 你必须回到 v0.3.4 之前,并放弃此后的所有修复。如果后续条目失败,自动回滚将不再可用。在 #24 修复之前,每次市场安装或更新后的首次重启请使用 guard launch。市场的 “Restart dsh” 按钮已经使用受保护的启动路径(它会生成 guard launch --await-exit),因此从 UI 重启是安全的。
如果日志已经显示 startup recovery: committed,随后启动失败,guard recover 通常已经没有快照可恢复 —— 请手动修复 cordis.patch.yml,或者恢复一个 profile 备份。
最后一道防线在启动阶段。该包附带一个独立的、不依赖宿主的 CLI(可执行文件 dsh-plugin-guard,也可通过路径运行):
受保护的安装:隔离预检 → 快照 → dsh plugin add → 磁盘上验证
dsh-plugin-guard guard add --profile web
在启动试用期下启动 dsh
dsh-plugin-guard guard launch --profile web -- dsh web
裸 dsh-plugin-guard 可执行文件只有在 profile 的(或全局的).bin 位于 PATH 上时才能解析。两种不依赖 PATH 的形式:
已安装的 profile:从 profile 自己的 node_modules/.bin 运行该可执行文件
pnpm --dir exec dsh-plugin-guard guard add --profile web
pnpm --dir exec dsh-plugin-guard guard launch --profile web -- dsh web
开发环境:通过源码路径运行
node /node_modules/@1e0zj/dsh-plugin-mall/src/cli.js guard add --profile web
node /node_modules/@1e0zj/dsh-plugin-mall/src/cli.js guard launch --profile web -- dsh web
一次普通的 dsh web 已经会解析待处理的安装。 市场插件在加载时会运行恢复,因此待处理标记会被提交 —— 或者被回滚,要么是因为 profile 验证失败,要么是因为安装被暂停在构建脚本审批关卡(未获批准的安装永远不会被提交,无论它看起来多么健康)。如果没有这一点,单次安装就会卡住 profile —— 只要有一个标记未处理,之后所有的安装和卸载都会被拒绝。
一次普通启动实际能证明的东西比看上去要窄: 到达市场自己的 apply 只意味着加载器走到了市场所在的位置——并不意味着整个插件树都组合成功了。它之后的条目仍可能导入或应用失败,而到那时快照已经没了(这就是 #24)。
guard launch 仍然严格更优,而且理由有两个而非一个:它覆盖了启动正常、几秒后才崩溃的插件,并且覆盖了位于市场之后的加载器条目中的导入/应用失败——这正是普通启动会直接放行的情况。它在启动 -- 之后的命令前,会检查配置文件的待安装标记:
- 没有待安装 —— 命令按原样运行,继承终端,其退出码被保留。
- 磁盘上明显损坏 —— 配置文件在启动之前回滚到安装前快照,然后命令在恢复后的状态上启动。
- 在宽限期内保持存活(默认 10 秒;用 --grace-ms 更改)—— 待处理快照被提交,包装器继续等待该进程。仍停留在审批门处的安装则会被回滚:仅靠存活过观察期只能证明 JS 能加载,不能证明你批准了它的构建脚本。
- 在宽限期内以 0 退出(一次性命令)—— 待处理快照被提交。
- 在宽限期内崩溃或以非零退出 —— 配置文件被回滚,完全相同的命令会用恢复后的状态重启一次(绝不循环);重启后进程的退出码被保留。在平台支持的情况下,SIGINT/SIGTERM 会被转发给子进程;在 Windows 上,.cmd/.bat 垫片会通过 %ComSpec% 并采用严格的逐参数引用。
限制:宽限窗口就是观察期——只有在它之后才暴露的失败(几分钟后才崩溃的插件,或在特定交互时崩溃的插件)无法自动回滚,因为提交会删除活动快照,之后 guard recover 就没有可恢复的东西了。guard validate 仍会诊断磁盘上的状态,但提交后的失败需要手动修复——卸载并重新安装该插件,或恢复你单独保留的备份。这两个命令都只做静态磁盘验证;两者都不能证明插件实际能加载。损坏的待处理标记会故障关闭:命令不会被启动,也不会删除任何未经验证的路径。保留快照并修复或恢复一个可信的标记,然后运行 guard recover;只有在你独立验证了配置文件,或决定放弃自动恢复之后,才隔离该标记。
dsh 在更新后无法启动?(影响 0.2.0 – 0.3.2,已在 0.3.3 中修复)
症状:dsh web 退出并报 cannot resolve profile bundle ""。
原因:将已安装的插件更新进一次回滚(最常见的是
目标带有构建脚本,且流程在审批卡片处暂停)可能会丢失
该包——回滚时重装旧版本被 pnpm 的
“Already up to date”短路逻辑所欺骗,因此插件离开了 node_modules,而它的
bundles 声明却保留了下来。全新安装和移除不受影响。
恢复:按照 profile 的 package.json 声明的方式,原样把该包加回来,
然后运行 dsh web。
= %USERPROFILE%\.dsh\profiles\web 或 ~/.dsh/profiles/web
npm 包——package.json 中写的是 "dsh-better-sidebar": "^0.13.1"
pnpm --dir add "dsh-better-sidebar@^0.13.1" --ignore-scripts
GitHub 源——package.json 中写的是 "dsh-at-file": "github:omdsh-dev/dsh-at-file"
pnpm --dir add "github:omdsh-dev/dsh-at-file" --ignore-scripts
此处不要动用 0.3.2 的 dsh-plugin-guard recover。它的逐包
回退逻辑会拒绝 ^(一个 cmd 元字符)并完全跳过 github:,而
pnpm 写入的几乎每个依赖都带有其中之一——因此回退逻辑
永远不会触发,恢复会以失败关闭。已在 main 上修复:回退逻辑现在会固定
锁文件解析出的版本(或提交)。已在 0.3.3 中修复——在该版本上
dsh-plugin-guard recover 确实能修复此问题。
更新卡在安装脚本审批卡片处?(更新到 0.4.15)
症状:更新市场本身时暂停在“安装期代码需要确认”,
点击允许会失败并报 allowBuildScripts cannot be specified on initial
install。再次点击也无济于事。
谁会遇到:只有那些从未批准过带安装脚本的依赖的 profile
(实际上是传递引入的 node-pty)。如果你的 profile 的
pnpm-workspace.yaml 已经带有 allowBuilds: {node-pty: true}——大多数
安装过东西的 profile 都是如此——审批卡片就永远不会出现,
更新会直接成功。
原因:pnpm 在审批门之前就安装了新的市场包,这会在磁盘上
替换正在运行的插件;dsh 重放组装树,整个
市场 UI 重新挂载,丢失了审批令牌签发时所绑定的浏览器会话。
该令牌随后无法恢复,因此重试时到达的请求没有它。
恢复——重启 dsh(启动恢复会回滚暂停的安装),然后
要么从 CLI 更新:
= web, guard-test, ...
dsh plugin --profile add @1e0zj/dsh-plugin-mall@0.4.15
要么预先批准一次构建脚本,然后照常从页面更新:
/pnpm-workspace.yaml
allowBuilds:
'node-pty@1.1.0': true
从 0.4.15 起已修复——会话身份现在能在重新挂载后存活。该
修复无法挽救到 0.4.15 本身的这一跳:在那次更新期间,页面
替换前的代码和正在运行的 dsh 进程都仍是旧版本。
代理工具
| 工具 | 作用 |
|---|---|
| market_search | 搜索带有 topic:dsh-plugin 标签的 GitHub 仓库(按 star 排序,关键词过滤,服务端 stars:>=1 噪声下限) |
| market_info | 查看单个仓库:stars、license、package.json、是否声明了 dsh.bundle.patch / dsh.client |
| market_install | 以后台任务方式将插件安装到某个 profile(npm 优先的 spec 解析) |
| market_uninstall | 移除插件:pnpm remove + bundle 层协调 + client 行清理 |
| market_installed | 列出某个 profile 已安装的插件及其 bundle 状态 |
中文说明
dsh 插件市场 — 搜索 GitHub dsh-plugin 话题下的 DeepSeek Harness 插件仓库,自动验证哪些是真 dsh 插件,一键安装与更新。
路线图(安全 > 便捷 > 精简,以及明确不做的)
与策展列表不同:任何打上 topic:dsh-plugin 的仓库推送后立即可被发现——无需投稿、无需审批。为保证开放性可用,做了这些事:
- 自动验证:逐仓库拉取 package.json(jsDelivr/raw 双源 CDN,不占 API 配额),按官方 dsh.bundle / dsh.client 声明打徽章;默认“只看已验证”视图过滤约 73% 的话题噪音
- 浏览期适配徽章:点安装之前,每张卡片就已对照你的 profile 做过一次静态扫描——声明冲突、独占组、loader-id 冲突(取自仓库补丁文件)、候选包的补丁会停用或改写你当前 profile 里的哪些行、宿主模块遮蔽、peer/Node/OS 范围;卡片上直接显示 适配 / 有风险 / 冲突 / 适配未知。徽章只是提示,真正的拦截闸门仍是安装预检
- 防抢注:仅当 npm registry 条目的 repository 指回同一 GitHub 仓库时才用 npm 安装,否则回退 github: 源
- npm 优先安装:registry tarball 比整仓库下载更小且带完整性校验;查询用的 registry 跟随 pnpm 实际安装源(profile .npmrc → pnpm config get registry → npmjs),换了镜像也不会退化成整仓库克隆
- 更新管理:已装插件与 registry latest 比对,逐个一键更新
- 冲突防护:每次安装先跑隔离预检——候选包在一次性目录里以禁用脚本的方式装好后,对照 live profile 扫描 loader-id 冲突、重复挂载、宿主模块遮蔽和版本/OS/peer 范围。补丁不是逐条读,而是按 loader 的方式组装——把候选包的层放进 dsh 会放的位置,装前装后各组装一棵树再逐行比对。这套组装每次跑测试都会和 loader 自己的 applyEntryPatches 对拍(固定形状 + 300 组随机生成的补丁组合),所以「dsh 会装出什么树」不是猜的。报出来的是真正会变的东西:停用、重新启用、或替换别人整块 config,都点名是哪几条 id 并警告;超过十条就不是叠加而是另一套组合,直接拦截;候选包本身就是另一套门面(自带 bin、依赖终端栈、没有浏览器半边)时,只要它停用/启用了行、或改了沙箱审批这类保护行,就直接拦截——指纹只做佐证,单凭它不拦。硬冲突直接拦截,警告需显式确认。安装前给 profile 的承重文件拍快照、失败即回滚;pending 安装在下次启动时自动了结,不挑启动方式:本插件加载时就跑恢复。但这份证明比听上去窄——它只说明 loader 执行到了市场所在的位置,不代表整棵树组装成功(#24)。经 guard launch 启动则多一层观察期,既覆盖「启动几秒后才崩」,也覆盖「排在市场后面的 entry 加载失败」(见下方「启动保护」)。
- 工程韧性:限流熔断、GitHub 5xx/超时退避重试(504 瞬时故障不再直达用户)、GitHub 1000 条搜索上限优雅处理、pnpm 缺失时 corepack 自愈、一键重启 dsh(仅 loopback,可 allowRestart: false 关闭)。旧 Host 只有在磁盘上的 helper 明确回报当前交接协议、并活过稳定窗口后才退出;helper 缺失、过旧或协议不兼容时,现有 Web 服务继续运行。之后 successor 会等旧进程真正退出再绑端口——抢在端口没释放时启动,会被判成「新装的插件把 dsh 搞崩了」,把一次本来正常的安装回滚掉。交互式 Windows 终端下,重启跑在一个可见的控制台窗口里(cmd /c start 拉起;被重启命令的 argv 走 JSON 计划文件,绝不经过 cmd 命令行):输出同时打到窗口和日志,关闭窗口即终止整组 guard/dsh 进程树(关窗事件不走控制台输入处理,永远有效);窗口里的 Ctrl+C 尽力而为——真机实测 dsh 内部组件可能把控制台翻成 raw mode,此后该控制台上任何进程都收不到 Ctrl+C,关窗才是可靠的停止方式;服务/计划任务/无 TTY 的 Windows 与其他平台维持全后台行为——不留控制台窗口、也不再重开浏览器:发起重启的那个页面还在,会自己重连;重启过程的输出写进 /guard/restart-.log(后台模式想停 dsh 用任务管理器结束 node 进程)
安装
从 npm
dsh plugin --profile web add @1e0zj/dsh-plugin-mall
从 GitHub
dsh plugin --profile web add github:1e0zj/dsh-plugin-mall
本地开发 —— 目前装不起来,见下方说明
dsh plugin --profile web add link:C:\path\to\dsh-plugin-mall
装完重启 dsh(dsh web 进程)后生效。
link: 目前用不了,原因在上游、与本插件无关。link: 下 Node 从项目的真实
路径加载模块,裸导入只能从项目自己的 node_modules 解析,所以得先在项目里
npm install 一次 —— 而这一步会失败:框架包在 registry 上的 dist-tags 眼下横跨
两条发布线(dsh-tools 的 latest 还停在 0.0.1-rc.x,dsh-app-boot 已经是
0.1.0-rc.x),二者对 dsh-invariants 的 peer 区间无交集(^0.0.1-rc.x 只收
0.0.1-rc.x,^0.1.0-rc.x 只收 0.1.x),npm 以 ERESOLVE 中止。
两个逃生口都不解决问题:--legacy-peer-deps 跳过 peer,裸导入照样解析不了;
--force 会在项目里装一套混版本的框架副本,link: 加载的就是那套而不是宿主
那套 —— 正好踩中下面「开发说明」里讲的双副本身份分裂崩溃。
在上游 dist-tags 对齐前,本地开发用本地 tarball:
npm pack # 产出 1e0zj-dsh-plugin-mall-.tgz
dsh plugin --profile web remove @1e0zj/dsh-plugin-mall
dsh plugin --profile web add file:C:\code\dsh-plugin-mall\1e0zj-dsh-plugin-mall-0.1.13.tgz
这样 pnpm 的规范副本本身就是新代码,后续任何 pnpm add/remove 重建依赖树都不会
把它换掉;顺带还验证了 files 字段没漏文件。
remove 那一步不能省。 改完代码重新 npm pack 之后只跑 add,pnpm 会
什么都不做:spec(file: 路径)和版本号都没变,它就判定「已经装好了」而
跳过,根本不去比对 tarball 的字节。表现是命令成功返回、package.json 看着也
对,但 node_modules 里还是上一版代码——排查时极难想到这一层。要么每次都
remove + add,要么给测试包换一个版本号(如 0.3.5-test.1)。
同理,装完必须完整重启 dsh(不是刷新页面):卸载/安装是由正在运行的那个
宿主进程执行的,它内存里跑的还是旧代码。新装的代码要下一次启动才生效。
不要用直接覆盖 node_modules 里文件的办法。 它有两个坑:
一是 pnpm 装出来的文件是硬链接(与全局 store 共享 inode),直接 cp 覆盖会
穿透硬链接改掉 store 里的内容且 pnpm 不会察觉,必须先 rm 再写;二是任何一次
pnpm 操作都会重建整棵树,按 lockfile 从 store 把你覆盖的文件还原回去 —— 而通过
本插件装/卸任何插件都会触发 pnpm add/remove,也就是说测试市场的安装功能这个动作
本身就会抹掉被测代码。(已加载进内存的模块不受影响,重启后才会退回旧版。)
通过 npm/GitHub 安装则没有上述任何问题:pnpm 把真实拷贝装进 profile 的
node_modules,框架包由宿主经 profiles/node_modules 里指向全局 dsh 的软链提供,
版本天然一致。
另:Windows 上 file:/link: 的路径不能含空格。pnpm 是经 cmd 拉起的,
Node 只把参数用空格拼接、不逐参加引号,带空格的路径会被拆成两个参数;
自己加引号也不行(" 属于被拦截的 shell 元字符)。市场会直接拒绝并说明原因。
启动保护(guard CLI)
已知问题——0.3.4 起的所有版本,包括 0.4.17(#24): 普通 dsh web 会在插件市场自身加载时就提前提交并删除 pending snapshot,此时后续的 loader entry 可能还没加载。降级不是规避手段——要退到 0.3.4 之前,等于放弃此后的全部修复。若后续加载失败,自动回滚点已经丢失。在 #24 修复前,每次从市场安装或更新后,第一次重启请使用 guard launch;市场里的「重启 dsh」按钮已经走带保护的重启路径(它拉起的就是 guard launch --await-exit),从界面上点重启是安全的。
如果日志已经出现 startup recovery: committed、随后启动失败,那么 guard recover 通常已经没有 snapshot 可用了——需要手工修复 cordis.patch.yml,或恢复 profile 备份。
最后一道防线在启动时。包自带一个独立于宿主的 CLI(bin 名 dsh-plugin-guard,也可按路径直接跑):
受 guard 保护的安装:隔离预检 → 快照 → dsh plugin add → 落盘校验
dsh-plugin-guard guard add --profile web
带启动缓刑期地启动 dsh
dsh-plugin-guard guard launch --profile web -- dsh web
裸的 dsh-plugin-guard 只有在 profile(或全局)的 .bin 在 PATH 上时才解析得到。两种不依赖 PATH 的写法:
已装 profile:从 profile 自己的 node_modules/.bin 里跑
pnpm --dir exec dsh-plugin-guard guard add --profile web
pnpm --dir exec dsh-plugin-guard guard launch --profile web -- dsh web
开发:按源码路径直接跑
node /node_modules/@1e0zj/dsh-plugin-mall/src/cli.js guard add --profile web
node /node_modules/@1e0zj/dsh-plugin-mall/src/cli.js guard launch --profile web -- dsh web
普通的 dsh web 就会了结 pending 安装。 本插件加载时即执行恢复,于是提交
pending 标记;两种情况改为回滚:profile 校验不过,或者那次安装停在构建脚本批准
闸而未获批准(没批准的安装绝不提交,哪怕它看起来一切正常)。没有这一步的话,
装完一个插件就会把 profile 卡住:只要标记还在,之后所有安装和卸载都会被拒绝。
但普通启动能证明的事比看上去窄: 走到插件市场自己的 apply,只说明 loader
执行到了市场所在的位置,不代表整棵 plugin tree 组装成功。排在它后面的 entry
仍然可能 import 或 apply 失败,而那时快照已经没了——这就是
#24。
guard launch 仍然更强,而且强在两处而不是一处:既覆盖「插件启动正常、几秒
后才崩」,也覆盖「排在市场后面的 loader entry import/apply 失败」——后者普通启动
会直接提交过去。它在启动 -- 之后的命令前检查该 profile 的 pending 安装标记:
- 无 pending 安装 —— 命令原样运行(继承终端),透传退出码;
- 静态校验明显过不了 —— 启动之前先把 profile 回滚到安装前快照,再在恢复后的状态上启动;
- 活过缓刑期(默认 10 秒,--grace-ms 可调)—— 提交 pending 快照,包装器继续守候该进程;但仍停在批准闸的安装改为回滚:活过缓刑期只证明 JS 能加载,不证明你批准了它的构建脚本;
- 缓刑期内以 0 退出(一次性命令)—— 同样提交 pending 快照;
- 缓刑期内崩溃或非零退出 —— 回滚 profile,并用恢复后的状态原样重启同一命令一次(绝不循环),透传重启进程的退出码。支持的平台会把 SIGINT/SIGTERM 转发给子进程;Windows 上 .cmd/.bat 经 %ComSpec% 启动,逐参数严格加引号。
限制:缓刑期就是观察期——之后才暴露的故障(跑了几分钟才崩、或某个特定操作才触发)无法自动回滚:提交会删掉当前快照,此时 guard recover 已无可恢复的东西。guard validate 仍能诊断落盘状态,但提交之后的故障只能手工修复——卸载并重装插件(或恢复你另行保留的备份)。两条命令都只做静态落盘校验,都不证明插件真的能加载。pending 标记损坏时关闭式失败:不启动命令、不删除任何未校验路径。要保留快照、修复或恢复一个可信的标记后再跑 guard recover;只有在你已经独立核实过 profile、或决定放弃自动恢复之后,才去隔离(删除/移走)标记。
升级后 dsh 起不来?(0.2.0 – 0.3.2 受影响,0.3.3 已修复)
症状:dsh web 报 cannot resolve profile bundle "" 直接退出。
原因:更新已装插件时若走到回滚(最常见:目标插件带构建脚本、停在批准卡),
回滚里「装回旧版本」的一步会被 pnpm 的 "Already up to date" 空转骗过——包从
node_modules 消失而 bundles 声明还在。新装、卸载不受影响。
恢复:照 profile package.json 里原本的写法把包装回去,然后 dsh web。
= %USERPROFILE%\.dsh\profiles\web 或 ~/.dsh/profiles/web
npm 包 —— package.json 里是 "dsh-better-sidebar": "^0.13.1"
pnpm --dir add "dsh-better-sidebar@^0.13.1" --ignore-scripts
GitHub 源 —— package.json 里是 "dsh-at-file": "github:omdsh-dev/dsh-at-file"
pnpm --dir add "github:omdsh-dev/dsh-at-file" --ignore-scripts
别指望 0.3.2 的 dsh-plugin-guard recover 修这个:它的 per-package 兜底会
拒掉 ^(cmd 转义符),github: 更是整个跳过,而 pnpm 存依赖几乎不是前者
就是后者——兜底一次也不会触发,恢复只会 fail-closed。main 上已修:兜底改钉
lockfile 解析出的版本(或 commit)。0.3.3 已修复——那个版本的
dsh-plugin-guard recover 确实能修这个故障。
更新卡在批准卡上?(更新到 0.4.15 时可能遇到)
症状:更新市场自己,停在「安装期代码需要确认」,点「允许」报
allowBuildScripts cannot be specified on initial install,再点还是这样。
谁会遇到:只有从未批准过带安装脚本的依赖的 profile(实际就是传递依赖
node-pty)。如果你的 pnpm-workspace.yaml 里已经有
allowBuilds: {node-pty: true}——装过东西的 profile 基本都有——批准卡根本
不会出现,更新一次过。
原因:pnpm 在批准闸之前就把新版市场装进了 node_modules,正在运行的插件实体
被替换,dsh 随之重放装配树、整个市场 UI 重挂载,签发审批 token 时的那个浏览器
session 一起没了。token 从此取不回来,重试自然是「没带 token」。
出路——重启 dsh(启动恢复会把暂停的安装撤回),然后二选一。命令行更新:
= web、guard-test……
dsh plugin --profile add @1e0zj/dsh-plugin-mall@0.4.15
或者先把构建脚本批准一次,再照常从页面更新:
/pnpm-workspace.yaml
allowBuilds:
'node-pty@1.1.0': true
0.4.15 起已修复——session 身份现在扛得住重挂载。但这个修复救不了「更新到
0.4.15」这一跳本身:那次更新时,页面上 swap 之前的代码和正在跑的 dsh 进程
都还是旧版。
工作原理
- 双面包(dual-face)插件:dsh.bundle 半边挂在 host 平面(profile bundle 层),
注册 5 个 agent 工具(进 global 层,所有会话可见,与 MCP 工具同理);
dsh.client 半边是浏览器插件,往设置页插件区注册「插件市场」tab
(settings.plugins.tab slot;手写无构建,经 window.__ModuleLoader__ 加载)。
- 浏览器 → 服务端走 Connection 服务的独立 RPC 通道 /market
(loopback-only,与 /api 通道互不干扰);页面发起的安装任务用进程内
tracker 跟踪 —— web host 层没有 job 控制器,ctx.jobs 无法在会话外起任务。
- market_install 复刻官方 dsh plugin add 的流程:在 profile 目录跑
pnpm add ,成功后把声明了 dsh.bundle.patch 的依赖登记进
dsh.profile.bundles(layer 列表),声明了 dsh.client 的依赖自动在
profile 的 cordis.patch.yml 注册加载行,与官方 reconcile 逻辑一致。
- market_uninstall 复刻官方 dsh plugin remove 的流程:在 profile 目录跑
pnpm remove ,成功后从 dsh.profile.bundles 剔除该依赖的
bundle 条目,并删掉 cordis.patch.yml 里由安装流程注册的客户端加载行
(文本级精准移除,用户手写的行不受影响)。
- 安装源解析:github:owner/repo 优先改写为同名 npm 包(仅当 registry
条目的 repository 指回该仓库,防止抢注),否则用 GitHub 全仓库 spec。
- 「更新至 x.y.z」按钮传的是 包名@版本 而不是裸包名。pnpm 11 有
minimumReleaseAge 供应链防护,默认拒绝发布不足 24 小时的版本:传裸包名
等于让 pnpm 自己挑「最新可安装版本」,于是按钮写着「更新至 0.12.3」、
pnpm 却回落到 0.12.2 并报 Already up to date,点多少次都不动。带上版本号
是明确指定,pnpm 照装并自行往 minimumReleaseAgeExclude 记一条豁免
(这行会出现在任务日志里)。更新检查读的是 registry 的 /latest 端点,
不经过该策略,所以两边看到的「最新版本」本就可能不同。
首次安装(卡片按钮)不带版本,沿用 pnpm 的策略默认值即可。
- 启用 / 停用,不必卸载:已装面板每行一个开关,关掉即刻卸载该插件的
fiber,重新打开时它、以及因依赖它而挂起的插件都会回来。三层落地:内存用
entry.update({disabled});持久化改写 profile 的 cordis.patch.yml(保留
注释),由 dsh 自己的 watchUserPatches 事务性重放,所以重启后状态保持;
写入前自动备份到 /backups/(留最近 20 份)。
市场插件自身不给开关——停用了就没有界面再打开它。用户手写的
disabled: !!js … 条件表达式会被拒绝接管并提示手改:那是条件逻辑,
两态开关覆盖它等于把条件永久压成固定值。
(界面类插件的变化需要刷新页面才反映:浏览器那半边靠页面加载时注入的
启动清单,后端的开关立即生效,已加载的模块不会自行卸载。)
- 一次点击一个任务,日志从第一毫秒开始流:预检本身就是一个任务,点安装
的瞬间就出现在面板里,隔离探针的 pnpm 输出实时写入——而不是让按钮干等几秒
再冒出结果。预检通过后由安装任务接管同一条日志、撤掉预检条目,所以面板上
始终只有一条记录。运行中只露最后 8 行,落定后折叠成「查看日志(N 行)」。
- 预检通过直接装:verdict 为 safe 时不弹任何确认;只有有风险或被阻止才
出面板内联卡片(原因列表 + 取消 / 继续),不用模态弹窗打断。
- 安装期代码要用户点头:pnpm 默认拦掉依赖的构建脚本,放行等于让那些命令
以用户的权限在其机器上运行(早于任何插件代码加载)——这个决定属于用户。
所以被拦时安装停下,如实列出要批准的到底是什么:包名@版本、确切的
命令、周下载量、有无 provenance、以及「是你要装的插件本身,还是一个你
从没选过的传递依赖」(多数情况是后者)。用户同意后带点名的
allowBuildScripts 重新发起,同意不顺延到重试时新出现的包上。措辞刻意
不写「安全检查」——批准这些脚本对插件装好之后会做什么一无所证。
topic 里 77 个真插件自带 install 脚本的实测为 0,所以这道确认只在拖着
原生模块/构建步骤的少数插件上出现,同意一次后 allowBuilds 记住、不再问。
- 对 profile 配置的每一次写入都是先写后校验、解析不过就回滚
(writeChecked):装别人的插件失败,绝不能留下 dsh 或 pnpm 加载不了的
profile。覆盖 pnpm-workspace.yaml、package.json、cordis.patch.yml
三处。allowBuilds 是持久化的安全配置,所以安装最终失败时这次放宽会被
撤销 —— 否则一个没装成的插件会让那个包名从此静默获得构建脚本执行权。
- 配置(cordis.patch.yml 中可改):defaultProfile(默认装进哪个 profile,
不配则跟随本进程实际启动的那个 —— 从 loader 的配置树锚点 ctx.baseUrl
推导,那正是 profile 目录;推不出来才退回 web 并打日志)、apiBase
(GitHub API 地址)、npmRegistry(npm 查询源,留空则跟随 pnpm 实际安装
源)、rawSources(验证用的 package.json 源模板列表,{repo} 会替换成
owner/name,留空用内置的 jsDelivr + raw 双源)、perPageMax(搜索单页上限,
1–30 的整数,默认 30)、allowRestart(是否允许一键重启,默认 true;
交互式 Windows 终端下重启会开可见控制台窗口,其余环境为后台重启)。
自动识别同时决定启动恢复作用在哪个 profile 上:能在此提交半装状态,
凭据是「本次启动成功了」,而那只能证明启动的那个 profile 是好的。
发布
推一个 v tag,.github/workflows/release.yml 完成其余部分:
npm version patch # 改 package.json 版本并打 tag
git push --follow-tags
走 npm trusted publishing(OIDC):仓库里不存任何 npm 凭据,也没有会过期
需要轮换的 token —— GitHub 签发一个几分钟就失效的身份令牌换取发布权限。
附带自动生成 provenance:把 tarball 哈希、源 commit 和构建它的 workflow
签名绑定并进公开透明日志,所以「npm 上装到的东西」与「GitHub 上读到的源码」
之间那道缝是可验证地闭合的(npm view dist.attestations 可查)。
workflow 会先校验 tag 与 package.json 版本一致、再跑离线 fixture,任一不过
就不发。它不在仓库根目录跑 npm ci —— 根 package.json 里的框架包是宿主
提供的 peer,并非需要装进发布包的开发副本;这个包也没有构建步骤,
npm publish 本身不读 node_modules。guard/cli 自测需要的测试依赖单独放在
.github/fixtures/guard-tests,由提交进仓库的 package-lock.json 固定完整解析树,
CI 只在该隔离目录运行 npm ci --ignore-scripts。
首次配置需在 npmjs.com 的包设置里添加 Trusted Publisher(GitHub Actions +
仓库名 + release.yml),之后所有长期 token 都可以删掉。
插件要被市场发现,在 GitHub 仓库打上 topic:dsh-plugin。
开发说明
- 插件形态:package.json 里 "dsh": { "bundle": { "patch": "./cordis.patch.yml" } },
patch 文件把 dsh-plugin-mall 这一行 insert 进装配树;同一行同时是
client 插件行(dsh.client 声明让 client-modules 扫描并服务
/plugins//client.js)。
- @deepseek-ai/ 框架包必须声明为 peerDependencies:装成 dependencies
会把宿主模块副本 hoist 进 profile,cordis loader 双副本加载、Symbol 身份
分裂,宿主的工具调度全线崩溃。宿主经 profiles/node_modules fallback
提供框架包。
- node 半边导出具名成员 { name, inject, Config, apply },不要 export default
(cordis loader 会做 exports.default ?? exports 解包,default 会吞掉 inject/Config);
client 半边是 window.__ModuleLoader__.load({id, factory}),导出 { apply, inject }。
- 单测 src/github.js(无 harness 依赖):node src/github.js --self-test,
加 --offline 只跑不联网的 fixture(宿主依赖检测的判据固化在那里 ——
它当初的实测对象 dsh-TUI 已被上报修复,网络上不再有可复现的回归用例,
所以改 HOST_PACKAGES 前请先跑这组)。
- src/installer.js 也有一组 fixture,固化 allowBuilds 合并 + 事务串行化/回滚
的形状(改 mergeAllowBuilds 或事务逻辑前必跑)。它 import 宿主
@deepseek-ai/dsh-app-boot,CI 会从专用 fixture lock 重放宿主及 peer 依赖后跑
node src/installer.js --self-test;本地也可从已安装副本运行同一命令:
node ~/.dsh/profiles/web/node_modules/@1e0zj/dsh-plugin-mall/src/installer.js --self-test
- src/guard.js 与 src/cli.js 各自带一组离线 fixture(无网络、无 pnpm/dsh、
无宿主框架依赖),固化冲突扫描、快照/pending/回滚,以及 CLI 参数与启动缓刑
的判据:node src/guard.js --self-test、node src/cli.js self-test。两者只
import js-yaml + semver 两个叶子包,裸 checkout 里单点装这两个即可跑:
npm install --no-save --no-package-lock --ignore-scripts --legacy-peer-deps
js-yaml@4 semver@7,或从已安装副本跑同两条命令。改 guard 逻辑前必跑这组。
- src/index.js 的离线 fixture 固化 profile fingerprint、构建脚本审批 token
与 job/session 隔离:node src/index.js --self-test。它会静态 import
installer.js 及宿主的 dsh-tools / schemastery,所以由发布 CI 在隔离目录
从 .github/fixtures/guard-tests/package-lock.json 重放同发布线的最小宿主依赖后运行。扫码进群