dsh-gui
August 29, 2026 · View on GitHub
A thin desktop shell for the DeepSeek Harness
web UI. It is not a repackaged harness — it runs the harness in place from
the deepseek-harness git submodule, hosts the web server itself, and shows the
full harness UI in a single webview window. Nothing else is drawn: just the OS
title bar and border.
What it does
On launch the entry exe:
- Spawns
node deepseek-harness/apps/cli/lib/bin.jswithweb --port <port> --no-open, and pinsDSH_HOMEto./.dshinside this repository. - Waits until the harness answers
GET /with200on127.0.0.1:<port>. - Opens one frameless webview window that renders a custom title bar and embeds the harness web UI in an iframe. On exit it tears the harness process tree down; the harness runs inside a kill-on-close Windows job object, so the kernel enforces that teardown even when dsh-gui itself is killed (Task Manager, closing the terminal it was launched from).
The ui/ page is a thin shell only: a drag-anywhere title bar with
connection tabs (VSCode-style, one per connected DSH backend), a +
new-connection button, and on their inner side a hamburger (☰) menu — the
standard minimize / maximize / close controls come after it, and the hamburger
menu carries the tab list plus 新建连接 / 关闭当前连接 and the existing
关于 / 退出 entries. The new-connection dialog supports a local backend
(probe the port, or start the built-in harness on it) and a remote backend
(VSCode Remote SSH style: load the frontend, or SSH-deploy it to another host,
reaching it over an SSH local port forward). The connection chrome lives in the
native title bar — the embedded harness page renders none of it. The About
dialog shows the version (exact git tag, else the commit short hash), license,
and GitHub link for dsh-gui, the deepseek-harness submodule, the whale-girl
app-icon submodule, and every plugin package under plugins/. The harness UI
itself goes on talking to its self-hosted server over plain HTTP exactly like a
browser.
Screenshot

