Back to home@rqzhao1

dsh-wsl-launcher

Windows desktop launcher for DeepSeek Harness Web running in WSL

Stars
1
Language
JavaScript
Created
Sep 3, 2026
Updated
Sep 3, 2026
GitHub repo

Introduction

dsh-wsl-launcher

One double-click entry point to the DeepSeek Harness Web UI — for when dsh lives inside WSL.

中文文档

You run dsh web inside a WSL distribution, but your mouse is on Windows. Every time you open the UI you perform the same ritual:

open Windows terminal  →  wsl  →  dsh web  →  wait for boot  →  switch to browser  →  type the URL  →  Enter

This plugin deletes the ritual. After installing it, your Windows desktop hosts an app-style shortcut (black-whale icon). Double-click it and:

  1. wsl.exe -d <your-distro> --exec /bin/bash <launcher.sh> boots — no Windows terminal needed;
  2. the launcher finds dsh (PATH → login shell → recorded install path),
  3. if the Web UI port already listens it just opens the browser (idempotent — double-click it ten times, nothing breaks),
  4. otherwise it starts dsh web --port 3080 as a detached background daemon, polls the port until ready, then opens your default Windows browser; the console can close without stopping the server.

It also registers a wsl_launcher model tool, so any agent in a session can install / status / open / uninstall the launcher itself.


✅ Compatibility

dshVerified on 0.1.1-rc.2 (unit tests + scratch-profile composition via dsh plugin add / --dump-config)
Node>=20 (as declared in engines)
HostWSL1/WSL2 on Windows 10 1607+ / Windows 11 with interop enabled (the default) and a Windows drive mounted under /mnt/<drive>
dsh locationanywhere in the distro: npm -g, nvm, local checkout — the launcher resolves it at runtime
Last verified2026-08-28 (WSL2, Ubuntu 24.04, dsh 0.1.1-rc.2, Node 24)

📦 Install / Upgrade / Uninstall

Requires a dsh profile (e.g. the web profile where you run dsh web).

Install

# from npm (once published)
dsh plugin --profile web add dsh-wsl-launcher

# or straight from GitHub (monorepo sub-path)
dsh plugin --profile web add github:Small-tailqwq/dsh-deep-whale#path:/wsl-launcher

# or from a local checkout / tarball
dsh plugin --profile web add /path/to/dsh-wsl-launcher
dsh plugin --profile web add /path/to/dsh-wsl-launcher-0.1.0.tgz

dsh plugin uses the package's dsh.bundle manifest to register it as a bundle layer automatically — no manual cordis.patch.yml edits.

Upgrade / Uninstall

dsh plugin --profile web update dsh-wsl-launcher
dsh plugin --profile web remove dsh-wsl-launcher

Then restart dsh web. On activation with autoInstall: true (the default) the plugin (re)creates the launcher files and the desktop shortcut. Uninstalling the package does not remove your shortcut — use wsl_launcher with action uninstall (or the manual steps below) to clean up.

🚀 Quick start

  1. Install the plugin (see above) and restart dsh web.
  2. Look at your Windows desktop: DeepSeek Harness Web (whale icon).
  3. Double-click it → first run boots dsh web and opens the browser; later runs just open the browser if the server is already up.
  4. Reproducible check: ask any agent in a session to call wsl_launcher with action status — expect isWsl: true, distro: "<your distro>", running: true and the URL http://127.0.0.1:3080. Use wsl_launcher action stop to stop only the daemon recorded by this launcher.

WSL2 note: the server listens on 127.0.0.1 inside WSL; WSL's localhost forwarding makes it reachable from the Windows browser at the same URL — no --host flag needed.

🤖 wsl_launcher tool

ActionEffect
install(Re)create the launcher script + icon in ~/.dsh/dsh-wsl-launcher and the whale-icon desktop shortcut (WSL only)
statusWSL detection, distribution, interop availability, launcher files, and whether the Web UI port is listening (any platform)
stopStop only a live dsh web daemon whose PID and command identity match this launcher; never take over another process
openOpen the Web UI in the default Windows browser (WSL interop)
uninstallRemove the desktop shortcut and the whole ~/.dsh/dsh-wsl-launcher directory (WSL only)

⚙️ Configuration

Override the plugin row in your profile's cordis.patch.yml (e.g. ~/.dsh/profiles/web/cordis.patch.yml). A patch replaces the whole row config, so restate the keys you keep:

