返回目录
模型与 MCP 技能

omdp

XJungit/omdp

only my DSH plugins — monorepo of DeepSeek Harness plugin bundles

Stars
1
Forks
0
Issues
0
更新
7 天前

PROJECT TOPICS

项目标签

INSTALL REFERENCE

安装参考

未验证
dsh plugin --profile web add github:XJungit/omdp

该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。

PROJECT README

README

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-vision-bridge/   # vision bridge: let text-only models "see" via a configured multimodal endpoint
│   ├── index.js         # host half
│   ├── client.js        # client half (paste/drop → temp path)
│   ├── 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
│   └── 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.8)

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.

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

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

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().
"dependencies": { "@omdp/dsh-key-fallback": "^3.3.2" }

@omdp/dsh-vision-bridge — vision for text-only models (v0.1.12)

A zero-dependency plugin that gives text-only models vision: it auto-detects whether the routed model supports images (llm.resolveModelInfo().inputModalities), and for text-only models forwards pasted / attached images to a configurable OpenAI-compatible multimodal endpoint (default Agnes agnes-2.5-flash) and feeds the returned text back as evidence. Ships a vision_bridge_read_image tool, a paste/drop → temp-path browser handler, a wrapped (vision bridge) provider entry, and an agent/pre-step auto-read hook.

"dependencies": { "@omdp/dsh-vision-bridge": "^0.1.12" }

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

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.
"dependencies": { "@omdp/dsh-archived-sessions": "^0.3.10" }

Installing from npm (recommended)

All four 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.8",
  "@omdp/dsh-vision-bridge": "^0.1.12",
  "@omdp/dsh-key-fallback": "^3.3.2",
  "@omdp/dsh-archived-sessions": "^0.3.10"
}
cd ~/.dsh/profiles/<name>
pnpm install

Updating is a standard pnpm update:

cd ~/.dsh/profiles/<name>
pnpm update @omdp/dsh-connector @omdp/dsh-vision-bridge @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-vision-bridge
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-vision-bridge': 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 — one tag per released package, <version>-<plugin>:
    git tag v0.3.4-dsh-connector
    git push origin master && git push origin v0.3.4-dsh-connector
  3. .github/workflows/publish.yml publishes the touched packages to npm with provenance (re-publishing an already-published version is a no-op — skip message is printed).
  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.

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 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 / vision-bridge / key-fallback) 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

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-vision-bridge/   # 视觉桥:让纯文本模型通过配置的多模态端点「看见」
│   ├── index.js         # host 半区
│   ├── client.js        # client 半区(粘贴/拖拽 → 临时路径)
│   ├── 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 各版本快照与设计草案
│   └── README.md
├── notes/               # 开发教训与笔记(三层:notes/<date>/<category>/)
├── docs/                # 研究笔记、兼容性矩阵、发布指南
└── <未来插件>/          # 每个插件一个子目录 + package.json

插件

@omdp/dsh-connector — MCP + Skills 管理器(v0.3.8)

一个设置页(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/sourceUpdated frontmatter 检查技能更新。市场数据只存进程内存(30 分钟 TTL),绝不落盘。

  • 工具过滤(v0.3.0+) —— 每 server 的 MCP 工具 allow 名单(DSH 0.1.7+ 存 connector profile 条目的 config.toolFilters;旧版 rc.x 存 settings.yaml 的 connector.toolFilters),提示词隐藏 + 执行期拦截;不配 = 全放行。

  • DSH 0.1.7 适配(v0.3.3) —— jsdom 不再顶层静态 import(DSH 包解析器对 punycode/ 这类「内置名 + 子路径」会抛 TypeError,导致整个插件 import 失败、设置页永远停在「已安装,重启后生效」);工具过滤迁到 volatile Config / settings.replace(),同时保留 rc.x 的 settings.yaml 后端。

  • 声明 DSH 版本支持 + 抢救滞留的过滤规则(v0.3.4) —— 新增 @deepseek-ai/dsh peer(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,并静默跳过没有 volatile Config 的段,于是 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 门禁执行新声明放行 + scratch dsh@0.1.7-rc.2 profile 真机回归(过滤端点 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。

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

@omdp/dsh-key-fallback — 多 key API 池 + 轮换(v3.3.2)

挂在 DSH 的凭证接缝 credentials.resolve 上:每次适配器取 key 时,若该 ref 属于已配置的池,就返回池的当前 key;遇配置的触发错误时,把失败 key 标记为冷却并切到下一把——重发完全交给 DSH 自带的 dsh-llm-retry。带一个常驻可见的设置页(设置 → API Key 回退)与全新 UI:

  • 可配置的轮换触发码(rotateOn)——点选 chips 覆盖 DSH LlmError 标准码全集(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/dsh peer(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.4 tarball 里就有它(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()。
"dependencies": { "@omdp/dsh-key-fallback": "^3.3.2" }

@omdp/dsh-vision-bridge — 给纯文本模型的视觉(v0.1.12)

零依赖插件,给纯文本模型装上视觉:自动探测被路由模型是否支持图片(llm.resolveModelInfo().inputModalities);对纯文本模型,把粘贴/附加的图片转发到可配置的 OpenAI 兼容多模态端点(默认 Agnes agnes-2.5-flash),并把返回文本喂回作为证据。附带 vision_bridge_read_image 工具、粘贴/拖拽 → 临时路径的浏览器处理器、一个包装后的 (vision bridge) provider 条目,以及 agent/pre-step 自动读取钩子。

"dependencies": { "@omdp/dsh-vision-bridge": "^0.1.12" }

@omdp/dsh-archived-sessions — 归档会话管理(v0.3.10)

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 吞掉),刷新逻辑一直是死代码。验证方式是在 scratch DSH_HOME 上对非 live 的归档会话做 A/B:旧代码 0 个移除事件 + 记账泄漏;新代码 1 个事件 + 记账已释放。
  • 声明 DSH 版本支持(0.3.6),0.3.9 起改用三元组区间——@deepseek-ai/dsh peer 现为 >=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 + scratch dsh@0.1.7-rc.2 profile 真机删除全链路实测;(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。
"dependencies": { "@omdp/dsh-archived-sessions": "^0.3.10" }

从 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.8",
  "@omdp/dsh-vision-bridge": "^0.1.12",
  "@omdp/dsh-key-fallback": "^3.3.2",
  "@omdp/dsh-archived-sessions": "^0.3.10"
}
cd ~/.dsh/profiles/<name>
pnpm install

升级就是标准的 pnpm update:

cd ~/.dsh/profiles/<name>
pnpm update @omdp/dsh-connector @omdp/dsh-vision-bridge @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-vision-bridge
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-vision-bridge': 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)

  1. 在子目录的 package.json 里 bump version(只 bump 你动过的)。
  2. 提交,然后打 tag 并推送——每个待发包一个 tag,格式 <version>-<plugin>:
    git tag v0.3.4-dsh-connector
    git push origin master && git push origin v0.3.4-dsh-connector
  3. .github/workflows/publish.yml 把动过的包发布到 npm(带 provenance;已发布的版本重复发布是 no-op,会打印 skip 信息)。
  4. 更新你的 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 / vision-bridge / key-fallback)为蓝本,而不是复制模板——真实插件就是活的模板。
  • 文档纪律:插件有任何更新(源码/配置/版本)时,必须同步更新根 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

CLASSIFICATION EVIDENCE

分类依据

项目类型技能
功能分类模型与 MCP
规则置信度高

系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: agent-skills、mcp。