
DSH Webview Wrapper
☆ 0贯彻 "一切皆插件" 的 DeepSeek Harness webview 封装。 A DeepSeek Harness webview wrapper in the everything-is-a-plugin spirit.
Get this plugin
Review the source, then continue to the publisher.
dsh plugin add is only for **out-of-tree** plugins. The Web bundles (@deepseek-ai/dsh-base, @deepseek-ai/dsh-web-app) are **in-box**: they load from the harness installation and belong in dsh.profile.bundles — never in the profile's dependencies. adding an in-box bundle makes pnpm materialize the whole harness tree inside the profile's node_modules:About this plugin
Source snapshot 8/15/2026dsh-webview-wrapper
English | 中文
A naive native desktop shell for the DeepSeek Harness Web surface. dsh-webview-wrapper is an out-of-tree deepseek-harness plugin that hosts the already-running Web app in an OS-native window through WebviewJS — the platform's own webview engine (WebView2 on Windows, WebKit on macOS, WebKitGTK on Linux), never a bundled browser. No Electron, no fork, no patch to the harness.
In keeping with the everything-is-a-plugin philosophy, the integration is pure composition. The package installs into a profile like any out-of-tree plugin, its bundle patch layer inserts one plugin row, and the plugin mounts itself once the Web composition provides the webServer / webRuntime services. The harness itself stays a stock installation.
Features
- Native window + system tray for the exact surface
dsh webserves. - Close-to-tray: closing the window hides it and the harness keeps running (an invisible 1×1 keep-alive window holds the process alive).
- Tray menu (
Show/Quit) and tray double-click. - Clean teardown: unloading the plugin — or shutting the profile down — exits the native app through the effect disposer.
- Invariant companion (
dsh-webview-wrapper/invariant) registered with theInvariantRegistry.
Installation
Prerequisites:
- A stock deepseek-harness installation whose profile mounts the Web bundle (
dsh web). The wrapper injectswebServerandwebRuntime, so the target profile must be Web-based — the shippedwebprofile is the natural target. - The WebviewJS platform requirements: Windows → WebView2 (ships with Windows 11 and current Edge), macOS → built-in WebKit (10.15+), Linux → WebKitGTK 4.1 and
libxdo. pnpm(the plugin manager forwards its arguments to pnpm).
Quick start
dsh plugin --profile web add dsh-webview-wrapper
dsh --profile web
Custom profile
The shipped web profile already lists the Web layers (@deepseek-ai/dsh-base, @deepseek-ai/dsh-web-app) in its dsh.profile.bundles. To use a custom profile, copy it and add only the wrapper:
cp -r ~/.dsh/profiles/web ~/.dsh/profiles/ww
dsh plugin --profile ww add dsh-webview-wrapper
dsh --profile ww
What add does, step by step
dsh plugininitializes the profile on first use, then runspnpm addin the profile directory.- Because
dsh-webview-wrapperdeclaresdsh.bundle.patchin its manifest, the plugin manager automatically appends it to the profile'sdsh.profile.bundleslayer stack. - On the next boot, the bundle's patch layer (
cordis.patch.yml) inserts thewebviewplugin row into the composition. - The plugin waits for
webServer/webRuntime, then owns the native window lifecycle.
One rule: don't add the in-box bundles
dsh plugin add is only for out-of-tree plugins. The Web bundles (@deepseek-ai/dsh-base, @deepseek-ai/dsh-web-app) are in-box: they load from the harness installation and belong in dsh.profile.bundles — never in the profile's dependencies. adding an in-box bundle makes pnpm materialize the whole harness tree inside the profile's node_modules:
- pnpm installs it with
autoInstallPeers: false, so the tree is missing its peers (e.g.@deepseek-ai/cordis);pnpm peers checkinside the profile reports them. - At boot the loader resolves plugins from the profile directory, so those copies shadow the installation (the
$DSH_HOME/profiles/node_modulesjunctions) and fail to load — tool calls error. - A first symptom along the way:
@deepseek-ai/dsh-web-apppulls in the harness's native directory picker, whosekoffidependency has an unapproved build script; pnpm 11 (defaultstrict-dep-builds: true) exits non-zero (ERR_PNPM_IGNORED_BUILDS),dsh pluginskips thedsh.profile.bundlesreconcile, and the profile boots without the Web layers.
To get out of that state, drop the in-box bundle from dependencies (keep it in dsh.profile.bundles — it resolves from the installation), run pnpm install in the profile directory to prune the harness tree, and boot again.
Running a local checkout
Build the checkout first, then install from the folder:
pnpm run build
dsh plugin --profile web add file:/absolute/path/to/dsh-webview-wrapper
Usage
- First boot starts in the tray — no main window is created until you ask for it. Double-click the tray icon or choose
Showto open the 1024×768 window hostinghttp://127.0.0.1:<port>(the live Web surface). - Closing the window hides it to the tray; the harness keeps running.
Quitin the tray menu exits the application.
How it works
| File | Role |
|---|---|
src/index.ts | The plugin: creates the WebviewJS Application, the tray, and the main window; routes custom-menu-click, window-close-requested, and application-close-requested; calls app.exit() on disposal. |
cordis.patch.yml | The bundle patch layer: inserts { id: webview, name: 'dsh-webview-wrapper' } into the composition. |
src/invariant.ts | The invariant companion, registering the package with the InvariantRegistry. |
assets/icon.svg | Taskbar / tray icon source, rasterized with sharp. |
Lifecycle sketch:
apply(ctx)
├─ readIcon(icon.svg, 16) # one shared async rasterization
└─ ctx.effect(() => # plugin lifetime == native app lifetime
├─ new Application()
├─ whenReady() → 1×1 keep-alive window + tray (Show/Quit)
├─ tray double-click / Show → createOrShowMainWindow()
│ └─ BrowserWindow(1024×768) + webview → http://127.0.0.1:<ctx.webServer.port>
├─ window-close-requested → hide (close-to-tray)
└─ disposer → app.exit()
Known limitations and roadmap
The wrapper is deliberately naive; the rough edges below are the roadmap:
- No native notifications — nothing in the harness surfaces as an OS notification. Roadmap: WebviewJS
Notification. - Generic HTTP transport — the webview loads the Web app over
http://127.0.0.1:<port>, the same browser HTTP carrier any tab uses. Roadmap: replace it with WebviewJS IPC (webview.expose()/window.ipc.postMessage) and/or a custom protocol, dropping the loopback-HTTP dependency. - No menu bar — only the tray menu exists. Roadmap:
app.setMenu()with File/Edit roles and accelerators. - Console window + tray-only exit — launching through the CLI shows the host console window, and the only exit paths are the tray
Quitand profile shutdown. Roadmap: package as a GUI-subsystem executable (the WebviewJS CLI / Node SEA) so no console appears, and add an in-page quit affordance.
Other current rough edges: default fixed 1024×768 window with a hardcoded title (no config surface yet), and the main window is not opened at startup (the app starts in the tray).
Development
git clone https://github.com/no1xsyzy/dsh-webview-wrapper.git
cd dsh-webview-wrapper
pnpm install
pnpm run build # re-run after every change
dsh plugin --profile web add file:/absolute/path/to/dsh-webview-wrapper
For a private dev profile, copy web first as in Custom profile — add only out-of-tree plugins, never the in-box @deepseek-ai/dsh-web-app.
Notes:
- The workspace never runs the app.
pnpm-workspace.yamlblocks the WebviewJS native addon builds (koffi,libwebview-nodejs) on purpose; the wrapper is composed into a profile to run, not executed from the checkout. Use thedsh plugin ... add file:<path>flow to test. lib/andnode_modulesare gitignored;lib/is build output.- Name discipline (npm publication). The npm package name must stay identical across
package.jsonname, the plugin name insrc/index.ts, the bundle row incordis.patch.yml, and the invariant registration insrc/invariant.ts. The plugin manager resolves bundles and theInvariantRegistrykeys registrations by npm name, so renaming means updating all four places. - Keep
README.mdandREADME.zh.mdin sync.