DSH Plugins Marketplace

DSH Plugins

Plugins

/

Tools & Capabilities

/

dsh-ws-deflate

d

dsh-ws-deflate

Manifest valid

Enable permessage-deflate on DSH's WebSocket transport without modifying the DSH install tree.

hasBundlePatch

dsh-ws-deflate

English | 简体中文

Turns on permessage-deflate for DeepSeek Harness (dsh) web's WebSocket transport, without modifying the DSH installation. Measured on one machine: a session transcript that left the server as 1,206,822 B went out as 350,599 B — 70.9 % fewer wire bytes.

It is a runtime monkey-patch, not a supported extension point. Read "Should I install this" before installing it.

Should I install this?

Install it if you open dsh web from somewhere that is not your own machine — a Tailscale/VPN link, a reverse proxy over the internet, any path with real latency or packet loss. DSH replays the whole session transcript over /api/remote.mux on every reconnect, so on a lossy link those bytes are paid repeatedly.

Do not install it if you only ever use dsh web on localhost or a LAN. The saving is real but you don't need it there, and what you'd be taking on in exchange is a patch against another project's internals that can go quietly stale on every upgrade.

What it costs, measured on a Windows 10 / node v24 box: +10 ms to dsh startup (alternating 4-run measurement), and zlib level 6 at 42.7 ms per 1.53 MB of transcript. The compression is per-message above a 1 KiB threshold, so small events are untouched.

What it actually changes

Nothing on disk. ws@8.22.0, @deepseek-ai/dsh-api-gateway, and @deepseek-ai/dsh/lib were compared file-by-file (sha256) against the pristine registry tarballs: 63 files, 0 mismatches, 0 extra, 0 missing, and 0 files in the install tree written or created that day.

What it does change is one method, in memory, in the dsh process:

