---
url: /wave-agent/specs/desktop/desktop-wsl.md
description: >-
  CodeWave IDE 桌面端（Windows）把本机 WSL2 发行版作为一种主机接入：发行版枚举与选择、发行版内 Node/CLI
  按需安装与同步、POSIX 工作目录与目录浏览、终端与文件/差异/预览面板、与 SSH 远端的行为边界
---

# 功能规格说明：桌面端 WSL 发行版主机

> 本规格描述「同一台 Windows 机器里的那个 Linux 环境」：怎样把它列进主机选择器、选它之后会话怎样在发行版内运行、以及它与 SSH 远端在哪些地方**必须不同**（不需要隧道、不需要 daemon、localhost 直达）。主机接入的通用语义（主机与工作目录的身份、会话树按「主机 + 目录」分组、远端不可达时不得删会话/最近目录）见 [desktop-remote.md](./desktop-remote.md)；会话本身见 [desktop-sessions.md](./desktop-sessions.md)；面板差异见 [desktop-file-panel.md](./desktop-file-panel.md) / [desktop-terminal-panel.md](./desktop-terminal-panel.md) / [desktop-preview.md](./desktop-preview.md) / [desktop-diff-panel.md](./desktop-diff-panel.md)。

## 用户场景与测试

### 用户故事：WSL 发行版主机（优先级：P2）

作为在 Windows 上做开发的用户，我希望主机选择器里直接列出本机的 WSL 发行版（Ubuntu、Debian…），选中其中一个就能像用 SSH 远程主机那样在里面开会话、跑命令、改文件，以便我不必为了在自己机器上的 Linux 环境里干活而先配一套 sshd 与本机回环连接。我已装好的工具链、仓库和依赖都在发行版里，Windows 侧只有编辑器。

**为什么是这个优先级**：WSL 与 SSH 远端共用「会话运行在别的工作环境里」这一模型，也共用同一套 JSON-RPC 与主机抽象，属于远程开发能力的延伸而非新的会话模型；它比 SSH 更常见于 Windows 用户的日常，但依赖 Windows 专属的 `wsl.exe`，所以排在 SSH 之后、作为同一族的第二种主机类型交付。

**为什么它不能照抄 SSH 的实现**：SSH 远端的传输层是「远端 daemon 常驻 + `ssh -N -L` 隧道」，因为网络会断、进程要跨本地生命周期活着。WSL 跑在同一台机器的同一个内核里：不需要隧道、不需要端口转发，发行版里的 `127.0.0.1` 在 Windows 侧就是 `localhost`，发行版被关掉（`wsl --shutdown`）时进程一定一起没。所以 WSL 的传输层是「经 `wsl.exe` 直连的标准输入输出子进程」，与本地会话同构；代价是**没有后台会话**（见「非目标」）。

**独立测试**：在装好至少一个 WSL2 发行版的 Windows 上打开应用，**先在该发行版内刻意不安装 Node.js**（或只装一个远低于 22 的旧版），验证主机选择器多出「WSL 发行版」分组并列出该发行版；选中它并在目录浏览面板中选定发行版内的仓库根目录开会话，验证首连后发行版内出现 `~/.wave/runtime/node/bin/node`（版本为固定版本）、CLI 由它启动、发行版全程不需要出网；再验证 agent 的 bash/读文件/搜索/git 全部作用在发行版文件系统上（例如 `uname -a` 输出含 `microsoft`、路径为 `/home/...`）；在该会话中让 agent 启动 dev server，点击消息里的 `http://localhost:<port>` 验证预览面板直接加载成功；打开终端验证落在发行版 shell 且工作目录为会话工作目录；重启应用后验证主机选择重置为「本地」、该发行版最近打开目录仍在。在未装 WSL 的机器上验证选择器不出现该分组且不报错。

**验收场景**：

