- commit
- 3c1e65e
- parent
- 26bd7b6
- author
- im_wower
- date
- 2026-03-28 02:15:05 +0800 CST
docs(claude): BUG-022 final-message.js missing Claude platform — critical blocker for BAA instruction loop
1 files changed,
+131,
-0
+131,
-0
1@@ -0,0 +1,131 @@
2+# BUG-022: final-message.js 缺少 Claude 平台支持,有机回复永远不会被捕获
3+
4+> 提交者:Claude(代码审查 + 实测验证)
5+> 日期:2026-03-27
6+
7+## 现象
8+
9+用户在 mini 的 Firefox 上打开 Claude 对话,发消息请求 AI 输出 baa 代码块。Claude 正常回复了,但 conductor 的 instruction_ingest 没有收到任何 Claude 平台的 final_message。recent_ingests 全是 Gemini,0 条 Claude。
10+
11+## 根因
12+
13+`plugins/baa-firefox/final-message.js` 的 `isRelevantStreamUrl` 函数缺少 Claude 分支:
14+
15+```javascript
16+function isRelevantStreamUrl(platform, url) {
17+ if (platform === "chatgpt") {
18+ return lower.includes("/conversation");
19+ }
20+ if (platform === "gemini") {
21+ return lower.includes("streamgenerate") || ...;
22+ }
23+ return false; // ← Claude 走到这里,永远 false
24+}
25+```
26+
27+`observeSse` 第一行就返回 null:
28+```javascript
29+if (!isRelevantStreamUrl(state.platform, detail.url)) {
30+ return null; // Claude 的 SSE 永远不会被处理
31+}
32+```
33+
34+同时,`observeSse` 最终提取候选时也没有 Claude 分支:
35+```javascript
36+const finalCandidate = state.platform === "chatgpt"
37+ ? extractChatgptCandidateFromText(...)
38+ : extractGeminiCandidateFromText(...); // Claude 走 Gemini 分支,无法提取
39+```
40+
41+## 缺失的三样东西
42+
43+1. `isRelevantStreamUrl` 缺少 Claude case:URL 包含 `/completion`
44+2. `extractClaudeCandidateFromText` 不存在:需要解析 Claude SSE 格式
45+3. `observeSse` 的候选提取缺少 Claude 分支
46+
47+## Claude SSE 格式
48+
49+```
50+event: completion
51+data: {"type":"completion","id":"chatcompl_xxx","completion":" hello","stop_reason":null,...}
52+
53+event: completion
54+data: {"type":"completion","id":"chatcompl_xxx","completion":" world","stop_reason":"end_turn",...}
55+```
56+
57+- `completion` 字段拼接 = 完整回复文本
58+- `id` 字段 = assistant message id
59+- URL 格式:`/api/organizations/{org}/chat_conversations/{conv_id}/completion`
60+ → conv_id 可从 URL 提取
61+
62+## 修复
63+
64+### 1. isRelevantStreamUrl 加 Claude
65+
66+```javascript
67+if (platform === "claude") {
68+ return lower.includes("/completion");
69+}
70+```
71+
72+### 2. 新增 extractClaudeCandidateFromText
73+
74+```javascript
75+function extractClaudeConversationIdFromUrl(url) {
76+ const match = String(url || "").match(
77+ /\/chat_conversations\/([a-f0-9-]+)\/completion/i
78+ );
79+ return match?.[1] || null;
80+}
81+
82+function extractClaudeCandidateFromText(text, context) {
83+ let fullText = "";
84+ let assistantMessageId = null;
85+
86+ for (const line of String(text || "").split("\n")) {
87+ const trimmed = line.trim();
88+ if (!trimmed.startsWith("data:")) continue;
89+ const payload = parseJson(trimmed.slice(5).trimStart());
90+ if (!payload || payload.type !== "completion") continue;
91+ if (typeof payload.completion === "string") {
92+ fullText += payload.completion;
93+ }
94+ if (!assistantMessageId && payload.id) {
95+ assistantMessageId = String(payload.id);
96+ }
97+ }
98+
99+ const rawText = normalizeMessageText(fullText);
100+ if (!rawText) return null;
101+
102+ return {
103+ assistantMessageId: assistantMessageId || null,
104+ conversationId: extractClaudeConversationIdFromUrl(context.url)
105+ || extractClaudeConversationIdFromUrl(context.pageUrl),
106+ rawText
107+ };
108+}
109+```
110+
111+### 3. observeSse 加 Claude 分支
112+
113+```javascript
114+let finalCandidate;
115+if (state.platform === "chatgpt") {
116+ finalCandidate = extractChatgptCandidateFromText(fullText, context);
117+} else if (state.platform === "claude") {
118+ finalCandidate = extractClaudeCandidateFromText(fullText, context);
119+} else {
120+ finalCandidate = extractGeminiCandidateFromText(fullText, context);
121+}
122+```
123+
124+## 严重度
125+
126+**Critical** — Claude 是 Phase 1 的主要平台,没有这个修复整个 BAA 指令系统的端到端闭环对 Claude 完全不工作
127+
128+## 验收
129+
130+1. 用户在 Firefox 上的 Claude 对话中发消息
131+2. Claude 回复后,conductor 的 `instruction_ingest.recent_ingests` 出现 `platform: "claude"` 的记录
132+3. 如果回复包含 baa 代码块,`status` 应为 `"executed"` 而非 `"ignored_no_instructions"`