reactive-resume
reactive-resume
A one-of-a-kind resume builder that keeps your privacy in mind. Completely secure, customizable, portable, open-source and free forever. Try it out today!
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:XJungit/omdp
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
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.
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
@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:
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).key_fallback_<provider>_key1, …) with one-time idempotent migration of legacy long refs.GET /keys/plain, pool-owned keys / env key only) and editable env keys (file-backed ones; read-only when supplied by the launching environment).nextRef, pool lock ("设为当前"), cooldown reset, and delete.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).@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).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.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.@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).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./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:
api-session/removed + prune the archive id + release the workspace accounting slot (two-step confirm);parentSession subtree (fixes upstream issue #2 — orphan subagents);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.)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.@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" }
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
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 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-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.
version in the subdirectory's package.json (only the one(s) you touched).<version>-<plugin>:git tag v0.3.4-dsh-connector
git push origin master && git push origin v0.3.4-dsh-connector
.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).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.
dsh.bundle (and optionally dsh.client) manifest.@omdp/ to avoid colliding with upstream dsh-* packages on npm."@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.docs/ files in sync; lessons learned during development go into notes/<date>/<category>/ (see AGENTS.md 规范 2).| 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_fallback_<provider>_key1、…),旧长 ref 一次性幂等迁移。GET /keys/plain,仅限池内 key / env key)与可编辑 env key(文件托管的可编辑;由启动环境注入的只读)。nextRef、池锁定("设为当前")、冷却重置与删除。Config / settings.replace() 写本插件 profile 条目,旧版 rc.x 用 settings.yaml 文件后端。v3.2.1 追加一次性救援:把残留在 settings.yaml.imported 里的池捞回来(0.1.7 的迁移会改名该文件,文件后端从此再也看不到它们,被救池表现为「尚未启用 / 还没有任何池」且重启无效)。@deepseek-ai/dsh peer(0.1.7-rc.1,当时为逐版本枚举;v3.3.2 起改为三元组区间,见下),把「支持的运行时」变成显式且机器可校验的契约(门禁语义见上方 connector 条目)。0.1.7-rc.2(v3.2.3) —— 代码零改动;依据:tarball diff(credentials 的 lib/ 逐字节一致、llm 纯新增)+ rc.2 门禁放行 + scratch profile 真机回归。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 门禁放行 + 沙箱真机装配入树。@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。credentials.resolve 包装自 v3.0.0 就存在(75ea76a),已发布的 3.2.4 tarball 里就有它(lib/index.js:298)及配套 pool-hit 诊断(:305)——从 registry 下载 tarball 核对过。轮换一直在生效;那些 process.env 写入是与可用包装并存的冗余代码,而不是机制本身。下方 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 起内置的原生「已归档会话」页共存:
api-session/removed + 移除归档标记 + 释放工作区记账槽(两步确认);parentSession 子树一起删(修复上游 issue #2——孤儿子代理);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。)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 个事件 + 记账已释放。@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" }
四个插件都会由 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
每个活跃插件都是独立子目录里的独立 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。
package.json 里 bump version(只 bump 你动过的)。<version>-<plugin>:git tag v0.3.4-dsh-connector
git push origin master && git push origin v0.3.4-dsh-connector
.github/workflows/publish.yml 把动过的包发布到 npm(带 provenance;已发布的版本重复发布是 no-op,会打印 skip 信息)。pnpm update @omdp/<plugin>。完整配置(npm token、GitHub Secret、排障)见 docs/npm-publish.md;抗崩溃矩阵见 docs/plugin-compatibility.md。
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,让运行中的插件就是仓库源码,更新 = 改代码/拉取 + 重启。开发活跃期仍是好选择。
dsh.bundle(可选 dsh.client)清单。@omdp/ 作用域下,避免与上游 dsh-* 包在 npm 上撞名。"@omdp/<插件>": "link:<绝对路径>/omdp/<插件>" 加进 profile 的 dependencies,跑 pnpm install —— 插件直接从仓库加载,改完重启即生效。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
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: agent-skills、mcp。