← Back to home@moxingovo

dsh-sidebar

Unofficial community extension. Claude Code-style native DeepSeek Harness sidebar for VS Code: self-written chat UI (no iframe) reusing the existing dsh web service — workspace-synced sessions, permission/model/reasoning pickers, context ring. / 非官方社区扩展:VS Code 里的 Claude Code 风格 DSH 原生侧边栏,自写聊天 UI,复用本机 dsh web 服务;工作区会话同步、沙箱权限/模型/推理档、上下文占用环。

Stars
1
Language
JavaScript
Created
Aug 14, 2026
Updated
Oct 6, 2026

Introduction

DSH Sidebar

English | 简体中文 · Changelog · Releases

A Claude Code-style native DSH sidebar for VS Code: a self-written native front-end (no iframe) that reuses your existing dsh web service (127.0.0.1:3080 by default) and ~/.dsh — no second gateway, no server changes.

Unofficial community extension. Not affiliated with DeepSeek.

Harness compatibility: 0.6.x speaks the 0.1.6 wire (Typert API Gateway: cookie-authenticated /api/<ns>/<method>, one remote.mux WebSocket, durable events plus process-local assistant frames). Verified on 0.1.7-rc.2 and on 0.2.0-rc.2 — the runtime the official Desktop app bundles — as well: 0.1.7 only adds uplink frames (item / end) and typert uplink types, and 0.2.0 changed no wire at all (packages/typert/* source is byte-identical), so no protocol change was needed. For DeepSeek Harness 0.1.0–0.1.5 use release 0.5.0; the two wires are not interchangeable.

Running next to the official Desktop app: the Desktop shell bundles its own dsh runtime and serves it on 19387, keeping its credentials inside Electron. This extension does not attach to it — it keeps its own server on dshWeb.port (default 3080) as before, so expect two servers while both are open. Pointing dshWeb.port at 19387 is not supported: the extension cannot read the Desktop app's launch token.

Both hosts share ~/.dsh, which is the one thing to be careful about: two hosts cannot hold the same conversation at once — the second one reports that the session is already in use. So keep passive spawning off while letting the panel still work when you ask for it:

{
  "dshWeb.spawnIfMissing": false,   // never spawn behind your back
  "dshWeb.startWhenOpened": true,   // but opening the panel may start one
  "dshWeb.checkout": "",            // rely on `dsh` on PATH, or npx
  "dshWeb.command": "C:\\...\\DeepSeek Harness\\resources\\runtime\\cli\\bin\\dsh.cmd"
}

0.6.6 split the old single knob into two, because "don't spawn automatically" and "don't start when I open it" are different wishes:

SettingAnswers
dshWeb.spawnIfMissingmay a server start passively — on activation, or from a background health retry?
dshWeb.startWhenOpenedmay opening the panel start one?

With spawnIfMissing: false nothing comes up behind your back: VS Code starting, or VS Code restoring a panel you left open, only attaches to a server that is already there. An intentional act, however — clicking the DSH activity-bar icon, the toggle command, the panel's retry button, or "DSH: restart server" — starts one when startWhenOpened is on. That is the difference between a sidebar that resurrects a server you just stopped and one that simply works when you open it.

DSH Sidebar: a Claude Code-style DSH sidebar inside VS Code (screenshot)

The screenshot is served through the jsDelivr CDN because GitHub's own image host (raw.githubusercontent.com) is unreachable on some networks — e.g. mainland China. Source file: media/demo-panel.png.

  • Look (Claude Code style): the header shows the open conversation's name with only two round buttons at its right — session list and new chat; the bottom function area is one rounded card with a DeepSeek-blue ring and a divider between the input and the toolbar, with pills / send button / context ring scaled to match.
  • Blank sessions & drafts: a new conversation you leave without sending anything does not occupy a list slot; if you typed something there it stays (drafts are kept per conversation and never leak into another one), and the next "new chat" reuses that empty conversation instead of piling up another one.
  • Entry points (same as Claude Code): the DeepSeek Harness icon (DeepSeek blue) in the top-right auxiliary bar — click to summon the chat panel; the status-bar DSH item shows server state and toggles the panel; Ctrl+Alt+D.
  • Sessions: current-workspace sessions only — create / switch / archive / rename / fork; the context meter shows real server-side token data.
  • Model & preset: model + reasoning-effort pickers; preset switching is blank-session-only (locked once the conversation starts — a server constraint).
  • Capabilities: streaming replies, stop, tool cards / approval cards / todos / timeline, image attachments (vision), /compact, Markdown + code blocks.
  • Protocol: 0.1.6 Typert gateway — POST /api/<ns>/<method> with named args, a browser-session cookie minted from the launch token, and one /api/remote.mux socket carrying every stream (session follow, workspace baseline, $events) — see docs/protocol.md.

Install

From a released .vsix:

code --install-extension dsh-webview-0.6.2.vsix

Or build it yourself (run in the repo root):

npx @vscode/vsce package
pwsh -File test\fix-vsix.ps1   # repairs vsce's UTF-8 mangling of package.json
code --install-extension dsh-webview-0.6.2.vsix

⚠️ Known issue: on some Windows environments vsce package re-encodes the Chinese text in package.json as GBK mojibake and can even break the JSON. Always run test\fix-vsix.ps1 after packaging.

Zero-config launch

On startup the extension probes dshWeb.port (default 3080) and attaches if a dsh instance responds. Otherwise it starts one, trying in order: dshWeb.command → dshWeb.checkout → dsh on PATH → npx @deepseek-ai/dsh. The server runs with cwd = the first workspace folder and DSH_HOME pinned to ~/.dsh (identical to attach — never isolated).

Settings

SettingDefaultMeaning
dshWeb.port3080Port to attach to or start on
dshWeb.attachExistingtrueReuse a running instance instead of starting a new one
dshWeb.spawnIfMissingtrueStart a server when none is running
dshWeb.checkout"" (auto)Optional checkout path (launches apps/cli/lib/bin.js)
dshWeb.command""Full command override, e.g. pnpm dsh
dshWeb.extraArgs[]Extra arguments, e.g. --trusted-host
dshWeb.attachWaitSeconds30Seconds to wait for a launcher before starting a server of our own
dshWeb.takeoverAfterSeconds45Seconds an attached server may stay silent before we take the port over
dshWeb.nodeMaxOldSpaceMb8192--max-old-space-size for the node we launch (0 = Node's default)
dshWeb.nodeArgs[]Extra flags for the node executable itself (checkout launcher)
dshWeb.followWorkspacetrueRestart self-started server when the first folder changes
dshWeb.stopOnExittrueStop a self-started server when VS Code exits

Troubleshooting: Output → DSH (logs connection and protocol traffic).

Troubleshooting: missing top-right icon / persistent "Chat" tab

Two independent root causes, both fixed by the bundled one-click fix-dsh.cmd:

Cause 1: the extension scan cache points at a deleted old-version folder

VS Code caches its extension scan in .vscode\extensions\extensions.json. If a new version is installed by deleting the old folder, VS Code still looks for the old path at startup → ENOENT → the extension is marked broken and the new version in the same folder is never discovered (hence no icon).

Fix: test\fix-cache.js — rewrites the cache entry to the new path, drops the profile-level scan caches (forcing a full rescan), and repairs the placeholder icon path. Backups are created automatically.

Cause 2: auxiliary-bar container icons / Chat tab persistence live in global storage

VS Code 1.136 stores auxiliary-bar container icons (title-bar / right-edge strip) in global storage workbench.auxiliarybar.pinnedPanels; the Chat tab's persistence lives there too. Patching only the per-workspace state is ineffective, and edits made while VS Code is running get overwritten on exit.

Fix: test\fix-state.js — removes Chat from the global pinned list, registers dsh-aux, hides the Chat view across all workspace DBs, and backs up every state.vscdb (.bak-dsh); idempotent.

Usage

  1. Fully exit VS Code (all windows, including minimized);
  2. Double-click fix-dsh.cmd on the Desktop (it refuses to run while VS Code is open);
  3. Reopen VS Code → the blue harness icon appears top-right, Chat is gone.

Development

No build step: plain JS, checked with node --check plus a set of regression scripts (test/*.js; 10 suites carry assertions, 227 checks in total right now).

node test/respond-wire-verify.js        # approval/question answer wire shape (18 checks, mock 0.1.6 gateway)
node test/session-list-filter-verify.js # session-list filtering (workspace / subagent / archived) (13 checks)
node test/blank-session-verify.js       # blank-session lifecycle + per-session drafts + reuse cache + failed-send restore (31 checks)
node test/panel-fixes-verify.js         # markdown escaping, queue rendering, archive set, projections, attachments, paging (22 checks)
node test/header-composer-verify.js     # header & composer structure + style contract (34 checks)
node test/approval-card-verify.js       # approval card rendering + dedup (36 checks, jsdom)
node test/live-sync-verify.js           # live event folding (8 checks)
node test/pill-menu-verify.js           # pill menu lifecycle + listener leaks (11 checks)
node test/spawn-verify.js               # launch chain (checkout / dsh CLI / npx) (jsdom)
node test/command-quote-verify.js        # dshWeb.command 的引号处理(带空格路径)
node test/panel-open-verify.js        # 打开面板时会不会把服务拉起来(复刻真实配置)
node test/reminder-hide-verify.js     # 注入的系统提醒(技能清单)不应渲染成用户消息
node test/launch-flags-verify.js        # launch argument assembly (15 checks)
node test/attach-paste-verify.js        # image attachment paste/drop (29 checks)
node test/real-launch-verify.js         # spawns the real dsh web server (needs a local checkout; manual)

The jsdom-backed suites need DSH_CHECKOUT_NODE_MODULES pointing at a node_modules that has jsdom.

approval-card-verify.js runs the real webview/app.js in jsdom and drives it with the same host messages the extension sends. It pins the field-name contract behind the duplicate-card bug: session-log events carry the approval id as data.id, while server-request frames carry it as approvalId; reading only the latter makes the id undefined, so the log card and the replayed frame render as two identical cards and approval/decided never settles either.

respond-wire-verify.js is the regression guard for the sidebar's permission and question buttons: it stands up a fake 0.1.6 gateway, activates the extension against it, pushes an approval/* and a question/* waterfall into the sidebar webview, and asserts the answer frame that goes back out — POST /api/$events/result carrying the opening clientId and the waterfall eventId. The payload is validated against approvalResponsePayloadSchema / questionResponsePayloadSchema, both of which also require sessionId; a wrong shape is rejected with {accepted:false} and surfaces to the user as server rejected response to undefined.

License

MIT — see LICENSE.