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:
@@ -53,7 +53,9 @@ import {
|
||||
listRegisteredPaseoBrowserIds,
|
||||
readBrowserIdFromWebviewAttach,
|
||||
registerBrowserWebviewNavigationGuards,
|
||||
registerPaseoBrowserWorkspace,
|
||||
registerPaseoBrowserWebContents,
|
||||
setAgentActivePaseoBrowserId,
|
||||
setWorkspaceActivePaseoBrowserId,
|
||||
} from "./features/browser-webviews/index.js";
|
||||
import { parseOpenProjectPathFromArgv } from "./open-project-routing.js";
|
||||
@@ -70,6 +72,7 @@ import {
|
||||
} from "./daemon/quit-lifecycle.js";
|
||||
import { runDesktopStartup } from "./desktop-startup.js";
|
||||
import { autoUpdateInstalledSkills } from "./integrations/skills/index.js";
|
||||
import { registerBrowserAutomationIpc } from "./features/browser-automation/ipc.js";
|
||||
|
||||
const DEV_SERVER_URL = process.env.EXPO_DEV_URL ?? "http://localhost:8081";
|
||||
const APP_SCHEME = "paseo";
|
||||
@@ -109,6 +112,50 @@ const DESKTOP_SMOKE_ENV = "PASEO_DESKTOP_SMOKE";
|
||||
const DESKTOP_SMOKE_STOP_REQUEST = "paseo-smoke-stop";
|
||||
app.setName(APP_NAME);
|
||||
|
||||
function readBrowserWorkspaceInput(
|
||||
input: unknown,
|
||||
): { browserId: string; workspaceId: string } | null {
|
||||
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
||||
return null;
|
||||
}
|
||||
const record = input as Record<string, unknown>;
|
||||
if (typeof record.browserId !== "string" || record.browserId.trim().length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (typeof record.workspaceId !== "string" || record.workspaceId.trim().length === 0) {
|
||||
return null;
|
||||
}
|
||||
return { browserId: record.browserId.trim(), workspaceId: record.workspaceId.trim() };
|
||||
}
|
||||
|
||||
function readActiveBrowserInput(
|
||||
input: unknown,
|
||||
): { workspaceId: string; browserId: string | null } | null {
|
||||
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
||||
return null;
|
||||
}
|
||||
const record = input as Record<string, unknown>;
|
||||
if (typeof record.workspaceId !== "string" || record.workspaceId.trim().length === 0) {
|
||||
return null;
|
||||
}
|
||||
const browserId = typeof record.browserId === "string" ? record.browserId.trim() : null;
|
||||
return { workspaceId: record.workspaceId.trim(), browserId: browserId || null };
|
||||
}
|
||||
|
||||
function readAgentActiveBrowserInput(
|
||||
input: unknown,
|
||||
): { agentId: string; browserId: string | null } | null {
|
||||
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
||||
return null;
|
||||
}
|
||||
const record = input as Record<string, unknown>;
|
||||
if (typeof record.agentId !== "string" || record.agentId.trim().length === 0) {
|
||||
return null;
|
||||
}
|
||||
const browserId = typeof record.browserId === "string" ? record.browserId.trim() : null;
|
||||
return { agentId: record.agentId.trim(), browserId: browserId || null };
|
||||
}
|
||||
|
||||
const pendingBrowserWebviewIds: string[] = [];
|
||||
|
||||
function isBrowserRefreshInput(input: Electron.Input): boolean {
|
||||
@@ -254,8 +301,25 @@ ipcMain.handle("paseo:get-pending-open-project", (event) => {
|
||||
return result;
|
||||
});
|
||||
|
||||
ipcMain.handle("paseo:browser:set-workspace-active-browser", (_event, browserId: unknown) => {
|
||||
setWorkspaceActivePaseoBrowserId(typeof browserId === "string" ? browserId : null);
|
||||
ipcMain.handle("paseo:browser:register-workspace-browser", (_event, rawInput: unknown) => {
|
||||
const input = readBrowserWorkspaceInput(rawInput);
|
||||
if (input) {
|
||||
registerPaseoBrowserWorkspace(input);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("paseo:browser:set-workspace-active-browser", (_event, rawInput: unknown) => {
|
||||
const input = readActiveBrowserInput(rawInput);
|
||||
if (input) {
|
||||
setWorkspaceActivePaseoBrowserId(input);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("paseo:browser:set-agent-active-browser", (_event, rawInput: unknown) => {
|
||||
const input = readAgentActiveBrowserInput(rawInput);
|
||||
if (input) {
|
||||
setAgentActivePaseoBrowserId(input);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("paseo:browser:open-devtools", (_event, browserId: unknown) => {
|
||||
@@ -696,6 +760,7 @@ async function bootstrap(): Promise<void> {
|
||||
registerNotificationHandlers();
|
||||
registerOpenerHandlers();
|
||||
registerEditorTargetHandlers();
|
||||
registerBrowserAutomationIpc();
|
||||
|
||||
// In-app "Open in new window": opens a window that lands on the given project
|
||||
// via the same open-project flow as a CLI launch (no move, no ownership).
|
||||
|
||||
Reference in New Issue
Block a user