baa-conductor

git clone 

commit
978aa4c
parent
6e458f3
author
im_wower
date
2026-03-28 16:09:50 +0800 CST
fix: dedupe in-page conductor overlay
10 files changed,  +436, -450
M bugs/README.md
+2, -0
1@@ -13,6 +13,8 @@ bugs/
2 
3 ## 待修复
4 
5+- `BUG-024`:[`BUG-024-chatgpt-final-message-stale-identity.md`](./BUG-024-chatgpt-final-message-stale-identity.md)
6+- `BUG-025`:[`BUG-025-chatgpt-delivery-targets-shell-page.md`](./BUG-025-chatgpt-delivery-targets-shell-page.md)
7 - `OPT-002`:[`OPT-002-executor-timeout.md`](./OPT-002-executor-timeout.md)
8 - `OPT-003`:[`OPT-003-policy-configurable.md`](./OPT-003-policy-configurable.md)
9 - `OPT-004`:[`OPT-004-final-message-claude-sse-fallback.md`](./OPT-004-final-message-claude-sse-fallback.md)
M plans/BAA_INSTRUCTION_SYSTEM.md
+38, -12
 1@@ -1,9 +1,22 @@
 2 # BAA 指令系统
 3 
 4-日期:2026-03-27
 5+日期:2026-03-28
 6 
 7 > 让任何 AI 通过代码块驱动外部能力——shell、文件、浏览器代发、跨 AI 能力借用——不需要原生 function calling。
 8 
 9+## 0. 当前实现状态
10+
11+这份文档同时包含“当前已实现能力”和“后续规划目标”。当前代码主线请先按下面这组事实理解:
12+
13+- 当前正式支持的 target 只有:
14+  - `conductor`
15+  - `system`
16+  - `browser.claude`
17+- `browser.chatgpt` / `browser.gemini` 目前仍是规划目标,不是当前放开的 Phase 1 target
18+- upload / download / binary delivery 已经废弃,不再是当前插件职责
19+- 当前 delivery 主链仍是 text-only DOM fallback
20+- 下一步主线改造是:由 conductor 持有 conversation / routing 真相,插件只执行 page-context browser request
21+
22 ---
23 
24 ## 1. 核心规则
25@@ -30,13 +43,12 @@
26 
27 插件不解析 baa 代码块。所有解析、路由、去重、权限、执行、结果总结、交付计划都在 conductor 内完成。
28 
29-插件只做四件事:
30+插件只做三件事:
31 - **观察**:拦截 AI 最终回复,转发给 conductor
32-- **上传**:按 conductor 指示上传文件到 AI 对话
33-- **下载**:从 AI 对话下载文件给 conductor
34-- **注入**:按 conductor 指示注入文本并发送
35+- **执行**:在页面上下文里带真实登录态执行 browser request
36+- **回退**:必要时按 conductor 指示做最小 text-only 注入/发送 fallback
37 
38-这样无论接多少个平台,插件逻辑不变,不会每个平台重复实现解析和格式化。
39+这样无论接多少个平台,解析、路由、权限和上下文真相都仍留在 conductor,而不是散落到插件里。
40 
41 ### 1.4 语法极简,复杂度后移
42 
43@@ -130,6 +142,11 @@ AI 回复中出现 3 个 baa 代码块 = 3 条独立指令 = 并行提交:
44 | `codex` | codexd 代理 | `/v1/codex/*` |
45 | `node.mini` / `node.mbp` | 指定机器节点 | SSH / tailscale 执行 |
46 
47+注意:
48+
49+- 上表是协议目标全集,不等于当前已开放实现
50+- 当前真正放开的 Phase 1 target 仍只有 `conductor` / `system` / `browser.claude`
51+
52 ### 3.2 逻辑 target(Phase 4+)
53 
54 | target | 含义 | 路由方式 |
55@@ -209,8 +226,8 @@ AI 回复中出现 3 个 baa 代码块 = 3 条独立指令 = 并行提交:
56 ### 5.3 架构角色
57 
58 ```
59-conductor     = 唯一中控,提取指令、路由、执行、总结、生成交付计划
60-Firefox 插件  = 薄网关,观察回复、上传文件、下载文件、注入文本
61+conductor     = 唯一中控,提取指令、路由、执行、总结、保存上下文并决定回写目标
62+Firefox 插件  = 薄网关,观察回复、执行 page-context 请求、必要时做最小 fallback 回写
63 各 AI         = 能力节点,各有所长,通过 baa 代码块互相借用
64 ```
65 
66@@ -246,9 +263,9 @@ Firefox 插件  = 薄网关,观察回复、上传文件、下载文件、注
67 → conductor 去重 + 权限校验
68 → conductor 并行执行(exec / files / browser/request / codex)
69 → conductor 汇总结果
70-→ conductor 生成 delivery plan(哪些走文件上传,哪些走文本注入)
71-→ WS 指示插件执行 delivery plan
72-→ 插件上传文件 / 注入文本 / 点击发送
73+→ conductor 生成 delivery route(当前默认 text-only;旧 artifact 方案已废弃)
74+→ WS / browser request 指示插件执行回写
75+→ 插件执行 page-context 请求,或在 fallback 模式下注入文本 / 点击发送
76 → AI 看到结果,继续工作
77 ```
78 
79@@ -331,7 +348,16 @@ function parseParamValue(raw) {
80 
81 ---
82 
83-## 8. 结果交付:文件上传优先,文本兜底
84+## 8. 结果交付(历史设计,已废弃)
85+
86+本章描述的是早期“文件上传优先、文本兜底”的 artifact 交付方案。它已经不是当前主线实现,保留这里只是为了回溯设计演进。
87+
88+当前实际主线请按下面这组事实理解:
89+
90+- artifact upload / download 已废弃
91+- 当前 delivery 仍是 text-only fallback
92+- 下一步主线改造见 [`./BAA_BROWSER_PROXY_DELIVERY_REQUIREMENTS.md`](./BAA_BROWSER_PROXY_DELIVERY_REQUIREMENTS.md)
93+- 不要再把本章里的 upload receipt / manifest / 文件上传流程当成当前合同
94 
95 ### 8.1 分层策略
96 
M plans/BAA_PLUGIN_DELIVERY_HARDENING_REQUIREMENTS.md
+4, -3
 1@@ -3,14 +3,15 @@
 2 ## 状态
 3 
 4 - `已完成(T-S035,2026-03-28)`
 5+- `当前只保留为 DOM fallback 历史方案;主线改造已转到 ./BAA_BROWSER_PROXY_DELIVERY_REQUIREMENTS.md`
 6 - 优先级:`high`
 7 - 记录时间:`2026-03-27`
 8 
 9 ## 关联文档
10 
11-- [BAA_DELIVERY_BRIDGE_REQUIREMENTS.md](/Users/george/code/baa-conductor/plans/archive/BAA_DELIVERY_BRIDGE_REQUIREMENTS.md)
12-- [BAA_ARTIFACT_CENTER_REQUIREMENTS.md](/Users/george/code/baa-conductor/plans/archive/BAA_ARTIFACT_CENTER_REQUIREMENTS.md)
13-- [README.md](/Users/george/code/baa-conductor/docs/firefox/README.md)
14+- [archive/BAA_DELIVERY_BRIDGE_REQUIREMENTS.md](./archive/BAA_DELIVERY_BRIDGE_REQUIREMENTS.md)
15+- [archive/BAA_ARTIFACT_CENTER_REQUIREMENTS.md](./archive/BAA_ARTIFACT_CENTER_REQUIREMENTS.md)
16+- [../docs/firefox/README.md](../docs/firefox/README.md)
17 
18 ## 背景
19 
M plans/STATUS_SUMMARY.md
+59, -169
  1@@ -2,185 +2,75 @@
  2 
  3 ## 当前时间
  4 
  5-- `2026-03-27`
  6+- `2026-03-28`
  7 
  8 ## 当前代码基线
  9 
 10-- 浏览器控制主链路收口基线:`main@07895cd`
 11-- 最近功能代码提交:`main@25be868`(启动时自动恢复受管 Firefox shell tabs)
 12-- `2026-03-27` 当前本地代码已额外完成 `BUG-011`、`BUG-012`、`BUG-013`、`BUG-014`、`BUG-017` 修复,以及 `T-S029`、`T-S030`、`T-S031`、`T-S032`、`T-S033`、`T-S034` 落地
 13-- 活跃任务文档保留在 `tasks/` 根目录,已完成任务文档已归档到 [`../tasks/archive/README.md`](../tasks/archive/README.md)
 14-- 当前活动任务见 `tasks/TASK_OVERVIEW.md`
 15-- 已完成需求文档已归档到 [`./archive/README.md`](./archive/README.md)
 16+- 当前主分支:`main@499bd69`
 17+- canonical local API:`http://100.71.210.78:4317`
 18+- canonical public host:`https://conductor.makefile.so`
 19+- 活跃任务文档保留在 `tasks/` 根目录;已完成任务文档归档到 [`../tasks/archive/README.md`](../tasks/archive/README.md)
 20 
 21-## 当前状态分类
 22+## 当前已经跑通的主链
 23 
 24-- `已完成`:见 [`../tasks/archive/README.md`](../tasks/archive/README.md) 和 [`./archive/README.md`](./archive/README.md)
 25-- `当前 TODO`:
 26-  - 下一张主线任务卡待新建
 27-- `待处理缺陷`:见 [`../bugs/README.md`](../bugs/README.md)
 28-- `低优先级 TODO`:`4318/status-api` 兼容层删旧与解耦
 29+- Firefox 插件已经具备 page-context `browser/request` 代理能力
 30+- ChatGPT / Claude / Gemini 都已经接入 `browser.final_message` raw relay
 31+- conductor 已具备 BAA Phase 1:
 32+  - `@conductor`
 33+  - `@system`
 34+  - `@browser.claude`
 35+- delivery 当前仍是 text-only fallback:
 36+  - conductor 渲染执行结果文本
 37+  - 通过 `browser.inject_message` / `browser.send_message`
 38+  - 插件用 DOM adapter 回写
 39+- 页面右下角控制浮层与页面级暂停控制已完成,任务 `T-S037` 已归档
 40 
 41-当前活跃需求文档:
 42+## 当前已纠正的文档/代码不一致
 43+
 44+- `T-S037` 之前仍作为 active task 留在根目录,但代码已经合入;现在已归档
 45+- `BAA_INSTRUCTION_SYSTEM.md` 之前把 upload / download 写成当前插件职责,也把 `browser.chatgpt` / `browser.gemini` 写成了像是当前已支持 target;现在已补充“当前实现 vs 规划目标”的区分
 46+- `BAA_PLUGIN_DELIVERY_HARDENING_REQUIREMENTS.md` 现在只保留为 DOM fallback 历史方案,不再代表当前主线架构方向
 47+
 48+## 当前最高优先级
 49+
 50+1. `T-BUG-020`
 51+   修复 ChatGPT 新对话 final-message 仍复用旧 identity,导致新回复被 conductor 判成 `duplicate_message`
 52+2. `T-S038`
 53+   把 delivery 主链从 DOM `inject / send` 收口成“conductor 持有上下文,插件只执行 browser request”的代理式回写
 54+3. `OPT-002`
 55+   给 executor 加超时保护
 56+4. `OPT-006`
 57+   给 execution persistence 表加自动清理
 58+
 59+说明:
 60+
 61+- `BUG-025` 已确认存在,但它本质上是 delivery 架构问题,优先跟随 `T-S038` 一并解决
 62+- 其余优化项见 [`../bugs/README.md`](../bugs/README.md)
 63+
 64+## 当前活跃需求文档
 65+
 66+- [`./BAA_BROWSER_PROXY_DELIVERY_REQUIREMENTS.md`](./BAA_BROWSER_PROXY_DELIVERY_REQUIREMENTS.md)
 67+- [`./BAA_INSTRUCTION_SYSTEM.md`](./BAA_INSTRUCTION_SYSTEM.md)
 68+- [`./STATUS_SUMMARY.md`](./STATUS_SUMMARY.md)
 69+
 70+保留在根目录但不是当前主线目标的历史文档:
 71 
 72 - [`./BAA_PLUGIN_DELIVERY_HARDENING_REQUIREMENTS.md`](./BAA_PLUGIN_DELIVERY_HARDENING_REQUIREMENTS.md)
 73 - [`./BAA_ARTIFACT_DOWNLOAD_REQUIREMENTS.md`](./BAA_ARTIFACT_DOWNLOAD_REQUIREMENTS.md)
 74-- [`./BAA_INSTRUCTION_SYSTEM.md`](./BAA_INSTRUCTION_SYSTEM.md)
 75-- 已完成需求归档见 [`./archive/README.md`](./archive/README.md)
 76-
 77-## 当前状态
 78-
 79-- 部署目标:`mini` 单节点
 80-- canonical 主接口:`http://100.71.210.78:4317`
 81-- canonical 公网入口:`https://conductor.makefile.so`
 82-- 历史主备资料已从主线移除
 83-- 回溯 tag:`ha-failover-archive-2026-03-22`
 84-- `apps/control-api-worker` 目录当前主线已不存在
 85-- `ops/cloudflare/` 当前只剩 legacy README
 86-- `tests/control-api/` 当前只剩一份 legacy absence smoke 测试文件
 87-- `status-api` 当前默认读 `BAA_CONDUCTOR_LOCAL_API` / `4317`,`BAA_CONTROL_API_BASE` 只保留为兼容覆盖入口
 88-- `status-api` 当前结论是继续保留为显式 opt-in 的本地兼容包装层,不立即删除
 89-- 浏览器桥接当前正式模型已经收口为“登录态元数据持久化 + 空壳标签页 + 浏览器本地代发请求”
 90-- 通用 browser request / cancel / SSE 与插件管理动作已经进入正式主线合同
 91-
 92-## 当前在线面
 93-
 94-- `https://conductor.makefile.so` -> VPS Nginx -> `100.71.210.78:4317`
 95-- `mini` 本地 conductor:`http://100.71.210.78:4317`
 96-- `mini` 本地 status-api:`http://100.71.210.78:4318`,仅用于本地只读观察
 97-- `mini` 本地 codexd:`http://127.0.0.1:4319`
 98-- `https://control-api.makefile.so`,仅作为迁移期 legacy 兼容壳和残留依赖盘点目标,不再是当前入口
 99-