BEFORE  WebSocketServer.prototype.handleUpgrade = handleUpgrade(req, socket, head, cb) {   ← ws's own
AFTER   WebSocketServer.prototype.handleUpgrade = function (req, socket, head, cb) { try {  ← the wrapper
disk sha256 of ws/lib/websocket-server.js: 6d8dd89a841748c8 -> 6d8dd89a841748c8            ← unchanged

The wrapper sets this.options.perMessageDeflate at handshake time, which works because ws 8.x reads that option inside handleUpgrade (websocket-server.js:299/:303), not at construction — so it reaches the gateway's already-built new WebSocketServer({ noServer: true }) (dsh-api-gateway/lib/index.js:179). It only opts in when nothing else set the option, so if DSH enables compression itself upstream, this plugin steps aside automatically.

Why the option can't just be configured: DSH exposes no key for it. The webserver's compression is HTTP gzip and does not cover streams.

Install

dsh plugin --profile web add dsh-ws-deflate

Then restart dsh web. The bundle list is read at startup, so no restart means no effect.

Verify

grep 'dsh-ws-deflate' <wherever your dsh web logs>

You want three things:

[dsh-ws-deflate] permessage-deflate enabled -> …/node_modules/ws [ws 8.22.0] via path (level 6, threshold 1024 B, noContextTakeover on)
[dsh-ws-deflate] self test OK: this process negotiates permessage-deflate
[dsh-ws-deflate] negotiated permessage-deflate with Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) …

The self test is the interesting one. DSH's browser auth rejects non-browser handshakes, so you cannot probe a live server from a script — the plugin instead builds its own new WebSocketServer({ noServer: true }) on an ephemeral loopback port and does a real handshake against it. That proves this process, with this ws copy, negotiates, which is the claim no external probe can reach. The negotiated … lines then log the first few real browser handshakes (user agent truncated, maxLog caps it at 5, 0 disables).

Browser side, if you want it: DevTools → Network → the remote.mux request → Response Headers should contain Sec-WebSocket-Extensions: permessage-deflate; ….

Uninstall

dsh plugin --profile web remove dsh-ws-deflate

Restart. Nothing needs restoring, because nothing on disk was ever different.

Configuration

Add or edit the entry in your profile's cordis.patch.yml:

- id: ws-deflate
  name: dsh-ws-deflate
  config:
    enabled: true
    level: 6
    thresholdBytes: 1024
    noContextTakeover: true
    concurrencyLimit: 10
    selfTest: true
    maxLog: 5
keydefaultnotes
enabledtruefalse keeps the wrapper installed but opts no handshake in
level6measured here: level 6 → ratio 0.176, level 1 → 0.238 on the same payload, at 42.7 ms vs 5.9 ms per 1.53 MB
thresholdBytes1024below this, frames go out raw (the median DSH event is ~800 B)
noContextTakeovertrueindependent deflate stream per frame: bounded memory, and a dropped frame cannot corrupt later messages. Costs ratio, buys robustness on a lossy link — which is the whole reason to run this plugin
concurrencyLimit10cap on simultaneous compress operations
selfTesttruerun the loopback handshake at activation. Not hot-reloadable
maxLog5how many successful negotiations to log; 0 is silent

Everything except selfTest is .volatile(), so editing the patch and letting the profile hot-reload re-applies it without a restart.

How it finds the gateway's ws copy

A plugin cannot import 'ws' and get the gateway's copy. The profile is a pnpm tree that contains no ws at all; declaring it as a dependency installs a second copy — a different class object, and patching it does nothing. Three routes, tried in this order, all tested:

  1. Ask the process. The gateway's ws is in the CommonJS module cache; the class found there is provably the one the gateway uses (cached === path-required: true). Match on ws/lib/websocket-server.js, not ws/index.js — ws routes import to wrapper.mjs, which goes straight at the lib file, so index.js never enters the cache in an ESM host. Note lib/websocket-server.js does module.exports = WebSocketServer, so the export is the class.
  2. Guess the path. Honours $DSH_HOME (precedence: configured path > $DSH_HOME > ~/.dsh) and checks <home>/profiles/node_modules/ws, which dsh itself creates as a junction into the install tree — the most portable entry. Plus npm-global prefixes for Windows (%APPDATA%\npm), Linux (/usr/local, /usr/lib) and both macOS locations (/usr/local, /opt/homebrew).
  3. Retry for ~2 s. If neither finds anything, retry every 250 ms (unref'd, so it can never hold the process open). This is what covers an install whose prefix is not in the list: by the time the gateway has loaded, route 1 works. Tested by neutralising every candidate (DSH_HOME + APPDATA + argv[1] pointed at empty dirs), activating — nothing patched — then loading the gateway late: located ws on deferred attempt 2 … via require.cache, self test OK.

One thing that will confuse you, on purpose

import 'dsh-ws-deflate' from a plain node script fails with Cannot find package '@deepseek-ai/schemastery', even though the plugin loads fine inside dsh. That is not a bug to fix: dsh runs its own interception resolver (dsh-app-boot/lib/index.js:1459 scope === "installation", :1479 readPeerNames(...).has(name)) and supplies declared peers from the installation tree. A fresh profile has no schemastery anywhere in it — verified on two platforms — and the plugin still activates. So don't "verify" this plugin by importing it from a script; verify it by restarting dsh web and reading the log.

Tested on

platformnodedshwspnpmresult
Windows 10v24.20.00.2.0-rc.28.22.010.18.2live, 3 browser handshakes logged incl. macOS Safari
Linuxv22.19.00.2.0-rc.28.22.012.11.2clean-room: fresh profile from the shipped template, installed from a packed tarball, booted on :3099, self test OK

Both were installed from a local tarball, not from the registry. One combination per platform — treat anything else as untested. The structural guard is typeof WebSocketServer.prototype.handleUpgrade === 'function'; if a future ws changes that, the plugin warns and stays off rather than breaking the handshake.

Failure modes

  • No patchable WebSocketServer → one warning line, plugin still activates, compression stays off. dsh is unaffected.
  • apply() throws → warning only. Third-party plugin ids are not in dsh-app-boot's hardcoded requiredStartupEntryIds, so a plugin cannot stop dsh from starting.
  • A future DSH sets perMessageDeflate itself → its value wins, this plugin becomes a no-op. Delete it.
  • A DSH upgrade changes how the gateway builds its server → the plugin may find nothing and warn. Check the three log lines after every upgrade.

Exit condition

Upstream already wants this: discussion #6950 asks for "permessage-deflate on the session-stream WebSocket … compresses ~3–5×; no protocol change needed" (their measurement, 22.5 MB / 41,794 events; my ratio of 0.291 is 3.4×, consistent). When DSH ships this, delete this plugin. Until then it is an interim with a self-retiring behaviour, not a permanent fork of DSH's behaviour.

Security note

permessage-deflate is a compression feature and compression features have a BREACH-family history. Here the protected secret and the attacker-controlled input are both inside your own session transcript, on a link you already trust to carry your token in a URL query string. If you are running dsh web as plaintext HTTP on a public host, fix that first — it is a much bigger hole than anything this plugin touches.

Not affiliated with or endorsed by DeepSeek. MIT.

Comments

Loading…

Similar plugins

dsh-plugin-mobile-gateway

by Clarklevis1995

以websocket为通信方式的dsh网关插件,支持在同一网域内移动端的接入,实现移动端的dsh app

Tools & CapabilitiesManifest valid

★ 93

↓ 860/wk

MIT

JavaScript

Oct 17, 2026

dsh plugin --profile web add dsh-plugin-mobile-gateway

by TBChaos

把本机 / WSL / 虚拟机 / SSH 远端上另一套 DSH 的 WebUI 镜像进桌面版 DSH,用同一套界面和体验管理它们。DSH 插件,MIT。

Development & InfrastructureManifest valid

★ 0

MIT

TypeScript

Oct 17, 2026

dsh plugin --profile web add dsh-remote-desks

by kinderao

Phone and remote access to the DSH Web UI: scan a LAN QR code, or reach your PC from anywhere through a self-hosted relay server you run yourself instead of a cloudflared tunnel. Per-device credential

Tools & CapabilitiesManifest valid

★ 4

GPL-2.0

JavaScript

Sep 17, 2026

dsh plugin --profile web add dsh-pocket-relay

by godchen520

DSH 手机/外网远程访问插件:免配置公网隧道 + 局域网 HTTPS 直连 + 自定义公网链接/端口 + 微信机器人

UI & ExperienceManifest valid

★ 17

↓ 566/wk

MIT

JavaScript

Oct 19, 2026

dsh plugin --profile agent add dsh-web-remote

by fatatalia

dsh webhook 插件:HTTP webhook → 固定会话投递(异步队列),随 dsh web 启停

Manifest valid

★ 0

↓ 3/wk

JavaScript

Sep 24, 2026

dsh plugin --profile web add dsh-webhook

by Hongjun1-RGB

DSH 插件:在界面里拨一个开关,用手机浏览器继续当前会话(内嵌反向代理 + Cloudflare 隧道 + 微信推送)

Tools & CapabilitiesDevelopment & InfrastructureManifest valid

★ 1

MIT

JavaScript

Oct 15, 2026

dsh plugin --profile web add dsh-phone-access