XJungit/omdp

omdp

only my DSH plugins — monorepo of DeepSeek Harness plugin bundles

Uncategorizedagent-skillscordisdeepseek-harness

omdp — only my DSH plugins

🌐 语言 / Language: 中文 · English


English

A single GitHub repo that collects all of my DeepSeek Harness (DSH) plugins as a monorepo. Each plugin lives in its own subdirectory and is an independently installable DSH bundle, published to npm on every v* tag.

Layout

omdp/
├── README.md            # this file
├── package.json         # root manifest — keeps bare-git installs functional (see below)
├── dsh-connector/       # unified MCP + Skills manager (Web UI settings tab)
│   ├── index.js         # host half
│   ├── client.js        # client half (Web UI)
│   ├── cordis.patch.yml # bundle activation row
│   ├── package.json
│   └── README.md
├── dsh-key-fallback/    # multi-key API key pool with automatic rotation
│   ├── lib/index.js     # host half (ESM)
│   ├── lib/client.js    # client half (Web UI settings tab)
│   ├── cordis.patch.yml # bundle activation row
│   ├── package.json
│   └── README.md
├── dsh-archived-sessions/ # archived-session manager (fork of @muwinds, DSH 0.2.0-rc.1 / 0.2.0-rc.2)
│   ├── lib/index.js     # host half (ESM)
│   ├── lib/client.js    # client half (Web UI settings tab)
│   ├── cordis.patch.yml # bundle activation row
│   ├── package.json
│   └── README.md
├── archive/             # archived plugins retained for historical reference
│   ├── dsh-gitbash-win/
│   ├── resume-stream/
│   ├── key-fallback-*/                  # dsh-key-fallback version snapshots & design drafts
│   ├── dsh-vision-bridge-*/              # archived 2026-10-18 (was an active vision plugin)
│   └── README.md
├── notes/               # development lessons & notes (三层:notes/<date>/<category>/)
├── docs/                # research notes, compatibility matrix, publish guide
└── <future plugins>/    # each its own subdirectory + package.json

Plugins

@omdp/dsh-connector — MCP + Skills manager (v0.3.10)

