English | 中文
A Web plugin manager for DeepSeek Harness (DSH). Browse installed plugins, fuzzy-search across package names, repository URLs, descriptions and loader entry ids, then enable, disable or uninstall each one. System-shipped packages and user-installed packages are separated into two groups, so you can always tell what you installed yourself.
This plugin replaces Harness's read-only ui-settings-plugin-inventory tab with a richer view; the inventory tab is disabled by the bundle patch and the plugin manager takes its place.
- Two-level grouping: system plugins first, then user plugins; each group can be expanded or collapsed.
- Live fuzzy search across package name, repository URL, homepage, description, author, keywords, and every loader entry id, config id and module specifier the package owns.
- Enable / disable one loader entry or the whole package. The desired state is persisted in the profile's
cordis.patch.ymland takes effect at runtime when the loader allows; otherwise the UI tells you a restart is required — via a modal, not a barely-visible status line. - Uninstall user packages through the same
dsh plugin --profile <name> remove <package>command the harness launcher uses, with a confirmation dialog and a verified post-condition. - Dependency-aware disable guard: disabling a package whose client services another enabled package still injects is refused with a clear explanation (e.g. disabling
dsh-better-sidebarwhiledsh-plugin-better-sidebar-plugin-officedepends on it). - Development mode (on by default): ships a quarantine CLI (
bin/quarantine.mjs) that flips an owner-markeddisabledrow incordis.patch.yml. When a plugin you are developing crashes the profile at boot, quarantine it from the terminal and restart — the UI then opens with the plugin skipped. Fix the plugin andenableit again. Toggle the mode from the settings tab header. - Distinguish the install kind (npm registry, local link, local path, git repo, tarball, workspace).
- Protected ids (the manager itself, Loader plumbing, runtime, webserver, api gateway, settings, client-runtime, locale, modules) cannot be disabled from the page; extend the set through the host config (
protectedEntries). - Bilingual UI (English + Simplified Chinese).
The manager ships as a bundle plugin — install it into the web profile the same way you install any DSH plugin:
git clone https://github.com/helloydh007/dsh-plugin-master.git
cd dsh-plugin-master
./install.sh # POSIX
# or, on Windows:
# powershell -File install.ps1install.sh links the current directory into ~/.dsh/profiles/web/node_modules/ and registers the plugin in the profile's cordis.patch.yml (idempotent — safe to re-run). It never downloads anything when run from inside a clone.
dsh plugin --profile web add dsh-plugin-masterdsh plugin --profile web add link:/absolute/path/to/dsh-plugin-masterThen reload the Web UI (restart dsh web and hard-refresh the browser). The "Plugin Manager" tab appears under Settings → Plugins, ahead of the (disabled) Plugin list.
# if installed via npm / dsh plugin add:
dsh plugin --profile web remove dsh-plugin-master
# if installed via install.sh / symlink:
rm ~/.dsh/profiles/web/node_modules/dsh-plugin-master
# and remove the two rows the installer appended to:
# ~/.dsh/profiles/web/cordis.patch.yml
# (the rows carry a "#Managed by dsh-plugin-master." comment)Then restart dsh web. The official plugin list tab is restored automatically once the disabling row is removed.
The crash-recovery tool is exposed two ways:
# from anywhere (after `npm link` in the repo, or `npm install -g .`):
dsh-quarantine ls
dsh-quarantine disable <entryId>
dsh-quarantine enable <entryId>
# or from inside a clone:
node bin/quarantine.mjs ls
node bin/quarantine.mjs disable <entryId>ls lists every plugin the profile mounts with its ENTRY ID / PACKAGE
/ STATUS, so after a boot crash you can find the entry id of the plugin
named in the error before disabling it.
The repo ships a toggleable crash demo at examples/dev-mode-demo — a no-op plugin whose apply throws only when the environment variable DEV_MODE_DEMO_CRASH=1 is set. Use it to see the quarantine in action:
# 1. link the demo into the profile and mount it
ln -s "$PWD/examples/dev-mode-demo" ~/.dsh/profiles/web/node_modules/dsh-dev-mode-demo
# add to ~/.dsh/profiles/web/cordis.patch.yml:
# - insert:
# - id: dev-mode-demo
# name: dsh-dev-mode-demo
# 2. crash mode — the harness refuses to start (this is a hard loader
# limit: an apply failure is collected before any plugin could react)
DEV_MODE_DEMO_CRASH=1 dsh web
# 3. quarantine the demo from the terminal, then restart — the UI opens
# with the demo skipped
dsh-quarantine disable dev-mode-demo
dsh web
# 4. fix it, then re-enable and restart — the demo loads again
dsh-quarantine enable dev-mode-demo
dsh webWhy not automatic: the loader's Promise.allSettled collects a failing
entry's rejection and deletes the entry before any other plugin in the
tree can observe it, and waiting inside apply deadlocks the boot. The
quarantine CLI is the reliable, architecture-honest path.
Open Settings → Plugins → Plugin Manager:
- Search — type into the search box; the query is tokenized and matched fuzzily against names, repository slugs, descriptions and entry ids.
vision toolkitmatchesvision-toolkit;anionexmatches@anionex/...; typos still find their target via subsequence matching. - Enable / disable — use the per-entry toggle on any loader entry row, or the package-level button at the bottom of a card. The desired state is written to
cordis.patch.yml. - Uninstall — the red button on a user-installed package card; confirm in the dialog. The host shells out to
dsh plugin remove, then verifies the package leftnode_modules.
Harness's built-in inventory tab uses strict substring matching against only moduleName and entryId. The vision toolkit is registered as @anionex/dsh-vision-toolkit (entry id vision-toolkit), so searching for "Vision Toolkit" (with the space and capital T) returned nothing. The plugin manager:
- Splits the query on
-_. /@:and matches per token, sovision toolkitmatchesvision-toolkit,dsh aquamatchesdsh-client-ui-aqua,anionexmatches@anionex/.... - Includes each package's
repositoryfield frompackage.jsonin the searchable corpus, so searching the author or the repo slug finds the package even when its loader id is unrelated. - Falls back to subsequence matching when no token matches exactly.
pnpm install
pnpm typecheck
pnpm build # builds lib/index.mjs + lib/client.cjs
pnpm build:host # host half only (esbuild)
pnpm build:client # browser half only (tsdown)lib/index.mjs is the host half (Cordis + Typert); lib/client.cjs is the browser half (settings tab + Remote face), wrapped for the DSH module loader.
The host service accepts a small config block via the plugin-master row in your profile cordis.patch.yml. All keys are optional:
- id: plugin-master
name: dsh-plugin-master
config:
protectedEntries: [my-auth-provider]
settleTimeoutMs: 8000
uninstallTimeoutMs: 60000protectedEntries— additional Loader entry ids that the manager UI must refuse to disable.settleTimeoutMs— how long to wait for the runtime to reflect a toggle before reporting a required restart.uninstallTimeoutMs— process timeout fordsh plugin remove.
- The host reads
cordis.patch.ymland writes only its own marked rows; user-authored rows are never modified. - Uninstall goes through
dsh plugin --profile <name> remove, which runs pnpm and reconciles the bundle stack — never a direct directory deletion. - Disabling a package that other enabled packages depend on (client services) is refused with a modal explaining the dependency chain.
- Protected ids prevent the page from disabling itself, the Cordis Loader plumbing, the API gateway, the Web server, or the harness settings shell.
- Quarantine is persisted, not automatic.
bin/quarantine.mjswrites an owner-markeddisabledrow that the loader honors before the plugin is ever created, so the next boot skips it. System packages and protected entries are never targeted by the tool. - The host has no in-memory cache; every read returns a fresh snapshot from
loader.entries()and the livenode_moduleswalk.
- System detection is authoritative, not heuristic. A package is system only when it resolves inside the real Harness install root's
@deepseek-ai/scope. Packages in the profilebundleslist or impersonating the@deepseek-ai/scope stay in the user group — that is intentional. - Bulk actions are per-package only. There is no "enable / disable every user plugin" button yet.
- Uninstall requires pnpm. If the
dshlauncher or pnpm are missing on the host, the uninstall button reports a failure rather than attempting a direct delete.
MIT
