diff --git a/docs/guides/a0-cli-connector.md b/docs/guides/a0-cli-connector.md index ee83105bc..f0812efc8 100644 --- a/docs/guides/a0-cli-connector.md +++ b/docs/guides/a0-cli-connector.md @@ -162,8 +162,9 @@ control starts when Agent Zero actually needs to use the browser. > control. The **Host browser** list in Browser settings comes from the connected local A0 -CLI, not from the Agent Zero Web UI server. If a newly authorized browser does -not appear, restart or reconnect A0 CLI. +CLI, not from the Agent Zero Web UI server. It shows Automatic, currently +advertised debug endpoints, and **Custom endpoint**. If a newly authorized +browser does not appear, restart or reconnect A0 CLI. If the inspect checkbox is not enough for your browser build, launch it with an explicit remote debugging port and a separate profile: @@ -172,7 +173,8 @@ explicit remote debugging port and a separate profile: opera --remote-debugging-port=9222 --user-data-dir="$HOME/.config/a0-opera-debug" ``` -Then pass the full DevTools websocket endpoint to A0 CLI: +Then choose **Custom endpoint** in Browser settings, run `/browser ws://...` in +A0 CLI, or pass the full DevTools websocket endpoint to A0 CLI: ```bash export A0_HOST_BROWSER_REMOTE_DEBUGGING_ENDPOINTS="ws://127.0.0.1:9222/devtools/browser/..." diff --git a/docs/guides/browser.md b/docs/guides/browser.md index bc9255c1b..33d1b719f 100644 --- a/docs/guides/browser.md +++ b/docs/guides/browser.md @@ -156,7 +156,8 @@ Remote debugging pages: | Chrome, Edge, Brave, Vivaldi, Chromium | `chrome://inspect/#remote-debugging` | | Opera | `opera://inspect/#remote-debugging` | -If a browser does not appear in the **Host browser** list after enabling remote +The **Host browser** list shows Automatic, currently advertised debug endpoints, +and **Custom endpoint**. If a browser does not appear after enabling remote debugging, restart or reconnect the local A0 CLI. Restarting only the Agent Zero Web UI server does not refresh the browser inventory; the list comes from the connected CLI. @@ -168,7 +169,8 @@ directory: opera --remote-debugging-port=9222 --user-data-dir="$HOME/.config/a0-opera-debug" ``` -Then pass the full DevTools websocket endpoint to the CLI: +Then choose **Custom endpoint** in Browser settings, or pass the full DevTools +websocket endpoint to the CLI: ```bash export A0_HOST_BROWSER_REMOTE_DEBUGGING_ENDPOINTS="ws://127.0.0.1:9222/devtools/browser/..." diff --git a/plugins/_browser/AGENTS.md b/plugins/_browser/AGENTS.md index c8f8a8c09..a0e570a9d 100644 --- a/plugins/_browser/AGENTS.md +++ b/plugins/_browser/AGENTS.md @@ -26,6 +26,7 @@ - Keep narrow WebUI Browser controls usable by grouping navigation with Annotate/settings above a full-width address bar. - For Bring Your Own Browser with an existing host profile, `host_browser_selection` may target automatic CLI selection, a browser family/id, or an explicit CDP endpoint and must be forwarded to the connector runtime as `browser_selection`. - Browser Settings must refresh connected A0 CLI host-browser inventory while the settings view is open so newly authorized endpoints appear without saving or reopening. +- Browser Settings keeps the Host browser dropdown focused on automatic selection, advertised debug endpoints, and a validated Custom endpoint field instead of listing every installed local profile. - Browser URL-intent handling must only claim web URL schemes and leave custom Agent Zero schemes to their owning surfaces. - Prefer DOM/CDP browser actions with refs, selectors, frame-chain refs, and screenshots over viewport coordinate input. Coordinates remain a visual fallback. - Do not hardcode user-specific browser paths or secrets. diff --git a/plugins/_browser/webui/browser-config-store.js b/plugins/_browser/webui/browser-config-store.js index 1f385430b..4f32fe0ac 100644 --- a/plugins/_browser/webui/browser-config-store.js +++ b/plugins/_browser/webui/browser-config-store.js @@ -10,7 +10,8 @@ const HOST_PROFILE_MODES = new Set(["existing", "agent"]); const DEFAULT_MAX_OPEN_TABS = 32; const MIN_MAX_OPEN_TABS = 1; const HARD_MAX_OPEN_TABS = 50; -const HOST_BROWSER_STATUS_REFRESH_MS = 3000; +const HOST_BROWSER_STATUS_REFRESH_MS = 1000; +const CUSTOM_HOST_BROWSER_SELECTION = "__custom_endpoint__"; function normalizePathList(value) { const source = Array.isArray(value) @@ -72,6 +73,39 @@ function normalizeHostBrowserSelection(value) { return String(value || "").trim().toLowerCase().replace(/\s+/g, "_").slice(0, 200); } +function normalizeCustomHostBrowserEndpoint(value) { + const raw = String(value || "").trim(); + if (!raw) return ""; + const candidate = raw.includes("://") ? raw : `ws://${raw}`; + try { + const url = new URL(candidate); + if (!["ws:", "wss:"].includes(url.protocol) || !url.host || !url.pathname.startsWith("/devtools/browser/")) { + return ""; + } + return normalizeHostBrowserSelection(`${url.protocol}//${url.host}${url.pathname}${url.search || ""}`); + } catch (_error) { + return ""; + } +} + +function isCustomHostBrowserEndpoint(value) { + return Boolean(normalizeCustomHostBrowserEndpoint(value)); +} + +function debugPortVersionUrl(value) { + const raw = String(value || "").trim(); + if (!raw) return ""; + const candidate = raw.includes("://") ? raw : `ws://${raw}`; + try { + const url = new URL(candidate); + if (!["ws:", "wss:"].includes(url.protocol) || !url.host) return ""; + if (url.pathname && url.pathname !== "/") return ""; + return `http://${url.host}/json/version`; + } catch (_error) { + return ""; + } +} + function normalizeBoolean(value, fallback = true) { if (value === undefined || value === null || value === "") return fallback; if (typeof value === "boolean") return value; @@ -121,6 +155,8 @@ export const store = createStore("browserConfig", { hostBrowserStatus: null, hostBrowserStatusLoading: false, hostBrowserStatusRefreshTimer: null, + hostBrowserCustomEndpoint: "", + hostBrowserCustomMode: false, async init(config) { this.bindConfig(config); @@ -137,6 +173,8 @@ export const store = createStore("browserConfig", { this.extensionDeleteLoadingPath = ""; this.hostBrowserStatus = null; this.hostBrowserStatusLoading = false; + this.hostBrowserCustomEndpoint = ""; + this.hostBrowserCustomMode = false; }, startHostBrowserStatusRefresh() { @@ -158,6 +196,9 @@ export const store = createStore("browserConfig", { if (!safeConfig) return; if (this.config === safeConfig) return; this.config = safeConfig; + if (isCustomHostBrowserEndpoint(safeConfig.host_browser_selection)) { + this.hostBrowserCustomEndpoint = safeConfig.host_browser_selection; + } }, setAutofocusActivePage(enabled) { @@ -215,29 +256,82 @@ export const store = createStore("browserConfig", { ? connector.available_browsers : []; for (const browser of advertised) { - const value = normalizeHostBrowserSelection(browser?.id || browser?.family || browser?.cdp_endpoint); + const value = normalizeCustomHostBrowserEndpoint(browser?.cdp_endpoint || browser?.id); if (!value || seen.has(value)) continue; seen.add(value); const label = browser?.label || hostBrowserFamilyLabel(browser?.family || value); const status = browser?.status ? ` - ${hostBrowserStatusLabel(browser.status)}` : ""; options.push({ value, label: `${label}${status}` }); } - const fallbackValue = normalizeHostBrowserSelection(connector?.browser_id || connector?.browser_family); + const fallbackValue = normalizeCustomHostBrowserEndpoint(connector?.cdp_endpoint || connector?.browser_id); if (fallbackValue && !seen.has(fallbackValue)) { seen.add(fallbackValue); const label = connector?.browser_label || hostBrowserFamilyLabel(connector?.browser_family || fallbackValue); options.push({ value: fallbackValue, label }); } } + const selected = normalizeHostBrowserSelection(this.config?.host_browser_selection); + if (selected && !seen.has(selected) && !isCustomHostBrowserEndpoint(selected)) { + seen.add(selected); + options.push({ value: selected, label: `Saved: ${selected}` }); + } + options.push({ value: CUSTOM_HOST_BROWSER_SELECTION, label: "Custom endpoint" }); return options; }, + hostBrowserSelectValue() { + if (this.hostBrowserCustomMode) return CUSTOM_HOST_BROWSER_SELECTION; + const selected = normalizeHostBrowserSelection(this.config?.host_browser_selection); + if (!selected) return ""; + if (this.hostBrowserOptions().some((option) => option.value === selected)) return selected; + if (isCustomHostBrowserEndpoint(selected)) return CUSTOM_HOST_BROWSER_SELECTION; + return selected; + }, + setHostBrowserSelection(value) { const safeConfig = ensureConfig(this.config); if (!safeConfig) return; + if (value === CUSTOM_HOST_BROWSER_SELECTION) { + this.hostBrowserCustomMode = true; + if (isCustomHostBrowserEndpoint(safeConfig.host_browser_selection)) { + this.hostBrowserCustomEndpoint = safeConfig.host_browser_selection; + } else { + safeConfig.host_browser_selection = ""; + } + return; + } + this.hostBrowserCustomMode = false; safeConfig.host_browser_selection = normalizeHostBrowserSelection(value); }, + showCustomHostBrowserEndpoint() { + return this.hostBrowserSelectValue() === CUSTOM_HOST_BROWSER_SELECTION; + }, + + setCustomHostBrowserEndpoint(value) { + this.hostBrowserCustomMode = true; + this.hostBrowserCustomEndpoint = String(value || "").trim(); + const safeConfig = ensureConfig(this.config); + if (!safeConfig) return; + const endpoint = normalizeCustomHostBrowserEndpoint(this.hostBrowserCustomEndpoint); + if (endpoint || !this.hostBrowserCustomEndpoint) { + safeConfig.host_browser_selection = endpoint; + } + }, + + customHostBrowserEndpointDiagnostic() { + if (!this.hostBrowserCustomEndpoint) { + return "Paste a ws://.../devtools/browser/... endpoint from the browser inspect page."; + } + const endpoint = normalizeCustomHostBrowserEndpoint(this.hostBrowserCustomEndpoint); + if (endpoint) return `Using ${endpoint}`; + const versionUrl = debugPortVersionUrl(this.hostBrowserCustomEndpoint); + if (versionUrl) { + return `This looks like a debug port. Open ${versionUrl} and copy webSocketDebuggerUrl.`; + } + return "Endpoint must be a ws:// or wss:// URL ending in /devtools/browser/..."; + }, + hostBrowserProfileModeLabel() { const value = this.config?.host_browser_profile_mode || "existing"; if (value === "agent") return "Clean Agent Profile"; diff --git a/plugins/_browser/webui/config.html b/plugins/_browser/webui/config.html index 79851960c..40cba5d37 100644 --- a/plugins/_browser/webui/config.html +++ b/plugins/_browser/webui/config.html @@ -72,14 +72,32 @@ > Host browser - Choose which installed browser/debug endpoint A0 CLI should use when multiple are available. + Choose an allowed debug endpoint, or leave Automatic so A0 CLI picks. + + +