* Add opt-in browser tools for desktop tabs Adds the daemon opt-in, desktop tab routing, MCP tools, and real browser automation surfaces for Paseo desktop browser tabs. * Fix browser tools CI expectations * Address browser tools review findings * Restrict browser file automation paths * Fix browser upload test on Windows * Harden browser navigation inputs * Make browser tools create usable tabs * Update browser MCP empty-state test * Fail browser tab creation when registration times out * Fix browser screenshots for agents * Hide disabled browser tools from agents * Address browser tools architecture review * Replace browser tools review tests * Wrap browser tab registration errors * Mock Expo Router in app unit tests * Handle invalid browser automation requests * Return browser failure on desktop disconnect * Update browser disconnect websocket test * Relax browser timeout polling test * Handle invalid browser responses * Return browser failure when send fails * Remove local diagnostics and fixture paths * Fix dev service home fallback * Use worktree home for dev services * Use managed daemon in desktop dev * fix(browser): keep agent tabs addressable Track agent-active browser targets separately from human-focused tabs and keep resident webviews alive for automation. Browser tool visibility now comes from registration while the broker reports disabled execution. * refactor(browser): register tools through catalog Move browser tool registration onto the shared Paseo tool catalog so the MCP server remains only the transport adapter. * fix(settings): translate browser tools host error
91 lines
3.9 KiB
TypeScript
91 lines
3.9 KiB
TypeScript
import { contextBridge, ipcRenderer, webUtils } from "electron";
|
|
|
|
type EventHandler = (payload: unknown) => void;
|
|
|
|
contextBridge.exposeInMainWorld("paseoDesktop", {
|
|
platform: process.platform,
|
|
invoke: (command: string, args?: Record<string, unknown>) =>
|
|
ipcRenderer.invoke("paseo:invoke", command, args),
|
|
getPendingOpenProject: () =>
|
|
ipcRenderer.invoke("paseo:get-pending-open-project") as Promise<string | null>,
|
|
events: {
|
|
on: (event: string, handler: EventHandler): Promise<() => void> => {
|
|
const listener = (_ipcEvent: Electron.IpcRendererEvent, payload: unknown) => {
|
|
handler(payload);
|
|
};
|
|
ipcRenderer.on(`paseo:event:${event}`, listener);
|
|
return Promise.resolve(() => {
|
|
ipcRenderer.removeListener(`paseo:event:${event}`, listener);
|
|
});
|
|
},
|
|
},
|
|
window: {
|
|
openNew: (options?: { pendingOpenProjectPath?: string | null }) =>
|
|
ipcRenderer.invoke("paseo:window:openNew", options),
|
|
getCurrentWindow: () => ({
|
|
toggleMaximize: () => ipcRenderer.invoke("paseo:window:toggleMaximize"),
|
|
isFullscreen: () => ipcRenderer.invoke("paseo:window:isFullscreen"),
|
|
updateWindowControls: (update: {
|
|
height?: number;
|
|
backgroundColor?: string;
|
|
foregroundColor?: string;
|
|
}) => ipcRenderer.invoke("paseo:window:updateWindowControls", update),
|
|
onResized: (handler: EventHandler): (() => void) => {
|
|
const listener = (_ipcEvent: Electron.IpcRendererEvent, payload: unknown) => {
|
|
handler(payload);
|
|
};
|
|
ipcRenderer.on("paseo:window:resized", listener);
|
|
return () => {
|
|
ipcRenderer.removeListener("paseo:window:resized", listener);
|
|
};
|
|
},
|
|
setBadgeCount: (count?: number) => ipcRenderer.invoke("paseo:window:setBadgeCount", count),
|
|
}),
|
|
},
|
|
dialog: {
|
|
ask: (message: string, options?: Record<string, unknown>) =>
|
|
ipcRenderer.invoke("paseo:dialog:ask", message, options),
|
|
askWithCheckbox: (message: string, options: Record<string, unknown>) =>
|
|
ipcRenderer.invoke("paseo:dialog:askWithCheckbox", message, options),
|
|
open: (options?: Record<string, unknown>) => ipcRenderer.invoke("paseo:dialog:open", options),
|
|
},
|
|
notification: {
|
|
isSupported: () => ipcRenderer.invoke("paseo:notification:isSupported"),
|
|
sendNotification: (payload: { title: string; body?: string; data?: Record<string, unknown> }) =>
|
|
ipcRenderer.invoke("paseo:notification:send", payload),
|
|
},
|
|
opener: {
|
|
openUrl: (url: string) => ipcRenderer.invoke("paseo:opener:openUrl", url),
|
|
},
|
|
editor: {
|
|
listTargets: () => ipcRenderer.invoke("paseo:editor:listTargets"),
|
|
openTarget: (input: {
|
|
editorId: string;
|
|
path: string;
|
|
cwd?: string;
|
|
mode?: "open" | "reveal";
|
|
}) => ipcRenderer.invoke("paseo:editor:openTarget", input),
|
|
},
|
|
webUtils: {
|
|
getPathForFile: (file: File) => webUtils.getPathForFile(file),
|
|
},
|
|
menu: {
|
|
showContextMenu: (input?: Record<string, unknown>) =>
|
|
ipcRenderer.invoke("paseo:menu:showContextMenu", input),
|
|
},
|
|
browser: {
|
|
registerWorkspaceBrowser: (input: { browserId: string; workspaceId: string }) =>
|
|
ipcRenderer.invoke("paseo:browser:register-workspace-browser", input),
|
|
setWorkspaceActiveBrowser: (input: { workspaceId: string; browserId: string | null }) =>
|
|
ipcRenderer.invoke("paseo:browser:set-workspace-active-browser", input),
|
|
setAgentActiveBrowser: (input: { agentId: string; browserId: string | null }) =>
|
|
ipcRenderer.invoke("paseo:browser:set-agent-active-browser", input),
|
|
openDevTools: (browserId: string) =>
|
|
ipcRenderer.invoke("paseo:browser:open-devtools", browserId),
|
|
clearPartition: (browserId: string) =>
|
|
ipcRenderer.invoke("paseo:browser:clear-partition", browserId),
|
|
executeAutomationCommand: (request: Record<string, unknown>) =>
|
|
ipcRenderer.invoke("paseo:browser:execute-automation-command", request),
|
|
},
|
|
});
|