From 1f45e74378bdb82f5f821783b2ab77d7d7f7f7aa Mon Sep 17 00:00:00 2001 From: Simon <10131203+gaomeng1900@users.noreply.github.com> Date: Thu, 2 Jul 2026 02:11:32 +0800 Subject: [PATCH 1/2] fix(extension): resolve active tab/window per caller context --- .../src/agent/TabsController.background.ts | 46 ++++++++++++++++--- .../extension/src/agent/TabsController.ts | 22 ++++++++- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/packages/extension/src/agent/TabsController.background.ts b/packages/extension/src/agent/TabsController.background.ts index 5b4baff..34b8b06 100644 --- a/packages/extension/src/agent/TabsController.background.ts +++ b/packages/extension/src/agent/TabsController.background.ts @@ -7,6 +7,39 @@ const PREFIX = '[TabsController.background]' const debug = console.debug.bind(console, `\x1b[90m${PREFIX}\x1b[0m`) +/** + * Resolve active tab. + * + * - `tabs.query({ active: true })` does not work in multi-window scenarios. + * - Extension pages (side panel, hub tab) can resolve their own windowId. + * We just find the active tab within that window. + * - Content scripts (PAGE_AGENT_EXT) can't self-report a windowId. + * Chrome populates `sender.tab` for every content-script message, + * which is the tab hosting the script. + */ +async function resolveActiveTab( + payload: { windowId?: number } | undefined, + sender: chrome.runtime.MessageSender +): Promise { + const windowId = payload?.windowId + + if (windowId != null) { + debug('get_active_tab: resolving via caller-reported windowId', windowId) + const [tab] = await chrome.tabs.query({ active: true, windowId }) + if (!tab) throw new Error(`No active tab found in window ${windowId}.`) + return tab + } + + if (sender.tab) { + debug('get_active_tab: resolving via sender.tab (content script)', sender.tab.id) + return sender.tab + } + + throw new Error( + 'Cannot resolve active tab: caller reported no windowId and is not a content script (no sender.tab).' + ) +} + export function handleTabControlMessage( message: { type: 'TAB_CONTROL'; action: TabAction; payload: any }, sender: chrome.runtime.MessageSender, @@ -16,12 +49,11 @@ export function handleTabControlMessage( switch (action as TabAction) { case 'get_active_tab': { - debug('get_active_tab') - chrome.tabs - .query({ active: true }) - .then((tabs) => { - debug('get_active_tab: success', tabs) - sendResponse({ success: true, tab: tabs[0] }) + debug('get_active_tab', payload) + resolveActiveTab(payload, sender) + .then((tab) => { + debug('get_active_tab: success', tab) + sendResponse({ success: true, tab }) }) .catch((error) => { sendResponse({ error: error instanceof Error ? error.message : String(error) }) @@ -46,7 +78,7 @@ export function handleTabControlMessage( case 'open_new_tab': { debug('open_new_tab', payload) chrome.tabs - .create({ url: payload.url, active: false }) + .create({ url: payload.url, windowId: payload.windowId, active: false }) .then((newTab) => { debug('open_new_tab: success', newTab) sendResponse({ success: true, tabId: newTab.id }) diff --git a/packages/extension/src/agent/TabsController.ts b/packages/extension/src/agent/TabsController.ts index 35f1c45..a157798 100644 --- a/packages/extension/src/agent/TabsController.ts +++ b/packages/extension/src/agent/TabsController.ts @@ -15,6 +15,20 @@ function sendMessage(message: { }) } +/** + * Resolve the window hosting this script's own context, when knowable. + * + * Extension pages (side panel, hub tab) have `chrome.windows` access and can + * identify their own window directly via `getCurrent()`. + * Content scripts have no `chrome.windows` access; they resolve `undefined` + * here and the background script falls back to `sender.tab` instead. + */ +async function getOwnWindowId(): Promise { + if (typeof chrome.windows === 'undefined') return undefined + const win = await chrome.windows.getCurrent() + return win.id +} + /** * Controller for managing browser tabs. * - live in the agent env (extension page or content script) @@ -57,6 +71,7 @@ export class TabsController { const activeTabResult = await sendMessage({ type: 'TAB_CONTROL', action: 'get_active_tab', + payload: { windowId: await getOwnWindowId() }, }) this.initialTabId = activeTabResult.tab?.id @@ -124,7 +139,7 @@ export class TabsController { const result = await sendMessage({ type: 'TAB_CONTROL', action: 'open_new_tab', - payload: { url }, + payload: { url, windowId: this.windowId }, }) if (!result.success) { @@ -307,7 +322,10 @@ export class TabsController { if (message.action === 'created') { const tab = message.payload.tab as chrome.tabs.Tab - const shouldTrack = this.experimentalIncludeAllTabs || tab.groupId === this.tabGroupId + const shouldTrack = + tab.groupId === this.tabGroupId || + // @note Never track tabs from other windows. + (this.experimentalIncludeAllTabs && tab.windowId === this.windowId) if (shouldTrack && tab.id != null) { this.addTab({ id: tab.id, isInitial: false }) this.switchToTab(tab.id) From 8b902e1e10cfab88378a8ac6139d9611bfedb8ed Mon Sep 17 00:00:00 2001 From: Simon <10131203+gaomeng1900@users.noreply.github.com> Date: Thu, 2 Jul 2026 02:32:33 +0800 Subject: [PATCH 2/2] docs(ext): add limitations section --- packages/extension/docs/extension_api.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/extension/docs/extension_api.md b/packages/extension/docs/extension_api.md index bb48461..730e5b2 100644 --- a/packages/extension/docs/extension_api.md +++ b/packages/extension/docs/extension_api.md @@ -96,6 +96,10 @@ Returns: `Promise` Stop the current task. +## Limitations + +- **Normal browser windows only.** This extension relies on tab group API which does not work for pop-up window or PWA App window. + ## Types Install `@page-agent/core` for complete types: