- commit
- 7c7e4c5
- parent
- 6391ada
- author
- im_wower
- date
- 2026-03-29 03:54:51 +0800 CST
docs: update T-BUG-029 with root cause from diagnostic logs Root cause identified via T-S054 plugin logs: 1. Main SSE stream (/backend-api/f/conversation) is aborted, chunks discarded 2. isRelevantStreamUrl too broad, matches auxiliary endpoints 3. Need to extract candidate from chunks even on abort Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 files changed,
+77,
-18
+77,
-18
1@@ -41,17 +41,40 @@
2
3 ## 目标
4
5-修复 ChatGPT 使用思考模式(o3/o4 模型)时,final-message 提取器只捕获到 "Thought for a few seconds" 思考标记,漏掉真正 assistant 回复的问题。
6+修复 ChatGPT final-message 捕获完全失败的问题。当前 ChatGPT 回复的 baa 指令永远无法被提取。
7
8 ## 背景
9
10-手动验证 ChatGPT 闭环时发现:ChatGPT 回复包含 baa 指令块,但 conductor 收到的 raw_text 是 "Thought for a couple of seconds"。
11+通过 T-S054 的插件诊断日志(`logs/baa-plugin/`)已定位到根本原因。**不是之前猜测的 thinking 模式问题**,而是两个并发问题:
12
13-ChatGPT 的思考模式 SSE 流结构:
14-- 先发 thinking/reasoning 相关的 message(status 为 in_progress)
15-- 再发真正的 assistant 回复(status 为 finished_successfully 或 completed)
16+### 根本原因 1:主对话 SSE 流被 abort
17
18-`extractChatgptCandidateFromText` 可能把 thinking 标记当成了最终回复。
19+ChatGPT 当前的主对话 SSE 流路径是 `/backend-api/f/conversation`(新路径,带 `/f/` 前缀)。该流在传输过程中被浏览器或 ChatGPT 前端 abort:
20+
21+```
22+[SSE] tab=8 chatgpt error /backend-api/f/conversation duration=-ms error=The operation was aborted.
23+```
24+
25+`observeSse` 中当 `detail.done !== true` 时返回 null,而 abort 不会触发 `done=true`,所以已收集的 SSE chunks 被丢弃,relay 永远是 null。
26+
27+### 根本原因 2:辅助请求匹配但无 assistant 内容
28+
29+`isRelevantStreamUrl` 对 ChatGPT 的判断是 `url.includes("/conversation")`,过于宽泛。以下辅助路径都匹配了但不含 assistant 回复:
30+
31+- `/backend-api/conversation/implicit_message_feedback`
32+- `/backend-api/f/conversation/prepare`
33+
34+这些请求的 FM-SSE 和 FM-NET 全部返回 `relay=null, assistant=-`。
35+
36+### 诊断日志证据
37+
38+```
39+sse_stream_start POST /backend-api/f/conversation ← 主流开始
40+sse_stream_start POST /backend-api/conversation/implicit_message_feedback ← 辅助流
41+sse_stream_done POST /backend-api/conversation/implicit_message_feedback ← 辅助流结束
42+[FM-SSE] relay=null ← 辅助流无内容
43+[SSE] error /backend-api/f/conversation error=The operation was aborted. ← 主流被 abort!
44+```
45
46 ## 涉及仓库
47
48@@ -69,24 +92,60 @@ ChatGPT 的思考模式 SSE 流结构:
49
50 ## 必须完成
51
52-### 1. 排查
53+### 1. 修复 SSE abort 时丢弃已收集数据的问题
54+
55+**文件**:`plugins/baa-firefox/final-message.js` 的 `observeSse` 函数
56+
57+当前逻辑:`detail.done !== true` 时返回 null,已收集的 chunks 被丢弃。
58+
59+**修复**:当 `detail.error` 或 SSE 流异常结束时(abort),如果已有收集到的 chunks,仍然尝试从中提取 candidate 并生成 relay。不能简单丢弃。
60+
61+```javascript
62+// 伪代码:在 error/abort 分支中
63+if (detail.error && stream.chunks.length > 0) {
64+ // 尝试从已有 chunks 提取 candidate
65+ const fullText = stream.chunks.join("\n\n");
66+ const candidate = extractChatgptCandidateFromText(fullText, context);
67+ const relay = buildRelayEnvelope(platform, candidate, meta.observedAt);
68+ state.activeStream = null;
69+ if (relay && !hasSeenRelay(state, relay)) return relay;
70+}
71+```
72+
73+### 2. 收紧 ChatGPT 的 isRelevantStreamUrl
74+
75+**文件**:`plugins/baa-firefox/final-message.js` 的 `isRelevantStreamUrl` 函数
76+
77+当前:`lower.includes("/conversation")` — 太宽泛,匹配了 `implicit_message_feedback` 和 `prepare` 等辅助流。
78+
79+**修复**:只匹配主对话流路径:
80+
81+```javascript
82+if (platform === "chatgpt") {
83+ // 匹配 /backend-api/conversation 和 /backend-api/f/conversation
84+ // 排除 /conversation/implicit_message_feedback、/conversation/prepare 等辅助路径
85+ return /\/(?:backend-api\/)?(?:f\/)?conversation\/?$/i.test(lower)
86+ || lower.includes("/backend-api/conversation") && !lower.includes("/implicit_message_feedback") && !lower.includes("/prepare");
87+}
88+```
89+
90+或更简单的方式:排除已知的辅助路径。
91+
92+### 3. 处理 page-interceptor 中 SSE abort 的事件传递
93
94-- 先看 `logs/baa-ingest/` 的日志,了解 conductor 收到了什么
95-- 在 ChatGPT 页面用浏览器 DevTools 观察 SSE 流的原始数据,确认思考模式下的消息结构
96-- 确认 `extractChatgptCandidateFromText` 和 `extractChatgptCandidateFromChunk` 的当前行为
97+**文件**:`plugins/baa-firefox/page-interceptor.js`
98
99-### 2. 修复
100+确认 SSE 流被 abort 时,page-interceptor 是否发出了 `__baa_sse__` 事件。当前可能只在正常结束时发 `done: true`,abort 时什么都不发。
101
102-- 修改 `extractChatgptCandidateFromText` / `extractChatgptCandidateFromChunk`
103-- 确保跳过 thinking/reasoning 类型的消息
104-- 只提取 status 为 `finished_successfully` / `completed` 且 content 非空的 assistant 回复
105-- 评分逻辑应优先选择更长、有实际内容的 candidate
106+如果 abort 时没发事件,需要在 `streamSse` 的 catch/finally 中补发一个事件(带 `error: true` 和已收集的 chunks)。
107
108-### 3. 验证
109+### 4. 验证
110
111-- ChatGPT 思考模式下回复含 baa 指令 → conductor 收到完整回复(不是 "Thought for...")
112-- ChatGPT 非思考模式下回复 → 行为不变
113+- ChatGPT 回复含 baa 指令 → conductor 收到完整回复并提取到指令
114+- `logs/baa-plugin/` 中 FM-SSE 不再全是 `relay=null`
115+- `logs/baa-ingest/` 中 `blocks_count > 0`
116 - Claude 和 Gemini 的 final-message 不受影响
117+- 辅助流(`implicit_message_feedback`、`prepare`)不再触发 final-message 观察器
118
119 ## 验收标准
120