dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Stop malformed plugin mounts from failing every DeepSeek request with REQUEST_EXTENSION — a guard for DeepSeek Harness.
Plugin-mount identity guard for DeepSeek Harness.
Stops a malformed plugin mount from failing every DeepSeek request with REQUEST_EXTENSION.
@deepseek-ai/dsh-plugin-package-inventory-deepseek resolves a package identity for every active
Loader row at request time. When a row's name is a path, it walks up to the nearest
package.json and demands that name and version are both non-empty — otherwise it throws:
function identityFromManifest(path, allowAnonymous) {
const manifest = JSON.parse(readFileSync(path, "utf8"));
if (allowAnonymous && manifest.name === void 0) return void 0; // ← only tolerates a missing name
if (typeof manifest.name !== "string" || manifest.name.length === 0
|| typeof manifest.version !== "string" || manifest.version.length === 0)
throw new Error(`... ${path} must declare non-empty name and version`);
return { name: manifest.name, version: manifest.version };
}
dsh-llm-deepseek then wraps that throw:
catch (error) {
throw new LlmError("DeepSeek request extension preparation failed", "REQUEST_EXTENSION", { cause: error });
}
Result: every official DeepSeek request fails. The UI shows only the generic wrapped message, so it looks like a model or account problem.
The failing manifest is usually DSH's own profile manifest, which ships with a name but no version:
// $DSH_HOME/profiles/<name>/package.json ← generated by DSH
{ "name": "dsh-profile-desktop" }
So any local plugin mounted without its own package.json triggers it:
# $DSH_HOME/profiles/<profile>/cordis.patch.yml
- insert:
- id: myplugin
name: "C:/Users/<user>/.dsh/profiles/<profile>/plugins/myplugin/index.ts"
nearestManifest() walks up from plugins/myplugin/ → plugins/ → profiles/<profile>/ and lands on
that version-less manifest.
The caller already tolerates an unresolvable identity:
const identity = resolver.resolve(activeEntry);
if (identity === void 0) continue; // contract: undefined = skip this entry
resolve() is even typed as ... | undefined. Throwing here is error-severity misclassification:
best-effort diagnostic metadata should not hold a request-level veto.
Three defense layers, all implemented by mirroring the upstream resolution rules exactly.
Ships as a DSH bundle (dsh.bundle.patch), so it can be installed rather than copied.
| # | Mechanism | When | Effect |
|---|---|---|---|
| ① | Startup self-heal | apply(), before any model request | Scans every profile's patch layers; writes a minimal compliant package.json next to any offending plugin module |
| ② | Check-on-write | after any profile .yml/.yaml is written | Re-audits and self-heals immediately, appending the result to the tool output |
| ③ | Three tools | on demand | dsh_guard_audit / dsh_guard_repair / dsh_guard_verify |
Startup self-heal is profile-wide: load the plugin once and every session in that profile is covered. Measured cost: ~1 ms.
This repository is a DSH bundle: an npm package that declares dsh.bundle.patch, so a profile can
list it in dsh.profile.bundles and get the plugin row inserted automatically.
# from a clone
dsh plugin --profile <profile> add /path/to/dsh-plugin-guard
# or straight from git
dsh plugin --profile <profile> add github:Linicc/dsh-plugin-guard
dsh plugin forwards to pnpm inside the profile directory, then appends the package to
dsh.profile.bundles because it declares dsh.bundle. The bundle's cordis.patch.yml is what inserts
the plugin row:
- insert:
- id: plugin-guard
name: dsh-plugin-guard
Note that the row references the package by name, not by path — that is what lets Node resolve the installed code. It also means this row takes the bare package resolution branch, which is the one that does not depend on a nearest-manifest walk.
If you would rather not install a package, clone anywhere and mount the file by absolute path:
# $DSH_HOME/profiles/<profile>/cordis.patch.yml
- insert:
- id: plugin-guard
name: "<abs path>/dsh-plugin-guard/src/index.ts"
⚠️ The plugin directory must contain a compliant
package.json. Mounting a directory that has none is exactly the condition this plugin exists to prevent — see the problem. This repository ships one, but if you copy the source elsewhere, copy the manifest too.
- id: plugin-guard
name: dsh-plugin-guard
config:
autoRepair: true # write the missing package.json (default: true)
verbose: false # print audit details to the console
# profileRoot: "..." # default: $DSH_HOME/profiles
| Field | Type | Default | Meaning |
|---|---|---|---|
autoRepair | boolean | true | Self-heal at startup and on patch writes |
verbose | boolean | false | Print audit details |
profileRoot | string | $DSH_HOME/profiles | Root scanned for profiles |
dsh-plugin-guard/
├─ package.json declares dsh.bundle.patch; main -> lib/index.js
├─ cordis.patch.yml the layer a profile applies when it lists this bundle
├─ lib/index.js compiled entry (committed, so consumers need no build step)
├─ src/index.ts source
├─ scripts/build.mjs regenerates lib/ from src/ using Node's built-in type stripping
├─ README.md / README.zh.md
└─ LICENSE
Rebuild after editing the source:
npm run build # node scripts/build.mjs — no dependencies required
dsh_guard_auditAudits every profile's plugin mount rows and lists those that would throw.
✅ plugin-guard audit passed
profiles 2 · rows 8 · all identities resolvable
· desktop/plugin-guard → dsh-plugin-guard@0.1.0
On failure it names the profile, the row id, the module directory, and the exact problem.
dsh_guard_repairWrites a minimal package.json for non-compliant plugin directories. Idempotent, never overwrites.
Supports dryRun.
dsh_guard_verifyPre-install check: given a plugin entry file or directory, tells you whether it will pass the official resolution before you mount it.
The resolution rules are mirrored one-to-one so that the guard can never disagree with upstream:
| Upstream | Rule | Here |
|---|---|---|
barePackageName(specifier) | starts with . / contains : / isAbsolute() → treat as a path | isPathLike() |
nearestManifest(dir) | walk up from the module directory to the first package.json | nearestManifest() |
identityFromManifest(path, allowAnonymous) | name and version must both be non-empty | identityFromManifest() |
The guard is deliberately more conservative than upstream: upstream only inspects active rows, the guard inspects all rows. The cost is occasionally reporting a row that is disabled today but would fail if enabled — which is the desired behaviour.
package.json at all; never modifies another
package's manifest.repairable: false and left for a human — the guard will not edit your profile manifest.try/catch: the guard must never become the failure it
guards against.Three tool schemas are contributed to the system prompt: dsh_guard_audit, dsh_guard_repair, and
dsh_guard_verify, each with a Chinese description explaining when to call it. The schemas are static;
no conversation content is read.
dsh_guard_audit — 审计 DSH 所有 profile 的插件装载行,复刻官方「包身份解析」逻辑,找出会导致 REQUEST_EXTENSION (整轮 DeepSeek 请求失败)的行。任何新增/修改插件之后都应跑一次。
dsh_guard_repair — 为不合规的插件目录补一个最小 package.json(name + version),消除 REQUEST_EXTENSION 隐患。幂等、永不覆盖已有文件。补完后用 dsh_guard_audit 复验。
dsh_guard_verify — 安装一个本地插件**之前**的预检:给定 .ts/.js 文件或目录,判断它作为 patch insert 行时能否通过官方包身份解析。
Fixed and small: three tool schemas in the tool catalogue. No result is injected unless a tool is called or a profile patch file is written.
Prefix-stable, with one exception. The tool schemas are static, so the prompt prefix is unchanged across
requests. When a .tex-unrelated profile patch file is written, tools/post-execute appends a short
audit note to that tool result — this changes only the tail of the conversation, not the cached prefix.
No other request-context entry is added.
identityFromManifest() in @deepseek-ai/dsh-plugin-package-inventory-deepseek, and any other
trigger of the same failure path (for example a stale fallback symlink, see upstream discussion #5439)
will still take requests down. Reported upstream: discussion #4950.- id: / name: pairs are extracted. Unusual patch
shapes (anchors, merge keys, deeply nested inserts) may not be seen. The guard fails open (reports
nothing) rather than failing the boot.package.json is reported, not repaired.write/edit tool activity and host startup trigger an audit. A patch file
edited by an external editor is picked up at the next DSH start.The exact failure condition can be reconstructed in a sandbox and all four paths exercised
(audit / verify / repair / startup self-heal):
Sandbox:
profiles/desktop/package.json → { "name": "dsh-profile-desktop" } ← name, no version
cordis.patch.yml → insert row pointing at a directory with no package.json
plugins/myplugin/index.ts → entry file only
autoRepair: false
dsh_guard_audit → ok=false, problems=1 ✅ detects
dsh_guard_verify → ok=false + remediation advice ✅ pre-checks
dsh_guard_repair → creates package.json ✅ repairs
audit again → ok=true, problems=0 ✅ re-verifies
autoRepair: true (default)
apply() creates the manifest in ~1 ms ✅ defuses before it fires
Real profile
audit → profiles 2 · rows 8 · problems 0
MIT — see LICENSE.