dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DSH 插件管家(dsh-p2m / A插件):统一管理+冲突处理+按用时排序 | A plugin manager & conflict resolver for DeepSeek Harness (DSH/Cordis)
English | 中文
DSH Plugin Conflict Manager (P2M): pre-empts crash-causing conflicts — duplicate service registration, peer version drift, patch override clashes — isolates the offending plugin and ranks plugins by cumulative usage time, alongside unified enable/disable control and dynamic maintenance priority.
The repo/package identifiers stay
DSH-P2M/dsh-p2m(technical identifiers unchanged); P2M is the public nickname.
| Problem | What DSH-P2M does |
|---|---|
| No unified plugin management | Register/enable/disable/isolate/restore every plugin in one place; every decision is written to a single guard file and survives restarts |
| Conflicts only handled reactively | Detect (boot-crash attribution / runtime auto-disable / static patch scan) → decide by priority → record & rollback |
| No concept of priority | Fixed DSH > P2M > others; others are ranked live by cumulative usage time: when B outlives C in usage, maintenance order becomes DSH > P2M > B > C > … |
Extra in v1: "trial before you really enable it" — after a plugin is downloaded, it is dry-run once in a child process before being actually enabled; on failure a dialog offers Cancel enable / Ignore risk & continue.
tier 0 DSH core (name starts with @deepseek-ai/ or in configured coreEntryIds)
tier 1 P2M itself (entry id: p2m — suicide-immune; guard rows naming it are self-healed away)
tier 2 other plugins (sorted by cumulative usage ms from usage.json = maintenance priority)
Invariant: tier 0/1 entries are NEVER auto-disabled; on same-tier conflicts the
least-used plugin is sacrificed.
Dynamic example: ledger B=12h, C=5h, D=2h → order DSH → P2M → B → C → D. When C is idle and D reaches 3h → DSH → P2M → B → D → C.
cordis.patch.yml declares its mount row.--patch overlay (guard file) wins last, so it is the strongest place to express "disabled".create/update/remove) for runtime scheduling; the root tree's write() is a no-op, so every decision is dual-written: guard (persistent across restarts) + runtime hot update (immediate).Requires DSH (dsh CLI installed globally) and Node ≥ 18.
dsh plugin --profile web add github:GiantAxeint/DSH-P2M
E:\DeepseekHome\Plugin\DSH-P2M).package.json (%USERPROFILE%\.dsh\profiles\web\package.json on Windows):
dependencies: add "dsh-p2m": "link:E:/DeepseekHome/Plugin/DSH-P2M""dsh-p2m" to dsh.profile.bundles (loads it first, honoring DSH > P2M).pnpm install in the profile directory (DSH uses a pnpm workspace).The P2M entry id is fixed to
p2m— do not change it (guard self-heal depends on it).
launcher/dsh-safe.mjs is the v2 supervisor: pure boot supervision (emergency isolation only), the canonical guard lives at <DSH_HOME>/p2m/plugin-guard.yml shared with P2M via the same lock/backup protocol, and boot crashes land in the same incidents.jsonl. Copy it over your old dsh-safe.mjs (the old one is backed up first). One-click on Windows:
scripts\install-safe.cmd
P2M works automatically once loaded. State lives under <DSH_HOME>/p2m/ (default ~/.dsh/p2m/):
| File | Contents |
|---|---|
plugin-guard.yml | The single guard (persistent disabled list, injected at boot via --patch) |
usage.json | Per-plugin cumulative usage ledger (30s sampling, configurable) |
incidents.jsonl | Append-only audit trail (conflicts / isolates / restores / dialog decisions) |
state.json | p2m state (boot counter, snapshots) |
Every time DSH starts and loads P2M, the log shows a line with a number N (e.g. boot #11):
[p2m] boot #11 | guard=<...> | autoIsolate=true
#N is not a level or phase — it is the machine-wide cumulative count of successful boot runs. The counter lives in state.json#bootCount, incremented by 1 on every boot and never reset (restarts after a crash or repeated in-process loads also bump it, so it ≈ "number of startups", not an exact process count).incidents.jsonl timeline (e.g. "crashed on boot #11" → inspect incidents around that boot).bootCount field (or the whole file) from state.json — P2M recreates it automatically; guard/usage/incidents are untouched.P2M logs have three levels:
| Level | Prefix | Color | When |
|---|---|---|---|
| info | — | none | normal actions: boot, isolate, enable, reconcile decisions |
| Advisory | [WARNING] | yellow | non-fatal issues that need attention: resolve drift, preflight peer violations, static-scan conflicts, self-heal, loader unavailable |
| Fatal / internal | [ERROR] | red | interrupted or internal failures: a protected entry auto-disabled by the loader, p2m internal errors |
[WARNING] / [ERROR] is followed by an indented English hint: line telling you which file/path to check or what the conflict is about (e.g. ~/.dsh/p2m/incidents.jsonl, the profile package.json, the guard file).NO_COLOR is set, so log files never contain ANSI escape garbage; force colors with logColor: 'always' (or disable with 'never').Example (preflight violations found):
[WARNING] [p2m] preflight: 2 peer violation(s) — 建议先按下方 fix 命令钉版本,再重启 DSH
@foo/bar peer @deepseek-ai/dsh-settings: declared ^0.1.1-rc.2, resolved 0.1.2-rc.1 | fix: pnpm add @deepseek-ai/dsh-settings@^0.1.1-rc.2
hint: Check the peer ranges declared in <profile>/package.json against the resolved versions above, then run the fix command (pnpm add ...) inside the profile before restarting DSH.
The launcher (DSH-safe.cmd → dsh-safe.mjs) follows the same rules.
| Method | Purpose |
|---|---|
list() | Priority-ordered list (tier / usageMs / running / disabled) |
status() | Current config & guard state |
usage() / incidents(n) | Ledger / last n audit entries |
scanConflicts() | Run one static+runtime conflict scan (report only) |
enable(id) / disable(id) | Restore / disable (enable goes through the trial gate; protected ids cannot be disabled) |
trial(id) | Manual child-process dry run → verdict ok/crash/timeout/unsupported |
preflight() | Run one peer-version preflight (E3) → findings + summary |
reconcile() / sample() | Manual reconcile / sampling tick |
autoGateRisk:false, or ui:'none' to never pop up (always defaults to cancel).| key | default | description |
|---|---|---|
sampleIntervalMs | 30000 | usage sampling interval |
autoIsolate | true | auto-isolate runtime failures (false = report only) |
autoGateRisk | true | enable the trial gate |
ui | 'auto' | dialog mode: auto/desktop/none |
popupCooldownMs | 120000 | per-plugin popup cooldown |
trialTimeoutMs | 15000 | child-process trial timeout |
coreNamePrefixes | ['@deepseek-ai/'] | DSH-core detection prefixes |
coreEntryIds | [] | extra DSH-core entry ids |
knownCoreServices | ['sessionPersistence'] | C7 known engine-core service list (set [] to silence) |
preflightOnBoot | true | E3: run peer preflight at boot (report only) |
scanBootLayers | true | E4: static-scan all patch layers at boot |
patchLayers | [] | extra patch files for scanConflicts() (auto-discovered by default) |
logColor | 'auto' | log colors: auto (TTY & no NO_COLOR) / always / never |
| Kind | Meaning | Handling |
|---|---|---|
| C1 boot crash | boot fails matching failed to … loader entry | dsh-safe v2 emergency isolate + audit |
| C2 runtime auto-disable | loader disabled an entry, not persisted | trial: conflict→isolate; self-crash→dialog |
| C3 config clash | layers give one id different configs (incl. factory-disabled overridden by profile) | report + either-or advice (manual fix) |
| C4/C5 duplicate id / duplicate module name | structural | report (manual fix) |
| C6 self/privilege violation | guard names p2m/core | self-heal removal + audit |
| C7 same-name service registration | two plugins would register the same cordis service (or collide with engine-core defaults like sessionPersistence) | pre-boot preflight report + either-or advice |
| version drift (preflight) | peer range vs actually-resolved version mismatch (local dep missing → climbs to a stale global CLI copy) | pre-boot report + pin command (DSH_P2M_PREFLIGHT=block to refuse boot) |
Resolve-path drift (E2): every reconcile records each plugin's
require.resolvedestination + version; a drift writes aresolve-driftaudit and persists intostate.json#lastResolve.
Zero-dependency unit tests (node:test):
node --test test/*.test.mjs # 74 cases: guard/ledger/priority/conflicts/policy/yaml/trial/C7/preflight/resolve
DSH-P2M/
├─ lib/ P2M (ESM, zero runtime deps)
│ ├─ index.js entry: ctx.p2m service, sampling, reconcile, gate
│ ├─ guard.js single guard writer (lock+backup+migration)
│ ├─ yaml-min.js bundled loader-patch-dialect YAML parser
│ ├─ ledger.js usage ledger (pure tick())
│ ├─ priority.js tiers & dynamic ordering
│ ├─ conflicts.js conflict detection (static+runtime+C7 service preflight)
│ ├─ policy.js decision engine
│ ├─ preflight.js peer-version preflight (E3, zero-dep semver subset)
│ ├─ trial.js child-process dry run (R5)
│ └─ dialog.js two-button crash-risk dialog (R5)
├─ launcher/ dsh-safe v2 boot supervisor (copy over old one)
├─ test/ unit tests + dry-run fixtures
└─ DESIGN.md full design & limits (read first)
dsh plugin add flow is "download then restart": p2m cannot intervene before that restart (v2 launcher fuse + audit covers it); all p2m-controlled entry points (hot mounts, manual restore) go through the trial gate.MIT © 2026 Erius (GiantAxeint)