dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Tool results are reclaimed before they enter history, so the prompt cache never goes cold.
⚠️ DSH 1.5 only (
>=0.1.5-rc.2 <0.2.0). Newer DSH releases will be supported in follow-up updates.
🟢 Collision risk: very low. It occupies 5 server-side seams and 0 client slots — measured alongside the 19 same-track plugins among the 44 installed here, with no conflict, and it takes no
chainslot (the only kind that makes plugins displace each other). See Compatibility.
English · 简体中文
A DSH session's context is not blown up by the conversation; it is blown up by tool results: a single read of several hundred lines, a single pwsh dumping a wall of logs, a system prompt repeated verbatim every turn. Once this content has entered the history, the only thing left is for the compactor to operate on it after the fact — and compaction has to rewrite already-committed history, which knocks out the whole server-side prefix cache and makes every compaction more expensive than the last.
dsh-context-reclaim moves to a different position: it handles them before the tool result is appended to history, and it never rewrites content that has already been committed. So the prefix cache is never invalidated because of this plugin.
It hooks into 5 seams:
| Seam | What it does | Default |
|---|---|---|
tools/post-execute | Purify a tool result the moment it is produced + bound it by content type, inserting a retrieval pointer at the truncation point | on |
system-prompt/assemble | Fold the tool definition surface, stabilize the order of prefix sections, truncate over-long description lines | on |
tools/pre-execute | Duplicate-edit interception (a second identical edit to the same content of the same file is rejected outright) | on |
session/event | Compaction-summary integrity audit (warns when a compaction summary is missing sections) | on |
llm/stream | One cost-attribution line per request, printing route / prompt / cache hit / output | on |
This is the core. Every time a tool call returns, the plugin first performs lossless purification, then bounding.
Collapses ANSI escape sequences, strips trailing whitespace, normalizes CR. This step is pure purification and loses no information.
It only acts when the content meets both thresholds: length ≥ 32 characters, and a reduction ratio after purification ≥ 0.2%. Small results are not touched at all.
Turn it off: config.noise = false, or the environment variable DSH_RECLAIM_NOISE=0.
Different types of tool results have completely different tolerances. The plugin sorts tools into three classes:
| Class | Tools covered | Head / tail kept | Per-line cap | Total character cap |
|---|---|---|---|---|
| Read-only | read grep glob codegraph_* lsp_* | 80 lines / 12 lines | 512 | 160 |
| Side-effecting | pwsh bash docker_* job_output | 40 lines / 40 lines | 1024 | 256 |
| Faithful | web_fetch web_search knowledge_* read_image pilot_snapshot | left exactly as-is | — | — |
Tools it does not recognize are treated as faithful — better to save nothing than to cause unintended damage.
Wherever content was truncated, a retrieval pointer is inserted telling you how much of the original is left and how it can be retrieved, rather than dropping it silently.
The trigger threshold for bounding is "saves more than 5%"; if it does not save much, nothing is touched.
Turn it off: config.bounded = false, or the environment variable DSH_RECLAIM_BOUNDED=0.
About oversized results: content above
config.maxInlineBytes(default 50000) is handed to the spill mechanism to be written to disk. This requires the spill plugin to be installed in your profile — without it, this threshold has no effect and over-limit content passes through unchanged.
The lists for all three tool classes can be replaced wholesale in the configuration:
- id: reclaim
config:
readOnlyTools: ['read', 'grep', 'glob'] # a full-table replacement, not an append
sideEffectTools: ['pwsh', 'bash']
faithfulTools: ['web_fetch', 'web_search']
Note that this is a full-table replacement, not a per-field merge — writing only headLines will lose the character caps. Either write the whole table or write none of it.
At the start of every session, DSH stuffs the complete definitions (name, description, parameter schema) of every available tool into the system prompt. With dozens of plugins installed, this block can account for a large proportion of it, while most of those tools will not be used at all in the current turn.
By default the plugin folds this part: it keeps the full definitions of the most frequently used tools and compresses the rest into a one-line list (name + one-line description, no parameter schema).
A folded tool is still callable. The one-line list only removes the schema from the prompt — the tool itself stays registered with the host, so the model can still find it with search_tools, call it straight away with invoke_tool, or make it resident again with load_plugin. There is no load_tools tool; search_tools + invoke_tool is the pair. If you would rather keep every schema in the prompt, turn the folding off (next line).
Turn it off: config.foldTools = false, or the environment variable DSH_RECLAIM_TOOL_FOLD=0.
A server-side prefix cache only hits when the prefix is byte-for-byte unchanged. Yet the system prompt inherently carries a pile of things that change every turn: token counts, timestamps, session sequence numbers.
By default the plugin flattens these volatile numbers and reorders the sections so that stable sections come first and volatile ones last. Each reorder prints one [reclaim/section-order] line in the log, telling you which sections were moved.
The system prompt occasionally contains a single exceptionally long description line (long comments written by some plugins). Lines above the threshold (not exposed in config; the environment variable DSH_RECLAIM_PROMPT_LINE_MAX, default 120 characters) are truncated, and the line numbers that were truncated are printed to the log.
If the model issues exactly the same modification to the same content of the same file twice in a row, the second one is intercepted and an explanation is returned — such a call is almost certainly a meaningless duplicate that burns a whole round trip for nothing.
Log line: [reclaim/dedup-deny].
Turn it off: the environment variable DSH_RECLAIM_DEDUP_DENY=0.
If the summary produced by the compactor is truncated by the output budget, it silently becomes shorter — you receive no notice at all, the next turn simply starts with a piece of the context missing.
After every compaction the plugin audits whether the summary contains the structural sections it should have, and warns when any are missing:
[reclaim/summary-audit] ⚠️ 摘要缺 3/8 段
It only warns, it does not modify the summary. What actually prevents summaries from being truncated is dsh-aux-degrade; see the interaction section below.
One line is printed at the end of every request:
[reclaim/usage] route=aux prompt=12043 hit=11890 miss=153 out=204 purpose=compaction
hit / miss are the prefix-cache hit and miss character counts, and purpose is the purpose of this call (if it has one). When the cache is broken, an extra [reclaim/cache-break] line is printed.
This log is the fastest entry point for diagnosing "why is my cache hit rate not high?".
dsh-llm patch requiredThis is the reason you can install the plugin and stop patching files.
On a stock @deepseek-ai/dsh@0.1.5-rc.2 install the main-path conversation request is deep-frozen by the host before it reaches @deepseek-ai/dsh-llm. Its entry point is:
stream(options) { return this.streamWithRegistration(options) }
streamWithRegistration(options, prepared) {
return this.ctx.waterfall(this, "llm/stream", options, () => this.adapterStream(options, prepared))
}
The waterfall's next is a closure that ignores its argument and the adapter reads the outer options. So a plugin listening on llm/stream can read the request but cannot rewrite it — writing to a frozen object throws, and passing a new object to next(...) does nothing at all. That is why the desktop build of DSH has always shipped with a local file patch to dsh-llm/lib/index.js whose entire body is six lines: copy the frozen request shallowly, copy the messages array, re-mark it with markAgentLoopRequest, and hand the copy to the waterfall.
Version 1.1.0 does the same thing from the plugin layer. On load, this plugin wraps ctx.llm.streamWithRegistration once:
messages array copy + every message object copied, then re-marked through markAgentLoopRequest taken from the host's own dsh-llm module, and the copy enters llm/stream;Because of this, an unpatched install gets a writable request without a single file being touched. Measured A/B on a stock runtime that carries no patches at all (dsh --profile headless, real request, [reclaim/wire] line):
# takeover on (default)
[reclaim/wire] 入口探测:messages=5 条 · options 冻结=false · messages 冻结=false · route=deepseek-official|deepseek-flash|main|ba8a6419
# takeover off (DSH_RECLAIM_WRITABLE_REQUEST=0)
[reclaim/wire] 入口探测:messages=5 条 · options 冻结=true · messages 冻结=true · route=deepseek-official|deepseek-flash|main|d7d22e75
Turn it off with DSH_RECLAIM_WRITABLE_REQUEST=0. The startup log tells you exactly what happened:
[reclaim/writable] 已接管 llm.streamWithRegistration:冻结的主请求先浅拷贝再进 llm/stream ⇒ 不再需要给官方 dsh-llm 打文件补丁(可用 dsh-llm 副本 2 份)
markAgentLoopRequest writes into a WeakSet that lives inside one module instance, and Node creates a separate instance for every distinct module URL. Marking a copy with a foreign instance is useless: the object simply is not in the set your other plugins check. So this plugin
process.argv (the real …/@deepseek-ai/dsh/lib/bin.js — on the desktop shell it sits in argv[2], not argv[1]), from createRequire resolution of the host entry, and from $DSH_HOME/profiles{/*}/node_modules;isAgentLoopRequest(options) returns true is the host's own instance and is used;@deepseek-ai/dsh-session-title may stop auto-titling sessions, because it checks the same WeakSet. This has been observed on one npm-style install layout and not on the desktop layout. Nothing else in DSH consumes that marker.The takeover is skipped automatically if an invariants service is present (the dsh-agent-loop frozen-contract assertion would otherwise abort your session). No shipping 1.5 profile loads it, but the plugin steps aside rather than breaking a session if it ever appears.
On load the plugin inspects the official patch targets it can locate and prints exactly one line:
[reclaim/patch-check] 官方补丁体检(只读,本插件不会自动改官方文件):7 个目标里未检测到 7 条 —— dsh-llm 可写请求、agent-loop 快照回收 / 系统提示收敛、…
It is deliberately read-only. This plugin is not a patcher: modifying files inside your DSH installation is your decision, not a side effect of installing a plugin. The check is a heuristic (it looks for the *.orig backups a patch script leaves next to the file it touched), so it says "not detected" rather than "not patched", and it stays completely silent when it cannot work out your install layout. Silence it with DSH_RECLAIM_PATCH_CHECK=0.
When something is missing and you do want it, do not hunt for a script — hand the job to your own agent. The file shipped inside this package, tutorial-install-and-official-patches.md (Chinese original: tutorial-install-and-official-patches.zh-CN.md), contains a ready-to-paste prompt describing each patch, its target, and its acceptance check; your agent reads it and patches your machine. That is the recommended route, because your agent can see your actual installation while a hard-coded script inside a plugin cannot.
The patch library — all ten official patches, published separately → https://github.com/xiamubumu/dsh-patch-library The patches themselves, an adaptive applier, five ways to apply them and a one-command revert.
node apply.mjs --checkis a read-only health check,--applybacks every file up and runsnode --checkon each one,--revertputs everything back.
Stated plainly, because installing these plugins does not make the other file patches unnecessary:
| Official 1.5 file patch | Edits | Can a plugin replace it? |
|---|---|---|
@deepseek-ai/dsh-llm — writable request | lib/index.js | ✅ Yes — done by this plugin, see above |
@deepseek-ai/dsh-client-ui-sidebar — flex-wrap on the footer slot | lib/lib-client.js | ✅ Yes (a client plugin can inject CSS); not shipped here |
@deepseek-ai/dsh-agent-loop — runtime-snapshot reclaim, system-prompt convergence | lib/index.js | ❌ No — needs the loop's private projection class and its commit point |
@deepseek-ai/dsh-llm-pi-ai — splitSystemPrompt collects every leading system message | lib/index.js | ❌ No, and it is pointless without the dsh-agent-loop patch |
@deepseek-ai/dsh-compaction-tool-result-pruner — result pruning + load-bearing guard | lib/index.js | ❌ No — the host instantiates the exported class directly; there is no registration seam |
@deepseek-ai/dsh-client-ui-renderer — conversation.chat.turnTail chain election | lib/client.js | ❌ No — the official client bundle itself must be edited |
@deepseek-ai/dsh-session-format-v1-to-v2 — turn/start repair | lib/index.js | ❌ No — runs inside the session restore path |
@deepseek-ai/dsh-session-persistence-jsonl — the same, persistence side | lib/worker.cjs | ❌ No — runs in a worker thread |
@earendil-works/pi-ai — max_tokens floor | dist/api/simple-options.js | ❌ No — third-party dist, its ESM namespace is read-only |
In short: the one patch that these plugins had to depend on is gone. The rest are independent improvements that change how DSH's own machinery behaves, and they remain optional. To apply any of them, use the patch library: https://github.com/xiamubumu/dsh-patch-library.
The official patches this plugin benefits from are announced by this plugin itself, in one dialog. No other plugin speaks for it, and it never speaks for another.
node apply.mjs --check then node apply.mjs --apply --only <id>; C apply by hand, cd <package> && git apply -p1 <library>/patches/<id>/change.diff;*.orig backup next to each target file plus a content hash against the library manifest (pristine / patched). When neither can decide, it says "cannot tell" outright and never guesses "applied";The same dialog also lists the other official patches (owned by no plugin — apply them by symptom), so nothing goes unseen.
The configuration file is the profile's cordis.patch.yml:
- id: reclaim
config:
enabled: true
noise: true
bounded: true
stripLines: false
noOpGuardRatio: 0.05
maxInlineBytes: 50000
attribution: true
trimSections: false
foldTools: true
activePlugins: []
| Key | Type | Default | Effect |
|---|---|---|---|
enabled | boolean | true | Master switch (!== false counts as on) |
noise | boolean | true | Lossless purification (ANSI / trailing whitespace / CR) |
bounded | boolean | true | Bounding by content type + retrieval pointer |
stripLines | boolean | false | Strip line-number prefixes |
noOpGuardRatio | number | 0.05 | The "only act if it saves enough" threshold for bounding, 5% |
maxInlineBytes | number | 50000 | Above this byte count, content goes to spill (spill plugin required) |
readOnlyTools | string[] | see the table above | Read-only tool list, a full-table replacement |
sideEffectTools | string[] | see the table above | Side-effecting tool list, a full-table replacement |
faithfulTools | string[] | see the table above | Faithful tool list, a full-table replacement |
attribution | boolean | true | Print the [reclaim/usage] attribution line |
trimSections | boolean | false | Section trimming (note: the real gate is the environment variable DSH_RECLAIM_SECTION_TRIM, see below) |
foldTools | boolean | true | Fold the tool definition surface |
activePlugins | string[] | [] | Plugin list participating in section-ownership computation |
⚠️ The value of the
trimSectionsoption does not actually open the gate — the gate in the code only honours the environment variableDSH_RECLAIM_SECTION_TRIM, and it additionally requiresactivePluginsto be non-empty. The old documentation got this one wrong; it is stated accurately here.
There are more than 40 environment variables in total, covering every internal threshold. Grouped by function, the most commonly used ones are:
| Variable | Default | Effect |
|---|---|---|
DSH_RECLAIM_NOISE | 1 | Set to 0 to turn off lossless purification |
DSH_RECLAIM_BOUNDED | 1 | Set to 0 to turn off bounding |
DSH_RECLAIM_STRIP_LINES | 0 | Set to 1 to strip line numbers |
DSH_RECLAIM_TOOL_FOLD | 1 | Set to 0 to turn off tool definition surface folding |
DSH_RECLAIM_PROMPT_LINE_MAX | 120 | Truncation threshold for a single system-prompt line |
DSH_RECLAIM_DEDUP_DENY | 1 | Set to 0 to turn off duplicate-edit interception |
DSH_RECLAIM_SECTION_TRIM | 0 | Set to 1 to turn on section trimming (works together with activePlugins) |
DSH_RECLAIM_STABILIZE | 1 | Set to 0 to turn off prefix stabilization |
DSH_RECLAIM_WRITABLE_REQUEST | 1 | Set to 0 to stop taking over llm.streamWithRegistration (see section 8); the official dsh-llm patch is only needed if you turn this off |
DSH_RECLAIM_PATCH_CHECK | 1 | Set to 0 to silence the one-line "official patch check" notice printed at startup |
DSH_RECLAIM_LOG | 1 | Set to 0 to turn off this plugin's log output |
DSH_RECLAIM_DUMP_SYSTEM | 0 | Set to 1 to dump the assembled system prompt to a temporary directory, for diagnosis |
The remaining threshold variables (sampling line counts, budget observation, pollution-detection window, archive limits, reasoning stripping, etc.) each have a per-item comment at the top of the source.
| Document | What it covers |
|---|---|
| Install & official patches (English) | How to install (two verified routes) and what to do about the official patches — including prompts that let your own agent apply them the patches themselves ship as a library: https://github.com/xiamubumu/dsh-patch-library |
| 安装与官方补丁(中文) | the same, in Chinese 补丁本体已开源成补丁库:https://github.com/xiamubumu/dsh-patch-library |
Each plugin installs on its own; together they save the most. The other three:
dsh-aux-degrade — Auxiliary-call degradation: summaries and titles stop burning their budget on reasoningdsh-capability-composer — Capability composer: switchable tool/skill/MCP sets instead of one bloated surfacedsh-composer-launcher — Launcher: one panel for compositions, plugins and MCP, plus the extension walkthroughAll of them are DSH 1.5 only; newer DSH releases will be supported in follow-up updates. Plugins that need official patches announce them in the UI once you install them — nothing goes unseen.
DSH_HOME)dsh plugin --profile web add github:xiamubumu/dsh-context-reclaim
One command. DSH registers it into the profile's dsh.profile.bundles by itself; no config to hand-edit.
Download dsh-context-reclaim-1.1.0.tgz from Releases, then:
dsh plugin --profile web add /path/to/dsh-context-reclaim-1.1.0.tgz
The marketplace submission (an awesome-dsh-plugin PR) has not been filed and nothing is published on npm yet ⇒ neither route works today.
Once it is listed/published, installing from the marketplace, or dsh plugin --profile web add dsh-context-reclaim, becomes valid.
⚠️ Known issue: do not feed the release asset's tarball URL to
dsh plugin add.dsh plugin --profile web add https://github.com/.../releases/download/v1.1.0/....tgzfails on the pnpm DSH bundles (10.34.5) withERR_PNPM_MISSING_TARBALL_INTEGRITY(pnpm#14351, fixed upstream only in pnpm ≥ 12.5.1). Installing a second plugin that way always fails — use the two routes above instead.
After installing you need to restart DSH — host-side plugins are only loaded when DSH starts.
This plugin has no runtime dependencies at all; peerDependencies only declares the official @deepseek-ai/dsh-tools. Installation will not stuff any third-party package into your profile.
**This plugin hook