mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 10:27:33 +00:00
fix(browser): an agent's new server tab takes the selection from the user's This-device page (#41953)
Pageless agent commands prefer the workspace's active client page, and creating an offscreen or renderer tab never cleared it, so the next command after `orca tab create` acted on the user's page. Yield client selection wherever a create moves the bridge's active tab, as orcad already does.
This commit is contained in:
@@ -158,11 +158,12 @@ export async function yieldClientPageSelection(
|
||||
worktree: string | undefined
|
||||
): Promise<void> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<RuntimeBrowserPageRegistry, 'yieldSelection'>
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user