Skip to content

fix(multi-server): 服务器作用域请求统一使用当前绑定的 serverId - #169

Open
rayn1314 wants to merge 3 commits into
lehhair:mainfrom
rayn1314:fix/server-scoped-request-stale-serverid
Open

rayn1314 wants to merge 3 commits into
lehhair:mainfrom
rayn1314:fix/server-scoped-request-stale-serverid

Conversation

@rayn1314

Copy link
Copy Markdown
Contributor

起因

我在 Windows 上主力用 WSL 跑 opencode,也在做 WSL 服务器管理那个 PR(#164)。用着用着发现一个很别扭的问题:在 WSL 的对话里,模型一提问(带选项的卡片),卡片能正常显示,但不管我选选项、输入自己的回答还是跳过,都点不动——点完卡片会消失一下(像发送成功了),过一会儿又冒出来,对话也不往前走。权限确认弹窗其实也一样,只是我没第一时间反应过来是同一回事。

排查过程

我让 AI 排查了这个问题,重点是「面板绑定的服务器」这条线:

  1. 面板绑定的服务器(paneServerId)在面板生命周期里会变——启动时活动服务器是 local,WSL sidecar 就绪后 wslStore 才把活动服务器切过去;切换会话当然也会变。而面板本身不会重挂(key 只是 paneId)。
  2. usePermissionHandler 里三个回复回调(权限回复、提问回复、提问跳过)的依赖数组是空的 []serverId 被冻在面板首次渲染时的值上。同一个 hook 里的 refreshPendingRequests 却是 [serverId](每次拿新值)——这一新一旧的不对称,正好解释「卡片消失又回来」。
  3. 为了确认不是我在猜,写了个临时探针测试(renderHook,先 localrerenderwsl:Ubuntu,然后分别调用三个函数,把各自收到的 serverId 打出来):
replyQuestion        serverId = "local"        ← 错,回复发到了本地
rejectQuestion       serverId = "local"        ← 错,跳过也发到了本地
getPendingQuestions  serverId = "wsl:Ubuntu"   ← 对,所以又把 WSL 上的提问拉回来了

数据出来就对上了:卡片显示走 SSE(按服务器分别订阅,是对的),回复走 HTTP(用的是旧 serverId,发到 local 报错),于是 WSL 那条请求一直 pending,刷新一拉就"复活"。探针脚本用完删掉了。

  1. 既然是一类问题,就把这条线走到底。用仓库已经在用的 ESLint 规则 react-hooks/exhaustive-deps 扫全仓:同类漏声明 serverId 的地方一共 29 条告警(还有 1 处规则看不到但同样会漏:useChatSession 的 SSE 回调)。逐个判断了哪些真会把请求打到旧服务器(被 routeSessionId 守卫的那几处不会,因为那种情况下 paneServerId 是它的纯函数),然后一起修掉。
  2. 还有一处是顺着数据流读出来的,ESLint 抓不到:task 工具在匹配子 session 的权限/提问卡片时,拿消息 metadata 里的原始 session id 去调 splitSessionKey,而这个函数碰到不带 :: 前缀的 id 会直接回退到「全局活动服务器」。于是子 agent 再起一层子 session 时,只要活动服务器和面板绑定的服务器不是同一台,那张卡片就永远匹配不上——和前面是同一个根因,只是换了个入口。修法是把权威 serverId 从面板一路传下来,而不是让匹配器去猜。

修复内容

  1. usePermissionHandler:三个回复回调的依赖数组 [][serverId],并在 hook 上写明约定「凡发起请求的回调都必须声明 serverId」
  2. useChatSession:补齐 paneServerId 依赖——SSE 回调、agents 列表、@ 目录与斜杠命令预取、待处理请求刷新、发送消息、fork、中止、执行命令、归档;同时把这条约定写进 paneServerId 定义处的注释(并标注例外:被 routeSessionId 守卫的单元)
  3. 其余服务器作用域单元一并补齐:useFileExplorer(4)、BottomPanel(4)、SessionChangesPanel(3)、SessionChildrenSlot(3)、ProjectDialog(2)、RightPanel(1)、Terminal(1)
  4. task 工具的子 session 匹配:InlineToolRequestContext 新增 TaskChildSessionRef(子 session key + 面板绑定的权威 serverId),context 值加上必填 serverId,复合 key 用它合成,不再走 splitSessionKey 的「裸 id 回退活动服务器」;ChatPanepaneServerId 发布进 context,ToolPartView / MessageRenderer 跟着透传
  5. 新增回归测试两条:面板从 local 切到 wsl:Ubuntu 后,回复与跳过必须打到 wsl:Ubuntu;活动服务器是 local 而面板绑定 wsl:Ubuntu 时,子 agent 下一层会话的权限/提问卡片必须能匹配上。两条都是把修复回退就会红的(前者 AssertionError: expected "vi.fn()" to be called with …,后者 expected undefined to be { id: 'perm-1', … }

验证

  • typecheck:0 错误
  • ESLint:src 下 0 错误 / 43 警告(与改动前基线一致);该类 serverId 漏声明告警 29 → 0
  • 测试:93 个文件 / 598 个用例全部通过
  • build:成功
  • 手工验证的路子(给 reviewer 参考):桌面端 F12 → Network 筛 question,点一个选项,看那条 POST 打到哪个端口——修复前是 127.0.0.1:4096(local)并报错,修复后是 WSL sidecar 的随机端口且 200。子 agent 那条路径同理:在别的服务器上开会话跑一个会再下一层的 task,看孙层卡片能不能点动
  • 顺带一处测试稳定性修改:ConfigSettings.search.test.tsx 挂了完整 ConfigSettings 和配置编辑器弹窗,单跑 0.5 秒,但全量并发跑会超过 5 秒默认 testTimeout 被误判超时(我这边一加测试文件就复现)。给它单独放宽到 20 秒并写了注释,断言没动。这一步是独立的 commit,跟上面的修复无关,觉得不合适可以只挑第一个

小提示:我本机上有个未跟踪的 opencode/ 参考目录会被 eslint . 和 vitest 扫到(它只在 WSL 分支的 cfd5ce6 里被排除),所以本地验证用的是 eslint srcvitest --dir src;CI 全新 clone 不受影响。

说明

这个缺陷的引入点是 main 里已有的多服务器提交(75a00b64),不是 #164 带来的,WSL 只是最容易触发它的场景(活动服务器是异步切过去的),所以我单独开了这个 PR,而不是塞进 #164

Fixes #168

- 修复 usePermissionHandler 的权限与提问回复回调:空依赖数组把 serverId 冻结在首次渲染的值上,切换服务器后回复被发到旧服务器,表现为弹窗消失又复现、对话不前进
- 补齐 useChatSession 中 paneServerId 依赖:SSE 回调、agents 列表、@ 目录与斜杠命令预取、待处理请求刷新、发送消息、fork、中止、命令执行、归档
- 补齐其余服务器作用域单元的 serverId 依赖:useFileExplorer、BottomPanel、RightPanel、SessionChangesPanel、Terminal、ProjectDialog、SessionChildrenSlot
- 新增回归测试:断言面板切换服务器后权限与提问回复打到当前服务器;该测试在修复前会失败
- 校验:typecheck、eslint src、vitest --dir src(92 文件 / 596 用例)、build 全部通过
task 工具匹配子/孙 session 的权限与提问卡片时,把消息 metadata 里的原始
session id 交给 splitSessionKey;该函数遇到不带 :: 前缀的 id 会回退到全局
活动服务器。多服务器 / WSL 场景下面板绑定的服务器与活动服务器不是同一台,
孙层请求因此永远匹配不上,卡片点不动。

- InlineToolRequestContext: 新增 TaskChildSessionRef(子 session key + 权威
  serverId),context 值补必填 serverId,复合 key 由它合成,不再猜服务器
- ChatPane: 把 paneServerId 发布进 context,并补进 memo 依赖
- ToolPartView / MessageRenderer: 调用点透传 serverId
- 新增契约测试:活动服务器为 local、面板绑定 wsl:Ubuntu 时,孙层的权限与
  提问必须能命中
该用例挂载完整 ConfigSettings 与配置编辑器弹窗,是 jsdom 下最重的路径之一:
单跑 0.5 秒通过,但全量并发跑会超过 5 秒默认 testTimeout 被误判为失败。
给它单独设 20 秒并写明原因,断言与流程都未改动。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: 多服务器/WSL 下权限与提问的回复打到旧服务器,卡片消失又复现、对话不前进

1 participant