Skip to content

功能规格说明:桌面端 WSL 发行版主机 ​

本规格描述「同一台 Windows 机器里的那个 Linux 环境」:怎样把它列进主机选择器、选它之后会话怎样在发行版内运行、以及它与 SSH 远端在哪些地方必须不同(不需要隧道、不需要 daemon、localhost 直达)。主机接入的通用语义(主机与工作目录的身份、会话树按「主机 + 目录」分组、远端不可达时不得删会话/最近目录)见 desktop-remote.md;会话本身见 desktop-sessions.md;面板差异见 desktop-file-panel.md / desktop-terminal-panel.md / desktop-preview.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「内置 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「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「首次启动的进度文案」。
  • 发行版尚未完成首次启动(wsl --install --no-launch 之后再没启动过,Ubuntu 会要求创建 UNIX 用户):会话预检可能已经可用(-e 绕过首次启动引导),但打开终端会落到发行版自己的建号流程。这属于系统侧的安装未完成,应用不做特殊处理,也不得因此从列表里移除该发行版。

非目标 ​

  • 不做 WSL 后台会话:WSL 会话在应用退出时结束,与本地会话一致。原因是 WSL 实例随发行版生命周期存在,且传输层是同机子进程而非远端常驻 daemon;「应用退出后仍在跑」属于 SSH 远端独有能力(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)。