dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
为 dsh 打造的自动技能策展插件:每 N 轮真实对话,后台起一个评审子代理阅读会话摘要,主动把值得沉淀的经验提炼为 ~/.dsh/skills/<name>/SKILL.md - 把 Hermes 的「后台评审自我改进」闭环移植到 DSH,零侵入 bundle 插件,不改 dsh 源码。
Automatic skill curation for DeepSeek Harness (DSH). After every N real conversation turns (default 3), the plugin fires a background subagent that reads a digest of the session and edits, merges, prunes and creates SKILL.md files under the user skills directory (~/.dsh/skills/) — the same self-improvement loop Nous Research's Hermes Agent runs, ported to DSH as a zero-intrusion bundle plugin.
English | 简体中文
turn ends ──▶ agent/turn-stopping (scoped listener, counted per agent)
│ 3 turns reached? (skillNudgeInterval)
▼
async fire-and-forget (never blocks the turn close)
▼
session digest built ──▶ latest N messages verbatim + older turns compressed
▼
spawn subagent (provider: spawn) with digest + review instructions
│ toolFilter allow = skill-library-* only (whitelist, hermes-style)
▼
subagent reads the library first → subtracts (merge / shorten / delete) before it adds
│ every write must echo the sha256 it just read (= hard read-before-write)
▼
writes ~/.dsh/skills/<name>/SKILL.md (Chinese body; original backed up first)
▼
summary logged (host journal) + visible in the settings card status panel
Manual trigger: /skill-refine [focus] runs a review of the current session immediately.
| Hermes | dsh-skill-curator |
|---|---|
| turn_finalizer nudge counters (10) | scoped agent/turn-stopping counters (3, configurable) |
| fork of AIAgent in a daemon thread | platform subagent (ctx.subagents.start) — visible in the UI, fully isolated session |
| replay full conversation (warm prefix cache) | digest injection (tail-verbatim + head compression) — DSH has no cache advantage |
| runtime tool whitelist (memory+skills) | toolFilter.allow whitelist + prompt constraint |
| edits only curator-created skills | the whole library is editable, guarded by read-before-write + auto-backup + a protected list |
/refine | /skill-refine command |
Behavioral parity matrix: docs/COMPARISON.md.
cd /path/to/dsh-skill-curator
dsh plugin --profile web add ./
# restart dsh, then open: Settings → Plugins tab → "Skill Curator" card
Skip the restart? The plugin only takes effect on next start (standard bundle plugin; no dsh source changes, ever).
/chat/completions). When reviewBaseUrl + reviewModel are set, the review subagent runs against that endpoint through a dedicated adapter route (provider name = reviewProvider, or skill-curator-review by default). Settings are read live on every request — no restart needed~/.dsh/skill-curator/reviews.json (last 50 entries) — survives plugin removal/reinstall and restarts⚠️已回退主模型, "fell back to the main model", in the host log, the review log and the status panel); ② if the final attempt then dies on an endpoint-class error or a detail-less killed, it retries up to reviewRetryCount times with reviewRetryDelayMs backoff — a model connection blip no longer loses the review. Tool-layer errors are never retriedkilled; 0 disables retrying. Tool-layer errors are never retried. Default 1base × n ms (default 5000)The review subagent can only call these eleven (they are also usable by any session):
| Tool | Purpose |
|---|---|
skill-library-list | list skills (name, description, body line count, protected?) |
skill-library-read | read any file (SKILL.md, or a support file via filePath) + that file's own sha256 |
skill-library-tree | list every file in a skill (lines/size) — see what you actually wrote |
skill-library-create | create a class-level umbrella skill (Chinese body, bilingual description) |
skill-library-patch | targeted oldString→newString or whole-body replacement (frontmatter preserved) |
skill-library-write-file | support files; overwriting an existing file backs up its previous content |
skill-library-delete-file | delete one support file (read-first; backed up) |
skill-library-delete | delete a redundant/merged-away skill (read-first; whole directory backed up) |
skill-library-adopt | stamp an externally authored skill with the author marker (provenance only) |
skill-library-lifecycle | list / archive / restore / pin — inspect or maintain skill lifecycle state (archive is recoverable, never a delete) |
skill-library-git | self-check the library repo: status / diff / log (no push, no reset, never a shell) |
patch / write-file / delete must echo the expectedSha256 returned by skill-library-read. Writing without reading, or writing against a file that changed since the read, is refused. This is the only mechanical defence against rewriting someone's skill from memory.~/.dsh/skill-curator/backups/, keeping the latest 30, so a wrong move is recoverable.excludedSkills are read-only.Paths are boundary-checked; writes are atomic (tmp + rename). Bodies over 150 lines come back with a "move detail into references/" nudge in the tool result (advisory, never blocking).
Each skill is tracked as active → stale → archived by real use (a skill tool load), not by file mtime:
staleDays (default 30) / archiveDays (default 90) — windowsarchiveEnabled (default off) — off means the review only reports which skills are eligible; nothing on disk movesskill-library-lifecycle (there is no settings field for it) and it is exempt forever<DSH_HOME>/skill-curator/archived/<name>/ — outside the library, so the host does not discover them, and restore brings them backThe plugin also registers a runtime skill named skill-curator so the model knows how to use the eleven skill-library-* tools.
The review prompt carries a shape contract aimed squarely at "skills only ever grow":
references/. Standard skeleton: one-line conclusion → when to use → how → pitfalls → boundary.// package.json — machine-readable
"engines": { "node": "^22.19.0 || >=24.0.0" },
"peerDependencies": { // the host runtime is the single source of truth; no bundled dsh-* copies
"@deepseek-ai/dsh-llm": ">=0.1.2-alpha.3 <0.1.8 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.7-alpha.0 <0.1.8 || >=0.2.0-alpha.0 <0.3.0 || >=0.2.1-alpha.0 <0.3.0",
"@deepseek-ai/dsh-settings": "…same…",
"@deepseek-ai/dsh-subagent": "…same…",
"@deepseek-ai/dsh-tools": "…same…",
"@deepseek-ai/schemastery": "~3.18.4" // .volatile() needs 3.18.4+
},
"devDependencies": { …local copies of the four dsh-* packages + schemastery… } // no @deepseek-ai/dsh umbrella: it drags the whole host tree in
@deepseek-ai/dsh-desktop-runtime 0.2.1-rc.2 (Node v24) — real-machine verified, /skill-refine runs end to end.[major,minor,patch] tuple, so the plain <0.2.0 group does not cover 0.1.5-rc.1 or 0.1.7-rc.2, and the 0.2.0-alpha.0 group does not cover 0.2.1-rc.2 (its tuple is 0.2.1). test/entry.test.mjs pins this with a decision table, a counter-proof (reverting to the older range turns red), and a cross-check against the host's real semver.satisfies.settings.installSection was removed from dsh-settings. The namespace is the cordis row id, Config must be exported from the plugin module, and every live-editable field must be .volatile() (the host's _commitVolatile rewrites the live reference in place, no fiber restart). The settings card therefore hangs off configForms.get(ns) and the plugins.item slot.test/entry.test.mjs guards this (no install/postinstall/prepare, no optionalDependencies, dependency scope limited to @deepseek-ai/*).pnpm install # needs proxy/cache off the default npm cache on some hosts
npm test # entry + unit + smoke + client-smoke (all suites)
node test/entry.test.mjs # entry load, manifest, engines range, key-set parity, dep hygiene
node test/smoke.mjs # module/tool/whitelist smoke
node test/client-smoke.mjs # client bundle smoke (real component-tree execution)
All tests run inside a disposable DSH_HOME; no deployed instance is touched.
Verified against the DSH 0.2.1-rc.2 desktop runtime by comparing against its unpacked
asar sources and by exercising the plugin end-to-end. 0.2.1 packs several breaking
changes — clearing the compatibility gate is not enough on its own:
dsh-subagent no longer exports settleRun
from its package root (only settleRunResult; the implementation moved into
lib/types/run-settlement.js but is not reachable through the exports map).
A top-level import { settleRun } throws a SyntaxError at ESM link time, so
apply() never runs and the entire plugin silently dies — the host only logs a
single warning. Fix: inline settleRun (src/run-settlement.js), aligned statement
by statement with the host's semantics.subagents.start() was removed in favour of startActivation(spec) (whose spec
carries an extra request layer). Branch at runtime on "is the method present" rather
than betting on a version number. Note that startActivation's signal covers
admission only — a timeout must dispose() explicitly or the review hangs until the
built-in 15-minute self-timeout.request layer to the Agent Preset layer.
startLocal only writes composition: { agentPreset }, so locally spawned subagents
never see request.toolFilter; meanwhile dsh-tool-subagent throws outright on
config.toolFilter and points you at a preset. The plugin therefore declares its own
internal preset (skill-curator-review) mounting src/preset-guard.js, which calls
tools.restrict({ allow }) inside the subagent's own scope.restrict() validates names
through view(scope).restrictableNames, which only accepts the inheritance
surface; profile entries are created concurrently by Promise.all, and the preset
row injects only tools while this plugin also waits on settings — so the preset
races ahead, the tools are not registered yet, the call throws, and the registry writes
record.broken permanently ("A mount failure is final"). The preset stays broken
forever while the Loader row still shows active. Fix: the plugin calls
ctx.provide(TOOLS_READY_SERVICE, …) after registering its tools, and the guard waits
for it via inject.LlmAdapter base class gained imageRequestTarget. A duck-typed adapter
missing it throws a TypeError (same origin as imageRequestPricing); a guard test
caught this immediately.peerDependencies only. The decision function
(evaluatePluginCompatibility in dsh-app-boot) walks only the @deepseek-ai/dsh*
entries under peerDependencies and never reads dsh.engines.dsh (zero consumers
across the whole tree). The two must stay literally identical, which the tests enforce.includePrerelease: true,
which bypasses the prerelease-visibility rule, so prereleases of the upper bound
itself are admitted too (<0.1.8 admits 0.1.8-rc.1, <0.3.0 admits
0.3.0-alpha.0). Strict mode (used at pnpm install time) rejects them. The upper bound
therefore blocks stable releases, not prereleases; to reject prereleases as well,
write it as <0.3.0-0.set_plugin re-mounting does not always take effect. If a package's realDir
changed (for example right after pnpm install), ResolutionRouter.replace() throws
ResolutionRestartRequired and returns
restart-required / restartReason: "package-moved". A dsh restart is then
mandatory — and because a broken preset is written down permanently, re-mounting
will not rescue it either.MIT