baa-conductor

git clone 

commit
eb2b6b3
parent
3d06308
author
im_wower
date
2026-03-22 16:19:39 +0800 CST
fix(firefox): disable legacy ws by default
5 files changed,  +152, -64
M docs/firefox/README.md
+6, -0
 1@@ -56,6 +56,7 @@
 2 - Firefox 插件只调用 HTTP control API,不直接操作 conductor 进程。
 3 - 插件负责显示状态和发起人工控制动作,不负责创建、分配、恢复 task。
 4 - `pause`、`resume`、`drain` 都是全局动作,不是单标签页动作。
 5+- Firefox 插件默认模式不依赖 websocket;如果保留兼容能力,也只能作为手动启用的可选通道。
 6 - 插件里的“跟踪标签页”只统计当前真实打开且仍匹配平台 host 的页面,不把历史 storage 残留当成当前状态。
 7 - 插件里的“凭证”只统计绑定到当前 tracked tab、最近重新捕获且仍通过平台登录规则校验的快照;启动时不自动补开全部平台页。
 8 
 9@@ -79,9 +80,12 @@
10 
11 - `CONTROL_API_AUTH_REQUIRED=false`
12 - Firefox 插件默认直接请求 `https://control-api.makefile.so`
13+- Firefox 插件默认不配置 websocket,也不会主动连接 `ws://127.0.0.1:9800`
14+- 当前不要求本地 `baa-server`
15 - 当前不要求在插件里填写 token
16 
17 如果后续重新启用鉴权,再恢复 `Authorization: Bearer <token>` 即可。
18+如果后续需要兼容旧的 browser-proxy WS,再在插件里手动填写 `ws://` / `wss://` 地址并显式启用。
19 
20 ## 4. 读取状态
21 
22@@ -252,6 +256,7 @@ Firefox 插件至少要读取这些字段:
23 - 当前 leader host
24 - `active_runs`
25 - `queued_tasks`
26+- 可选 WS 状态;未配置时展示 `未启用`,不要展示成 `WS 未连接`
27 
28 ## 7. `control` 与 `dispatch` 边界
29 
30@@ -280,5 +285,6 @@ Firefox 插件要明确区分两个通道:
31 - 实现 `POST /v1/system/pause`
32 - 实现 `POST /v1/system/resume`
33 - 可选实现 `POST /v1/system/drain`
34+- 当前默认不配置 websocket;只有用户手动填写后才启用可选 WS
35 - 当前临时模式下,插件不携带 token;如果后续重新启用鉴权,再恢复 bearer token
36 - 所有状态展示都以 control API 返回值为准
M plugins/baa-firefox/README.md
+12, -10
 1@@ -15,10 +15,11 @@ This extension keeps an always-open controller page and tracks one live browser
 2 
 3 It does four things:
 4 
 5-- keeps a WebSocket connection to `baa-server`
 6+- reads and updates conductor state via `https://control-api.makefile.so`
 7+- keeps an optional legacy WebSocket path for browser-proxy workflows when manually enabled
 8 - tracks current real tabs for `claude.ai`, `chatgpt.com`, and `gemini.google.com`, and only opens them on demand
 9 - discovers live API endpoints from real browser traffic
