# wsl-keepalive — WSL Keep-Alive Toggle Plugin [中文](README.md) | [English](README.eng.md) > [!IMPORTANT] > **This is an unofficial third-party project.** This project is not an official DeepSeek product, and is not developed, published, endorsed, or supported by DeepSeek, nor does it represent DeepSeek's position. `DeepSeek`, `DeepSeek Harness`, `dsh` and related names, logos, and trademarks belong to their respective owners. For keep-alive issues, please submit them to this repository; do not contact DeepSeek official support. >
This plugin is completely AI-generated and distributed under the MIT license. Provides a **Keep-Alive** toggle in the **DSH WebUI** settings to prevent WSL distros from being automatically shut down by Windows when idle. When toggled, the DSH host starts/stops/queries the dbus-daemon via `wsl.exe` and shows the current dbus-daemon PID in the settings UI. --- ## 1. Features | Capability | Description | | --- | --- | | Keep-Alive toggle | Adds a **Keep-Alive** row under **Settings → Plugins**; flip it to enable/disable WSL keep-alive | | Status / PID display | The subtitle shows `Enabled · PID ` or `Disabled` in real time, plus the distro name (e.g. `Enabled · PID 498 · Arch`) | | Duplicate-start protection | Runs `pgrep -x dbus-daemon` before enabling; if already running, returns immediately without starting a duplicate | | Precise stop | On disable, `kill`s only the dbus-daemon PIDs recorded in memory / detected, without indiscriminately killing all dbus-daemon processes | | Auto-detect wsl.exe | When `wsl-exec-path` is not configured, auto-detects `/mnt/c/Windows/System32/wsl.exe` and writes it to the config if present | | Command-config validation | The distro/user/wsl.exe items under **Command configuration** carry descriptions plus runtime validation — a failed check reverts the change and shows the reason in red, a successful one persists | | Config persistence | Config is stored at `${DSH_HOME:-~/.dsh}/wsl-keepalive.json`, persists across restarts / sessions | | Localized UI | WebUI display text supports Chinese (`zh`) and English (`en`), defaulting/falling back to English; the dictionary is registered into the DSH locale service and follows DSH UI language switches in real time | ## 2. Requirements (Prerequisites) The machine running DSH Web must satisfy: 1. **WSL Interop**: This plugin works by launching dbus-daemon through wsl.exe to prevent the distro from going idle; it needs WSL Interop so that `wsl.exe --exec` can be called from within WSL. 2. **wsl.exe accessible**: `/mnt/c/Windows/System32/wsl.exe`, or set `wsl-exec-path` to its absolute path under the Linux environment. 3. **dbus-launch**: `dbus-launch` (`/usr/bin/dbus-launch`) must be installed inside the target distro. 4. **DSH Web**: The plugin must be installed via the Web profile (see "Installation"). 5. **Node.js**: Required for the build toolchain (`^22.19.0 || >=24.0.0`). > Note: Stopping keep-alive only `kill`s the dbus-daemon PIDs recorded/detected by the plugin; it never indiscriminately terminates all dbus-daemon processes in the target. ## 3. Configuration `~/.dsh/wsl-keepalive.json` Read at plugin startup; missing fields or a missing file are treated as empty values, and the file is auto-created / written when needed. The path prefers `${DSH_HOME}/wsl-keepalive.json` (falling back to `~/.dsh/wsl-keepalive.json` when `DSH_HOME` is unset), matching the previous config location. ```json { "wsl-dist-name": "", "wsl-user-name": "", "wsl-exec-path": "/mnt/c/Windows/System32/wsl.exe" } ``` | Field | Type | Meaning | | --- | --- | --- | | `wsl-dist-name` | string | The target distro name. When **non-empty**, the start command appends `-d ${wsl-dist-name}`; when **empty**, the default distro is used. Checked at runtime — if the distro does not exist the change is reverted and an error is shown | | `wsl-user-name` | string | The user that runs the keep-alive process inside the distro. When **non-empty**, the start command appends `--user ${wsl-user-name}`; when **empty**, the distro default user is used. Checked at runtime — if the user does not exist the change is reverted and an error is shown | | `wsl-exec-path` | string | Absolute path to `wsl.exe` on the Linux filesystem. When **empty**, `/mnt/c/Windows/System32/wsl.exe` is auto-detected at startup and written to the config if present; if neither exists it errors and enters an error state. Checked at runtime — if the file does not exist the change is reverted and an error is shown | On the WSL Keep-Alive tab under **Settings → Plugins**, the three command-config items each carry a description plus runtime validation: editing commits on blur/Enter, a failed check reverts that change and shows the reason in red (distro missing / user missing / wsl.exe missing), and a successful check persists to the config file above. ## 4. Installation ### Option A — Install via `dsh plugin add` The content repository is at `https://github.com/TheColdWorld/dsh-wsl-keepalive.git` and can be installed directly from the repository URL: ```bash # 1. Install directly from the repository URL dsh plugin --profile web add "github:TheColdWorld/dsh-wsl-keepalive" # Or use an explicit git URL: # dsh plugin --profile web add "git+https://github.com/TheColdWorld/dsh-wsl-keepalive.git" # 2. Restart DSH ``` ### Option B — Build locally ```bash git clone https://github.com/TheColdWorld/dsh-wsl-keepalive.git cd dsh-wsl-keepalive pnpm install . # install build deps pnpm build # produce lib/index.js and lib/client.js dsh plugin --profile web add link:$(pwd) # restart dsh after this ``` After the restart, the **Keep-Alive** toggle appears under **Settings → Plugins** in the WebUI. ### Uninstall ```bash dsh plugin --profile web remove wsl-keepalive # restart dsh ``` The config file `~/.dsh/wsl-keepalive.json` is retained; delete it manually if needed. ## 5. How It Works - **Communication**: The plugin Host exposes four same-origin JSON endpoints — `/api/wsl-keepalive/status`, `/api/wsl-keepalive/set`, and `/api/wsl-keepalive/config` (GET/POST); the browser half calls them with `fetch`. - **The `webServer` carrier is optional (DSH ≥ 0.1.5)**: since 0.1.5 the HTTP carrier is an *optional* capability — non-HTTP shells (Electron, worker carriers) load the same client stack without it. The plugin therefore does not put `webServer` in its static `inject` (that would leave the row permanently `pending` and fail the boot activation audit); instead `apply` **always** acquires the carrier with `ctx.inject(['webServer'], …)` — the same shape as DSH's own `/api` route in `dsh-client-connection`. That call creates a child fiber which runs as soon as the carrier is available (already bound or bound later) while the row itself activates unconditionally. Route registration lives inside that fiber's `ctx.effect`, so the routes are removed with the carrier and rebuilt if it returns; with no carrier the row still activates and the routes simply 404. Do not read `ctx.webServer` on the row's own context — cordis 4 throws `cannot get property "webServer" without inject` for an undeclared service property. - **Explicit service references**: the Host half imports only the contracts it uses — `Context` from `@deepseek-ai/cordis` and `WebRoute` from `@deepseek-ai/dsh-host-webserver` (whose `declare module` augmentation makes `ctx.webServer` a typed property). The browser half imports `dsh-client-ui-renderer` (`ctx.slots`), `dsh-client-ui-settings` (the `settings.plugins.tab` slot declaration), `dsh-client-ui-slots` (`PropsLocale`/`LocaleNamespaceMap`), and `dsh-client-locale` (`ctx.locale`). It does **not** use `ctx.shell`/`ctx.fs`: the keep-alive commands run on the host plane through Node's own `child_process`/`fs`, so `dsh-shell`/`dsh-fs` are no longer dependencies (the v0.1.2-rc.1 era imported them as empty `import type {}` augmentations that were erased at build time anyway). - **Keep-alive state**: dbus-daemon PIDs are recorded by the Host in memory after each query and live for the plugin's lifetime; the real state is re-queried after a restart. - **Two failure families, never conflated**: | Family | Trigger | What the tab shows | | --- | --- | --- | | **Endpoint unreachable** (`endpoint`) | The host serves no `/api/wsl-keepalive/*` route at all: this shell provides no HTTP carrier (Electron / worker carriers), the plugin row is inactive, or the transport failed outright. Includes the "SPA fallback answers an unknown path with `index.html` and HTTP 200" pseudo-success | Subtitle: `Unavailable: the host does not serve /api/wsl-keepalive/* (response: HTTP …)`; status row: a red "likely causes" line (no HTTP carrier / row inactive) plus a dimmed retry note and the raw endpoint detail | | **Host refusal** (`host`) | The request reached the host and the host refused: not a WSL environment, missing wsl.exe path, or a failed command | Subtitle: the reason localized to the DSH UI language (`Not a WSL environment (kernel: …)`, …); status row: the host's own raw detail (English text / command line / exit code) in dimmed text | To support this, `/api/wsl-keepalive/status` and `/set` now carry two extra fields — `errorCode` (a stable localization code owned by the client dictionary in `src/client/i18n.ts`) and `errorParams` (its template params). The original `error` stays as **technical detail** and is no longer the primary UI message. ## 6. Directory Structure ``` wsl-keepalive/ ├── package.json # dsh.bundle.patch + dsh.client declarations ├── tsconfig.json # TypeScript build config ├── tsdown.config.ts # tsdown build entry ├── cordis.patch.yml # plugin row declaration (injected on `dsh plugin add`) ├── README.md # this document (Chinese original) ├── README.eng.md # this document (English translation) ├── shared/ # common build helpers (web-platform.ts / tsdown.client.ts) └── src/ ├── index.ts # Host plugin entry: acquires the carrier via ctx.inject(['webServer']) and registers /api/wsl-keepalive/* routes ├── service.ts # keep-alive core: config read/write, PID tracking, status/start/stop ├── routes.ts # HTTP routes (status / set / config) └── client/ ├── index.ts # Client module: registers the plugin config tab under "Settings → Plugins" ├── KeepAliveConfig.tsx ├── host-endpoint.ts # same-origin JSON fetch + failure classification (endpoint vs host) ├── i18n.ts # localization dictionaries (en / zh), registered into the DSH locale service ├── keepalive.module.css └── css-modules.d.ts ```