Features
GUI shell
- Frameless single-window shell — a drag-anywhere custom title bar with native minimize / maximize / close controls; the title bar colors follow the light/dark theme reported by the embedded harness page.
- Native Windows 11 title-bar interactions — hovering maximize shows the OS Snap Layouts flyout, double-clicking the title bar toggles maximize/restore, double-clicking the top border vertically fills/restores the window (including drag-to-restore), and the top-left app icon opens the window-control menu.
- VSCode-style connection tabs — one tab per connected DSH backend, with close buttons and persisted tab/active state; closing the last tab reopens the new-connection dialog.
- New connection (
+) — local backend (probe the port, or start the built-in harness on it) and remote backend (load the frontend, or SSH-deploy it to another host and reach it through a loopback-only SSH local port forward). - Hamburger menu (
☰) — tab list,新建连接/关闭当前连接,检查更新,关于/退出. - About dialog — version (git tag or commit short hash), license, and GitHub link for dsh-gui, the harness, the icon submodule, and every plugin.
- Update dialog — checks every in-repo project for updates, lets you choose
which ones to update and their target (latest commit / latest tag), then
exits, updates in a console window, and restarts. The dialog recommends
AI-sync updates: each updatable row and the bottom bar offer an AI 更新
button that returns to the project home screen, selects the dsh-gui
directory there, and prefills a prompt that asks the agent to sync the
submodule(s) to the chosen target and run the install scripts (the bottom
button lists every updatable module with its combo choice). The agent preset
is left for you to pick. Each behind row also has an 更新日志 button:
for a tag target it shows the official GitHub Release notes when the remote
is a GitHub repository and a release exists, otherwise it asks the dsh AI to
summarize the commits between the current checkout and the update target —
through the running harness's raw-LLM route (the dsh-ai-update plugin,
default model from the Models page), which creates no session, so the run
never appears in the DSH session list; when that route is unavailable the
shell retries it once and then falls back to the one-shot headless mode
(
dsh --profile headless), whose session store is redirected to a temp directory — neither path persists a session. The markdown result renders in a scrollable, selectable dialog whose copy button copies the raw source text.
Plugins
dsh-remote— multi-backend remote mode: the connection tabs, new-connection dialog, local backend start, and VSCode Remote SSH-style deploy/tunnel described above (seeplugins/remote/dsh-remote/docs/).dsh-flowglass— turn the current session into a live flowgraph (three lanes, subagent branches, parallel groups, drill-down) plus a hot-reloadable session toolbox drawer (seeplugins/better-sidebar/dsh-flowglass/README.md; installed by the better-sidebar wrapper, after dsh-better-sidebar).dsh-sidebar-qa— select conversation text → right-panel follow-up question in a dedicated same-workspace session (❓追问·<主题>), without interrupting the main conversation; thin consumer ofdsh-better-sidebar(seeplugins/better-sidebar/dsh-sidebar-qa/README.md; installed by the better-sidebar wrapper, after dsh-better-sidebar).dsh-better-sidebar— service-first sidebar workbench (right sidebar- bottom panel): per-session explorer, CodeMirror editor + file viewers
(image/PDF/Markdown/HTML/code), real terminal (xterm.js + node-pty with
reconnect replay; optional
terminal_*model tools, off by default), Git panel, embedded browser, and background-job page (seeplugins/better-sidebar/DSH-better-sidebar/README.md).
- bottom panel): per-session explorer, CodeMirror editor + file viewers
(image/PDF/Markdown/HTML/code), real terminal (xterm.js + node-pty with
reconnect replay; optional
dsh-terminal— VSCode-style integrated terminal panel (xterm.js + node-pty):Ctrl+`toggle, resizable panel under the composer, multiple terminals with a dropdown, and new/close terminal controls. ⛔ Temporarily masked bydsh-better-sidebar(its installer skips installation).dsh-file-explorer— right-side resizable file tree (260–900 px) with recursive search, Markdown/syntax-highlighted preview, in-panel editing, and one-click VS Code open (seedocs/plugins/dsh-file-explorer.md). ⛔ Temporarily masked bydsh-better-sidebar(its installer skips installation).dsh-review—/reviewslash command for code review of uncommitted changes, a commit, a branch diff, a PR URL/number, or a custom request (seeplugins/review/dsh-review/README.md).dsh-ai-update— browser-half bridge behind the update dialog's AI update buttons: receives the shell'sdsh-gui:ai-updatemessage, returns to the new-session home, selects the dsh-gui workspace, and prefills the composer with the update prompt — never creating a session directly and never picking a preset (seeplugins/ai-update/dsh-ai-update/docs/).dsh-deep-whale— whale-girl skin series; currently ships the hot-pluggablemaid-atelierskin (light/dark palace backgrounds, navy lace UI overlay, Q-version sidebar, embedded assets).dsh-routing-suite— the runtime plugin injector (dsh-super-injector:dev_*tool family, hot reload, staging-promote/uninject, plugin management UI), installed from thedsh-routing-suitesubmodule (seeplugins/routing-suite/README.md).
Agent presets
review(审阅模式) — review-focused coding agent built from the opencode review prompt; reviews changes and replies in the user's language.anchored-standard— experimental two-stage preset: the Minimal pair (bash+str_replace_editor) for the first request, then a minimal resident toolset after the first persisted tool call or reply; its install script also injects the platform-specific instruction hint (Windows CRLF and ripgrep), promotes the Windows shell topwsh, and denies the promoted session'sbashat runtime via an agent-scopetools.restrict()(lifted again when compaction returns the session to the controlled phase).liangshen(梁神模式) — the dsh-web-ui distribution of the two-stage anchored idea: phase 1 exposes the Minimal pair and quarantines injected context, then the wire switches to Code Mode (PTC) after the anchored promotion. Installed byplugins/dsh-web-ui/install.mjsas the@linxin666/dsh-liangshennpm plugin (@latest), which also mountsdsh-petand thedsh-web-ui-settingscompatibility bridge; the plugin itself syncs the preset into.dsh\.agent-presets\liangshenat startup (the preset ships its own Windows custom-bash), so no manual preset copy is needed. No other dsh-web-ui npm plugin is installed.router-standard(Router Standard (experimental)) — task-aware reasoning-mode routing (spec/react/weak bands, self-optimization toolsdev_router_status/dev_router_mode); installed byplugins/routing-suite/install.mjsalongside its deep-think-first variantrouter-spec(Router Spec (experimental)). Both presets come from thedsh-routing-suitesubmodule and land in.dsh\.agent-presets\, matching the suite README's manual install step.

Requirements
- Windows 10/11 with the WebView2 Evergreen runtime (ships with most modern Windows / Edge installs).
- Node.js
^22.19 || >=24and npm onPATH(used by the build tooling and by the shell to launchbin.js). - Rust toolchain (
rustc/cargo) for the entry exe.
Everything fetched at build time (npm packages, the pnpm store, cargo crates) lands inside this repository or the standard local caches — no global install of the harness is ever performed.
The tooling is a single cross-platform Node CLI (scripts/dsh-gui.mjs) exposed
through npm scripts, so the same commands work on Windows, macOS, and Linux
(WSL).
Layout
dsh-gui/
├─ deepseek-harness/ # git submodule: the harness checkout (built in place)
├─ package.json # npm scripts: setup / build / install:plugins / start / ...
├─ dsh-gui.exe # the Tauri shell entry binary (Windows; `dsh-gui` on
│ # Linux/macOS — build scripts copy it here)
├─ src-tauri/ # the Tauri shell (Rust); cargo output at target/<profile>/
│ ├─ ui/ # the frameless shell page: title bar (connection tabs,
│ │ # + button, hamburger menu, window controls), the
│ │ # new-connection dialog, and the About dialog
│ └─ whale-icon/ # git submodule: the DeepSeek Harness whale-girl icon pack
│ # (DeepSeekHarness-WhaleGirl.ico is the app icon)
├─ scripts/
│ └─ dsh-gui.mjs # the cross-platform CLI behind every npm script
├─ presets/ # agent preset sources: each presets/<id>/ directory owns
│ # its install.mjs; the build installs every preset into
│ # .dsh/.agent-presets/ (see presets/README.md)
├─ plugins/ # plugin wrappers, preset-style: each plugins/<id>/ owns an
│ # install.mjs plus the plugin package/repo checkout
│ # (remote/dsh-remote in-tree; terminal/dsh-terminal,
│ # file-explorer/dsh-file-explorer, better-sidebar/
│ # DSH-better-sidebar, deep-whale/dsh-deep-whale,
│ # dsh-web-ui/dsh-web-ui and routing-suite/dsh-routing-suite
│ # are git submodules; dsh-web-ui installs dsh-liangshen
│ # + dsh-pet + dsh-web-ui-settings from npm, and
│ # routing-suite installs dsh-super-injector
│ # + the router-standard/router-spec presets;
│ # see plugins/README.md)
└─ .dsh/ # (runtime, gitignored) harness home: profiles/plugins/sessions
Build (one shot)
npm run setup
This is idempotent and fully repo-internal:
- Bootstraps pnpm 11.7.0 into
.toolchain/(the exact version the harness pins), so a system pnpm is not required. - Runs
pnpm install --store-dir .pnpm-store --frozen-lockfileinsidedeepseek-harness/, so the package cache lives at.pnpm-store/in this repository — not in a global store. Both.toolchain/and.pnpm-store/are gitignored. - Builds the harness (
pnpm run build: host lib + webdist/). - Compiles the entry exe with
cargo build --release(release by default) and copies it to the repository root (dsh-gui.exeon Windows,dsh-guielsewhere). - Runs every plugin install script under
plugins/— eachplugins/<id>/install.mjsnormally builds, installs, and mounts its plugin package into the web profile;dsh-web-ui/install.mjsinstalls thedsh-liangshen,dsh-petanddsh-web-ui-settingsplugin bundles from npm@latestthrough the same profile pipeline (see Adding plugins). - Runs every agent-preset install script under
presets/— eachpresets/<id>/directory lands in.dsh\.agent-presets\<id>\and appears on the preset roster (seepresets/README.mdfor the pattern).
The result is the entry binary at the repository root (cargo keeps its own
output at src-tauri\target\release\ or target\debug\).
Flags (pass after --): --debug for a cargo build debug build,
--skip-harness to skip the harness install+build, --skip-exe to skip cargo
entirely (harness/plugins only — useful on Linux without Tauri system deps).
Example: npm run build -- --debug.
Packaging/installer generation is intentionally disabled (
bundle.active: falseinsrc-tauri/tauri.conf.json). The app always runs from this checkout, which is what lets yougit pullthe submodule or add plugins and pick them up on the next launch.
Run
npm start
# or double-click the entry binary at the repository root
npm start launches the entry exe detached: the terminal returns immediately
and closing it never kills dsh-gui (or its harness child). The launcher and
entry exe both pin DSH_HOME to this checkout's .dsh. Startup refuses an
already occupied loopback port and, on Windows, also verifies that the listener
belongs to the Node child it just spawned, so an unrelated dsh web process
can never be mistaken for this checkout's backend.
To run only that same harness backend in the foreground, without starting the Tauri shell or opening a browser, use:
npm run harness
It uses the same web --port <port> --no-open arguments, starts from the
deepseek-harness/ checkout, and pins DSH_HOME to this repository's .dsh.
Its output stays in the terminal; press Ctrl+C to stop it.
Both launch paths honor $env:DSH_GUI_PORT (default 3080). Under npm start,
harness output is logged to .dsh\gui\harness.log; dsh-gui's own status lines
go to .dsh\gui\gui.log, and startup failures also pop a message box (a GUI app
has no console to print to).
System shortcut (Windows)
npm run shortcut # desktop shortcut
npm run shortcut -- "D:\x.lnk" # arbitrary location
App icon
The app icon comes from the src-tauri/whale-icon submodule
(fornarwhal/deepseek-whale-girl-icon,
CC BY-NC-SA 4.0): DeepSeekHarness-WhaleGirl.ico (16–256 px, transparent).
src-tauri/tauri.conf.json points bundle.icon at it, so it is embedded as the
Windows exe resource and used as the window/taskbar icon. The shell UI itself
keeps no icon copy — swap the submodule to change the icon, then rebuild:
git submodule update --remote src-tauri/whale-icon
npm run build
Updating the harness submodule
git submodule update --remote deepseek-harness
npm run build # reinstall + rebuild harness, then rebuild exe + plugins
Adding plugins at runtime
Plugins are installed into the web profile under the repo-local DSH_HOME
(.dsh/). The layout mirrors presets/: each plugins/<id>/ wrapper owns an
install.mjs plus the plugin package/repo checkout — normally in a
second-level directory, one level deeper for a multi-package distribution repo
such as deep-whale:
plugins/<id>/install.mjs # plugin: builds + installs + mounts; dsh-web-ui: preset + pet + settings bridge
plugins/<id>/<package>/ # the plugin package (in-tree, or a git submodule)
A minimal install.mjs delegates the shared pipeline and declares its own id,
package directory, and submodule hint:
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { installPlugin } from '../../scripts/plugin-install.mjs'
const here = dirname(fileURLToPath(import.meta.url))
installPlugin({
id: 'my-plugin',
packageDir: join(here, 'dsh-my-plugin'),
})
npm run build (or npm run setup, or npm run install:plugins) runs every
plugins/*/install.mjs in directory-name order. Each plugin wrapper:
- builds the package in place (
pnpm install+pnpm run buildwith the pinned toolchain pnpm) — a package without abuildscript is used as shipped (prebuiltlib/), and a wrapper may passbuild: falseto force that for a prebuilt distribution package that still declares abuildscript, - pins the profile's pnpm store (
.dsh\profiles\web\pnpm-workspace.yaml), - installs it into the web profile as a
link:dependency, and - mounts it into the web composition by appending an
insertentry to.dsh\profiles\web\cordis.patch.yml(the harness only loads entries from the composition — a dependency alone stays inert). The entry id comes from the plugin'sdsh.gui.mountIddeclaration in itspackage.json, defaulting to the package name without a leadingdsh-.
A plugin that declares dsh.bundle.patch (its own cordis.patch.yml bundle
layer, e.g. dsh-file-explorer) mounts itself: dsh plugin add reconciles it
into the profile's dsh.profile.bundles list and its patch inserts the entry
as a bundle layer — no cordis.patch.yml insert is written for it. When a
previous version was mounted manually before gaining a bundle declaration, the
shared installer removes its matching legacy insert during reinstall to avoid a
duplicate Loader entry.
plugins/deep-whale installs its maid-atelier skin as a 免编译源码安装: the
submodule checkout ships prebuilt lib/, so the wrapper links it as shipped
(build: false, no copy, no patch).
plugins/dsh-web-ui is the partial exception: its install.mjs installs
three npm packages at @latest through installNpmPlugin —
@linxin666/dsh-liangshen, @linxin666/dsh-client-ui-web-ui-settings
(ordered before) and @linxin666/dsh-pet; all declare dsh.bundle.patch, so
dsh plugin add reconciles each into dsh.profile.bundles (no manual
cordis insert). The dsh-liangshen plugin syncs its own preset into
.dsh\.agent-presets\liangshen on host startup (no copy from the submodule;
it ships its own Windows custom-bash). The bridge is required because
dsh-host-apiproxy's hard-coded settings allowlist does not expose third-party
namespaces such as pet, so without it the pet's configuration form is
read-only. dsh-web-ui/ stays as the source reference only (v0.3.x layout:
packages/dsh-web-settings, packages/dsh-web-all); every other dsh-web
package stays uninstalled and unmounted.
Restart dsh-gui (or the harness) afterwards — plugin-set changes take effect
on boot. Any other dsh plugin / --patch workflow also works — nothing
escapes this repository.
Linux / WSL
The tooling is pure Node and runs on Linux (e.g. inside WSL): npm run setup,
npm run build, and npm run install:plugins behave identically there. The
harness, the plugins, and the dsh web server are all cross-platform. Building
the Tauri shell on Linux additionally needs the Tauri system libraries
(libwebkit2gtk-4.1-dev, libgtk-3-dev, libayatana-appindicator3-dev, ...)
and a display to run; without them, use npm run build -- --skip-exe to build
and install just the harness + plugins.
Troubleshooting
- "harness is not built" — run
npm run setup. ERR_PNPM_UNEXPECTED_STOREwhen installing plugins — the profile's pnpm store was not pinned (an install ran from a context whose home variables resolve a different default store than the one used before). Runnpm run install:pluginsonce — every plugin install script writesstoreDirinto.dsh\profiles\web\pnpm-workspace.yamlbefore re-adding itslink:dependency.- "failed to spawn harness (is
nodeon PATH?)" — install Node 22+. - Blank window / connection refused — read
.dsh\gui\harness.log; the harness failed to start (e.g. port already in use — setDSH_GUI_PORT). - "127.0.0.1:3080 is already in use" — close the existing dsh-gui /
dsh webprocess, or set a different$env:DSH_GUI_PORT. The shell deliberately refuses to attach to an existing server because it may use anotherDSH_HOME, which would look like a first-time setup with missing sessions. - Nothing happens on launch — a message box reports startup errors;
.dsh\gui\gui.logkeeps the history, and a panic writesdsh-gui-crash.lognext to the exe. - A leftover
nodeprocess after an old version crashed — current builds kill the harness via a Windows job object; kill strays once withtaskkill /IM node.exe /F(check nothing else needs them first). - WebView2 error — install the WebView2 Evergreen runtime.
License
This project (the dsh-gui shell) is released into the public domain under
The Unlicense. The deepseek-harness submodule retains its own
license.