10-- captures auth-related request headers and forwards them to `baa-server`
11+- captures auth-related request headers and forwards them to the optional WS channel when enabled
12 
13 This MVP is intentionally narrow:
14 
15@@ -33,7 +34,7 @@ This MVP is intentionally narrow:
16 - `manifest.json` - Firefox MV3 manifest
17 - `background.js` - opens and keeps the controller tab around
18 - `controller.html` - always-open management page
19-- `controller.js` - WS owner, multi-platform tab manager, webRequest capture, message relay
20+- `controller.js` - control API client, optional WS owner, multi-platform tab manager, webRequest capture, message relay
21 - `content-script.js` - bridge from page events into the extension runtime
22 - `page-interceptor.js` - MAIN world `fetch` interceptor
23 - `scripts/run-persistent.sh` - starts Firefox with the persistent `baa-firefox-persistent` profile
24@@ -71,17 +72,18 @@ BAA_FIREFOX_PROFILE=baa-firefox-persistent ./scripts/run-persistent.sh
25 
26 ## How To Run
27 
28-1. Start `baa-server` so `ws://localhost:9800` is available.
29-2. Load this extension temporarily.
30-3. In the controller page, keep the default WS URL or change it.
31+1. Load this extension temporarily.
32+2. Open `controller.html`; the default control API should already be `https://control-api.makefile.so`.
33+3. Leave the optional WS input empty unless you explicitly need the legacy browser-proxy channel.
34 4. Use the controller buttons to open only the platform pages you actually need.
35 5. Log in manually in the tabs you want to intercept.
36 6. Use Claude, ChatGPT, or Gemini normally.
37-7. Watch the controller page and `baa-server` logs for:
38+7. Watch the controller page for:
39+   - control-plane mode and leader sync
40    - endpoint discovery
41    - credential snapshots
42-   - network events
43-   - SSE events
44+
45+If you still need the old WS path, manually fill a `ws://` or `wss://` address and click the reconnect button to enable it.
46 
47 ## Expected MVP Result
48 
49@@ -94,7 +96,7 @@ Once the platform tabs start making real requests, the extension should forward:
50 - `sse_event`
51 - `client_log`
52 
53-These message shapes match the current `baa-server` browser-side WS handling.
54+These message shapes still match the current `baa-server` browser-side WS handling when the optional WS channel is enabled.
55 
56 ## Limitations
57 
M plugins/baa-firefox/controller.html
+28, -28
 1@@ -18,14 +18,14 @@
 2         <button id="open-claude-btn" type="button">打开 Claude</button>
 3         <button id="open-chatgpt-btn" type="button">打开 ChatGPT</button>
 4         <button id="open-gemini-btn" type="button">打开 Gemini</button>
 5-        <button id="reconnect-btn" type="button">重连 WS</button>
 6+        <button id="reconnect-btn" type="button">启用可选 WS</button>
 7       </div>
 8     </section>
 9 