1. **假设** 应用运行在 Windows 且本机装有至少一个 WSL 发行版，**当** 用户打开新会话的主机选择器，**则** 必须在「本地」之外显示一个「WSL 发行版」分组，逐项列出本机发行版名称；分组位于「本地」之后、「SSH 主机」分组之前；未装任何发行版或 `wsl.exe` 不可用时该分组整体不出现（不得显示空分组或报错）。
2. **假设** 本机安装了 WSL 1 发行版，**当** 用户查看该分组，**则** 该发行版必须以禁用状态显示并标注「WSL 1：不支持」（会话需要在发行版内运行 Linux 版托管 Node 运行时，WSL 1 的环境无法保证）；点击它不得切换主机，也不得报错。
3. **假设** 用户在主机选择器中选中某个 WSL2 发行版，**当** 选择生效，**则** 工作目录选择器必须切换到该发行版的上下文（与选中 SSH 主机后一致）：列出该发行版的最近打开目录并提供「浏览…」入口，不提供 Windows 本地目录对话框；分支选择器与 worktree 勾选项在该目录是 git 仓库时照常显示。
4. **假设** 用户选中某发行版并通过目录浏览面板选定目录，**当** 校验通过（该目录在发行版内存在且是目录），**则** 必须以（主机，POSIX 绝对路径）开始新会话，会话工作目录显示为发行版内路径（如 `/home/me/repo`），不得改写为 `\\wsl.localhost\…` 或 `C:\…` 形式；**当** 路径不存在或不可读，**则** 必须按既有规则报错且不进入会话。
5. **假设** 用户为一个 WSL 发行版开始会话，**当** 会话建立，**则** 连接必须按「预检 → 就绪检查 → CLI 同步 → 启动」的顺序进行，任一步失败都必须给出可操作的错误且不得进入半可用状态：`wsl.exe` 不可用（未安装 WSL）、发行版不存在、发行版为 WSL 1、发行版无法启动（服务/虚拟机/磁盘错误）、发行版平台/架构不受托管运行时支持（如 musl libc）、托管运行时获取或校验失败、CLI 推送失败。**发行版里有没有装 Node.js、装的是哪个版本，一律不影响会话**（不探测、不使用发行版自带的 Node）。**libc 判定以发行版实际使用的 glibc/musl 为准**：发行版里额外安装了 musl 工具链（`musl` / `musl-tools` 落下的 `/lib/ld-musl-*.so.1`）不得把 glibc 发行版判成 musl，进而拒绝整个发行版。
6. **假设** 发行版内尚未安装 wave CLI，**当** 应用连接该发行版，**则** 必须把本机内置 CLI bundle（`bin/wave-code.js` + `dist/bundle/wave.mjs` + `package.json`）推送到发行版固定目录 `~/.wave/cli/desktop/` 后继续启动（口径与 SSH 远端完全一致，见 [desktop-distribution.md](./desktop-distribution.md)「内置 CLI 一致保障」），**并在同一次连接里把匹配发行版平台/架构的托管 Node 运行时推送到 `~/.wave/runtime/node/`**（版本与平台 marker 命中即跳过；运行时的获取与校验全部发生在 Windows 侧，发行版既不必出网也不必装 Node.js）；**当** 内置 CLI 字节与发行版内那份不一致（应用升级携带了新 bundle 但版本号未变），**则** 必须重新推送。
7. **假设** 用户在一个 WSL 会话中，**当** agent 使用 bash/读文件/写文件/搜索/git/worktree/权限确认等工具，**则** 全部工具必须在发行版内执行、作用于发行版文件系统，行为与本地会话一致；消息流式、确认交互、任务列表、媒体附件在界面中的呈现不变。
8. **假设** 用户在一个 WSL 会话中打开文件面板并点击某个文件，**则** 必须以只读方式显示该文件内容（与 SSH 远端一致）；**当** 用户尝试在面板内编辑并保存，**则** 必须拒绝并提示「远端会话的文件不能在面板内保存」，不得静默丢弃修改。
9. **假设** 用户在一个 WSL 会话中打开差异面板，**当** 面板加载，**则** 必须显示该发行版内仓库相对 HEAD 的工作区差异，取数与本地会话走同一条 CLI RPC（`getWorkspaceDiff`），不得由本机主进程对发行版逐文件发起 `wsl.exe` 命令。
10. **假设** 用户在一个 WSL 会话中打开终端面板，**当** 终端启动，**则** 必须在该发行版内分配 PTY 运行用户默认 shell，工作目录为会话当前工作目录（发行版内路径）；**当** shell 退出或发行版被关闭，**则** 终端面板必须显示退出状态，与本地终端一致。
11. **假设** 用户在一个 WSL 会话中点击消息或终端输出里的 `http://localhost:<port>` 链接，**当** 链接被打开，**则** 必须直接在预览面板中加载该地址、**不得**建立 SSH 端口转发（WSL2 的 localhost 转发使 Windows 侧可直接访问发行版内的回环端口）；**当** 该端口在发行版内无人监听，**则** 必须显示与本地一致的「无法连接」与重试入口。
12. **假设** 用户在 WSL 会话中触发需要登录的操作，**当** 登录流程返回授权 URL，**则** 必须在 Windows 侧直接打开该 URL，不得走远端 SSO 回调端口转发；该发行版的登录态独立于 Windows 本地会话（各自的 `~/.wave/`），两者互不影响。
13. **假设** 用户在一个 WSL 会话中点击「+ 上传文件」并选择 Windows 本地文件，**当** 上传完成，**则** 插入输入框的必须是发行版内可访问的路径（如 `/tmp/wave-artifacts/<文件名>`），agent 的读文件工具必须能读到该文件内容；**当** 上传失败，**则** 必须显示明确错误，不得静默成功。
14. **假设** 用户已开始过某发行版的会话，**当** 应用被关闭并重新打开，**则** 主机选择必须重置为「本地」（与既有规则一致）；该发行版的最近打开目录必须仍然可用（重启后仍可被再次选择）。
15. **假设** 某 WSL 会话正在运行，**当** 发行版被外部关闭（`wsl --shutdown`、系统休眠、发行版崩溃），**则** 该会话必须显示中断/错误状态而不是静默卡住；**当** 用户随后再次在该发行版开会话或恢复该会话，**则** 必须自动重新拉起发行版内的 CLI 并接续，且不得删除该会话或其索引记录。
16. **假设** 用户在侧边栏查看会话树，**当** 存在 WSL 会话与本地会话指向同名目录（如本地 `~/repo` 与发行版 `/home/me/repo`），**则** 它们必须分属不同分组、互不混入；分组标签必须能区分主机类型（显示形如「Ubuntu (WSL)」而不是裸的发行版名或内部主机标识）。
17. **假设** 分屏的当前主机是某个 WSL 发行版，**当** 用户查看分屏与主机选择器的显示，**则** 主机标识必须显示为人类可读的发行版标签（如「Ubuntu (WSL)」），不得暴露内部主机标识串；同一发行版在触发键、菜单选中态、侧边栏分组标签三处的显示必须一致。
18. **假设** 应用运行在 macOS 或 Linux，**当** 用户打开主机选择器，**则** 不得出现「WSL 发行版」分组（WSL 只存在于 Windows）；该平台上其余主机能力与现状完全一致。
19. **假设** 发行版用户的登录 shell 在启动时输出横幅/每日提示（Ubuntu 首次登录会打印「To run a command as administrator…」与 motd），**当** 应用用 `-lic` 运行预检命令，**则** 每个取值都必须从输出里**定位到答案**（取最后一个非空行 / 匹配 `vX.Y.Z` / 找到 `WAVE_REMOTE_FILE_V1` 表头），不得假定首行即答案；**且** 该横幅不得以任何形式被读成 hosts 主目录、CLI 哈希或 Node 版本。
20. **假设** 会话的 CLI 子进程通过 `wsl.exe` 启动，**当** 该子进程被拉起，**则** 它必须以**发行版内托管运行时的绝对路径**（`<发行版 HOME>/.wave/runtime/node/bin/node`）直接执行——与 SSH 远端、JB 插件同一份运行时口径（见 [stdio-transport.md](../ui/stdio-transport.md)「JB 插件自动获取托管 Node 运行时」），登录 shell 只用于一次性取 `$PATH`、**不再用于解析 node**；不得把 CLI 挂在交互式登录 shell 下——交互 shell 的横幅会写进 stdout，而 stdout 就是 `--stdio` 的 JSON-RPC 通道。
21. **假设** 会话的 CLI 子进程按场景 20 的方式启动，**当** 它运行 hook、bash 工具或 MCP server，**则** 这些子进程继承的 `PATH` 必须是「**托管运行时的 `bin` 目录** + 发行版用户登录 shell 的 `$PATH`」（即 `-lic` 下的 `$PATH`）——`node` 因此解析到托管运行时（而不是用户用 nvm/fnm 装的任意版本），`pnpm` 等其它工具照旧可解析，hook 不得以 `/bin/sh: node: not found` 失败；该注入必须在非交互 shell 内完成，不得因此把登录 shell 的横幅带进 stdout（场景 19/20 仍然成立）。

