feat: add persistent workbar browser tabs and scoped MCP routing

This commit is contained in:
mesalogo
2026-09-10 01:26:56 +08:00
parent 428b4c0d08
commit 80b0b18712
54 changed files with 5446 additions and 1468 deletions
+84 -22
View File
@@ -7,7 +7,23 @@ import {
type AgentRuntimeDetection,
type AgentRuntimeStatus,
type AppInfo,
type BrowserBackRequest,
type BrowserClickRequest,
type BrowserCloseTabRequest,
type BrowserCreateTabRequest,
type BrowserLiveState,
type BrowserListTabsRequest,
type BrowserNavigateRequest,
type BrowserReloadRequest,
type BrowserScreenshotRequest,
type BrowserScreenshotResult,
type BrowserSelectRequest,
type BrowserSetViewportRequest,
type BrowserSnapshotRequest,
type BrowserSnapshotResult,
type BrowserStopLoadingRequest,
type BrowserTabSummary,
type BrowserTypeRequest,
type ContextAttachment,
type ContextFileSelectionProgress,
type DesktopApi,
@@ -187,6 +203,7 @@ import type {
TerminalWriteRequest
} from '../shared/terminal-contracts'
const desktopApi: DesktopApi = {
app: {
getInfo: () => ipcRenderer.invoke(ipcChannels.appInfo) as Promise<AppInfo>,
@@ -307,32 +324,77 @@ const desktopApi: DesktopApi = {
}
},
browser: {
navigate: async (conversationId: string, url: string) => {
await ipcRenderer.invoke(ipcChannels.browserNavigate, {
conversationId,
url
})
createTab: (request: BrowserCreateTabRequest) =>
ipcRenderer.invoke(
ipcChannels.browserCreateTab,
request
) as Promise<BrowserTabSummary>,
listTabs: (request: BrowserListTabsRequest) =>
ipcRenderer.invoke(
ipcChannels.browserListTabs,
request
) as Promise<BrowserTabSummary[]>,
closeTab: async (request: BrowserCloseTabRequest) => {
await ipcRenderer.invoke(ipcChannels.browserCloseTab, request)
},
back: async (conversationId: string) => {
await ipcRenderer.invoke(ipcChannels.browserBack, {
conversationId
})
navigate: async (
requestOrConversation: BrowserNavigateRequest | string,
url?: string
) => {
await ipcRenderer.invoke(
ipcChannels.browserNavigate,
typeof requestOrConversation === 'string'
? { conversationId: requestOrConversation, url }
: requestOrConversation
)
},
reload: async (conversationId: string) => {
await ipcRenderer.invoke(ipcChannels.browserReload, {
conversationId
})
back: async (requestOrConversation: BrowserBackRequest | string) => {
await ipcRenderer.invoke(
ipcChannels.browserBack,
typeof requestOrConversation === 'string'
? { conversationId: requestOrConversation }
: requestOrConversation
)
},
stopLoading: async (conversationId: string) => {
await ipcRenderer.invoke(ipcChannels.browserStopLoading, {
conversationId
})
reload: async (requestOrConversation: BrowserReloadRequest | string) => {
await ipcRenderer.invoke(
ipcChannels.browserReload,
typeof requestOrConversation === 'string'
? { conversationId: requestOrConversation }
: requestOrConversation
)
},
setViewport: async (conversationId, bounds) => {
await ipcRenderer.invoke(ipcChannels.browserSetViewport, {
...(conversationId ? { conversationId } : {}),
...(bounds ? { bounds } : {})
})
stopLoading: async (
requestOrConversation: BrowserStopLoadingRequest | string
) => {
await ipcRenderer.invoke(
ipcChannels.browserStopLoading,
typeof requestOrConversation === 'string'
? { conversationId: requestOrConversation }
: requestOrConversation
)
},
snapshot: (request: BrowserSnapshotRequest) =>
ipcRenderer.invoke(
ipcChannels.browserSnapshot,
request
) as Promise<BrowserSnapshotResult>,
click: async (request: BrowserClickRequest) => {
await ipcRenderer.invoke(ipcChannels.browserClick, request)
},
type: async (request: BrowserTypeRequest) => {
await ipcRenderer.invoke(ipcChannels.browserType, request)
},
select: async (request: BrowserSelectRequest) => {
await ipcRenderer.invoke(ipcChannels.browserSelect, request)
},
screenshot: (request: BrowserScreenshotRequest) =>
ipcRenderer.invoke(
ipcChannels.browserScreenshot,
request
) as Promise<BrowserScreenshotResult>,
setViewport: async (request: BrowserSetViewportRequest) => {
await ipcRenderer.invoke(ipcChannels.browserSetViewport, request)
},
stop: async (conversationId: string) => {
await ipcRenderer.invoke(
+25 -1
View File
@@ -47,16 +47,40 @@ describe('sandboxed preload', () => {
/browser: \{(?<body>[\s\S]*?)\r?\n {2}\},\r?\n {2}terminal:/u
)?.groups?.body ?? ''
for (const method of [
'createTab:',
'listTabs:',
'closeTab:',
'navigate:',
'back:',
'reload:',
'stopLoading:',
'interact:',
'snapshot:',
'click:',
'type:',
'select:',
'screenshot:',
'setViewport:',
'stop:',
'onState:'
]) {
expect(browser).toContain(method)
}
for (const channel of [
'browserCreateTab',
'browserListTabs',
'browserCloseTab',
'browserSnapshot',
'browserClick',
'browserType',
'browserSelect',
'browserScreenshot',
'browserSetViewport'
]) {
expect(browser).toContain(`ipcChannels.${channel}`)
}
expect(browser).toContain('requestOrConversation: BrowserNavigateRequest')
expect(browser).toContain('setViewport: async (request: BrowserSetViewportRequest)')
expect(browser).not.toContain('leaseId')
expect(browser).not.toMatch(
/\b(?:webContents|debugger|session|partition|cookie)\b/iu
)