10     <section class="settings">
11-      <label for="ws-url">BAA WS</label>
12-      <input id="ws-url" type="text" spellcheck="false">
13-      <button id="save-ws-btn" type="button">保存</button>
14+      <label for="ws-url">可选 WS</label>
15+      <input id="ws-url" type="text" spellcheck="false" placeholder="留空表示未启用">
16+      <button id="save-ws-btn" type="button">保存 WS 配置</button>
17       <label for="control-base-url">控制 API</label>
18       <input id="control-base-url" type="text" spellcheck="false" placeholder="https://control-api.makefile.so">
19       <button id="save-control-btn" type="button">保存控制面配置</button>
20@@ -36,30 +36,6 @@
21     </section>
22 
23     <section class="grid">
24-      <article class="card">
25-        <p class="label">WS</p>
26-        <p id="ws-status" class="value off">未连接</p>
27-        <p id="client-id" class="meta">客户端: -</p>
28-      </article>
29-
30-      <article class="card">
31-        <p class="label">跟踪标签页</p>
32-        <p id="tab-status" class="value off">0 / 3</p>
33-        <p id="tab-meta" class="meta">标签页: -</p>
34-      </article>
35-
36-      <article class="card">
37-        <p class="label">凭证</p>
38-        <p id="cred-status" class="value off">0 / 3</p>
39-        <p id="cred-meta" class="meta">请求头: 0</p>
40-      </article>
41-
42-      <article class="card">
43-        <p class="label">端点</p>
44-        <p id="endpoint-count" class="value">0</p>
45-        <p class="meta">自动探测</p>
46-      </article>
47-
48       <article class="card">
49         <p class="label">自动化模式</p>
50         <p id="control-mode" class="value off">未知</p>
51@@ -83,6 +59,30 @@
52         <p id="runs-value" class="value off">-</p>
53         <p id="runs-meta" class="meta">控制面</p>
54       </article>
55+
56+      <article class="card">
57+        <p class="label">可选 WS</p>
58+        <p id="ws-status" class="value off">未启用</p>
59+        <p id="client-id" class="meta">当前默认仅使用 Control API</p>
60+      </article>
61+
62+      <article class="card">
63+        <p class="label">跟踪标签页</p>
64+        <p id="tab-status" class="value off">0 / 3</p>
65+        <p id="tab-meta" class="meta">标签页: -</p>
66+      </article>
67+
68+      <article class="card">
69+        <p class="label">凭证</p>
70+        <p id="cred-status" class="value off">0 / 3</p>
71+        <p id="cred-meta" class="meta">请求头: 0</p>
72+      </article>
73+
74+      <article class="card">
75+        <p class="label">端点</p>
76+        <p id="endpoint-count" class="value">0</p>
77+        <p class="meta">自动探测</p>
78+      </article>
79     </section>
80 
81     <section class="panel">
M plugins/baa-firefox/controller.js
+97, -24
  1@@ -20,8 +20,12 @@ const CONTROLLER_STORAGE_KEYS = {
  2   geminiSendTemplate: "baaFirefox.geminiSendTemplate"
  3 };
  4 
  5-const DEFAULT_WS_URL = "ws://127.0.0.1:9800";
  6+const DEFAULT_WS_URL = "";
  7 const DEFAULT_CONTROL_BASE_URL = "https://control-api.makefile.so";
  8+const LEGACY_DEFAULT_WS_URLS = new Set([
  9+  "ws://127.0.0.1:9800",
 10+  "ws://localhost:9800"
 11+]);
 12 const STATUS_SCHEMA_VERSION = 2;
 13 const CREDENTIAL_SEND_INTERVAL = 30_000;
 14 const CREDENTIAL_TTL = 15 * 60_000;
 15@@ -196,12 +200,36 @@ function normalizeSavedControlBaseUrl(value) {
 16   return normalized;
 17 }
 18 
 19+function normalizeSavedWsUrl(value) {
 20+  const normalized = trimTrailingSlash(value);
 21+
 22+  if (!normalized || LEGACY_DEFAULT_WS_URLS.has(normalized)) {
 23+    return DEFAULT_WS_URL;
 24+  }
 25+
 26+  try {
 27+    const parsed = new URL(normalized);
 28+    return parsed.protocol === "ws:" || parsed.protocol === "wss:"
 29+      ? normalized
 30+      : DEFAULT_WS_URL;
 31+  } catch (_) {
 32+    return DEFAULT_WS_URL;
 33+  }
 34+}
 35+
 36 function deriveControlBaseUrl(wsUrl) {
 37+  const normalizedWsUrl = normalizeSavedWsUrl(wsUrl);
 38+  if (!normalizedWsUrl) {
 39+    return DEFAULT_CONTROL_BASE_URL;
 40+  }
 41+
 42   try {
 43-    const parsed = new URL(wsUrl || DEFAULT_WS_URL);
 44+    const parsed = new URL(normalizedWsUrl);
 45     parsed.protocol = parsed.protocol === "wss:" ? "https:" : "http:";
 46     const derived = trimTrailingSlash(parsed.toString());
 47-    return derived === "http://127.0.0.1:9800" ? DEFAULT_CONTROL_BASE_URL : derived;
 48+    return derived === "http://127.0.0.1:9800" || derived === "http://localhost:9800"
 49+      ? DEFAULT_CONTROL_BASE_URL
 50+      : derived;
 51   } catch (_) {
 52     return DEFAULT_CONTROL_BASE_URL;
 53   }
 54@@ -436,6 +464,10 @@ function formatSyncTime(timestamp) {
 55   return new Date(timestamp).toLocaleString("zh-CN", { hour12: false });
 56 }
 57 
 58+function isWsEnabled() {
 59+  return !!state.wsUrl;
 60+}
 61+
 62 function controlModeClass(snapshot) {
 63   if (!snapshot || snapshot.mode === "unknown") return "off";
 64   if (snapshot.error) return "warn";
 65@@ -1109,9 +1141,10 @@ function render() {
 66   const credentialCount = getCredentialCount();
 67   const totalEndpointCount = getTotalEndpointCount();
 68   const controlSnapshot = cloneControlState(state.controlState);
 69+  const wsEnabled = isWsEnabled();
 70 
 71-  ui.wsStatus.textContent = state.wsConnected ? "已连接" : "未连接";
 72-  ui.wsStatus.className = `value ${state.wsConnected ? "on" : "off"}`;
 73+  ui.wsStatus.textContent = !wsEnabled ? "未启用" : state.wsConnected ? "已连接" : "未连接";
 74+  ui.wsStatus.className = `value ${!wsEnabled ? "off" : state.wsConnected ? "on" : "warn"}`;
 75 
 76   ui.tabStatus.textContent = `${trackedCount} / ${PLATFORM_ORDER.length}`;
 77   ui.tabStatus.className = `value ${trackedCount === 0 ? "off" : trackedCount === PLATFORM_ORDER.length ? "on" : "warn"}`;
 78@@ -1131,7 +1164,9 @@ function render() {
 79 
 80   ui.endpointCount.textContent = String(totalEndpointCount);
 81   ui.endpointCount.className = `value ${totalEndpointCount > 0 ? "on" : "off"}`;
 82-  ui.clientId.textContent = `客户端: ${state.clientId || "-"}`;
 83+  ui.clientId.textContent = !wsEnabled
 84+    ? "可选能力;当前默认仅使用 Control API"
 85+    : `客户端: ${state.clientId || "-"}${state.wsConnected ? "" : " · 等待连接"}`;
 86   ui.controlMode.textContent = formatModeLabel(controlSnapshot.mode);
 87   ui.controlMode.className = `value ${controlModeClass(controlSnapshot)}`;
 88   ui.controlMeta.textContent = `${formatSyncTime(controlSnapshot.syncedAt)}${controlSnapshot.error ? ` · ${controlSnapshot.error}` : ""}`;
 89@@ -1151,6 +1186,9 @@ function render() {
 90   ui.logView.textContent = state.logs.length > 0
 91     ? state.logs.join("\n")
 92     : "还没有日志。";
 93+  if (ui.reconnectBtn) {
 94+    ui.reconnectBtn.textContent = wsEnabled ? "重连可选 WS" : "启用可选 WS";
 95+  }
 96 }
 97 
 98 async function setControlState(next) {
 99@@ -1441,7 +1479,7 @@ function sendCredentialSnapshot(platform = null, force = false) {
100   }
101 }
102 
103-function connectWs() {
104+function closeWsConnection() {
105   clearTimeout(state.reconnectTimer);
106 
107   if (state.ws) {
108@@ -1454,6 +1492,23 @@ function connectWs() {
109     } catch (_) {}
110   }
111 
112+  state.ws = null;
113+  state.wsConnected = false;
114+}
115+
116+function connectWs(options = {}) {
117+  const { silentWhenDisabled = false } = options;
118+
119+  closeWsConnection();
120+  render();
121+
122+  if (!isWsEnabled()) {
123+    if (!silentWhenDisabled) {
124+      addLog("info", "可选 WS 未启用;当前默认仅使用 Control API", false);
125+    }
126+    return;
127+  }
128+
129   addLog("info", `正在连接 WS:${state.wsUrl}`, false);
130 
131   try {
132@@ -1525,8 +1580,9 @@ function connectWs() {
133 
134 function scheduleReconnect() {
135   clearTimeout(state.reconnectTimer);
136+  if (!isWsEnabled()) return;
137   state.reconnectTimer = setTimeout(() => {
138-    connectWs();
139+    connectWs({ silentWhenDisabled: true });
140   }, 3000);
141 }
142 
143@@ -2029,6 +2085,7 @@ function bindUi() {
144   ui.logView = qs("log-view");
145   ui.wsUrl = qs("ws-url");
146   ui.controlBaseUrl = qs("control-base-url");
147+  ui.reconnectBtn = qs("reconnect-btn");
148 
149   for (const platform of PLATFORM_ORDER) {
150     qs(`open-${platform}-btn`).addEventListener("click", () => {
151@@ -2038,16 +2095,36 @@ function bindUi() {
152     });
153   }
154 
155-  qs("reconnect-btn").addEventListener("click", () => {
156-    state.wsUrl = ui.wsUrl.value.trim() || DEFAULT_WS_URL;
157+  ui.reconnectBtn.addEventListener("click", () => {
158+    state.wsUrl = normalizeSavedWsUrl(ui.wsUrl.value);
159+    ui.wsUrl.value = state.wsUrl;
160     persistState().catch(() => {});
161-    connectWs();
162+    connectWs({ silentWhenDisabled: false });
163   });
164 
165   qs("save-ws-btn").addEventListener("click", () => {
166-    state.wsUrl = ui.wsUrl.value.trim() || DEFAULT_WS_URL;
167+    const previousWsUrl = state.wsUrl;
168+    const wasConnected = state.wsConnected;
169+
170+    state.wsUrl = normalizeSavedWsUrl(ui.wsUrl.value);
171+    ui.wsUrl.value = state.wsUrl;
172     persistState().catch(() => {});
173-    addLog("info", `已保存 WS 地址:${state.wsUrl}`, false);
174+
175+    if (!isWsEnabled()) {
176+      closeWsConnection();
177+      addLog("info", "已禁用可选 WS;当前默认仅使用 Control API", false);
178+      render();
179+      return;
180+    }
181+
182+    if (previousWsUrl !== state.wsUrl) {
183+      closeWsConnection();
184+      addLog("info", `已保存可选 WS 地址:${state.wsUrl};点击“重连可选 WS”开始连接`, false);
185+      render();
186+      return;
187+    }
188+
189+    addLog("info", `已保存可选 WS 地址:${state.wsUrl}${wasConnected ? "" : ";点击“重连可选 WS”开始连接"}`, false);
190     render();
191   });
192 
193@@ -2091,10 +2168,7 @@ async function init() {
194   const needsStatusReset = savedSchemaVersion < STATUS_SCHEMA_VERSION;
195 
196   state.clientId = saved[CONTROLLER_STORAGE_KEYS.clientId] || genClientId();
197-  state.wsUrl = saved[CONTROLLER_STORAGE_KEYS.wsUrl] || DEFAULT_WS_URL;
198-  if (state.wsUrl === "ws://localhost:9800") {
199-    state.wsUrl = DEFAULT_WS_URL;
200-  }
201+  state.wsUrl = normalizeSavedWsUrl(saved[CONTROLLER_STORAGE_KEYS.wsUrl]);
202   state.controlBaseUrl = normalizeSavedControlBaseUrl(saved[CONTROLLER_STORAGE_KEYS.controlBaseUrl]);
203   state.controlState = loadControlState(saved[CONTROLLER_STORAGE_KEYS.controlState]);
204 
205@@ -2146,20 +2220,19 @@ async function init() {
206     addLog("info", "已清理旧版平台状态缓存,等待新的真实请求重新建立凭证", false);
207   }
208 
209-  connectWs();
210+  if (isWsEnabled()) {
211+    connectWs({ silentWhenDisabled: true });
212+  } else {
213+    addLog("info", "当前默认模式只使用 Control API;可选 WS 保持未启用", false);
214+  }
215   restartControlPlaneRefreshTimer();
216   refreshControlPlaneState({ source: "startup", silent: true }).catch(() => {});
217 }
218 
219 window.addEventListener("beforeunload", () => {
220-  clearTimeout(state.reconnectTimer);
221   clearInterval(state.controlRefreshTimer);
222   clearTimeout(state.trackedTabRefreshTimer);
223-  if (state.ws) {
224-    try {
225-      state.ws.close();
226-    } catch (_) {}
227-  }
228+  closeWsConnection();
229 });
230 
231 init().catch((error) => {
M plugins/baa-firefox/docs/conductor-control.md
+9, -2
 1@@ -17,12 +17,17 @@
 2 
 3 ## 配置
 4 
 5-控制页新增了一项:
 6+控制页现在有两项控制面相关配置:
 7 
 8 - `Control API`
 9   - 默认预填 `https://control-api.makefile.so`
10+- `可选 WS`
11+  - 默认留空
12+  - 留空表示未启用
13+  - 只有手动填写 `ws://` / `wss://` 地址后才会尝试连接
14 
15 当前临时单节点模式默认不开启 Control API 鉴权,因此插件不会再要求填写 bearer token。
16+当前默认模式也不再要求本地 `ws://127.0.0.1:9800` 或 `ws://localhost:9800`。
17 
18 状态快照会持久化到 `browser.storage.local` 的 `baaFirefox.controlState`,供:
19 
20@@ -47,7 +52,8 @@
21 - 扩展工具栏 badge
22   - `RUN` / `PAU` / `DRN` / `ERR`
23 - `controller.html`
24-  - 可见 control-plane 卡片、原始快照和动作按钮
25+  - 优先展示 control-plane 卡片、原始快照和动作按钮
26+  - 如果未配置 WS,则显示 `未启用`,不再把它当成主故障
27 - Claude 页面
28   - 右下角 `BAA` 浮层,可直接 `Pause` / `Resume` / `Drain` 或打开 controller
29 
30@@ -56,3 +62,4 @@
31 - 本次没有改 `manifest.json`
32 - 因此 Control API 需要落在当前扩展 CSP 允许的地址范围内
33 - 当前默认目标就是 `https://control-api.makefile.so`
34+- 可选 WS 能力仍然保留,但默认不启用