diff --git a/extension/dist/background.js b/extension/dist/background.js index f456097fa..47117fd0a 100644 --- a/extension/dist/background.js +++ b/extension/dist/background.js @@ -1045,6 +1045,11 @@ async function focusOwnedWindowIfRequested(windowId, mode) { async function toOwnedContainerDiscoveryCandidate(group) { try { const chromeWindow = await chrome.windows.get(group.windowId); + const windowTabs = await chrome.tabs.query({ windowId: group.windowId }); + const hasUserTabsOutsideGroup = windowTabs.some( + (tab) => tab.groupId !== group.id && !!tab.url && isSafeNavigationUrl(tab.url) + ); + if (hasUserTabsOutsideGroup) return null; const reusableTabId = await findReusableOwnedContainerTab(group.windowId); return { windowId: group.windowId, diff --git a/extension/src/background.test.ts b/extension/src/background.test.ts index 510da347e..35c2ede5b 100644 --- a/extension/src/background.test.ts +++ b/extension/src/background.test.ts @@ -1290,6 +1290,35 @@ describe('background tab isolation', () => { expect(chrome.tabGroups.update).not.toHaveBeenCalled(); }); + it('skips a residual OpenCLI Adapter group when its window holds user http tabs outside the group', async () => { + const { chrome, tabs, groups } = createChromeMock(); + tabs.push({ + id: 77, + windowId: 7, + url: 'https://example.com/users-real-tab', + title: 'users real tab', + active: true, + status: 'complete', + groupId: -1, + }); + groups.push({ + id: 99, + windowId: 7, + title: 'OpenCLI Adapter', + color: 'orange', + collapsed: true, + }); + vi.stubGlobal('chrome', chrome); + + const mod = await import('./background'); + const tabId = await mod.__test__.resolveTabId(undefined, adapterKey('twitter')); + + expect(chrome.windows.create).toHaveBeenCalledTimes(1); + expect(mod.__test__.getAutomationWindowId(adapterKey('twitter'))).not.toBe(7); + expect(tabs.find((tab) => tab.id === 77)?.url).toBe('https://example.com/users-real-tab'); + expect(tabs.find((tab) => tab.id === tabId)?.windowId).not.toBe(7); + }); + it('prefers a focused OpenCLI Adapter group when multiple matching groups exist', async () => { const { chrome, tabs, groups, setLastFocusedWindowId } = createChromeMock(); setLastFocusedWindowId(8); diff --git a/extension/src/background.ts b/extension/src/background.ts index 7ec54dbb2..931741f28 100644 --- a/extension/src/background.ts +++ b/extension/src/background.ts @@ -557,6 +557,13 @@ async function focusOwnedWindowIfRequested(windowId: number, mode: WindowMode): async function toOwnedContainerDiscoveryCandidate(group: chrome.tabGroups.TabGroup): Promise { try { const chromeWindow = await chrome.windows.get(group.windowId); + // Skip windows holding user http(s) tabs outside our group: a tab group + // moved into the user's main window (via drag or merge) is residue. + const windowTabs = await chrome.tabs.query({ windowId: group.windowId }); + const hasUserTabsOutsideGroup = windowTabs.some((tab) => + tab.groupId !== group.id && !!tab.url && isSafeNavigationUrl(tab.url), + ); + if (hasUserTabsOutsideGroup) return null; const reusableTabId = await findReusableOwnedContainerTab(group.windowId); return { windowId: group.windowId,