Skip to content

Commit e9441ed

Browse files
committed
feat(desktop): browser pages as resource tabs, agent works in the background
Each live desktop browser page is now its own resource tab in the Chat panel, derived from the desktop app's tab list instead of a persisted singleton "Browser" resource with a second tab strip inside it. The inner browser tab strip, tab pinning, duplicate, reorder, the native tab context menu, and the auto-replacement of a closed last tab are removed. The agent drives its own tab (the desktop's automation cursor) without moving the user's visible page: the renderer no longer force-activates the browser on every tool call nor auto-switches the panel to the agent's tab. The agent's tab is announced as resource activity, so the existing view policy shows it or badges it depending on whether the user has taken over selection. Mentions and attachments point at exact tabs; the chat row never stores browser resources, and legacy rows are dropped on read.
1 parent 8d9875c commit e9441ed

49 files changed

Lines changed: 833 additions & 1560 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/desktop/src/main/browser-agent/driver.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1395,7 +1395,7 @@ describe('executeTool', () => {
13951395
it('keeps activation lazy, then restores and disposes through the driver API', async () => {
13961396
const snapshot: BrowserSessionSnapshot = {
13971397
v: 1,
1398-
tabs: [{ url: 'https://restored.example/', pinned: false }],
1398+
tabs: [{ url: 'https://restored.example/' }],
13991399
activeIndex: 0,
14001400
downloads: [],
14011401
}

apps/desktop/src/main/browser-agent/driver.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@ export async function clearBrowserProfile(
905905
retireAllDriverScopeStates()
906906
const settingsCleared = knownSessions?.clear() !== false
907907
const outcomes = await Promise.allSettled([session.clearProfileStorage(), clearCredentials()])
908-
// Last, covering the pinned-tab list `clearProfileStorage` just emptied.
908+
// Last, covering the saved tab list `clearProfileStorage` just emptied.
909909
// Settings writes coalesce, and an erasure that is still sitting in that
910910
// window when the process dies leaves the previous account's data on disk
911911
// after sign-out already told the user it was gone.
@@ -2368,8 +2368,7 @@ async function executeToolInner(
23682368
}
23692369
}
23702370
assertCurrentExecution()
2371-
// The agent chose to open this page to work in, so the panel follows it.
2372-
const tab = session.addAutomationTab({ reveal: true })
2371+
const tab = session.addAutomationTab()
23732372
const contents = tab.view.webContents
23742373
if (url) {
23752374
assertCurrentExecution()
@@ -4782,16 +4781,6 @@ export async function handlePanelAction(
47824781
}
47834782
return
47844783
}
4785-
if (action.action === 'new-tab') {
4786-
session.addTab()
4787-
return
4788-
}
4789-
if (action.action === 'duplicate-tab') {
4790-
if (typeof action.tabId === 'string') {
4791-
session.duplicateTab(action.tabId)
4792-
}
4793-
return
4794-
}
47954784
if (action.action === 'switch-tab') {
47964785
if (typeof action.tabId === 'string') {
47974786
session.switchTab(action.tabId)

apps/desktop/src/main/browser-agent/panel.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function freshPanel(): PanelModule {
2020
getMainWindow: () => null,
2121
activeTab: () => null,
2222
backgroundColor: () => '#ffffff',
23-
ensureInitialTab: () => {},
23+
restoreActiveScope: () => {},
2424
onViewDetached: () => {},
2525
})
2626
panelModule.activatePanelScope('chat-test')
@@ -33,12 +33,12 @@ const PANEL_RECT = { x: 400, y: 64, width: 600, height: 800 }
3333
function showPanel(panel: PanelModule) {
3434
const win = new BrowserWindow()
3535
const view = new WebContentsView()
36-
const active = { id: 'tab-1', scopeId: 'chat-test', view, pinned: false }
36+
const active = { id: 'tab-1', scopeId: 'chat-test', view }
3737
panel.initPanel({
3838
getMainWindow: () => win,
3939
activeTab: () => active,
4040
backgroundColor: () => '#0c0c0c',
41-
ensureInitialTab: () => {},
41+
restoreActiveScope: () => {},
4242
onViewDetached: () => {},
4343
})
4444
panel.activatePanelScope('chat-test')
@@ -56,13 +56,13 @@ describe('panel chat scope', () => {
5656
it('returns keyboard focus to the renderer when attaching a view steals it mid-typing', () => {
5757
const win = new BrowserWindow()
5858
const view = new WebContentsView()
59-
const active = { id: 'tab-1', scopeId: 'chat-test', view, pinned: false }
59+
const active = { id: 'tab-1', scopeId: 'chat-test', view }
6060
vi.mocked(win.webContents.isFocused).mockReturnValue(true)
6161
panel.initPanel({
6262
getMainWindow: () => win,
6363
activeTab: () => active,
6464
backgroundColor: () => '#0c0c0c',
65-
ensureInitialTab: () => {},
65+
restoreActiveScope: () => {},
6666
onViewDetached: () => {},
6767
})
6868
panel.activatePanelScope('chat-test')
@@ -327,12 +327,12 @@ describe('panel chat scope', () => {
327327
it('applies a forced hide before the panel reports its first bounds', () => {
328328
const win = new BrowserWindow()
329329
const view = new WebContentsView()
330-
const active = { id: 'tab-1', scopeId: 'chat-test', view, pinned: false }
330+
const active = { id: 'tab-1', scopeId: 'chat-test', view }
331331
panel.initPanel({
332332
getMainWindow: () => win,
333333
activeTab: () => active,
334334
backgroundColor: () => '#0c0c0c',
335-
ensureInitialTab: () => {},
335+
restoreActiveScope: () => {},
336336
onViewDetached: () => {},
337337
})
338338
panel.activatePanelScope('chat-test')

apps/desktop/src/main/browser-agent/panel.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,8 @@ export interface PanelHost {
4141
activeTab: () => AgentTab | null
4242
/** Native backdrop used by a blank tab before its first page paint. */
4343
backgroundColor: () => string
44-
/**
45-
* Materializes the initial tab when the panel first becomes visible: a
46-
* visible browser resource always represents one open browser window, and
47-
* the tab strip, omnibox, and native session must not disagree about that.
48-
*/
49-
ensureInitialTab: () => void
44+
/** Hydrates the active scope's saved pages when the panel first becomes visible. */
45+
restoreActiveScope: () => void
5046
/** Lets the session drop focus tracking for a view that is no longer attached. */
5147
onViewDetached: (view: WebContentsView | null) => void
5248
}
@@ -55,7 +51,7 @@ let host: PanelHost = {
5551
getMainWindow: () => null,
5652
activeTab: () => null,
5753
backgroundColor: () => '#ffffff',
58-
ensureInitialTab: () => {},
54+
restoreActiveScope: () => {},
5955
onViewDetached: () => {},
6056
}
6157

@@ -834,7 +830,7 @@ export function setPanelBounds(
834830
panelBounds = bounds
835831
panelAnchor = bounds === null ? null : (anchor ?? null)
836832
if (bounds !== null) {
837-
host.ensureInitialTab()
833+
host.restoreActiveScope()
838834
} else {
839835
resetOcclusion()
840836
}

0 commit comments

Comments
 (0)