dsh-wsl-launcher 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zhaoruoqing
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,237 @@
1
+ # dsh-wsl-launcher
2
+
3
+ **One double-click entry point to the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Web UI — for when `dsh` lives inside WSL.**
4
+
5
+ [中文文档](README.zh.md)
6
+
7
+ You run `dsh web` inside a WSL distribution, but your mouse is on Windows.
8
+ Every time you open the UI you perform the same ritual:
9
+
10
+ ```
11
+ open Windows terminal → wsl → dsh web → wait for boot → switch to browser → type the URL → Enter
12
+ ```
13
+
14
+ This plugin deletes the ritual. After installing it, your **Windows desktop** hosts an
15
+ app-style shortcut (black-whale icon). Double-click it and:
16
+
17
+ 1. `wsl.exe -d <your-distro> --exec /bin/bash <launcher.sh>` boots — no Windows terminal needed;
18
+ 2. the launcher finds `dsh` (PATH → login shell → recorded install path),
19
+ 3. if the Web UI port already listens it **just opens the browser** (idempotent — double-click it ten times, nothing breaks),
20
+ 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.
21
+
22
+ It also registers a `wsl_launcher` model tool, so any agent in a session can
23
+ `install` / `status` / `open` / `uninstall` the launcher itself.
24
+
25
+ ---
26
+
27
+ ## ✅ Compatibility
28
+
29
+ | | |
30
+ |---|---|
31
+ | dsh | Verified on `0.1.1-rc.2` (unit tests + scratch-profile composition via `dsh plugin add` / `--dump-config`) |
32
+ | Node | `>=20` (as declared in `engines`) |
33
+ | Host | WSL1/WSL2 on Windows 10 1607+ / Windows 11 with **interop enabled** (the default) and a Windows drive mounted under `/mnt/<drive>` |
34
+ | dsh location | anywhere in the distro: `npm -g`, nvm, local checkout — the launcher resolves it at runtime |
35
+ | Last verified | 2026-08-28 (WSL2, Ubuntu 24.04, dsh `0.1.1-rc.2`, Node 24) |
36
+
37
+ ## 📦 Install / Upgrade / Uninstall
38
+
39
+ Requires a `dsh` profile (e.g. the `web` profile where you run `dsh web`).
40
+
41
+ **Install**
42
+
43
+ ```sh
44
+ # from npm (once published)
45
+ dsh plugin --profile web add dsh-wsl-launcher
46
+
47
+ # or straight from GitHub (monorepo sub-path)
48
+ dsh plugin --profile web add github:Small-tailqwq/dsh-deep-whale#path:/wsl-launcher
49
+
50
+ # or from a local checkout / tarball
51
+ dsh plugin --profile web add /path/to/dsh-wsl-launcher
52
+ dsh plugin --profile web add /path/to/dsh-wsl-launcher-0.1.0.tgz
53
+ ```
54
+
55
+ `dsh plugin` uses the package's `dsh.bundle` manifest to register it as a bundle
56
+ layer automatically — **no manual `cordis.patch.yml` edits**.
57
+
58
+ **Upgrade / Uninstall**
59
+
60
+ ```sh
61
+ dsh plugin --profile web update dsh-wsl-launcher
62
+ dsh plugin --profile web remove dsh-wsl-launcher
63
+ ```
64
+
65
+ Then restart `dsh web`. On activation with `autoInstall: true` (the default) the
66
+ plugin (re)creates the launcher files and the desktop shortcut. Uninstalling the
67
+ package does not remove your shortcut — use `wsl_launcher` with action
68
+ `uninstall` (or the manual steps below) to clean up.
69
+
70
+ ## 🚀 Quick start
71
+
72
+ 1. Install the plugin (see above) and restart `dsh web`.
73
+ 2. Look at your Windows desktop: **DeepSeek Harness Web** (whale icon).
74
+ 3. Double-click it → first run boots `dsh web` and opens the browser; later runs
75
+ just open the browser if the server is already up.
76
+ 4. Reproducible check: ask any agent in a session to call `wsl_launcher` with
77
+ action `status` — expect `isWsl: true`, `distro: "<your distro>"`,
78
+ `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.
79
+
80
+ > **WSL2 note:** the server listens on `127.0.0.1` inside WSL; WSL's
81
+ > localhost forwarding makes it reachable from the Windows browser at the same
82
+ > URL — no `--host` flag needed.
83
+
84
+ ## 🤖 wsl_launcher tool
85
+
86
+ | Action | Effect |
87
+ |---|---|
88
+ | `install` | (Re)create the launcher script + icon in `~/.dsh/dsh-wsl-launcher` and the whale-icon desktop shortcut (WSL only) |
89
+ | `status` | WSL detection, distribution, interop availability, launcher files, and whether the Web UI port is listening (any platform) |
90
+ | `stop` | Stop only a live `dsh web` daemon whose PID and command identity match this launcher; never take over another process |
91
+ | `open` | Open the Web UI in the default Windows browser (WSL interop) |
92
+ | `uninstall` | Remove the desktop shortcut and the whole `~/.dsh/dsh-wsl-launcher` directory (WSL only) | safe to repeat; it only removes launcher-owned files |
93
+
94
+ ## ⚙️ Configuration
95
+
96
+ Override the plugin row in your profile's `cordis.patch.yml`
97
+ (e.g. `~/.dsh/profiles/web/cordis.patch.yml`). A patch replaces the whole row
98
+ config, so restate the keys you keep:
99
+
100
+ ```yaml
101
+ - id: wsl-launcher
102
+ config:
103
+ autoInstall: true # create/refresh launcher + shortcut on activation (default true)
104
+ port: 3080 # Web UI port (default 3080)
105
+ distro: "" # WSL distro name; empty = auto (WSL_DISTRO_NAME)
106
+ wslUser: "" # WSL user for the shortcut (wsl --user); empty = distro default
107
+ linkName: "DeepSeek Harness Web.lnk" # shortcut file name on the desktop
108
+ launcherDir: "" # launcher folder; empty = ~/.dsh/dsh-wsl-launcher
109
+ scriptName: dsh-web-launcher.sh # launcher file name
110
+ withIcon: true # copy the whale icon + apply it to the shortcut
111
+ autostopEnabled: false # opt-in: stop the daemon after all browser tabs close
112
+ autostopGraceMs: 30000 # idle grace period in milliseconds
113
+ autostopMinUptimeMs: 45000 # startup protection in milliseconds
114
+ ```
115
+
116
+ ### Launch strategy
117
+
118
+ `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.
119
+
120
+ For a disposable or `npx`-only setup, opt in explicitly and pin the exact package version:
121
+
122
+ ```yaml
123
+ - id: wsl-launcher
124
+ config:
125
+ launchMode: npx
126
+ npxPackage: "@deepseek-ai/dsh@0.1.1-rc.2"
127
+ ```
128
+
129
+ 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.
130
+
131
+ No secrets are involved.
132
+
133
+ ## 🔐 Permissions & data
134
+
135
+ | Area | What the plugin does |
136
+ |---|---|
137
+ | Files written (WSL side) | `~/.dsh/dsh-wsl-launcher/dsh-web-launcher.sh` and `~/.dsh/dsh-wsl-launcher/icons/whale.ico` |
138
+ | Files written (Windows side) | One `.lnk` on your desktop (`DeepSeek Harness Web.lnk`) — created by `powershell.exe` (WScript.Shell), no other Windows files touched |
139
+ | Files read | Only its own template and icon inside the package, plus `/proc/version` for WSL detection |
140
+ | Processes | Spawns `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 |
141
+ | Network | Loopback only: probes `http://127.0.0.1:<port>` for status; the browser is opened by the OS, not by the plugin |
142
+ | Credentials | None — never reads, stores, or sends credentials |
143
+
144
+ ## 🔧 How it works
145
+
146
+ ```
147
+ ┌──────────────────────────────┐ ┌────────────────────────────────────────────┐
148
+ │ DeepSeek Harness Web │──▶│ wsl.exe -d <distro> --exec /bin/bash │
149
+ │ (Windows desktop .lnk, │ │ ~/.dsh/dsh-wsl-launcher/ │
150
+ │ whale .ico via \\wsl.…) │ │ dsh-web-launcher.sh --port 3080 │
151
+ └──────────────────────────────┘ └────────────────────┬───────────────────────┘
152
+ │ (inside WSL)
153
+ ┌───────────────────────────────┼─────────────────────────┐
154
+ ▼ ▼ ▼
155
+ where is dsh? port 3080 listening? start `dsh web --port 3080`
156
+ (PATH → login shell → (open Windows browser, (console = server window;
157
+ recorded install path) exit 0 — idempotent) poll /dev/tcp → explorer.exe)
158
+ ```
159
+
160
+ - **`cordis.patch.yml`** — the bundle patch: one loader row (`id: wsl-launcher`,
161
+ `name: dsh-wsl-launcher`); `dsh plugin add` reconciles it into the profile's
162
+ bundle stack from the `dsh.bundle` manifest.
163
+ - **`lib/launcher.js`** — WSL detection (kernel banner + `WSL_DISTRO_NAME` /
164
+ `WSL_INTEROP` fallbacks), launcher template rendering (pure ASCII),
165
+ `wsl.exe` argument building with command-line quoting, Windows icon
166
+ candidates (`\\wsl.localhost\<distro>\…` first, `\\wsl$\…` fallback), and the
167
+ PowerShell builders for shortcut create/remove. All PowerShell string values
168
+ are single-quoted with `''` escaping.
169
+ - **`lib/index.js`** — the Cordis plugin (`name: 'wsl-launcher'`,
170
+ `inject: ['tools']`, zod `Config` schema) plus the `wsl_launcher` tool via
171
+ `@deepseek-ai/dsh-tools` `defineTool`.
172
+ - **`lib/template.sh.txt`** — the generated launcher: resolves `dsh` (PATH →
173
+ `bash -ic` login shell (covers nvm) → recorded absolute path), probes the port
174
+ with bash `/dev/tcp` (no curl/netcat), opens the browser via `explorer.exe`,
175
+ falling back to PowerShell `Start-Process` and then `cmd.exe /c start`
176
+ (needed on some WSL setups, e.g. containerized WSL, where `explorer.exe`
177
+ exits without opening a browser), and ties the console window's lifetime to
178
+ the server (`trap` on EXIT/INT/TERM).
179
+ - **`icons/whale.ico`** — multi-size whale icon; at install time it is copied
180
+ into the launcher directory so Windows can reach it through the
181
+ `\\wsl.localhost` mapping for the `.lnk` icon.
182
+
183
+ ## 🩺 Troubleshooting
184
+
185
+ | Symptom | Cause & fix |
186
+ |---|---|
187
+ | No shortcut after restart | Check `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`. |
188
+ | `powershell.exe not found` in tool output | Same as above — interop disabled or no Windows drive mounted under `/mnt`. |
189
+ | Shortcut appears but with a generic icon | The `\\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. |
190
+ | Desktop icon lands in the wrong place | OneDrive-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. |
191
+ | Launcher says `dsh was not found` | In 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`. |
192
+ | Launcher says `npx was not found` | `launchMode: 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. |
193
+ | Launcher says `dsh web exited early` | Read the console output above the error (profile/CLI problem, not a launcher bug). |
194
+ | `EADDRINUSE` / port already bound | The 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. |
195
+ | Server stops after the launcher window closes | Current 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. |
196
+ | Autostop endpoints reject requests | The 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. |
197
+ | Uninstall says the shortcut is absent | This is an idempotent success state. The Windows shortcut and wrapper are removed when present, and the WSL launcher directory is removed. |
198
+ | Browser opens but the page shows a transport/403 error | The **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. |
199
+ | No browser window appears | The 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. |
200
+ | Shortcut boots the wrong distro / user | The `.lnk` encodes `wsl.exe -d <distro> [--user <name>]`. Set `distro` / `wslUser` in the config, then `wsl_launcher install`. |
201
+ | Closing the console mid-session | The normal launcher starts a detached daemon, so closing the console does not stop it. Use `wsl_launcher stop`; foreground mode remains available for debugging. |
202
+
203
+ **Rollback**: `dsh plugin --profile web remove dsh-wsl-launcher`, then
204
+ `wsl_launcher` with action `uninstall` (removes the `.lnk` and
205
+ `~/.dsh/dsh-wsl-launcher`). Nothing else is left behind.
206
+
207
+ ## 🛠️ Development
208
+
209
+ - **No build step** — plain ESM JavaScript in `lib/`; zero runtime dependencies
210
+ beyond the dsh peer packages.
211
+ - **Tests**: `npm test` (node:test — no third-party dependencies).
212
+ - **Manual E2E in a real WSL**: `node tools/e2e-wsl.mjs install|verify|uninstall`
213
+ (dev helper, not published).
214
+ - **Release**: bump `version` in `package.json` → `pnpm pack` → publish / push.
215
+ The `dsh.bundle` manifest means `dsh plugin update` picks up new versions.
216
+
217
+ ## ❓ FAQ
218
+
219
+ **Q: Does the shortcut run anything as admin?** No. `wsl.exe` and the launcher
220
+ run with your normal user permissions.
221
+
222
+ **Q: Does it interfere with a running `dsh web`?** No — the port probe finds a
223
+ live server and skips straight to the browser.
224
+
225
+ **Q: Why a bash script and not a .exe / .cmd?** WSL side is Linux: bash +
226
+ `/dev/tcp` needs zero extra tools, stays pure ASCII, and is ~100 lines you can
227
+ read before it runs. The Windows side is one standard `.lnk`.
228
+
229
+ **Q: Non-WSL hosts?** The plugin loads fine, installs nothing, and keeps the
230
+ `wsl_launcher` tool active (`status` works everywhere; `open` degrades to a
231
+ "open manually" message; `install`/`uninstall` report a clear no-op).
232
+
233
+ **Q: WSL1?** Works — everything used is interop + files, both present in WSL1.
234
+
235
+ ## 📜 License
236
+
237
+ MIT — see [LICENSE](LICENSE).
package/README.zh.md ADDED
@@ -0,0 +1,223 @@
1
+ # dsh-wsl-launcher
2
+
3
+ **当 `dsh` 住在 WSL 里时,给 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Web 界面一个「双击直达」的入口。**
4
+
5
+ [English](README.md)
6
+
7
+ 你的 `dsh web` 跑在 WSL 里,但鼠标在 Windows 上。每次打开界面都要重复同一套仪式:
8
+
9
+ ```
10
+ 打开 Windows 终端 → wsl → dsh web → 等它启动 → 切到浏览器 → 敲网址 → 回车
11
+ ```
12
+
13
+ 这个插件把这套仪式删掉。安装后,你的 **Windows 桌面**上会出现一个应用风格的
14
+ 快捷方式(黑鲸图标)。双击它:
15
+
16
+ 1. 由 `wsl.exe -d <你的发行版> --exec /bin/bash <launcher.sh>` 直接拉起——不需要 Windows 终端;
17
+ 2. 启动脚本会找到 `dsh`(PATH → 登录 shell → 安装时记录的路径);
18
+ 3. 如果 Web 端口已经在监听,**直接打开浏览器**(天生幂等——连点十下也不坏,没有端口冲突);
19
+ 4. 否则启动 `dsh web --port 3080`,**这个控制台窗口就是服务器窗口**(关窗即停服务),轮询端口就绪后自动打开 Windows 默认浏览器。
20
+
21
+ 同时注册 `wsl_launcher` 模型工具:会话里的 agent 自己就能
22
+ `install` / `status` / `open` / `uninstall` 这个启动器。
23
+
24
+ ---
25
+
26
+ ## ✅ 兼容性
27
+
28
+ | | |
29
+ |---|---|
30
+ | dsh | 已在 `0.1.1-rc.2` 验证(单元测试 + 草稿 profile 的 `dsh plugin add` / `--dump-config` 组合检查) |
31
+ | Node | `>=20`(`engines` 声明) |
32
+ | 运行环境 | Windows 10 1607+ / Windows 11 上的 WSL1/WSL2,**interop 开启**(默认开启),且有 Windows 盘挂载在 `/mnt/<盘符>` |
33
+ | dsh 位置 | 发行版内的任意位置:`npm -g`、nvm、本地 checkout 均可——启动脚本运行时自行解析 |
34
+ | 最后验证 | 2026-08-28(WSL2、Ubuntu 24.04、dsh `0.1.1-rc.2`、Node 24) |
35
+
36
+ ## 📦 安装 / 升级 / 卸载
37
+
38
+ 需要一个 `dsh` profile(例如你跑 `dsh web` 的 `web` profile)。
39
+
40
+ **安装**
41
+
42
+ ```sh
43
+ # 从 npm(发布之后)
44
+ dsh plugin --profile web add dsh-wsl-launcher
45
+
46
+ # 或直接来自 GitHub(monorepo 子路径写法)
47
+ dsh plugin --profile web add github:Small-tailqwq/dsh-deep-whale#path:/wsl-launcher
48
+
49
+ # 或本地 checkout / tarball
50
+ dsh plugin --profile web add /path/to/dsh-wsl-launcher
51
+ dsh plugin --profile web add /path/to/dsh-wsl-launcher-0.1.0.tgz
52
+ ```
53
+
54
+ `dsh plugin` 通过包的 `dsh.bundle` 清单自动把它注册为 bundle 层——
55
+ **无需手工编辑任何 `cordis.patch.yml`**。
56
+
57
+ **升级 / 卸载**
58
+
59
+ ```sh
60
+ dsh plugin --profile web update dsh-wsl-launcher
61
+ dsh plugin --profile web remove dsh-wsl-launcher
62
+ ```
63
+
64
+ 然后重启 `dsh web`。激活时若 `autoInstall: true`(默认),插件会自动
65
+ (重新)创建启动文件与桌面快捷方式。卸载包不会删你的快捷方式——请用
66
+ `wsl_launcher` 的 `uninstall` 动作(或下面的手动步骤)清理。
67
+
68
+ ## 🚀 快速开始
69
+
70
+ 1. 安装插件(见上文)并重启 `dsh web`。
71
+ 2. 看你的 Windows 桌面:**DeepSeek Harness Web**(鲸鱼图标)。
72
+ 3. 双击它 → 首次启动 `dsh web` 并打开浏览器;之后如果服务已在运行,双击只开浏览器。
73
+ 4. 可复现验证:在任意会话里让 agent 调用 `wsl_launcher`,动作 `status`——
74
+ 应返回 `isWsl: true`、`distro: "<你的发行版>"`、`running: true` 与 `http://127.0.0.1:3080`。
75
+
76
+ > **WSL2 说明**:服务器监听 WSL 内的 `127.0.0.1`;WSL 的 localhost 转发
77
+ > 让 Windows 浏览器用同一个 URL 就能访问——不需要 `--host` 参数。
78
+
79
+ ## 🤖 wsl_launcher 工具
80
+
81
+ | 动作 | 效果 |
82
+ |---|---|
83
+ | `install` | (重新)创建 `~/.dsh/dsh-wsl-launcher` 下的启动脚本 + 图标,以及鲸鱼图标的桌面快捷方式(仅 WSL) |
84
+ | `status` | WSL 检测、发行版名、interop 可用性、启动文件、Web 端口是否在监听(全平台) |
85
+ | `open` | 用 Windows 默认浏览器打开 Web 界面(经 WSL interop) |
86
+ | `uninstall` | 删除桌面快捷方式与整个 `~/.dsh/dsh-wsl-launcher` 目录(仅 WSL) |
87
+
88
+ ## ⚙️ 配置
89
+
90
+ 在 profile 的 `cordis.patch.yml`(如 `~/.dsh/profiles/web/cordis.patch.yml`)
91
+ 中覆盖插件行。patch 会整体替换该行 config,请保留需要保留的键:
92
+
93
+ ```yaml
94
+ - id: wsl-launcher
95
+ config:
96
+ autoInstall: true # 激活时创建/刷新启动器与快捷方式(默认 true)
97
+ port: 3080 # Web 端口(默认 3080)
98
+ distro: "" # WSL 发行版名;留空 = 自动(WSL_DISTRO_NAME)
99
+ wslUser: "" # 快捷方式使用的 WSL 用户(wsl --user);留空 = 发行版默认
100
+ linkName: "DeepSeek Harness Web.lnk" # 桌面快捷方式文件名
101
+ launcherDir: "" # 启动器目录;留空 = ~/.dsh/dsh-wsl-launcher
102
+ scriptName: dsh-web-launcher.sh # 启动脚本文件名
103
+ withIcon: true # 复制鲸鱼图标并应用到快捷方式
104
+ ```
105
+
106
+ ### 启动策略
107
+
108
+ 默认 `launchMode: installed`。启动器会解析已经安装的 `dsh` 二进制文件(PATH、登录 shell、常见 Node 版本管理器目录、安装时记录的路径),不会访问 npm。
109
+
110
+ 如果只通过 `npx` 使用或需要一次性运行,请显式启用,并锁定精确包版本:
111
+
112
+ ```yaml
113
+ - id: wsl-launcher
114
+ config:
115
+ launchMode: npx
116
+ npxPackage: "@deepseek-ai/dsh@0.1.1-rc.2"
117
+ ```
118
+
119
+ 这会生成 `npx --yes @deepseek-ai/dsh@0.1.1-rc.2 web --port 3080 --no-open`。`npxPackage` 只接受一个精确的 npm 包版本:不允许省略版本、`latest`、语义化版本范围、空白字符或 shell 语法。`npx` 模式绝不会作为自动兜底。非交互式 WSL 启动环境中必须有 Node.js 和 `npx`;首次运行需要访问 npm registry,并可能下载该固定版本。
120
+
121
+ 不涉及任何敏感信息。
122
+
123
+ ## 🔐 权限与数据
124
+
125
+ | 领域 | 插件做了什么 |
126
+ |---|---|
127
+ | 写入文件(WSL 侧) | `~/.dsh/dsh-wsl-launcher/dsh-web-launcher.sh` 与 `~/.dsh/dsh-wsl-launcher/icons/whale.ico` |
128
+ | 写入文件(Windows 侧) | 仅桌面一个 `.lnk`(`DeepSeek Harness Web.lnk`)——由 `powershell.exe`(WScript.Shell)创建,不碰其他 Windows 文件 |
129
+ | 读取文件 | 只读包内自带的模板与图标,以及 `/proc/version`(WSL 检测) |
130
+ | 进程 | 经 WSL interop 拉起 `powershell.exe`(建/删快捷方式、浏览器兜底)与 `explorer.exe` / `cmd.exe`(开浏览器);启动器本体是纯 ASCII 的 bash 脚本,运行前可以通读 |
131
+ | 网络 | 仅回环:`status` 探测 `http://127.0.0.1:<port>`;浏览器由操作系统打开,不是插件打开 |
132
+ | 凭据 | 无——从不读取、存储、发送凭据 |
133
+
134
+ ## 🔧 工作原理
135
+
136
+ ```
137
+ ┌──────────────────────────────┐ ┌────────────────────────────────────────────┐
138
+ │ DeepSeek Harness Web │──▶│ wsl.exe -d <发行版> --exec /bin/bash │
139
+ │ (Windows 桌面 .lnk, │ │ ~/.dsh/dsh-wsl-launcher/ │
140
+ │ 鲸鱼图标经 \\wsl.… 映射) │ │ dsh-web-launcher.sh --port 3080 │
141
+ └──────────────────────────────┘ └────────────────────┬───────────────────────┘
142
+ │ (WSL 内部)
143
+ ┌───────────────────────────────┼─────────────────────────┐
144
+ ▼ ▼ ▼
145
+ dsh 在哪? 端口 3080 在监听吗? 启动 `dsh web --port 3080`
146
+ (PATH → 登录 shell → (打开 Windows 浏览器, (控制台 = 服务器窗口;
147
+ 记录的绝对路径) exit 0——幂等) 轮询 /dev/tcp → explorer.exe)
148
+ ```
149
+
150
+ - **`cordis.patch.yml`** — bundle patch:一条 loader 行(`id: wsl-launcher`、
151
+ `name: dsh-wsl-launcher`);`dsh plugin add` 依据 `dsh.bundle` 清单自动把它
152
+ 归入 profile 的 bundle 栈。
153
+ - **`lib/launcher.js`** — WSL 检测(内核 banner + `WSL_DISTRO_NAME` /
154
+ `WSL_INTEROP` 兜底)、启动模板渲染(纯 ASCII)、`wsl.exe` 参数构造(含命令行
155
+ 引号处理)、Windows 图标候选(`\\wsl.localhost\<发行版>\…` 优先、`\\wsl$\…`
156
+ 兜底)、以及建/删快捷方式的 PowerShell 构造。所有 PowerShell 字符串值都用
157
+ 单引号 + `''` 转义。
158
+ - **`lib/index.js`** — Cordis 插件(`name: 'wsl-launcher'`、`inject: ['tools']`、
159
+ zod `Config` schema)+ 基于 `@deepseek-ai/dsh-tools` `defineTool` 的
160
+ `wsl_launcher` 工具。
161
+ - **`lib/template.sh.txt`** — 生成的启动脚本:解析 `dsh`(PATH →
162
+ `bash -ic` 登录 shell(覆盖 nvm)→ 记录的绝对路径),用 bash `/dev/tcp`
163
+ 探测端口(不依赖 curl/netcat),开浏览器按 `explorer.exe` → PowerShell
164
+ `Start-Process` → `cmd.exe /c start` 的顺序兜底(部分 WSL 环境,如容器化
165
+ WSL,`explorer.exe` 会直接退出、开不了浏览器),并把控制台窗口的生命周期
166
+ 绑定到服务器(EXIT/INT/TERM 上挂 `trap`)。
167
+ - **`icons/whale.ico`** — 多尺寸鲸鱼图标;安装时复制到启动器目录,Windows 侧
168
+ 通过 `\\wsl.localhost` 映射读取它来给 `.lnk` 上图标。
169
+
170
+ ## 🩺 故障排查
171
+
172
+ | 症状 | 原因与解决 |
173
+ |---|---|
174
+ | 重启后没出现快捷方式 | 看 `dsh web` 日志里 `dsh-wsl-launcher: auto install skipped: …` 警告。通常是 WSL interop 被关:在 `C:\Users\<你>\.wslconfig` 的 `[boot]` 下设 `interop = true`(或改 wsl.conf),重启发行版,再跑 `wsl_launcher install`。 |
175
+ | 工具输出 `powershell.exe not found` | 同上——interop 关闭,或 `/mnt` 下没有挂载的 Windows 盘。 |
176
+ | 快捷方式出现了但图标是通用的 | 可能是 `\\wsl.localhost` 映射缺失(较老的 Windows)。插件会回退到 `\\wsl$`;若两者都没有,快捷方式照样能用,只是没图标。升级 Windows,或设 `withIcon: false` 跳过探测。 |
177
+ | 快捷方式出现在奇怪的位置 | OneDrive 托管桌面:插件按 `%USERPROFILE%\Desktop` → `%OneDrive%\Desktop` → `%USERPROFILE%\OneDrive\Desktop` 的顺序取第一个存在的目录——与 Explorer 所见一致。 |
178
+ | 启动脚本提示 `dsh was not found` | 默认 `installed` 模式下,非交互 shell 里找不到 `dsh`。在发行版里装好(`npm install -g @deepseek-ai/dsh`);如果用 nvm,登录 shell 探测会找到它——或从同一环境重装插件以刷新记录路径。也可改为显式、固定版本的 `launchMode: npx`。 |
179
+ | 启动脚本提示 `npx was not found` | `launchMode: npx` 要求非交互式 WSL 环境中可用 Node.js 和 `npx`。请安装 Node.js/npm,或将 `NPX_BIN` 指向可执行的 `npx`;`npxPackage` 必须保持精确固定版本。 |
180
+ | 提示 `dsh web exited early` | 看错误上方控制台的输出(profile/CLI 问题,不是启动脚本的 bug)。 |
181
+ | `EADDRINUSE` / 端口被占用 | 启动器检测到端口在监听会直接开浏览器。需要换端口就在 config 里设 `port`。 |
182
+ | 浏览器开了但页面报 transport/403 | **浏览器信任围栏**拦截了跨源调用——DevTools → Network → 失败请求 → 看 `Origin`,必须是 `http://127.0.0.1:<port>`。常见元凶是浏览器扩展或残留标签页。 |
183
+ | 没有弹出浏览器窗口 | 启动器会选择一种 Windows interop 启动方式:优先 `explorer.exe`,否则使用 PowerShell `Start-Process` 或 `cmd.exe /c start`;一次启动不会再串联其他方式,从而避免重复标签页。若全部不可用,会在控制台打印网址;请检查 WSL interop。 |
184
+ | 快捷方式拉起了错误的发行版/用户 | `.lnk` 里编码了 `wsl.exe -d <发行版> [--user <用户>]`。在 config 里设 `distro` / `wslUser` 后重新 `wsl_launcher install`。 |
185
+ | 关掉控制台窗口会怎样 | 故意的:控制台窗口就是服务器窗口——关窗会停掉 `dsh web`(脚本转发信号)。 |
186
+
187
+ **回滚**:`dsh plugin --profile web remove dsh-wsl-launcher`,再调用
188
+ `wsl_launcher` 的 `uninstall`(删除 `.lnk` 与 `~/.dsh/dsh-wsl-launcher`)。
189
+ 不会留下其他任何东西。
190
+
191
+ ## 🛠️ 开发
192
+
193
+ - **无构建步骤** — `lib/` 是纯 ESM JavaScript;除 dsh 的 peer 依赖外零运行时依赖。
194
+ - **测试**:`npm test`(node:test——无需任何第三方依赖)。
195
+ - **真实 WSL 里手动 E2E**:`node tools/e2e-wsl.mjs install|verify|uninstall`
196
+ (开发辅助,不随包发布)。
197
+ - **发布**:bump `package.json` 的 `version` → `pnpm pack` → 发布 / 推仓库。
198
+ `dsh.bundle` 清单意味着 `dsh plugin update` 会自动激活新版本。
199
+
200
+ ## ❓ 常见问题
201
+
202
+ **Q: 快捷方式会以管理员权限运行吗?** 不会。`wsl.exe` 与启动脚本都以你的普通用户权限运行。
203
+
204
+ **Q: 会干扰正在运行的 `dsh web` 吗?** 不会——端口探测发现服务在运行就直接进浏览器。
205
+
206
+ **Q: 为什么是 bash 脚本而不是 exe / cmd?** WSL 侧是 Linux:bash + `/dev/tcp` 零额外依赖、纯 ASCII、约 100 行,运行前可以通读。Windows 侧只有一个标准的 `.lnk`。
207
+
208
+ **Q: 非 WSL 环境?** 插件正常加载、不安装任何东西,`wsl_launcher` 工具保持可用(`status` 全平台可用;`open` 退化为「请手动打开」提示;`install`/`uninstall` 返回明确的 no-op 消息)。
209
+
210
+ **Q: WSL1 能用吗?** 能——用到的只有 interop 与文件,WSL1 两者都有。
211
+
212
+ ## 📜 许可
213
+
214
+ MIT —— 见 [LICENSE](LICENSE)。
215
+
216
+
217
+ ## 稳定性与容错
218
+
219
+ - 启动、停止按端口使用原子锁,避免双击产生重复 `dsh web` 进程。
220
+ - PID 文件必须是完整数字,停止前还会校验进程命令和进程组;陈旧或不匹配的 PID 只会被清理,不会误杀其他进程。
221
+ - `status`、`install`、`stop`、`uninstall` 的外部文件和 Windows 互操作错误会返回可读结果。
222
+ - 自动停止默认关闭。启用 `autostopEnabled` 后,只有同源 JSON 请求中的合法浏览器 client id 才会计入在线状态;错误请求会被拒绝。
223
+ - `uninstall` 可重复执行;它只删除插件创建的快捷方式、Windows wrapper 和 launcher 目录,不会停止外部启动的服务。
@@ -0,0 +1,24 @@
1
+ # dsh-wsl-launcher bundle layer -- applied when a profile lists this bundle
2
+ # (package.json's `dsh.bundle.patch` points at this file; `dsh plugin add`
3
+ # reconciles the package into the profile's bundle stack automatically).
4
+ # `name` is the package name (resolved from the profile's node_modules),
5
+ # not a relative path.
6
+ #
7
+ # The autostop settings are read from the environment at boot:
8
+ # DSH_WEB_AUTOSTOP=1 enable "stop the server when every browser
9
+ # tab is closed" (the launcher script exports
10
+ # it when it starts the background daemon)
11
+ # DSH_WEB_AUTOSTOP_GRACE_MS idle window before shutdown (default 30000)
12
+ # DSH_WEB_AUTOSTOP_MIN_UPTIME_MS startup grace before any shutdown (default
13
+ # 45000 -- covers page load + first beat)
14
+ - insert:
15
+ - id: wsl-launcher
16
+ name: dsh-wsl-launcher
17
+ # webServer feeds the optional autostop feature (lib/autostop.js).
18
+ # On a non-web host this row simply fails to load (the loader
19
+ # continues without it) and the plugin is absent.
20
+ inject: [webServer]
21
+ config:
22
+ autostopEnabled: !!js process.env.DSH_WEB_AUTOSTOP === '1'
23
+ autostopGraceMs: !!js (Number(process.env.DSH_WEB_AUTOSTOP_GRACE_MS) || 30000)
24
+ autostopMinUptimeMs: !!js (Number(process.env.DSH_WEB_AUTOSTOP_MIN_UPTIME_MS) || 45000)
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file