baa-conductor

git clone 

commit
a8e69d5
parent
56e9a4f
author
im_wower
date
2026-03-21 21:49:44 +0800 CST
docs: define firefox pause resume protocol
2 files changed,  +261, -11
M coordination/tasks/T-009-firefox-pause.md
+14, -8
 1@@ -1,10 +1,10 @@
 2 ---
 3 task_id: T-009
 4 title: Firefox 插件 Pause 与 Resume 协议
 5-status: todo
 6+status: review
 7 branch: feat/T-009-firefox-pause
 8 repo: /Users/george/code/baa-conductor
 9-base_ref: main@28829de
10+base_ref: main@56e9a4f
11 depends_on:
12   - T-001
13 write_scope:
14@@ -20,7 +20,7 @@ updated_at: 2026-03-21
15 
16 ## 统一开工要求
17 
18-- 必须从 `main@28829de` 切出该分支
19+- 必须从 `main@56e9a4f` 切出该分支
20 - 新 worktree 进入后先执行 `npx --yes pnpm install`
21 - 不允许从其他任务分支切分支
22 
23@@ -52,23 +52,29 @@ updated_at: 2026-03-21
24 
25 ## files_changed
26 
27-- 待填写
28+- `coordination/tasks/T-009-firefox-pause.md`
29+- `docs/firefox/README.md`
30 
31 ## commands_run
32 
33-- 待填写
34+- `git worktree add /Users/george/code/baa-conductor-T009 -b feat/T-009-firefox-pause main`
35+- `npx --yes pnpm install`
36+- `rg -n "Firefox|pause|resume|dispatch|control" DESIGN.md docs/firefox/README.md`
37+- `git diff --stat`
38 
39 ## result
40 
41-- 待填写
42+- 已在 `docs/firefox/README.md` 定义 Firefox 插件与 control API 的协议,包括状态读取字段、`pause/resume/drain` 请求体、按钮启用规则、成功/失败响应,以及 `control` 与 `dispatch` 的职责边界。
43+- 已把任务卡基线修正为本次实际要求的 `main@56e9a4f`。
44 
45 ## risks
46 
47-- 待填写
48+- `docs/firefox/README.md` 当前定义的是协议契约,仍需要后续 `T-003` / `baa-firefox` 实现方按该契约落地接口与 UI。
49+- `coordination/SECOND_WAVE_START.md` 在当前仓库树中不存在;本任务实际依据 `DESIGN.md`、`TASK_OVERVIEW.md`、`WORKFLOW.md` 与任务卡执行。
50 
51 ## next_handoff
52 
53-- 给 `baa-firefox` 仓库实现方使用
54+- `baa-firefox` 仓库按 `docs/firefox/README.md` 接入 control API,并与 `T-003` 对齐返回字段和错误码。
55 
56 ## notes
57 
M docs/firefox/README.md
+247, -3
  1@@ -1,6 +1,250 @@
  2-# firefox
  3+# Firefox Control Protocol
  4 
  5-这个目录预留给 Firefox 插件与 conductor 之间的协议说明。
  6+本文档定义 `baa-firefox` 与 `baa-conductor` control API 之间的最小协议。
  7 
  8-当前只建立目录边界,具体内容由 `T-009` 完成。
  9+目标:
 10 
 11+- 让 Firefox 插件能显示全局自动化状态
 12+- 让 Firefox 插件能触发 `pause`、`resume`,可选 `drain`
 13+- 明确可见 `control` 与隐藏 `dispatch` 的职责边界
 14+
 15+非目标:
 16+
 17+- 不在本仓库实现 Firefox 插件代码
 18+- 不定义完整 UI 视觉稿
 19+- 不让浏览器成为调度真相来源
 20+
 21+## 1. 基本原则
 22+
 23+- 真相来源是 control API 背后的 D1 `system_state`,不是浏览器本地状态。
 24+- Firefox 插件只调用 HTTP control API,不直接操作 conductor 进程。
 25+- 插件负责显示状态和发起人工控制动作,不负责创建、分配、恢复 task。
 26+- `pause`、`resume`、`drain` 都是全局动作,不是单标签页动作。
 27+
 28+## 2. 模式语义
 29+
 30+全局模式只有三种:
 31+
 32+- `running`: 正常调度。
 33+- `draining`: 不再启动新的 step,已启动的 run 可以自然结束。
 34+- `paused`: 不再启动新的 step,conductor 调度暂停;已运行任务是否继续或终止由后端策略决定。
 35+
 36+插件文案必须和这个语义保持一致,尤其不要把 `paused` 展示成“所有运行中的工作都已强制停止”。
 37+
 38+## 3. 鉴权与入口
 39+
 40+推荐入口:
 41+
 42+- Base URL: `https://control-api.makefile.so`
 43+
 44+请求头:
 45+
 46+- `Authorization: Bearer <token>`
 47+- `Content-Type: application/json`
 48+
 49+角色约束:
 50+
 51+- `browser_admin`: 可以调用 `GET /v1/system/state`、`POST /v1/system/pause`、`POST /v1/system/resume`、`POST /v1/system/drain`
 52+- `readonly`: 只可读取 `GET /v1/system/state`
 53+
 54+如果插件同时需要读写,直接使用 `browser_admin` token 即可。
 55+
 56+## 4. 读取状态
 57+
 58+### `GET /v1/system/state`
 59+
 60+Firefox 插件至少要读取这些字段:
 61+
 62+| 字段 | 类型 | 说明 |
 63+| --- | --- | --- |
 64+| `automation.mode` | string | `running \| draining \| paused` |
 65+| `automation.updated_at` | integer | 最近一次模式变更时间,毫秒时间戳 |
 66+| `automation.requested_by` | string \| null | 最近一次变更来源,建议用于审计展示 |
 67+| `automation.reason` | string \| null | 最近一次变更原因 |
 68+| `leader.controller_id` | string \| null | 当前 leader controller id |
 69+| `leader.host` | string \| null | 当前 leader host,例如 `mini` 或 `mac` |
 70+| `leader.role` | string \| null | 当前 leader 注册角色 |
 71+| `leader.lease_expires_at` | integer \| null | 当前 lease 到期时间,毫秒时间戳 |
 72+| `queue.active_runs` | integer | 当前运行中的 run 数 |
 73+| `queue.queued_tasks` | integer | 当前排队中的 task 数 |
 74+| `request_id` | string | 请求追踪 id |
 75+
 76+推荐响应:
 77+
 78+```json
 79+{
 80+  "ok": true,
 81+  "request_id": "req_123",
 82+  "automation": {
 83+    "mode": "running",
 84+    "updated_at": 1760000000000,
 85+    "requested_by": "browser_admin",
 86+    "reason": "human_clicked_resume"
 87+  },
 88+  "leader": {
 89+    "controller_id": "mini-main",
 90+    "host": "mini",
 91+    "role": "primary",
 92+    "lease_expires_at": 1760000030000
 93+  },
 94+  "queue": {
 95+    "active_runs": 2,
 96+    "queued_tasks": 7
 97+  }
 98+}
 99+```
