refactor(browser-tools): require explicit tab targets
Remove hidden active-browser targeting so automation requests carry workspace in the envelope and browserId in tab command args.
This commit is contained in:
@@ -138,7 +138,7 @@ Electron wrapper for macOS, Linux, and Windows.
|
|||||||
|
|
||||||
> **Window-state v1 limitation:** only the _first_ window of a session restores and persists saved geometry (size/position/maximized). Windows opened via ⌘⇧N / second-instance / "Open in new window" open at the default size, OS-cascaded, and do not persist — this avoids every window stacking on the same restored bounds and fighting over the single window-state store. Lifting this needs per-window state keys.
|
> **Window-state v1 limitation:** only the _first_ window of a session restores and persists saved geometry (size/position/maximized). Windows opened via ⌘⇧N / second-instance / "Open in new window" open at the default size, OS-cascaded, and do not persist — this avoids every window stacking on the same restored bounds and fighting over the single window-state store. Lifting this needs per-window state keys.
|
||||||
>
|
>
|
||||||
> **In-app browser panes are not yet per-window.** Browser webviews are tracked by one process-global registry that keeps a single current `WebContents` per browser id. Human focus and agent automation targets are intentionally separate: the workspace-active browser follows the user's focused tab, while the agent-active browser is the default target for browser MCP commands. The webview registration queue (`pendingBrowserWebviewIds` in `main.ts`) is still process-global. With browser panes open in two windows, a menu Reload can target the other window's webview, and near-simultaneous webview attach across windows can register under the wrong browser id. Multi-window v1 ships windows; making the browser-webview subsystem window-scoped is a follow-up.
|
> **In-app browser panes are not yet per-window.** Browser webviews are tracked by one process-global registry that keeps a single current `WebContents` per browser id. Human focus still records the workspace-active browser for UI state and `list_tabs` reporting, but agent automation targets only explicit browser ids returned by `browser_new_tab` or `browser_list_tabs`. The webview registration queue (`pendingBrowserWebviewIds` in `main.ts`) is still process-global. With browser panes open in two windows, a menu Reload can target the other window's webview, and near-simultaneous webview attach across windows can register under the wrong browser id. Multi-window v1 ships windows; making the browser-webview subsystem window-scoped is a follow-up.
|
||||||
|
|
||||||
### `packages/website` — Marketing site
|
### `packages/website` — Marketing site
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ function browserNewTabRequest(): BrowserAutomationExecuteRequest {
|
|||||||
workspaceId: "wks_workspace_a",
|
workspaceId: "wks_workspace_a",
|
||||||
command: {
|
command: {
|
||||||
command: "new_tab",
|
command: "new_tab",
|
||||||
args: { workspaceId: "wks_workspace_a", url: "https://example.com" },
|
args: { url: "https://example.com" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,6 @@ describe("mountBrowserAutomationHandler", () => {
|
|||||||
test("creates a focused workspace browser tab for browser_new_tab", async () => {
|
test("creates a focused workspace browser tab for browser_new_tab", async () => {
|
||||||
const client = new FakeDaemonClient();
|
const client = new FakeDaemonClient();
|
||||||
const setWorkspaceActiveBrowser = vi.fn(async () => undefined);
|
const setWorkspaceActiveBrowser = vi.fn(async () => undefined);
|
||||||
const setAgentActiveBrowser = vi.fn(async () => undefined);
|
|
||||||
const registerWorkspaceBrowser = vi.fn(async () => undefined);
|
const registerWorkspaceBrowser = vi.fn(async () => undefined);
|
||||||
const ensureResidentBrowserWebview = vi.fn();
|
const ensureResidentBrowserWebview = vi.fn();
|
||||||
const executeAutomationCommand = vi.fn(async () => currentListTabsPayload());
|
const executeAutomationCommand = vi.fn(async () => currentListTabsPayload());
|
||||||
@@ -150,7 +149,6 @@ describe("mountBrowserAutomationHandler", () => {
|
|||||||
executeAutomationCommand,
|
executeAutomationCommand,
|
||||||
registerWorkspaceBrowser,
|
registerWorkspaceBrowser,
|
||||||
setWorkspaceActiveBrowser,
|
setWorkspaceActiveBrowser,
|
||||||
setAgentActiveBrowser,
|
|
||||||
},
|
},
|
||||||
}) satisfies DesktopHostBridge,
|
}) satisfies DesktopHostBridge,
|
||||||
ensureResidentBrowserWebview,
|
ensureResidentBrowserWebview,
|
||||||
@@ -180,10 +178,6 @@ describe("mountBrowserAutomationHandler", () => {
|
|||||||
browserId,
|
browserId,
|
||||||
workspaceId: "wks_workspace_a",
|
workspaceId: "wks_workspace_a",
|
||||||
});
|
});
|
||||||
expect(setAgentActiveBrowser).toHaveBeenCalledWith({
|
|
||||||
agentId: "agent-1",
|
|
||||||
browserId,
|
|
||||||
});
|
|
||||||
expect(setWorkspaceActiveBrowser).toHaveBeenCalledWith({
|
expect(setWorkspaceActiveBrowser).toHaveBeenCalledWith({
|
||||||
browserId,
|
browserId,
|
||||||
workspaceId: "wks_workspace_a",
|
workspaceId: "wks_workspace_a",
|
||||||
|
|||||||
@@ -111,8 +111,6 @@ async function handleBrowserAutomationRequest(params: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await rememberAgentBrowserTarget({ request, browserHost });
|
|
||||||
|
|
||||||
if (!executeAutomationCommand) {
|
if (!executeAutomationCommand) {
|
||||||
client.sendBrowserAutomationExecuteResponse({
|
client.sendBrowserAutomationExecuteResponse({
|
||||||
type: "browser.automation.execute.response",
|
type: "browser.automation.execute.response",
|
||||||
@@ -159,11 +157,11 @@ async function openBrowserTabForRequest(params: {
|
|||||||
BrowserAutomationExecuteRequest["command"],
|
BrowserAutomationExecuteRequest["command"],
|
||||||
{ command: "new_tab" }
|
{ command: "new_tab" }
|
||||||
>;
|
>;
|
||||||
const workspaceId = request.workspaceId ?? command.args.workspaceId;
|
const workspaceId = request.workspaceId;
|
||||||
if (!serverId || !workspaceId) {
|
if (!serverId || !workspaceId) {
|
||||||
return browserAutomationFailure({
|
return browserAutomationFailure({
|
||||||
requestId: request.requestId,
|
requestId: request.requestId,
|
||||||
code: "browser_no_tab",
|
code: "browser_unsupported",
|
||||||
message: "Cannot create a browser tab without a workspace context.",
|
message: "Cannot create a browser tab without a workspace context.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -174,7 +172,7 @@ async function openBrowserTabForRequest(params: {
|
|||||||
if (!workspaceKey) {
|
if (!workspaceKey) {
|
||||||
return browserAutomationFailure({
|
return browserAutomationFailure({
|
||||||
requestId: request.requestId,
|
requestId: request.requestId,
|
||||||
code: "browser_no_tab",
|
code: "browser_unsupported",
|
||||||
message: "Cannot create a browser tab without a workspace context.",
|
message: "Cannot create a browser tab without a workspace context.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -185,9 +183,6 @@ async function openBrowserTabForRequest(params: {
|
|||||||
|
|
||||||
await browserHost?.registerWorkspaceBrowser?.({ browserId, workspaceId });
|
await browserHost?.registerWorkspaceBrowser?.({ browserId, workspaceId });
|
||||||
await browserHost?.setWorkspaceActiveBrowser?.({ browserId, workspaceId });
|
await browserHost?.setWorkspaceActiveBrowser?.({ browserId, workspaceId });
|
||||||
if (request.agentId) {
|
|
||||||
await browserHost?.setAgentActiveBrowser?.({ agentId: request.agentId, browserId });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (browserHost?.executeAutomationCommand) {
|
if (browserHost?.executeAutomationCommand) {
|
||||||
ensureResidentBrowserWebview({ browserId, url: normalizedUrl });
|
ensureResidentBrowserWebview({ browserId, url: normalizedUrl });
|
||||||
@@ -236,7 +231,7 @@ async function waitForBrowserRegistration(params: {
|
|||||||
agentId: params.request.agentId,
|
agentId: params.request.agentId,
|
||||||
cwd: params.request.cwd,
|
cwd: params.request.cwd,
|
||||||
workspaceId: params.workspaceId,
|
workspaceId: params.workspaceId,
|
||||||
command: { command: "list_tabs", args: { workspaceId: params.workspaceId } },
|
command: { command: "list_tabs", args: {} },
|
||||||
});
|
});
|
||||||
if (payload.ok && payload.result.command === "list_tabs") {
|
if (payload.ok && payload.result.command === "list_tabs") {
|
||||||
if (payload.result.tabs.some((tab) => tab.browserId === params.browserId)) {
|
if (payload.result.tabs.some((tab) => tab.browserId === params.browserId)) {
|
||||||
@@ -252,28 +247,6 @@ function delay(ms: number): Promise<void> {
|
|||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function rememberAgentBrowserTarget(input: {
|
|
||||||
request: BrowserAutomationExecuteRequest;
|
|
||||||
browserHost: DesktopHostBridge["browser"] | undefined;
|
|
||||||
}): Promise<void> {
|
|
||||||
if (!input.request.agentId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const browserId = readRequestBrowserId(input.request);
|
|
||||||
if (!browserId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await input.browserHost?.setAgentActiveBrowser?.({ agentId: input.request.agentId, browserId });
|
|
||||||
}
|
|
||||||
|
|
||||||
function readRequestBrowserId(request: BrowserAutomationExecuteRequest): string | null {
|
|
||||||
if (request.browserId) {
|
|
||||||
return request.browserId;
|
|
||||||
}
|
|
||||||
const args = request.command.args as { browserId?: unknown };
|
|
||||||
return typeof args.browserId === "string" && args.browserId.length > 0 ? args.browserId : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeBridgePayload(
|
function normalizeBridgePayload(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
payload: BrowserAutomationResponsePayload,
|
payload: BrowserAutomationResponsePayload,
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ export interface DesktopBrowserBridge {
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
browserId: string | null;
|
browserId: string | null;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
setAgentActiveBrowser?: (input: { agentId: string; browserId: string | null }) => Promise<void>;
|
|
||||||
openDevTools?: (browserId: string) => Promise<unknown>;
|
openDevTools?: (browserId: string) => Promise<unknown>;
|
||||||
clearPartition?: (browserId: string) => Promise<void>;
|
clearPartition?: (browserId: string) => Promise<void>;
|
||||||
executeAutomationCommand?: (
|
executeAutomationCommand?: (
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
|
import { BrowserAutomationBrowserIdSchema } from "@getpaseo/protocol/browser-automation/rpc-schemas";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { createJSONStorage, persist } from "zustand/middleware";
|
import { createJSONStorage, persist } from "zustand/middleware";
|
||||||
import {
|
import {
|
||||||
@@ -22,10 +23,14 @@ interface BrowserStoreState extends BrowserIndexState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createBrowserId(): string {
|
function createBrowserId(): string {
|
||||||
|
let browserId: string;
|
||||||
if (typeof globalThis.crypto?.randomUUID === "function") {
|
if (typeof globalThis.crypto?.randomUUID === "function") {
|
||||||
return globalThis.crypto.randomUUID();
|
browserId = globalThis.crypto.randomUUID();
|
||||||
|
} else {
|
||||||
|
const randomSuffix = Math.random().toString(16).slice(2) || "0";
|
||||||
|
browserId = `${Date.now()}-${randomSuffix}`;
|
||||||
}
|
}
|
||||||
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
return BrowserAutomationBrowserIdSchema.parse(browserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useBrowserStore = create<BrowserStoreState>()(
|
export const useBrowserStore = create<BrowserStoreState>()(
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ import {
|
|||||||
listRegisteredPaseoBrowserIds,
|
listRegisteredPaseoBrowserIds,
|
||||||
listRegisteredPaseoBrowserIdsForWorkspace,
|
listRegisteredPaseoBrowserIdsForWorkspace,
|
||||||
getPaseoBrowserWebContents,
|
getPaseoBrowserWebContents,
|
||||||
getWorkspaceActivePaseoBrowserWebContents,
|
|
||||||
getWorkspaceActivePaseoBrowserId,
|
getWorkspaceActivePaseoBrowserId,
|
||||||
getAgentActivePaseoBrowserId,
|
|
||||||
getPaseoBrowserWorkspaceId,
|
getPaseoBrowserWorkspaceId,
|
||||||
} from "../browser-webviews/index.js";
|
} from "../browser-webviews/index.js";
|
||||||
|
|
||||||
@@ -149,12 +147,7 @@ function createRegistry(): BrowserRegistry {
|
|||||||
return contents ? adaptWebContents(contents) : null;
|
return contents ? adaptWebContents(contents) : null;
|
||||||
},
|
},
|
||||||
getBrowserWorkspaceId: getPaseoBrowserWorkspaceId,
|
getBrowserWorkspaceId: getPaseoBrowserWorkspaceId,
|
||||||
getWorkspaceActiveTabContents(workspaceId: string): TabContents | null {
|
|
||||||
const contents = getWorkspaceActivePaseoBrowserWebContents(workspaceId);
|
|
||||||
return contents ? adaptWebContents(contents) : null;
|
|
||||||
},
|
|
||||||
getWorkspaceActiveBrowserId: getWorkspaceActivePaseoBrowserId,
|
getWorkspaceActiveBrowserId: getWorkspaceActivePaseoBrowserId,
|
||||||
getAgentActiveBrowserId: getAgentActivePaseoBrowserId,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -54,9 +54,7 @@ export interface BrowserRegistry {
|
|||||||
listRegisteredBrowserIdsForWorkspace(workspaceId: string): string[];
|
listRegisteredBrowserIdsForWorkspace(workspaceId: string): string[];
|
||||||
getTabContents(browserId: string): TabContents | null;
|
getTabContents(browserId: string): TabContents | null;
|
||||||
getBrowserWorkspaceId(browserId: string): string | null;
|
getBrowserWorkspaceId(browserId: string): string | null;
|
||||||
getWorkspaceActiveTabContents(workspaceId: string): TabContents | null;
|
|
||||||
getWorkspaceActiveBrowserId(workspaceId: string): string | null;
|
getWorkspaceActiveBrowserId(workspaceId: string): string | null;
|
||||||
getAgentActiveBrowserId(agentId: string): string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AutomationCommandPayload = BrowserAutomationExecuteResponse["payload"];
|
export type AutomationCommandPayload = BrowserAutomationExecuteResponse["payload"];
|
||||||
@@ -167,45 +165,18 @@ function tabInfoFromContents(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function executeAutomationCommand(
|
export function executeAutomationCommand(
|
||||||
rawRequest: BrowserAutomationExecuteRequest,
|
request: BrowserAutomationExecuteRequest,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
options?: { snapshotEngine?: BrowserSnapshotEngine },
|
options?: { snapshotEngine?: BrowserSnapshotEngine },
|
||||||
): AutomationCommandPayload | Promise<AutomationCommandPayload> {
|
): AutomationCommandPayload | Promise<AutomationCommandPayload> {
|
||||||
const request = resolveAgentBrowserTarget(rawRequest, registry);
|
|
||||||
const { requestId, command } = request;
|
const { requestId, command } = request;
|
||||||
const workspaceId = request.workspaceId ?? command.args.workspaceId;
|
const workspaceId = request.workspaceId;
|
||||||
const snapshotEngine = options?.snapshotEngine ?? defaultSnapshotEngine;
|
const snapshotEngine = options?.snapshotEngine ?? defaultSnapshotEngine;
|
||||||
const handler = commandHandlers[command.command];
|
const handler = commandHandlers[command.command];
|
||||||
|
|
||||||
return handler({ request, command, requestId, workspaceId, registry, snapshotEngine });
|
return handler({ request, command, requestId, workspaceId, registry, snapshotEngine });
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveAgentBrowserTarget(
|
|
||||||
request: BrowserAutomationExecuteRequest,
|
|
||||||
registry: BrowserRegistry,
|
|
||||||
): BrowserAutomationExecuteRequest {
|
|
||||||
if (request.browserId || readCommandBrowserId(request.command)) {
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
if (!request.agentId) {
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
const agentBrowserId = registry.getAgentActiveBrowserId(request.agentId);
|
|
||||||
if (!agentBrowserId) {
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...request, browserId: agentBrowserId };
|
|
||||||
}
|
|
||||||
|
|
||||||
function readCommandBrowserId(command: BrowserAutomationCommand): string | undefined {
|
|
||||||
const args = command.args as { browserId?: unknown };
|
|
||||||
return typeof args.browserId === "string" && args.browserId.length > 0
|
|
||||||
? args.browserId
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CommandHandlerContext {
|
interface CommandHandlerContext {
|
||||||
request: BrowserAutomationExecuteRequest;
|
request: BrowserAutomationExecuteRequest;
|
||||||
command: BrowserAutomationCommand;
|
command: BrowserAutomationCommand;
|
||||||
@@ -224,54 +195,49 @@ const commandHandlers: Record<BrowserAutomationCommand["command"], CommandHandle
|
|||||||
executeListTabs(requestId, workspaceId, registry),
|
executeListTabs(requestId, workspaceId, registry),
|
||||||
new_tab: ({ requestId }) =>
|
new_tab: ({ requestId }) =>
|
||||||
fail(requestId, "browser_unsupported", "browser_new_tab is handled by the app runtime."),
|
fail(requestId, "browser_unsupported", "browser_new_tab is handled by the app runtime."),
|
||||||
page_info: ({ request, command, requestId, workspaceId, registry }) => {
|
page_info: ({ command, requestId, workspaceId, registry }) => {
|
||||||
const pageInfoCommand = command as Extract<BrowserAutomationCommand, { command: "page_info" }>;
|
const pageInfoCommand = command as Extract<BrowserAutomationCommand, { command: "page_info" }>;
|
||||||
return executePageInfo(
|
return executePageInfo(requestId, workspaceId, pageInfoCommand.args.browserId, registry);
|
||||||
requestId,
|
|
||||||
workspaceId,
|
|
||||||
pageInfoCommand.args.browserId ?? request.browserId,
|
|
||||||
registry,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
snapshot: ({ request, command, requestId, workspaceId, registry, snapshotEngine }) => {
|
snapshot: ({ command, requestId, workspaceId, registry, snapshotEngine }) => {
|
||||||
const snapshotCommand = command as Extract<BrowserAutomationCommand, { command: "snapshot" }>;
|
const snapshotCommand = command as Extract<BrowserAutomationCommand, { command: "snapshot" }>;
|
||||||
return executeSnapshot(
|
return executeSnapshot(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
snapshotCommand.args.browserId ?? request.browserId,
|
snapshotCommand.args.browserId,
|
||||||
registry,
|
registry,
|
||||||
snapshotEngine,
|
snapshotEngine,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
click: ({ request, command, requestId, workspaceId, registry, snapshotEngine }) => {
|
click: ({ command, requestId, workspaceId, registry, snapshotEngine }) => {
|
||||||
const clickCommand = command as Extract<BrowserAutomationCommand, { command: "click" }>;
|
const clickCommand = command as Extract<BrowserAutomationCommand, { command: "click" }>;
|
||||||
return executeClick(
|
return executeClick(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
clickCommand.args.browserId ?? request.browserId,
|
clickCommand.args.browserId,
|
||||||
clickCommand.args.ref,
|
clickCommand.args.ref,
|
||||||
registry,
|
registry,
|
||||||
snapshotEngine,
|
snapshotEngine,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
fill: ({ request, command, requestId, workspaceId, registry, snapshotEngine }) => {
|
fill: ({ command, requestId, workspaceId, registry, snapshotEngine }) => {
|
||||||
const fillCommand = command as Extract<BrowserAutomationCommand, { command: "fill" }>;
|
const fillCommand = command as Extract<BrowserAutomationCommand, { command: "fill" }>;
|
||||||
return executeFill(
|
return executeFill(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
fillCommand.args.browserId ?? request.browserId,
|
fillCommand.args.browserId,
|
||||||
fillCommand.args.ref,
|
fillCommand.args.ref,
|
||||||
fillCommand.args.value,
|
fillCommand.args.value,
|
||||||
registry,
|
registry,
|
||||||
snapshotEngine,
|
snapshotEngine,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
wait: ({ request, command, requestId, workspaceId, registry }) => {
|
wait: ({ command, requestId, workspaceId, registry }) => {
|
||||||
const waitCommand = command as Extract<BrowserAutomationCommand, { command: "wait" }>;
|
const waitCommand = command as Extract<BrowserAutomationCommand, { command: "wait" }>;
|
||||||
return executeWait(
|
return executeWait(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
waitCommand.args.browserId ?? request.browserId,
|
waitCommand.args.browserId,
|
||||||
{
|
{
|
||||||
text: waitCommand.args.text,
|
text: waitCommand.args.text,
|
||||||
url: waitCommand.args.url,
|
url: waitCommand.args.url,
|
||||||
@@ -280,83 +246,78 @@ const commandHandlers: Record<BrowserAutomationCommand["command"], CommandHandle
|
|||||||
registry,
|
registry,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
type: ({ request, command, requestId, workspaceId, registry, snapshotEngine }) => {
|
type: ({ command, requestId, workspaceId, registry, snapshotEngine }) => {
|
||||||
const typeCommand = command as Extract<BrowserAutomationCommand, { command: "type" }>;
|
const typeCommand = command as Extract<BrowserAutomationCommand, { command: "type" }>;
|
||||||
return executeType(
|
return executeType(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
typeCommand.args.browserId ?? request.browserId,
|
typeCommand.args.browserId,
|
||||||
typeCommand.args.ref,
|
typeCommand.args.ref,
|
||||||
typeCommand.args.text,
|
typeCommand.args.text,
|
||||||
registry,
|
registry,
|
||||||
snapshotEngine,
|
snapshotEngine,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
keypress: ({ request, command, requestId, workspaceId, registry, snapshotEngine }) => {
|
keypress: ({ command, requestId, workspaceId, registry, snapshotEngine }) => {
|
||||||
const keypressCommand = command as Extract<BrowserAutomationCommand, { command: "keypress" }>;
|
const keypressCommand = command as Extract<BrowserAutomationCommand, { command: "keypress" }>;
|
||||||
return executeKeypress(
|
return executeKeypress(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
keypressCommand.args.browserId ?? request.browserId,
|
keypressCommand.args.browserId,
|
||||||
keypressCommand.args.ref,
|
keypressCommand.args.ref,
|
||||||
keypressCommand.args.key,
|
keypressCommand.args.key,
|
||||||
registry,
|
registry,
|
||||||
snapshotEngine,
|
snapshotEngine,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
navigate: ({ request, command, requestId, workspaceId, registry }) => {
|
navigate: ({ command, requestId, workspaceId, registry }) => {
|
||||||
const navigateCommand = command as Extract<BrowserAutomationCommand, { command: "navigate" }>;
|
const navigateCommand = command as Extract<BrowserAutomationCommand, { command: "navigate" }>;
|
||||||
return executeNavigate(
|
return executeNavigate(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
navigateCommand.args.browserId ?? request.browserId,
|
navigateCommand.args.browserId,
|
||||||
navigateCommand.args.url,
|
navigateCommand.args.url,
|
||||||
registry,
|
registry,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
back: ({ request, command, requestId, workspaceId, registry }) => {
|
back: ({ command, requestId, workspaceId, registry }) => {
|
||||||
const backCommand = command as Extract<BrowserAutomationCommand, { command: "back" }>;
|
const backCommand = command as Extract<BrowserAutomationCommand, { command: "back" }>;
|
||||||
return executeNavigationAction(
|
return executeNavigationAction(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
backCommand.args.browserId ?? request.browserId,
|
backCommand.args.browserId,
|
||||||
"back",
|
"back",
|
||||||
registry,
|
registry,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
forward: ({ request, command, requestId, workspaceId, registry }) => {
|
forward: ({ command, requestId, workspaceId, registry }) => {
|
||||||
const forwardCommand = command as Extract<BrowserAutomationCommand, { command: "forward" }>;
|
const forwardCommand = command as Extract<BrowserAutomationCommand, { command: "forward" }>;
|
||||||
return executeNavigationAction(
|
return executeNavigationAction(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
forwardCommand.args.browserId ?? request.browserId,
|
forwardCommand.args.browserId,
|
||||||
"forward",
|
"forward",
|
||||||
registry,
|
registry,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
reload: ({ request, command, requestId, workspaceId, registry }) => {
|
reload: ({ command, requestId, workspaceId, registry }) => {
|
||||||
const reloadCommand = command as Extract<BrowserAutomationCommand, { command: "reload" }>;
|
const reloadCommand = command as Extract<BrowserAutomationCommand, { command: "reload" }>;
|
||||||
return executeNavigationAction(
|
return executeNavigationAction(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
reloadCommand.args.browserId ?? request.browserId,
|
reloadCommand.args.browserId,
|
||||||
"reload",
|
"reload",
|
||||||
registry,
|
registry,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
screenshot: ({ request, command, requestId, workspaceId, registry }) => {
|
screenshot: ({ command, requestId, workspaceId, registry }) => {
|
||||||
const screenshotCommand = command as Extract<
|
const screenshotCommand = command as Extract<
|
||||||
BrowserAutomationCommand,
|
BrowserAutomationCommand,
|
||||||
{ command: "screenshot" }
|
{ command: "screenshot" }
|
||||||
>;
|
>;
|
||||||
return executeScreenshot(
|
return executeScreenshot(requestId, workspaceId, screenshotCommand.args.browserId, registry);
|
||||||
requestId,
|
|
||||||
workspaceId,
|
|
||||||
screenshotCommand.args.browserId ?? request.browserId,
|
|
||||||
registry,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
full_page_screenshot: ({ request, command, requestId, workspaceId, registry }) => {
|
full_page_screenshot: ({ command, requestId, workspaceId, registry }) => {
|
||||||
const screenshotCommand = command as Extract<
|
const screenshotCommand = command as Extract<
|
||||||
BrowserAutomationCommand,
|
BrowserAutomationCommand,
|
||||||
{ command: "full_page_screenshot" }
|
{ command: "full_page_screenshot" }
|
||||||
@@ -364,26 +325,26 @@ const commandHandlers: Record<BrowserAutomationCommand["command"], CommandHandle
|
|||||||
return executeFullPageScreenshot(
|
return executeFullPageScreenshot(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
screenshotCommand.args.browserId ?? request.browserId,
|
screenshotCommand.args.browserId,
|
||||||
registry,
|
registry,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
pdf: ({ request, command, requestId, workspaceId, registry }) => {
|
pdf: ({ command, requestId, workspaceId, registry }) => {
|
||||||
const pdfCommand = command as Extract<BrowserAutomationCommand, { command: "pdf" }>;
|
const pdfCommand = command as Extract<BrowserAutomationCommand, { command: "pdf" }>;
|
||||||
return executePdf(
|
return executePdf(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
pdfCommand.args.browserId ?? request.browserId,
|
pdfCommand.args.browserId,
|
||||||
{ landscape: pdfCommand.args.landscape, printBackground: pdfCommand.args.printBackground },
|
{ landscape: pdfCommand.args.landscape, printBackground: pdfCommand.args.printBackground },
|
||||||
registry,
|
registry,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
download: ({ request, command, requestId, workspaceId, registry }) => {
|
download: ({ command, requestId, workspaceId, registry }) => {
|
||||||
const downloadCommand = command as Extract<BrowserAutomationCommand, { command: "download" }>;
|
const downloadCommand = command as Extract<BrowserAutomationCommand, { command: "download" }>;
|
||||||
return executeDownload(
|
return executeDownload(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
downloadCommand.args.browserId ?? request.browserId,
|
downloadCommand.args.browserId,
|
||||||
{ url: downloadCommand.args.url, fileName: downloadCommand.args.fileName },
|
{ url: downloadCommand.args.url, fileName: downloadCommand.args.fileName },
|
||||||
registry,
|
registry,
|
||||||
);
|
);
|
||||||
@@ -394,101 +355,96 @@ const commandHandlers: Record<BrowserAutomationCommand["command"], CommandHandle
|
|||||||
requestId,
|
requestId,
|
||||||
request.cwd,
|
request.cwd,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
uploadCommand.args.browserId ?? request.browserId,
|
uploadCommand.args.browserId,
|
||||||
{ ref: uploadCommand.args.ref, filePaths: uploadCommand.args.filePaths },
|
{ ref: uploadCommand.args.ref, filePaths: uploadCommand.args.filePaths },
|
||||||
registry,
|
registry,
|
||||||
snapshotEngine,
|
snapshotEngine,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
focus: ({ request, command, requestId, workspaceId, registry, snapshotEngine }) => {
|
focus: ({ command, requestId, workspaceId, registry, snapshotEngine }) => {
|
||||||
const focusCommand = command as Extract<BrowserAutomationCommand, { command: "focus" }>;
|
const focusCommand = command as Extract<BrowserAutomationCommand, { command: "focus" }>;
|
||||||
return executeFocus(
|
return executeFocus(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
focusCommand.args.browserId ?? request.browserId,
|
focusCommand.args.browserId,
|
||||||
focusCommand.args.ref,
|
focusCommand.args.ref,
|
||||||
registry,
|
registry,
|
||||||
snapshotEngine,
|
snapshotEngine,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
clear: ({ request, command, requestId, workspaceId, registry, snapshotEngine }) => {
|
clear: ({ command, requestId, workspaceId, registry, snapshotEngine }) => {
|
||||||
const clearCommand = command as Extract<BrowserAutomationCommand, { command: "clear" }>;
|
const clearCommand = command as Extract<BrowserAutomationCommand, { command: "clear" }>;
|
||||||
return executeClear(
|
return executeClear(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
clearCommand.args.browserId ?? request.browserId,
|
clearCommand.args.browserId,
|
||||||
clearCommand.args.ref,
|
clearCommand.args.ref,
|
||||||
registry,
|
registry,
|
||||||
snapshotEngine,
|
snapshotEngine,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
check: ({ request, command, requestId, workspaceId, registry, snapshotEngine }) => {
|
check: ({ command, requestId, workspaceId, registry, snapshotEngine }) => {
|
||||||
const checkCommand = command as Extract<BrowserAutomationCommand, { command: "check" }>;
|
const checkCommand = command as Extract<BrowserAutomationCommand, { command: "check" }>;
|
||||||
return executeCheck(
|
return executeCheck(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
checkCommand.args.browserId ?? request.browserId,
|
checkCommand.args.browserId,
|
||||||
checkCommand.args.ref,
|
checkCommand.args.ref,
|
||||||
checkCommand.args.checked,
|
checkCommand.args.checked,
|
||||||
registry,
|
registry,
|
||||||
snapshotEngine,
|
snapshotEngine,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
select: ({ request, command, requestId, workspaceId, registry, snapshotEngine }) => {
|
select: ({ command, requestId, workspaceId, registry, snapshotEngine }) => {
|
||||||
const selectCommand = command as Extract<BrowserAutomationCommand, { command: "select" }>;
|
const selectCommand = command as Extract<BrowserAutomationCommand, { command: "select" }>;
|
||||||
return executeSelect(
|
return executeSelect(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
selectCommand.args.browserId ?? request.browserId,
|
selectCommand.args.browserId,
|
||||||
selectCommand.args.ref,
|
selectCommand.args.ref,
|
||||||
selectCommand.args.value,
|
selectCommand.args.value,
|
||||||
registry,
|
registry,
|
||||||
snapshotEngine,
|
snapshotEngine,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
hover: ({ request, command, requestId, workspaceId, registry, snapshotEngine }) => {
|
hover: ({ command, requestId, workspaceId, registry, snapshotEngine }) => {
|
||||||
const hoverCommand = command as Extract<BrowserAutomationCommand, { command: "hover" }>;
|
const hoverCommand = command as Extract<BrowserAutomationCommand, { command: "hover" }>;
|
||||||
return executeHover(
|
return executeHover(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
hoverCommand.args.browserId ?? request.browserId,
|
hoverCommand.args.browserId,
|
||||||
hoverCommand.args.ref,
|
hoverCommand.args.ref,
|
||||||
registry,
|
registry,
|
||||||
snapshotEngine,
|
snapshotEngine,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
drag: ({ request, command, requestId, workspaceId, registry, snapshotEngine }) => {
|
drag: ({ command, requestId, workspaceId, registry, snapshotEngine }) => {
|
||||||
const dragCommand = command as Extract<BrowserAutomationCommand, { command: "drag" }>;
|
const dragCommand = command as Extract<BrowserAutomationCommand, { command: "drag" }>;
|
||||||
return executeDrag(
|
return executeDrag(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
dragCommand.args.browserId ?? request.browserId,
|
dragCommand.args.browserId,
|
||||||
dragCommand.args.sourceRef,
|
dragCommand.args.sourceRef,
|
||||||
dragCommand.args.targetRef,
|
dragCommand.args.targetRef,
|
||||||
registry,
|
registry,
|
||||||
snapshotEngine,
|
snapshotEngine,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
logs: ({ request, command, requestId, workspaceId, registry }) => {
|
logs: ({ command, requestId, workspaceId, registry }) => {
|
||||||
const logsCommand = command as Extract<BrowserAutomationCommand, { command: "logs" }>;
|
const logsCommand = command as Extract<BrowserAutomationCommand, { command: "logs" }>;
|
||||||
return executeLogs(
|
return executeLogs(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
logsCommand.args.browserId ?? request.browserId,
|
logsCommand.args.browserId,
|
||||||
logsCommand.args.maxEntries,
|
logsCommand.args.maxEntries,
|
||||||
registry,
|
registry,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
storage: ({ request, command, requestId, workspaceId, registry }) => {
|
storage: ({ command, requestId, workspaceId, registry }) => {
|
||||||
const storageCommand = command as Extract<BrowserAutomationCommand, { command: "storage" }>;
|
const storageCommand = command as Extract<BrowserAutomationCommand, { command: "storage" }>;
|
||||||
return executeStorage(
|
return executeStorage(requestId, workspaceId, storageCommand.args.browserId, registry);
|
||||||
requestId,
|
|
||||||
workspaceId,
|
|
||||||
storageCommand.args.browserId ?? request.browserId,
|
|
||||||
registry,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
environment: ({ request, command, requestId, workspaceId, registry }) => {
|
environment: ({ command, requestId, workspaceId, registry }) => {
|
||||||
const environmentCommand = command as Extract<
|
const environmentCommand = command as Extract<
|
||||||
BrowserAutomationCommand,
|
BrowserAutomationCommand,
|
||||||
{ command: "environment" }
|
{ command: "environment" }
|
||||||
@@ -496,7 +452,7 @@ const commandHandlers: Record<BrowserAutomationCommand["command"], CommandHandle
|
|||||||
return executeEnvironment(
|
return executeEnvironment(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environmentCommand.args.browserId ?? request.browserId,
|
environmentCommand.args.browserId,
|
||||||
{
|
{
|
||||||
viewport: environmentCommand.args.viewport,
|
viewport: environmentCommand.args.viewport,
|
||||||
geolocation: environmentCommand.args.geolocation,
|
geolocation: environmentCommand.args.geolocation,
|
||||||
@@ -504,7 +460,7 @@ const commandHandlers: Record<BrowserAutomationCommand["command"], CommandHandle
|
|||||||
registry,
|
registry,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
set_background: ({ request, command, requestId, workspaceId, registry }) => {
|
set_background: ({ command, requestId, workspaceId, registry }) => {
|
||||||
const setBackgroundCommand = command as Extract<
|
const setBackgroundCommand = command as Extract<
|
||||||
BrowserAutomationCommand,
|
BrowserAutomationCommand,
|
||||||
{ command: "set_background" }
|
{ command: "set_background" }
|
||||||
@@ -512,7 +468,7 @@ const commandHandlers: Record<BrowserAutomationCommand["command"], CommandHandle
|
|||||||
return executeSetBackground(
|
return executeSetBackground(
|
||||||
requestId,
|
requestId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
setBackgroundCommand.args.browserId ?? request.browserId,
|
setBackgroundCommand.args.browserId,
|
||||||
setBackgroundCommand.args.color,
|
setBackgroundCommand.args.color,
|
||||||
registry,
|
registry,
|
||||||
);
|
);
|
||||||
@@ -555,7 +511,7 @@ function executeListTabs(
|
|||||||
function executePageInfo(
|
function executePageInfo(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
): AutomationCommandPayload {
|
): AutomationCommandPayload {
|
||||||
const target = resolveTabTarget({
|
const target = resolveTabTarget({
|
||||||
@@ -586,7 +542,7 @@ function executePageInfo(
|
|||||||
async function executeSnapshot(
|
async function executeSnapshot(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
snapshotEngine: BrowserSnapshotEngine,
|
snapshotEngine: BrowserSnapshotEngine,
|
||||||
): Promise<AutomationCommandPayload> {
|
): Promise<AutomationCommandPayload> {
|
||||||
@@ -624,7 +580,7 @@ async function executeSnapshot(
|
|||||||
async function executeClick(
|
async function executeClick(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
ref: string,
|
ref: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
snapshotEngine: BrowserSnapshotEngine,
|
snapshotEngine: BrowserSnapshotEngine,
|
||||||
@@ -647,7 +603,7 @@ async function executeClick(
|
|||||||
async function executeFill(
|
async function executeFill(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
ref: string,
|
ref: string,
|
||||||
value: string,
|
value: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
@@ -672,7 +628,7 @@ async function executeFill(
|
|||||||
async function executeFocus(
|
async function executeFocus(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
ref: string,
|
ref: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
snapshotEngine: BrowserSnapshotEngine,
|
snapshotEngine: BrowserSnapshotEngine,
|
||||||
@@ -695,7 +651,7 @@ async function executeFocus(
|
|||||||
async function executeClear(
|
async function executeClear(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
ref: string,
|
ref: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
snapshotEngine: BrowserSnapshotEngine,
|
snapshotEngine: BrowserSnapshotEngine,
|
||||||
@@ -718,7 +674,7 @@ async function executeClear(
|
|||||||
async function executeCheck(
|
async function executeCheck(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
ref: string,
|
ref: string,
|
||||||
checked: boolean,
|
checked: boolean,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
@@ -747,7 +703,7 @@ async function executeCheck(
|
|||||||
async function executeSelect(
|
async function executeSelect(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
ref: string,
|
ref: string,
|
||||||
value: string,
|
value: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
@@ -776,7 +732,7 @@ async function executeSelect(
|
|||||||
async function executeHover(
|
async function executeHover(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
ref: string,
|
ref: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
snapshotEngine: BrowserSnapshotEngine,
|
snapshotEngine: BrowserSnapshotEngine,
|
||||||
@@ -799,7 +755,7 @@ async function executeHover(
|
|||||||
async function executeDrag(
|
async function executeDrag(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
sourceRef: string,
|
sourceRef: string,
|
||||||
targetRef: string,
|
targetRef: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
@@ -828,7 +784,7 @@ async function executeDrag(
|
|||||||
async function executeLogs(
|
async function executeLogs(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
maxEntries: number,
|
maxEntries: number,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
): Promise<AutomationCommandPayload> {
|
): Promise<AutomationCommandPayload> {
|
||||||
@@ -855,7 +811,7 @@ async function executeLogs(
|
|||||||
async function executeStorage(
|
async function executeStorage(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
): Promise<AutomationCommandPayload> {
|
): Promise<AutomationCommandPayload> {
|
||||||
const target = resolveTabTarget({ requestId, workspaceId, browserId, registry });
|
const target = resolveTabTarget({ requestId, workspaceId, browserId, registry });
|
||||||
@@ -882,7 +838,7 @@ async function executeStorage(
|
|||||||
async function executeEnvironment(
|
async function executeEnvironment(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
environment: {
|
environment: {
|
||||||
viewport?: { width: number; height: number; deviceScaleFactor?: number };
|
viewport?: { width: number; height: number; deviceScaleFactor?: number };
|
||||||
geolocation?: { latitude: number; longitude: number; accuracy?: number };
|
geolocation?: { latitude: number; longitude: number; accuracy?: number };
|
||||||
@@ -941,7 +897,7 @@ function staleRefFailure(requestId: string, ref: string): FailurePayload {
|
|||||||
async function executeWait(
|
async function executeWait(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
condition: { text?: string; url?: string; timeoutMs?: number },
|
condition: { text?: string; url?: string; timeoutMs?: number },
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
): Promise<AutomationCommandPayload> {
|
): Promise<AutomationCommandPayload> {
|
||||||
@@ -999,7 +955,7 @@ async function executeWait(
|
|||||||
async function executeSetBackground(
|
async function executeSetBackground(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
color: string,
|
color: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
): Promise<AutomationCommandPayload> {
|
): Promise<AutomationCommandPayload> {
|
||||||
@@ -1032,7 +988,7 @@ function buildSetBackgroundScript(color: string): string {
|
|||||||
async function executeType(
|
async function executeType(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
ref: string | undefined,
|
ref: string | undefined,
|
||||||
text: string,
|
text: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
@@ -1061,7 +1017,7 @@ async function executeType(
|
|||||||
async function executeKeypress(
|
async function executeKeypress(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
ref: string | undefined,
|
ref: string | undefined,
|
||||||
key: string,
|
key: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
@@ -1090,7 +1046,7 @@ async function executeKeypress(
|
|||||||
async function executeNavigate(
|
async function executeNavigate(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
url: string,
|
url: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
): Promise<AutomationCommandPayload> {
|
): Promise<AutomationCommandPayload> {
|
||||||
@@ -1112,7 +1068,7 @@ async function executeNavigate(
|
|||||||
function executeNavigationAction(
|
function executeNavigationAction(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
action: "back" | "forward" | "reload",
|
action: "back" | "forward" | "reload",
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
): AutomationCommandPayload {
|
): AutomationCommandPayload {
|
||||||
@@ -1135,7 +1091,7 @@ function executeNavigationAction(
|
|||||||
async function executeScreenshot(
|
async function executeScreenshot(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
): Promise<AutomationCommandPayload> {
|
): Promise<AutomationCommandPayload> {
|
||||||
const target = resolveTabTarget({ requestId, workspaceId, browserId, registry });
|
const target = resolveTabTarget({ requestId, workspaceId, browserId, registry });
|
||||||
@@ -1212,7 +1168,7 @@ async function getCdpLayoutMetrics(contents: TabContents): Promise<{
|
|||||||
async function executeFullPageScreenshot(
|
async function executeFullPageScreenshot(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
): Promise<AutomationCommandPayload> {
|
): Promise<AutomationCommandPayload> {
|
||||||
const target = resolveTabTarget({ requestId, workspaceId, browserId, registry });
|
const target = resolveTabTarget({ requestId, workspaceId, browserId, registry });
|
||||||
@@ -1260,7 +1216,7 @@ async function executeFullPageScreenshot(
|
|||||||
async function executePdf(
|
async function executePdf(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
options: { landscape?: boolean; printBackground: boolean },
|
options: { landscape?: boolean; printBackground: boolean },
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
): Promise<AutomationCommandPayload> {
|
): Promise<AutomationCommandPayload> {
|
||||||
@@ -1290,7 +1246,7 @@ async function executePdf(
|
|||||||
async function executeDownload(
|
async function executeDownload(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
input: { url: string; fileName?: string },
|
input: { url: string; fileName?: string },
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
): Promise<AutomationCommandPayload> {
|
): Promise<AutomationCommandPayload> {
|
||||||
@@ -1331,7 +1287,7 @@ async function executeUpload(
|
|||||||
requestId: string,
|
requestId: string,
|
||||||
cwd: string | undefined,
|
cwd: string | undefined,
|
||||||
workspaceId: string | undefined,
|
workspaceId: string | undefined,
|
||||||
browserId: string | undefined,
|
browserId: string,
|
||||||
input: { ref: string; filePaths: string[] },
|
input: { ref: string; filePaths: string[] },
|
||||||
registry: BrowserRegistry,
|
registry: BrowserRegistry,
|
||||||
snapshotEngine: BrowserSnapshotEngine,
|
snapshotEngine: BrowserSnapshotEngine,
|
||||||
@@ -1573,41 +1529,22 @@ const VIEWPORT_SCRIPT = String.raw`(() => JSON.stringify({
|
|||||||
function resolveTabTarget(input: {
|
function resolveTabTarget(input: {
|
||||||
requestId: string;
|
requestId: string;
|
||||||
workspaceId: string | undefined;
|
workspaceId: string | undefined;
|
||||||
browserId: string | undefined;
|
browserId: string;
|
||||||
registry: BrowserRegistry;
|
registry: BrowserRegistry;
|
||||||
}): ResolvedTabTarget | FailurePayload {
|
}): ResolvedTabTarget | FailurePayload {
|
||||||
const { requestId, workspaceId, browserId, registry } = input;
|
const { requestId, workspaceId, browserId, registry } = input;
|
||||||
let contents: TabContents | null;
|
if (workspaceId && registry.getBrowserWorkspaceId(browserId) !== workspaceId) {
|
||||||
let resolvedBrowserId: string;
|
return fail(requestId, "browser_tab_not_found", `No browser tab found for ID: ${browserId}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (browserId) {
|
const contents = registry.getTabContents(browserId);
|
||||||
if (workspaceId && registry.getBrowserWorkspaceId(browserId) !== workspaceId) {
|
if (!contents) {
|
||||||
return fail(requestId, "browser_tab_not_found", `No browser tab found for ID: ${browserId}`);
|
return fail(requestId, "browser_tab_not_found", `No browser tab found for ID: ${browserId}`);
|
||||||
}
|
|
||||||
contents = registry.getTabContents(browserId);
|
|
||||||
resolvedBrowserId = browserId;
|
|
||||||
if (!contents) {
|
|
||||||
return fail(requestId, "browser_tab_not_found", `No browser tab found for ID: ${browserId}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!workspaceId) {
|
|
||||||
return fail(requestId, "browser_no_tab", "No active browser tab in workspace");
|
|
||||||
}
|
|
||||||
contents = registry.getWorkspaceActiveTabContents(workspaceId);
|
|
||||||
const activeId = registry.getWorkspaceActiveBrowserId(workspaceId);
|
|
||||||
if (!contents || !activeId) {
|
|
||||||
return fail(requestId, "browser_no_tab", "No active browser tab in workspace");
|
|
||||||
}
|
|
||||||
resolvedBrowserId = activeId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contents.isDestroyed()) {
|
if (contents.isDestroyed()) {
|
||||||
return fail(
|
return fail(requestId, "browser_tab_closed", `Browser tab ${browserId} has been closed`);
|
||||||
requestId,
|
|
||||||
"browser_tab_closed",
|
|
||||||
`Browser tab ${resolvedBrowserId} has been closed`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { browserId: resolvedBrowserId, contents };
|
return { browserId, contents };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,17 +75,6 @@ export function getWorkspaceActivePaseoBrowserId(workspaceId: string): string |
|
|||||||
return browserRegistry.getWorkspaceActiveBrowserId(workspaceId);
|
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 {
|
export function getPaseoBrowserWebContents(browserId: string): WebContents | null {
|
||||||
const contentsId = browserRegistry.getWebContentsIdForBrowser(browserId);
|
const contentsId = browserRegistry.getWebContentsIdForBrowser(browserId);
|
||||||
if (contentsId === null) {
|
if (contentsId === null) {
|
||||||
@@ -99,16 +88,6 @@ export function getPaseoBrowserWebContents(browserId: string): WebContents | nul
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
export function getMostRecentWorkspaceActivePaseoBrowserWebContents(): WebContents | null {
|
||||||
const browserId = browserRegistry.getMostRecentWorkspaceActiveBrowserId();
|
const browserId = browserRegistry.getMostRecentWorkspaceActiveBrowserId();
|
||||||
return browserId ? getPaseoBrowserWebContents(browserId) : null;
|
return browserId ? getPaseoBrowserWebContents(browserId) : null;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ describe("PaseoBrowserWebviewRegistry", () => {
|
|||||||
registry.registerWebContents({ webContentsId: 1, browserId: "browser-a" });
|
registry.registerWebContents({ webContentsId: 1, browserId: "browser-a" });
|
||||||
registry.registerWorkspace({ browserId: "browser-a", workspaceId: "workspace-a" });
|
registry.registerWorkspace({ browserId: "browser-a", workspaceId: "workspace-a" });
|
||||||
registry.setWorkspaceActiveBrowser({ workspaceId: "workspace-a", browserId: "browser-a" });
|
registry.setWorkspaceActiveBrowser({ workspaceId: "workspace-a", browserId: "browser-a" });
|
||||||
registry.setAgentActiveBrowser({ agentId: "agent-a", browserId: "browser-a" });
|
|
||||||
registry.registerWebContents({ webContentsId: 2, browserId: "browser-a" });
|
registry.registerWebContents({ webContentsId: 2, browserId: "browser-a" });
|
||||||
|
|
||||||
expect(registry.getBrowserIdForWebContents(1)).toBeNull();
|
expect(registry.getBrowserIdForWebContents(1)).toBeNull();
|
||||||
@@ -16,7 +15,6 @@ describe("PaseoBrowserWebviewRegistry", () => {
|
|||||||
expect(registry.getWebContentsIdForBrowser("browser-a")).toBe(2);
|
expect(registry.getWebContentsIdForBrowser("browser-a")).toBe(2);
|
||||||
expect(registry.getWorkspaceId("browser-a")).toBe("workspace-a");
|
expect(registry.getWorkspaceId("browser-a")).toBe("workspace-a");
|
||||||
expect(registry.getWorkspaceActiveBrowserId("workspace-a")).toBe("browser-a");
|
expect(registry.getWorkspaceActiveBrowserId("workspace-a")).toBe("browser-a");
|
||||||
expect(registry.getAgentActiveBrowserId("agent-a")).toBe("browser-a");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ignores stale destroy events after a duplicate browserId moved", () => {
|
it("ignores stale destroy events after a duplicate browserId moved", () => {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ export class PaseoBrowserWebviewRegistry {
|
|||||||
private readonly webContentsIdsByBrowserId = new Map<string, number>();
|
private readonly webContentsIdsByBrowserId = new Map<string, number>();
|
||||||
private readonly workspaceIdsByBrowserId = new Map<string, string>();
|
private readonly workspaceIdsByBrowserId = new Map<string, string>();
|
||||||
private readonly activeBrowserIdsByWorkspaceId = new Map<string, string>();
|
private readonly activeBrowserIdsByWorkspaceId = new Map<string, string>();
|
||||||
private readonly activeBrowserIdsByAgentId = new Map<string, string>();
|
|
||||||
|
|
||||||
public registerWebContents(input: { webContentsId: number; browserId: string }): void {
|
public registerWebContents(input: { webContentsId: number; browserId: string }): void {
|
||||||
const previousWebContentsId = this.webContentsIdsByBrowserId.get(input.browserId) ?? null;
|
const previousWebContentsId = this.webContentsIdsByBrowserId.get(input.browserId) ?? null;
|
||||||
@@ -80,29 +79,11 @@ export class PaseoBrowserWebviewRegistry {
|
|||||||
return Array.from(this.activeBrowserIdsByWorkspaceId.values()).at(-1) ?? null;
|
return Array.from(this.activeBrowserIdsByWorkspaceId.values()).at(-1) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setAgentActiveBrowser(input: { agentId: string; browserId: string | null }): void {
|
|
||||||
if (input.browserId) {
|
|
||||||
this.activeBrowserIdsByAgentId.delete(input.agentId);
|
|
||||||
this.activeBrowserIdsByAgentId.set(input.agentId, input.browserId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.activeBrowserIdsByAgentId.delete(input.agentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getAgentActiveBrowserId(agentId: string): string | null {
|
|
||||||
return this.activeBrowserIdsByAgentId.get(agentId) ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private deleteActiveBrowserReferences(browserId: string): void {
|
private deleteActiveBrowserReferences(browserId: string): void {
|
||||||
for (const [workspaceId, activeBrowserId] of this.activeBrowserIdsByWorkspaceId) {
|
for (const [workspaceId, activeBrowserId] of this.activeBrowserIdsByWorkspaceId) {
|
||||||
if (activeBrowserId === browserId) {
|
if (activeBrowserId === browserId) {
|
||||||
this.activeBrowserIdsByWorkspaceId.delete(workspaceId);
|
this.activeBrowserIdsByWorkspaceId.delete(workspaceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const [agentId, activeBrowserId] of this.activeBrowserIdsByAgentId) {
|
|
||||||
if (activeBrowserId === browserId) {
|
|
||||||
this.activeBrowserIdsByAgentId.delete(agentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ import {
|
|||||||
registerBrowserWebviewNavigationGuards,
|
registerBrowserWebviewNavigationGuards,
|
||||||
registerPaseoBrowserWorkspace,
|
registerPaseoBrowserWorkspace,
|
||||||
registerPaseoBrowserWebContents,
|
registerPaseoBrowserWebContents,
|
||||||
setAgentActivePaseoBrowserId,
|
|
||||||
setWorkspaceActivePaseoBrowserId,
|
setWorkspaceActivePaseoBrowserId,
|
||||||
} from "./features/browser-webviews/index.js";
|
} from "./features/browser-webviews/index.js";
|
||||||
import { parseOpenProjectPathFromArgv } from "./open-project-routing.js";
|
import { parseOpenProjectPathFromArgv } from "./open-project-routing.js";
|
||||||
@@ -144,20 +143,6 @@ function readActiveBrowserInput(
|
|||||||
return { workspaceId: record.workspaceId.trim(), browserId: browserId || 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[] = [];
|
const pendingBrowserWebviewIds: string[] = [];
|
||||||
|
|
||||||
function isBrowserRefreshInput(input: Electron.Input): boolean {
|
function isBrowserRefreshInput(input: Electron.Input): boolean {
|
||||||
@@ -350,13 +335,6 @@ ipcMain.handle("paseo:browser:set-workspace-active-browser", (_event, rawInput:
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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) => {
|
ipcMain.handle("paseo:browser:open-devtools", (_event, browserId: unknown) => {
|
||||||
if (typeof browserId !== "string" || browserId.trim().length === 0) {
|
if (typeof browserId !== "string" || browserId.trim().length === 0) {
|
||||||
const result = {
|
const result = {
|
||||||
|
|||||||
@@ -78,8 +78,6 @@ contextBridge.exposeInMainWorld("paseoDesktop", {
|
|||||||
ipcRenderer.invoke("paseo:browser:register-workspace-browser", input),
|
ipcRenderer.invoke("paseo:browser:register-workspace-browser", input),
|
||||||
setWorkspaceActiveBrowser: (input: { workspaceId: string; browserId: string | null }) =>
|
setWorkspaceActiveBrowser: (input: { workspaceId: string; browserId: string | null }) =>
|
||||||
ipcRenderer.invoke("paseo:browser:set-workspace-active-browser", input),
|
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) =>
|
openDevTools: (browserId: string) =>
|
||||||
ipcRenderer.invoke("paseo:browser:open-devtools", browserId),
|
ipcRenderer.invoke("paseo:browser:open-devtools", browserId),
|
||||||
clearPartition: (browserId: string) =>
|
clearPartition: (browserId: string) =>
|
||||||
|
|||||||
@@ -5,96 +5,215 @@ import {
|
|||||||
BrowserAutomationExecuteResponseSchema,
|
BrowserAutomationExecuteResponseSchema,
|
||||||
} from "./rpc-schemas.js";
|
} from "./rpc-schemas.js";
|
||||||
|
|
||||||
describe("browser automation execute RPC schemas", () => {
|
const BROWSER_ID = "11111111-1111-4111-8111-111111111111";
|
||||||
test("rejects navigate and download commands for non-http URLs", () => {
|
const FALLBACK_BROWSER_ID = "1777777777777-abcdef";
|
||||||
for (const command of ["navigate", "download"] as const) {
|
const BROWSER_ID_MESSAGE =
|
||||||
expect(() =>
|
"browserId must be a real id returned by browser_new_tab or browser_list_tabs";
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
const WAIT_CONDITION_MESSAGE = "browser_wait requires exactly one of text or url";
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: `req-${command}`,
|
|
||||||
command: {
|
|
||||||
command,
|
|
||||||
args: { url: "file:///tmp/secret.txt" },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).toThrow();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parses list tabs requests with top-level correlation and typed command args", () => {
|
describe("browser automation execute RPC schemas", () => {
|
||||||
|
test("list tabs reads workspace from the request envelope", () => {
|
||||||
const parsed = BrowserAutomationExecuteRequestSchema.parse({
|
const parsed = BrowserAutomationExecuteRequestSchema.parse({
|
||||||
type: "browser.automation.execute.request",
|
type: "browser.automation.execute.request",
|
||||||
requestId: "req-1",
|
requestId: "req-list-tabs",
|
||||||
workspaceId: "workspace-1",
|
workspaceId: "workspace-1",
|
||||||
command: {
|
command: { command: "list_tabs", args: {} },
|
||||||
command: "list_tabs",
|
|
||||||
args: { workspaceId: "workspace-1" },
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(parsed).toEqual({
|
expect(parsed).toEqual({
|
||||||
type: "browser.automation.execute.request",
|
type: "browser.automation.execute.request",
|
||||||
requestId: "req-1",
|
requestId: "req-list-tabs",
|
||||||
workspaceId: "workspace-1",
|
workspaceId: "workspace-1",
|
||||||
command: {
|
command: { command: "list_tabs", args: {} },
|
||||||
command: "list_tabs",
|
|
||||||
args: { workspaceId: "workspace-1" },
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parses new tab requests and responses", () => {
|
test("new tab reads workspace from the request envelope", () => {
|
||||||
expect(
|
const parsed = BrowserAutomationExecuteRequestSchema.parse({
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
type: "browser.automation.execute.request",
|
||||||
type: "browser.automation.execute.request",
|
requestId: "req-new-tab",
|
||||||
requestId: "req-new-tab",
|
workspaceId: "workspace-1",
|
||||||
workspaceId: "workspace-1",
|
command: { command: "new_tab", args: { url: "https://example.com" } },
|
||||||
command: {
|
|
||||||
command: "new_tab",
|
|
||||||
args: { workspaceId: "workspace-1", url: "https://example.com" },
|
|
||||||
},
|
|
||||||
}).command,
|
|
||||||
).toEqual({
|
|
||||||
command: "new_tab",
|
|
||||||
args: { workspaceId: "workspace-1", url: "https://example.com" },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(
|
expect(parsed.command).toEqual({
|
||||||
BrowserAutomationExecuteResponseSchema.parse({
|
command: "new_tab",
|
||||||
type: "browser.automation.execute.response",
|
args: { url: "https://example.com" },
|
||||||
payload: {
|
});
|
||||||
requestId: "req-new-tab",
|
});
|
||||||
ok: true,
|
|
||||||
result: {
|
test("tab commands require a browser id from browser_new_tab or browser_list_tabs", () => {
|
||||||
command: "new_tab",
|
const parsed = BrowserAutomationExecuteRequestSchema.safeParse({
|
||||||
browserId: "browser-1",
|
type: "browser.automation.execute.request",
|
||||||
workspaceId: "workspace-1",
|
requestId: "req-snapshot",
|
||||||
url: "https://example.com",
|
workspaceId: "workspace-1",
|
||||||
},
|
command: { command: "snapshot", args: {} },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parsed).toMatchObject({
|
||||||
|
success: false,
|
||||||
|
error: { issues: [expect.objectContaining({ message: BROWSER_ID_MESSAGE })] },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("tab commands reject hallucinated browser ids", () => {
|
||||||
|
const parsed = BrowserAutomationExecuteRequestSchema.safeParse({
|
||||||
|
type: "browser.automation.execute.request",
|
||||||
|
requestId: "req-page-info",
|
||||||
|
workspaceId: "workspace-1",
|
||||||
|
command: { command: "page_info", args: { browserId: "default" } },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parsed).toMatchObject({
|
||||||
|
success: false,
|
||||||
|
error: { issues: [expect.objectContaining({ message: BROWSER_ID_MESSAGE })] },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("tab commands parse browser ids produced by the fallback generator", () => {
|
||||||
|
const parsed = BrowserAutomationExecuteRequestSchema.parse({
|
||||||
|
type: "browser.automation.execute.request",
|
||||||
|
requestId: "req-page-info",
|
||||||
|
workspaceId: "workspace-1",
|
||||||
|
command: { command: "page_info", args: { browserId: FALLBACK_BROWSER_ID } },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parsed.command).toEqual({
|
||||||
|
command: "page_info",
|
||||||
|
args: { browserId: FALLBACK_BROWSER_ID },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("requests reject browser id in the envelope", () => {
|
||||||
|
const parsed = BrowserAutomationExecuteRequestSchema.safeParse({
|
||||||
|
type: "browser.automation.execute.request",
|
||||||
|
requestId: "req-click",
|
||||||
|
workspaceId: "workspace-1",
|
||||||
|
browserId: BROWSER_ID,
|
||||||
|
command: { command: "click", args: { browserId: BROWSER_ID, ref: "@e1" } },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parsed).toMatchObject({
|
||||||
|
success: false,
|
||||||
|
error: { issues: [expect.objectContaining({ message: 'Unrecognized key: "browserId"' })] },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("tab commands reject workspace id in command args", () => {
|
||||||
|
const parsed = BrowserAutomationExecuteRequestSchema.safeParse({
|
||||||
|
type: "browser.automation.execute.request",
|
||||||
|
requestId: "req-click",
|
||||||
|
workspaceId: "workspace-1",
|
||||||
|
command: {
|
||||||
|
command: "click",
|
||||||
|
args: { workspaceId: "workspace-1", browserId: BROWSER_ID, ref: "@e1" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parsed).toMatchObject({
|
||||||
|
success: false,
|
||||||
|
error: { issues: [expect.objectContaining({ message: 'Unrecognized key: "workspaceId"' })] },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("wait rejects calls without exactly one condition", () => {
|
||||||
|
const parsed = BrowserAutomationExecuteRequestSchema.safeParse({
|
||||||
|
type: "browser.automation.execute.request",
|
||||||
|
requestId: "req-wait",
|
||||||
|
command: { command: "wait", args: { browserId: BROWSER_ID } },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parsed).toMatchObject({
|
||||||
|
success: false,
|
||||||
|
error: { issues: [expect.objectContaining({ message: WAIT_CONDITION_MESSAGE })] },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("wait rejects calls with both text and url conditions", () => {
|
||||||
|
const parsed = BrowserAutomationExecuteRequestSchema.safeParse({
|
||||||
|
type: "browser.automation.execute.request",
|
||||||
|
requestId: "req-wait",
|
||||||
|
command: {
|
||||||
|
command: "wait",
|
||||||
|
args: { browserId: BROWSER_ID, text: "Ready", url: "/ready" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parsed).toMatchObject({
|
||||||
|
success: false,
|
||||||
|
error: { issues: [expect.objectContaining({ message: WAIT_CONDITION_MESSAGE })] },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("wait accepts one text condition", () => {
|
||||||
|
const parsed = BrowserAutomationExecuteRequestSchema.parse({
|
||||||
|
type: "browser.automation.execute.request",
|
||||||
|
requestId: "req-wait",
|
||||||
|
command: {
|
||||||
|
command: "wait",
|
||||||
|
args: { browserId: BROWSER_ID, text: "Ready", timeoutMs: 1000 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parsed.command).toEqual({
|
||||||
|
command: "wait",
|
||||||
|
args: { browserId: BROWSER_ID, text: "Ready", timeoutMs: 1000 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("navigate rejects non-http URLs at the protocol boundary", () => {
|
||||||
|
const parsed = BrowserAutomationExecuteRequestSchema.safeParse({
|
||||||
|
type: "browser.automation.execute.request",
|
||||||
|
requestId: "req-navigate",
|
||||||
|
command: {
|
||||||
|
command: "navigate",
|
||||||
|
args: { browserId: BROWSER_ID, url: "file:///tmp/secret.txt" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parsed).toMatchObject({
|
||||||
|
success: false,
|
||||||
|
error: { issues: [expect.objectContaining({ message: "URL must use http or https" })] },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("new tab responses declare the generated browser id shape", () => {
|
||||||
|
const parsed = BrowserAutomationExecuteResponseSchema.parse({
|
||||||
|
type: "browser.automation.execute.response",
|
||||||
|
payload: {
|
||||||
|
requestId: "req-new-tab",
|
||||||
|
ok: true,
|
||||||
|
result: {
|
||||||
|
command: "new_tab",
|
||||||
|
browserId: BROWSER_ID,
|
||||||
|
workspaceId: "workspace-1",
|
||||||
|
url: "https://example.com",
|
||||||
},
|
},
|
||||||
}).payload,
|
},
|
||||||
).toEqual({
|
});
|
||||||
|
|
||||||
|
expect(parsed.payload).toEqual({
|
||||||
requestId: "req-new-tab",
|
requestId: "req-new-tab",
|
||||||
ok: true,
|
ok: true,
|
||||||
result: {
|
result: {
|
||||||
command: "new_tab",
|
command: "new_tab",
|
||||||
browserId: "browser-1",
|
browserId: BROWSER_ID,
|
||||||
workspaceId: "workspace-1",
|
workspaceId: "workspace-1",
|
||||||
url: "https://example.com",
|
url: "https://example.com",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parses page info responses with result data under payload", () => {
|
test("responses reject hallucinated browser ids", () => {
|
||||||
const parsed = BrowserAutomationExecuteResponseSchema.parse({
|
const parsed = BrowserAutomationExecuteResponseSchema.safeParse({
|
||||||
type: "browser.automation.execute.response",
|
type: "browser.automation.execute.response",
|
||||||
payload: {
|
payload: {
|
||||||
requestId: "req-1",
|
requestId: "req-page-info",
|
||||||
ok: true,
|
ok: true,
|
||||||
result: {
|
result: {
|
||||||
command: "page_info",
|
command: "page_info",
|
||||||
tab: {
|
tab: {
|
||||||
browserId: "browser-1",
|
browserId: "default",
|
||||||
workspaceId: "workspace-1",
|
workspaceId: "workspace-1",
|
||||||
url: "https://example.com",
|
url: "https://example.com",
|
||||||
title: "Example",
|
title: "Example",
|
||||||
@@ -103,672 +222,9 @@ describe("browser automation execute RPC schemas", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(parsed.payload).toEqual({
|
expect(parsed).toMatchObject({
|
||||||
requestId: "req-1",
|
success: false,
|
||||||
ok: true,
|
error: { issues: [expect.objectContaining({ message: BROWSER_ID_MESSAGE })] },
|
||||||
result: {
|
|
||||||
command: "page_info",
|
|
||||||
tab: {
|
|
||||||
browserId: "browser-1",
|
|
||||||
workspaceId: "workspace-1",
|
|
||||||
url: "https://example.com",
|
|
||||||
title: "Example",
|
|
||||||
isActive: false,
|
|
||||||
isLoading: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parses snapshot requests and ref-bearing snapshot responses", () => {
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-snapshot",
|
|
||||||
workspaceId: "workspace-1",
|
|
||||||
command: {
|
|
||||||
command: "snapshot",
|
|
||||||
args: { workspaceId: "workspace-1", browserId: "browser-1" },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).toEqual({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-snapshot",
|
|
||||||
workspaceId: "workspace-1",
|
|
||||||
command: {
|
|
||||||
command: "snapshot",
|
|
||||||
args: { workspaceId: "workspace-1", browserId: "browser-1" },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const parsed = BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-snapshot",
|
|
||||||
ok: true,
|
|
||||||
result: {
|
|
||||||
command: "snapshot",
|
|
||||||
browserId: "browser-1",
|
|
||||||
workspaceId: "workspace-1",
|
|
||||||
url: "https://example.com/form",
|
|
||||||
title: "Fixture",
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
ref: "@e1",
|
|
||||||
role: "textbox",
|
|
||||||
tagName: "input",
|
|
||||||
text: "Name",
|
|
||||||
selector: "#name",
|
|
||||||
attributes: { id: "name", type: "text" },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(parsed.payload).toEqual({
|
|
||||||
requestId: "req-snapshot",
|
|
||||||
ok: true,
|
|
||||||
result: {
|
|
||||||
command: "snapshot",
|
|
||||||
browserId: "browser-1",
|
|
||||||
workspaceId: "workspace-1",
|
|
||||||
url: "https://example.com/form",
|
|
||||||
title: "Fixture",
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
ref: "@e1",
|
|
||||||
role: "textbox",
|
|
||||||
tagName: "input",
|
|
||||||
text: "Name",
|
|
||||||
selector: "#name",
|
|
||||||
attributes: { id: "name", type: "text" },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parses click and fill ref commands", () => {
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-click",
|
|
||||||
command: {
|
|
||||||
command: "click",
|
|
||||||
args: { workspaceId: "workspace-1", browserId: "browser-1", ref: "@e1" },
|
|
||||||
},
|
|
||||||
}).command,
|
|
||||||
).toEqual({
|
|
||||||
command: "click",
|
|
||||||
args: { workspaceId: "workspace-1", browserId: "browser-1", ref: "@e1" },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-fill",
|
|
||||||
command: {
|
|
||||||
command: "fill",
|
|
||||||
args: { workspaceId: "workspace-1", ref: "@e2", value: "Ada" },
|
|
||||||
},
|
|
||||||
}).command,
|
|
||||||
).toEqual({
|
|
||||||
command: "fill",
|
|
||||||
args: { workspaceId: "workspace-1", ref: "@e2", value: "Ada" },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-fill",
|
|
||||||
ok: true,
|
|
||||||
result: { command: "fill", browserId: "browser-1", ref: "@e2" },
|
|
||||||
},
|
|
||||||
}).payload,
|
|
||||||
).toEqual({
|
|
||||||
requestId: "req-fill",
|
|
||||||
ok: true,
|
|
||||||
result: { command: "fill", browserId: "browser-1", ref: "@e2" },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parses wait text commands and responses", () => {
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-wait",
|
|
||||||
command: {
|
|
||||||
command: "wait",
|
|
||||||
args: { workspaceId: "workspace-1", text: "Ready", timeoutMs: 1000 },
|
|
||||||
},
|
|
||||||
}).command,
|
|
||||||
).toEqual({
|
|
||||||
command: "wait",
|
|
||||||
args: { workspaceId: "workspace-1", text: "Ready", timeoutMs: 1000 },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-wait",
|
|
||||||
ok: true,
|
|
||||||
result: { command: "wait", browserId: "browser-1", matched: "text" },
|
|
||||||
},
|
|
||||||
}).payload,
|
|
||||||
).toEqual({
|
|
||||||
requestId: "req-wait",
|
|
||||||
ok: true,
|
|
||||||
result: { command: "wait", browserId: "browser-1", matched: "text" },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parses set background commands and responses", () => {
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-bg",
|
|
||||||
command: {
|
|
||||||
command: "set_background",
|
|
||||||
args: { workspaceId: "workspace-1", color: "red" },
|
|
||||||
},
|
|
||||||
}).command,
|
|
||||||
).toEqual({
|
|
||||||
command: "set_background",
|
|
||||||
args: { workspaceId: "workspace-1", color: "red" },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-bg",
|
|
||||||
ok: true,
|
|
||||||
result: { command: "set_background", browserId: "browser-1", color: "red" },
|
|
||||||
},
|
|
||||||
}).payload,
|
|
||||||
).toEqual({
|
|
||||||
requestId: "req-bg",
|
|
||||||
ok: true,
|
|
||||||
result: { command: "set_background", browserId: "browser-1", color: "red" },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parses type and keypress commands", () => {
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-type",
|
|
||||||
command: { command: "type", args: { browserId: "browser-1", ref: "@e1", text: "Ada" } },
|
|
||||||
}).command,
|
|
||||||
).toEqual({ command: "type", args: { browserId: "browser-1", ref: "@e1", text: "Ada" } });
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-keypress",
|
|
||||||
command: { command: "keypress", args: { browserId: "browser-1", key: "Enter" } },
|
|
||||||
}).command,
|
|
||||||
).toEqual({ command: "keypress", args: { browserId: "browser-1", key: "Enter" } });
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parses navigation commands", () => {
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-nav",
|
|
||||||
command: {
|
|
||||||
command: "navigate",
|
|
||||||
args: { browserId: "browser-1", url: "https://example.com/next" },
|
|
||||||
},
|
|
||||||
}).command,
|
|
||||||
).toEqual({
|
|
||||||
command: "navigate",
|
|
||||||
args: { browserId: "browser-1", url: "https://example.com/next" },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-nav",
|
|
||||||
ok: true,
|
|
||||||
result: { command: "navigate", browserId: "browser-1", url: "https://example.com/next" },
|
|
||||||
},
|
|
||||||
}).payload,
|
|
||||||
).toEqual({
|
|
||||||
requestId: "req-nav",
|
|
||||||
ok: true,
|
|
||||||
result: { command: "navigate", browserId: "browser-1", url: "https://example.com/next" },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parses screenshot responses", () => {
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-shot",
|
|
||||||
ok: true,
|
|
||||||
result: {
|
|
||||||
command: "screenshot",
|
|
||||||
browserId: "browser-1",
|
|
||||||
mimeType: "image/png",
|
|
||||||
dataBase64: "iVBORw0KGgo=",
|
|
||||||
width: 100,
|
|
||||||
height: 50,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).payload,
|
|
||||||
).toEqual({
|
|
||||||
requestId: "req-shot",
|
|
||||||
ok: true,
|
|
||||||
result: {
|
|
||||||
command: "screenshot",
|
|
||||||
browserId: "browser-1",
|
|
||||||
mimeType: "image/png",
|
|
||||||
dataBase64: "iVBORw0KGgo=",
|
|
||||||
width: 100,
|
|
||||||
height: 50,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-shot-no-frame",
|
|
||||||
ok: false,
|
|
||||||
error: {
|
|
||||||
code: "screenshot_no_frame",
|
|
||||||
message:
|
|
||||||
"The browser tab has no painted frame. Focus the tab in the app, then try again.",
|
|
||||||
retryable: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).payload,
|
|
||||||
).toEqual({
|
|
||||||
requestId: "req-shot-no-frame",
|
|
||||||
ok: false,
|
|
||||||
error: {
|
|
||||||
code: "screenshot_no_frame",
|
|
||||||
message: "The browser tab has no painted frame. Focus the tab in the app, then try again.",
|
|
||||||
retryable: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parses form control commands and responses", () => {
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-focus",
|
|
||||||
command: { command: "focus", args: { browserId: "browser-1", ref: "@e1" } },
|
|
||||||
}).command,
|
|
||||||
).toEqual({ command: "focus", args: { browserId: "browser-1", ref: "@e1" } });
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-clear",
|
|
||||||
command: { command: "clear", args: { browserId: "browser-1", ref: "@e1" } },
|
|
||||||
}).command,
|
|
||||||
).toEqual({ command: "clear", args: { browserId: "browser-1", ref: "@e1" } });
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-check",
|
|
||||||
command: { command: "check", args: { browserId: "browser-1", ref: "@e2" } },
|
|
||||||
}).command,
|
|
||||||
).toEqual({ command: "check", args: { browserId: "browser-1", ref: "@e2", checked: true } });
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-select",
|
|
||||||
command: { command: "select", args: { browserId: "browser-1", ref: "@e3", value: "us" } },
|
|
||||||
}).command,
|
|
||||||
).toEqual({ command: "select", args: { browserId: "browser-1", ref: "@e3", value: "us" } });
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-hover",
|
|
||||||
command: { command: "hover", args: { browserId: "browser-1", ref: "@e4" } },
|
|
||||||
}).command,
|
|
||||||
).toEqual({ command: "hover", args: { browserId: "browser-1", ref: "@e4" } });
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-drag",
|
|
||||||
command: {
|
|
||||||
command: "drag",
|
|
||||||
args: { browserId: "browser-1", sourceRef: "@e4", targetRef: "@e5" },
|
|
||||||
},
|
|
||||||
}).command,
|
|
||||||
).toEqual({
|
|
||||||
command: "drag",
|
|
||||||
args: { browserId: "browser-1", sourceRef: "@e4", targetRef: "@e5" },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-select",
|
|
||||||
ok: true,
|
|
||||||
result: { command: "select", browserId: "browser-1", ref: "@e3", value: "us" },
|
|
||||||
},
|
|
||||||
}).payload,
|
|
||||||
).toEqual({
|
|
||||||
requestId: "req-select",
|
|
||||||
ok: true,
|
|
||||||
result: { command: "select", browserId: "browser-1", ref: "@e3", value: "us" },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parses browser log commands and responses", () => {
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-logs",
|
|
||||||
command: { command: "logs", args: { browserId: "browser-1" } },
|
|
||||||
}).command,
|
|
||||||
).toEqual({ command: "logs", args: { browserId: "browser-1", maxEntries: 50 } });
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-logs",
|
|
||||||
ok: true,
|
|
||||||
result: {
|
|
||||||
command: "logs",
|
|
||||||
browserId: "browser-1",
|
|
||||||
console: [{ level: "info", message: "ready", timestamp: 10 }],
|
|
||||||
network: [
|
|
||||||
{
|
|
||||||
url: "https://example.com/app.js",
|
|
||||||
type: "script",
|
|
||||||
startTime: 1,
|
|
||||||
duration: 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).payload,
|
|
||||||
).toEqual({
|
|
||||||
requestId: "req-logs",
|
|
||||||
ok: true,
|
|
||||||
result: {
|
|
||||||
command: "logs",
|
|
||||||
browserId: "browser-1",
|
|
||||||
console: [{ level: "info", message: "ready", timestamp: 10 }],
|
|
||||||
network: [
|
|
||||||
{
|
|
||||||
url: "https://example.com/app.js",
|
|
||||||
type: "script",
|
|
||||||
startTime: 1,
|
|
||||||
duration: 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parses browser storage commands and responses", () => {
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-storage",
|
|
||||||
command: { command: "storage", args: { browserId: "browser-1" } },
|
|
||||||
}).command,
|
|
||||||
).toEqual({ command: "storage", args: { browserId: "browser-1" } });
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-storage",
|
|
||||||
ok: true,
|
|
||||||
result: {
|
|
||||||
command: "storage",
|
|
||||||
browserId: "browser-1",
|
|
||||||
url: "https://example.com",
|
|
||||||
cookies: [{ name: "theme", value: "dark", domain: "example.com", httpOnly: true }],
|
|
||||||
localStorage: [{ key: "token", value: "abc" }],
|
|
||||||
sessionStorage: [{ key: "tab", value: "1" }],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).payload,
|
|
||||||
).toEqual({
|
|
||||||
requestId: "req-storage",
|
|
||||||
ok: true,
|
|
||||||
result: {
|
|
||||||
command: "storage",
|
|
||||||
browserId: "browser-1",
|
|
||||||
url: "https://example.com",
|
|
||||||
cookies: [{ name: "theme", value: "dark", domain: "example.com", httpOnly: true }],
|
|
||||||
localStorage: [{ key: "token", value: "abc" }],
|
|
||||||
sessionStorage: [{ key: "tab", value: "1" }],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parses browser environment commands and responses", () => {
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-environment",
|
|
||||||
command: {
|
|
||||||
command: "environment",
|
|
||||||
args: {
|
|
||||||
browserId: "browser-1",
|
|
||||||
viewport: { width: 390, height: 844, deviceScaleFactor: 3 },
|
|
||||||
geolocation: { latitude: 37.7749, longitude: -122.4194, accuracy: 5 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).command,
|
|
||||||
).toEqual({
|
|
||||||
command: "environment",
|
|
||||||
args: {
|
|
||||||
browserId: "browser-1",
|
|
||||||
viewport: { width: 390, height: 844, deviceScaleFactor: 3 },
|
|
||||||
geolocation: { latitude: 37.7749, longitude: -122.4194, accuracy: 5 },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-environment",
|
|
||||||
ok: true,
|
|
||||||
result: {
|
|
||||||
command: "environment",
|
|
||||||
browserId: "browser-1",
|
|
||||||
viewport: { width: 390, height: 844, deviceScaleFactor: 3 },
|
|
||||||
geolocation: { latitude: 37.7749, longitude: -122.4194, accuracy: 5 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).payload,
|
|
||||||
).toEqual({
|
|
||||||
requestId: "req-environment",
|
|
||||||
ok: true,
|
|
||||||
result: {
|
|
||||||
command: "environment",
|
|
||||||
browserId: "browser-1",
|
|
||||||
viewport: { width: 390, height: 844, deviceScaleFactor: 3 },
|
|
||||||
geolocation: { latitude: 37.7749, longitude: -122.4194, accuracy: 5 },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parses browser full-page screenshot and PDF commands and responses", () => {
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-full-page",
|
|
||||||
command: { command: "full_page_screenshot", args: { browserId: "browser-1" } },
|
|
||||||
}).command,
|
|
||||||
).toEqual({ command: "full_page_screenshot", args: { browserId: "browser-1" } });
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-pdf",
|
|
||||||
command: { command: "pdf", args: { browserId: "browser-1" } },
|
|
||||||
}).command,
|
|
||||||
).toEqual({ command: "pdf", args: { browserId: "browser-1", printBackground: true } });
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-full-page",
|
|
||||||
ok: true,
|
|
||||||
result: {
|
|
||||||
command: "full_page_screenshot",
|
|
||||||
browserId: "browser-1",
|
|
||||||
mimeType: "image/png",
|
|
||||||
dataBase64: "iVBORw0KGgo=",
|
|
||||||
width: 390,
|
|
||||||
height: 1200,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).payload.result,
|
|
||||||
).toEqual({
|
|
||||||
command: "full_page_screenshot",
|
|
||||||
browserId: "browser-1",
|
|
||||||
mimeType: "image/png",
|
|
||||||
dataBase64: "iVBORw0KGgo=",
|
|
||||||
width: 390,
|
|
||||||
height: 1200,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-pdf",
|
|
||||||
ok: true,
|
|
||||||
result: {
|
|
||||||
command: "pdf",
|
|
||||||
browserId: "browser-1",
|
|
||||||
mimeType: "application/pdf",
|
|
||||||
dataBase64: "JVBERi0xLjQ=",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).payload.result,
|
|
||||||
).toEqual({
|
|
||||||
command: "pdf",
|
|
||||||
browserId: "browser-1",
|
|
||||||
mimeType: "application/pdf",
|
|
||||||
dataBase64: "JVBERi0xLjQ=",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parses browser download and upload commands and responses", () => {
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-download",
|
|
||||||
command: {
|
|
||||||
command: "download",
|
|
||||||
args: { browserId: "browser-1", url: "https://example.com/file.txt" },
|
|
||||||
},
|
|
||||||
}).command,
|
|
||||||
).toEqual({
|
|
||||||
command: "download",
|
|
||||||
args: { browserId: "browser-1", url: "https://example.com/file.txt" },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteRequestSchema.parse({
|
|
||||||
type: "browser.automation.execute.request",
|
|
||||||
requestId: "req-upload",
|
|
||||||
command: {
|
|
||||||
command: "upload",
|
|
||||||
args: { browserId: "browser-1", ref: "@e1", filePaths: ["/tmp/file.txt"] },
|
|
||||||
},
|
|
||||||
}).command,
|
|
||||||
).toEqual({
|
|
||||||
command: "upload",
|
|
||||||
args: { browserId: "browser-1", ref: "@e1", filePaths: ["/tmp/file.txt"] },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-download",
|
|
||||||
ok: true,
|
|
||||||
result: {
|
|
||||||
command: "download",
|
|
||||||
browserId: "browser-1",
|
|
||||||
url: "https://example.com/file.txt",
|
|
||||||
filePath: "/tmp/file.txt",
|
|
||||||
totalBytes: 5,
|
|
||||||
state: "completed",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).payload.result,
|
|
||||||
).toEqual({
|
|
||||||
command: "download",
|
|
||||||
browserId: "browser-1",
|
|
||||||
url: "https://example.com/file.txt",
|
|
||||||
filePath: "/tmp/file.txt",
|
|
||||||
totalBytes: 5,
|
|
||||||
state: "completed",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-upload",
|
|
||||||
ok: true,
|
|
||||||
result: {
|
|
||||||
command: "upload",
|
|
||||||
browserId: "browser-1",
|
|
||||||
ref: "@e1",
|
|
||||||
filePaths: ["/tmp/file.txt"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).payload.result,
|
|
||||||
).toEqual({
|
|
||||||
command: "upload",
|
|
||||||
browserId: "browser-1",
|
|
||||||
ref: "@e1",
|
|
||||||
filePaths: ["/tmp/file.txt"],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("parses stable model-actionable error responses", () => {
|
|
||||||
const parsed = BrowserAutomationExecuteResponseSchema.parse({
|
|
||||||
type: "browser.automation.execute.response",
|
|
||||||
payload: {
|
|
||||||
requestId: "req-1",
|
|
||||||
ok: false,
|
|
||||||
error: {
|
|
||||||
code: "browser_no_desktop",
|
|
||||||
message: "No desktop browser automation client is connected.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(parsed.payload).toEqual({
|
|
||||||
requestId: "req-1",
|
|
||||||
ok: false,
|
|
||||||
error: {
|
|
||||||
code: "browser_no_desktop",
|
|
||||||
message: "No desktop browser automation client is connected.",
|
|
||||||
retryable: false,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { z } from "zod";
|
|||||||
export const BrowserAutomationErrorCodeSchema = z.enum([
|
export const BrowserAutomationErrorCodeSchema = z.enum([
|
||||||
"browser_disabled",
|
"browser_disabled",
|
||||||
"browser_no_desktop",
|
"browser_no_desktop",
|
||||||
"browser_no_tab",
|
|
||||||
"browser_tab_not_found",
|
"browser_tab_not_found",
|
||||||
"browser_tab_closed",
|
"browser_tab_closed",
|
||||||
"browser_timeout",
|
"browser_timeout",
|
||||||
@@ -14,10 +13,23 @@ export const BrowserAutomationErrorCodeSchema = z.enum([
|
|||||||
"browser_unknown_error",
|
"browser_unknown_error",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const BrowserAutomationTabTargetSchema = z.object({
|
const BROWSER_AUTOMATION_BROWSER_ID_PATTERN =
|
||||||
workspaceId: z.string().min(1).optional(),
|
/^(?:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|\d{13,}-[0-9a-f]+)$/i;
|
||||||
browserId: z.string().min(1).optional(),
|
const BROWSER_AUTOMATION_BROWSER_ID_MESSAGE =
|
||||||
});
|
"browserId must be a real id returned by browser_new_tab or browser_list_tabs";
|
||||||
|
const BROWSER_AUTOMATION_WAIT_CONDITION_MESSAGE =
|
||||||
|
"browser_wait requires exactly one of text or url";
|
||||||
|
|
||||||
|
export const BrowserAutomationBrowserIdSchema = z
|
||||||
|
.string({ error: () => BROWSER_AUTOMATION_BROWSER_ID_MESSAGE })
|
||||||
|
.min(1, BROWSER_AUTOMATION_BROWSER_ID_MESSAGE)
|
||||||
|
.regex(BROWSER_AUTOMATION_BROWSER_ID_PATTERN, BROWSER_AUTOMATION_BROWSER_ID_MESSAGE);
|
||||||
|
|
||||||
|
const BrowserAutomationTabTargetSchema = z
|
||||||
|
.object({
|
||||||
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
const BrowserAutomationRefSchema = z.string().regex(/^@e\d+$/);
|
const BrowserAutomationRefSchema = z.string().regex(/^@e\d+$/);
|
||||||
const BrowserAutomationHttpUrlSchema = z
|
const BrowserAutomationHttpUrlSchema = z
|
||||||
@@ -30,31 +42,27 @@ const BrowserAutomationHttpUrlSchema = z
|
|||||||
|
|
||||||
export const BrowserAutomationListTabsCommandSchema = z.object({
|
export const BrowserAutomationListTabsCommandSchema = z.object({
|
||||||
command: z.literal("list_tabs"),
|
command: z.literal("list_tabs"),
|
||||||
args: z
|
args: z.object({}).strict().default({}),
|
||||||
.object({
|
|
||||||
workspaceId: z.string().min(1).optional(),
|
|
||||||
})
|
|
||||||
.default({}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationNewTabCommandSchema = z.object({
|
export const BrowserAutomationNewTabCommandSchema = z.object({
|
||||||
command: z.literal("new_tab"),
|
command: z.literal("new_tab"),
|
||||||
args: z
|
args: z
|
||||||
.object({
|
.object({
|
||||||
workspaceId: z.string().min(1).optional(),
|
|
||||||
url: BrowserAutomationHttpUrlSchema.optional(),
|
url: BrowserAutomationHttpUrlSchema.optional(),
|
||||||
})
|
})
|
||||||
|
.strict()
|
||||||
.default({}),
|
.default({}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationPageInfoCommandSchema = z.object({
|
export const BrowserAutomationPageInfoCommandSchema = z.object({
|
||||||
command: z.literal("page_info"),
|
command: z.literal("page_info"),
|
||||||
args: BrowserAutomationTabTargetSchema.default({}),
|
args: BrowserAutomationTabTargetSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationSnapshotCommandSchema = z.object({
|
export const BrowserAutomationSnapshotCommandSchema = z.object({
|
||||||
command: z.literal("snapshot"),
|
command: z.literal("snapshot"),
|
||||||
args: BrowserAutomationTabTargetSchema.default({}),
|
args: BrowserAutomationTabTargetSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationClickCommandSchema = z.object({
|
export const BrowserAutomationClickCommandSchema = z.object({
|
||||||
@@ -78,6 +86,8 @@ export const BrowserAutomationWaitCommandSchema = z.object({
|
|||||||
text: z.string().min(1).optional(),
|
text: z.string().min(1).optional(),
|
||||||
url: z.string().min(1).optional(),
|
url: z.string().min(1).optional(),
|
||||||
timeoutMs: z.number().int().positive().max(30_000).optional(),
|
timeoutMs: z.number().int().positive().max(30_000).optional(),
|
||||||
|
}).refine((args) => Number(Boolean(args.text)) + Number(Boolean(args.url)) === 1, {
|
||||||
|
message: BROWSER_AUTOMATION_WAIT_CONDITION_MESSAGE,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -106,27 +116,27 @@ export const BrowserAutomationNavigateCommandSchema = z.object({
|
|||||||
|
|
||||||
export const BrowserAutomationBackCommandSchema = z.object({
|
export const BrowserAutomationBackCommandSchema = z.object({
|
||||||
command: z.literal("back"),
|
command: z.literal("back"),
|
||||||
args: BrowserAutomationTabTargetSchema.default({}),
|
args: BrowserAutomationTabTargetSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationForwardCommandSchema = z.object({
|
export const BrowserAutomationForwardCommandSchema = z.object({
|
||||||
command: z.literal("forward"),
|
command: z.literal("forward"),
|
||||||
args: BrowserAutomationTabTargetSchema.default({}),
|
args: BrowserAutomationTabTargetSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationReloadCommandSchema = z.object({
|
export const BrowserAutomationReloadCommandSchema = z.object({
|
||||||
command: z.literal("reload"),
|
command: z.literal("reload"),
|
||||||
args: BrowserAutomationTabTargetSchema.default({}),
|
args: BrowserAutomationTabTargetSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationScreenshotCommandSchema = z.object({
|
export const BrowserAutomationScreenshotCommandSchema = z.object({
|
||||||
command: z.literal("screenshot"),
|
command: z.literal("screenshot"),
|
||||||
args: BrowserAutomationTabTargetSchema.default({}),
|
args: BrowserAutomationTabTargetSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationFullPageScreenshotCommandSchema = z.object({
|
export const BrowserAutomationFullPageScreenshotCommandSchema = z.object({
|
||||||
command: z.literal("full_page_screenshot"),
|
command: z.literal("full_page_screenshot"),
|
||||||
args: BrowserAutomationTabTargetSchema.default({}),
|
args: BrowserAutomationTabTargetSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationPdfCommandSchema = z.object({
|
export const BrowserAutomationPdfCommandSchema = z.object({
|
||||||
@@ -134,7 +144,7 @@ export const BrowserAutomationPdfCommandSchema = z.object({
|
|||||||
args: BrowserAutomationTabTargetSchema.extend({
|
args: BrowserAutomationTabTargetSchema.extend({
|
||||||
landscape: z.boolean().optional(),
|
landscape: z.boolean().optional(),
|
||||||
printBackground: z.boolean().default(true),
|
printBackground: z.boolean().default(true),
|
||||||
}).default({ printBackground: true }),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationDownloadCommandSchema = z.object({
|
export const BrowserAutomationDownloadCommandSchema = z.object({
|
||||||
@@ -202,12 +212,12 @@ export const BrowserAutomationLogsCommandSchema = z.object({
|
|||||||
command: z.literal("logs"),
|
command: z.literal("logs"),
|
||||||
args: BrowserAutomationTabTargetSchema.extend({
|
args: BrowserAutomationTabTargetSchema.extend({
|
||||||
maxEntries: z.number().int().positive().max(200).default(50),
|
maxEntries: z.number().int().positive().max(200).default(50),
|
||||||
}).default({ maxEntries: 50 }),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationStorageCommandSchema = z.object({
|
export const BrowserAutomationStorageCommandSchema = z.object({
|
||||||
command: z.literal("storage"),
|
command: z.literal("storage"),
|
||||||
args: BrowserAutomationTabTargetSchema.default({}),
|
args: BrowserAutomationTabTargetSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
const BrowserAutomationViewportInputSchema = z.object({
|
const BrowserAutomationViewportInputSchema = z.object({
|
||||||
@@ -227,7 +237,7 @@ export const BrowserAutomationEnvironmentCommandSchema = z.object({
|
|||||||
args: BrowserAutomationTabTargetSchema.extend({
|
args: BrowserAutomationTabTargetSchema.extend({
|
||||||
viewport: BrowserAutomationViewportInputSchema.optional(),
|
viewport: BrowserAutomationViewportInputSchema.optional(),
|
||||||
geolocation: BrowserAutomationGeolocationInputSchema.optional(),
|
geolocation: BrowserAutomationGeolocationInputSchema.optional(),
|
||||||
}).default({}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationSetBackgroundCommandSchema = z.object({
|
export const BrowserAutomationSetBackgroundCommandSchema = z.object({
|
||||||
@@ -269,7 +279,7 @@ export const BrowserAutomationCommandSchema = z.discriminatedUnion("command", [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
export const BrowserAutomationTabInfoSchema = z.object({
|
export const BrowserAutomationTabInfoSchema = z.object({
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
workspaceId: z.string().min(1).optional(),
|
workspaceId: z.string().min(1).optional(),
|
||||||
url: z.string(),
|
url: z.string(),
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
@@ -286,7 +296,7 @@ export const BrowserAutomationListTabsResultSchema = z.object({
|
|||||||
|
|
||||||
export const BrowserAutomationNewTabResultSchema = z.object({
|
export const BrowserAutomationNewTabResultSchema = z.object({
|
||||||
command: z.literal("new_tab"),
|
command: z.literal("new_tab"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
workspaceId: z.string().min(1),
|
workspaceId: z.string().min(1),
|
||||||
url: z.string().min(1),
|
url: z.string().min(1),
|
||||||
});
|
});
|
||||||
@@ -307,7 +317,7 @@ export const BrowserAutomationSnapshotElementSchema = z.object({
|
|||||||
|
|
||||||
export const BrowserAutomationSnapshotResultSchema = z.object({
|
export const BrowserAutomationSnapshotResultSchema = z.object({
|
||||||
command: z.literal("snapshot"),
|
command: z.literal("snapshot"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
workspaceId: z.string().min(1).optional(),
|
workspaceId: z.string().min(1).optional(),
|
||||||
url: z.string(),
|
url: z.string(),
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
@@ -316,59 +326,59 @@ export const BrowserAutomationSnapshotResultSchema = z.object({
|
|||||||
|
|
||||||
export const BrowserAutomationClickResultSchema = z.object({
|
export const BrowserAutomationClickResultSchema = z.object({
|
||||||
command: z.literal("click"),
|
command: z.literal("click"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
ref: BrowserAutomationRefSchema,
|
ref: BrowserAutomationRefSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationFillResultSchema = z.object({
|
export const BrowserAutomationFillResultSchema = z.object({
|
||||||
command: z.literal("fill"),
|
command: z.literal("fill"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
ref: BrowserAutomationRefSchema,
|
ref: BrowserAutomationRefSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationWaitResultSchema = z.object({
|
export const BrowserAutomationWaitResultSchema = z.object({
|
||||||
command: z.literal("wait"),
|
command: z.literal("wait"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
matched: z.enum(["text", "url"]),
|
matched: z.enum(["text", "url"]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationTypeResultSchema = z.object({
|
export const BrowserAutomationTypeResultSchema = z.object({
|
||||||
command: z.literal("type"),
|
command: z.literal("type"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
ref: BrowserAutomationRefSchema.optional(),
|
ref: BrowserAutomationRefSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationKeypressResultSchema = z.object({
|
export const BrowserAutomationKeypressResultSchema = z.object({
|
||||||
command: z.literal("keypress"),
|
command: z.literal("keypress"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
key: z.string().min(1),
|
key: z.string().min(1),
|
||||||
ref: BrowserAutomationRefSchema.optional(),
|
ref: BrowserAutomationRefSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationNavigateResultSchema = z.object({
|
export const BrowserAutomationNavigateResultSchema = z.object({
|
||||||
command: z.literal("navigate"),
|
command: z.literal("navigate"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
url: z.string().min(1),
|
url: z.string().min(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationBackResultSchema = z.object({
|
export const BrowserAutomationBackResultSchema = z.object({
|
||||||
command: z.literal("back"),
|
command: z.literal("back"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationForwardResultSchema = z.object({
|
export const BrowserAutomationForwardResultSchema = z.object({
|
||||||
command: z.literal("forward"),
|
command: z.literal("forward"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationReloadResultSchema = z.object({
|
export const BrowserAutomationReloadResultSchema = z.object({
|
||||||
command: z.literal("reload"),
|
command: z.literal("reload"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationScreenshotResultSchema = z.object({
|
export const BrowserAutomationScreenshotResultSchema = z.object({
|
||||||
command: z.literal("screenshot"),
|
command: z.literal("screenshot"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
mimeType: z.literal("image/png"),
|
mimeType: z.literal("image/png"),
|
||||||
dataBase64: z.string().min(1),
|
dataBase64: z.string().min(1),
|
||||||
width: z.number().int().nonnegative(),
|
width: z.number().int().nonnegative(),
|
||||||
@@ -377,7 +387,7 @@ export const BrowserAutomationScreenshotResultSchema = z.object({
|
|||||||
|
|
||||||
export const BrowserAutomationFullPageScreenshotResultSchema = z.object({
|
export const BrowserAutomationFullPageScreenshotResultSchema = z.object({
|
||||||
command: z.literal("full_page_screenshot"),
|
command: z.literal("full_page_screenshot"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
mimeType: z.literal("image/png"),
|
mimeType: z.literal("image/png"),
|
||||||
dataBase64: z.string().min(1),
|
dataBase64: z.string().min(1),
|
||||||
width: z.number().int().nonnegative(),
|
width: z.number().int().nonnegative(),
|
||||||
@@ -386,14 +396,14 @@ export const BrowserAutomationFullPageScreenshotResultSchema = z.object({
|
|||||||
|
|
||||||
export const BrowserAutomationPdfResultSchema = z.object({
|
export const BrowserAutomationPdfResultSchema = z.object({
|
||||||
command: z.literal("pdf"),
|
command: z.literal("pdf"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
mimeType: z.literal("application/pdf"),
|
mimeType: z.literal("application/pdf"),
|
||||||
dataBase64: z.string().min(1),
|
dataBase64: z.string().min(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationDownloadResultSchema = z.object({
|
export const BrowserAutomationDownloadResultSchema = z.object({
|
||||||
command: z.literal("download"),
|
command: z.literal("download"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
url: z.string().min(1),
|
url: z.string().min(1),
|
||||||
filePath: z.string().min(1),
|
filePath: z.string().min(1),
|
||||||
totalBytes: z.number().int().nonnegative().optional(),
|
totalBytes: z.number().int().nonnegative().optional(),
|
||||||
@@ -402,46 +412,46 @@ export const BrowserAutomationDownloadResultSchema = z.object({
|
|||||||
|
|
||||||
export const BrowserAutomationUploadResultSchema = z.object({
|
export const BrowserAutomationUploadResultSchema = z.object({
|
||||||
command: z.literal("upload"),
|
command: z.literal("upload"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
ref: BrowserAutomationRefSchema,
|
ref: BrowserAutomationRefSchema,
|
||||||
filePaths: z.array(z.string().min(1)).min(1),
|
filePaths: z.array(z.string().min(1)).min(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationFocusResultSchema = z.object({
|
export const BrowserAutomationFocusResultSchema = z.object({
|
||||||
command: z.literal("focus"),
|
command: z.literal("focus"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
ref: BrowserAutomationRefSchema,
|
ref: BrowserAutomationRefSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationClearResultSchema = z.object({
|
export const BrowserAutomationClearResultSchema = z.object({
|
||||||
command: z.literal("clear"),
|
command: z.literal("clear"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
ref: BrowserAutomationRefSchema,
|
ref: BrowserAutomationRefSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationCheckResultSchema = z.object({
|
export const BrowserAutomationCheckResultSchema = z.object({
|
||||||
command: z.literal("check"),
|
command: z.literal("check"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
ref: BrowserAutomationRefSchema,
|
ref: BrowserAutomationRefSchema,
|
||||||
checked: z.boolean(),
|
checked: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationSelectResultSchema = z.object({
|
export const BrowserAutomationSelectResultSchema = z.object({
|
||||||
command: z.literal("select"),
|
command: z.literal("select"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
ref: BrowserAutomationRefSchema,
|
ref: BrowserAutomationRefSchema,
|
||||||
value: z.string(),
|
value: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationHoverResultSchema = z.object({
|
export const BrowserAutomationHoverResultSchema = z.object({
|
||||||
command: z.literal("hover"),
|
command: z.literal("hover"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
ref: BrowserAutomationRefSchema,
|
ref: BrowserAutomationRefSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationDragResultSchema = z.object({
|
export const BrowserAutomationDragResultSchema = z.object({
|
||||||
command: z.literal("drag"),
|
command: z.literal("drag"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
sourceRef: BrowserAutomationRefSchema,
|
sourceRef: BrowserAutomationRefSchema,
|
||||||
targetRef: BrowserAutomationRefSchema,
|
targetRef: BrowserAutomationRefSchema,
|
||||||
});
|
});
|
||||||
@@ -466,7 +476,7 @@ export const BrowserAutomationNetworkLogEntrySchema = z.object({
|
|||||||
|
|
||||||
export const BrowserAutomationLogsResultSchema = z.object({
|
export const BrowserAutomationLogsResultSchema = z.object({
|
||||||
command: z.literal("logs"),
|
command: z.literal("logs"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
console: z.array(BrowserAutomationConsoleLogEntrySchema),
|
console: z.array(BrowserAutomationConsoleLogEntrySchema),
|
||||||
network: z.array(BrowserAutomationNetworkLogEntrySchema),
|
network: z.array(BrowserAutomationNetworkLogEntrySchema),
|
||||||
});
|
});
|
||||||
@@ -488,7 +498,7 @@ export const BrowserAutomationStorageEntrySchema = z.object({
|
|||||||
|
|
||||||
export const BrowserAutomationStorageResultSchema = z.object({
|
export const BrowserAutomationStorageResultSchema = z.object({
|
||||||
command: z.literal("storage"),
|
command: z.literal("storage"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
url: z.string(),
|
url: z.string(),
|
||||||
cookies: z.array(BrowserAutomationCookieEntrySchema),
|
cookies: z.array(BrowserAutomationCookieEntrySchema),
|
||||||
localStorage: z.array(BrowserAutomationStorageEntrySchema),
|
localStorage: z.array(BrowserAutomationStorageEntrySchema),
|
||||||
@@ -509,14 +519,14 @@ export const BrowserAutomationGeolocationResultSchema = z.object({
|
|||||||
|
|
||||||
export const BrowserAutomationEnvironmentResultSchema = z.object({
|
export const BrowserAutomationEnvironmentResultSchema = z.object({
|
||||||
command: z.literal("environment"),
|
command: z.literal("environment"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
viewport: BrowserAutomationViewportResultSchema,
|
viewport: BrowserAutomationViewportResultSchema,
|
||||||
geolocation: BrowserAutomationGeolocationResultSchema.optional(),
|
geolocation: BrowserAutomationGeolocationResultSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationSetBackgroundResultSchema = z.object({
|
export const BrowserAutomationSetBackgroundResultSchema = z.object({
|
||||||
command: z.literal("set_background"),
|
command: z.literal("set_background"),
|
||||||
browserId: z.string().min(1),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
color: z.string().min(1),
|
color: z.string().min(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -557,15 +567,16 @@ export const BrowserAutomationErrorSchema = z.object({
|
|||||||
retryable: z.boolean().default(false),
|
retryable: z.boolean().default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BrowserAutomationExecuteRequestSchema = z.object({
|
export const BrowserAutomationExecuteRequestSchema = z
|
||||||
type: z.literal("browser.automation.execute.request"),
|
.object({
|
||||||
requestId: z.string().min(1),
|
type: z.literal("browser.automation.execute.request"),
|
||||||
agentId: z.string().min(1).optional(),
|
requestId: z.string().min(1),
|
||||||
cwd: z.string().min(1).optional(),
|
agentId: z.string().min(1).optional(),
|
||||||
workspaceId: z.string().min(1).optional(),
|
cwd: z.string().min(1).optional(),
|
||||||
browserId: z.string().min(1).optional(),
|
workspaceId: z.string().min(1).optional(),
|
||||||
command: BrowserAutomationCommandSchema,
|
command: BrowserAutomationCommandSchema,
|
||||||
});
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export const BrowserAutomationExecuteResponseSchema = z.object({
|
export const BrowserAutomationExecuteResponseSchema = z.object({
|
||||||
type: z.literal("browser.automation.execute.response"),
|
type: z.literal("browser.automation.execute.response"),
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
} from "./messages.js";
|
} from "./messages.js";
|
||||||
|
|
||||||
describe("browser automation protocol integration", () => {
|
describe("browser automation protocol integration", () => {
|
||||||
|
const browserId = "11111111-1111-4111-8111-111111111111";
|
||||||
|
|
||||||
test("desktop automation capability parses in hello without narrowing old clients", () => {
|
test("desktop automation capability parses in hello without narrowing old clients", () => {
|
||||||
expect(
|
expect(
|
||||||
WSHelloMessageSchema.parse({
|
WSHelloMessageSchema.parse({
|
||||||
@@ -39,7 +41,7 @@ describe("browser automation protocol integration", () => {
|
|||||||
const parsed = SessionOutboundMessageSchema.parse({
|
const parsed = SessionOutboundMessageSchema.parse({
|
||||||
type: "browser.automation.execute.request",
|
type: "browser.automation.execute.request",
|
||||||
requestId: "req-1",
|
requestId: "req-1",
|
||||||
command: { command: "page_info", args: { browserId: "browser-1" } },
|
command: { command: "page_info", args: { browserId } },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(parsed.type).toBe("browser.automation.execute.request");
|
expect(parsed.type).toBe("browser.automation.execute.request");
|
||||||
|
|||||||
@@ -609,8 +609,7 @@ describe("browser MCP tools", () => {
|
|||||||
expect(execute).toHaveBeenCalledWith({
|
expect(execute).toHaveBeenCalledWith({
|
||||||
agentId: "agent-1",
|
agentId: "agent-1",
|
||||||
cwd: REPO_CWD,
|
cwd: REPO_CWD,
|
||||||
workspaceId: REPO_CWD,
|
command: { command: "list_tabs", args: {} },
|
||||||
command: { command: "list_tabs", args: { workspaceId: REPO_CWD } },
|
|
||||||
});
|
});
|
||||||
expect(response.structuredContent).toEqual({
|
expect(response.structuredContent).toEqual({
|
||||||
ok: false,
|
ok: false,
|
||||||
@@ -619,7 +618,7 @@ describe("browser MCP tools", () => {
|
|||||||
message: "Browser tools are disabled.",
|
message: "Browser tools are disabled.",
|
||||||
retryable: false,
|
retryable: false,
|
||||||
},
|
},
|
||||||
context: { agentId: "agent-1", cwd: REPO_CWD, workspaceId: REPO_CWD },
|
context: { agentId: "agent-1", cwd: REPO_CWD },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -646,19 +645,18 @@ describe("browser MCP tools", () => {
|
|||||||
expect(execute).toHaveBeenCalledWith({
|
expect(execute).toHaveBeenCalledWith({
|
||||||
agentId: "agent-1",
|
agentId: "agent-1",
|
||||||
cwd: REPO_CWD,
|
cwd: REPO_CWD,
|
||||||
workspaceId: REPO_CWD,
|
command: { command: "list_tabs", args: {} },
|
||||||
command: { command: "list_tabs", args: { workspaceId: REPO_CWD } },
|
|
||||||
});
|
});
|
||||||
expect(response.content).toEqual([
|
expect(response.content).toEqual([
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "text",
|
||||||
text: "No Paseo browser tabs are open. Call browser_new_tab to create one, then use the returned browserId or omit browserId for the active tab.",
|
text: "No Paseo browser tabs are open. Call browser_new_tab to create one.",
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
expect(response.structuredContent).toEqual({
|
expect(response.structuredContent).toEqual({
|
||||||
ok: true,
|
ok: true,
|
||||||
result: { command: "list_tabs", tabs: [] },
|
result: { command: "list_tabs", tabs: [] },
|
||||||
context: { agentId: "agent-1", cwd: REPO_CWD, workspaceId: REPO_CWD },
|
context: { agentId: "agent-1", cwd: REPO_CWD },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import type {
|
|||||||
import { BrowserToolsBroker, type BrowserToolsDesktopClient } from "./broker.js";
|
import { BrowserToolsBroker, type BrowserToolsDesktopClient } from "./broker.js";
|
||||||
import { StaticBrowserToolsPolicy } from "./policy.js";
|
import { StaticBrowserToolsPolicy } from "./policy.js";
|
||||||
|
|
||||||
|
const BROWSER_ID = "11111111-1111-4111-8111-111111111111";
|
||||||
|
|
||||||
class FakeDesktopClient implements BrowserToolsDesktopClient {
|
class FakeDesktopClient implements BrowserToolsDesktopClient {
|
||||||
public readonly receivedRequests: BrowserAutomationExecuteRequest[] = [];
|
public readonly receivedRequests: BrowserAutomationExecuteRequest[] = [];
|
||||||
|
|
||||||
@@ -44,7 +46,7 @@ function createBroker(options: { enabled: boolean; timeoutMs?: number }): Browse
|
|||||||
}
|
}
|
||||||
|
|
||||||
function pageInfoCommand(): BrowserAutomationCommand {
|
function pageInfoCommand(): BrowserAutomationCommand {
|
||||||
return { command: "page_info", args: { browserId: "browser-1" } };
|
return { command: "page_info", args: { browserId: BROWSER_ID } };
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("BrowserToolsBroker", () => {
|
describe("BrowserToolsBroker", () => {
|
||||||
@@ -111,7 +113,7 @@ describe("BrowserToolsBroker", () => {
|
|||||||
broker.registerClient(client);
|
broker.registerClient(client);
|
||||||
|
|
||||||
const resultPromise = broker.execute({
|
const resultPromise = broker.execute({
|
||||||
command: { command: "list_tabs", args: { workspaceId: "workspace-1" } },
|
command: { command: "list_tabs", args: {} },
|
||||||
workspaceId: "workspace-1",
|
workspaceId: "workspace-1",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -120,7 +122,7 @@ describe("BrowserToolsBroker", () => {
|
|||||||
type: "browser.automation.execute.request",
|
type: "browser.automation.execute.request",
|
||||||
requestId: "req-1",
|
requestId: "req-1",
|
||||||
workspaceId: "workspace-1",
|
workspaceId: "workspace-1",
|
||||||
command: { command: "list_tabs", args: { workspaceId: "workspace-1" } },
|
command: { command: "list_tabs", args: {} },
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
expect(broker.getPendingRequestCount()).toBe(1);
|
expect(broker.getPendingRequestCount()).toBe(1);
|
||||||
@@ -133,7 +135,7 @@ describe("BrowserToolsBroker", () => {
|
|||||||
command: "list_tabs",
|
command: "list_tabs",
|
||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
browserId: "browser-1",
|
browserId: BROWSER_ID,
|
||||||
workspaceId: "workspace-1",
|
workspaceId: "workspace-1",
|
||||||
url: "https://example.com",
|
url: "https://example.com",
|
||||||
title: "Example",
|
title: "Example",
|
||||||
@@ -150,7 +152,7 @@ describe("BrowserToolsBroker", () => {
|
|||||||
command: "list_tabs",
|
command: "list_tabs",
|
||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
browserId: "browser-1",
|
browserId: BROWSER_ID,
|
||||||
workspaceId: "workspace-1",
|
workspaceId: "workspace-1",
|
||||||
url: "https://example.com",
|
url: "https://example.com",
|
||||||
title: "Example",
|
title: "Example",
|
||||||
@@ -169,7 +171,7 @@ describe("BrowserToolsBroker", () => {
|
|||||||
broker.registerClient(client);
|
broker.registerClient(client);
|
||||||
|
|
||||||
const resultPromise = broker.execute({
|
const resultPromise = broker.execute({
|
||||||
command: { command: "snapshot", args: { workspaceId: "workspace-1" } },
|
command: { command: "snapshot", args: { browserId: BROWSER_ID } },
|
||||||
workspaceId: "workspace-1",
|
workspaceId: "workspace-1",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -178,7 +180,7 @@ describe("BrowserToolsBroker", () => {
|
|||||||
type: "browser.automation.execute.request",
|
type: "browser.automation.execute.request",
|
||||||
requestId: "req-1",
|
requestId: "req-1",
|
||||||
workspaceId: "workspace-1",
|
workspaceId: "workspace-1",
|
||||||
command: { command: "snapshot", args: { workspaceId: "workspace-1" } },
|
command: { command: "snapshot", args: { browserId: BROWSER_ID } },
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -187,7 +189,7 @@ describe("BrowserToolsBroker", () => {
|
|||||||
ok: true,
|
ok: true,
|
||||||
result: {
|
result: {
|
||||||
command: "snapshot",
|
command: "snapshot",
|
||||||
browserId: "browser-1",
|
browserId: BROWSER_ID,
|
||||||
workspaceId: "workspace-1",
|
workspaceId: "workspace-1",
|
||||||
url: "https://example.com",
|
url: "https://example.com",
|
||||||
title: "Example",
|
title: "Example",
|
||||||
@@ -200,7 +202,7 @@ describe("BrowserToolsBroker", () => {
|
|||||||
ok: true,
|
ok: true,
|
||||||
result: {
|
result: {
|
||||||
command: "snapshot",
|
command: "snapshot",
|
||||||
browserId: "browser-1",
|
browserId: BROWSER_ID,
|
||||||
workspaceId: "workspace-1",
|
workspaceId: "workspace-1",
|
||||||
url: "https://example.com",
|
url: "https://example.com",
|
||||||
title: "Example",
|
title: "Example",
|
||||||
@@ -281,7 +283,7 @@ describe("BrowserToolsBroker", () => {
|
|||||||
ok: false,
|
ok: false,
|
||||||
error: {
|
error: {
|
||||||
code: "browser_tab_not_found",
|
code: "browser_tab_not_found",
|
||||||
message: "Browser tab browser-1 was not found.",
|
message: `Browser tab ${BROWSER_ID} was not found.`,
|
||||||
retryable: false,
|
retryable: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -291,7 +293,7 @@ describe("BrowserToolsBroker", () => {
|
|||||||
ok: false,
|
ok: false,
|
||||||
error: {
|
error: {
|
||||||
code: "browser_tab_not_found",
|
code: "browser_tab_not_found",
|
||||||
message: "Browser tab browser-1 was not found.",
|
message: `Browser tab ${BROWSER_ID} was not found.`,
|
||||||
retryable: false,
|
retryable: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export interface BrowserToolsExecuteInput {
|
|||||||
agentId?: string;
|
agentId?: string;
|
||||||
cwd?: string;
|
cwd?: string;
|
||||||
workspaceId?: string;
|
workspaceId?: string;
|
||||||
browserId?: string;
|
|
||||||
requestId?: string;
|
requestId?: string;
|
||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
}
|
}
|
||||||
@@ -114,7 +113,6 @@ export class BrowserToolsBroker {
|
|||||||
...(input.agentId ? { agentId: input.agentId } : {}),
|
...(input.agentId ? { agentId: input.agentId } : {}),
|
||||||
...(input.cwd ? { cwd: input.cwd } : {}),
|
...(input.cwd ? { cwd: input.cwd } : {}),
|
||||||
...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
|
...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
|
||||||
...(input.browserId ? { browserId: input.browserId } : {}),
|
|
||||||
command: input.command,
|
command: input.command,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { BrowserAutomationBrowserIdSchema } from "@getpaseo/protocol/browser-automation/rpc-schemas";
|
||||||
import type { BrowserToolsBroker } from "./broker.js";
|
import type { BrowserToolsBroker } from "./broker.js";
|
||||||
import type { BrowserToolsResponsePayload } from "./errors.js";
|
import type { BrowserToolsResponsePayload } from "./errors.js";
|
||||||
import type {
|
import type {
|
||||||
@@ -49,6 +50,16 @@ const BrowserToolOutputSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const BrowserRefInputSchema = z.string().regex(/^@e\d+$/);
|
const BrowserRefInputSchema = z.string().regex(/^@e\d+$/);
|
||||||
|
const BrowserWaitInputSchema = z
|
||||||
|
.object({
|
||||||
|
text: z.string().min(1).optional(),
|
||||||
|
url: z.string().min(1).optional(),
|
||||||
|
timeoutMs: z.number().int().positive().max(30_000).optional(),
|
||||||
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
|
})
|
||||||
|
.refine((input) => Number(Boolean(input.text)) + Number(Boolean(input.url)) === 1, {
|
||||||
|
message: "browser_wait requires exactly one of text or url",
|
||||||
|
});
|
||||||
|
|
||||||
export function registerBrowserTools(options: RegisterBrowserToolsOptions): void {
|
export function registerBrowserTools(options: RegisterBrowserToolsOptions): void {
|
||||||
options.registerTool(
|
options.registerTool(
|
||||||
@@ -56,7 +67,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
{
|
{
|
||||||
title: "List browser tabs",
|
title: "List browser tabs",
|
||||||
description:
|
description:
|
||||||
"List open Paseo desktop browser tabs for this agent's workspace context. Use the returned browserId values exactly; do not use 'default'. If a tool omits browserId, it targets the active workspace tab.",
|
"List open Paseo desktop browser tabs for this agent's workspace context. Use the returned browserId values for tab-scoped browser tools.",
|
||||||
inputSchema: {},
|
inputSchema: {},
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
@@ -68,7 +79,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
command: {
|
command: {
|
||||||
command: "list_tabs",
|
command: "list_tabs",
|
||||||
args: context.workspaceId ? { workspaceId: context.workspaceId } : {},
|
args: {},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return browserToolResult({ payload, context });
|
return browserToolResult({ payload, context });
|
||||||
@@ -80,7 +91,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
{
|
{
|
||||||
title: "Create browser tab",
|
title: "Create browser tab",
|
||||||
description:
|
description:
|
||||||
"Create and focus a new Paseo desktop browser tab in this agent's workspace. Optionally pass an http(s) URL to open immediately. Returns the new browserId; use that exact ID for later calls or omit browserId to use the active tab.",
|
"Create and focus a new Paseo desktop browser tab in this agent's workspace. Optionally pass an http(s) URL to open immediately. Returns the browserId for later tab-scoped browser tools.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
url: z.string().url().optional(),
|
url: z.string().url().optional(),
|
||||||
},
|
},
|
||||||
@@ -94,10 +105,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
command: {
|
command: {
|
||||||
command: "new_tab",
|
command: "new_tab",
|
||||||
args: {
|
args: url ? { url } : {},
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
|
||||||
...(url ? { url } : {}),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return browserToolResult({ payload, context });
|
return browserToolResult({ payload, context });
|
||||||
@@ -109,9 +117,9 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
{
|
{
|
||||||
title: "Get browser page info",
|
title: "Get browser page info",
|
||||||
description:
|
description:
|
||||||
"Get page info for a Paseo desktop browser tab. Omit browserId to use the active workspace tab. If you pass browserId, use a real ID returned by browser_list_tabs or browser_new_tab; never pass 'default'.",
|
"Get page info for a Paseo desktop browser tab. Pass browserId from browser_new_tab or browser_list_tabs.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
browserId: z.string().min(1).optional(),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
@@ -121,12 +129,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "page_info",
|
command: "page_info",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -139,9 +146,9 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
{
|
{
|
||||||
title: "Snapshot browser page",
|
title: "Snapshot browser page",
|
||||||
description:
|
description:
|
||||||
"Return a model-readable snapshot of the active Paseo desktop browser tab for this agent's workspace. Snapshot refs like @e1 are valid until the page changes or a new snapshot is taken.",
|
"Return a model-readable snapshot of a Paseo desktop browser tab. Pass browserId from browser_new_tab or browser_list_tabs. Snapshot refs like @e1 are valid until the page changes or a new snapshot is taken.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
browserId: z.string().min(1).optional(),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
@@ -151,12 +158,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "snapshot",
|
command: "snapshot",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -172,7 +178,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
"Click an element ref from the latest browser_snapshot for this agent's workspace browser tab.",
|
"Click an element ref from the latest browser_snapshot for this agent's workspace browser tab.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
ref: BrowserRefInputSchema,
|
ref: BrowserRefInputSchema,
|
||||||
browserId: z.string().min(1).optional(),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
@@ -182,12 +188,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "click",
|
command: "click",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
ref,
|
ref,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -205,7 +210,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
inputSchema: {
|
inputSchema: {
|
||||||
ref: BrowserRefInputSchema,
|
ref: BrowserRefInputSchema,
|
||||||
value: z.string(),
|
value: z.string(),
|
||||||
browserId: z.string().min(1).optional(),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
@@ -215,12 +220,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "fill",
|
command: "fill",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
ref,
|
ref,
|
||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
@@ -235,13 +239,8 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
{
|
{
|
||||||
title: "Wait for browser condition",
|
title: "Wait for browser condition",
|
||||||
description:
|
description:
|
||||||
"Wait until the active Paseo desktop browser tab contains text or reaches a URL fragment.",
|
"Wait until a Paseo desktop browser tab contains text or reaches a URL fragment. Pass browserId from browser_new_tab or browser_list_tabs.",
|
||||||
inputSchema: {
|
inputSchema: BrowserWaitInputSchema,
|
||||||
text: z.string().min(1).optional(),
|
|
||||||
url: z.string().min(1).optional(),
|
|
||||||
timeoutMs: z.number().int().positive().max(30_000).optional(),
|
|
||||||
browserId: z.string().min(1).optional(),
|
|
||||||
},
|
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ text, url, timeoutMs, browserId }) => {
|
async ({ text, url, timeoutMs, browserId }) => {
|
||||||
@@ -250,13 +249,12 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
...(timeoutMs ? { timeoutMs: timeoutMs + 1_000 } : {}),
|
...(timeoutMs ? { timeoutMs: timeoutMs + 1_000 } : {}),
|
||||||
command: {
|
command: {
|
||||||
command: "wait",
|
command: "wait",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
...(text ? { text } : {}),
|
...(text ? { text } : {}),
|
||||||
...(url ? { url } : {}),
|
...(url ? { url } : {}),
|
||||||
...(timeoutMs ? { timeoutMs } : {}),
|
...(timeoutMs ? { timeoutMs } : {}),
|
||||||
@@ -276,7 +274,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
inputSchema: {
|
inputSchema: {
|
||||||
text: z.string(),
|
text: z.string(),
|
||||||
ref: BrowserRefInputSchema.optional(),
|
ref: BrowserRefInputSchema.optional(),
|
||||||
browserId: z.string().min(1).optional(),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
@@ -286,12 +284,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "type",
|
command: "type",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
...(ref ? { ref } : {}),
|
...(ref ? { ref } : {}),
|
||||||
text,
|
text,
|
||||||
},
|
},
|
||||||
@@ -310,7 +307,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
inputSchema: {
|
inputSchema: {
|
||||||
key: z.string().min(1),
|
key: z.string().min(1),
|
||||||
ref: BrowserRefInputSchema.optional(),
|
ref: BrowserRefInputSchema.optional(),
|
||||||
browserId: z.string().min(1).optional(),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
@@ -320,12 +317,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "keypress",
|
command: "keypress",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
...(ref ? { ref } : {}),
|
...(ref ? { ref } : {}),
|
||||||
key,
|
key,
|
||||||
},
|
},
|
||||||
@@ -339,8 +335,9 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
"browser_navigate",
|
"browser_navigate",
|
||||||
{
|
{
|
||||||
title: "Navigate browser",
|
title: "Navigate browser",
|
||||||
description: "Navigate the active Paseo desktop browser tab to a URL.",
|
description:
|
||||||
inputSchema: { url: z.string().min(1), browserId: z.string().min(1).optional() },
|
"Navigate a Paseo desktop browser tab to a URL. Pass browserId from browser_new_tab or browser_list_tabs.",
|
||||||
|
inputSchema: { url: z.string().min(1), browserId: BrowserAutomationBrowserIdSchema },
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ url, browserId }) => {
|
async ({ url, browserId }) => {
|
||||||
@@ -349,12 +346,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "navigate",
|
command: "navigate",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
url,
|
url,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -369,8 +365,8 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
name,
|
name,
|
||||||
{
|
{
|
||||||
title: `Browser ${command}`,
|
title: `Browser ${command}`,
|
||||||
description: `${command} the active Paseo desktop browser tab.`,
|
description: `${command} a Paseo desktop browser tab. Pass browserId from browser_new_tab or browser_list_tabs.`,
|
||||||
inputSchema: { browserId: z.string().min(1).optional() },
|
inputSchema: { browserId: BrowserAutomationBrowserIdSchema },
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ browserId }) => {
|
async ({ browserId }) => {
|
||||||
@@ -379,12 +375,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command,
|
command,
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -397,8 +392,9 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
"browser_screenshot",
|
"browser_screenshot",
|
||||||
{
|
{
|
||||||
title: "Capture browser screenshot",
|
title: "Capture browser screenshot",
|
||||||
description: "Capture a PNG screenshot of the active Paseo desktop browser tab.",
|
description:
|
||||||
inputSchema: { browserId: z.string().min(1).optional() },
|
"Capture a PNG screenshot of a Paseo desktop browser tab. Pass browserId from browser_new_tab or browser_list_tabs.",
|
||||||
|
inputSchema: { browserId: BrowserAutomationBrowserIdSchema },
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ browserId }) => {
|
async ({ browserId }) => {
|
||||||
@@ -407,12 +403,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "screenshot",
|
command: "screenshot",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -425,7 +420,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
{
|
{
|
||||||
title: "Capture full-page browser screenshot",
|
title: "Capture full-page browser screenshot",
|
||||||
description: "Capture a full-page PNG screenshot of a Paseo desktop browser tab.",
|
description: "Capture a full-page PNG screenshot of a Paseo desktop browser tab.",
|
||||||
inputSchema: { browserId: z.string().min(1).optional() },
|
inputSchema: { browserId: BrowserAutomationBrowserIdSchema },
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ browserId }) => {
|
async ({ browserId }) => {
|
||||||
@@ -434,12 +429,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "full_page_screenshot",
|
command: "full_page_screenshot",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -451,9 +445,10 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
"browser_pdf",
|
"browser_pdf",
|
||||||
{
|
{
|
||||||
title: "Export browser page PDF",
|
title: "Export browser page PDF",
|
||||||
description: "Export the current Paseo desktop browser tab as a PDF.",
|
description:
|
||||||
|
"Export a Paseo desktop browser tab as a PDF. Pass browserId from browser_new_tab or browser_list_tabs.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
browserId: z.string().min(1).optional(),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
landscape: z.boolean().optional(),
|
landscape: z.boolean().optional(),
|
||||||
printBackground: z.boolean().default(true),
|
printBackground: z.boolean().default(true),
|
||||||
},
|
},
|
||||||
@@ -465,12 +460,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "pdf",
|
command: "pdf",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
...(landscape !== undefined ? { landscape } : {}),
|
...(landscape !== undefined ? { landscape } : {}),
|
||||||
printBackground: printBackground ?? true,
|
printBackground: printBackground ?? true,
|
||||||
},
|
},
|
||||||
@@ -488,7 +482,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
inputSchema: {
|
inputSchema: {
|
||||||
url: z.string().min(1),
|
url: z.string().min(1),
|
||||||
fileName: z.string().min(1).optional(),
|
fileName: z.string().min(1).optional(),
|
||||||
browserId: z.string().min(1).optional(),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
@@ -498,12 +492,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "download",
|
command: "download",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
url,
|
url,
|
||||||
...(fileName ? { fileName } : {}),
|
...(fileName ? { fileName } : {}),
|
||||||
},
|
},
|
||||||
@@ -522,7 +515,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
inputSchema: {
|
inputSchema: {
|
||||||
ref: BrowserRefInputSchema,
|
ref: BrowserRefInputSchema,
|
||||||
filePaths: z.array(z.string().min(1)).min(1),
|
filePaths: z.array(z.string().min(1)).min(1),
|
||||||
browserId: z.string().min(1).optional(),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
@@ -532,12 +525,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "upload",
|
command: "upload",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
ref,
|
ref,
|
||||||
filePaths,
|
filePaths,
|
||||||
},
|
},
|
||||||
@@ -572,7 +564,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
{
|
{
|
||||||
title: toolConfig.title,
|
title: toolConfig.title,
|
||||||
description: toolConfig.description,
|
description: toolConfig.description,
|
||||||
inputSchema: { ref: BrowserRefInputSchema, browserId: z.string().min(1).optional() },
|
inputSchema: { ref: BrowserRefInputSchema, browserId: BrowserAutomationBrowserIdSchema },
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ ref, browserId }) => {
|
async ({ ref, browserId }) => {
|
||||||
@@ -581,12 +573,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: toolConfig.command,
|
command: toolConfig.command,
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
ref,
|
ref,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -604,7 +595,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
inputSchema: {
|
inputSchema: {
|
||||||
ref: BrowserRefInputSchema,
|
ref: BrowserRefInputSchema,
|
||||||
checked: z.boolean().default(true),
|
checked: z.boolean().default(true),
|
||||||
browserId: z.string().min(1).optional(),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
@@ -614,12 +605,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "check",
|
command: "check",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
ref,
|
ref,
|
||||||
checked,
|
checked,
|
||||||
},
|
},
|
||||||
@@ -637,7 +627,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
inputSchema: {
|
inputSchema: {
|
||||||
ref: BrowserRefInputSchema,
|
ref: BrowserRefInputSchema,
|
||||||
value: z.string(),
|
value: z.string(),
|
||||||
browserId: z.string().min(1).optional(),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
@@ -647,12 +637,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "select",
|
command: "select",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
ref,
|
ref,
|
||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
@@ -671,7 +660,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
inputSchema: {
|
inputSchema: {
|
||||||
sourceRef: BrowserRefInputSchema,
|
sourceRef: BrowserRefInputSchema,
|
||||||
targetRef: BrowserRefInputSchema,
|
targetRef: BrowserRefInputSchema,
|
||||||
browserId: z.string().min(1).optional(),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
@@ -681,12 +670,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "drag",
|
command: "drag",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
sourceRef,
|
sourceRef,
|
||||||
targetRef,
|
targetRef,
|
||||||
},
|
},
|
||||||
@@ -704,7 +692,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
"Read recent console messages and browser performance network entries for a Paseo desktop browser tab.",
|
"Read recent console messages and browser performance network entries for a Paseo desktop browser tab.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
maxEntries: z.number().int().positive().max(200).optional(),
|
maxEntries: z.number().int().positive().max(200).optional(),
|
||||||
browserId: z.string().min(1).optional(),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
@@ -714,12 +702,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "logs",
|
command: "logs",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
maxEntries: maxEntries ?? 50,
|
maxEntries: maxEntries ?? 50,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -734,7 +721,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
title: "Read browser storage",
|
title: "Read browser storage",
|
||||||
description:
|
description:
|
||||||
"Read cookies plus localStorage and sessionStorage for a Paseo desktop browser tab.",
|
"Read cookies plus localStorage and sessionStorage for a Paseo desktop browser tab.",
|
||||||
inputSchema: { browserId: z.string().min(1).optional() },
|
inputSchema: { browserId: BrowserAutomationBrowserIdSchema },
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
async ({ browserId }) => {
|
async ({ browserId }) => {
|
||||||
@@ -743,12 +730,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "storage",
|
command: "storage",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -776,7 +762,7 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
accuracy: z.number().positive().optional(),
|
accuracy: z.number().positive().optional(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
browserId: z.string().min(1).optional(),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
@@ -786,12 +772,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "environment",
|
command: "environment",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
...(viewport ? { viewport } : {}),
|
...(viewport ? { viewport } : {}),
|
||||||
...(geolocation ? { geolocation } : {}),
|
...(geolocation ? { geolocation } : {}),
|
||||||
},
|
},
|
||||||
@@ -806,10 +791,10 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
{
|
{
|
||||||
title: "Set browser background",
|
title: "Set browser background",
|
||||||
description:
|
description:
|
||||||
"Set the current page background color in the active Paseo desktop browser tab. Omit browserId to use the active workspace tab; pass a returned browserId only when targeting a specific tab.",
|
"Set the current page background color in a Paseo desktop browser tab. Pass browserId from browser_new_tab or browser_list_tabs.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
color: z.string().min(1),
|
color: z.string().min(1),
|
||||||
browserId: z.string().min(1).optional(),
|
browserId: BrowserAutomationBrowserIdSchema,
|
||||||
},
|
},
|
||||||
outputSchema: BrowserToolOutputSchema,
|
outputSchema: BrowserToolOutputSchema,
|
||||||
},
|
},
|
||||||
@@ -819,12 +804,11 @@ export function registerBrowserTools(options: RegisterBrowserToolsOptions): void
|
|||||||
agentId: context.agentId,
|
agentId: context.agentId,
|
||||||
cwd: context.cwd,
|
cwd: context.cwd,
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
command: {
|
command: {
|
||||||
command: "set_background",
|
command: "set_background",
|
||||||
args: {
|
args: {
|
||||||
...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
|
browserId,
|
||||||
...(browserId ? { browserId } : {}),
|
|
||||||
color,
|
color,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -941,20 +925,20 @@ function summarizeBrowserSuccess(
|
|||||||
if (payload.result.command === "list_tabs") {
|
if (payload.result.command === "list_tabs") {
|
||||||
const count = payload.result.tabs.length;
|
const count = payload.result.tabs.length;
|
||||||
if (count === 0) {
|
if (count === 0) {
|
||||||
return "No Paseo browser tabs are open. Call browser_new_tab to create one, then use the returned browserId or omit browserId for the active tab.";
|
return "No Paseo browser tabs are open. Call browser_new_tab to create one.";
|
||||||
}
|
}
|
||||||
const tabLines = payload.result.tabs.map((tab) => {
|
const tabLines = payload.result.tabs.map((tab) => {
|
||||||
const active = tab.isActive ? " active" : "";
|
const active = tab.isActive ? " active" : "";
|
||||||
return `- browserId=${tab.browserId}${active} title=${JSON.stringify(tab.title || "Untitled")} url=${tab.url}`;
|
return `- browserId=${tab.browserId}${active} title=${JSON.stringify(tab.title || "Untitled")} url=${tab.url}`;
|
||||||
});
|
});
|
||||||
return [
|
return [
|
||||||
`Found ${count} Paseo browser tab${count === 1 ? "" : "s"}. Use these browserId values exactly; do not use 'default'. You may omit browserId to use the active tab.`,
|
`Found ${count} Paseo browser tab${count === 1 ? "" : "s"}. Use these browserId values for tab-scoped browser tools.`,
|
||||||
...tabLines,
|
...tabLines,
|
||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.result.command === "new_tab") {
|
if (payload.result.command === "new_tab") {
|
||||||
return `Created browser tab browserId=${payload.result.browserId} url=${payload.result.url}. Use this browserId exactly, or omit browserId to use this active tab.`;
|
return `Created browser tab browserId=${payload.result.browserId} url=${payload.result.url}. Use this browserId for tab-scoped browser tools.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.result.command === "snapshot") {
|
if (payload.result.command === "snapshot") {
|
||||||
@@ -1112,8 +1096,6 @@ function summarizeBrowserError(
|
|||||||
return "Browser tools are disabled. Enable desktop browser tools on the host, then try again.";
|
return "Browser tools are disabled. Enable desktop browser tools on the host, then try again.";
|
||||||
case "browser_no_desktop":
|
case "browser_no_desktop":
|
||||||
return "No desktop browser automation client is connected. Open the Paseo desktop app and try again.";
|
return "No desktop browser automation client is connected. Open the Paseo desktop app and try again.";
|
||||||
case "browser_no_tab":
|
|
||||||
return "No active browser tab is available. Call browser_new_tab to create one, then omit browserId for the active tab or use the returned browserId.";
|
|
||||||
case "browser_timeout":
|
case "browser_timeout":
|
||||||
return "The browser did not respond before the timeout. Try again or check the desktop app.";
|
return "The browser did not respond before the timeout. Try again or check the desktop app.";
|
||||||
case "screenshot_no_frame":
|
case "screenshot_no_frame":
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ interface QueuedBrowserRequests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const harnesses: BrowserToolsDaemonHarness[] = [];
|
const harnesses: BrowserToolsDaemonHarness[] = [];
|
||||||
|
const BROWSER_ID = "11111111-1111-4111-8111-111111111111";
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await Promise.all(harnesses.splice(0).map((harness) => harness.stop()));
|
await Promise.all(harnesses.splice(0).map((harness) => harness.stop()));
|
||||||
@@ -131,7 +132,7 @@ describe("WebSocketServer browser tools wiring", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const resultPromise = harness.broker.execute({
|
const resultPromise = harness.broker.execute({
|
||||||
command: { command: "click", args: { ref: "@e1" } },
|
command: { command: "click", args: { browserId: BROWSER_ID, ref: "@e1" } },
|
||||||
});
|
});
|
||||||
const request = await resumedDesktop.nextBrowserRequest();
|
const request = await resumedDesktop.nextBrowserRequest();
|
||||||
resumedDesktop.respondToBrowserRequest({
|
resumedDesktop.respondToBrowserRequest({
|
||||||
@@ -139,13 +140,13 @@ describe("WebSocketServer browser tools wiring", () => {
|
|||||||
payload: {
|
payload: {
|
||||||
requestId: request.requestId,
|
requestId: request.requestId,
|
||||||
ok: true,
|
ok: true,
|
||||||
result: { command: "click", browserId: "browser-1", ref: "@e1" },
|
result: { command: "click", browserId: BROWSER_ID, ref: "@e1" },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(resultPromise).resolves.toMatchObject({
|
await expect(resultPromise).resolves.toMatchObject({
|
||||||
ok: true,
|
ok: true,
|
||||||
result: { command: "click", browserId: "browser-1", ref: "@e1" },
|
result: { command: "click", browserId: BROWSER_ID, ref: "@e1" },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user