Tools & Capabilities
omdp
XJungit/omdp · ★ 1
only my DSH plugins — monorepo of DeepSeek Harness plugin bundles
README body is shown in Chinese.
Manifest details
安装
dsh plugin --profile web add github:XJungit/omdpGitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试
For install and environment setup, plus plugin development tutorials, see DSH community docs ↗
README
Table of contents
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 asmcp-remote --transport sse-onlystdio 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 /
mcpServersconfig snippets, skill "check update" viasource/sourceUpdatedfrontmatter. 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
connectorprofile entry'sconfig.toolFilters; older rc.x:settings.yamlconnector.toolFilters), prompt hiding + execution guard; unset = allow all.DSH 0.1.7 support (v0.3.3) —
jsdomis no longer imported at module top level (apunycode/resolution defect in DSH's package resolver made the whole plugin fail to import); tool filters moved to the volatileConfig/settings.replace()backend while keeping the rc.xsettings.yamlbackend.Declared DSH support + a stranded-filter rescue (v0.3.4) — new
@deepseek-ai/dshpeer (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 ownevaluatePluginCompatibility(): an untested newer runtime has the bundle gracefully skipped instead of failing undefined-ly, while runtimes≤0.1.6just 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 renamessettings.yaml→settings.yaml.importedand silently skips sections without a volatileConfig, soconnector.toolFiltersstayed 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.2added to the peer enumeration (v0.3.5) — zero code changes; verified by a file-by-file tarball diff (shell/settings/credentialslib/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 scratchdsh@0.1.7-rc.2profile (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), sotransport/serverName/command/urlall fell into thepreservebucket: the API answeredtransport:"" serverName:"", which made the tool-filter section vanish silently (if (!props.serverName) return null) and the transport badge degrade tostdio. Parsing now normalizes line endings first (toLf()), and writes emit LF. (2)patchPath()hardcodedprofiles/web/cordis.patch.yml, so under the desktop app (which runsprofiles/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 fromctx.get('profileContext').patchPath(thedsh-app-bootProfileContextcontract), keeping the legacy path only where that service does not exist (0.1.5/0.1.6);GET /api/mcpreturns it so the settings page displays the true file.DSH
0.2.0-rc.1added to the peer enumeration (v0.3.7) — zero code changes. After the desktop app moved to0.2.0-rc.1the gate skipped the bundle declared only up to0.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 everylib/file of both official tarballs (dsh-web-app/dsh-host-webserver/dsh-credentials/dsh-settings/dsh-tools/dsh-mcp-clientbyte-identical), by running the 0.2.0-rc.1evaluatePluginCompatibility()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+ isolatedDSH_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/dshpeer moved from per-version enumeration to>=0.2.0-rc.1 <0.2.1-0. When0.2.0-rc.2shipped, 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 diff0.2.0-rc.1 → rc.2(everything this plugin touches changes only inpackage.jsonversion strings; thedshmain package's changes are entirelydesktop-profile support) and by running the real gate over the whole matrix —0.2.0-rc.1/rc.2/rc.3/rc.9/stable0.2.0all pass,0.2.1-rc.1/0.3.0-rc.1all blocked. ⚠️ This narrows support: the0.1.7line is no longer declared — install 0.3.7 if you need it there.DSH
0.2.1support (v0.3.10) — the@deepseek-ai/dshpeer gained a second triple segment:>=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-rc.1 <0.2.2-0(schemasteryalso gained3.18.5, pinned by0.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.jschurn), a real ESM import/export-set diff of the 9 packages we consume (the only removals are unused, indsh-app-boot), a method-level existence check of all 37 symbols we call, and a real0.2.1-rc.2CLI assembly with an isolatedDSH_HOME(--dump-configcomposes 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 DSHLlmErrorstandard code set (QUOTA/AUTH/RATE_LIMIT/TIMEOUT/TRANSPORT/SERVER/EMPTY_RESPONSE/INVALID_CREDENTIAL) plus custom codes (matched exactly againstfailure.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+, thesettings.yamlfile backend on older rc.x. v3.2.1 adds a one-shot rescue that recovers pools stranded insettings.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/dshpeer (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.2added to all four dsh peer enumerations (v3.2.3) — zero code changes; verified by tarball diff (credentialslib/byte-identical, llm additive-only), gate execution on rc.2, and a live scratch-profile regression.DSH
0.2.0-rc.1added to all four dsh peer enumerations (v3.2.4) — zero code changes; verified by a fulllib/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-settingsnow all declare>=0.2.0-rc.1 <0.2.1-0; zero code changes.0.2.0-rc.2had 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 inpackage.jsonversion strings) and by the real gate over the whole matrix. ⚠️ The0.1.7line is no longer declared — install 3.3.1 if you need it.cordis/schemasterystay 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.resolvewrapper that supplies the key has existed since v3.0.0 (75ea76a), and the published3.2.4tarball already contains it (lib/index.js:298) along with itspool-hitdiagnostic (:305) — verified by downloading the tarball from the registry. Rotation was already working; theprocess.envwrites 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)
/poolsactiveRefno longer misreports the current key: it used to reverse-look-upprocess.env[<pool env>]and could highlight the wrong member (measured live:activeRef=SENSENOVA_API_KEYwhilecurrentRef=key_fallback_sensenova_key1); it is now justcurrentRef. (2) The 5process.envwrites 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-errorno longer falls back to blamingkeyValues[0]. (4) Uninstall restores the originalresolveviactx.effectwith an identity guard, so hot reloads stop stacking wrappers. Re-send stays DSH's job — every rotation path still callsnext().DSH
0.2.1support (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;cordisgained4.0.5andschemastery3.18.5(both pinned by0.2.1-rc.2). Zero code changes — the whole surface this plugin touches (credentials.resolvewrapping,ctx.llmevents,settings,webServer) is byte-identical between0.2.0-rc.2and0.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
parentSessionsubtree (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 prefersctx.shell.resolve()→execute()→await result()on DSH 0.1.7+, and falls back toresolve()→run()on ≤0.1.6, so one build deletes across the whole line. (0.3.5 only calledexecute()— which fixed 0.1.7 but silently broke rc.x, whererun()is the only method; 0.3.4 had the mirror-image bug. Verified by unpacking each@deepseek-ai/dsh-shelltarball'slib/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-queryhas zeroarchiv*references), so the client row survives inmanager.summariesand merely gets hidden. The old code only removed the archive id — lifting the mask made the surviving row visible again (and clicking it failed withsession/not-found), because the only removal notice it ever emitted came incidentally fromentry.detach()'ssession/disposed → api-session/removed, which only fires for a live session. 0.3.10 emitsapi-session/removedexplicitly on every delete path (wrapped in try/catch — cordisemitis synchronous and uncontained, so a throwing third-party listener must not abort a completed delete), releases the workspacesessionIdsaccounting slot viaentity.detachSession()(DSH keeps archived ids accounted by design, and never forgets them), and stops ignoringlocation.found === false(the old coderm -rf'd a guessed path, which always failed and left the archive id stuck forever). Client side: the component never receivedctx(it read free variables and swallowed theReferenceErrorin a silenttry/catch), so its refresh was dead code. Verified with an A/B run on a scratchDSH_HOMEagainst 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/dshpeer is now>=0.2.0-rc.1 <0.2.1-0, so0.2.0-rc.2/ later rc builds / the stable0.2.0all pass without a republish. Earlier: (0.3.7) rc.2 added to the enumeration — zero code changes, verified by the byte-identicaldsh-shelldiff and a live end-to-end delete test on a scratchdsh@0.1.7-rc.2profile; (0.3.8) 0.2.0-rc.1 added — verified by a fulllib/SHA1 diff (dsh-shellbyte-identical;dsh-session's changes additive-only, newToolCallRecoveryexports, no removals), gate execution on 0.2.0-rc.1, and a live sandbox assembly. ⚠️ The0.1.7line is no longer declared as of 0.3.9 — install 0.3.8 if you need it there.DSH
0.2.1support (v0.3.11) — the@deepseek-ai/dshpeer gained the second triple segment>=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-rc.1 <0.2.2-0;cordisgained4.0.5. Zero code changes. Theapi-session/removedchain this plugin relies on (the 0.3.10 fix) now runs through the newdsh-api-remote-accessservice, which short-circuits to ALLOW for the profile owner — and the desktop profile is the owner.
"dependencies": { "@omdp/dsh-archived-sessions": "^0.3.11" }
Installing from npm (recommended)
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 addcommands above assumedshis on yourPATH. If you run DSH vianpxper the official docs (no globaldshcommand), those lines fail withcommand not found: dsh— prefix each line withnpx @deepseek-ai/dshinstead (nodshon 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)
- Bump
versionin the subdirectory'spackage.json(only the one(s) you touched). - 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 .github/workflows/publish.ymlpublishes 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.- 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.
Historical: GitHub and local-link installs
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 optionallydsh.client) manifest. - Package names are scoped under
@omdp/to avoid colliding with upstreamdsh-*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'sdependenciesand runpnpm 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 intonotes/<date>/<category>/(see AGENTS.md 规范 2).
Docs index
| Doc | What it covers |
|---|---|
docs/npm-publish.md |
npm publishing pipeline, why npm over GitHub installs, release flow |
docs/plugin-compatibility.md |
crash-resistance matrix per plugin against DSH updates |
docs/AI-DSH-plugin-quality.md |
community research: why AI-written DSH plugins break, and defensive practices |
docs/DSH-plugin-quality-zh-discussion.md |
Chinese write-up of the same research + omdp practice |
docs/dsh-drag-and-drop-troubleshooting.md |
troubleshooting record for the dsh-drag-and-drop plugin (Windows/Chinese) |
notes/README.md |
development lessons & notes index (notes/<date>/<category>/) — see AGENTS.md 规范 2 |
中文版
一个把作者全部 DeepSeek Harness(DSH)插件收进单一 GitHub 仓库的 monorepo。每个插件各自独立子目录,是可直接安装的 DSH bundle,并在每次打 v* tag 时自动发布到 npm。
目录结构
omdp/
├── README.md # 本文件
├── package.json # 根清单——保证 bare-git 安装可用(见下文)
├── dsh-connector/ # 统一 MCP + Skills 管理器(Web UI 设置页)
│ ├── index.js # host 半区
│ ├── client.js # client 半区(Web UI)
│ ├── cordis.patch.yml # bundle 激活行
│ ├── package.json
│ └── README.md
├── dsh-key-fallback/ # 多 key API 池 + 自动轮换
│ ├── lib/index.js # host 半区(ESM)
│ ├── lib/client.js # client 半区(Web UI 设置页)
│ ├── cordis.patch.yml # bundle 激活行
│ ├── package.json
│ └── README.md
├── dsh-archived-sessions/ # 归档会话管理(fork @muwinds,适配 DSH 0.2.0-rc.1 / 0.2.0-rc.2)
│ ├── lib/index.js # host 半区(ESM)
│ ├── lib/client.js # client 半区(Web UI 设置页)
│ ├── cordis.patch.yml # bundle 激活行
│ ├── package.json
│ └── README.md
├── archive/ # 已归档插件,留作历史参考
│ ├── dsh-gitbash-win/
│ ├── resume-stream/
│ ├── key-fallback-*/ # dsh-key-fallback 各版本快照与设计草案
│ ├── dsh-vision-bridge-*/ # 2026-10-18 起归档(原为活跃视觉插件)
│ └── README.md
├── notes/ # 开发教训与笔记(三层:notes/<date>/<category>/)
├── docs/ # 研究笔记、兼容性矩阵、发布指南
└── <未来插件>/ # 每个插件一个子目录 + package.json
插件
@omdp/dsh-connector — MCP + Skills 管理器(v0.3.10)
一个设置页(Connector),从 DSH Web UI 管理三件事:
MCP 服务器 —— 编辑
cordis.patch.yml中的 MCP 块(stdio / streamable-http),带完整校验,坏配置绝不可能带到下次启动。旧版 SSE 服务器(如知乎)保留为mcp-remote --transport sse-only的 stdio 桥;插件只管这段配置文本。用户 Skills —— 读写/删除
~/.dsh/skills/<name>/SKILL.md下的技能(保留 frontmatter)。市场浏览器(v0.2.0+) —— 匿名 OpenAPI 只读浏览 ModelScope Skills 集市 与 MCP 广场;一键复制安装命令 /
mcpServers配置片段;通过source/sourceUpdatedfrontmatter 检查技能更新。市场数据只存进程内存(30 分钟 TTL),绝不落盘。工具过滤(v0.3.0+) —— 每 server 的 MCP 工具 allow 名单(DSH 0.1.7+ 存
connectorprofile 条目的config.toolFilters;旧版 rc.x 存settings.yaml的connector.toolFilters),提示词隐藏 + 执行期拦截;不配 = 全放行。DSH 0.1.7 适配(v0.3.3) ——
jsdom不再顶层静态 import(DSH 包解析器对punycode/这类「内置名 + 子路径」会抛TypeError,导致整个插件 import 失败、设置页永远停在「已安装,重启后生效」);工具过滤迁到 volatileConfig/settings.replace(),同时保留 rc.x 的settings.yaml后端。声明 DSH 版本支持 + 抢救滞留的过滤规则(v0.3.4) —— 新增
@deepseek-ai/dshpeer(0.1.7-rc.1,当时为逐版本枚举;v0.3.8 起改为三元组区间,见下),由 DSH 自己的evaluatePluginCompatibility()执行:未实测的新运行时会让 bundle 被优雅跳过(而不是以未定义方式崩掉),而≤0.1.6的运行时只是看到一条不认识的 peer(该门禁 0.1.7 才引入)。同时修复工具过滤静默变回「全放行」:0.1.7 的一次性导入器把settings.yaml改名为settings.yaml.imported,并静默跳过没有 volatileConfig的段,于是connector.toolFilters被留在.imported;ensureLegacyFiltersMigration()会在首个请求时把它搬回(不覆盖你已有的过滤规则、不删.imported)。peer 枚举追加 DSH
0.1.7-rc.2(v0.3.5) —— 代码零改动;依据:tarball 逐文件 diff(shell/settings/credentials 的lib/逐字节一致、llm 纯新增、app-boot 变更与插件无关)+ rc.2 门禁执行新声明放行 + scratchdsh@0.1.7-rc.2profile 真机回归(过滤端点 200)。CRLF 补丁 + 改错 profile 文件(v0.3.6) —— 两个真机 bug。(1)
parseMcpServers()的键值正则^\s+(\w+):\s*(.*)$在 CRLF 文件上必然失配(JS 里.*不匹配\r、$也不匹配\r之前),transport/serverName/command/url全落进preserve:接口返回transport:"" serverName:""导致工具过滤区静默消失(if (!props.serverName) return null)、transport 徽标退化成stdio。现在解析前先归一化换行(toLf()),写回也统一 LF。(2)patchPath()曾硬编码profiles/web/cordis.patch.yml,桌面端(跑profiles/desktop)因此在改另一个 profile 的文件——对自己的 MCP 配置毫无影响,UI 还显示错误路径。0.1.7+ 改为从ctx.get('profileContext').patchPath取真实路径(dsh-app-boot的ProfileContext契约),仅在没有该服务的 0.1.5/0.1.6 上保留历史路径;GET /api/mcp回传该路径供设置页显示。peer 枚举追加 DSH
0.2.0-rc.1(v0.3.7) —— 代码零改动。桌面端升到0.2.0-rc.1后,门禁把只声明到0.1.7-rc.2的 bundle 跳过(Connector 标签消失、/connector/api/*→ 404;是优雅跳过而非崩溃)。依据:两版官方 tarball 全量lib/文件 SHA1 逐文件 diff(dsh-web-app/dsh-host-webserver/dsh-credentials/dsh-settings/dsh-tools/dsh-mcp-client逐字节一致)+ 用 0.2.0-rc.1 的evaluatePluginCompatibility()判定新声明放行(旧声明被拦)+ 沙箱真机装配(官方@deepseek-ai/dsh@0.2.0-rc.1+ 独立DSH_HOME+--dump-config→ 插件入树、零拦截)。peer 声明改用三元组区间(v0.3.8) ——
@deepseek-ai/dsh由逐版本枚举改为>=0.2.0-rc.1 <0.2.1-0;代码零改动。0.2.0-rc.2发布后门禁第三次把旧声明拦下(Connector 标签消失、/connector/api/*→ 404),根因不是不兼容而是枚举在每次 rc 迭代下必然过时。依据:rc.1→rc.2 全量 20 包逐文件 SHA256(本插件依赖面只有package.json版本号变化)+ 真门禁全矩阵(0.2.0-rc.1/rc.2/rc.3/rc.9/正式版0.2.0全放行;0.2.1-rc.1/0.3.0-rc.1全拦截)。⚠️ 同时收窄支持面:0.1.7线不再声明,需要它请装 0.3.7。支持 DSH
0.2.1(v0.3.10) ——@deepseek-ai/dshpeer 新增第二段三元组区间:>=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-rc.1 <0.2.2-0(schemastery同步追加3.18.5,为0.2.1-rc.2的钉版)。代码零改动。 四层核查:全量@deepseek-ai/*逐文件 SHA256(0.2.0-rc.2→0.2.1-rc.2:288 → 305 个包,177 个有真实.js改动)、我们消费的 9 个包真实 ESM 导出面比对(移除项均未使用,在dsh-app-boot)、调用到的 37 个符号逐个存在性复核、以及真实0.2.1-rc.2CLI 装配(隔离DSH_HOME,--dump-config三个插件全部进树、零跳过)。
"dependencies": { "@omdp/dsh-connector": "^0.3.10" }
@omdp/dsh-key-fallback — 多 key API 池 + 轮换(v3.3.3)
挂在 DSH 的凭证接缝 credentials.resolve 上:每次适配器取 key 时,若该 ref 属于已配置的池,就返回池的当前 key;遇配置的触发错误时,把失败 key 标记为冷却并切到下一把——重发完全交给 DSH 自带的 dsh-llm-retry。带一个常驻可见的设置页(设置 → API Key 回退)与全新 UI:
可配置的轮换触发码(
rotateOn)——点选 chips 覆盖 DSHLlmError标准码全集(QUOTA/AUTH/RATE_LIMIT/TIMEOUT/TRANSPORT/SERVER/EMPTY_RESPONSE/INVALID_CREDENTIAL),也支持自定义码(与failure.code精确匹配)。显示当前实际使用的 key(池的当前 ref —— 正是凭证接缝交给适配器的那把),不是截断的哈希。
短 ref(
key_fallback_<provider>_key1、…),旧长 ref 一次性幂等迁移。眼睛开关明文揭示(
GET /keys/plain,仅限池内 key / env key)与可编辑 env key(文件托管的可编辑;由启动环境注入的只读)。每把 key 的
nextRef、池锁定("设为当前")、冷却重置与删除。DSH 0.1.7 适配(v3.2.0/v3.2.1) —— 配置双后端:0.1.7+ 用 volatile
Config/settings.replace()写本插件 profile 条目,旧版 rc.x 用settings.yaml文件后端。v3.2.1 追加一次性救援:把残留在settings.yaml.imported里的池捞回来(0.1.7 的迁移会改名该文件,文件后端从此再也看不到它们,被救池表现为「尚未启用 / 还没有任何池」且重启无效)。声明 DSH 版本支持(v3.2.2) —— 新增
@deepseek-ai/dshpeer(0.1.7-rc.1,当时为逐版本枚举;v3.3.2 起改为三元组区间,见下),把「支持的运行时」变成显式且机器可校验的契约(门禁语义见上方 connector 条目)。四条 dsh peer 枚举同步追加
0.1.7-rc.2(v3.2.3) —— 代码零改动;依据:tarball diff(credentials 的lib/逐字节一致、llm 纯新增)+ rc.2 门禁放行 + scratch profile 真机回归。四条 dsh peer 枚举同步追加
0.2.0-rc.1(v3.2.4) —— 代码零改动;依据:全量lib/SHA1 diff(credentials/llm/settings/shell/tools/mcp-client/web/agent 逐字节一致——本插件用到的整个调用面)+ 0.2.0-rc.1 门禁放行 + 沙箱真机装配入树。四条 dsh peer 改用三元组区间(v3.3.2) ——
@deepseek-ai/dsh/dsh-credentials/dsh-llm/dsh-settings四条统一改为>=0.2.0-rc.1 <0.2.1-0;代码零改动。0.2.0-rc.2发布后门禁第三次拦下(设置项消失、路由 404/405)。依据:rc.1→rc.2 全量 20 包逐文件 SHA256(六条 peer 只有package.json版本号变化)+ 真门禁全矩阵放行/拦截符合预期。⚠️ 不再声明0.1.7线,需要它请装 3.3.1。对 v3.3.0 说明的更正(v3.3.1) —— 下方 v3.3.0 条目原先声称「换 key 从来没到达 provider」。那是错的。 真正供 key 的
credentials.resolve包装自 v3.0.0 就存在(75ea76a),已发布的3.2.4tarball 里就有它(lib/index.js:298)及配套pool-hit诊断(:305)——从 registry 下载 tarball 核对过。轮换一直在生效;那些process.env写入是与可用包装并存的冗余代码,而不是机制本身。下方 v3.3.0 条目保留,但其中错误断言已改正。四项真实修复(v3.3.0) —— ①
/pools的activeRef不再报错当前 key:原先用process.env[<池 env>]反查、可能高亮错误的成员(实机测得activeRef=SENSENOVA_API_KEY而currentRef=key_fallback_sensenova_key1),现在直接等于currentRef。② 删除 5 处process.env写入——已证实启动快照冻结、凭证层读不到,对鉴权是死代码;删掉同时避免插件改写宿主环境。③agent/request-error不再退化成归咎keyValues[0]。④ 卸载经ctx.effect还原原始resolve(带身份校验),热重载不再叠加包装层。重发仍归 DSH 管——每条轮换路径都仍调用next()。支持 DSH
0.2.1(v3.3.3) —— 四条 dsh peer 均新增第二段三元组区间>=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-rc.1 <0.2.2-0;cordis追加4.0.5、schemastery追加3.18.5(均为0.2.1-rc.2的钉版)。代码零改动 —— 本插件触及的整个面(credentials.resolve包装、ctx.llm事件、settings、webServer)在0.2.0-rc.2与0.2.1-rc.2之间逐字节一致,已由导出面比对 + 真机装配双重确认。
"dependencies": { "@omdp/dsh-key-fallback": "^3.3.3" }
@omdp/dsh-archived-sessions — 归档会话管理(v0.3.11)
fork 自 @muwinds/dsh-archived-sessions 0.2.0,适配 DSH 0.2.0-rc.1 / 0.2.0-rc.2(0.3.9 起用三元组区间声明;更早的 0.1.5-rc.1 → 0.1.7-rc.2 线支持到 0.3.8,现已不再声明)(上游已不维护,且在 0.1.5+ 下损坏:sessionPersistence.list() 返回快照、locate() 被移除)。新增 设置 → 归档会话管理,与 DSH 0.1.6 起内置的原生「已归档会话」页共存:
列表显示归档会话的标题 / ID / 工作区 / 磁盘占用 / 运行状态;
释放:把会话移出归档集合(不删数据);
删除:删会话目录 + 广播
api-session/removed+ 移除归档标记 + 释放工作区记账槽(两步确认);按树删除:删主会话时连同其
parentSession子树一起删(修复上游 issue #2——孤儿子代理);孤儿清理:扫描并清理「父会话已删除、自己还在盘上」的残留子会话目录(自 0.3.10 起与删除走同一套四步收尾);
删除安全:目录名不是会话目录(
session-<uuid>或裸 UUID)一律拒绝删除;删除自 0.3.6 起双时代自适应——DSH 0.1.7+ 走ctx.shell.resolve()→execute()→await result(),≤0.1.6回退resolve()→run(),一份构建覆盖整条版本线。(0.3.5 只调execute():修好了 0.1.7 却让 rc.x 静默失效——那里只有run();0.3.4 则恰好相反。依据:逐版解包@deepseek-ai/dsh-shell的lib/types/index.d.ts。)0.3.10——「删掉的归档会话又回到对话列表」:归档在 DSH 里只是可见性遮罩(从不参与列表过滤,
dsh-session-query全库 0 处archiv*),客户端行一直留在manager.summaries里、只是被遮住。旧代码删除时只摘归档 id——遮罩一撤,幸存的行立刻重新可见(点进去报session/not-found);它唯一的客户端通知来自entry.detach()顺带触发的session/disposed → api-session/removed,而这条链只在会话仍 live 时存在。0.3.10 在每条删除路径上显式广播api-session/removed(包 try/catch——cordis 的emit同步且不隔离,第三方监听器抛错不能把已完成的删除变成报错),并经entity.detachSession()释放工作区sessionIds记账(DSH 故意保留归档会话的记账,且从不遗忘),同时不再忽略location.found === false(旧代码拿猜测路径去rm -rf,必然失败且让归档 id 永久卡住)。客户端侧:组件原本拿不到ctx(读的是自由变量,ReferenceError被静默try/catch吞掉),刷新逻辑一直是死代码。验证方式是在 scratchDSH_HOME上对非 live 的归档会话做 A/B:旧代码 0 个移除事件 + 记账泄漏;新代码 1 个事件 + 记账已释放。声明 DSH 版本支持(0.3.6),0.3.9 起改用三元组区间——
@deepseek-ai/dshpeer 现为>=0.2.0-rc.1 <0.2.1-0,0.2.0-rc.2/ 后续同三元组 rc / 正式版0.2.0都不必重发包即可通过。(0.3.7)追加0.1.7-rc.2——代码零改动,依据dsh-shell逐字节零变化 diff + scratchdsh@0.1.7-rc.2profile 真机删除全链路实测;(0.3.8)追加0.2.0-rc.1——依据全量lib/SHA1 diff(dsh-shell逐字节一致;dsh-session的 5 个变化文件为附加式新增ToolCallRecovery等导出、既有导出零删除)+ 0.2.0-rc.1 门禁放行 + 沙箱真机装配入树。⚠️0.1.7线自 0.3.9 起不再声明,需要它请装 0.3.8。支持 DSH
0.2.1(v0.3.11) ——@deepseek-ai/dshpeer 新增第二段三元组区间>=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-rc.1 <0.2.2-0;cordis追加4.0.5。代码零改动。 本插件依赖的api-session/removed链路(0.3.10 修复)在 0.2.1 上改由新的dsh-api-remote-access服务把关,而该服务对 profile owner 直接短路放行(桌面 profile 即 owner)。
"dependencies": { "@omdp/dsh-archived-sessions": "^0.3.11" }
从 npm 安装(推荐)
三个插件都会由 GitHub Actions 在每次打 v* tag 时自动发布到 npm。这是首选安装路径——绕开 GitHub 安装带来的 git-#path: 规范化、交叉解析与 allowBuilds 摩擦(历史详见 docs/npm-publish.md)。
// ~/.dsh/profiles/<name>/package.json —— 可用其一或自由组合
"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
升级就是标准的 pnpm update:
cd ~/.dsh/profiles/<name>
pnpm update @omdp/dsh-connector @omdp/dsh-key-fallback @omdp/dsh-archived-sessions
从 GitHub 远程安装(备选)
每个活跃插件都是独立子目录里的独立 npm 包,因此也能不经过本地 checkout、直接从 GitHub 安装:
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
安装命令前提:上面的
dsh plugin add假设dsh已在 PATH。若你是按官方文档用npx运行 dsh(没有全局dsh命令),这几行会报command not found: dsh—— 每行前面加npx @deepseek-ai/dsh即可(不要求dsh在 PATH)。
#path:<子目录> 选择器告诉 pnpm 安装哪个 workspace 子目录(解析到该子包的 package.json,而不是仓库根)。
pnpm ≥10 构建脚本门禁。 git 安装拉取的是源码,pnpm 默认拒绝运行 git 依赖的 prepare/构建脚本,直到显式放行——首次 add 会失败,需要先在 profile 的 pnpm-workspace.yaml 里白名单:
allowBuilds:
'@omdp/dsh-connector': true
'@omdp/dsh-key-fallback': true
'@omdp/dsh-archived-sessions': true
然后重新执行 add。(这些插件是纯 JavaScript、无构建步骤,所以白名单是唯一障碍——不需要 prepare 脚本。官方 publish.md 有完整的"构建脚本坑"说明。)放行等于允许在安装时运行该包的代码;对不可信来源,请固定到具体 commit(github:XJungit/omdp#<sha>&path:<子目录>)。
其他 DSH 插件集合也采用同样的 monorepo 布局,例如 zhu1090093659/dsh-web-ui。
发布新版本(GitHub Actions)
- 在子目录的
package.json里 bumpversion(只 bump 你动过的)。 - 提交,然后打 tag 并推送——tag 决定发哪个包,所以要写成
<version>-<plugin>, 且 version 就是刚 bump 的那个:git tag v0.3.10-dsh-connector git push origin master && git push origin v0.3.10-dsh-connector .github/workflows/publish.yml只发布该 tag 指名的那个包到 npm(带 provenance;已发布的版本重复发布是 no-op,会打印 skip 信息)。tag 匹配不上任何活跃包时 job 直接失败,不会误发别的包。- 更新你的 profile:
pnpm update @omdp/<plugin>。
完整配置(npm token、GitHub Secret、排障)见 docs/npm-publish.md;抗崩溃矩阵见 docs/plugin-compatibility.md。
历史:GitHub 与本地 link 安装
GitHub 安装(dsh plugin add github:XJungit/omdp#path:<插件>)能用,但会撞上网络/TLS 摩擦(如 UNABLE_TO_VERIFY_LEAF_SIGNATURE),以及 pnpm 在 update 时的 git-#path: 规范化问题(会丢 #path: 片段,甚至可能把两个包都交叉解析到仓库根)。当时用一次性修复脚本(~/.dsh/profiles/web/update-omdp.ps1)兜底,但 npm 安装让这一切都不再必要。
本地 link: 安装("@omdp/<插件>": "link:<绝对路径>/omdp/<插件>")依然可用:pnpm install 会创建 junction,让运行中的插件就是仓库源码,更新 = 改代码/拉取 + 重启。开发活跃期仍是好选择。
约定
- 每个插件子目录都是独立 npm 包,带
dsh.bundle(可选dsh.client)清单。 - 包名统一挂在
@omdp/作用域下,避免与上游dsh-*包在 npm 上撞名。 - 仓库里的插件都是纯 JavaScript(无构建步骤),所以本地 link 与 GitHub 安装都不需要编译环节。
- 开发期优先本地安装:把
"@omdp/<插件>": "link:<绝对路径>/omdp/<插件>"加进 profile 的dependencies,跑pnpm install—— 插件直接从仓库加载,改完重启即生效。 - 新插件应以现有三个插件(connector / key-fallback / archived-sessions)为蓝本,而不是复制模板——真实插件就是活的模板。
- 文档纪律:插件有任何更新(源码/配置/版本)时,必须同步更新根 README、对应插件 README、docs/ 相关文档;开发中产生的教训/经验主动写入
notes/<date>/<category>/(详见 AGENTS.md 规范 2)。
文档索引
| 文档 | 内容 |
|---|---|
docs/npm-publish.md |
npm 发布管线、为什么选 npm 而非 GitHub 安装、发布流程 |
docs/plugin-compatibility.md |
各插件对 DSH 更新的抗崩溃矩阵 |
docs/AI-DSH-plugin-quality.md |
社区研究:为什么 AI 写的 DSH 插件会坏,以及防御性实践 |
docs/DSH-plugin-quality-zh-discussion.md |
同一研究的中文版 + omdp 实践 |
docs/dsh-drag-and-drop-troubleshooting.md |
dsh-drag-and-drop 插件排障记录(Windows/中文) |
notes/README.md |
开发教训与笔记索引(notes/<date>/<category>/)——见 AGENTS.md 规范 2 |