# 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. > [!NOTE] > **v0.2.0 official-alignment rewrite**: configuration is now the row's **Volatile Config**, persisted by the DSH settings service into the profile's `cordis.patch.yml` (no more `~/.dsh/wsl-keepalive.json`); the page moved from the **Settings → Plugins** tab to **Main page → Plugins → WSL Keep-Alive → WSL Keep-Alive component** (`plugins.row.config`); the host endpoints are now **Connection exact Fetch routes** on the authenticated `/api` bridge instead of raw `webServer` routes. Provides a **Keep-Alive** toggle on the **Main page → Plugins → WSL Keep-Alive → WSL Keep-Alive component** page of the **DSH WebUI** 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 on that component page. --- ## 1. Features | Capability | Description | | --- | --- | | Keep-Alive toggle | Flip the switch on the **Main page → Plugins → WSL Keep-Alive → WSL Keep-Alive component** page 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 `wslExecPath` is empty, falls back to `/mnt/c/Windows/System32/wsl.exe` (read-only; no config file is written) | | Command configuration | The distro/user/wsl.exe items under **Command configuration** are edited and saved through the official settings form; runtime failures (toggle/command) surface in the UI, and a save no longer reverts in place | | Config persistence | Config is the row's Volatile Config, written into the profile's `cordis.patch.yml`; persists across restarts / sessions | | Localized UI | The plugin card title/description come from `meta` in `locale/en.json` and `locale/zh.json`; page copy supports Chinese (`zh`) and English (`en`), defaulting/falling back to English and following 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 Configuration is the plugin row's **Volatile Config**, projected by the DSH settings service as an editable form and persisted into the profile's `cordis.patch.yml` (not a plugin-private file). Open **Main page → Plugins → WSL Keep-Alive → WSL Keep-Alive component** (the config page of the `wsl-keepalive` bundle's row with id `wsl-keepalive`). | Field | Type | Meaning | | --- | --- | --- | | `wslDistName` | string | Target distro name. When **non-empty** the start command appends `-d `; when **empty** the default distro is used | | `wslUserName` | string | User that runs the keep-alive process inside the target distro. When **non-empty** the start command appends `--user `; when **empty** the distro default user is used | | `wslExecPath` | string | Absolute path to `wsl.exe` on the Linux filesystem. When **empty** it falls back to `/mnt/c/Windows/System32/wsl.exe` (read-only probe); if neither exists the plugin errors | Saving goes through the shared DSH settings form (`ctx.configForms` → profile patch): a schema-valid save is written and takes effect **without a restart** (the service re-reads the Volatile Config on every status/toggle). Whether the distro/user/wsl.exe actually exist is validated at execution time and surfaced in the status area, instead of silently reverting the save. > **Automatic import from the old version**: on first start, if the legacy `${DSH_HOME:-~/.dsh}/wsl-keepalive.json` exists, its non-empty fields are imported once into this row's settings document (written to the profile's `cordis.patch.yml`), and the old file is renamed to `wsl-keepalive.json.imported`. Fields already configured are not overwritten; if the write is refused, the legacy file is kept and the import is retried on the next start. ## 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, open **Main page → Plugins → WSL Keep-Alive → WSL Keep-Alive component** in the WebUI to see the **Keep-Alive** toggle. ### Uninstall ```bash dsh plugin --profile web remove wsl-keepalive # restart dsh ``` The old config file `${DSH_HOME:-~/.dsh}/wsl-keepalive.json` is retained; delete it manually if needed. ## 5. How It Works - **Communication**: the plugin Host provides two same-origin JSON endpoints through **Connection exact Fetch routes** — `/api/wsl-keepalive/status` (GET) and `/api/wsl-keepalive/set` (POST); the browser half calls them with `fetch`. Configuration no longer uses HTTP: the DSH settings form (`ctx.configForms`) writes it into the profile's Cordis patch. - **The `connection` carrier is optional**: `connection` is an optional capability (non-HTTP shells do not provide it). The plugin therefore does not put it in its static `inject` (that would leave the row permanently `pending`); instead `apply` acquires it with `ctx.inject(['connection'], …)` and calls `ctx.connection.fetch.register(route)` inside that child fiber's `ctx.effect`, so the routes are removed with the carrier and rebuilt if it returns. Registration sits under Connection's `/api` prefix, so it passes `ctx.connection.admit()` (Host/Origin plus browser-session auth) instead of bypassing that fence. - **Explicit service references**: the Host half imports `Context`/`Volatile` from `@deepseek-ai/cordis`, `@deepseek-ai/schemastery` (the Config schema), `@deepseek-ai/dsh-client-connection` (`ctx.connection` + `ConnectionFetchRoute`), and `@deepseek-ai/dsh-settings` (page policy). The browser half imports `dsh-client-ui-renderer` (`ctx.slots`), `dsh-client-ui-plugin-manager` (the `plugins.row.config` slot declaration), `dsh-client-ui-settings` (`ctx.configForms`), `dsh-client-ui-primitives` (`SettingsForm`/`SettingsValueField`), `dsh-client-ui-slots` (`PropsLocale`/`LocaleNamespaceMap`), `dsh-client-locale` (`ctx.locale`), and `dsh-client-store` (`SnapshotStore`). It does **not** use `ctx.shell`/`ctx.fs`: the keep-alive commands run on the host plane through Node's own `child_process`. - **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) ├── locale/ # localized plugin-card metadata: en.json / zh.json (meta.title / meta.description) ├── shared/ # common build helpers (web-platform.ts / tsdown.client.ts) └── src/ ├── index.ts # Host plugin entry: exports the Volatile Config schema and registers /api/wsl-keepalive/* Fetch routes via ctx.inject(['connection']) ├── service.ts # keep-alive core: reads the Volatile Config, PID tracking, status/start/stop ├── routes.ts # Connection exact Fetch routes (status / set) └── client/ ├── index.ts # Client module: registers the plugins.row.config page (Main page → Plugins → WSL Keep-Alive → WSL Keep-Alive component) ├── controller.ts # settings form model over ctx.configForms + live status/toggle ├── 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 ```