Add opt-in browser tools for desktop tabs
* 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
This commit is contained in:
@@ -4,11 +4,12 @@ import {
|
||||
handleBrowserWindowOpenRequest,
|
||||
isAllowedBrowserWebviewUrl,
|
||||
} from "./window-open.js";
|
||||
import { PaseoBrowserWebviewRegistry, type BrowserWorkspaceRegistration } from "./registry.js";
|
||||
|
||||
export { BROWSER_NEW_TAB_REQUEST_EVENT, handleBrowserWindowOpenRequest };
|
||||
export type { BrowserWorkspaceRegistration };
|
||||
|
||||
const browserIdsByWebContentsId = new Map<number, string>();
|
||||
let workspaceActiveBrowserId: string | null = null;
|
||||
const browserRegistry = new PaseoBrowserWebviewRegistry();
|
||||
|
||||
function getBrowserIdFromWebviewPartition(partition: string | undefined): string | null {
|
||||
const prefix = "persist:paseo-browser-";
|
||||
@@ -30,16 +31,15 @@ export function readBrowserIdFromWebviewAttach(input: {
|
||||
}
|
||||
|
||||
export function listRegisteredPaseoBrowserIds(): string[] {
|
||||
return Array.from(new Set(browserIdsByWebContentsId.values())).sort();
|
||||
return browserRegistry
|
||||
.listBrowserIds()
|
||||
.filter((browserId) => getPaseoBrowserWebContents(browserId));
|
||||
}
|
||||
|
||||
export function registerPaseoBrowserWebContents(contents: WebContents, browserId: string): void {
|
||||
browserIdsByWebContentsId.set(contents.id, browserId);
|
||||
browserRegistry.registerWebContents({ webContentsId: contents.id, browserId });
|
||||
contents.once("destroyed", () => {
|
||||
browserIdsByWebContentsId.delete(contents.id);
|
||||
if (workspaceActiveBrowserId === browserId) {
|
||||
workspaceActiveBrowserId = null;
|
||||
}
|
||||
browserRegistry.unregisterWebContents(contents.id);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -47,29 +47,71 @@ export function getPaseoBrowserIdForWebContents(contents: WebContents | null): s
|
||||
if (!contents || contents.isDestroyed()) {
|
||||
return null;
|
||||
}
|
||||
return browserIdsByWebContentsId.get(contents.id) ?? null;
|
||||
return browserRegistry.getBrowserIdForWebContents(contents.id);
|
||||
}
|
||||
|
||||
export function setWorkspaceActivePaseoBrowserId(browserId: string | null): void {
|
||||
workspaceActiveBrowserId = browserId;
|
||||
export function registerPaseoBrowserWorkspace(input: BrowserWorkspaceRegistration): void {
|
||||
browserRegistry.registerWorkspace(input);
|
||||
}
|
||||
|
||||
export function getPaseoBrowserWorkspaceId(browserId: string): string | null {
|
||||
return browserRegistry.getWorkspaceId(browserId);
|
||||
}
|
||||
|
||||
export function listRegisteredPaseoBrowserIdsForWorkspace(workspaceId: string): string[] {
|
||||
return browserRegistry
|
||||
.listBrowserIdsForWorkspace(workspaceId)
|
||||
.filter((browserId) => getPaseoBrowserWebContents(browserId));
|
||||
}
|
||||
|
||||
export function setWorkspaceActivePaseoBrowserId(input: {
|
||||
workspaceId: string;
|
||||
browserId: string | null;
|
||||
}): void {
|
||||
browserRegistry.setWorkspaceActiveBrowser(input);
|
||||
}
|
||||
|
||||
export function getWorkspaceActivePaseoBrowserId(workspaceId: string): string | null {
|
||||
return browserRegistry.getWorkspaceActiveBrowserId(workspaceId);
|
||||
}
|
||||
|
||||
export function setAgentActivePaseoBrowserId(input: {
|
||||
agentId: string;
|
||||
browserId: string | null;
|
||||
}): void {
|
||||
browserRegistry.setAgentActiveBrowser(input);
|
||||
}
|
||||
|
||||
export function getAgentActivePaseoBrowserId(agentId: string): string | null {
|
||||
return browserRegistry.getAgentActiveBrowserId(agentId);
|
||||
}
|
||||
|
||||
export function getPaseoBrowserWebContents(browserId: string): WebContents | null {
|
||||
for (const [contentsId, registeredBrowserId] of browserIdsByWebContentsId) {
|
||||
if (registeredBrowserId !== browserId) continue;
|
||||
const contents = allWebContents.fromId(contentsId);
|
||||
if (contents && !contents.isDestroyed()) {
|
||||
return contents;
|
||||
}
|
||||
const contentsId = browserRegistry.getWebContentsIdForBrowser(browserId);
|
||||
if (contentsId === null) {
|
||||
return null;
|
||||
}
|
||||
const contents = allWebContents.fromId(contentsId);
|
||||
if (contents && !contents.isDestroyed()) {
|
||||
return contents;
|
||||
}
|
||||
browserRegistry.unregisterWebContents(contentsId);
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getWorkspaceActivePaseoBrowserWebContents(): WebContents | null {
|
||||
if (!workspaceActiveBrowserId) {
|
||||
return null;
|
||||
}
|
||||
return getPaseoBrowserWebContents(workspaceActiveBrowserId);
|
||||
export function getWorkspaceActivePaseoBrowserWebContents(workspaceId: string): WebContents | null {
|
||||
const activeBrowserId = getWorkspaceActivePaseoBrowserId(workspaceId);
|
||||
return activeBrowserId ? getPaseoBrowserWebContents(activeBrowserId) : null;
|
||||
}
|
||||
|
||||
export function getAgentActivePaseoBrowserWebContents(agentId: string): WebContents | null {
|
||||
const activeBrowserId = getAgentActivePaseoBrowserId(agentId);
|
||||
return activeBrowserId ? getPaseoBrowserWebContents(activeBrowserId) : null;
|
||||
}
|
||||
|
||||
export function getMostRecentWorkspaceActivePaseoBrowserWebContents(): WebContents | null {
|
||||
const browserId = browserRegistry.getMostRecentWorkspaceActiveBrowserId();
|
||||
return browserId ? getPaseoBrowserWebContents(browserId) : null;
|
||||
}
|
||||
|
||||
function preventUnsafeBrowserWebviewNavigation(
|
||||
|
||||
Reference in New Issue
Block a user