现象
用 WSL 服务器(或任意第二台服务器)聊天时,只要 AI 提出一个问题(带选项的卡片),卡片能正常渲染,但不管我选选项、输入自己的回答还是跳过,都像点不动一样:
- 点完卡片会消失一下(看起来像发送成功了)
- 过一会儿同一张卡片又冒出来
- 整个对话不往前走了,AI 那边也没继续
权限确认弹窗是同一个毛病(批准/拒绝之后弹窗又回来,实际没生效)。
子 agent(task 工具)跑出来的会话里,如果它自己又起了下一层子 session,那一层的权限/提问卡片也是点不动——同一个根因的另一条路径,只是要跑到子 agent 里才碰得到。
复现步骤(不需要 WSL,两台服务器就能复现)
- 设置里打开多服务器,添加一台非 local 的服务器(本机另一个端口 / 远端 / WSL 都行)
- 重启应用,让侧边栏焦点停在 local,面板处于首页(还没开会话)
- 打开那台服务器上的一个会话
- 让 AI 提一个问题,然后点选项
预期:答复生效,对话继续。
实际:卡片消失又复现,对话卡住。
定位
面板绑定的服务器(paneServerId)在面板生命周期内会变:启动时活动服务器是 local,WSL sidecar 就绪后 wslStore 才把活动服务器切过去;切换会话也会变。而 usePermissionHandler 里三个回复回调(权限回复、提问回复、提问跳过)的依赖数组是空的,serverId 被冻在面板首次渲染的值上。
于是:卡片显示走 SSE(按服务器分别订阅,这部分是对的)→ 卡片正常渲染;回复走 HTTP(用的是冻结的旧 serverId)→ 请求发到 local,local 上没有这条请求所以报错;代码又把失败的卡片乐观地收起来,而真实服务器上那条请求仍在 pending,刷新时又被拉回来。这就是"消失一下又回来 + 对话不前进"的来源。
同一个 hook 里 refreshPendingRequests 的依赖是 [serverId](每次拿新值),回复是 [](永远旧值)——一新一旧的不对称正好对上现象。
顺带用仓库里已经开启的 ESLint 规则(react-hooks/exhaustive-deps)扫了一遍,同类漏声明 serverId 的地方全仓一共 29 条告警(useChatSession、useFileExplorer、BottomPanel、RightPanel、SessionChangesPanel、Terminal、ProjectDialog、侧边栏子会话列表等),切服务器后同样会把请求打到旧服务器。
还有一处规则看不见:task 工具匹配子 session 的权限/提问卡片时,拿消息 metadata 里的原始 session id 去调 splitSessionKey,而这个函数遇到不带 :: 前缀的 id 就直接回退到「全局活动服务器」。pane 绑定的不是活动服务器时(多服务器 / WSL),子 agent 下一层的卡片永远匹配不上。这条线 ESLint 抓不到,得顺着数据流读。
影响范围
- 多服务器场景(含 WSL 服务器):权限确认、提问回复、文件树/变更面板/终端等服务器作用域操作都可能打到旧服务器
- 子 agent(task 工具)产生的下一层会话里,权限/提问卡片在活动服务器 ≠ pane 服务器时匹配不上
- WSL 最容易触发,因为活动服务器是 sidecar 就绪后异步切换的
- 引入点是 main 里已有的多服务器提交 75a00b6,与 WSL 功能本身无关
环境
- Windows 11 桌面端(Tauri 2),opencode 服务器含 local + WSL
- 浏览器端多服务器同样可复现
现象
用 WSL 服务器(或任意第二台服务器)聊天时,只要 AI 提出一个问题(带选项的卡片),卡片能正常渲染,但不管我选选项、输入自己的回答还是跳过,都像点不动一样:
权限确认弹窗是同一个毛病(批准/拒绝之后弹窗又回来,实际没生效)。
子 agent(task 工具)跑出来的会话里,如果它自己又起了下一层子 session,那一层的权限/提问卡片也是点不动——同一个根因的另一条路径,只是要跑到子 agent 里才碰得到。
复现步骤(不需要 WSL,两台服务器就能复现)
预期:答复生效,对话继续。
实际:卡片消失又复现,对话卡住。
定位
面板绑定的服务器(paneServerId)在面板生命周期内会变:启动时活动服务器是 local,WSL sidecar 就绪后 wslStore 才把活动服务器切过去;切换会话也会变。而
usePermissionHandler里三个回复回调(权限回复、提问回复、提问跳过)的依赖数组是空的,serverId被冻在面板首次渲染的值上。于是:卡片显示走 SSE(按服务器分别订阅,这部分是对的)→ 卡片正常渲染;回复走 HTTP(用的是冻结的旧 serverId)→ 请求发到 local,local 上没有这条请求所以报错;代码又把失败的卡片乐观地收起来,而真实服务器上那条请求仍在 pending,刷新时又被拉回来。这就是"消失一下又回来 + 对话不前进"的来源。
同一个 hook 里
refreshPendingRequests的依赖是[serverId](每次拿新值),回复是[](永远旧值)——一新一旧的不对称正好对上现象。顺带用仓库里已经开启的 ESLint 规则(
react-hooks/exhaustive-deps)扫了一遍,同类漏声明serverId的地方全仓一共 29 条告警(useChatSession、useFileExplorer、BottomPanel、RightPanel、SessionChangesPanel、Terminal、ProjectDialog、侧边栏子会话列表等),切服务器后同样会把请求打到旧服务器。还有一处规则看不见:task 工具匹配子 session 的权限/提问卡片时,拿消息 metadata 里的原始 session id 去调
splitSessionKey,而这个函数遇到不带::前缀的 id 就直接回退到「全局活动服务器」。pane 绑定的不是活动服务器时(多服务器 / WSL),子 agent 下一层的卡片永远匹配不上。这条线 ESLint 抓不到,得顺着数据流读。影响范围
环境