- id: wsl-launcher
  config:
    autoInstall: true            # create/refresh launcher + shortcut on activation (default true)
    port: 3080                   # Web UI port (default 3080)
    distro: ""                   # WSL distro name; empty = auto (WSL_DISTRO_NAME)
    wslUser: ""                  # WSL user for the shortcut (wsl --user); empty = distro default
    linkName: "DeepSeek Harness Web.lnk"   # shortcut file name on the desktop
    launcherDir: ""              # launcher folder; empty = ~/.dsh/dsh-wsl-launcher
    scriptName: dsh-web-launcher.sh        # launcher file name
    withIcon: true               # copy the whale icon + apply it to the shortcut
  autostopEnabled: false        # opt-in: stop the daemon after all browser tabs close
  autostopGraceMs: 30000       # idle grace period in milliseconds
  autostopMinUptimeMs: 45000   # startup protection in milliseconds

Launch strategy

launchMode: installed is the default. The launcher resolves an already installed dsh binary (PATH, login shell, common Node manager paths, then the recorded path) and does not contact npm.

For a disposable or npx-only setup, opt in explicitly and pin the exact package version:

- id: wsl-launcher
  config:
    launchMode: npx
    npxPackage: "@deepseek-ai/dsh@0.1.1-rc.2"

This generates npx --yes @deepseek-ai/dsh@0.1.1-rc.2 web --port 3080 --no-open. The package field accepts only one exact npm package/version specifier: no omitted version, latest, semver range, whitespace, or shell syntax. npx mode is never used as an automatic fallback. Node.js and npx must be available to the non-interactive WSL launcher; the first run needs npm-registry network access and may download the pinned package.

No secrets are involved.

🔐 Permissions & data

AreaWhat the plugin does
Files written (WSL side)~/.dsh/dsh-wsl-launcher/dsh-web-launcher.sh and ~/.dsh/dsh-wsl-launcher/icons/whale.ico
Files written (Windows side)One .lnk on your desktop (DeepSeek Harness Web.lnk) — created by powershell.exe (WScript.Shell), no other Windows files touched
Files readOnly its own template and icon inside the package, plus /proc/version for WSL detection
ProcessesSpawns powershell.exe (shortcut create/remove, browser fallback) and explorer.exe / cmd.exe (open browser) through WSL interop; the launcher itself is an ASCII bash script you can read before it runs
NetworkLoopback only: probes http://127.0.0.1:<port> for status; the browser is opened by the OS, not by the plugin
CredentialsNone — never reads, stores, or sends credentials

🔧 How it works

┌──────────────────────────────┐   ┌────────────────────────────────────────────┐
│  DeepSeek Harness Web        │──▶│  wsl.exe -d <distro> --exec /bin/bash      │
│  (Windows desktop .lnk,      │   │      ~/.dsh/dsh-wsl-launcher/              │
│  whale .ico via \\wsl.…)     │   │      dsh-web-launcher.sh --port 3080       │
└──────────────────────────────┘   └────────────────────┬───────────────────────┘
                                                        │ (inside WSL)
                        ┌───────────────────────────────┼─────────────────────────┐
                        ▼                               ▼                         ▼
                  where is dsh?                port 3080 listening?      start `dsh web --port 3080`
        (PATH → login shell →          (open Windows browser,          (console = server window;
         recorded install path)         exit 0 — idempotent)            poll /dev/tcp → explorer.exe)
  • cordis.patch.yml — the bundle patch: one loader row (id: wsl-launcher, name: dsh-wsl-launcher); dsh plugin add reconciles it into the profile's bundle stack from the dsh.bundle manifest.
  • lib/launcher.js — WSL detection (kernel banner + WSL_DISTRO_NAME / WSL_INTEROP fallbacks), launcher template rendering (pure ASCII), wsl.exe argument building with command-line quoting, Windows icon candidates (\\wsl.localhost\<distro>\… first, \\wsl$\… fallback), and the PowerShell builders for shortcut create/remove. All PowerShell string values are single-quoted with '' escaping.
  • lib/index.js — the Cordis plugin (name: 'wsl-launcher', inject: ['tools'], zod Config schema) plus the wsl_launcher tool via @deepseek-ai/dsh-tools defineTool.
  • lib/template.sh.txt — the generated launcher: resolves dsh (PATH → bash -ic login shell (covers nvm) → recorded absolute path), probes the port with bash /dev/tcp (no curl/netcat), opens the browser via explorer.exe, falling back to PowerShell Start-Process and then cmd.exe /c start (needed on some WSL setups, e.g. containerized WSL, where explorer.exe exits without opening a browser), and ties the console window's lifetime to the server (trap on EXIT/INT/TERM).
  • icons/whale.ico — multi-size whale icon; at install time it is copied into the launcher directory so Windows can reach it through the \\wsl.localhost mapping for the .lnk icon.