One settings tab (Connector) that manages three things from the DSH Web UI:

  • MCP servers — edits the MCP block in cordis.patch.yml (stdio / streamable-http), with full validation so bad config never reaches the next boot. Legacy SSE servers (e.g. Zhihu) are kept as mcp-remote --transport sse-only stdio bridges; the plugin only manages that config text.

  • User skills — read / write / delete skills under ~/.dsh/skills/<name>/SKILL.md (frontmatter preserved).

  • Market explorer (v0.2.0+) — read-only browsing of ModelScope Skills hub and MCP plaza via anonymous OpenAPI; one-click copy of install commands / mcpServers config snippets, skill "check update" via source/sourceUpdated frontmatter. Market data lives only in process memory (30-min TTL), never on disk.

  • Tool filter (v0.3.0+) — per-server allow-list for MCP tools (DSH 0.1.7+: the connector profile entry's config.toolFilters; older rc.x: settings.yaml connector.toolFilters), prompt hiding + execution guard; unset = allow all.

  • DSH 0.1.7 support (v0.3.3) — jsdom is no longer imported at module top level (a punycode/ resolution defect in DSH's package resolver made the whole plugin fail to import); tool filters moved to the volatile Config / settings.replace() backend while keeping the rc.x settings.yaml backend.

  • Declared DSH support + a stranded-filter rescue (v0.3.4) — new @deepseek-ai/dsh peer (0.1.7-rc.1, per-version enumeration at the time; switched to a triple range in v0.3.8, see below), enforced by DSH's own evaluatePluginCompatibility(): an untested newer runtime has the bundle gracefully skipped instead of failing undefined-ly, while runtimes ≤0.1.6 just see an unrecognized peer (the gate does not exist before 0.1.7). Also fixes tool filters silently reverting to allow-all: 0.1.7's one-shot importer renames settings.yaml → settings.yaml.imported and silently skips sections without a volatile Config, so connector.toolFilters stayed behind in .imported; ensureLegacyFiltersMigration() now moves it back on the first request (never overwriting filters you already have, never deleting .imported).

  • DSH 0.1.7-rc.2 added to the peer enumeration (v0.3.5) — zero code changes; verified by a file-by-file tarball diff (shell/settings/credentials lib/ byte-identical, llm additive-only, app-boot changes unrelated to plugins), running the rc.2 gate against the new declaration, and a live regression on a scratch dsh@0.1.7-rc.2 profile (filters endpoint 200).

  • CRLF patches + the right profile file (v0.3.6) — two live bugs. (1) parseMcpServers()'s key/value regex ^\s+(\w+):\s*(.*)$ never matches on a CRLF file (in JS .* does not match \r, and $ does not match before it), so transport/serverName/command/url all fell into the preserve bucket: the API answered transport:"" serverName:"", which made the tool-filter section vanish silently (if (!props.serverName) return null) and the transport badge degrade to stdio. Parsing now normalizes line endings first (toLf()), and writes emit LF. (2) patchPath() hardcoded profiles/web/cordis.patch.yml, so under the desktop app (which runs profiles/desktop) the MCP editor was rewriting another profile's file — with no effect on its own MCP config, plus a wrong path shown in the UI. 0.1.7+ now reads the real path from ctx.get('profileContext').patchPath (the dsh-app-boot ProfileContext contract), keeping the legacy path only where that service does not exist (0.1.5/0.1.6); GET /api/mcp returns it so the settings page displays the true file.

  • DSH 0.2.0-rc.1 added to the peer enumeration (v0.3.7) — zero code changes. After the desktop app moved to 0.2.0-rc.1 the gate skipped the bundle declared only up to 0.1.7-rc.2 (Connector tab gone, /connector/api/* → 404 — a graceful skip, not a crash). Verified by a file-by-file SHA1 diff over every lib/ file of both official tarballs (dsh-web-app/dsh-host-webserver/dsh-credentials/dsh-settings/dsh-tools/dsh-mcp-client byte-identical), by running the 0.2.0-rc.1 evaluatePluginCompatibility() against the new manifest (pass; the old manifest is blocked), and by a live sandbox assembly — official @deepseek-ai/dsh@0.2.0-rc.1 + isolated DSH_HOME + --dump-config → plugin in the tree, no gate skip.

  • Peer declaration switched to the triple range (v0.3.8) — zero code changes. The @deepseek-ai/dsh peer moved from per-version enumeration to >=0.2.0-rc.1 <0.2.1-0. When 0.2.0-rc.2 shipped, the gate blocked the old declaration for the third time (Connector tab gone, /connector/api/* → 404); the root cause was never incompatibility but enumeration going stale on every rc. Verified by a full 20-package file-by-file SHA256 diff 0.2.0-rc.1 → rc.2 (everything this plugin touches changes only in package.json version strings; the dsh main package's changes are entirely desktop-profile support) and by running the real gate over the whole matrix — 0.2.0-rc.1/rc.2/rc.3/rc.9/stable 0.2.0 all pass, 0.2.1-rc.1/0.3.0-rc.1 all blocked. ⚠️ This narrows support: the 0.1.7 line is no longer declared — install 0.3.7 if you need it there.

  • DSH 0.2.1 support (v0.3.10) — the @deepseek-ai/dsh peer gained a second triple segment: >=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-rc.1 <0.2.2-0 (schemastery also gained 3.18.5, pinned by 0.2.1-rc.2). Zero code changes. Verified in four layers: a per-file SHA256 diff of the whole @deepseek-ai/* tree (0.2.0-rc.2 → 0.2.1-rc.2: 288 → 305 packages, 177 with real .js churn), a real ESM import/export-set diff of the 9 packages we consume (the only removals are unused, in dsh-app-boot), a method-level existence check of all 37 symbols we call, and a real 0.2.1-rc.2 CLI assembly with an isolated DSH_HOME (--dump-config composes all three plugins, zero skips).

"dependencies": { "@omdp/dsh-connector": "^0.3.10" }

@omdp/dsh-key-fallback — multi-key API key pool with rotation (v3.3.3)

Hooks DSH's credential seam credentials.resolve: each time an adapter asks for a key, if that ref belongs to a configured pool the wrapper returns the pool's current key; on a configured trigger error it marks the failed key cooling and advances to the next key — re-sending is left entirely to DSH's own dsh-llm-retry. Ships an always-visible settings page (Settings → API Key 回退) with a redesigned UI:

  • Configurable rotation triggers (rotateOn) — clickable chips covering the full DSH LlmError standard code set (QUOTA/AUTH/RATE_LIMIT/TIMEOUT/TRANSPORT/SERVER/EMPTY_RESPONSE/INVALID_CREDENTIAL) plus custom codes (matched exactly against failure.code).

  • Shows the actually-used key (the pool's current ref — exactly what the credential seam hands to the adapter), not a truncated hash.

  • Short refs (key_fallback_<provider>_key1, …) with one-time idempotent migration of legacy long refs.

  • Plaintext reveal via an eye toggle (GET /keys/plain, pool-owned keys / env key only) and editable env keys (file-backed ones; read-only when supplied by the launching environment).

  • Per-key nextRef, pool lock ("设为当前"), cooldown reset, and delete.

  • DSH 0.1.7 support (v3.2.0/v3.2.1) — dual config backend: the volatile Config / settings.replace() profile-entry backend on 0.1.7+, the settings.yaml file backend on older rc.x. v3.2.1 adds a one-shot rescue that recovers pools stranded in settings.yaml.imported (the 0.1.7 migration renames the file, so the file-reading backend never sees them again).

  • Declared DSH support (v3.2.2) — new @deepseek-ai/dsh peer (0.1.7-rc.1, per-version enumeration at the time; all four switched to a triple range in v3.3.2, see below), making the supported runtime an explicit, machine-checked contract (see the connector entry above for the gate's semantics).

  • DSH 0.1.7-rc.2 added to all four dsh peer enumerations (v3.2.3) — zero code changes; verified by tarball diff (credentials lib/ byte-identical, llm additive-only), gate execution on rc.2, and a live scratch-profile regression.

  • DSH 0.2.0-rc.1 added to all four dsh peer enumerations (v3.2.4) — zero code changes; verified by a full lib/ SHA1 diff (credentials/llm/settings/shell/tools/mcp-client/web/agent all byte-identical — the entire surface this plugin touches), gate execution on 0.2.0-rc.1, and a live sandbox assembly.

  • All four dsh peers switched to the triple range (v3.3.2) — @deepseek-ai/dsh / dsh-credentials / dsh-llm / dsh-settings now all declare >=0.2.0-rc.1 <0.2.1-0; zero code changes. 0.2.0-rc.2 had the gate block 3.3.1 for the third consecutive round (settings item gone, routes 404/405). Verified by a full 20-package file-by-file SHA256 diff (the six declared peers change only in package.json version strings) and by the real gate over the whole matrix. ⚠️ The 0.1.7 line is no longer declared — install 3.3.1 if you need it. cordis / schemastery stay enumerated (they never go through the gate).

  • Correction to the v3.3.0 notes (v3.3.1) — the v3.3.0 entry below originally claimed that key switching had never reached the provider. That was wrong. The credentials.resolve wrapper that supplies the key has existed since v3.0.0 (75ea76a), and the published 3.2.4 tarball already contains it (lib/index.js:298) along with its pool-hit diagnostic (:305) — verified by downloading the tarball from the registry. Rotation was already working; the process.env writes were redundant code sitting next to a working wrapper, not the mechanism. The v3.3.0 entry is retained below with its claim corrected.

  • Four real fixes (v3.3.0) — (1) /pools activeRef no longer misreports the current key: it used to reverse-look-up process.env[<pool env>] and could highlight the wrong member (measured live: activeRef=SENSENOVA_API_KEY while currentRef=key_fallback_sensenova_key1); it is now just currentRef. (2) The 5 process.env writes are gone — verified invisible to the credential layer (DSH freezes {...process.env} into a launch-environment snapshot at boot), so they were dead weight for auth; dropping them also stops the plugin mutating the host env. (3) agent/request-error no longer falls back to blaming keyValues[0]. (4) Uninstall restores the original resolve via ctx.effect with an identity guard, so hot reloads stop stacking wrappers. Re-send stays DSH's job — every rotation path still calls next().

  • DSH 0.2.1 support (v3.3.3) — all four dsh peers gained the second triple segment >=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-rc.1 <0.2.2-0; cordis gained 4.0.5 and schemastery 3.18.5 (both pinned by 0.2.1-rc.2). Zero code changes — the whole surface this plugin touches (credentials.resolve wrapping, ctx.llm events, settings, webServer) is byte-identical between 0.2.0-rc.2 and 0.2.1-rc.2, confirmed by both an export-set diff and real runtime assembly.

"dependencies": { "@omdp/dsh-key-fallback": "^3.3.3" }

@omdp/dsh-archived-sessions — archived-session manager (v0.3.11)

Fork of @muwinds/dsh-archived-sessions 0.2.0, adapted for DSH 0.2.0-rc.1 / 0.2.0-rc.2 (since 0.3.9 via the triple range; the earlier 0.1.5-rc.1 → 0.1.7-rc.2 line was supported through 0.3.8 but is no longer declared) — upstream is unmaintained and broken on 0.1.5+ (sessionPersistence.list() returns snapshots, locate() was removed). Adds Settings → 归档会话管理, coexisting with the native archived-sessions page introduced in DSH 0.1.6:

  • List archived sessions with title / ID / workspace / disk usage / running state;

  • Release (释放): move a session out of the archive set (no data deleted);

  • Delete (删除): delete the session directory + broadcast api-session/removed + prune the archive id + release the workspace accounting slot (two-step confirm);

  • Tree delete: deleting a main session also deletes its parentSession subtree (fixes upstream issue #2 — orphan subagents);

  • Orphan sweep: scan and clean leftover subagent dirs whose parent session is already gone (same 4-step teardown as Delete since 0.3.10);

  • Deletion safety: refuses to delete any path whose directory name is not a session dir (session-<uuid> or bare UUID); deletion is dual-era since 0.3.6 — it prefers ctx.shell.resolve() → execute() → await result() on DSH 0.1.7+, and falls back to resolve() → run() on ≤0.1.6, so one build deletes across the whole line. (0.3.5 only called execute() — which fixed 0.1.7 but silently broke rc.x, where run() is the only method; 0.3.4 had the mirror-image bug. Verified by unpacking each @deepseek-ai/dsh-shell tarball's lib/types/index.d.ts.)

  • 0.3.10 — deleted archived sessions reappeared in the chat list: archiving is only a visibility mask in DSH (it never filters the host list; dsh-session-query has zero archiv* references), so the client row survives in manager.summaries and merely gets hidden. The old code only removed the archive id — lifting the mask made the surviving row visible again (and clicking it failed with session/not-found), because the only removal notice it ever emitted came incidentally from entry.detach()'s session/disposed → api-session/removed, which only fires for a live session. 0.3.10 emits api-session/removed explicitly on every delete path (wrapped in try/catch — cordis emit is synchronous and uncontained, so a throwing third-party listener must not abort a completed delete), releases the workspace sessionIds accounting slot via entity.detachSession() (DSH keeps archived ids accounted by design, and never forgets them), and stops ignoring location.found === false (the old code rm -rf'd a guessed path, which always failed and left the archive id stuck forever). Client side: the component never received ctx (it read free variables and swallowed the ReferenceError in a silent try/catch), so its refresh was dead code. Verified with an A/B run on a scratch DSH_HOME against a non-live archived session: old = 0 removal events + accounting leaked; new = 1 event + accounting released.

  • Declared DSH support (0.3.6), then switched to a triple range in 0.3.9 — the @deepseek-ai/dsh peer is now >=0.2.0-rc.1 <0.2.1-0, so 0.2.0-rc.2 / later rc builds / the stable 0.2.0 all pass without a republish. Earlier: (0.3.7) rc.2 added to the enumeration — zero code changes, verified by the byte-identical dsh-shell diff and a live end-to-end delete test on a scratch dsh@0.1.7-rc.2 profile; (0.3.8) 0.2.0-rc.1 added — verified by a full lib/ SHA1 diff (dsh-shell byte-identical; dsh-session's changes additive-only, new ToolCallRecovery exports, no removals), gate execution on 0.2.0-rc.1, and a live sandbox assembly. ⚠️ The 0.1.7 line is no longer declared as of 0.3.9 — install 0.3.8 if you need it there.

  • DSH 0.2.1 support (v0.3.11) — the @deepseek-ai/dsh peer gained the second triple segment >=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-rc.1 <0.2.2-0; cordis gained 4.0.5. Zero code changes. The api-session/removed chain this plugin relies on (the 0.3.10 fix) now runs through the new dsh-api-remote-access service, which short-circuits to ALLOW for the profile owner — and the desktop profile is the owner.

"dependencies": { "@omdp/dsh-archived-sessions": "^0.3.11" }

All three plugins are published to npm automatically by GitHub Actions on every v* tag. This is the preferred install path — it avoids the git-#path: normalization, cross-resolution, and allowBuilds friction that GitHub installs cause (see the history in docs/npm-publish.md).

// ~/.dsh/profiles/<name>/package.json — you can use one or mix-and-match
"dependencies": {
  "@omdp/dsh-connector": "^0.3.10",
  "@omdp/dsh-key-fallback": "^3.3.3",
  "@omdp/dsh-archived-sessions": "^0.3.11"
}
cd ~/.dsh/profiles/<name>
pnpm install

Updating is a standard pnpm update:

cd ~/.dsh/profiles/<name>
pnpm update @omdp/dsh-connector @omdp/dsh-key-fallback @omdp/dsh-archived-sessions

Remote installs from GitHub (alternative)

Each active plugin is a standalone npm package in its own subdirectory, so it can also be installed straight from GitHub without a local checkout:

dsh plugin --profile web add github:XJungit/omdp#path:dsh-connector
dsh plugin --profile web add github:XJungit/omdp#path:dsh-key-fallback
dsh plugin --profile web add github:XJungit/omdp#path:dsh-archived-sessions

Command availability — the dsh plugin add commands above assume dsh is on your PATH. If you run DSH via npx per the official docs (no global dsh command), those lines fail with command not found: dsh — prefix each line with npx @deepseek-ai/dsh instead (no dsh on PATH required).

The #path:<subdir> selector tells pnpm which workspace subdirectory to install (it resolves to that subpackage's package.json, not the repo root).

pnpm ≥10 build-script gate. A git install fetches sources, and pnpm refuses to run a git dependency's prepare/build scripts until explicitly allowed — the first add fails until you whitelist it in the profile's pnpm-workspace.yaml:

allowBuilds:
  '@omdp/dsh-connector': true
  '@omdp/dsh-key-fallback': true
  '@omdp/dsh-archived-sessions': true

Then re-run the add. (These plugins are plain JavaScript with no build step, so the whitelist is the only hurdle — no prepare script is needed. See the official publish.md for the full "build-script catch".) Treat the allowance as permission to run the package's code at install time; for untrusted sources, pin a commit (github:XJungit/omdp#<sha>&path:<subdir>).

The same monorepo layout is used by other DSH plugin collections, e.g. zhu1090093659/dsh-web-ui.

Releasing a new version (GitHub Actions)

  1. Bump version in the subdirectory's package.json (only the one(s) you touched).
  2. Commit, then tag and push — the tag selects which package is published, so it must be <version>-<plugin> with the version you just bumped:
    git tag v0.3.10-dsh-connector
    git push origin master && git push origin v0.3.10-dsh-connector
    
  3. .github/workflows/publish.yml publishes that one package to npm with provenance (an already-published version is a no-op — a skip message is printed). A tag that matches no active package fails the job rather than publishing something else.
  4. Update your profile: pnpm update @omdp/<plugin>.

See docs/npm-publish.md for the full setup (npm token, GitHub Secret, troubleshooting) and docs/plugin-compatibility.md for the crash-resistance matrix.

GitHub installs (dsh plugin add github:XJungit/omdp#path:<plugin>) worked but hit network/TLS friction (e.g. UNABLE_TO_VERIFY_LEAF_SIGNATURE) and pnpm's git-#path: normalization on update (which dropped the #path: spec and could cross-resolve both packages to the repo root). A one-shot repair script (~/.dsh/profiles/web/update-omdp.ps1) handled those, but npm installs make all of that unnecessary.

Local link: installs ("@omdp/<plugin>": "link:<abs-path>/omdp/<plugin>") still work: pnpm install creates a junction so the running plugin is the repo source, and updating = edit/pull + restart. They remain a good choice during active development.

Conventions

  • Every plugin subdirectory is a standalone npm package with a dsh.bundle (and optionally dsh.client) manifest.
  • Package names are scoped under @omdp/ to avoid colliding with upstream dsh-* packages on npm.
  • Plugins in this repo are plain JavaScript (no build step), so both local-link and GitHub installs work without a compile stage.
  • Installing locally is preferred during development: add "@omdp/<plugin>": "link:<abs-path>/omdp/<plugin>" to the profile's dependencies and run pnpm install — the plugin loads straight from the repo and updates with a restart.
  • New plugins should be modelled on the existing three (connector / key-fallback / archived-sessions) rather than on a skeleton — the real plugins are the living templates.
  • Doc discipline — any plugin update (source / config / version) must keep the root README, the plugin's own README, and the docs/ files in sync; lessons learned during development go into notes/<date>/<category>/ (see AGENTS.md 规范 2).

Docs index

DocWhat it covers
docs/npm-publish.mdnpm publishing pipeline, why npm over GitHub installs, release flow
docs/plugin-compatibility.mdcrash-resistance matrix per plugin against DSH updates
docs/AI-DSH-plugin-quality.mdcommunity research: why AI-written DSH plugins break, and defensive practices
docs/DSH-plugin-quality-zh-discussion.mdChinese write-up of the same research + omdp practice
docs/dsh-drag-and-drop-troubleshooting.mdtroubleshooting record for the dsh-drag-and-drop plugin (Windows/Chinese)
notes/README.mddevelopment lessons & notes index (notes/<date>/<category>/) — see AGENTS.md 规范 2

中文版

一个把作者全部 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-