100-## 当前保留内容
101-
102-- `mini` 的 conductor / worker-runner
103-- `mini` 的 codexd
104-- launchd 安装与检查脚本
105-- Firefox 插件子目录 `plugins/baa-firefox`
106-- `packages/host-ops` 以及 `/v1/exec`、`/v1/files/read`、`/v1/files/write`
107-- `/v1/browser/*`、本地 `/ws/firefox` 和 browser-control smoke
108-- 单节点的 Nginx / DNS 计划脚本
109-- 迁移期兼容件:`apps/status-api`、`ops/cloudflare/**`、`tests/control-api/**`、`BAA_CONTROL_API_BASE`
110-
111-## 当前主线收口情况
112-
113-浏览器桥接第二阶段已经完成主线收口:
114-
115-1. `T-S023`:已完成,通用 browser request / cancel / SSE 链路和首版风控策略已接入主线
116-2. `T-S024`:已完成,README / docs / smoke / 状态视图已同步到正式口径
117-3. `T-S025`:已完成,`shell_runtime`、结构化 `action_result` 和控制读面已接入主线
118-4. `2026-03-27` 跟进修复:Firefox 插件管理页启动、浏览器重开或扩展重载后,会自动恢复之前明确启用过、但当前缺失的 shell tab;平台根页也会被收进受管理 shell 集合
119-5. `2026-03-27` 跟进修复:`BUG-011` 已修复,`writeHttpResponse()` 的 body / stream 背压断连不再永久挂起
120-6. `2026-03-27` 跟进修复:`BUG-012` 已修复,browser request policy waiter 现在会超时退出并返回明确错误
121-7. `2026-03-27` 跟进修复:`BUG-014` 已修复,`ws_reconnect` 的 `action_result.completed` 不再提前为 `true`
122-8. `2026-03-27` 跟进修复:`BUG-013` 已完成回归确认,stream session 结束后会清理 timer,不会再触发多余 timeout / cancel
123-9. `2026-03-27` 跟进修复:`BUG-017` 已修复,buffered 模式的 SSE 响应现在会返回结构化 `events` / `full_text`
124-10. `2026-03-27` 跟进任务:`T-S027` 已完成,browser request policy 已补 stale `inFlight` 自愈清扫、lease 活跃时间追踪和 `GET /v1/browser` 读面诊断字段
125-11. `2026-03-27` 跟进任务:`T-S028` 已完成,`platform=chatgpt` 的 `/v1/browser/request` 现已正式支持 path-based buffered / SSE / cancel,并已补到 automated smoke 与文档
126-12. `2026-03-27` 跟进任务:`T-S031` 已完成,`browser.final_message` 已接入 live instruction ingest,并把最近一次 ingest / execute 摘要暴露到 Firefox WS 与 `/v1/browser`
127-13. `2026-03-27` 跟进任务:`T-S032` 已完成,`conductor-daemon` 已补 service-side artifact center 首版实现
128-14. `2026-03-27` 跟进任务:`T-S033` 已完成,live message dedupe、instruction dedupe 和 bounded execution journal 已落到本地 control-plane sqlite,并在重启后恢复
129-15. `2026-03-27` 跟进任务:`T-S034` 已完成,live 执行结果已接到首版 delivery 主链,并补了 fail-closed 自动化 smoke
130-16. `2026-03-27` 跟进任务:`T-S026` 已完成真实 Firefox 手工 smoke,覆盖 `plugin_status`、`tab_open`、`tab_restore`、`ws_reconnect` 和“扩展重载后自动恢复”验收;额外完成了 `disconnect_ms=3000`、`repeat_count=3`、`repeat_interval_ms=500` 的多轮 WS 重连稳定性验证
131-17. `2026-03-28` delivery 主链已转为 text-only:artifact materialize / manifest / upload/download 路径已移除,`browser.final_message` 的执行结果会直接渲染成纯文本,通过 `browser.inject_message` / `browser.send_message` 交付;超长默认按前 `200` 行截断并追加 `超长截断`
132-
133-当前策略:
134-
135-- 当前最高优先级剩余任务:
136-  - 新任务待建立;上一轮 delivery 相关代码已完成并已归档
137-- 当前 bug backlog 单独留在 `bugs/` 目录,不把它和主线需求任务混写
138-- 当前不把大文件拆分当作主线 blocker
139-- 以下重构工作顺延到下一轮专门重构任务:
140-  - `apps/conductor-daemon/src/local-api.ts`
141-  - `plugins/baa-firefox/controller.js`
142-  - `packages/db/src/index.ts`
143-  - `apps/conductor-daemon/src/index.ts`
144-  - `apps/conductor-daemon/src/index.test.js`
145-
146-## 当前缺陷 backlog
147-
148-- 当前 open bug backlog:见 [`../bugs/README.md`](../bugs/README.md)
149-- 当前没有 bug fix 正在主线开发中;下一波主线任务待新建
150-
151-## 低优先级 TODO
152-
153-下面这些继续保留,但不再是当前最高优先级:
154-
155-- 清理仍依赖 `4318` / `status-api` wrapper 的旧脚本、书签和运维说明
156-- 把 `conductor-daemon` 对 `status-api` 构建产物的复用提成共享模块
157-- 只有在完成上面两项后,才重新评估是否删除 `apps/status-api`
158-
159-## 最近完成
160-
161-- `T-S001`:`codexd` transport 现在会在关闭前冲刷尾部缓冲区,`BUG-010` 已修复,`BUG-008` 不再作为独立未解问题保留
162-- `T-S002`:legacy Worker / D1 入口已从当前主线收口;`tests/control-api/**` 改为 absence smoke,`ops/cloudflare/**` 收敛到 legacy 说明
163-- `T-S003`:`status-api`、launchd 模板和 runtime 文档已切到当前 `conductor` 主接口,`BAA_CONTROL_API_BASE` 只保留为兼容覆盖
164-- `T-S004`:`conductor-daemon` 测试已统一通过 `withRuntimeFixture(...)` 收口 cleanup,`BUG-009` 已修复
165-- `T-S005`:默认 launchd 现在只把 `BAA_CONTROL_API_BASE` 写给 `conductor`;`status-api` 和 `worker-runner` 不再携带这个变量
166-- `T-S006`:`tasks/`、`plans/`、`README` 与 `docs/api/**` / `docs/runtime/**` 已把 `control-api` 收口为 legacy 背景,不再写成当前默认入口
167-- `T-S007`:`ConductorRuntime.stop()` 已补专项测试,覆盖 listener 释放、Firefox bridge client 关闭和重复 stop 的幂等行为
168-- `T-S008`:`codexd` 现在会把“未收到合法 completed 就提前断流”单独归类成新的 child / transport 故障,并写入 reopen 规则
169-- `T-S009`:`conductor-daemon` 和 runtime 脚本已经引入 `BAA_CONDUCTOR_PUBLIC_API_BASE` / `--public-api-base`,legacy `BAA_CONTROL_API_BASE` / `--control-api-base` 降为兼容别名
170-- `T-S010`:仓库根 `pnpm lint` / `pnpm test` 已变成真实入口,并新增 repo verification 文档
171-- `T-S011`:`status-api` 已补包级 `test` 入口,并接入根 `pnpm test`
172-- `T-S012`:repo 模板、`install-mini.sh` 帮助文本、`pnpm-lock.yaml` 与 legacy smoke 已进一步收口到当前主线口径
173-- `T-S013`:`worker-runner` 已补包级 `test`,并接入根 `pnpm test`
174-- `T-S014`:runtime 默认服务集合已收口到 `conductor` + `codexd`,`status-api` 改为显式 opt-in
175-- `T-S015`:`mini` 节点 on-node 静态+运行态检查已收口到 `scripts/runtime/verify-mini.sh`,仓库根入口是 `pnpm verify:mini`
176-- `T-S016`:`conductor-daemon` 已承接 `/v1/status` 和 `/v1/status/ui`;`status-api` 降为 `4318` 上的兼容包装层
177-- `T-S017`:浏览器登录态与端点元数据仓储模型已落地
178-- `T-S018`:Firefox 插件已收口到空壳标签页,并开始上报 `account`、凭证指纹和端点元数据
179-- `T-S019`:`conductor-daemon` 已接入浏览器登录态持久化、读接口合并视图和 `fresh` / `stale` / `lost` 状态收口
180-- `T-S020`:浏览器桥接文档、browser smoke 和状态视图已经回写到“登录态元数据持久化 + 空壳标签页 + 浏览器本地代发”正式模型
181-- `T-S021`:`conductor` 的浏览器能力发现已继续收口到 `business` / `control` 两层 describe,并定义了通用 browser HTTP 合同与 legacy Claude 包装位次
182-- `T-S022`:Firefox 插件侧已完成空壳页 runtime、`desired/actual` 状态模型和插件管理类 payload 准备
183-- `T-S023`:通用 browser request / cancel / SSE 链路、`stream_*` 事件模型和首版浏览器风控策略已经接入 `conductor` 与 Firefox bridge
184-- `T-S024`:README、API / Firefox / runtime 文档、browser smoke 和任务状态视图已同步到正式主线口径
185-- `T-S025`:`shell_runtime`、结构化 `action_result` 和控制读面已接入主线;后续遗留的真实 Firefox 验收已由 `T-S026` 在 `2026-03-27` 收口
186-- `2026-03-27` 跟进修复:Firefox 插件现在会在管理页启动、浏览器重开或扩展重载后自动执行一次后台 `tab_restore`,并会把用户手工打开的 Claude `new`、ChatGPT 根页和 Gemini `app` 收进受管理 shell 集合
187-- 根级 `pnpm smoke` 已进主线,覆盖 runtime public-api compatibility、legacy absence、codexd e2e 和 browser-control e2e smoke
188-
189-## 4318 依赖盘点与结论
190-
191-当前仍直接依赖 `4318` / `status-api` 兼容层的内容,主要只剩 4 类:
192-
193-- 兼容 HTTP 合同:`apps/status-api` 继续保留 `/describe`、`/v1/status`、`/v1/status/ui`、`/ui`
194-- opt-in runtime/ops 面:`install-mini.sh --with-status-api`、`--service status-api`、`ops/launchd/so.makefile.baa-status-api.plist`
195-- 文档/runbook:`docs/runtime/**`、`docs/ops/README.md`、`README.md` 里仍明确说明如何启用和检查 `4318`
196-- 构建时复用:`apps/conductor-daemon/src/local-api.ts` 当前直接复用 `status-api` 的已构建 snapshot/render 逻辑,`apps/conductor-daemon/package.json` 因此依赖 `@baa-conductor/status-api build`
197-
198-当前结论:
199-
200-- `status-api` 继续保留为显式 opt-in 兼容层
201-- 现在不删除 `apps/status-api`
202-- 这组工作已降为低优先级 backlog
203-- 只有在清完 `4318` 调用方,并把 `conductor-daemon` 对 `status-api` 构建产物的复用提成共享模块之后,才适合讨论删除
204+
205+## 当前主线判断
206+
207+现在的关键不是“再补一个 DOM selector”,而是统一浏览器主链:
208+
209+- 入站:继续保留 `browser.final_message`
210+- 真相源:由 conductor 记录 conversation / request / routing
211+- 出站:优先改成 browser request proxy
212+- DOM `inject / send`:降为 fallback
213 
214 ## 当前仍需关注
215 
216-- 如果后续再次出现 app-server 根本没有发出合法 `turn/completed` 就提前断流,这属于新的 child / transport 故障,应单独新开 bug
217-- `BAA_CONTROL_API_BASE` / `--control-api-base` 仍保留在运行时代码和脚本里,定位是兼容名字,不是 canonical truth source
218-- 仍保留的 `control-api` 命名已经限定在历史任务卡、legacy 测试路径、兼容变量名和外部残留资产说明里;如果未来要继续删旧,需要单独评估文件名和兼容面
219-- 如果未来新增 runtime 测试绕开 `withRuntimeFixture(...)`,同类 listener 泄漏仍可能重新出现
220-- 这次没有改 `ConductorRuntime.stop()` 内部逻辑;如果未来关闭路径本身阻塞,还需要单独补运行时层测试
221-- 根 `pnpm test` 现在已经覆盖 `worker-runner`;runtime / e2e 检查则由 `pnpm smoke` 收口
222-- runtime smoke 当前仍假定仓库根已经存在 `state/`、`runs/`、`worktrees/`、`logs/launchd/`、`logs/codexd/`、`tmp/` 等本地运行目录;这是现有脚本前提,不是本轮浏览器桥接功能改动本身
223-- `pnpm verify:mini` 只收口 on-node 静态检查和运行态探针,不替代会话级 smoke
224-- `status-api` 的终局已经先收口到“保留为 opt-in 兼容层”;真正删除它之前,还要先清 `4318` 调用方并拆掉当前构建时复用
225 - 风控状态当前仍是进程内内存态;`conductor` 重启后,限流、退避和熔断计数会重置
226-- 正式 browser HTTP relay 现已正式验收 Claude 与 ChatGPT;Gemini 当前新增的是最终消息 raw relay,不是 `/v1/browser/request` 正式支持面
227-- 当前 open bug backlog:见 [`../bugs/README.md`](../bugs/README.md)
228-- `T-S029`、`T-S030`、`T-S031`、`T-S032`、`T-S033`、`T-S034` 已完成,且 `2026-03-28` 已把 delivery 主链继续收口到 text-only `inject / send`
229-- 当前 BAA 仍保留这些边界:
230-  - ChatGPT 当前主要依赖 conversation SSE 结构;如果页面 payload 形态变化,需要同步修改提取器
231-  - Gemini 最终文本提取当前基于 `StreamGenerate` / `batchexecute` 风格 payload 的启发式解析,稳定性弱于 ChatGPT,因此保留 synthetic `assistant_message_id` 兜底
232-  - 插件侧 `inject / send` 仍是 DOM heuristic,当前只对 `Claude` / `ChatGPT` 做了首版选择器与流程
233-  - 当前交付仍按任务边界停留在单客户端、单轮 delivery 首版
234-  - live message dedupe 和 instruction dedupe 当前已做成单节点本地持久化,但不做跨节点共享
235-  - execution journal 当前只保留最近窗口,不扩成无限历史审计
236-  - 当前 live 执行路径仍只覆盖 Phase 1 精确 target `conductor` / `system`,不扩到跨节点或完整 task/run 编排
237-- `BUG-012` 这轮修复已补上 stale `inFlight` 自愈清扫;当前残余边界是“健康但长时间完全静默”的超长 buffered 请求,理论上仍可能被 `5min` idle 阈值误判
238-- ChatGPT 当前仍依赖浏览器里真实捕获到的有效登录态 / header,且没有 Claude 风格 prompt shortcut;这是当前正式支持面的已知边界
239-- `BUG-014` 的自动化验证目前覆盖的是 conductor 侧语义透传,不是 Firefox 扩展真实运行环境里的 reconnect 生命周期;真实“重连完成”仍依赖后续 `hello` / 状态同步
240-- 真实 Firefox 手工 smoke 已在 `2026-03-27` 完成;浏览器管理面当前不再把“真机 tab / reconnect 生命周期未验收”列为残余风险
241-- 当前多个源码文件已超过常规体量,但拆分重构已明确延后到浏览器桥接主线收口之后再做
242+- `Gemini` 当前仍不是 `/v1/browser/request` 的正式支持面,正式 browser target 仍只有 `browser.claude`
243+- ChatGPT final-message 仍存在真实页面 identity 复用问题,见 [`../bugs/BUG-024-chatgpt-final-message-stale-identity.md`](../bugs/BUG-024-chatgpt-final-message-stale-identity.md)
244+- ChatGPT 当前 delivery 仍可能落到 shell 页,见 [`../bugs/BUG-025-chatgpt-delivery-targets-shell-page.md`](../bugs/BUG-025-chatgpt-delivery-targets-shell-page.md)
245+- `status-api` 继续保留为显式 opt-in 兼容层,不是当前删除重点
M plans/archive/README.md
+3, -2
 1@@ -18,7 +18,8 @@
 2 
 3 ## 当前仍在根目录的需求文档
 4 
 5-- [`../BAA_PLUGIN_DELIVERY_HARDENING_REQUIREMENTS.md`](../BAA_PLUGIN_DELIVERY_HARDENING_REQUIREMENTS.md)
 6-- [`../BAA_ARTIFACT_DOWNLOAD_REQUIREMENTS.md`](../BAA_ARTIFACT_DOWNLOAD_REQUIREMENTS.md)
 7+- [`../BAA_BROWSER_PROXY_DELIVERY_REQUIREMENTS.md`](../BAA_BROWSER_PROXY_DELIVERY_REQUIREMENTS.md)
 8 - [`../BAA_INSTRUCTION_SYSTEM.md`](../BAA_INSTRUCTION_SYSTEM.md)
 9 - [`../STATUS_SUMMARY.md`](../STATUS_SUMMARY.md)