🩺 Troubleshooting

SymptomCause & fix
No shortcut after restartCheck dsh web logs for a dsh-wsl-launcher: auto install skipped: … warning. Usually WSL interop is disabled: set interop = true under [boot] in C:\Users\<you>\.wslconfig (or wsl.conf), restart the distro, then retry wsl_launcher install.
powershell.exe not found in tool outputSame as above — interop disabled or no Windows drive mounted under /mnt.
Shortcut appears but with a generic iconThe \\wsl.localhost mapping may be missing (older Windows). The plugin falls back to \\wsl$; if that is gone too the shortcut still works, iconless. Update Windows or set withIcon: false to silence the probe.
Desktop icon lands in the wrong placeOneDrive-managed desktops: the shortcut targets %USERPROFILE%\Desktop, %OneDrive%\Desktop or %USERPROFILE%\OneDrive\Desktop in that order — the first existing one wins, same as Explorer sees it.
Launcher says dsh was not foundIn the default installed mode, dsh is not reachable from a non-interactive shell. Install it in the distro (npm install -g @deepseek-ai/dsh); if you use nvm, the login-shell probe finds it — or set the recorded path by reinstalling the plugin from the same environment. Alternatively configure explicit, version-pinned launchMode: npx.
Launcher says npx was not foundlaunchMode: npx requires Node.js with npx available in the non-interactive WSL environment. Install Node.js/npm or set NPX_BIN to an executable npx path; keep npxPackage an exact pinned version.
Launcher says dsh web exited earlyRead the console output above the error (profile/CLI problem, not a launcher bug).
EADDRINUSE / port already boundThe launcher detects any listening service and opens the browser instead; it never kills or replaces an unrelated service. Use another port in the config if needed.
Server stops after the launcher window closesCurrent versions use a detached daemon. Use wsl_launcher stop or the generated script's stop action; an autostop-enabled daemon stops after its browser presence grace period.
Autostop endpoints reject requestsThe browser client sends same-origin JSON POST requests. Requests with another origin, wrong method, missing JSON content type, malformed JSON, or invalid client ids are rejected.
Uninstall says the shortcut is absentThis is an idempotent success state. The Windows shortcut and wrapper are removed when present, and the WSL launcher directory is removed.
Browser opens but the page shows a transport/403 errorThe browser-trust fence rejected a cross-origin call to the local API — open DevTools → Network → the failed request → check its Origin; it must be http://127.0.0.1:<port>. A browser extension or leftover tab is the usual culprit.
No browser window appearsThe launcher selects one Windows interop opener: explorer.exe when available, otherwise PowerShell Start-Process or cmd.exe /c start; it never chains launchers after an attempt, preventing duplicate tabs. If none is available it prints the URL. If none works in your WSL setup, check WSL interop; the printed URL is always shown in the console as a last resort.
Shortcut boots the wrong distro / userThe .lnk encodes wsl.exe -d <distro> [--user <name>]. Set distro / wslUser in the config, then wsl_launcher install.
Closing the console mid-sessionThe normal launcher starts a detached daemon, so closing the console does not stop it. Use wsl_launcher stop; foreground mode remains available for debugging.

Rollback: dsh plugin --profile web remove dsh-wsl-launcher, then wsl_launcher with action uninstall (removes the .lnk and ~/.dsh/dsh-wsl-launcher). Nothing else is left behind.

🛠️ Development

  • No build step — plain ESM JavaScript in lib/; zero runtime dependencies beyond the dsh peer packages.
  • Tests: npm test (node:test — no third-party dependencies).
  • Manual E2E in a real WSL: node tools/e2e-wsl.mjs install|verify|uninstall (dev helper, not published).
  • Release: bump version in package.jsonpnpm pack → publish / push. The dsh.bundle manifest means dsh plugin update picks up new versions.

❓ FAQ

Q: Does the shortcut run anything as admin? No. wsl.exe and the launcher run with your normal user permissions.

Q: Does it interfere with a running dsh web? No — the port probe finds a live server and skips straight to the browser.

Q: Why a bash script and not a .exe / .cmd? WSL side is Linux: bash + /dev/tcp needs zero extra tools, stays pure ASCII, and is ~100 lines you can read before it runs. The Windows side is one standard .lnk.

Q: Non-WSL hosts? The plugin loads fine, installs nothing, and keeps the wsl_launcher tool active (status works everywhere; open degrades to a "open manually" message; install/uninstall report a clear no-op).

Q: WSL1? Works — everything used is interop + files, both present in WSL1.

📜 License

MIT — see LICENSE.