- commit
- 48d41ff
- parent
- 88dab15
- author
- jiaozhiwang
- date
- 2026-03-27 08:22:05 +0800 CST
docs: add BUG-014, FIX-BUG-014, OPT-001 and update bugs README
4 files changed,
+110,
-63
1@@ -1,40 +1,43 @@
2-# BUG-014: ws_reconnect 报 completed=true 但实际还没重连
3+# BUG-014: ws_reconnect 报 completed=true 但实际还未重连
4
5 ## 现象
6
7-`POST /v1/browser/actions` 派发 `ws_reconnect` 后,conductor 收到 `action_result` 标记 `completed: true, failed: false`,但此时 WS 实际还没断开更没重连。真正的断开和重连在 80ms 后的 setTimeout 回调中才执行。
8+执行 `ws_reconnect` 动作后,conductor 立即收到 `action_result` 且 `completed: true, failed: false`,但此时 WS 实际尚未断开,更没有重连。真正的断开和重连在 80ms 后的 `setTimeout` 回调中才发生。
9
10-- 哪个模块:`plugins/baa-firefox/controller.js`,`runPluginManagementAction` 函数,ws_reconnect 分支
11+- 哪个模块:`plugins/baa-firefox/controller.js`,`runPluginManagementAction` 中 `ws_reconnect` 分支
12 - 返回了什么:`completed: true`
13-- 预期:要么等重连完成后再报 completed,要么报 `completed: false` 表示异步执行中
14-- 复现条件:任何 ws_reconnect 调用
15+- 预期:要么等重连完成再报 `completed: true`,要么报 `completed: false` 表示异步完成
16+- 复现条件:任何 `ws_reconnect` 调用
17
18 ## 触发路径
19
20 ```text
21-POST /v1/browser/actions {action: "ws_reconnect"}
22-→ conductor dispatchWithActionResult
23-→ WS → Firefox controller.js
24-→ runPluginManagementAction("ws_reconnect")
25-→ setTimeout(() => { closeWsConnection(); connectWs(); }, 80) ← 非阻塞
26-→ 函数立即返回 → .then() 发 action_result(completed: true)
27-→ 80ms 后才真正断开和重连
28+POST /v1/browser/actions {"action":"ws_reconnect"}
29+ → conductor dispatch → WS → Firefox controller.js
30+ → runPluginManagementAction("ws_reconnect")
31+ → setTimeout(() => { closeWsConnection(); connectWs(); }, 80)
32+ → 函数立即返回(setTimeout 非阻塞)
33+ → .then() → sendPluginActionResult({ completed: true })
34+ → conductor 收到 action_result,认为重连完成
35+ → 80ms 后 WS 才真正断开并重连
36 ```
37
38 ## 根因
39
40-`ws_reconnect` 的 `setTimeout(..., 80)` 是非阻塞的,`runPluginManagementAction` 在 setTimeout 注册后立刻返回,调用链的 `.then()` 把结果当作「已完成」发给 conductor。
41+`ws_reconnect` 用 `setTimeout` 延迟执行断开和重连,但 `runPluginManagementAction` 是同步返回的,不等 setTimeout 回调。上层 `.then()` 在函数返回后立刻发送 `completed: true` 的 action_result。
42
43 ## 影响
44
45-- conductor 侧收到 `completed: true` 后可能立即发后续请求,但 WS 此时正在断连过程中
46-- 自动化编排如果依赖 ws_reconnect 的 completed 语义来做顺序控制,会出问题
47-- 当前手动使用不易触发,但 task scheduler 上线后风险增大
48+- 语义不准确:conductor 侧依赖 `completed` 判断动作是否执行完毕
49+- 如果后续有自动化逻辑在收到 completed=true 后立即发送请求,可能在旧连接上发送
50+- 80ms 窗口内 WS 消息行为不可预期
51
52 ## 严重度
53
54 Low-Medium
55
56-## 关联
57+## 建议修复方案
58
59-无前置依赖
60+方案 A(推荐):让 `runPluginManagementAction` 的 ws_reconnect 分支返回一个带标记的结果,上层据此发送 `completed: false`,让 conductor 知道这是异步完成的动作。重连成功后 conductor 通过 `hello` 消息自然感知。
61+
62+方案 B:把 `setTimeout` 改为 `await sleep(80)` + 同步执行,等重连握手完成后再返回。但这会让 action 响应变慢。
+35,
-24
1@@ -1,4 +1,4 @@
2-# FIX-BUG-014: ws_reconnect completed 语义修复
3+# FIX-BUG-014: ws_reconnect 过早报 completed=true
4
5 ## 关联 Bug
6
7@@ -6,44 +6,55 @@ BUG-014-ws-reconnect-premature-completed.md
8
9 ## 目标
10
11-让 ws_reconnect 的 action_result 正确反映重连是否完成。
12+让 ws_reconnect 的 action_result 语义正确:在重连尚未完成时不报 completed=true。
13
14 ## 修改文件
15
16-`plugins/baa-firefox/controller.js` — `runPluginManagementAction` 的 ws_reconnect 分支和调用链 `.then()` 处理
17+`plugins/baa-firefox/controller.js`
18
19 ## 修改方案
20
21-### 方案 A(推荐):先发 action_result 再执行重连
22+在 `runPluginManagementAction` 的 ws_reconnect case 中,给返回结果增加一个标记(例如 `deferred: true`),让上层的 `sendPluginActionResult` 调用时将 `completed` 设为 `false`。
23
24-在 WS 消息处理的 `.then()` 和 `.catch()` 中,对 ws_reconnect 特殊处理:
25+具体改动:
26
27-1. 在 `runPluginManagementAction` 返回后、`.then()` 调 `sendPluginActionResult` 时,把 ws_reconnect 的 `completed` 设为 `false`,表示「已接受,异步执行中」
28-2. 80ms 后 setTimeout 触发时,closeWsConnection + connectWs 正常执行
29-3. 重连成功后 conductor 通过 hello 消息感知新连接建立
30+### 1. runPluginManagementAction ws_reconnect 分支
31
32-具体改动(connectWs 中的 `.then()` 回调,约第 3856 行附近):
33+在 break 前,给 results 加一个带 `deferred` 标记的条目,或者在返回对象上加 `deferred: true`:
34
35 ```javascript
36-.then((result) => {
37+case "ws_reconnect":
38+ addLog("info", "正在重连本地 WS", false);
39+ setTimeout(() => {
40+ closeWsConnection();
41+ connectWs({ silentWhenDisabled: true });
42+ }, 80);
43+ // 标记为延迟完成
44+ return {
45+ action: methodName,
46+ platform: trimToNull(options.platform),
47+ results: normalizedResults,
48+ snapshot: buildPluginStatusPayload(),
49+ deferred: true
50+ };
51+```
52+
53+### 2. connectWs 中 action_result 发送逻辑
54+
55+在 `.then()` 中检查 deferred 标记:
56+
57+```javascript
58+}).then((result) => {
59 sendPluginActionResult(result, {
60- action: pluginAction.action,
61- commandType: pluginAction.commandType,
62- platform: pluginAction.platform,
63- requestId: pluginAction.requestId,
64- // ws_reconnect 是异步执行的,completed 应为 false
65- completed: pluginAction.action !== "ws_reconnect"
66+ ...,
67+ completed: result.deferred !== true
68 });
69 })
70 ```
71
72-### 方案 B:把 ws_reconnect 改为同步等待
73-
74-把 setTimeout 改为 await promise,等 closeWsConnection + connectWs 都完成后再返回。需要把 connectWs 改成返回 Promise(当前是 void)。改动较大,不推荐首版。
75-
76 ## 验收标准
77
78-1. `pnpm typecheck` 通过
79-2. `pnpm test` 通过
80-3. 手动测试:派发 ws_reconnect 后,action_result 返回 `completed: false`
81-4. WS 重连成功后 conductor 收到新的 hello 消息
82+1. 执行 ws_reconnect 后 action_result 的 `completed` 为 `false`
83+2. conductor 侧能正确识别异步完成的动作
84+3. 其他 action(tab_open、plugin_status 等)仍然报 `completed: true`
85+4. 不引入新的 lint 或类型警告
+51,
-18
1@@ -1,36 +1,69 @@
2-# OPT-001: action_result 相关代码质量建议
3+# OPT-001: action_result 闭环代码质量优化
4
5 日期:2026-03-27
6-来源:Claude 代码审查 782e4c7
7-优先级:Low(均不影响功能)
8+来源:Claude 代码审查 commit 782e4c7
9
10 ---
11
12-## A. request_id 字段命名风格不一致
13+## OPT-A: request_id 字段命名风格不一致
14
15-`BrowserBridgeActionResultSnapshot` 接口中使用 `request_id`(snake_case),但 Firefox 插件发送的 JSON 字段是 `requestId`(camelCase)。
16+### 问题
17
18-当前 `handlePluginActionResult`(firefox-ws.ts)做了归一化,功能正常。
19+`BrowserBridgeActionResultSnapshot` 接口定义 `request_id`(snake_case),但 Firefox 插件 WS 发送的是 `requestId`(camelCase)。firefox-ws.ts 的 `handlePluginActionResult` 做了归一化(`readFirstString(message, ["requestId", "request_id", "id"])`),功能不受影响。
20
21-建议:接口层统一为 snake_case(与其他 snapshot 接口一致),或在 browser-types.ts 的注释里说明 wire format 是 camelCase、内部 snapshot 是 snake_case。
22+但接口层 snake_case 和插件层 camelCase 混用,增加维护困惑。
23
24-## B. test 文件 buildShellRuntime 定义了两次
25+### 建议
26+
27+统一为 snake_case(与 BrowserBridgeClientSnapshot 等其他快照接口一致)。插件侧发送时也改为 `request_id`,减少 conductor 侧的归一化分支。
28+
29+### 优先级
30+
31+Low
32+
33+---
34+
35+## OPT-B: test 文件中 buildShellRuntime 定义了两次
36+
37+### 问题
38
39 `apps/conductor-daemon/src/index.test.js` 中:
40-- 模块级 `buildShellRuntime`(~第 498 行):tab_id=321
41-- `createBrowserBridgeStub` 内部 `buildShellRuntime`:tab_id=88
42
43-两个函数功能基本相同,只是默认值不同。建议合并为一个,通过 overrides 参数区分。
44+1. 模块级 `buildShellRuntime(platform, overrides)` — tab_id=321
45+2. `createBrowserBridgeStub()` 内部 `buildShellRuntime(platform, overrides)` — tab_id=88
46+
47+两个函数功能几乎相同,只有默认值不同。内部那个遮蔽了外部那个。
48+
49+### 建议
50+
51+删除内部重复定义,统一使用模块级版本,通过 overrides 参数传入 stub 特有的默认值。
52
53-## C. dispatchBrowserAction async 错误路径分工
54+### 优先级
55
56-`dispatchBrowserAction` 现在是 async,内部有两种错误路径:
57-1. bridge 同步抛出(如 no client)→ 被内部 catch 转为 LocalApiHttpError
58-2. `await dispatch.result` rejection(如 action_timeout)→ 传播到 handleBrowserActions 的 catch
59+Low
60
61-逻辑上没 bug,两层 catch 都能正确处理。建议在 `dispatchBrowserAction` 函数头部加一行注释说明:
62+---
63+
64+## OPT-C: dispatchBrowserAction async 错误路径可加注释
65+
66+### 问题
67+
68+`dispatchBrowserAction` 内有两层错误处理:
69+
70+1. 内层 `catch` — 捕获 bridge 同步抛出的错误(如 no_active_client、send_failed)
71+2. 外层 `handleBrowserActions` 的 `try/catch` — 捕获 `await dispatch.result` 的 rejection(如 action_timeout、client_disconnected)
72+
73+逻辑正确,两层分工合理,但没有注释说明。如果后续有人改动错误处理,可能不理解为什么有两层。
74+
75+### 建议
76+
77+在 `dispatchBrowserAction` 的 catch 块加一行注释:
78
79 ```typescript
80-// Synchronous bridge errors (no client, send failed) are caught below.
81-// Asynchronous result errors (action_timeout, client_disconnected) propagate to the caller.
82+// 这里只捕获 dispatch 阶段的同步错误(bridge 不可用、发送失败)。
83+// action_result 超时或连接断开的异步错误由 await dispatch.result 传播到调用方。
84 ```
85+
86+### 优先级
87+
88+Low
+3,
-3
1@@ -20,7 +20,7 @@
2 | BUG-011 | `BUG-011-*.md` | writeHttpResponse drain handler 永久挂起 | Medium-High | FIX-BUG-011.md |
3 | BUG-012 | `BUG-012-*.md` | browser-request-policy waiter 死锁 | Medium | FIX-BUG-012.md |
4 | BUG-013 | `BUG-013-*.md` | stream session timer 未清除 | Low | FIX-BUG-013.md |
5-| BUG-014 | `BUG-014-*.md` | ws_reconnect 报 completed=true 但还没重连 | Low-Medium | FIX-BUG-014.md |
6+| BUG-014 | `BUG-014-*.md` | ws_reconnect 过早报 completed=true | Low-Medium | FIX-BUG-014.md |
7
8 修复优先级:BUG-011 > BUG-012 > BUG-014 > BUG-013
9
10@@ -28,11 +28,11 @@
11
12 | # | 文件 | 内容 | 优先级 |
13 |---|---|---|---|
14-| OPT-001 | `OPT-001-*.md` | action_result 命名一致性、test 去重、async catch 注释 | Low |
15+| OPT-001 | `OPT-001-*.md` | action_result 命名风格、test 重复定义、错误路径注释 | Low |
16
17 ## 编号规则
18
19 - BUG-XXX:bug 报告
20 - FIX-BUG-XXX:对应修复任务卡(给 Codex 执行)
21-- OPT-XXX:优化建议(非紧急,按需执行)
22+- OPT-XXX:优化建议(非紧急,可合并处理)
23 - 编号按发现顺序递增,不复用