- commit
- 3283b54
- parent
- 72de524
- author
- im_wower
- date
- 2026-03-27 22:19:16 +0800 CST
docs: code review of BAA instruction system — BUG-018/019/020 + improvements
4 files changed,
+274,
-0
1@@ -0,0 +1,21 @@
2+# BUG-018: preflight 整批失败 — 一条坏指令杀死所有并行指令
3+
4+## 现象
5+
6+AI 发 3 个 baa 代码块,其中 1 个 target 不支持,preflight 对第一个不支持的指令 throw,导致整批 3 条指令全部失败。
7+
8+## 文件
9+
10+`apps/conductor-daemon/src/instructions/loop.ts` preflight()
11+
12+## 根因
13+
14+preflight 用 `.map()` + throw,一个失败整个 map 中断。
15+
16+## 修复
17+
18+改为 filter-and-collect:合法指令继续执行,不合法的收集到 denied 数组。processAssistantMessage 结果中包含 denied 信息。
19+
20+## 严重度
21+
22+High
1@@ -0,0 +1,25 @@
2+# BUG-019: 未关闭的 baa 代码块导致整条消息处理失败
3+
4+## 现象
5+
6+AI 回复中有一个未关闭的 ` ```baa ` 块时,extractBaaInstructionBlocks throw,已关闭的合法 baa 块也不会被处理。
7+
8+## 文件
9+
10+`apps/conductor-daemon/src/instructions/extract.ts`
11+
12+## 根因
13+
14+```javascript
15+if (pending?.isBaa) {
16+ throw new BaaInstructionExtractError("Unterminated ```baa code block.");
17+}
18+```
19+
20+## 修复
21+
22+未关闭的块忽略(warn log),不 throw。已关闭的合法块正常返回。
23+
24+## 严重度
25+
26+Medium
1@@ -0,0 +1,18 @@
2+# BUG-020: InMemory 去重器无上限,长期运行内存泄漏
3+
4+## 现象
5+
6+InMemoryBaaInstructionDeduper 和 InMemoryBaaLiveInstructionMessageDeduper 的 Set 只 add 不清理,没有 TTL 或 maxSize。
7+
8+## 文件
9+
10+- `apps/conductor-daemon/src/instructions/dedupe.ts` InMemoryBaaInstructionDeduper
11+- `apps/conductor-daemon/src/instructions/ingest.ts` InMemoryBaaLiveInstructionMessageDeduper
12+
13+## 修复
14+
15+加 maxSize 限制(默认 10000),超出时清理最旧的 20%。或用 Map<string, number> 记录时间戳做 LRU。
16+
17+## 严重度
18+
19+Low-Medium — 短期无影响,但 conductor 是常驻进程
+210,
-0
1@@ -0,0 +1,210 @@
2+# BAA 指令系统代码审查(Codex commit 72de524)
3+
4+日期:2026-03-27
5+审查范围:instructions/(11 文件)、artifacts/(5 文件)、final-message.js、content-script.js 扩展、firefox-ws.ts 扩展、local-api.ts 扩展、DB migration 0003
6+
7+---
8+
9+## BUG
10+
11+### BUG-018: preflight 整批失败 — 一条坏指令杀死所有并行指令
12+
13+**文件**:`instructions/loop.ts` preflight()
14+
15+**现象**:AI 发 3 个 baa 代码块,其中 1 个 target 不支持(比如 `@browser.chatgpt::send`),preflight 对第一个不支持的指令 throw BaaInstructionCenterError,导致整批 3 条指令全部失败,包括 2 条完全合法的 `@conductor::exec`。
16+
17+**根因**:preflight 用 `.map()` + throw,一个失败整个 map 中断。
18+
19+**修复**:改为 filter-and-collect 模式——合法指令继续执行,不合法的收集到 `policyDenied` 数组返回:
20+
21+```typescript
22+private preflight(instructions) {
23+ const routed = [];
24+ const denied = [];
25+ for (const inst of instructions) {
26+ const decision = evaluateBaaInstructionPolicy(inst);
27+ if (!decision.ok) {
28+ denied.push({ instruction: inst, reason: decision.message });
29+ continue;
30+ }
31+ try {
32+ routed.push({ instruction: inst, route: routeBaaInstruction(inst) });
33+ } catch (error) {
34+ denied.push({ instruction: inst, reason: error.message });
35+ }
36+ }
37+ return { routed, denied };
38+}
39+```
40+
41+processAssistantMessage 中把 denied 也放进结果,让调用方知道哪些被拒了。
42+
43+**严重度**:High — 直接影响并行执行的核心场景
44+
45+
46+### BUG-019: 未关闭的 baa 代码块导致整条消息处理失败
47+
48+**文件**:`instructions/extract.ts` extractBaaInstructionBlocks()
49+
50+**现象**:如果 AI 回复的最终文本中有一个未关闭的 ` ```baa ` 块(比如 AI 在写示例时忘了关闭),extractBaaInstructionBlocks 会 throw BaaInstructionExtractError("Unterminated ```baa code block."),导致整条消息的所有已关闭的合法 baa 块也不会被处理。
51+
52+**修复**:未关闭的块应该被忽略(warn log),不应该 throw。已经关闭的合法块应该正常返回:
53+
54+```typescript
55+if (pending?.isBaa) {
56+ // 未关闭的 baa 块 → 忽略,不 throw
57+ // 可选:记录 warning
58+}
59+return blocks;
60+```
61+
62+**严重度**:Medium — AI 偶尔会输出未关闭的代码块
63+
64+
65+### BUG-020: InMemoryBaaInstructionDeduper 无上限,长期运行内存泄漏
66+
67+**文件**:`instructions/dedupe.ts` InMemoryBaaInstructionDeduper
68+
69+**现象**:Set 只 add 不清理,没有 TTL 也没有 maxSize。长时间运行后 Set 无限增长。
70+
71+**同类问题**:`ingest.ts` InMemoryBaaLiveInstructionMessageDeduper 同样无上限。
72+
73+**修复**:加 LRU 或 maxSize 限制 + 定期清理:
74+
75+```typescript
76+class InMemoryBaaInstructionDeduper {
77+ private readonly keys = new Map<string, number>(); // key → timestamp
78+ private readonly maxSize = 10000;
79+
80+ add(instruction) {
81+ if (this.keys.size >= this.maxSize) {
82+ // 删最旧的 20%
83+ const entries = [...this.keys.entries()].sort((a, b) => a[1] - b[1]);
84+ for (let i = 0; i < entries.length * 0.2; i++) {
85+ this.keys.delete(entries[i][0]);
86+ }
87+ }
88+ this.keys.set(instruction.dedupeKey, Date.now());
89+ }
90+}
91+```
92+
93+**严重度**:Low-Medium — 短期无影响,但 conductor 是常驻进程
94+
95+
96+---
97+
98+## 功能缺失
99+
100+### MISSING-001: 执行结果没有回注到 AI 对话
101+
102+**现状**:instructions/ 模块完成了 提取 → 解析 → 去重 → 权限 → 路由 → 执行 的完整链路,artifacts/ 模块有 materialize、manifest、delivery-plan、upload-session 的完整代码。但**从执行结果到实际注入 AI 对话框**的最后一步没有接通。
103+
104+具体来说:
105+- `upload-session.ts` 有 `this.bridge.injectMessage()` 和 `this.bridge.uploadFile()` 调用
106+- 但 `this.bridge` 的接口是抽象的,没有看到和 firefox-bridge 的实际 wiring
107+- conductor 执行完指令后,结果停留在 `BaaInstructionProcessResult` 中,没有触发 delivery plan → 插件上传 → 注入
108+
109+**需要补**:
110+1. firefox-ws.ts 收到 `browser.final_message` → instruction ingest 执行后 → 触发 artifact materialize → 生成 delivery plan → 通过 WS 下发给插件
111+2. 插件收到 delivery plan → 执行上传/注入 → 返回 receipt
112+3. conductor 收到 receipt → 发送索引文本
113+
114+这是端到端闭环的最后一环。
115+
116+**优先级**:Phase 1 关键路径
117+
118+
119+### MISSING-002: 插件侧没有 delivery plan 执行器
120+
121+**现状**:controller.js 有 `relayObservedFinalMessage` 把最终消息发给 conductor,但没有处理 conductor 返回的 delivery plan 的代码——不会上传文件、不会注入结果文本。
122+
123+**需要补**:controller.js 中加处理 WS 消息 `type: "delivery_plan"` 的分支。
124+
125+
126+### MISSING-003: browser.* target 在 Phase 1 被完全拒绝
127+
128+**文件**:`instructions/policy.ts`
129+
130+**现状**:SUPPORTED_TARGETS 只有 `conductor` 和 `system`。根据 BAA_INSTRUCTION_SYSTEM.md,Phase 1 应该包含 `browser.claude` 的 send/current 能力。
131+
132+**建议**:至少把 `browser.claude` 加入 Phase 1 的 policy 白名单,router 中加对应的路由到 `/v1/browser/request`。
133+
134+
135+---
136+
137+## 改进建议
138+
139+### OPT-002: executor 缺少超时保护
140+
141+**文件**:`instructions/executor.ts` executeBaaInstruction()
142+
143+**现状**:直接调 `handleConductorHttpRequest` 无超时。如果 exec 命令卡住(比如死循环的 shell 脚本),整个 instruction processing pipeline 会无限阻塞。
144+
145+**建议**:加 AbortController 或 Promise.race 超时(默认 30s,exec 工具可配 60s)。
146+
147+
148+### OPT-003: policy 应支持配置化
149+
150+**文件**:`instructions/policy.ts`
151+
152+**现状**:SUPPORTED_TARGETS 和 SUPPORTED_TOOLS 硬编码为 Set 常量。
153+
154+**建议**:改为从配置读取,或者至少让 BaaInstructionCenter 构造时可以传入自定义 policy。这样 Phase 2 加新 target/tool 时不需要改代码。
155+
156+
157+### OPT-004: final-message.js 三平台观察器 — Claude SSE 拼接需要验证
158+
159+**文件**:`plugins/baa-firefox/final-message.js`
160+
161+**现状**:692 行代码,实现了 Claude / ChatGPT / Gemini 三个平台的最终消息提取。Claude 的逻辑依赖 SSE chunk 中的 `type: "completion"` 和 `completion` 字段拼接全文。
162+
163+**风险**:如果 Claude 的 SSE 格式变化(比如新版 API 用 `content_block_delta`),拼接会断。建议加一个 fallback:如果 SSE 拼接失败,降级到从 DOM 提取最终文本。
164+
165+
166+### OPT-005: normalize 阶段 parse 错误丢失了 blockIndex
167+
168+**文件**:`instructions/loop.ts` normalize()
169+
170+**现状**:`.map()` 遍历 blocks 解析,如果中间某个 block 解析失败(比如格式错误),throw 会中断整个 map。后面的合法 blocks 不会被处理。
171+
172+**和 BUG-018 同类**:应该改为 `flatMap` + try/catch,解析失败的 block 跳过并记录错误,不影响其他 blocks。
173+
174+
175+### OPT-006: DB migration 缺少表大小限制
176+
177+**文件**:`ops/sql/migrations/0003_baa_execution_persistence.sql`
178+
179+**现状**:三张表(baa_message_dedupes、baa_instruction_dedupes、baa_execution_journal)没有自动清理策略。
180+
181+**建议**:packages/db 中加定期清理(保留最近 N 天或最近 M 条),避免 SQLite 文件无限增长。看代码 store.ts 中 `listBaaExecutionJournal` 有 limit 参数但只影响查询,不影响存储。
182+
183+
184+---
185+
186+## 做得好的地方
187+
188+1. **类型系统完整**:types.ts 定义了完整的指令生命周期类型链(Extracted → Parsed → Normalized → Envelope → Route → ExecutionResult),每个阶段类型清晰
189+2. **去重设计扎实**:SHA-256 + stable JSON stringify + 双层去重(消息级 + 指令级),持久化版本走 DB
190+3. **executor 内部复用**:直接调 handleConductorHttpRequest 而不是发 HTTP 请求,避免了网络开销和认证问题
191+4. **final-message.js 三平台覆盖**:Claude(SSE chunk 拼接)、ChatGPT(conversation init 响应 + status 检测)、Gemini(streamgenerate 响应解析)都写了
192+5. **测试覆盖全面**:6 个 BAA 专项测试 + 3 个 artifacts 测试 + 1 个 DB 测试,关键路径都有
193+6. **DB schema 干净**:三张表职责分离,索引合理
194+
195+---
196+
197+## 总结优先级
198+
199+| # | 类型 | 内容 | 优先级 |
200+|---|---|---|---|
201+| MISSING-001 | 功能缺失 | 执行结果没有回注到 AI 对话 | **最高** |
202+| MISSING-002 | 功能缺失 | 插件侧没有 delivery plan 执行器 | **最高** |
203+| BUG-018 | Bug | 一条坏指令杀死整批并行指令 | High |
204+| BUG-019 | Bug | 未关闭 baa 块导致整条消息失败 | Medium |
205+| MISSING-003 | 功能缺失 | browser.* target Phase 1 被拒绝 | Medium |
206+| OPT-002 | 改进 | executor 缺少超时保护 | Medium |
207+| BUG-020 | Bug | 内存去重器无上限 | Low-Medium |
208+| OPT-003 | 改进 | policy 应支持配置化 | Low |
209+| OPT-004 | 改进 | final-message.js Claude SSE 拼接 fallback | Low |
210+| OPT-005 | 改进 | normalize parse 错误不应中断整批 | Low |
211+| OPT-006 | 改进 | DB 表缺少自动清理 | Low |