diff --git a/src/main/runtime/browser/client-pages/browser-client-page-queries.ts b/src/main/runtime/browser/client-pages/browser-client-page-queries.ts index 07b6c157b09..98509e9c4ca 100644 --- a/src/main/runtime/browser/client-pages/browser-client-page-queries.ts +++ b/src/main/runtime/browser/client-pages/browser-client-page-queries.ts @@ -158,11 +158,12 @@ export async function yieldClientPageSelection( worktree: string | undefined ): Promise { const pages = host.getRuntimeBrowserPageRegistry() + if (worktree) { + pages.yieldSelection((await host.resolveBrowserWorkspace(worktree)).id) + return + } pages.deactivateGlobal() - const workspaceIds = worktree - ? [(await host.resolveBrowserWorkspace(worktree)).id] - : [...new Set(pages.listPages().map((page) => page.workspaceId))] - for (const workspaceId of workspaceIds) { + for (const workspaceId of new Set(pages.listPages().map((page) => page.workspaceId))) { pages.deactivateWorkspace(workspaceId) } } diff --git a/src/main/runtime/browser/client-pages/orca-runtime-browser.test.ts b/src/main/runtime/browser/client-pages/orca-runtime-browser.test.ts index 35465319fc9..57e0e6f0245 100644 --- a/src/main/runtime/browser/client-pages/orca-runtime-browser.test.ts +++ b/src/main/runtime/browser/client-pages/orca-runtime-browser.test.ts @@ -191,4 +191,42 @@ describe('RuntimeBrowserCommands browser screencast', () => { expect(waitForTabRegistrationMock).toHaveBeenCalledWith('page-new') expect(bridge.setActiveTab).toHaveBeenCalledWith(101, 'wt-1') }) + // Pageless agent commands prefer an active client page; an agent's new server tab must take that over. + it('moves the workspace selection off a client page when a server tab is created', async () => { + const { RuntimeBrowserCommands } = await import('../commands/browser-commands') + const pages = new RuntimeBrowserPageRegistry() + pages.publishClientPage({ + browserPageId: 'user-page', + workspaceId: 'wt-1', + browserProfileId: 'default', + executionHostKey: 'local', + placement: { + kind: 'client', + browserHostClientId: 'desktop-1', + browserHostGeneration: 1, + pageHostGeneration: 1 + }, + url: 'https://user.example/', + loading: false, + active: true + }) + const bridge = { + getRegisteredTabs: vi.fn(() => new Map([['agent-page', 102]])), + setActiveTab: vi.fn() + } + const offscreen = { createTab: vi.fn(async () => ({ browserPageId: 'agent-page' })) } + const host = createHost({ + // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: create reaches only these two bridge methods. + getAgentBrowserBridge: () => bridge as unknown as AgentBrowserBridge, + getRuntimeBrowserPageRegistry: () => pages, + // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: create reaches only createTab. + getOffscreenBrowserBackend: () => offscreen as never + }) + const commands = new RuntimeBrowserCommands(host) + + await commands.browserTabCreate({ worktree: 'id:wt-1', url: 'https://agent.example/' }) + + expect(bridge.setActiveTab).toHaveBeenCalledWith(102, 'wt-1') + expect(pages.listPages('wt-1').find((page) => page.active)).toBeUndefined() + }) }) diff --git a/src/main/runtime/browser/client-pages/runtime-browser-page-registry.ts b/src/main/runtime/browser/client-pages/runtime-browser-page-registry.ts index 519ed9d37d9..a7fb5cb373d 100644 --- a/src/main/runtime/browser/client-pages/runtime-browser-page-registry.ts +++ b/src/main/runtime/browser/client-pages/runtime-browser-page-registry.ts @@ -199,6 +199,14 @@ export class RuntimeBrowserPageRegistry { this.globalActivePageId = null } + /** An engine tab took the selection; unscoped creates and switches move only the global pick. */ + yieldSelection(workspaceId: string | undefined): void { + this.deactivateGlobal() + if (workspaceId !== undefined) { + this.deactivateWorkspace(workspaceId) + } + } + deactivateWorkspace(workspaceId: string): void { for (const [browserPageId, page] of this.pages) { if (page.workspaceId === workspaceId && page.active) { diff --git a/src/main/runtime/browser/tabs/browser-tab-create-publication.ts b/src/main/runtime/browser/tabs/browser-tab-create-publication.ts index 08ba33d6c28..9d6401abb34 100644 --- a/src/main/runtime/browser/tabs/browser-tab-create-publication.ts +++ b/src/main/runtime/browser/tabs/browser-tab-create-publication.ts @@ -1,4 +1,5 @@ import type { AgentBrowserBridge } from '../../../browser/agent-browser/agent-browser-bridge' +import type { RuntimeBrowserPageRegistry } from '../client-pages/runtime-browser-page-registry' import { navigationTargetsHost, resolveRuntimeNavigationTarget, @@ -20,6 +21,7 @@ type BrowserTabCreatePublicationBridge = Pick< export type BrowserTabCreatePublicationHost = { getAgentBrowserBridge(): BrowserTabCreatePublicationBridge | null + getRuntimeBrowserPageRegistry(): Pick markHeadlessBrowserSessionTabActive?( worktreeId: string | undefined, browserPageId: string, @@ -194,6 +196,11 @@ export function publishCreatedBrowserSessionTab( ?.getRegisteredTabs(publication.worktreeId) .get(publication.browserPageId) if (bridge && webContentsId != null) { + // Pageless commands prefer an active client page, so the new engine tab must take over from it. + // Yield first so the bridge's own tabs-changed publication carries the cleared client row. + if (publication.focus.startsActive) { + host.getRuntimeBrowserPageRegistry().yieldSelection(publication.worktreeId) + } bridge.setActiveTab(webContentsId, publication.worktreeId) } } diff --git a/src/main/runtime/browser/tabs/browser-tab-queries.ts b/src/main/runtime/browser/tabs/browser-tab-queries.ts index 51f31bdbe76..5f6deea8b96 100644 --- a/src/main/runtime/browser/tabs/browser-tab-queries.ts +++ b/src/main/runtime/browser/tabs/browser-tab-queries.ts @@ -166,10 +166,7 @@ export class RuntimeBrowserTabQueries { ? (await this.host.resolveBrowserWorkspace(params.worktree)).id : undefined const result = await bridge.tabSwitch(undefined, worktreeId, selected.browserPageId) - this.host.getRuntimeBrowserPageRegistry().deactivateGlobal() - if (worktreeId) { - this.host.getRuntimeBrowserPageRegistry().deactivateWorkspace(worktreeId) - } + this.host.getRuntimeBrowserPageRegistry().yieldSelection(worktreeId) // Scope focus to the owning worktree; never switch the user's workspace. const focusWorktreeId = worktreeId ?? browserManager.getWorktreeIdForTab(result.browserPageId) ?? undefined