### 边界情况

* **发行版正在启动或升级**：预检期间 `wsl.exe` 报发行版忙碌/磁盘挂载失败时，必须显示可操作的错误（重启 WSL 或稍后重试），不得把它当成「发行版不存在」而从列表移除。
* **发行版名称含空格**（如 `Ubuntu 24.04 LTS`）：必须能正常工作——名称只作为 `wsl.exe` 的参数传递，不得拼接进 shell 命令字符串。
* **同名目录的最近列表**：Windows 本地目录与发行版目录的最近记录必须按（主机，目录）分开保存，互不覆盖。
* **发行版被卸载后**：最近打开目录条目可以保留（不主动删除）；用户再次选择时报错即可，不得因为一次不可达而清空历史。
* **同时打开多个发行版的会话**：各发行版必须各自持有一条独立连接，会话与通知按（主机，sessionId）寻址，互不串话。
* **WSL2 localhost 转发被关闭**（`.wslconfig` 里 `localhostForwarding=false`）：预览链接在不做任何转发的情况下会加载失败——此时必须显示明确的连接失败与重试入口，不得白屏；不要求为此实现端口转发。
* **发行版自身拒绝交互式命令**（例如 Rancher Desktop 的两个实现细节发行版）：它会出现在列表里，但预检拿不到 `$HOME` 或登录 shell 的 `$PATH`——此时必须把 `wsl.exe` 的原话带进错误提示，而不是只说「连不上」。
* **发行版里装了 Node.js（含很旧的版本、nvm/fnm 管理的版本）**：一律不探测、不使用——会话只跑 `~/.wave/runtime/node/` 里那份托管运行时；发行版内 `node -v` 是 v18 也不得影响会话，也不得因此改变 CLI 的 `PATH` 注入顺序（托管运行时的 `bin` 目录恒在最前）。
* **发行版无外网**：托管运行时与内置 CLI 都由 Windows 侧下载后经 `wsl.exe` 推入，发行版不出网也能建立会话；运行时依赖（rg/sharp）仍由发行版内 CLI 在启动期自取（与远端口径一致），取不到只降级 grep 与超限图片，不影响会话本身。
* **首次连接发行版的等待**：恢复该发行版上的会话时，扫光动画下方按阶段显示进度文案（连接主机 → 检查远端环境 → 下载 / 安装托管 Node 运行时 → 同步 CLI → 下载工具依赖 → 启动会话），与 SSH 远端及插件端同一套口径，见 [stdio-transport.md](../ui/stdio-transport.md)「首次启动的进度文案」。
* **发行版尚未完成首次启动**（`wsl --install --no-launch` 之后再没启动过，Ubuntu 会要求创建 UNIX 用户）：会话预检可能已经可用（`-e` 绕过首次启动引导），但打开终端会落到发行版自己的建号流程。这属于系统侧的安装未完成，应用不做特殊处理，也不得因此从列表里移除该发行版。

### 非目标

* **不做 WSL 后台会话**：WSL 会话在应用退出时结束，与本地会话一致。原因是 WSL 实例随发行版生命周期存在，且传输层是同机子进程而非远端常驻 daemon；「应用退出后仍在跑」属于 SSH 远端独有能力（[desktop-remote.md](./desktop-remote.md)「SSH 远程后台会话」）。
* **不支持 WSL 1**：只列 WSL 2 发行版，WSL 1 显示为禁用。
* **不做发行版的创建/安装/删除/转换**：安装发行版、`wsl --set-version`、`wsl --shutdown` 等运维动作不提供界面入口，由用户在系统内自行完成。
* **不做 Windows 侧 UNC 路径作为会话工作目录**：会话工作目录只能是发行版内 POSIX 路径；`\\wsl.localhost\…` 不作为进入会话的路径形态。
* **不做文件面板内编辑保存**（与 SSH 远端一致，见场景 8）。
* **不做非 Windows 平台的等价物**（macOS 无 WSL）。
