packages/dsh-plugin-manager
zhu1090093659/dsh-web设置 → 插件 分区里的插件管理 Tab:从 npm/git 安装带进度、下次启动生效的启停开关、安装冲突对账可撤销、失败一键转交修复会话。
开放式插件市场:GitHub dsh-plugin 话题实时搜索,逐仓库 package.json 验证(dsh.bundle/dsh.client 声明徽章与只看已验证过滤),npm 优先安装带同源防抢注校验,更新检测,并附五个可在无界面环境使用的 agent 工具。
An open plugin marketplace for DeepSeek Harness (dsh): search every GitHub repo tagged topic:dsh-plugin, automatically verify which ones are real dsh plugins, install and update with one click.
中文说明 · Install · Why another marketplace · Roadmap
Two surfaces: a Settings → Plugins → Marketplace tab in the dsh web UI, and five agent tools usable from any session.
Curated lists only show what has been reviewed and merged. This marketplace is open by construction: any repo tagged topic:dsh-plugin is discoverable the moment it is pushed — no submission, no approval queue. To keep that openness usable:
package.json is fetched (jsDelivr/raw dual-source CDN, no API quota) and checked for the official dsh.bundle / dsh.client manifest. Verified plugins get a green badge; the default "verified only" view filters out ~73% of topic noise (empty repos and unrelated projects riding the tag).repository URL points back to the same GitHub repo; anything else falls back to the explicit github: spec..npmrc → pnpm config get registry → npmjs), so a mirror user keeps npm-first instead of silently falling back to whole-repo clones.latest; one-click update per plugin.applyEntryPatches on every test run, over fixed shapes and 300 generated patch combinations, so "what dsh would boot" is not a guess. What gets reported is what would actually change: disabling, re-enabling or replacing the config of someone else's rows warns and names them, past ten such rows the candidate is a rival composition rather than an addition and is blocked, and a bundle that is itself another front door (own bin, terminal peers, no browser half) is blocked once it switches rows on or off or rewrites a protection row — the fingerprint corroborates, it never blocks on its own. A hard conflict is blocked, warnings require explicit confirmation. The profile's load-bearing files are snapshotted before pnpm touches them and restored on failure. A pending install is resolved on the next start, however you start it: this plugin runs recovery as it loads. That proof is narrower than it sounds — it means the loader reached the marketplace's position, not that the whole tree composed (#24). Starting through guard launch adds a grace window on top, covering both a plugin that crashes seconds later and an import/apply failure in an entry after the marketplace (see Startup protection).corepack enable pnpm self-heal when pnpm is missing, one-click dsh restart (loopback-only, allowRestart: false to disable). The outgoing Host exits only after the on-disk helper explicitly acknowledges the current handoff protocol and stays alive through a stability window; a missing, stale or incompatible helper leaves the existing Web service running. The successor then waits for the outgoing host to be gone before it binds — starting into an occupied port would read as "the pending plugin crashed dsh" and roll back an install that was fine. On Windows started from an interactive terminal, the restart runs in a visible console window (cmd /c start; the wrapped argv travels as a JSON plan file, never through the cmd command line): output is teed to the window and to the log, and closing the window terminates the whole guard/dsh tree (the close event bypasses console input processing entirely, so it always works). Ctrl+C in the window is honored best-effort — live testing showed a component inside dsh can flip the console into raw mode, after which no process on that console receives Ctrl+C at all; closing the window is the reliable stop. Service/scheduled-task/TTY-less Windows and every other platform keep the fully background behavior — no stray console, no re-opened browser (the page that asked is still there and reconnects on its own); whatever the restart prints lands in <home>/guard/restart-<profile>.log.# from npm
dsh plugin --profile web add @1e0zj/dsh-plugin-mall
# from GitHub
dsh plugin --profile web add github:1e0zj/dsh-plugin-mall
# local development — currently unusable, see the note below
dsh plugin --profile web add link:C:\path\to\dsh-plugin-mall
Restart dsh after installing.
link:cannot be set up right now, for reasons upstream of this plugin. Underlink:Node loads from the project's real path, so bare imports must resolve from the project's ownnode_modules— which requiresnpm installinside the project first. That fails: the framework packages' registry dist-tags currently straddle two release trains (dsh-toolslatest is still0.0.1-rc.xwhiledsh-app-bootis on0.1.0-rc.x), their peer ranges do not intersect, and npm stops with ERESOLVE. Neither escape hatch helps —--legacy-peer-depsskips peers so bare imports still will not resolve, and--forceplants a mixed-train copy of the framework inside the project, whichlink:would then load instead of the host's, causing exactly the duplicate-module crash described under 开发说明. Until those dist-tags line up, develop against a local tarball —npm pack, then install the.tgzwith afile:spec (recipe in the 安装 section below). Do not hand-overwrite files undernode_modules: they are hard-linked into pnpm's global store, and any laterpnpm add/removerebuilds the tree and restores them anyway.Re-packing and running
addagain does nothing: thefile:spec and the version are unchanged, so pnpm calls it already installed and never compares the tarball's bytes. The command succeeds,package.jsonlooks right, andnode_modulesstill holds the previous build. Alwaysremovebeforeadd, or give the test build its own version. Then restart dsh completely — the install ran inside the host process still executing the old code.
Known issue — every version since v0.3.4, including v0.4.17 (#24): a plain
dsh webstart may commit and delete the pending snapshot as soon as the marketplace plugin loads, before later loader entries have finished loading. Downgrading is not a workaround — you would have to go back before v0.3.4 and give up every fix since. If a later entry fails, automatic rollback is no longer available. Until #24 is fixed, useguard launchfor the first restart after every marketplace install or update. The marketplace's "Restart dsh" button already uses the guarded restart path (it spawnsguard launch --await-exit), so restarting from the UI is safe.If the log already shows
startup recovery: committedand the start then fails,guard recoverusually has no snapshot left to restore — repaircordis.patch.ymlby hand, or restore a profile backup.
The last line of defense is at startup. The package ships a standalone, host-independent CLI (bin dsh-plugin-guard, also runnable by path):
# guarded install: isolated preflight → snapshot → dsh plugin add → on-disk validation
dsh-plugin-guard guard add <spec> --profile web
# start dsh under startup probation
dsh-plugin-guard guard launch --profile web -- dsh web
The bare dsh-plugin-guard bin resolves only when the profile's (or global) .bin is on PATH. Two PATH-independent forms:
# installed profile: run the bin from the profile's own node_modules/.bin
pnpm --dir <profile> exec dsh-plugin-guard guard add <spec> --profile web
pnpm --dir <profile> exec dsh-plugin-guard guard launch --profile web -- dsh web
# development: run by source path
node <profile>/node_modules/@1e0zj/dsh-plugin-mall/src/cli.js guard add <spec> --profile web
node <profile>/node_modules/@1e0zj/dsh-plugin-mall/src/cli.js guard launch --profile web -- dsh web
A plain dsh web already resolves pending installs. The marketplace plugin runs
recovery when it loads, so the pending marker is committed — or rolled back, either
because the profile fails validation or because the install was left paused at the
build-script approval gate (an unapproved install is never committed, however healthy
it looks). Without this a single install would wedge the profile — every later install
and uninstall refuses while a marker is outstanding.
What a plain start actually proves is narrower than it looks: reaching the
marketplace's own apply only means the loader got as far as the position the
marketplace occupies — it does not mean the whole plugin tree composed. Entries
after it may still fail to import or apply, and by then the snapshot is already
gone (that is #24).
guard launch is still strictly better, and for two reasons rather than one:
it covers a plugin that boots fine and then crashes seconds later, and it covers
an import/apply failure in a loader entry that comes after the marketplace — the
case a plain start commits straight through. It checks the profile's pending-install
marker before starting the command after --:
--grace-ms <ms> to change) — the pending snapshot is committed and the wrapper keeps waiting on the process. An install still paused at the approval gate is rolled back instead: surviving probation only proves the JS loads, not that you approved its build scripts..cmd/.bat shims go through %ComSpec% with strict per-argument quoting.Limitations: the grace window is the probation period — a failure that only surfaces after it (a plugin that crashes minutes in, or on a specific interaction) cannot be rolled back automatically, because committing deletes the active snapshot and guard recover then has nothing to restore. guard validate still diagnoses the on-disk state, but a post-commit failure needs manual repair — uninstall and reinstall the plugin, or restore a backup you kept separately. Both commands do only static on-disk validation; neither proves the plugin actually loads. A corrupt pending marker fails closed: the command is not launched and no unvalidated path is deleted. Preserve the snapshot and repair or restore a trustworthy marker, then run guard recover; quarantine the marker only after you have independently verified the profile, or decided to abandon automatic recovery.
Symptom: dsh web exits with cannot resolve profile bundle "<package>".
Cause: updating an already-installed plugin into a rollback (most often the target carries build scripts and the flow paused at the approval card) could lose the package — the rollback's reinstall of the old version was fooled by pnpm's "Already up to date" short-circuit, so the plugin left node_modules while its bundles declaration stayed. Fresh installs and removals are unaffected.
Recovery: add the package back exactly as the profile's package.json declares
it, then run dsh web.
# <profile> = %USERPROFILE%\.dsh\profiles\web or ~/.dsh/profiles/web
# npm package — package.json says "dsh-better-sidebar": "^0.13.1"
pnpm --dir <profile> add "dsh-better-sidebar@^0.13.1" --ignore-scripts
# GitHub source — package.json says "dsh-at-file": "github:omdsh-dev/dsh-at-file"
pnpm --dir <profile> add "github:omdsh-dev/dsh-at-file" --ignore-scripts
Do not reach for 0.3.2's
dsh-plugin-guard recoverhere. Its per-package fallback refuses^(a cmd metacharacter) and skipsgithub:entirely, and pnpm writes nearly every dependency with one or the other — so the fallback never fires and recovery fails closed. Fixed on main: the fallback now pins the version (or commit) the lockfile resolved. Fixed in 0.3.3 — on that versiondsh-plugin-guard recoverdoes repair this.
Symptom: updating the marketplace itself pauses at "安装期代码需要确认", and
clicking 允许 fails with allowBuildScripts cannot be specified on initial install. Clicking again does not help.
Who hits it: only profiles that have never approved a dependency with install
scripts (in practice node-pty, pulled in transitively). If your profile's
pnpm-workspace.yaml already carries allowBuilds: {node-pty: true} — most
profiles that have installed anything do — the approval card never appears and
the update just works.
Cause: pnpm installs the new marketplace package before the approval gate, which swaps the running plugin on disk; dsh replays the assembly tree and the whole marketplace UI remounts, losing the browser session the approval token was issued to. The token is then unrecoverable, so the retry arrives without it.
Recovery — restart dsh (startup recovery rolls the paused install back), then either update from the CLI:
# <profile> = web, guard-test, ...
dsh plugin --profile <profile> add @1e0zj/dsh-plugin-mall@0.4.15
or pre-approve the build scripts once and update from the page as usual:
# <profile>/pnpm-workspace.yaml
allowBuilds:
'node-pty@1.1.0': true
Fixed from 0.4.15 onward — the session identity now survives a remount. The fix cannot rescue the hop to 0.4.15 itself: during that update both the page's pre-swap code and the running dsh process are still the old version.
| Tool | What it does |
|---|---|
market_search | Search GitHub repos tagged topic:dsh-plugin (star-ranked, keyword filter, server-side stars:>=1 noise floor) |
market_info | Inspect one repo: stars, license, package.json, whether it declares dsh.bundle.patch / dsh.client |
market_install | Install a plugin into a profile as a background job (npm-first spec resolution) |
market_uninstall | Remove a plugin: pnpm remove + bundle-layer reconcile + client-row cleanup |
market_installed | List a profile's installed plugins and their bundle status |
dsh 插件市场 — 搜索 GitHub dsh-plugin 话题下的 DeepSeek Harness 插件仓库,自动验证哪些是真 dsh 插件,一键安装与更新。
路线图(安全 > 便捷 > 精简,以及明确不做的)
与策展列表不同:任何打上 topic:dsh-plugin 的仓库推送后立即可被发现——无需投稿、无需审批。为保证开放性可用,做了这些事:
package.json(jsDelivr/raw 双源 CDN,不占 API 配额),按官方 dsh.bundle / dsh.client 声明打徽章;默认"只看已验证"视图过滤约 73% 的话题噪音repository 指回同一 GitHub 仓库时才用 npm 安装,否则回退 github: 源.npmrc → pnpm config get registry → npmjs),换了镜像也不会退化成整仓库克隆latest 比对,逐个一键更新applyEntryPatches 对拍(固定形状 + 300 组随机生成的补丁组合),所以「dsh 会装出什么树」不是猜的。报出来的是真正会变的东西:停用、重新启用、或替换别人整块 config,都点名是哪几条 id 并警告;超过十条就不是叠加而是另一套组合,直接拦截;候选包本身就是另一套门面(自带 bin、依赖终端栈、没有浏览器半边)时,只要它停用/启用了行、或改了沙箱审批这类保护行,就直接拦截——指纹只做佐证,单凭它不拦。硬冲突直接拦截,警告需显式确认。安装前给 profile 的承重文件拍快照、失败即回滚;pending 安装在下次启动时自动了结,不挑启动方式:本插件加载时就跑恢复。但这份证明比听上去窄——它只说明 loader 执行到了市场所在的位置,不代表整棵树组装成功(#24)。经 guard launch 启动则多一层观察期,既覆盖「启动几秒后才崩」,也覆盖「排在市场后面的 entry 加载失败」(见下方「启动保护」)。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 与其他平台维持全后台行为——不留控制台窗口、也不再重开浏览器:发起重启的那个页面还在,会自己重连;重启过程的输出写进 <home>/guard/restart-<profile>.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-<ver>.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 元字符)。市场会直接拒绝并说明原因。
已知问题——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 <spec> --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 <profile> exec dsh-plugin-guard guard add <spec> --profile web
pnpm --dir <profile> exec dsh-plugin-guard guard launch --profile web -- dsh web
# 开发:按源码路径直接跑
node <profile>/node_modules/@1e0zj/dsh-plugin-mall/src/cli.js guard add <spec> --profile web
node <profile>/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 安装标记:
--grace-ms <ms> 可调)—— 提交 pending 快照,包装器继续守候该进程;但仍停在批准闸的安装改为回滚:活过缓刑期只证明 JS 能加载,不证明你批准了它的构建脚本;.cmd/.bat 经 %ComSpec% 启动,逐参数严格加引号。限制:缓刑期就是观察期——之后才暴露的故障(跑了几分钟才崩、或某个特定操作才触发)无法自动回滚:提交会删掉当前快照,此时 guard recover 已无可恢复的东西。guard validate 仍能诊断落盘状态,但提交之后的故障只能手工修复——卸载并重装插件(或恢复你另行保留的备份)。两条命令都只做静态落盘校验,都不证明插件真的能加载。pending 标记损坏时关闭式失败:不启动命令、不删除任何未校验路径。要保留快照、修复或恢复一个可信的标记后再跑 guard recover;只有在你已经独立核实过 profile、或决定放弃自动恢复之后,才去隔离(删除/移走)标记。
症状:dsh web 报 cannot resolve profile bundle "<包名>" 直接退出。
原因:更新已装插件时若走到回滚(最常见:目标插件带构建脚本、停在批准卡), 回滚里「装回旧版本」的一步会被 pnpm 的 "Already up to date" 空转骗过——包从 node_modules 消失而 bundles 声明还在。新装、卸载不受影响。
恢复:照 profile package.json 里原本的写法把包装回去,然后 dsh web。
# <profile> = %USERPROFILE%\.dsh\profiles\web 或 ~/.dsh/profiles/web
# npm 包 —— package.json 里是 "dsh-better-sidebar": "^0.13.1"
pnpm --dir <profile> add "dsh-better-sidebar@^0.13.1" --ignore-scripts
# GitHub 源 —— package.json 里是 "dsh-at-file": "github:omdsh-dev/dsh-at-file"
pnpm --dir <profile> add "github:omdsh-dev/dsh-at-file" --ignore-scripts