10+- [`../BAA_PLUGIN_DELIVERY_HARDENING_REQUIREMENTS.md`](../BAA_PLUGIN_DELIVERY_HARDENING_REQUIREMENTS.md):已完成,当前只作为 DOM fallback 历史方案
11+- [`../BAA_ARTIFACT_DOWNLOAD_REQUIREMENTS.md`](../BAA_ARTIFACT_DOWNLOAD_REQUIREMENTS.md):已废弃,当前只保留历史决策说明
M plugins/baa-firefox/content-script.js
+23, -0
 1@@ -168,6 +168,27 @@ function formatPendingActionLabel(action) {
 2   }
 3 }
 4 
 5+function removeExistingOverlayRoots() {
 6+  if (typeof document?.querySelectorAll === "function") {
 7+    const roots = Array.from(document.querySelectorAll(`#${PAGE_CONTROL_OVERLAY_ID}`) || []);
 8+    for (const entry of roots) {
 9+      if (entry && typeof entry.remove === "function") {
10+        entry.remove();
11+      }
12+    }
13+    return;
14+  }
15+
16+  if (typeof document?.getElementById !== "function") {
17+    return;
18+  }
19+
20+  const existing = document.getElementById(PAGE_CONTROL_OVERLAY_ID);
21+  if (existing && typeof existing.remove === "function") {
22+    existing.remove();
23+  }
24+}
25+
26 function createPageControlOverlayRuntime() {
27   let root = null;
28   let shadow = null;
29@@ -622,6 +643,8 @@ function createPageControlOverlayRuntime() {
30       return;
31     }
32 
33+    removeExistingOverlayRoots();
34+
35     root = document.createElement("div");
36     root.id = PAGE_CONTROL_OVERLAY_ID;
37     shadow = root.attachShadow({ mode: "open" });
D tasks/T-S037.md
+0, -146
  1@@ -1,146 +0,0 @@
  2-# Task T-S037:恢复 AI 页面右下角控制浮层,并支持页面级暂停 BAA 控制
  3-
  4-## 直接给对话的提示词
  5-
  6-读 `/Users/george/code/baa-conductor/tasks/T-S037.md` 任务文档,完成开发任务。
  7-
  8-如需补背景,再读:
  9-
 10-- `/Users/george/code/baa-conductor/plugins/baa-firefox/README.md`
 11-- `/Users/george/code/baa-conductor/tasks/TASK_OVERVIEW.md`
 12-- `/Users/george/code/baa-conductor/plans/BAA_INSTRUCTION_SYSTEM.md`
 13-
 14-## 当前基线
 15-
 16-- 仓库:`/Users/george/code/baa-conductor`
 17-- 分支基线:`main`
 18-- 提交:`7dc34ad`
 19-- 开工要求:必须先从当前 `main` 新建任务分支,再开始开发;禁止直接在 `main` 上修改。功能任务分支名必须以 `feat/` 开头,缺陷任务分支名必须以 `bug/` 开头。
 20-- 开发隔离要求:必须使用新的 `git worktree` 开发本任务,禁止直接在当前正在使用的插件工作目录里修改并 reload 扩展,避免影响当前稳定可用的 Firefox 插件。
 21-
 22-## 必须创建的新分支名
 23-
 24-- `feat/page-level-baa-control-overlay`
 25-
 26-## 推荐 worktree
 27-
 28-- 建议新建:`/Users/george/code/baa-conductor-ts037`
 29-- 建议命令:`git worktree add ../baa-conductor-ts037 -b feat/page-level-baa-control-overlay main`
 30-
 31-## 目标
 32-
 33-把 AI 网页右下角的控制浮层正式恢复回来,并支持“仅暂停当前页面 / 当前对话的 BAA 控制”,避免死循环时只能做全局暂停。
 34-
 35-## 背景
 36-
 37-当前仓库已经补了一个临时止损方案:在支持的 AI 网页右下角注入全局 `pause / resume / drain` 浮层。但这只是全局控制,不是用户真正需要的“只暂停当前页面 / 当前对话”。
 38-
 39-现状问题:
 40-
 41-- 当前页面没有正式的页面级 BAA 控制开关
 42-- 一旦出现循环,用户只能全局 `pause`
 43-- `browser.final_message` 和 delivery 仍然按平台主链工作,无法只对某一个页面停用
 44-
 45-这张卡的目标不是继续扩大全局控制,而是把页面级 stop switch 正式落地。
 46-
 47-## 涉及仓库
 48-
 49-- `/Users/george/code/baa-conductor`
 50-
 51-## 范围
 52-
 53-- 恢复 AI 页面右下角控制浮层为正式能力,而不是临时 stopgap
 54-- 增加页面级 / tab 级 BAA 暂停状态
 55-- 让被暂停页面不再继续参与 `browser.final_message` 上报和 delivery 回写
 56-- 补最小自动化回归
 57-
 58-## 路径约束
 59-
 60-优先在 Firefox 插件运行时层完成,不要把这张卡扩成完整多页面会话框架重构。只有在确实需要 conductor 读面暴露时,才最小改动 `apps/conductor-daemon/src/`。
 61-
 62-## 推荐实现边界
 63-
 64-建议优先做:
 65-
 66-- 页面浮层状态以 `tabId + platform` 为主键;如果页面里已经能稳定取到 `conversationId`,可以作为补充字段保留
 67-- 页面级暂停后,至少阻断该页面的 `browser.final_message`
 68-- 如果该页面被标记为暂停,delivery 也不要继续往该页面注入 / 发送
 69-- 在 controller 或 `/v1/browser` 读面里保留最小可观察状态,便于排查“为什么这个页面没有继续执行”
 70-
 71-## 允许修改的目录
 72-
 73-- `/Users/george/code/baa-conductor/plugins/baa-firefox/content-script.js`
 74-- `/Users/george/code/baa-conductor/plugins/baa-firefox/controller.js`
 75-- `/Users/george/code/baa-conductor/plugins/baa-firefox/page-interceptor.js`
 76-- `/Users/george/code/baa-conductor/plugins/baa-firefox/final-message.js`
 77-- `/Users/george/code/baa-conductor/plugins/baa-firefox/background.js`
 78-- `/Users/george/code/baa-conductor/tests/browser/browser-control-e2e-smoke.test.mjs`
 79-- `/Users/george/code/baa-conductor/apps/conductor-daemon/src/firefox-ws.ts`
 80-- `/Users/george/code/baa-conductor/apps/conductor-daemon/src/browser-types.ts`
 81-- `/Users/george/code/baa-conductor/apps/conductor-daemon/src/local-api.ts`
 82-
 83-## 尽量不要修改
 84-
 85-- `/Users/george/code/baa-conductor/apps/conductor-daemon/src/instructions/`
 86-- `/Users/george/code/baa-conductor/plugins/baa-firefox/delivery-adapters.js`
 87-- `/Users/george/code/baa-conductor/docs/`
 88-
 89-## 必须完成
 90-
 91-### 1. 恢复正式页面浮层
 92-
 93-- 在 Claude / ChatGPT / Gemini 页面右下角显示 BAA 控制浮层
 94-- 浮层至少要能显示当前页面是否被暂停
 95-- 不要再只依赖 `controller.html` 做控制入口
 96-
 97-### 2. 支持页面级暂停
 98-
 99-- 用户可以只暂停当前页面 / 当前 tab 的 BAA 控制
100-- 被暂停页面的 `browser.final_message` 不再继续上报给 conductor
101-- 恢复后该页面能继续进入正常观察链
102-
103-### 3. 收口 delivery 行为
104-
105-- 被暂停页面不应继续接收自动 delivery 回写
106-- 其他未暂停页面和全局控制语义不能被破坏
107-- 临时全局浮层可以保留,但不能和页面级状态冲突
108-
109-### 4. 补回归
110-
111-- 覆盖页面级暂停后 `browser.final_message` 被抑制
112-- 覆盖恢复后观察链重新生效
113-- 覆盖现有全局 `pause / resume / drain` 语义不回归
114-
115-## 需要特别注意
116-
117-- 不要把这张卡扩成完整 conversation manager 或多 tab orchestration 重构
118-- 不要破坏当前 shell tab、desired / actual / drift 语义
119-- 不要回退已经修好的 Claude / ChatGPT / Gemini final-message 观察链
120-- 页面级暂停和全局 `system pause` 要能区分,不能混成一个状态
121-
122-## 验收标准
123-
124-- 在支持的 AI 网页里能看到右下角 BAA 控制浮层
125-- 用户可以只暂停当前页面,而不是只能全局暂停
126-- 被暂停页面不会继续触发 `browser.final_message -> conductor -> delivery`
127-- 恢复后该页面重新参与主链
128-- 现有 browser smoke 通过
129-
130-## 推荐验证命令
131-
132-- `node --check /Users/george/code/baa-conductor/plugins/baa-firefox/content-script.js`
133-- `node --check /Users/george/code/baa-conductor/plugins/baa-firefox/controller.js`
134-- `node --check /Users/george/code/baa-conductor/plugins/baa-firefox/page-interceptor.js`
135-- `node --check /Users/george/code/baa-conductor/plugins/baa-firefox/final-message.js`
136-- `node --test /Users/george/code/baa-conductor/tests/browser/browser-control-e2e-smoke.test.mjs`
137-- `git diff --check`
138-
139-## 交付要求
140-
141-完成后请说明:
142-
143-- 修改了哪些文件
144-- 页面级暂停状态是怎么建模的
145-- `browser.final_message` 和 delivery 各自如何尊重该状态
146-- 跑了哪些测试
147-- 还有哪些剩余风险
M tasks/TASK_OVERVIEW.md
+74, -118
  1@@ -1,148 +1,104 @@
  2 # 任务总览
  3 
  4-当前主线围绕一件事推进:把 `mini` 本地接口收口成唯一主接口,并把对外入口统一到 `conductor.makefile.so`。
  5+## 当前基线
  6 
  7-## 当前状态
  8-
  9-- canonical local API: `http://100.71.210.78:4317`
 10-- canonical public host: `https://conductor.makefile.so`
 11-- `control-api.makefile.so`、Cloudflare Worker、D1 只剩迁移期 legacy 兼容残留和依赖盘点用途
 12-- `baa-hand` / `baa-shell` 只保留为接口语义参考,不再作为主系统维护
 13+- 日期:`2026-03-28`
 14+- 主分支基线:`main@499bd69`
 15+- canonical local API:`http://100.71.210.78:4317`
 16+- canonical public host:`https://conductor.makefile.so`
 17 - 当前活跃任务卡保留在本目录;已完成任务卡已归档到 [`./archive/README.md`](./archive/README.md)
 18-- 浏览器控制主链路收口基线:`main@07895cd`
 19-- 最近功能代码提交:`main@25be868`(启动时自动恢复受管 Firefox shell tabs)
 20-- `2026-03-27` 当前本地代码已额外完成 `BUG-011`、`BUG-012`、`BUG-013`、`BUG-014`、`BUG-017` 修复,以及 `T-S029`、`T-S030`、`T-S031`、`T-S032`、`T-S033`、`T-S034` 落地
 21 
 22-## 状态分类
 23+## 当前代码状态
 24 
 25-- `已完成`:见 [`./archive/README.md`](./archive/README.md)
 26-- `当前 TODO`:
 27-  - [`T-S037.md`](./T-S037.md):恢复 AI 页面右下角控制浮层,并支持页面级暂停 BAA 控制
 28-- `待处理缺陷`:见 [`../bugs/README.md`](../bugs/README.md)
 29-- `低优先级 TODO`:`4318/status-api` 兼容层删旧与解耦
 30+当前主线已经完成这些能力:
 31 
 32-当前活跃需求文档:
 33+- Firefox 插件已具备页面观察、登录态元数据采集、page-context `browser/request` 代理执行
 34+- ChatGPT / Claude / Gemini 都已接入 `browser.final_message` raw relay
 35+- conductor 已具备 BAA Phase 1:`@conductor` / `@system` / `@browser.claude`
 36+- delivery 当前仍是 text-only fallback:
 37+  - conductor 生成执行结果文本
 38+  - 通过 `browser.inject_message` / `browser.send_message`
 39+  - 由插件用 DOM adapter 回写到页面
 40+- `T-S037` 对应的页面右下角浮层与页面级暂停控制已经在代码里完成,并已归档
 41 
 42-- [`../plans/BAA_PLUGIN_DELIVERY_HARDENING_REQUIREMENTS.md`](../plans/BAA_PLUGIN_DELIVERY_HARDENING_REQUIREMENTS.md)
 43-- [`../plans/BAA_ARTIFACT_DOWNLOAD_REQUIREMENTS.md`](../plans/BAA_ARTIFACT_DOWNLOAD_REQUIREMENTS.md)
 44-- [`../plans/BAA_INSTRUCTION_SYSTEM.md`](../plans/BAA_INSTRUCTION_SYSTEM.md)
 45-- 已完成需求归档见 [`../plans/archive/README.md`](../plans/archive/README.md)
 46+## 当前已确认的不一致
 47+
 48+这轮整理后,下面几处旧口径已经被显式改正:
 49+
 50+- `T-S037` 之前仍挂在根目录作为 active task,但代码已经合入 `main`;现在已归档
 51+- `plans/BAA_INSTRUCTION_SYSTEM.md` 之前把 upload / download 写成当前插件职责,也把 `browser.chatgpt` / `browser.gemini` 写得像当前已支持 target;现在已补充“当前实现 vs 规划目标”的区分
 52+- 当前真正的架构矛盾不在“有没有 proxy request”,而在“delivery 仍然走 DOM fallback”;这已经单独收口成新任务 [`T-S038.md`](./T-S038.md)
 53+
 54+## 当前活跃任务与优先级
 55+
 56+### P0
 57+
 58+- [`T-BUG-020.md`](./T-BUG-020.md):修复 ChatGPT 新对话 final-message 仍复用旧 message / conversation identity,导致 conductor 把新回复判成 `duplicate_message`
 59+
 60+### P1
 61+
 62+- [`T-S038.md`](./T-S038.md):把 delivery 从 DOM `inject / send` 重构为“conductor 持有上下文,插件只执行 page-context browser request”的代理式回写主链
 63+
 64+### P2
 65 
 66-## 已归档完成项
 67+- [`../bugs/OPT-002-executor-timeout.md`](../bugs/OPT-002-executor-timeout.md)
 68+- [`../bugs/OPT-006-db-table-auto-cleanup.md`](../bugs/OPT-006-db-table-auto-cleanup.md)
 69 
 70-- 已完成任务卡与修复卡:[`./archive/README.md`](./archive/README.md)
 71-- 已完成需求文档:[`../plans/archive/README.md`](../plans/archive/README.md)
 72-- 已关闭 / 已完成缺陷:[`../bugs/archive/`](../bugs/archive/)
 73+### P3
 74 
 75-当前主线已经额外收口:
 76+- [`../bugs/OPT-003-policy-configurable.md`](../bugs/OPT-003-policy-configurable.md)
 77+- [`../bugs/OPT-004-final-message-claude-sse-fallback.md`](../bugs/OPT-004-final-message-claude-sse-fallback.md)
 78+- [`../bugs/OPT-005-normalize-parse-error-isolation.md`](../bugs/OPT-005-normalize-parse-error-isolation.md)
 79 
 80-- 根级 `pnpm smoke`,覆盖 repo 内可自举的 runtime compatibility / legacy absence / codexd e2e / browser-control e2e smoke
 81-- 根级 `pnpm verify:mini`,作为 `mini` 节点 on-node 静态+运行态检查入口
 82-- `2026-03-27` 跟进修复:Firefox 插件管理页启动、浏览器重开或扩展重载后,会自动恢复之前明确启用过、但当前缺失的 shell tab;平台根页也会被收进受管理 shell 集合
 83+## 当前 open bug / gap
 84+
 85+- [`../bugs/BUG-024-chatgpt-final-message-stale-identity.md`](../bugs/BUG-024-chatgpt-final-message-stale-identity.md)
 86+- [`../bugs/BUG-025-chatgpt-delivery-targets-shell-page.md`](../bugs/BUG-025-chatgpt-delivery-targets-shell-page.md)
 87+- 其余优化项见 [`../bugs/README.md`](../bugs/README.md)
 88 
 89 说明:
 90 
 91-- 这些已完成任务卡里出现的 `control-api` / `BAA_CONTROL_API_BASE` 描述,默认都指任务开工时的历史背景;当前主线 canonical 口径仍是 `http://100.71.210.78:4317` 和 `https://conductor.makefile.so`
 92-
 93-## 当前活动任务
 94-
 95-- `2026-03-28` 代码已完成一次 delivery 架构转向:
 96-  - 主链改成 text-only `inject / send`
 97-  - upload / download / artifact route 已移除
 98-  - 默认超长策略改成前 `200` 行 + `超长截断`
 99-- `2026-03-28`:`T-BUG-015`、`T-BUG-016`、`T-BUG-017` 已完成并归档到 [`./archive/README.md`](./archive/README.md)
100-- `2026-03-28`:`T-BUG-018` 已完成并归档到 [`./archive/README.md`](./archive/README.md)
101-- `2026-03-28`:`T-MISSING-003` 已完成并归档到 [`./archive/README.md`](./archive/README.md)
102-- `2026-03-28`:`T-BUG-019` 已完成并归档到 [`./archive/README.md`](./archive/README.md)
103-- `2026-03-28`:当前活跃任务卡为 [`T-S037.md`](./T-S037.md),用于恢复 AI 页面右下角控制浮层,并实现页面级暂停 BAA 控制
104-
105-## 当前主线收口情况
106-
107-当前浏览器桥接主线第二阶段已经完成:
108-
109-1. `T-S017` 到 `T-S026`:已完成并已归档到 [`./archive/README.md`](./archive/README.md)
110-2. `2026-03-27` 跟进修复:Firefox 插件管理页启动、浏览器重开或扩展重载后,会自动恢复之前明确启用过、但当前缺失的 shell tab
111-3. `2026-03-27` 跟进修复:如果用户手工打开 Claude `new`、ChatGPT 根页或 Gemini `app`,插件会把它们纳入受管理 shell 集合
112-4. `2026-03-27` 跟进任务:`T-S027`、`T-S028` 已完成并已归档
113-5. `2026-03-27` 跟进任务:`T-S026` 已完成真实 Firefox 手工 smoke,覆盖 `plugin_status`、`tab_open`、`tab_restore`、`ws_reconnect` 和“扩展重载后自动恢复”验收
114-
115-最近代码跟进:
116-
117-- `2026-03-27`:`verify-mini.sh` 的 wrapper 调用已收口为数组参数组装,避免空参数场景下的命令拼接问题
118-- `2026-03-27`:`BUG-011` 已修复,`writeHttpResponse()` 的 body / stream 背压断连不再永久挂起
119-- `2026-03-27`:`BUG-012` 已修复,browser request policy waiter 现在会超时退出并返回明确错误
120-- `2026-03-27`:`BUG-014` 已修复,`ws_reconnect` 的 `action_result.completed` 现在会正确返回 `false`
121-- `2026-03-27`:`BUG-013` 已完成回归确认,stream session 结束后会清理 timer,不会再触发多余 timeout / cancel
122-- `2026-03-27`:`BUG-017` 已修复,buffered 模式收到 SSE 原始文本时现在会返回结构化 `events` / `full_text`
123-- `2026-03-27`:`T-S027` 已完成,browser request policy 已补 stale `inFlight` 自愈清扫与读面诊断字段
124-- `2026-03-27`:`T-S028` 已完成,`platform=chatgpt` 的 `/v1/browser/request` 现已正式支持 path-based buffered / SSE / cancel,并已补到 automated smoke 与文档
125-
126-建议并行关系:
127-
128-- `T-S017` 与 `T-S018` 已并行完成
129-- `T-S019` 已完成集成和验收收口
130-- `T-S020` 已在 `T-S019` 之后完成收尾
131-- `T-S021` 与 `T-S022` 已并行完成
132-- `T-S023` 已完成服务端集成和通用 request/SSE 主链路收口
133-- `T-S024` 已在 `T-S023` 之后完成文档与 smoke 收尾
134-
135-当前已知主线遗留:
136-
137-- 风控状态当前仍是进程内内存态;`conductor` 重启后,限流、退避和熔断计数会重置
138-- 正式 browser HTTP relay 现已正式验收 Claude 与 ChatGPT;Gemini 当前新增的是最终消息 raw relay,不是 `/v1/browser/request` 正式支持面
139-- 当前 open bug / missing backlog 见 [`../bugs/README.md`](../bugs/README.md)
140-- 当前这轮主线代码已完成并归档:
141-  - [`archive/T-S035.md`](./archive/T-S035.md):加固插件侧 text-only delivery adapter 与页面交付流程
142-  - [`archive/T-S036.md`](./archive/T-S036.md):收口 text-only delivery 主链与超长截断策略
143-- `T-S029`、`T-S030`、`T-S031`、`T-S032`、`T-S033`、`T-S034` 已完成,当前 BAA 已具备:
144-  - ChatGPT / Gemini 最终消息 raw relay 与 `browser.final_message` 快照保留
145-  - conductor 侧 instruction center Phase 1 最小闭环与 live ingest
146-  - conductor 侧 dedupe 与 execution journal 本地持久化,以及 `/v1/browser` 最近摘要恢复
147-  - conductor 侧 live ingest / execution journal / browser delivery 主链
148-  - text-only delivery bridge、inject / send 的 live 闭环
149-- 当前保留的 BAA 边界是:
150-  - ChatGPT 当前主要依赖 conversation SSE 结构;页面 payload 形态变化后需要同步调整提取器
151-  - Gemini 最终文本提取当前基于 `StreamGenerate` / `batchexecute` 风格 payload 的启发式解析,稳定性弱于 ChatGPT,因此保留 synthetic `assistant_message_id` 兜底
152-  - 插件侧 `inject / send` 仍是 DOM heuristic,当前只对 `Claude` / `ChatGPT` 做了首版选择器与流程
153-  - 当前交付仍按任务边界停留在单客户端、单轮 delivery 首版
154-  - live message dedupe 和 instruction dedupe 当前已做成单节点本地持久化,但不做跨节点共享
155-  - execution journal 当前只保留最近窗口,不扩成无限历史审计
156-  - 当前 live 执行路径仍只覆盖 Phase 1 精确 target `conductor` / `system`,不扩到跨节点或完整 task/run 编排
157-- `BUG-012` 这轮修复已补上 stale `inFlight` 自愈清扫;当前残余边界是“健康但长时间完全静默”的超长 buffered 请求,理论上仍可能被 `5min` idle 阈值误判
158-- ChatGPT 当前仍依赖浏览器里真实捕获到的有效登录态 / header,且没有 Claude 风格 prompt shortcut;这是当前正式支持面的已知边界,不是 regression
159-- `BUG-014` 的自动化验证目前覆盖的是 conductor 侧语义透传,不是 Firefox 扩展真实运行环境里的 reconnect 生命周期;真实“重连完成”仍依赖后续 `hello` / 状态同步
160-- runtime smoke 仍依赖仓库根已有 `state/`、`runs/`、`worktrees/`、`logs/launchd/`、`logs/codexd/`、`tmp/` 等本地运行目录;这是现有脚本前提,不是本轮功能回归
161-- 真实 Firefox 手工 smoke 已完成;delivery 主链改造也已完成,下一步需新建任务卡继续推进剩余风险
162-
163-## 低优先级 TODO
164-
165-下面两件事先降级为低优先级 backlog,不作为当前主线:
166-
167-1. 盘点并清理仍依赖 `4318` wrapper 的旧脚本、书签和运维说明
168-2. 把 `conductor-daemon` 目前对 `status-api` 构建产物的复用提成共享模块,再评估是否删除 `apps/status-api`
169+- `BUG-024` 是当前 ChatGPT 端到端闭环的直接 blocker,所以先单独拆 bug 修
170+- `BUG-025` 本质上是 delivery 架构问题,优先通过 [`T-S038.md`](./T-S038.md) 一次性收口,不再继续加码 DOM adapter
171+
172+## 当前活跃需求文档
173+
174+- [`../plans/BAA_BROWSER_PROXY_DELIVERY_REQUIREMENTS.md`](../plans/BAA_BROWSER_PROXY_DELIVERY_REQUIREMENTS.md)
175+- [`../plans/BAA_INSTRUCTION_SYSTEM.md`](../plans/BAA_INSTRUCTION_SYSTEM.md)
176+- [`../plans/STATUS_SUMMARY.md`](../plans/STATUS_SUMMARY.md)
177+
178+保留在根目录但不再作为当前主线目标的历史文档:
179+
180+- [`../plans/BAA_PLUGIN_DELIVERY_HARDENING_REQUIREMENTS.md`](../plans/BAA_PLUGIN_DELIVERY_HARDENING_REQUIREMENTS.md):已完成,当前只作为 DOM fallback 历史说明
181+- [`../plans/BAA_ARTIFACT_DOWNLOAD_REQUIREMENTS.md`](../plans/BAA_ARTIFACT_DOWNLOAD_REQUIREMENTS.md):已废弃,保留历史决策说明
182+
183+## 当前主线判断
184+
185+现在最值得继续推进的不是再补一个 DOM selector,而是把“浏览器请求代理”与“回写交付”统一起来:
186+
187+- 入站:继续保留 `browser.final_message`
188+- 中控:由 conductor 持有 conversation / request / routing 真相
189+- 出站:优先改成 conductor 下发代理请求,插件只在 page context 执行
190+- DOM `inject / send` 降为 fallback,而不是主链
191 
192 ## 任务文档约定
193 
194-- 任务卡统一使用纯 Markdown 结构,不再使用 frontmatter
195+- 任务卡统一使用纯 Markdown 结构,不使用 frontmatter
196 - 每张任务卡都要写清当前基线、必须创建的新分支名、允许修改目录和验收命令
197+- Firefox 插件相关的大任务,优先要求新建 `git worktree`,避免污染当前稳定可用插件
198 - 新任务优先参考 [`task-doc-template.md`](./task-doc-template.md)
199 
200 ## 现在该读什么
201 
202 1. [`../DESIGN.md`](../DESIGN.md)
203 2. [`../docs/api/README.md`](../docs/api/README.md)
204-3. [`../docs/runtime/README.md`](../docs/runtime/README.md)
205-4. [`../docs/ops/README.md`](../docs/ops/README.md)
206-5. [`../plans/STATUS_SUMMARY.md`](../plans/STATUS_SUMMARY.md)
207-6. 对应的任务卡
208+3. [`../plugins/baa-firefox/README.md`](../plugins/baa-firefox/README.md)
209+4. [`../plans/STATUS_SUMMARY.md`](../plans/STATUS_SUMMARY.md)
210+5. 对应的任务卡
211 
212-## 如需新任务
213+## 如需继续新建任务
214 
215-- 直接在当前 `tasks/` 目录新建任务卡
216-- 所有新任务默认以 `100.71.210.78:4317` 和 `conductor.makefile.so` 为 canonical 接口面
217-- `control-api.makefile.so` 只允许作为删旧前的 legacy 兼容背景或残留依赖盘点说明出现
218-- 当前任务编号继续使用 `T-S***`
219+- 所有新任务默认以 `4317` 和 `conductor.makefile.so` 为 canonical 接口面
220 - 新任务必须先从当前 `main` 新建任务分支再开发;功能任务用 `feat/` 前缀,缺陷任务用 `bug/` 前缀
221 - 能并行的任务优先拆开,并明确写清允许修改的目录
222 - 新任务文档结构参考 [`task-doc-template.md`](./task-doc-template.md)
223-- 不再恢复旧 wave 文档;历史内容继续靠 tag `ha-failover-archive-2026-03-22` 回溯
M tasks/archive/README.md
+3, -0
 1@@ -10,9 +10,12 @@
 2 - 基础收口:[`T-S001.md`](./T-S001.md)、[`T-S002.md`](./T-S002.md)、[`T-S003.md`](./T-S003.md)、[`T-S004.md`](./T-S004.md)、[`T-S005.md`](./T-S005.md)、[`T-S006.md`](./T-S006.md)、[`T-S007.md`](./T-S007.md)、[`T-S008.md`](./T-S008.md)、[`T-S009.md`](./T-S009.md)、[`T-S010.md`](./T-S010.md)、[`T-S011.md`](./T-S011.md)、[`T-S012.md`](./T-S012.md)、[`T-S013.md`](./T-S013.md)、[`T-S014.md`](./T-S014.md)、[`T-S015.md`](./T-S015.md)、[`T-S016.md`](./T-S016.md)
 3 - 浏览器桥接主线:[`T-S017.md`](./T-S017.md)、[`T-S018.md`](./T-S018.md)、[`T-S019.md`](./T-S019.md)、[`T-S020.md`](./T-S020.md)、[`T-S021.md`](./T-S021.md)、[`T-S022.md`](./T-S022.md)、[`T-S023.md`](./T-S023.md)、[`T-S024.md`](./T-S024.md)、[`T-S025.md`](./T-S025.md)、[`T-S026.md`](./T-S026.md)、[`T-S027.md`](./T-S027.md)、[`T-S028.md`](./T-S028.md)
 4 - BAA 主线:[`T-S029.md`](./T-S029.md)、[`T-S030.md`](./T-S030.md)、[`T-S031.md`](./T-S031.md)、[`T-S032.md`](./T-S032.md)、[`T-S033.md`](./T-S033.md)、[`T-S034.md`](./T-S034.md)、[`T-S035.md`](./T-S035.md)、[`T-S036.md`](./T-S036.md)
 5+- 页面控制:[`T-S037.md`](./T-S037.md)
 6 - 缺陷修复:[`T-BUG-011.md`](./T-BUG-011.md)、[`T-BUG-012.md`](./T-BUG-012.md)、[`T-BUG-014.md`](./T-BUG-014.md)、[`T-BUG-015.md`](./T-BUG-015.md)、[`T-BUG-016.md`](./T-BUG-016.md)、[`T-BUG-017.md`](./T-BUG-017.md)、[`T-BUG-018.md`](./T-BUG-018.md)、[`T-BUG-019.md`](./T-BUG-019.md)、[`T-MISSING-003.md`](./T-MISSING-003.md)
 7 
 8 ## 当前仍在根目录的任务卡
 9 
10 - [`../TASK_OVERVIEW.md`](../TASK_OVERVIEW.md):当前任务总览
11+- [`../T-BUG-020.md`](../T-BUG-020.md):当前最高优先级 ChatGPT final-message bug 修复
12+- [`../T-S038.md`](../T-S038.md):当前主线代理式 delivery 改造
13 - [`../task-doc-template.md`](../task-doc-template.md):新任务模板
M tests/browser/browser-control-e2e-smoke.test.mjs
+230, -0
  1@@ -24,6 +24,10 @@ const controllerSource = readFileSync(
  2   new URL("../../plugins/baa-firefox/controller.js", import.meta.url),
  3   "utf8"
  4 );
  5+const contentScriptSource = readFileSync(
  6+  new URL("../../plugins/baa-firefox/content-script.js", import.meta.url),
  7+  "utf8"
  8+);
  9 
 10 function createWebSocketMessageQueue(socket) {
 11   const messages = [];
 12@@ -491,6 +495,222 @@ function createControllerHarness(options = {}) {
 13   };
 14 }
 15 
 16+function createMockDomNode(tagName = "div") {
 17+  return {
 18+    children: [],
 19+    className: "",
 20+    dataset: {},
 21+    hidden: false,
 22+    id: "",
 23+    isConnected: false,
 24+    listeners: new Map(),
 25+    parentNode: null,
 26+    shadowRoot: null,
 27+    tagName: String(tagName || "div").toUpperCase(),
 28+    textContent: "",
 29+    appendChild(child) {
 30+      if (!child || typeof child !== "object") {
 31+        return child;
 32+      }
 33+
 34+      child.parentNode = this;
 35+      child.isConnected = true;
 36+      this.children.push(child);
 37+      return child;
 38+    },
 39+    attachShadow() {
 40+      const shadow = createMockDomNode("#shadow-root");
 41+      shadow.host = this;
 42+      shadow.isConnected = true;
 43+      this.shadowRoot = shadow;
 44+      return shadow;
 45+    },
 46+    addEventListener(type, listener) {
 47+      if (!this.listeners.has(type)) {
 48+        this.listeners.set(type, new Set());
 49+      }
 50+      this.listeners.get(type)?.add(listener);
 51+    },
 52+    remove() {
 53+      if (!this.parentNode) {
 54+        this.isConnected = false;
 55+        return;
 56+      }
 57+
 58+      this.parentNode.children = this.parentNode.children.filter((entry) => entry !== this);
 59+      this.parentNode = null;
 60+      this.isConnected = false;
 61+    }
 62+  };
 63+}
 64+
 65+function findDomNodesById(root, id, matches = []) {
 66+  if (!root || typeof root !== "object") {
 67+    return matches;
 68+  }
 69+
 70+  if (root.id === id) {
 71+    matches.push(root);
 72+  }
 73+
 74+  for (const child of root.children || []) {
 75+    findDomNodesById(child, id, matches);
 76+  }
 77+
 78+  if (root.shadowRoot) {
 79+    findDomNodesById(root.shadowRoot, id, matches);
 80+  }
 81+
 82+  return matches;
 83+}
 84+
 85+function createContentScriptHarness() {
 86+  const storage = {};
 87+  const runtimeMessageListeners = new Set();
 88+  const storageListeners = new Set();
 89+  const body = createMockDomNode("body");
 90+  const documentElement = createMockDomNode("html");
 91+  documentElement.appendChild(body);
 92+
 93+  const document = {
 94+    body,
 95+    documentElement,
 96+    readyState: "complete",
 97+    addEventListener() {},
 98+    createElement(tagName) {
 99+      return createMockDomNode(tagName);
100+    },
101+    execCommand() {
102+      return true;
103+    },
104+    getElementById(id) {
105+      return findDomNodesById(documentElement, id)[0] || null;
106+    },
107+    querySelectorAll(selector) {
108+      if (typeof selector === "string" && selector.startsWith("#")) {
109+        return findDomNodesById(documentElement, selector.slice(1));
110+      }
111+
112+      return [];
113+    }
114+  };
115+
116+  const browser = {
117+    runtime: {
118+      async sendMessage(message) {
119+        if (message?.type === "get_page_control_state") {
120+          return {
121+            ok: true,
122+            control: {
123+              mode: "running",
124+              controlConnection: "connected"
125+            },
126+            page: null
127+          };
128+        }
129+
130+        return { ok: true };
131+      },
132+      onMessage: {
133+        addListener(listener) {
134+          runtimeMessageListeners.add(listener);
135+        },
136+        removeListener(listener) {
137+          runtimeMessageListeners.delete(listener);
138+        }
139+      }
140+    },
141+    storage: {
142+      local: {
143+        async get(keys) {
144+          if (Array.isArray(keys)) {
145+            return Object.fromEntries(keys.map((key) => [key, storage[key]]));
146+          }
147+
148+          if (typeof keys === "string") {
149+            return {
150+              [keys]: storage[keys]
151+            };
152+          }
153+
154+          return { ...storage };
155+        },
156+        async set(values) {
157+          Object.assign(storage, values || {});
158+        }
159+      },
160+      onChanged: {
161+        addListener(listener) {
162+          storageListeners.add(listener);
163+        },
164+        removeListener(listener) {
165+          storageListeners.delete(listener);
166+        }
167+      }
168+    }
169+  };
170+
171+  function createWindow() {
172+    const listeners = new Map();
173+
174+    return {
175+      addEventListener(type, listener) {
176+        if (!listeners.has(type)) {
177+          listeners.set(type, new Set());
178+        }
179+        listeners.get(type)?.add(listener);
180+      },
181+      dispatchEvent(event) {
182+        const handlers = listeners.get(event?.type);
183+        for (const listener of handlers || []) {
184+          listener(event);
185+        }
186+        return true;
187+      },
188+      removeEventListener(type, listener) {
189+        listeners.get(type)?.delete(listener);
190+      }
191+    };
192+  }
193+
194+  function execute() {
195+    const windowObject = createWindow();
196+    const context = {
197+      URL,
198+      URLSearchParams,
199+      browser,
200+      clearTimeout,
201+      console,
202+      CustomEvent: class CustomEvent {
203+        constructor(type, init = {}) {
204+          this.detail = init.detail;
205+          this.type = type;
206+        }
207+      },
208+      document,
209+      globalThis: null,
210+      location: {
211+        href: "https://chatgpt.com/c/overlay-smoke"
212+      },
213+      setTimeout,
214+      window: windowObject
215+    };
216+
217+    context.globalThis = context;
218+    vm.runInNewContext(contentScriptSource, context, {
219+      filename: "content-script.js"
220+    });
221+    return context;
222+  }
223+
224+  return {
225+    execute,
226+    getOverlayRoots() {
227+      return findDomNodesById(documentElement, "__baaFirefoxPageControlOverlay__");
228+    }
229+  };
230+}
231+
232 function parseSseFrames(text) {
233   return String(text || "")
234     .split(/\n\n+/u)
235@@ -1522,6 +1742,16 @@ test("controller relays final_message for proxy_delivery SSE traffic", () => {
236   assert.equal(relay.tab_id, 51);
237 });
238 
239+test("content script removes an existing overlay root before reinjection", () => {
240+  const harness = createContentScriptHarness();
241+
242+  harness.execute();
243+  assert.equal(harness.getOverlayRoots().length, 1);
244+
245+  harness.execute();
246+  assert.equal(harness.getOverlayRoots().length, 1);
247+});
248+
249 test("browser control e2e smoke covers metadata read surface plus Claude and ChatGPT relay", async () => {
250   const stateDir = mkdtempSync(join(tmpdir(), "baa-browser-control-e2e-smoke-"));
251   const runtime = new ConductorRuntime(