- 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
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
+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 返回值为准