Appearance
功能规格说明:桌面端会话与远程
拆分自
desktop-app.md(CodeWave IDE 桌面端总纲),本文档仅承载该主题的用户故事与验收场景;跨主题的边界情况与非目标按主题分发至各文件。
用户场景与测试
用户故事:会话管理(优先级:P2)
作为用户,我希望在应用内新建会话并恢复上次的会话与工作目录,以便我可以在不同任务之间切换。
为什么是这个优先级:会话管理是日常使用的基础能力,且 sidebar 是 desktop 相对 IDE 插件的布局扩展。
独立测试:进行一个会话后点击"新对话"开始全新会话;重启应用验证(「最近打开」列表非空时)自动进入最近目录的新对话、(列表为空时)回到选择工作目录的初始状态且自动初始化不重复触发;从侧边栏会话树恢复一条历史会话;删除一条 worktree 会话并验证 worktree 与临时分支被一并清理;纯键盘操作:Tab 逐条遍历会话条目,↑/↓/Home/End 在条目间移动焦点、→ 到达删除按钮、Enter 弹出确认对话框。
验收场景:
- 假设应用正在运行,当用户点击侧边栏"新对话",则必须在当前窗口内开始一个全新会话(回到欢迎页 + 输入框状态);不中止任何正在生成的会话——原会话在后台继续运行,可从侧边栏切回查看结果。
- 假设用户关闭并重新打开应用,当应用启动,则不得恢复上次的会话——每次全新开始(工作目录与主机选择不跨重启持久化):当「最近打开」列表非空(用户曾主动选择过目录),则应用必须自动执行一次与点击侧边栏"新对话"完全一致的初始化(2026-09-08 拍板「刚打开桌面端就跟点击新对话的效果一样」):在聚焦分屏以「最近打开」列表首项(最近主动选择的仓库根,见场景 8)为工作目录启动一个新的空会话,输入框可用、上下文栏显示该目录,用户无需先经目录选择步骤即可直接对话;当「最近打开」列表为空(用户从未主动选择过目录),则维持选择工作目录的欢迎/初始界面(按场景 10 不自动绑定任何工作目录)。自动初始化只在本次启动首次界面就绪时执行一次:webview 重新加载、新增分屏、设置页往返重挂载、聚焦分屏变化等后续
webviewReady均不得重复触发(同一分屏不得出现第二个自动 agent);自动初始化的 agent 生成期间用户点击侧边栏历史会话或以其它操作改变该分屏,则必须以用户最新操作为准——自动生成的新会话不得覆盖用户选择(若其生成已完成则转入后台或销毁,不得孤儿驻留),语义与「历史会话即时进入与恢复加载动画」场景 4 一致。自动初始化后用户仍可照常换目录(工作目录选择器保持可用)、点历史会话或再点"新对话"(场景 8/10 语义不变)。 - 假设侧边栏显示会话树,当用户查看侧边栏,则必须看到二级结构:一级为目录分组(可折叠,启动时默认全部展开),二级为该目录下全部会话(按创建时间倒序,最新创建在上;会话的后续活跃——发消息、生成结束、被选中——不改变其位置);会话树数据完全来自 desktop 自维护的会话索引,不向 CLI 查询。
- 假设有一个或多个会话正在流式生成回复,当用户查看侧边栏,则每个正在生成中的会话条目的状态位都必须显示「运行中」标识(可同时存在多个;显示在条目的行状态位,视觉实现以 Figma 13656:5470 的 loading 环为准);运行中标识不受「仅后台会话」限制——正在激活对话中生成或已显示在其它分屏的会话同样在行上提示 loading(2026-09-09 拍板「选中的对话也应显示 loading」:即使运行状态在对话/分屏自身可见也不省略行上提示);对应会话生成结束后其标识消失。
- 假设某会话存在等待用户响应的确认请求(工具权限/编辑确认、计划确认、AskUserQuestion 等)且不在当前激活对话、也未显示在其它分屏,当用户查看侧边栏,则该会话条目必须显示「待确认」标识(与运行中标识以颜色区分;待确认与运行中同时成立时优先显示待确认;标识同样只在后台会话渲染——会话正显示在分屏时其确认对话框直接可见,不重复提示);用户响应确认或打开会话处理后其标识消失。
- 假设侧边栏会话树已加载,当用户点击当前目录下的某条会话,则必须恢复该会话到当前窗口;点击其他目录下的会话时,必须切换到该会话的工作目录再恢复;切换会话或目录不中止任何正在生成的后台会话。
- 假设侧边栏会话树已加载,当用户点击某条会话的删除图标并确认,则必须从索引中移除该会话;若该会话是 worktree 会话,还必须一并删除其 worktree 目录与临时分支(删除确认必须先按「删除 worktree 会话前提示将丢失的改动」展示将丢失的改动)。
- 假设用户已主动选择并打开过某主仓库(该仓库出现在「最近打开」列表),当用户点击侧边栏"新对话"或用
Cmd/Ctrl+Click、Cmd/Ctrl+Shift+N新建对话,则新对话的默认工作目录必须是「最近打开」列表中用户最近主动选择的仓库根,与上一会话的状态(是否为 worktree 会话、agent 工作目录是否被 bash 改写)完全无关;新会话页面的分支查询走该主仓库分支。 - 假设上一会话是 worktree 会话或其 agent 工作目录在会话中被 bash 工具
cd改写到了某个 worktree 路径,当用户新建对话,则该 worktree 路径不得被写入或覆盖「最近打开」列表(会话激活与 agent 工作目录变化均不触发「最近打开」写入,该列表只由用户主动选择/切换目录的动作更新),新对话的默认目录仍为用户最近主动选择的主仓库根,分支查询与「最近打开」列表均不残留该 worktree 路径。 - 假设用户从未主动选择过任何工作目录(「最近打开」列表为空),当用户点击侧边栏"新对话"或用
Cmd/Ctrl+Click、Cmd/Ctrl+Shift+N新建对话,则必须进入空白新对话界面(工作目录选择器显示"选择工作目录…"占位入口,不自动绑定任何工作目录,用户可稍后通过该入口选择目录),不得因列表为空而拒绝进入或静默无响应;当用户处于未选择工作目录的空白会话时,则发送入口整体禁用(输入区不可编辑、发送 / 附件 / 快捷指令 / 权限模式按钮一并置灰),并在输入框占位文案写明禁用原因「请先选择项目目录」(该原因与「未登录」互斥且登录优先,见 sso-auth.md 场景 8),不得在无工作目录的情况下启动会话。 - 假设用户在某会话的输入框中输入了尚未发送的内容(如"1"),当切换到另一个会话,则新会话的输入框必须显示其自身的草稿(此前未输入则为空),不得沿用上一会话的输入内容;当切回原会话,则输入框必须恢复此前未发送的内容——输入草稿按会话独立保存,同一分屏内切换会话不得共享草稿,会话在不同分屏间移动时草稿跟随会话。
- 假设用户在某会话输入内容后立即切换到另一个会话再切回(草稿保存尚未完成),当切回原会话,则仍必须恢复刚输入的内容;切换期间展示过的其他会话的输入框不得残留该内容,其草稿不得被写入该内容。
- 假设侧边栏会话树已加载且用户使用键盘,当用户连续按 Tab 键,则焦点必须按显示顺序逐条遍历每条会话条目主体(分组标题同为键盘可聚焦控件,保持可 Tab),不得把任何条目主体移出 Tab 序列;条目的删除按钮除外——它不得进入 Tab 序列,只能通过方向键到达。
- 假设焦点位于某条会话条目上,当用户按
↓或↑,则焦点必须在展开显示的会话条目之间移动(↓下一条、↑上一条,跨目录分组连续行进;到末条后继续↓回绕到首条,反之亦然),移动时列表自动滚动使目标条目完整可见。 - 假设焦点位于某条会话条目上,当用户按 Home 或 End,则焦点必须分别跳到第一条 / 最后一条会话条目上。
- 假设焦点位于某条会话条目主体,当用户按
→,则焦点必须移到该条目的删除按钮上,此时删除按钮变为可见并显示焦点指示;当焦点位于删除按钮上时按←,则焦点回到该条目主体;删除按钮本身不得进入 Tab 序列,只能通过方向键到达。 - 假设焦点位于某条会话条目主体,当用户按 Enter 或 Space,则必须恢复该会话(与普通点击一致);假设焦点位于删除按钮,当用户按 Enter 或 Space,则必须弹出与鼠标点击一致的删除确认对话框。
- 假设焦点位于某个目录分组标题(分组标题为键盘可聚焦控件),当用户按 Enter 或 Space,则必须切换该分组的折叠状态(与点击标题一致)。
- 假设用户在应用内任何位置按 Delete/Backspace 键,则不得因此触发任何会话的删除——删除会话不提供键盘快捷键,唯一路径是方向键到达条目的删除按钮后激活并经确认对话框完成。
- 假设应用刚启动且侧边栏无任何运行中会话——「最近打开」列表非空时应用已按场景 2 自动进入最近目录的新对话(聚焦分屏绑定了一个空会话,窗口处于新对话态)、列表为空时窗口处于选择工作目录的欢迎态;用户尚未主动点开任何历史会话,当用户查看侧边栏"新对话"按钮,则按钮必须保持可用(不得因当前尚无用户激活过的工作目录或会话而禁用);当用户点击该按钮,则宿主自行决定新会话的工作目录——「最近打开」列表非空时取最近主动选择的仓库根(场景 8),为空时进入空白新对话界面(场景 10)——不得要求用户先点开某条历史会话才解锁"新对话";自动初始化已绑定的空会话被再次"新对话"替换属预期(场景 1 语义,空 agent 被回收不残留)。
- 假设应用已通过侧边栏选中并激活某条会话(窗口进入会话态),当用户查看侧边栏"新对话"按钮,则按钮保持可用且行为与场景 1/8/10 一致(替换当前窗口开始全新会话,正在生成的原会话在后台继续)——按钮可用性不得在会话态与初始态之间跳变。
用户故事:会话重命名(侧边栏行内编辑)(优先级:P2)
作为桌面端用户,我希望在侧边栏的会话行上就地改标题,以便不用重开会话就能把某段对话标成自己认得出的名字。
为什么是这个优先级:标题目前不可改,只能靠首条消息截断;侧边栏会话树是高频入口,且行菜单已有「并排打开 / 删除会话」两项,加第三项成本低。存储侧(会话文件里的 custom-title 保留条目、列表读取与显示优先级)见 ui/session-management.md 的「会话自定义标题(重命名)」;CLI 与插件端的入口见同文件的「会话重命名入口:CLI 与 IDE 插件」。
独立测试:在侧边栏某会话行上打开行菜单点「重命名」,验证行内出现输入框且原标题被全选;输入新标题按 Enter 后行标题立即变化、重启应用后仍是新标题;再试一次按 Esc,验证标题回滚为原标题且未写盘。
验收场景:
- 假设侧边栏会话树已加载,当用户在某会话行上打开行菜单,则菜单必须含三项,顺序为「并排打开 / 重命名 / 删除会话」(在原两项之间插入);「重命名」不得使用删除项的危险配色。
- 假设行菜单打开且用户激活「重命名」,当进入编辑时,则该行标题必须被行内输入框就地顶掉(不是模态对话框),输入框自动聚焦、内容为当前标题并全选;列表不得进入 loading / 禁用 / 骨架状态。
- 假设用户正在行内编辑,当按 Enter 或点击输入框之外(blur),则必须保存新标题;行内形态不提供 Save 按钮。
- 假设用户正在行内编辑,当按 Esc,则必须退出编辑并回滚为原标题(不提交);假设输入内容 trim 后为空,当按 Enter 或 blur,则必须什么都不做(不发请求、标题不变、退出编辑)。
- 假设用户按 Enter 保存后输入框随即失焦,当保存流程执行时,则必须有一次性闩锁保证「Enter 紧跟的 blur」只提交一次,不得重复写盘。
- 假设用户使用中文/日文等输入法,当处于组合输入中(
isComposing),则不得把 Enter / Esc 当作保存 / 取消处理。 - 假设焦点在行内输入框,当用户按键,则输入框必须拦住按键冒泡,不得触发行上的裸键或行菜单快捷键。
- 假设用户提交新标题,当保存进行中,则必须乐观更新——侧边栏行标题、会话看板卡片、该会话(若在当前分屏)的头部标题立即一起变为新标题;假设保存失败,则必须回滚为原标题并给出可见的失败提示(不得只有静默回滚)。
- 假设新标题保存成功,当用户重启应用,则侧边栏该行必须仍显示自定义标题(标题随会话文件持久化,不依赖重启前的内存状态)。
- 假设用户把某会话重命名后继续在该会话中对话,当列表刷新或应用重启,则标题必须保持自定义标题,不得被「首条用户消息截断」的既有 first-wins 逻辑重新覆盖。
用户故事:SSH 远程主机(优先级:P2)
作为用户,我希望在工作目录选择器之前先选择「本地」或一个 SSH 主机(可添加新主机),让会话运行在远程主机上,以便我直接编辑远端服务器上的代码仓库(体验与 VS Code Remote-SSH 一致)。选中远程主机后,工作目录通过远程目录浏览面板以点击方式选择(如 VS Code Remote-SSH 的「打开文件夹」:路径输入框 + 面包屑 + 子目录列表),无需手输完整路径。
为什么是这个优先级:远程开发是独立于本地桌面体验的新能力,不影响本地会话基础能力;「运行在远端」的架构(整个 wave --stdio 子进程通过 ssh <host> -- wave --stdio 在远端执行,JSON-RPC 走 ssh 管道)使 agent 的 bash/文件/git 工具天然在远端文件系统上工作,与本地会话共享同一套 stdio 协议、通知路由与会话索引。
独立测试:配置 ~/.ssh/config 一个可用主机后,选择该主机,在远程目录浏览面板中逐级点击进入子目录并选择远端仓库根目录开始会话,验证 agent 的读写/命令/工具作用在远端;用 ssh user@host -p port 格式添加新主机验证写入配置文件并选中;断开网络或使用无密钥的主机验证快速失败与错误提示;重启应用验证主机选择重置为「本地」且最近打开列表保留。远程会话中让 agent 启动 dev server,点击消息中的 localhost 链接验证端口转发与预览面板加载、重复点击复用同一转发、服务停止后错误提示与重试、元素评论记录远端原始地址。
验收场景:
- 假设 用户打开应用并处于新会话状态,当 查看工作目录选择器,则 必须在其左侧显示主机选择器:首项为「本地」,其后为
~/.ssh/config中解析出的主机列表(顶层Host块名称,通配符条目除外),末项为「添加主机…」;默认选中「本地」,本地行为与现状完全一致。 - 假设 用户选中某台主机,当 选择生效,则 工作目录选择器必须切换到该主机的远程目录上下文:菜单列出该主机的最近打开远程目录与「浏览…」入口,不再提供本地目录浏览入口;分支选择器与 worktree 勾选项在远程 git 仓库目录下照常显示(git 命令经该主机在远端执行)。
- 假设 用户选中远程主机后通过远程目录浏览面板选定目录并确认,当 校验通过,则 必须将该(主机,路径)加入该主机最近打开列表并开始新会话,会话实际工作在远端路径;当 路径在远端不存在或不是目录,则 必须显示明确错误、不进入会话。
- 假设 用户选中远程主机后开始会话,当 会话运行,则 agent 的全部工具(bash/读文件/写文件/搜索/git、worktree 创建、分支查询、权限确认等)必须在远端主机上执行,行为与本地会话一致;消息流式、确认交互、任务列表、媒体附件等在 UI 中的呈现不变。
- 假设 用户点击「添加主机…」,当 输入
ssh user@hostname -p port形式的连接串并确认,则 必须将新主机写入~/.ssh/config(追加Host块并解析出 User/HostName/Port 选项),刷新主机列表并自动选中该主机;当 该主机名已存在,则 必须提示且不得覆盖或重复写入。 - 假设 远端连接需要密码或键盘交互认证,当 应用发起连接,则 不得挂起等待输入:必须以密钥/ssh-agent 方式快速失败,并显示可操作的错误提示(如「Permission denied (publickey)——请检查 SSH 密钥与 ssh-agent」);认证不要求任何用户交互。
- 假设 远端未安装
wave-codeCLI,当 应用连接该主机,则 必须把本机内置 CLI bundle(bin/wave-code.js+dist/bundle/wave.mjs+package.json)经 ssh 推送到远端固定目录~/.wave/cli/desktop/后继续启动流程(详见 desktop-shell.md「内置 CLI 一致保障」);当 远端无 Node.js(或主版本低于 22),则 必须显示安装 Node.js 的引导信息;当 远端缺少 grep 依赖 rg 且自取失败,则 必须显示含手动命令的错误信息,不得无限重试。 - 假设 远端 Node 主版本低于 22 或本机 ssh 命令不可用,当 应用连接,则 必须显示明确的引导错误(升级远端 Node / 安装 OpenSSH),不得进入不可用状态。
- 假设 会话从本地切换到远程主机(或反之),当 切换发生,则 会话与通知必须按(主机,sessionId)寻址,各主机会话互不串话;侧边栏会话树中远程会话与本地会话不得混入同一目录分组(分组键含主机)。
- 假设 某分屏展示的是远程会话,当 用户查看该分屏,则 终端、差异与预览面板必须全部可用:终端经本机 ssh 在远端分配 PTY 运行 shell,差异经 ssh 在远端执行 git 与文件读取,预览经本机 SSH 端口转发访问远端 localhost 服务;远程会话消息与终端输出中的 localhost 链接必须经端口转发后在预览面板中打开,不得用系统默认浏览器打开(本地浏览器访问不到远端 localhost)。
- 假设 用户关闭并重新打开应用,当 应用启动,则 主机选择必须重置为「本地」(与每次启动全新开始的规则一致);
~/.ssh/config主机列表与各主机最近打开目录在重启后仍可用。 - 假设 本地不存在
~/.ssh/config,当 用户打开主机选择器,则 必须仅显示「本地」与「添加主机…」,不得报错;首次添加主机时按需创建该文件。 - 假设 某分屏展示的是远程会话且分屏有打开的终端面板,当 用户向终端输入或调整终端尺寸,则 输入与尺寸调整必须经本机 ssh 会话转发到远端 PTY 中运行的用户默认 shell,工作目录为该分屏 agent 的当前工作目录(远端路径);当 远端连接中断或 shell 退出,则 终端面板必须显示退出状态/错误,与本地终端一致。
- 假设 某分屏展示的是远程 git 仓库会话,当 用户打开差异面板,则 必须显示该远程仓库相对 HEAD 的工作区差异(含未跟踪文件、二进制/超限文件按本地规则处理),git 命令与文件读取全部经 ssh 在远端执行,数据经 ssh 管道回传本机渲染。
- 假设 某分屏展示的是远程会话,当 用户点击该会话消息或终端输出中的 localhost 链接(如
http://localhost:5173),则 主进程必须按需建立 SSH 端口转发(ssh -N -L,本地端口默认与远端端口相同、被占用时递增至首个空闲端口,仅绑定本机回环 127.0.0.1),将链接重写为转发后的本地地址(如http://localhost:5173)并在预览面板中加载;同一(主机,远端端口)的转发必须复用,重复点击不得重复建立。 - 假设 远程 localhost 链接的端口转发已建立,当 远端服务不可达(未启动/已停止/连接中断)或转发建立失败(主机不可达、本地端口分配失败),则 预览面板必须显示可操作的错误提示与重试入口(与本地预览「无法连接」一致),不得白屏或静默失败;当 用户点击重试或再次点击链接,则 必须重新建立转发并加载。
- 假设 远程预览面板已打开且元素拾取可用,当 用户拾取元素并提交评论,则 评论中的页面 URL 必须记录远端原始地址(如
http://localhost:5173)而非转发后的本地地址,确保 agent 在远端可访问该 URL;拾取交互与评论格式与本地预览一致。 - 假设 远程会话已建立端口转发,当 该会话被删除(会话从树中移除),则 主进程必须终止对应
ssh -N -L转发进程并释放本地端口,不得残留孤儿进程;同一(主机,远端端口)转发被多个会话引用时,仅当最后一个引用会话删除后才终止。隧道所有权归会话而非面板:跨主机切换、同一对话中点击不同链接(无论路径还是 origin 不同)、关闭或重开预览面板、pane 卸载/移动、焦点切换等任何其他情况均不得销毁转发。ssh 转发进程自身意外退出(被动失败)与应用退出(进程终结的必然清理)同样终止转发。 - 假设 远端通过版本管理器(如 nvm)安装 Node.js,当 应用连接该主机,则 必须能检测到该 Node.js 并正常启动会话:Node 探测、rg 自取(npm)与 daemon 运行全部在用户登录 shell 的完整环境下执行(加载
.bashrc/.zshrc等启动文件中的 PATH 配置),CLI bundle 文件推送本身无需登录 shell 环境,不得因非交互 SSH 会话缺少 PATH 而误报「未检测到 Node.js」。 - 假设 用户选中远程主机后点击工作目录选择器的「浏览…」,当 打开远程目录浏览面板,则 面板必须呈现 VS Code「打开文件夹」式结构:弹层从触发按钮向上展开,底部固定于触发按钮上方,面板高度随加载/列表/筛选变化时输入框位置必须保持稳定(输入框固定在弹层底部,不得随高度伸缩上下移动);顶部为面包屑(路径各层级可点击跳转)、中部为当前目录的子目录列表(仅目录项、不含文件,按名称排序,点击进入子目录;非根目录时列表首项为「…」上级目录)、底部为筛选输入框与「选择此目录」确认按钮;筛选输入框支持输入关键词实时过滤当前单级目录列表并高亮匹配项(大小写不敏感的子串匹配,不匹配的目录项隐藏,过滤为空时显示无匹配提示),也支持输入
/或~开头的完整路径后按 Enter 直达(~开头自动展开为远端家目录);当 列表非空,则 用户按↑/↓必须在过滤后的子目录项中移动选中高亮(「…」上级项不参与),按 Enter 进入当前选中的子目录(无选中项时 Enter 仍按完整路径直达),点击某项同样将其置为选中;当 用户在筛选输入框中输入或修改关键词,则 存在匹配项时匹配列表必须默认选中第一项(无匹配项时无选中),无需方向键即可直接按 Enter 进入该匹配项;导航进入其他目录后选中状态必须重置;面板初开时定位到该主机的家目录。 - 假设 用户在远程目录浏览面板中浏览、输入关键词筛选或输入完整路径,当 目录列表经 ssh 在远端读取失败(连接中断、无读权限、路径非目录),则 面板必须显示明确的错误提示并停留在可重试状态,不得静默空白或直接进入会话;当 用户点击「选择此目录」或输入完整路径后按 Enter 确认,则 必须按场景 3 校验并开始会话。
- 假设 用户处于远程目录浏览面板,当 面板被关闭(点击外部、Esc)后再次打开,则 面板定位必须回到该主机最近一次浏览的位置或家目录,不得每次从根目录开始。
- 假设 用户处于新对话状态(分屏无任何消息)并将主机从「本地」切换到某台远程主机(或从一台远程主机切换到另一台),当 切换生效,则 工作目录选择器必须默认选中该主机最近打开目录列表的第一项(该主机最近打开列表为空时无默认目录,等待用户通过「浏览…」或最近目录菜单选择),不得沿用切换前主机的工作目录;用户不另作选择直接开始会话时,会话必须工作在该默认目录上。
- 假设 用户处于远程会话并通过「+ 上传文件」选择本地文件,当 上传完成,则 文件内容必须传输到远端主机并落盘到远端临时目录,回传并插入输入框的必须是远端主机上可访问的文件路径(如远端
/tmp/wave-artifacts/<文件名>),agent 的读文件等工具必须能在远端读取到该文件内容;当 远端上传失败,则 必须显示与本地一致的明确错误提示,不得静默成功。本地会话中该功能行为与现状完全一致(文件落盘到本地临时目录、路径为本地路径)。 - 假设 用户处于新会话状态并将主机从「本地」切换到某台远程主机(或从一台远程主机切换到另一台),当 新主机的工作目录已确定但该目录的分支查询尚未返回(需先建立 SSH 隧道与远端 daemon,可能耗时数秒),则 分支选择器与 worktree 勾选项位置必须在查询超过约 300ms 仍未返回时显示加载文案,不得整块消失且无任何反馈;查询返回后按该目录是否 git 仓库显示控件或隐藏(判定细则见「基于分支的 worktree 隔离会话」场景 7/8)。
用户故事:SSH 远程后台会话(优先级:P2)
作为用户,我希望远程主机上的会话在本地断开连接(ssh 中断、关闭桌面应用)后继续在远端后台运行,重新打开应用后能直接接回正在运行的会话并处理等待中的审批,以便我可以在本地不打开应用的情况下让远端任务持续执行。
为什么是这个优先级:远程开发(SSH 故事)已交付,但会话生命周期被 ssh 管道绑定——本地一断,远端任务即中断,与「后台」的预期相悖。真后台(远端 daemon 托管 + attach 接回)是远程开发形成闭环的关键能力,依赖 CLI daemon 化与 desktop 重连机制,改动横跨两层,优先级与 SSH 故事一致。
独立测试:在远程主机开始一个耗时任务后关闭应用并断开网络,验证任务在远端继续完成;重新打开应用连接该主机,验证会话从侧边栏直接接回(attach,非重放)且断线期间产生的消息可见、可正常发送与中断;在无客户端期间触发权限审批,验证会话挂起并标记「待确认」,重连进入后弹出确认框,确认后任务继续;验证侧边栏在未进入会话时即显示后台会话的「运行中」/「待确认」状态;删除后台会话验证远端 agent 终止;重启远端主机后验证历史会话仍可从转录恢复。
验收场景:
- 假设 某远程主机上存在正在运行(生成中/任务执行中)的会话,当 ssh 连接中断(断网、本地 app 退出或关闭窗口),则 远端会话必须脱离本地连接继续运行:正在执行的任务持续推进、生成中的回复继续生成,会话数据与运行状态在远端保留,不受本地断开影响。
- 假设 本地 app 重新打开并连接某台存在后台会话的远程主机,当 连接建立,则 应用必须自动检测该主机远端仍在运行的会话并将它们显示在侧边栏会话树(该主机分组下,附「运行中」/「待确认」标识),无需重新创建或手动恢复;当 用户点击其中某条会话,则 必须直接接入(attach)该运行中的会话——补回断线期间产生的消息与进行中的流式状态,用户可正常交互(发送消息、中断生成、响应确认),行为与从未断线一致。
- 假设 某后台会话在无客户端连接期间完成了生成或产生了新消息,当 用户重新连接并进入该会话,则 必须看到断线期间产生的全部消息,不得丢失或回退到断线前的状态。
- 假设 某后台会话(无客户端连接)执行到需要权限审批的步骤(bash 命令/文件编辑/计划确认等),当 审批请求产生,则 该会话必须挂起等待(任务不继续推进),审批请求在远端保留为「待确认」状态,等待用户响应;挂起期间该会话不得因无客户端而被丢弃或终止。
- 假设 远端存在处于「待确认」状态的会话,当 用户重新连接并进入该会话,则 必须弹出与在线会话一致的确认对话框(展示待审批的命令/改动内容),用户确认或拒绝后该会话继续执行或中止,行为与从未断线一致。
- 假设 远端 daemon 中存在运行中或待确认的后台会话,当 用户打开应用连接该主机(尚未进入任何会话),则 侧边栏对应会话条目前必须显示「运行中」/「待确认」标识(与现有标识规则一致:待确认优先于运行中),无需先进入会话即可感知远端会话状态;该状态在用户进入会话并处理(或审批完成)后消失。
- 假设 远端后台会话产生新的审批请求,当 审批产生,则 应用不得发送系统级通知(macOS 通知中心等),仅通过应用内呈现(侧边栏「待确认」标识 + 应用内 toast 提示 + 进入会话后的确认对话框)提示用户。
- 假设 同一远程主机的多个后台会话在远端同时运行,当 用户连接该主机,则 所有会话保持运行互不影响,任一会话均可接入查看与交互,会话与通知按(主机,sessionId)寻址,互不串话。
- 假设 用户删除一条后台会话,当 删除确认,则 远端对应 agent 必须终止(不再后台运行),worktree 目录与临时分支清理等既有删除逻辑照常执行。
- 假设 远端机器重启或远端 daemon 进程退出,当 用户重新连接该主机,则 断线前正在进行的任务终止(运行状态丢失),但历史会话仍可从侧边栏按现有恢复流程从远端转录恢复,不得出现幽灵「运行中」状态或脏数据。
- 假设 远端 daemon 正在运行,当 应用与之通信,则 通信必须全部经 ssh 隧道完成(如 unix socket 经 ssh 转发),daemon 不得监听公网地址或提供无认证访问;认证继续沿用现有 ssh 密钥机制,不新增凭据体系。
- 假设 本地会话与远程会话并存,当 用户连接远程主机或远程后台会话运行期间,则 本地会话行为与现有完全一致,不受远程后台模式影响;本地会话不启用后台保活(本地 app 退出即终止,与现状一致)。
用户故事:SSH 远程会话自动重连(优先级:P2)
作为用户,我希望远程会话的 ssh 隧道断开后(锁屏后系统睡眠唤醒、网络闪断、远端 sshd 重启),桌面应用在无需手动点击侧边栏的情况下自动重建连接并接回正在显示(或正在后台运行)的会话,以便长任务跨睡眠/断网持续可见、可交互,无需回到应用后手动找回会话。
为什么是这个优先级:「SSH 远程后台会话」已交付——会话在远端 daemon 进程里存活,断线只丢传输层。但本地连接断开后 UI 停在断线前状态,会话树「运行中」标识消失,用户必须手动点击侧边栏才能接回;锁屏睡眠唤醒是最常见的触发场景。自动重连补齐「断线 → 重新可见」的最后一段,实现集中在 desktop 侧(重建隧道 + 复用既有 restore/attach 路径),不触碰 daemon/CLI 协议,优先级与远程后台会话一致。睡眠唤醒后的头几十秒网络栈通常未就绪(Wi-Fi 关联、DHCP、认证),重连若在唤醒瞬间立即开始,全部次数都会浪费在无网络期而形同虚设——因此自动重连须感知唤醒事件,等待网络就绪宽限期后再启动。
独立测试:在远程主机开始长任务后锁屏并等待系统睡眠,唤醒后不点击任何会话,验证分屏自动重建隧道并接回该会话(断线期间产生的消息可见、运行状态继续);睡眠唤醒后延迟网络恢复(或唤醒后立即断网数秒),验证重连等待宽限期后才开始尝试、不把重试浪费在无网络期;断网后验证按退避间隔重试并在达到上限后停止、显示可操作的错误提示,无幽灵「运行中」状态;自动重连进行中点击其他会话或关闭分屏,验证以用户操作为准、不被自动重连覆盖;多分屏绑定同一主机会话时断开,验证全部自动接回且互不串话。
验收场景:
- 假设 桌面应用运行中,某分屏正显示远程主机的会话且任务在执行,当 ssh 隧道断开(网络闪断、远端 sshd 重启、系统睡眠唤醒后连接失效),则 应用必须自动重建该主机的隧道并重新接入(attach)该会话,无需用户点击侧边栏或重新打开会话;接入完成后该分屏恢复为与断线前一致的交互状态(发送消息、中断生成、响应确认),行为与从未断线一致。
- 假设 系统睡眠期间隧道呈半开状态(本地未收到 RST/FIN,连接看似存活但实际已失效),当 系统唤醒,则 应用必须在可预期的时间范围内感知隧道失效并触发重连,不得长时间停留在「看似连接但无任何更新」的状态;唤醒瞬间网络通常尚未就绪,重连启动须等待一个网络就绪宽限期(感知系统 resume 事件后延迟),不得将有限的自动重试次数浪费在无网络期。
- 假设 自动重连成功,当 会话重新接入,则 必须补回断线期间产生的全部消息与最新运行状态(生成/任务/后台任务),不得丢失或回退到断线前状态,与手动点击侧边栏接回的行为一致。
- 假设 断线期间远端会话产生新的权限审批请求并挂起,当 自动重连成功,则 必须重新弹出与在线会话一致的确认对话框,用户确认或拒绝后会话继续执行或中止;重连完成前该会话不得因无客户端而被丢弃。
- 假设 自动重连失败(远端不可达、认证失败、daemon 退出等),当 重试达到上限,则 必须停止自动重试,会话状态如实呈现(不得出现幽灵「运行中」标识);用户点击侧边栏该会话仍可手动重连,失败时显示与现有「恢复会话失败」一致的可操作错误提示。
- 假设 自动重连进行中,当 用户手动点击另一条会话、关闭分屏或切换主机,则 自动重连必须让位于用户操作:不得覆盖用户最新选择,也不得在用户已离开后把会话强塞回分屏。
- 假设 自动重连进行中(重建隧道 + 恢复会话耗时数秒),当 分屏等待接入,则 分屏必须显示明确的恢复指示(复用既有恢复加载动画或等效状态),不得表现为空白或无反馈;恢复完成或失败后指示消失。
- 假设 分屏绑定远程会话期间隧道断开,但断开发生前用户已切换到其他会话或关闭了该分屏,当 断开发生,则 应用不得自动重连(无需要接回的目标),维持现有懒重连行为——下次用户访问该主机/会话时再建立连接。
- 假设 同一远程主机的多个分屏绑定多个会话,当 隧道断开并自动重连成功,则 所有绑定该主机的分屏都必须接回各自会话,按(主机,sessionId)寻址互不串话。
- 假设 本地 stdio 会话,当 发生断连(仅存在于进程生命周期内,正常使用不触发),则 不启用自动重连,本地会话行为与现状完全一致。
- 假设 隧道在系统睡眠期间失效,当 系统唤醒后触发自动重连,则 重连序列须在等待网络就绪宽限期(感知系统 resume 事件)后才开始第一次尝试,且各次尝试间的退避间隔须拉长(如基数 5s 的指数退避)以覆盖唤醒后网络恢复所需的时间;宽限期内或退避窗口内网络恢复则自动接回,窗口结束仍未恢复则按场景 5 停止并提示手动重连。
用户故事:远程 daemon 常驻(空闲不退出)(优先级:P2)
作为用户,我希望远端主机上的 daemon 一经拉起即常驻运行——即使所有会话都已停止、会话内没有后台任务(后台 bash、子代理、工作流)且没有任何客户端连接也不自动退出,以便任务完成后再次连接仍接回同一进程,内存态(挂起审批、未落盘消息)持续保留;daemon 只在被外部回收时消失(CLI 升级重启 / 远端机器重启 / 手动 kill)。
为什么是这个优先级:原「空闲自动退出」在任务完成后回收 daemon,但会清空进程内存态并要求会话从磁盘转录重新载入。改为常驻后与桌面端既有回收机制配合——CLI 升级时重启 daemon 终止旧代码进程(见 desktop-shell.md「内置 CLI 一致保障」场景 3/4),远端机器重启时进程随系统消失;实现集中在 code 侧 daemon 服务器(移除空闲计时器),桌面端零改动。
独立测试:在远程主机开始一个耗时任务后关闭应用,验证任务完成(含所有后台任务/子代理结束)后 daemon 仍保持运行(socket 探测成功、同一进程仍在),期间重新打开应用连接直接接回同一 daemon;对 daemon 执行 CLI 升级重启或重启远端主机后重新连接,验证历史会话从远端转录恢复、无幽灵「运行中」状态。
验收场景:
- 假设 远端 daemon 中所有会话均已停止(无生成、无排队消息)、会话内无任何后台任务,且无客户端连接,当 该空闲状态持续(远超原 60 秒宽限期),则 daemon 必须保持运行不自动退出——socket 继续监听、同一进程继续服务;期间重新连接直接接回同一 daemon,进程内存中的会话注册表与挂起状态(含待确认审批)得以保留。
- 假设 远端 daemon 因 CLI 升级被重启(终止旧 daemon 并启动新 daemon,见 desktop-shell.md「内置 CLI 一致保障」)或远端机器重启,当 重新连接该主机,则 daemon 以新进程启动,历史会话从远端转录恢复,断线前正在进行的任务终止(与 daemon 退出的既有语义一致),不得出现幽灵「运行中」状态或脏数据。
- 假设 远端 daemon 启动后从未创建会话(无任何会话)且无客户端连接,当 该状态持续,则 daemon 同样保持运行不自动退出(不因「无会话」退出);无用的 daemon 由用户按既有方式回收(删除会话后 kill 进程或重启主机)。
用户故事:历史会话即时进入与恢复加载动画(优先级:P2)
作为用户,我希望点击侧边栏中的历史会话(尤其是远程主机上的会话)时,对话界面立即切换过去,并以已有的扫光加载动画示意会话正在恢复,以便我不再面对点击后数秒无反馈的空白等待。
为什么是这个优先级:远程会话的恢复需先建立 SSH 连接、再加载远端会话转录,耗时数秒;本地历史会话的恢复也走同一流程(只是更快)。即时进入 + 加载动画消除「点了没反应」的等待体验,不改变会话切换的任何既有语义。
独立测试:在远程主机完成一条会话后切走,再点击该会话,验证对话界面立即切换并以扫光动画显示加载,数秒后消息完整出现;加载期间点击另一条会话,验证最终显示最新点击的会话;断网后点击远程会话,验证加载动画消失并显示错误提示。
验收场景:
- 假设 侧边栏存在一条未在任何分屏展示的历史会话,当 用户点击该会话,则 目标分屏必须立即切换为该会话(不等待会话数据加载完成),消息与输入区域显示扫光加载动画示意正在恢复;恢复完成后动画消失、该会话消息完整显示。
- 假设 目标历史会话位于远程主机,当 用户点击该会话,则 行为与本地一致:分屏立即切换并显示加载动画,SSH 连接建立与远端会话转录加载均在动画期间完成,不得让用户在动画出现之前等待连接建立。
- 假设 用户以
Cmd/Ctrl+Click或拖拽将历史会话打开到新分屏,当 新分屏出现,则 新分屏必须立即显示该会话的加载动画直至恢复完成,不得先显示空白新会话界面。 - 假设 会话恢复进行中,当 用户又点击或切换到另一条会话,则 最终必须显示最新一次操作的会话;先前仍在恢复中的会话完成后不得覆盖当前显示(以最新操作为准)。
- 假设 会话恢复失败(远端不可达、连接中断、转录损坏等),当 恢复结束,则 加载动画必须消失,并在该分屏显示可操作的错误提示(与现状「恢复会话失败」一致),不得无限停留于加载动画。
- 假设 切换的目标会话已有活跃 agent 在后台运行(正在生成或挂起待确认),当 用户点击该会话,则 保持现状瞬时切换、不显示加载动画(无需恢复)。
- 假设 加载动画显示期间,当 用户查看该分屏,则 分屏头部与会话树选中态必须已切换为目标会话;动画覆盖消息与输入区域,用户不可输入、不可发送;VSCE/JetBrains 宿主无分屏概念,不受影响。
- 假设 会话恢复完成、消息首次渲染,当 用户查看该分屏,则 消息列表必须自动滚动到底部(最新消息完整可见);若吸顶用户消息随后在消息流顶部出现(滚动视口已滚出该用户消息),则 落点不得被其推高,仍须停在真底部(异步布局变化后补钉,VSCE/JetBrains 初始加载共用此行为)。
用户故事:基于分支的 worktree 隔离会话(优先级:P2)
作为用户,我希望在新会话页面选择分支并勾选 worktree,让会话在基于所选分支创建的临时 worktree 中进行,以便我并行试验而不污染主工作区。
为什么是这个优先级:worktree 隔离让用户在不污染主工作区的前提下并行试验,是桌面端"边改边试"的核心场景;创建与命名逻辑参考 CLI wave -w 的既有实现。
独立测试:选择一个 git 仓库作为工作目录,选择某分支并勾选 worktree 后开始会话,验证会话实际在新建的 worktree 目录中进行;不勾选时验证会话直接在所选目录中进行。在创建耗时较长(大型仓库)的场景下验证:发送首条消息后勾选处显示「worktree 创建中…」且勾选框不可操作,创建完成后文案消失、会话在 worktree 中正常开始。在远程主机(分支列表获取耗时较长)场景下验证:切换主机/目录后,分支列表获取超过约 300ms 未返回时,分支选择器与 worktree 勾选项位置显示加载文案,列表返回并确认该目录是 git 仓库后文案消失、控件出现;本地等快速返回的切换不出现加载文案(不闪烁);切到非 git 目录时最终不显示该组控件、加载文案一并消失。
验收场景:
- 假设用户选择的工作目录是 git 仓库,当处于新会话状态(无可见消息,隐藏的 meta 用户消息不计入),则工作目录选择器旁必须显示分支选择器与「worktree」勾选项,分支选择器默认选中当前分支,worktree 勾选项默认勾选。
- 假设用户勾选了 worktree,当开始会话,则主进程必须基于所选分支创建随机命名的临时分支与 worktree(命名方案与
wave -w一致),会话实际工作目录为该 worktree 路径。 - 假设用户未勾选 worktree,当开始会话,则会话必须直接在所选工作目录中进行(与现状一致)。
- 假设用户选择的工作目录不是 git 仓库或 git 命令不可用,当处于新会话状态,则不得显示分支选择器与 worktree 勾选项。
- 假设项目配置了 WorktreeCreate 钩子,当勾选 worktree 开始会话,则 worktree 创建由钩子接管(hook 自行执行
git worktree add并在 stdout 输出 worktree 路径,与wave -w行为一致);复用已存在的同名 worktree 时钩子仍执行(由 hook 自行决定如何处理已存在的路径)。 - 假设用户已勾选 worktree 并发送首条消息,当 worktree 创建进行中(大型仓库的
git worktree add可能持续数秒到数十秒,期间首条消息尚未进入会话、新会话页保持可见),则 worktree 勾选处必须显示「worktree 创建中…」文案向用户说明创建正在进行,且勾选框在此期间不可操作;无论创建成功(会话在 worktree 中开始)还是失败(错误已提示),创建中状态都必须被清除、文案消失。 - 假设用户处于新会话状态并切换/选定了某个工作目录(本地或远程主机),当新目录的分支查询正在进行或尚未返回(首次连接远程主机时需建立 SSH 隧道与远端 daemon,可能耗时数秒到数十秒),则分支选择器与 worktree 勾选项位置必须在查询超过约 300ms 仍未返回时显示加载文案(如「分支加载中…」)——查询在该阈值内返回则不得显示,避免本地快速切换闪烁;加载期间不得显示旧目录的分支名,且占位不得引起控件位置跳动;查询返回并确认新目录是 git 仓库后文案消失、显示控件;若新目录不是 git 仓库或 git 不可用,则文案一并消失、按场景 4 处理(不显示控件)。
- 假设用户处于新会话状态且当前工作目录是 git 仓库(分支选择器与 worktree 勾选项正在显示),当用户把工作目录切换到非 git 仓库目录,则旧目录的分支名不得闪现——分支查询结果按所查询的目录区分,控件显隐最终只由当前目录是否 git 仓库决定:目录一切换即隐藏旧控件,确认非 git 后保持隐藏。新目录的分支查询若在约 300ms 内返回(本地快速切换),则切换过程及完成后均不得出现加载占位(无闪烁);当查询超过该阈值仍未返回,则按场景 7 在该位置显示加载文案,返回确认非 git 后文案消失。
用户故事:删除 worktree 会话前提示将丢失的改动(优先级:P1)
作为用户,我希望在删除 worktree 会话前看到该 worktree 里将丢失的未提交文件与未合并提交,以便我不会在毫不知情的情况下丢掉工作。
为什么是这个优先级:删除会话会连同 worktree 目录与临时分支一起删除,是不可撤销的破坏性操作,而在侧边栏只需两次点击即可触发;CLI 退出对话框与 ExitWorktree 工具都已有改动提示(见 multi-agent/worktree.md),桌面端删除路径此前是唯一没有任何保护的入口。
独立测试:在 worktree 会话中留下未提交改动与未合并提交,从侧边栏删除该会话,验证确认对话框列出未提交文件数与未合并提交数;取消后会话、worktree 目录与临时分支全部保留;确认后按既有语义删除。再在干净的 worktree 会话上重复,验证不出现「改动将丢失」提示。
验收场景:
- 假设待删除的 worktree 会话存在 N(N>0)个未提交改动的文件,当用户点击该会话的删除入口,则确认对话框必须列明未提交文件数量(如「N 个未提交文件」)并说明删除后不可恢复。
- 假设待删除的 worktree 会话的临时分支上有 M(M>0)个未合并到基线的提交,当用户点击删除入口,则确认对话框必须列明未合并提交数量,并说明该临时分支将被删除。
- 假设待删除的 worktree 会话既无未提交改动也无未合并提交,当用户点击删除入口,则确认对话框不得出现「改动将丢失」类提示,仅保留常规删除确认。
- 假设确认对话框已列明将丢失的改动,当用户取消,则会话、worktree 目录与临时分支三者都必须原样保留。
- 假设用户确认删除,则按既有删除语义移除会话索引、worktree 目录与临时分支。
- 假设待删除会话位于远程主机,当用户点击删除入口,则改动检查必须在远端执行且结论与本地一致(见「远程会话删除/清理」边界情况);远端不可达或检查无法完成时,则必须退回为通用提示「该会话的 worktree 目录与临时分支将一并删除,未提交的改动将丢失」,不得阻塞删除流程,也不得静默省略提示。
- 假设改动检查尚未返回(远程主机或大型仓库可能耗时),当用户查看确认对话框,则对话框必须处于「正在检查」状态且此时不可确认删除(不得在状态未知时一键删除);检查返回后按场景 1-3 呈现结果,用户此时取消则关闭对话框、不执行任何删除。
用户故事:会话切换快捷键(优先级:P3)
作为用户,我希望用键盘快捷键在会话之间循环切换,以便无需鼠标点击侧边栏即可在多个并行会话间移动(与 Claude Code Desktop 的 Ctrl+Tab 体验一致)。
为什么是这个优先级:多会话并行后切换频次上升,快捷键是效率增强,非基础能力。
独立测试:创建多条会话(含跨目录分组),按 Ctrl+Tab / Ctrl+Shift+Tab 验证正/反向按侧边栏树顺序循环激活会话;macOS 上另按 Cmd+Shift+] / Cmd+Shift+[ 验证等效;查看应用菜单栏验证存在对应菜单项并显示快捷键。
验收场景:
- 假设侧边栏会话树有多条会话,当用户按下「下一个对话」快捷键,则必须按会话树的展平顺序激活当前会话的下一条(跨目录分组),当前为末尾时回卷到第一条;「上一个对话」反向循环,当前为第一条时回卷到末尾。
- 假设目标会话属于其他目录分组,当快捷键切换发生,则行为与鼠标点击该条目完全一致:切换到该会话的工作目录(worktree 会话恢复到其 worktree 路径),且不中止任何后台生成中的会话。
- 假设折叠分组中包含会话,当循环经过该分组,则其会话照常参与循环(折叠仅影响展示,不影响循环顺序)。
- 假设会话树为空或仅当前一条会话,当用户按下快捷键,则无任何效果。
- 假设用户查看应用菜单栏,则必须存在「下一个对话」「上一个对话」菜单项并显示对应快捷键,点击菜单项与按快捷键等效。
用户故事:后台会话确认提醒(优先级:P2)
作为 CodeWave IDE 桌面端用户,我希望后台会话需要我确认(侧边栏条目出现等待确认的琥珀色状态点)时,应用内弹出 toast 提示,以便我在查看其他会话时不会错过等待我操作的会话。设计依据参考 Claude Code Desktop:权限请求的提醒只在用户未查看该会话时出现(被查看的会话已有确认对话框,toast 只添乱)。toast 仅服务于完全没有分屏承载的会话——只要会话已显示在某个分屏(无论是否当前焦点分屏),其确认对话框都直接可见,不再额外弹 toast。后台会话完成任务的提醒由侧边栏「已完成未读」绿点承载,不弹 toast(2026-09-10 拍板:移除「已完成」toast,「已完成」的唯一提醒通道是绿点)。
为什么是这个优先级:多会话并排是桌面端核心场景,后台会话的琥珀色状态点只在侧边栏可见,窗口聚焦在其他应用或用户盯着别的分屏时极易错过;toast 复用既有 ToastStack 与 showToast 通道,改动集中在 host 触发点,风险低。系统级通知(macOS 通知中心等)不在本故事范围——既有远程会话约束(见「SSH 远程后台会话」场景 7)保持不变,本故事仅使用应用内 toast。
独立测试:在分屏 A 发起会触发确认的会话、切到分屏 B(两分屏并排显示),验证分屏 A 的会话出现确认请求时不弹 toast(对话框已在并排可见的分屏 A 弹出);关闭分屏 A 使该会话转入后台,再次触发确认,验证弹出带「查看」按钮的 toast,点击「查看」重新打开分屏并展示确认对话框;对无分屏承载的后台会话跑一个短会话,等待其完成,验证侧边栏条目出现「已完成未读」绿点且不弹 toast。
验收场景:
- 假设某会话未在任何分屏显示(如分屏被关闭后会话继续在后台运行、远端会话未打开,侧边栏条目出现等待确认的琥珀色状态点),当确认请求到达,则弹出应用内 toast,文案包含会话标题与确认类型(如「命令执行待确认」),并带「查看」按钮。
- 假设会话已显示在某个分屏(无论是否当前焦点分屏——多屏并排时用户能直接看到该分屏),当确认请求在该分屏弹出对话框,则不弹 toast(避免重复打扰)。
- 假设后台会话的确认 toast 已弹出,当用户点击「查看」按钮,则应用切换到该会话所在分屏并展示对应的确认对话框(该会话无分屏展示时按既有「历史会话即时进入」逻辑在新分屏打开)。
- 假设同一会话在等待确认期间连续发起多个确认请求,当后续请求到达,则不重复弹 toast(一个等待周期只弹一次;该会话所有待确认请求全部清除后再有新请求,视为新周期可再弹)。
- 假设多个后台会话同时等待确认,当确认请求到达,则各会话独立弹出 toast,互不覆盖。
- 假设远端后台会话(无本地分屏)产生确认请求,当请求到达,则同样弹出应用内 toast(应用内呈现,不发送系统级通知)。
- 假设后台会话的确认 toast 弹出后用户未做任何操作,当超过既有自动消失时长,则toast 按 ToastStack 既有规则处理——确认 toast 带「查看」按钮,不自动消失,等待用户处理或手动关闭。
- 假设后台会话的确认 toast 已弹出,当用户通过分屏切换(
Ctrl+Tab/Ctrl+Shift+Tab、点击分屏、侧边栏选择等)使该会话进入当前焦点分屏,则对应 toast 移除——确认对话框随会话可见弹出(场景 3),toast 不残留重复提示;该会话随后离开焦点分屏不重新弹 toast(同一等待周期只提示一次)。 - 假设某后台会话在后台退出计划模式(
ExitPlanMode待确认,仅弹 toast、无 UI 计划面板),当用户通过侧边栏点击/分屏切换/「查看」使该会话回到前台,则该会话的 Plan 面板自动打开并渲染计划全文(与前台会话触发ExitPlanMode时一致);无论宿主推送给 webview 的desktopPanes(分屏重新绑定)与携带待确认请求的会话快照是被 React 合批为一次提交还是分次提交,Plan 面板都必须出现,不得因会话切换的重置被抹掉。
用户故事:会话状态看板(优先级:P2)
作为用户,我希望点击侧边栏品牌行的「活动」按钮打开会话状态看板,按状态(等待中/运行中/已完成)三列总览全部会话,并按项目筛选,以便快速定位某状态下的会话并跳转。
为什么是这个优先级:看板是桌面端独有视图(VSCE/JetBrains 无侧边栏不提供),依赖已有的会话树数据,实现成本低,是批次 2 的原型功能之一。
独立测试:desktop 点击侧边栏「活动」按钮进入看板,验证三列分类(等待中=待确认、运行中=正在生成、已完成=其余)、列头计数、项目筛选联动、点击会话卡片跳回对应会话、右上「返回当前会话」回到原视图、再次点击「活动」按钮关闭看板、侧边栏「活动」按钮高亮态。
验收场景:
- 假设desktop 侧边栏品牌行可见,当用户点击「活动」按钮,则会话区切换为会话状态看板视图,品牌行「活动」按钮呈高亮态;看板顶部显示标题「会话状态」与项目筛选下拉,左上提供「返回当前会话」按钮。
- 假设看板已打开,当用户查看三列,则必须为「等待中 / 运行中 / 已完成」三列,列头各带状态色圆点、列名与计数(状态语义对齐视觉设计语言:等待=琥珀、运行中=中性灰、成功=绿)。
- 假设会话树中存在会话,当看板渲染,则每张会话卡片显示标题与所属项目(目录分组名),点击卡片必须恢复该会话并退出看板视图;某列无会话时显示「暂无会话」。
- 假设用户选择项目筛选下拉中的某项目,当筛选生效,则三列仅显示该项目(目录分组)下的会话,计数同步更新;选择「全部项目」恢复全量。
- 假设用户在会话进行中(有会话正在生成/待确认)打开看板,当状态变化,则看板必须实时反映最新状态(会话从「运行中」移入「已完成」等),无需手动刷新。
- 假设用户在看板视图点击「返回当前会话」,当操作生效,则回到打开看板前的会话视图(分屏布局与选中会话保持),「活动」按钮高亮态取消。
- 假设看板已打开,当用户再次点击侧边栏品牌行的「活动」按钮,则看板关闭并回到会话视图(分屏布局与选中会话保持),「活动」按钮高亮态取消——与点击「返回当前会话」等价(按钮为开关:未打开则打开,已打开则关闭)。
用户故事:跨客户端会话来源(/resume 的磁盘会话列表)(优先级:P1)
作为桌面端用户,我希望 /resume 能列出当前对话所属主机磁盘上存在的会话——包括命令行或其它客户端创建的会话,以便不必回到终端就能继续任意一段对话(当前对话在本地就列本地,在某个远程主机上就列那台主机)。
为什么是这个优先级:桌面端会话树只收录本应用创建并登记的会话(见「会话管理」),CLI 那边开的会话在桌面端完全不可见;/resume 的磁盘来源是让桌面端与 CLI / 插件端行为对齐的最小改动,也是"在桌面端接着昨天那段 CLI 对话"的唯一入口。
为什么只列当前主机:桌面端其余与主机相关的设置(登录态、最近目录、会话索引)都是"当前主机"语义,会话列表沿用同一规则可以避免"列表里混着三台机器的同名项目";跨主机查看会话不属于本故事,用户切换主机后再次 /resume 即得该主机的列表。
独立测试:用 CLI 在某个项目目录创建一段会话(桌面端从未登记过它),在本地对话中输入 /resume,验证列表中该会话存在且标注项目路径、选中后当前分屏显示该会话历史、后续消息追加到该会话原文件;切到一台远程主机的对话后再次 /resume,验证列表换成该主机磁盘上的会话(不出现本地会话);断开该主机后重新打开选择器,验证给出主机不可达提示、当前会话不受影响。
验收场景:
- 假设当前对话在本地主机且磁盘上存在会话(含桌面端、CLI、其它客户端创建的),当
/resume打开会话选择器时,则以居中模态对话框承载(含搜索框与列表,渲染在应用根层,分屏状态下同样可用),本地全部项目目录下的会话都被列出,每行标注项目路径;会话索引中不存在的会话同样出现。 - 假设当前对话属于某台远程主机,当
/resume列出会话时,则列表为该主机磁盘上的会话(经该主机客户端读取),每行标注项目路径,不混入本地或其它主机的会话。 - 假设用户在多个分屏间切换焦点(各分屏可能属于不同主机),当再次打开
/resume时,则列表随之切换为当前焦点分屏所属主机的会话——范围始终由"当前对话所属主机"决定。 - 假设当前主机不可达(远端连接失败、超时、daemon 未启动),当
/resume列出会话时,则给出可见的失败提示(无法连接主机/无法读取会话),不得长时间挂起、也不得静默显示空列表让用户以为没有历史会话;当前会话与内容不受影响。 - 假设用户选中的会话属于当前主机,当确认选择时,则经该主机恢复该会话(与从会话树进入历史会话的既有路径一致),消息与后台任务状态按既有语义加载。
- 假设选中的会话所属目录在该主机上已不存在(
ssh test -d判定目录确实不存在),当确认选择时,则不切换、提示「该会话的目录已不存在」,当前分屏与内容保持原状;仅探测失败(主机不可达)时不得据此判定目录缺失。 - 假设会话记录在磁盘上缺失或损坏(CLI 报
Session … not found on disk),当用户确认选择时,则与既有历史会话恢复行为一致:给出提示,并按既有规则处理该条目(本地条目从会话索引移除)。 - 假设用户成功恢复一条会话索引中不存在的会话(CLI / 其它客户端创建的),当该会话进入分屏后,则该会话被登记进桌面端会话索引(按当前主机登记)——出现在侧边栏会话树与状态看板中,并在此后重启应用(且仍连接该主机)时仍可再次进入;登记使用该会话磁盘记录中的工作目录等元数据,不改写会话文件本身。
边界情况
远端 Node.js 缺失或版本过低:连接远程主机时若远端 Node.js 缺失或主版本低于 22,应用必须显示安装/升级远端 Node.js 的引导信息,而不是抛出原始错误。
远端 rg 注册表不可达(SSH 远程适用):远端 rg 自取(
npm install --prefix ~/.wave/cli安装@vscode/ripgrep)使用 npmmirror 注册表以提高国内可达性;失败时给出手动命令提示(含 registry 参数),运行中的旧 CLI/daemon 不受影响,网络恢复后重连自动补装。工作目录不存在或被删除:会话绑定的 cwd 失效时,agent 文件操作应报错且 UI 能显示该错误。
非 git 工作目录或 git 不可用:不显示分支选择器与 worktree 勾选项,会话直接在该目录进行。
worktree 创建耗时较长:大型仓库的
git worktree add可能耗时数十秒,期间首条消息尚未进入会话、新会话页保持可见,必须在 worktree 勾选处显示「worktree 创建中…」文案,避免用户误以为界面卡死;创建完成或失败后文案消失,勾选框在创建期间禁用。worktree 创建失败:分支名冲突、磁盘错误等导致 worktree 创建失败时,必须向用户显示错误,不得静默回退为在原目录开会话。
删除 worktree 会话时 worktree 已被外部删除:git 清理命令失败不得阻塞删除流程,仅删除索引记录并向用户告警。
删除 worktree 会话时改动检查无法完成(worktree 目录已被外部删除、git 不可用、远端主机不可达):确认对话框退回为通用提示(「该会话的 worktree 目录与临时分支将一并删除,未提交的改动将丢失」),用户确认后照常执行既有删除逻辑——检查失败既不得阻塞删除,也不得省略提示。
删除 worktree 会话时改动检查迟迟无响应(宿主假死、或宿主走了提前返回分支未回复
desktopWorktreeChanges):确认对话框等待一个明确长于正常远端往返的时限后,必须自行退回为通用提示(「该会话的 worktree 目录与临时分支将一并删除,未提交的改动将丢失」)并放开确认按钮——不得因检查不回而永久停在「正在检查该 worktree 的改动…」、永久禁用确认,使该会话无法从 UI 删除。超时后到达的迟到回复必须被丢弃(不得把对话框打回「正在检查」状态或重新禁用已放开的确认按钮);超时只是放弃精确统计,不等于「无改动」,因此落点是通用警告而非无提示。删除 worktree 会话时会话内 checkout 了其它分支:只删除该会话自己的临时分支;会话内 checkout 或新建的其它分支必须保留(见
multi-agent/worktree.md的「删除 worktree 只删除自己的临时分支」)。删除当前活跃会话:允许删除;删除后回到新会话页(销毁该会话的 agent——若为 worktree 会话,须先切回原仓库目录再清理 worktree)。
快捷键触发时会话树为空或仅当前一条:无效果(不报错、不切换)。
快捷键循环经过磁盘上已删除目录下的会话:与点击行为一致——提示目录不存在,从会话索引移除该条记录。
远端主机不可达时不得删除会话/最近目录(SSH 远程适用):目录存在性校验(如
ssh test -d)失败必须区分「远端不可达」(ssh 连接失败、主机离线、探测超时)与「目录确实不存在」。当 远端不可达,则 会话索引记录与最近工作目录必须保留(不得按「目录已删除」逻辑清理),仅提示无法连接主机、用户可稍后重试;当 确认目录确实不存在,则 按既有逻辑从会话列表/最近列表移除。后台会话请求权限确认:后台(非活跃)会话发起工具确认/AskUserQuestion 时不弹窗打断当前视图,该会话挂起等待,侧边栏条目显示"待确认"标识;切回该会话时再弹出对应确认,用户响应后该会话继续。
CLI 侧创建的会话不出现在侧边栏:会话索引仅收录 desktop 内创建的会话,CLI 侧历史会话不展示、也不受影响。
不可见会话的确认请求:不在任何分屏展示的会话发起工具确认/AskUserQuestion 时维持后台挂起规则(见多会话并行的后台挂起规则),重新可见时再弹出;已在分屏中展示的会话的确认直接在该分屏内显示。
行状态位渲染规则:会话条目行状态位(「运行中」loading 环、「待确认」琥珀点、「已完成未读」绿点,见 Figma 13656:5470)中,「运行中」对任何正在生成的会话渲染——含处于当前激活对话或已显示在其它分屏的会话(其运行状态虽在该对话/分屏内直接可见,行上仍提示 loading;2026-09-09 拍板「选中的对话也应显示 loading」,反转原「仅后台会话」限制);「待确认」与「已完成未读」仍仅对后台会话提示——会话处于当前激活对话或已显示在其它分屏时不渲染这两类标记(其状态在该对话/分屏内直接可见,打开会话即视为已处理、不再重复提醒);会话重新落入后台(切走、关闭分屏)后按当时状态恢复标记。三类标记共存时按「待确认 > 运行中 > 已完成未读」优先级提示(后台会话;激活/可见会话只可能出现「运行中」)。
后台会话「已完成未读」绿点(newCompleted):后台会话在无人查看期间完成任务(一轮生成结束且无待确认请求、该会话未显示在任何分屏)时,其条目状态位显示绿点;会话被打开/聚焦到任一分屏或开始新一轮生成后绿点消失。与「运行中/待确认」同会话同时成立时按既有优先级提示。绿点是「已完成」的唯一提醒通道——已完成不弹 toast(2026-09-10 拍板,移除既有「已完成」toast)。
后台会话确认 toast 与既有提示的关系:确认 toast 是「侧边栏琥珀色状态点/运行标识」之外的补充提醒通道,只覆盖确认请求一种事件(「已完成」由绿点承载,不弹 toast);toast 不改变既有后台挂起规则(会话仍需用户切换过去处理确认);同一会话的确认对话框始终只在用户查看该会话时弹出,toast 的「查看」按钮只是把用户带过去的入口。该 toast 保留既有右下角通知形态(
position: "bottomRight"),不并入应用级全局 toast 的顶部居中新形态(位置/形态与配色路由见 desktop-account-and-settings.md)。远端 CLI 缺失且远端无 npm/无出网:CLI 文件本身经 ssh 推送(不经 registry),远端无 npm 或无出网也可完成推送;但其 grep 依赖 rg 需远端自取,远端无 Node.js/npm 或无法访问 registry 时,应用必须显示含手动命令的错误提示(在远端执行
npm install --prefix ~/.wave/cli安装@vscode/ripgrep)与重试提示,不得抛出原始错误、不得无限重试。远端 Node 版本过低:远端 Node 主版本低于 22 时,应用必须提示升级远端 Node.js。
ssh 命令不可用:本机未安装 ssh(如 Windows 未启用 OpenSSH 客户端)时,应用必须给出安装指引,不得直接抛错。
~/.ssh/config 不存在或不可读:主机列表为空(仅「本地」与「添加主机…」),不报错;添加主机时按需创建文件。
~/.ssh/config 写入失败(权限不足、文件被占用等):添加主机失败时必须显示错误,不得影响本地会话。
添加主机名冲突:与现有
Host同名时提示,不覆盖、不重复写入。首次连接主机密钥未知:自动接受并写入 known_hosts(
StrictHostKeyChecking=accept-new);known_hosts 中密钥与远端不符时连接失败并提示用户手动执行ssh <host>排查。连接超时或主机不可达:在 ConnectTimeout 内快速失败并提示,不得长时间挂起。
远程会话删除/清理:删除远程 worktree 会话时通过该主机客户端在远端执行清理,与本地清理行为一致;ssh 子进程随应用退出一并终止。
恢复加载期间切换/关闭分屏:历史会话恢复加载中用户切走或关闭该分屏时,先前的恢复完成后不得覆盖当前显示(以最新操作为准);恢复完成推送时目标分屏已不存在则静默丢弃,不得报错或影响其他分屏。
恢复加载期间删除会话:历史会话恢复加载中用户在侧边栏删除该会话,恢复完成后不得重新出现(会话索引以删除为准);加载动画随分屏状态一并清除。
会话记录写入被中断(应用/CLI 在消息追加中途退出):会话 JSONL 末行若是不以换行结尾的残缺记录(中断的未完成写入),恢复时必须丢弃该残片并载入其前全部已完整写入的消息,不得把整个会话判为不可恢复(2026-09-09 拍板:容忍截断尾行)。完整消息批次恒以换行结尾(见 agent-sdk jsonlHandler),无尾换行即唯一标识中断场景;中段行损坏仍属真损坏。
会话记录在磁盘上不存在或中段损坏:会话恢复失败且原因为文件缺失/损坏(CLI 报
Session … not found on disk/Session not found:,本地与远端一致),当 恢复结束,则 必须自动从会话列表移除该条目(会话索引与输入草稿一并清理,最近工作目录保留)并 toast 提示原因(如「会话记录在磁盘上不存在或已损坏,无法恢复,已从列表移除」),分屏回落新会话态——不得向对话流推送原始英文错误、不得让条目留在列表中可被反复点击制造重复报错。仅「文件缺失/损坏」类错误走自动移除;网络不可达、连接中断等瞬时错误维持现状(保留条目与错误提示,见上文「远端主机不可达时不得删除会话」)。/resume的磁盘来源与会话树的关系:磁盘扫描只服务/resume选择器本身,侧边栏会话树与状态看板沿用会话索引语义;被/resume恢复的索引外会话按故事场景 7 登记进索引(此后两条来源一致),未被恢复的磁盘会话不进入索引——两条来源并存是刻意设计,不是待清理的重复。/resume只列当前主机:范围恒为"当前对话所属主机"(本地对话即本地,远程对话即该主机),与登录态、最近目录、会话索引的既有语义一致;不做跨主机聚合,也不因存在其它已配置主机而扩大范围。切换主机或焦点分屏后再次打开选择器即为新主机的列表。/resume在当前主机不可达时:给出可见失败提示(无法连接主机/无法读取会话),不得长时间挂起、不得静默显示空列表(会误导用户以为没有历史会话)、也不得据此清理会话索引(见「远端主机不可达时不得删除会话」);当前会话与内容不受影响。/resume的目录缺失判定沿用既有口径:只有确认目录确实不存在(ssh test -d返回不存在)才提示「目录已不存在」;探测失败(主机不可达)不得按目录缺失处理,也不得据此清理会话索引(见上文「远端主机不可达时不得删除会话」)。同一段会话被多处同时使用:
/resume不检测会话是否正在终端或另一客户端中被使用,也不因此拒绝切换(本轮不做存活检测,见 ui/session-management.md 边界情况)。行内编辑态按会话独立:编辑态按会话 ID 记忆,一个会话的行内编辑不影响其它会话,编辑中切换会话不串标题。
对正在流式生成的会话改名:允许(改标题不影响生成),行状态位的「运行中」标识保持不变。
自定义标题长度上限:输入框与宿主持久化边界均限制 200 字符(口径见 ui/session-management.md);超长输入不入库,不得出现「输入框显示一半、落库另一半」。
远端会话重命名:走该会话所属主机的通道写入远端会话文件;主机不可达时按失败回滚处理,不得只把标题写进本机索引造成两端不一致。
会话索引 title 与 SDK 自定义标题的关系:侧边栏、会话看板与桌面头部读的仍是 desktop 会话索引的
title(既有语义不变,索引是 desktop 自维护的缓存);重命名必须同时把标题写进会话文件的custom-title保留条目(唯一权威来源,见 ui/session-management.md)。既有的「索引已有 title 就早退」逻辑正好保护手改标题不被首条消息重新推导覆盖;反过来重命名后若只写文件不更新索引,界面不会变化——两处必须一起更新。
非目标(明确排除)
- 按序号直跳会话(如
Cmd+1~9跳到第 N 条会话):首版不做,仅顺序循环。 - 快捷键清单面板(如
Cmd+/弹出快捷键速查):首版不做。 - VSCE/JetBrains 宿主的会话切换快捷键:IDE 自身占用
Ctrl+Tab(编辑器切换器),不提供。 - 折叠分组头部的状态聚合提示:运行中/待确认标识仅在会话条目上显示,分组折叠时不在分组头部做聚合上卷。
- SSH 交互式认证:仅支持 SSH 密钥与 ssh-agent,不支持密码/键盘交互式输入。
- 本地 stdio 模式的生命周期:本地
--stdio维持现状(随 stdin 关闭/宿主退出而终止),不常驻;仅 daemon(--daemon)常驻,且仅面向远端主机。 - 远程会话的差异/终端/预览面板:远程会话支持全部三类面板(差异/终端经 ssh 执行、预览经 SSH 端口转发),面板打开/复用可用;本地会话不受影响。
- SSH 端口转发:按 localhost 链接点击按需建立
ssh -N -L转发(仅绑定回环、引用按会话持有);隧道销毁时机仅一种——删除该会话(从树中移除);跨主机切换、同一对话点击不同链接(无论路径还是 origin 不同)、关闭/重开预览面板、pane 卸载/移动、焦点切换均不销毁;ssh 转发进程自身意外退出(被动失败)与应用退出(dispose)时同样终止并释放本地端口;不提供常驻转发列表或手动端口管理界面,转发进程退出后本地端口立即释放。 - 远端目录浏览的深度:浏览面板仅做单级目录列表(点击进入子目录/上级、面包屑跳转、输入完整路径确认);筛选关键词仅作用于当前单级列表,不做跨层级模糊搜索;目录项经 ssh 在远端读取;不做递归目录树、文件预览、目录收藏与重命名/创建等文件管理操作。
- 主机管理界面(编辑/删除):首版仅提供「添加主机…」写入
~/.ssh/config,编辑/删除由用户直接修改配置文件。 - ~/.ssh/config 的 Include 与通配符解析:主机列表仅展示本文件顶层
Host块名称(跳过通配符);连接参数由 ssh 自身按配置文件解析。 - 主机选择与远程连接状态持久化:重启回到「本地」与全新开始,不恢复上次主机选择;远程连接不跨重启复用。
- 会话状态看板在 IDE 宿主:看板为 desktop 独有(依赖侧边栏「活动」入口),VSCE/JetBrains 不提供。
- 会话看板的拖拽/批量操作:看板仅支持点击卡片跳转,不做拖拽排序、批量删除、暂停/恢复会话等操作。
/resume的 fork / 参数形式 / transcript 预览:本轮不做"复制为新会话"(fork)、不做/resume <id|搜索词>参数形式、不做会话内容预览;也不含会话存活检测与外部会话的信任确认(后两者另开议题)。- 桌面端会话改名的入口:侧边栏会话行的行菜单,加上聊天面板头部标题就地编辑(头部是 VSCE / JB / desktop 共用的 webview 组件,三端一起获得,行为见 ui/session-management.md「会话重命名入口:CLI 与 IDE 插件」);不提供全局快捷键(不照抄 Claude Code 的
⌘/Ctrl+⌥+R),不在会话看板卡片与/resume(含头部「历史对话」)列表上提供改名入口。 - CLI 与插件端的入口分工:CLI 走
/rename命令 + 选择器内Ctrl+R;插件端只有头部标题。三端共用同一份存储与显示优先级(见 ui/session-management.md)。 - AI 自动生成标题:不做(无额外模型调用、无
ai-title保留条目),没有自定义标题时继续用首条用户消息截断兜底。