Generalize browser automation hosting beyond the desktop app (#1878)

* feat(browser): generalize browser automation hosts

Rename the browser automation client capability to a host-neutral payload, advertise supported commands, and route tab-scoped commands by browserId affinity so future browser hosts can register without another protocol shape change.

* fix(browser): harden browser host registration

Reject unusable browser host capabilities, clean up pending requests when a host registration is replaced, and defer list_tabs affinity updates until every host in the aggregation succeeds.

* fix(browser): preserve host tab affinity across reconnects

Keep stranded tab ownership tied to the disconnected host so the same host can reclaim open tabs after reconnect, and tolerate future command names in browser_host capability payloads while retaining only known commands.
This commit is contained in:
Mohamed Boudra
2026-07-04 01:46:04 +08:00
committed by GitHub
parent f226222638
commit b28f291493
18 changed files with 1087 additions and 134 deletions
@@ -396,7 +396,7 @@ describe("mountBrowserAutomationHandler", () => {
ok: false,
error: {
code: "browser_unsupported",
message: "Desktop browser automation is not available in this app runtime.",
message: "Browser automation is not available in this app runtime.",
retryable: false,
},
},
@@ -450,7 +450,7 @@ describe("mountBrowserAutomationHandler", () => {
ok: false,
error: {
code: "browser_unsupported",
message: "Desktop browser automation is not implemented by this desktop build yet.",
message: "Browser automation is not implemented by this app build yet.",
retryable: false,
},
},
@@ -120,7 +120,7 @@ async function handleBrowserAutomationRequest(params: {
payload: browserAutomationFailure({
requestId: request.requestId,
code: "browser_unsupported",
message: "Desktop browser automation is not available in this app runtime.",
message: "Browser automation is not available in this app runtime.",
}),
});
return;
@@ -202,7 +202,7 @@ async function openBrowserTabForRequest(params: {
return browserAutomationFailure({
requestId: request.requestId,
code: "browser_timeout",
message: `Timed out waiting for browser tab ${browserId} to register with desktop automation. Try browser_new_tab again.`,
message: `Timed out waiting for browser tab ${browserId} to register with the browser automation host. Try browser_new_tab again.`,
retryable: true,
});
}
@@ -270,14 +270,14 @@ function normalizeThrownBridgeError(
return browserAutomationFailure({
requestId,
code: "browser_unsupported",
message: "Desktop browser automation is not implemented by this desktop build yet.",
message: "Browser automation is not implemented by this app build yet.",
});
}
return browserAutomationFailure({
requestId,
code: "browser_unknown_error",
message: message || "Desktop browser automation failed.",
message: message || "Browser automation failed.",
});
}
+9 -3
View File
@@ -39,6 +39,7 @@ import {
} from "@/desktop/daemon/desktop-daemon-transport";
import { getDesktopHost } from "@/desktop/host";
import { CLIENT_CAPS } from "@getpaseo/protocol/client-capabilities";
import { BROWSER_AUTOMATION_COMMAND_NAMES } from "@getpaseo/protocol/browser-automation/rpc-schemas";
import { replaceFetchedAgentDirectory } from "@/utils/agent-directory-sync";
import { useSessionStore } from "@/stores/session-store";
import {
@@ -522,10 +523,15 @@ function probeIntervalForConnection(
}
function createDefaultDeps(): HostRuntimeControllerDeps {
const desktopBrowserAutomationAvailable =
const browserHostAvailable =
typeof getDesktopHost()?.browser?.executeAutomationCommand === "function";
const browserAutomationCapabilities = desktopBrowserAutomationAvailable
? { [CLIENT_CAPS.desktopBrowserAutomation]: true }
const browserAutomationCapabilities = browserHostAvailable
? {
[CLIENT_CAPS.browserHost]: {
supportedCommands: [...BROWSER_AUTOMATION_COMMAND_NAMES],
hostKind: "desktop app",
},
}
: undefined;
return {
@@ -2,7 +2,7 @@ import type { MutableDaemonConfig } from "@getpaseo/protocol/messages";
export const BROWSER_TOOLS_TITLE = "Browser tools";
export const BROWSER_TOOLS_WARNING =
"Allow agents to access and control Paseo desktop browser tabs, including logged-in browser state. Only enable this for agents you trust.";
"Allow agents to access and control Paseo browser tabs, including logged-in browser state. Only enable this for agents you trust.";
export interface BrowserToolsCardState {
isVisible: boolean;