100+
101+插件行为:
102+
103+- popup 或侧边栏打开时立即请求一次。
104+- 建议每 `5` 到 `10` 秒轮询一次;面板关闭后停止轮询。
105+- 每次成功执行 `pause`、`resume`、`drain` 后,优先使用写接口返回的新状态更新 UI;如果后端暂未回传完整状态,则立即补一次 `GET /v1/system/state`。
106+
107+## 5. 写接口
108+
109+### 5.1 共用请求体
110+
111+`POST /v1/system/pause`、`POST /v1/system/resume`、`POST /v1/system/drain` 使用同一套 body。
112+
113+请求体:
114+
115+| 字段 | 类型 | 必填 | 说明 |
116+| --- | --- | --- | --- |
117+| `requested_by` | string | 是 | 固定写 `browser_admin` |
118+| `source` | string | 是 | 固定写 `firefox_extension` |
119+| `reason` | string | 是 | 例如 `human_clicked_pause` |
120+| `request_id` | string | 否 | 前端生成的幂等追踪 id,推荐 UUID |
121+
122+示例:
123+
124+```json
125+{
126+  "requested_by": "browser_admin",
127+  "source": "firefox_extension",
128+  "reason": "human_clicked_pause",
129+  "request_id": "4e08d0d6-4e78-4e58-b71f-9cc0f9c3f245"
130+}
131+```
132+
133+### 5.2 状态迁移
134+
135+- `POST /v1/system/pause`: 把全局模式设为 `paused`
136+- `POST /v1/system/resume`: 把全局模式设为 `running`
137+- `POST /v1/system/drain`: 把全局模式设为 `draining`
138+
139+推荐迁移规则:
140+
141+- `running -> paused`
142+- `draining -> paused`
143+- `paused -> running`
144+- `draining -> running`
145+- `running -> draining`
146+
147+推荐幂等规则:
148+
149+- 当前已经是目标模式时,返回 `200` 和当前状态,不报错。
150+- `paused -> drain` 返回 `409 invalid_mode_transition`,要求用户先 `resume` 再 `drain`。
151+
152+### 5.3 成功响应
153+
154+写接口成功时,建议直接返回最新状态,避免插件立刻多打一轮查询:
155+
156+```json
157+{
158+  "ok": true,
159+  "request_id": "req_124",
160+  "automation": {
161+    "mode": "paused",
162+    "updated_at": 1760000005000,
163+    "requested_by": "browser_admin",
164+    "reason": "human_clicked_pause"
165+  },
166+  "leader": {
167+    "controller_id": "mini-main",
168+    "host": "mini",
169+    "role": "primary",
170+    "lease_expires_at": 1760000030000
171+  },
172+  "queue": {
173+    "active_runs": 2,
174+    "queued_tasks": 7
175+  }
176+}
177+```
178+
179+### 5.4 失败响应
180+
181+失败体遵循 control API 的统一结构:
182+
183+```json
184+{
185+  "ok": false,
186+  "error": "invalid_mode_transition",
187+  "message": "Drain is not allowed while automation is paused.",
188+  "request_id": "req_125"
189+}
190+```
191+
192+插件至少要处理这些错误:
193+
194+- `401` / `403`: token 无效或角色不足
195+- `409`: 非法状态迁移或并发冲突
196+- `5xx`: control API 暂时不可用
197+
198+## 6. 按钮行为
199+
200+最少按钮:
201+
202+- `Pause`
203+- `Resume`
204+- 可选 `Drain`
205+
206+按钮启用规则:
207+
208+- `mode = running`: `Pause` 可点,`Drain` 可点,`Resume` 禁用
209+- `mode = draining`: `Pause` 可点,`Resume` 可点,`Drain` 禁用
210+- `mode = paused`: `Resume` 可点,`Pause` 禁用,`Drain` 禁用
211+
212+交互要求:
213+
214+- 任一写请求发出后,先把三个按钮全部置为 loading / disabled,直到请求结束。
215+- 成功后立即用返回状态刷新 badge、按钮和文案。
216+- 失败后保留旧状态,并展示后端错误消息。
217+- 插件 badge 至少区分 `running`、`draining`、`paused` 三种状态。
218+
219+推荐展示字段:
220+
221+- 当前 `mode`
222+- 当前 leader host
223+- `active_runs`
224+- `queued_tasks`
225+
226+## 7. `control` 与 `dispatch` 边界
227+
228+Firefox 插件要明确区分两个通道:
229+
230+- 可见 `control`: 给人类对话、查看状态、做高层决策
231+- 隐藏 `dispatch`: 给自动化 task dispatch 与 review 流程
232+
233+必须遵守:
234+
235+- 插件的 `pause`、`resume`、`drain` 只走 control API,不走 Claude 对话注入。
236+- 不把自动化任务下发到可见 `control` 对话。
237+- 不把人类交互消息写进隐藏 `dispatch` 通道。
238+- 浏览器内任何状态都不能替代 D1 中的 `system_state`。
239+
240+推荐实现:
241+
242+- 一个可见的 `control` 会话入口
243+- 一个隐藏的 `dispatch` 通道入口
244+- 一个独立的控制面板调用 control API
245+
246+## 8. 给 `baa-firefox` 的落地清单
247+
248+- 读取 `GET /v1/system/state`
249+- 根据 `automation.mode` 渲染 badge 与按钮禁用状态
250+- 实现 `POST /v1/system/pause`
251+- 实现 `POST /v1/system/resume`
252+- 可选实现 `POST /v1/system/drain`
253+- 所有写操作都携带 `browser_admin` token
254+- 所有状态展示都以 control API 返回值为准