baa-conductor

git clone 

commit
2b010d4
parent
7c7e4c5
author
im_wower
date
2026-03-29 04:08:02 +0800 CST
fix: reload open ai tabs after extension restart
2 files changed,  +185, -6
M plugins/baa-firefox/controller.js
+172, -2
  1@@ -46,6 +46,8 @@ const TRACKED_TAB_REFRESH_DELAY = 150;
  2 const SHELL_RUNTIME_HEALTHCHECK_INTERVAL = 30_000;
  3 const CONTROL_STATUS_BODY_LIMIT = 12_000;
  4 const FINAL_MESSAGE_RELAY_CACHE_LIMIT = 20;
  5+const STARTUP_OPEN_AI_TAB_RELOAD_DELAY = 2_500;
  6+const SESSION_CONTROLLER_BOOT_MARKER_KEY = "baaFirefox.controllerSessionBootMarker";
  7 const CONTENT_SCRIPT_INJECTION_FILES = ["delivery-adapters.js", "content-script.js"];
  8 const PAGE_INTERCEPTOR_INJECTION_FILES = ["page-interceptor.js"];
  9 const WS_RECONNECT_DELAY = 3_000;
 10@@ -199,6 +201,7 @@ const state = {
 11   lastControlFailureKey: "",
 12   trackedTabRefreshTimer: null,
 13   shellRuntimeTimer: null,
 14+  startupOpenAiTabReloadTimer: null,
 15   shellRuntimeLastHealthCheckAt: 0,
 16   trackedTabRefreshRunning: false,
 17   trackedTabRefreshQueued: false,
 18@@ -278,6 +281,14 @@ function normalizeSavedControlBaseUrl(value) {
 19   return DEFAULT_CONTROL_BASE_URL;
 20 }
 21 
 22+function getRuntimeOrigin() {
 23+  try {
 24+    return trimToNull(new URL(browser.runtime.getURL("controller.html")).origin);
 25+  } catch (_) {
 26+    return null;
 27+  }
 28+}
 29+
 30 function normalizeSavedWsUrl(value) {
 31   void value;
 32   return DEFAULT_WS_URL;
 33@@ -450,6 +461,7 @@ function createDefaultControllerRuntimeState(overrides = {}) {
 34     lastReloadAt: 0,
 35     lastAction: null,
 36     lastActionAt: 0,
 37+    extensionOrigin: null,
 38     ...overrides
 39   };
 40 }
 41@@ -466,7 +478,8 @@ function cloneControllerRuntimeState(value) {
 42     lastReadyAt: Number(value.lastReadyAt) || 0,
 43     lastReloadAt: Number(value.lastReloadAt) || 0,
 44     lastAction: trimToNull(value.lastAction),
 45-    lastActionAt: Number(value.lastActionAt) || 0
 46+    lastActionAt: Number(value.lastActionAt) || 0,
 47+    extensionOrigin: trimToNull(value.extensionOrigin)
 48   });
 49 }
 50 
 51@@ -5153,6 +5166,16 @@ async function queryPlatformTabs(platform) {
 52   return sortTabsByRecency(await browser.tabs.query({ url: PLATFORMS[platform].urlPatterns }));
 53 }
 54 
 55+async function queryOpenBusinessPlatformTabs(platform) {
 56+  return dedupeTabsById(await queryPlatformTabs(platform)).filter((tab) => {
 57+    if (!Number.isInteger(tab?.id) || !trimToNull(tab.url)) {
 58+      return false;
 59+    }
 60+
 61+    return !isPlatformShellUrl(platform, tab.url || "", { allowFallback: false });
 62+  });
 63+}
 64+
 65 async function findPlatformTab(platform) {
 66   const tabs = await queryPlatformTabs(platform);
 67   if (tabs.length === 0) return null;
 68@@ -5296,6 +5319,148 @@ async function reinjectAllOpenPlatformTabs(options = {}) {
 69   return results;
 70 }
 71 
 72+function describeStartupOpenAiTabReloadDecision(reason) {
 73+  switch (reason) {
 74+    case "initial_install":
 75+      return "首次安装";
 76+    case "extension_reload":
 77+      return "插件重载";
 78+    case "browser_startup":
 79+      return "浏览器启动";
 80+    case "controller_reopen":
 81+      return "controller 页面重新打开";
 82+    case "session_storage_unavailable":
 83+      return "session 存储不可用";
 84+    default:
 85+      return "未命中自动刷新条件";
 86+  }
 87+}
 88+
 89+async function resolveStartupOpenAiTabReloadPlan(previousExtensionOrigin = null) {
 90+  const currentExtensionOrigin = getRuntimeOrigin();
 91+  const sessionStorage = browser.storage?.session;
 92+  let firstControllerInBrowserSession = true;
 93+  let sessionStorageAvailable = false;
 94+
 95+  if (sessionStorage?.get && sessionStorage?.set) {
 96+    sessionStorageAvailable = true;
 97+
 98+    try {
 99+      const saved = await sessionStorage.get(SESSION_CONTROLLER_BOOT_MARKER_KEY);
100+      const existingMarker = trimToNull(saved?.[SESSION_CONTROLLER_BOOT_MARKER_KEY]);
101+      firstControllerInBrowserSession = !existingMarker;
102+
103+      if (!existingMarker) {
104+        await sessionStorage.set({
105+          [SESSION_CONTROLLER_BOOT_MARKER_KEY]: currentExtensionOrigin || String(Date.now())
106+        });
107+      }
108+    } catch (error) {
109+      firstControllerInBrowserSession = true;
110+      addLog(
111+        "warn",
112+        `无法读取 controller session 标记,自动刷新将退化为基于扩展 origin 的判断:${error instanceof Error ? error.message : String(error)}`,
113+        false
114+      );
115+    }
116+  }
117+
118+  const normalizedPreviousOrigin = trimToNull(previousExtensionOrigin);
119+  const initialInstall = !normalizedPreviousOrigin;
120+  const sameBrowserSession = !!currentExtensionOrigin && currentExtensionOrigin === normalizedPreviousOrigin;
121+  const shouldReload = firstControllerInBrowserSession && (initialInstall || sameBrowserSession);
122+  let reason = "unknown";
123+
124+  if (shouldReload) {
125+    reason = initialInstall ? "initial_install" : "extension_reload";
126+  } else if (!firstControllerInBrowserSession) {
127+    reason = "controller_reopen";
128+  } else if (!sessionStorageAvailable) {
129+    reason = "session_storage_unavailable";
130+  } else {
131+    reason = "browser_startup";
132+  }
133+
134+  return {
135+    shouldReload,
136+    reason,
137+    previousExtensionOrigin: normalizedPreviousOrigin,
138+    currentExtensionOrigin
139+  };
140+}
141+
142+async function reloadOpenBusinessPlatformTabs(options = {}) {
143+  const source = trimToNull(options.source) || "startup";
144+  const candidates = [];
145+
146+  for (const platform of PLATFORM_ORDER) {
147+    const tabs = await queryOpenBusinessPlatformTabs(platform);
148+    for (const tab of tabs) {
149+      candidates.push({
150+        platform,
151+        tabId: tab.id,
152+        url: trimToNull(tab.url)
153+      });
154+    }
155+  }
156+
157+  if (candidates.length === 0) {
158+    addLog("info", `无需刷新已打开的 AI 页面,来源 ${source}`, false);
159+    return [];
160+  }
161+
162+  const reloaded = [];
163+  for (const candidate of candidates) {
164+    try {
165+      await browser.tabs.reload(candidate.tabId);
166+      reloaded.push(candidate);
167+      addLog(
168+        "info",
169+        `已自动刷新 ${platformLabel(candidate.platform)} 标签页 ${candidate.tabId},URL ${candidate.url || "-"},来源 ${source}`,
170+        false
171+      );
172+    } catch (error) {
173+      addLog(
174+        "warn",
175+        `自动刷新 ${platformLabel(candidate.platform)} 标签页 ${candidate.tabId} 失败,URL ${candidate.url || "-"}:${error instanceof Error ? error.message : String(error)}`,
176+        false
177+      );
178+    }
179+  }
180+
181+  addLog("info", `已自动刷新 ${reloaded.length}/${candidates.length} 个 AI 业务标签页,来源 ${source}`, false);
182+  return reloaded;
183+}
184+
185+function scheduleStartupOpenAiTabReload(plan = null) {
186+  clearTimeout(state.startupOpenAiTabReloadTimer);
187+
188+  if (!plan?.shouldReload) {
189+    addLog("info", `跳过已打开 AI 页面自动刷新:${describeStartupOpenAiTabReloadDecision(plan?.reason)}`, false);
190+    return false;
191+  }
192+
193+  addLog(
194+    "info",
195+    `检测到${describeStartupOpenAiTabReloadDecision(plan.reason)},将在 ${STARTUP_OPEN_AI_TAB_RELOAD_DELAY}ms 后自动刷新已打开的 AI 业务页`,
196+    false
197+  );
198+
199+  state.startupOpenAiTabReloadTimer = setTimeout(() => {
200+    reloadOpenBusinessPlatformTabs({
201+      source: plan.reason
202+    }).catch((error) => {
203+      addLog(
204+        "error",
205+        `自动刷新已打开 AI 页面失败:${error instanceof Error ? error.message : String(error)}`,
206+        false
207+      );
208+    });
209+  }, STARTUP_OPEN_AI_TAB_RELOAD_DELAY);
210+
211+  return true;
212+}
213+
214 async function setTrackedTab(platform, tab) {
215   state.trackedTabs[platform] = tab ? tab.id : null;
216   state.actualTabs[platform] = buildActualTabSnapshot(
217@@ -7251,6 +7416,8 @@ async function init() {
218   restoreFinalMessageRelayCache(saved[CONTROLLER_STORAGE_KEYS.finalMessageRelayCache]);
219   state.claudeState = loadClaudeState(saved[CONTROLLER_STORAGE_KEYS.claudeState]);
220   state.controllerRuntime = loadControllerRuntimeState(saved[CONTROLLER_STORAGE_KEYS.controllerRuntime]);
221+  const previousExtensionOrigin = trimToNull(state.controllerRuntime.extensionOrigin);
222+  const startupOpenAiTabReloadPlan = await resolveStartupOpenAiTabReloadPlan(previousExtensionOrigin);
223   if (needsStatusReset) {
224     state.lastHeaders = createPlatformMap(() => ({}));
225     state.credentialCapturedAt = createPlatformMap(() => 0);
226@@ -7288,7 +7455,8 @@ async function init() {
227     tabId: current?.id ?? null,
228     ready: true,
229     status: "ready",
230-    lastReadyAt: Date.now()
231+    lastReadyAt: Date.now(),
232+    extensionOrigin: startupOpenAiTabReloadPlan.currentExtensionOrigin
233   });
234 
235   if (Number.isInteger(current?.id)) {
236@@ -7323,12 +7491,14 @@ async function init() {
237   restartShellRuntimeHealthTimer(SHELL_RUNTIME_HEALTHCHECK_INTERVAL);
238   await prepareStartupControlState();
239   refreshControlPlaneState({ source: "startup", silent: true }).catch(() => {});
240+  scheduleStartupOpenAiTabReload(startupOpenAiTabReloadPlan);
241 }
242 
243 window.addEventListener("beforeunload", () => {
244   clearTimeout(state.controlRefreshTimer);
245   clearTimeout(state.trackedTabRefreshTimer);
246   clearTimeout(state.shellRuntimeTimer);
247+  clearTimeout(state.startupOpenAiTabReloadTimer);
248   closeWsConnection();
249 });
250 
M tasks/T-BUG-031.md
+13, -4
 1@@ -2,7 +2,7 @@
 2 
 3 ## 状态
 4 
 5-- 当前状态:`待开始`
 6+- 当前状态:`已完成`
 7 - 规模预估:`S`
 8 - 依赖任务:无
 9 - 建议执行者:`均可`
10@@ -100,21 +100,30 @@
11 
12 ### 开始执行
13 
14-- 执行者:
15-- 开始时间:
16+- 执行者:`Codex (GPT-5)`
17+- 开始时间:`2026-03-29 03:45:00 CST`
18 - 状态变更:`待开始` → `进行中`
19 
20 ### 完成摘要
21 
22-- 完成时间:
23+- 完成时间:`2026-03-29 04:07:14 CST`
24 - 状态变更:`进行中` → `已完成`
25 - 修改了哪些文件:
26+  - `plugins/baa-firefox/controller.js`
27+  - `tasks/T-BUG-031.md`
28 - 核心实现思路:
29+  - 在 `controller.js` 启动初始化阶段增加一次性判定:结合 `browser.storage.session` 的本次浏览器会话标记与持久化的扩展 `origin`,区分“插件安装/重载”与“浏览器普通启动 / controller 页面重开”。
30+  - 仅在判定为安装或重载时,延迟 `2500ms` 查询 Claude / ChatGPT / Gemini 已打开标签页,并只刷新非 `#baa-shell` 的业务页面。
31+  - 为跳过、无需刷新、逐 tab 刷新成功 / 失败都增加日志,日志包含平台、tab ID、URL 和来源。
32 - 跑了哪些测试:
33+  - `node --check plugins/baa-firefox/controller.js`
34 
35 ### 执行过程中遇到的问题
36 
37 > 记录执行过程中遇到的阻塞、环境问题、临时绕过方案等。合并时由合并者判断是否需要修复或建新任务。
38 
39+- 未发现阻塞问题。
40+
41 ### 剩余风险
42 
43+- 该实现依赖 Firefox 扩展侧 `browser.storage.session` 与持久化的扩展 `origin` 来区分“插件重载”和“浏览器重启”。如果未来 Firefox 在这两处行为上发生变化,可能需要调整判定逻辑。