Accept host-browser discovery endpoints

Preserve explicit endpoint case, accept host:port and HTTP discovery addresses in Browser settings, and keep live-runtime guidance independent of any fixed localhost port.

Alessandro committed Jul 14, 2026 at 16:10 UTC 2e32a2f8dc16e32d6f3595403a64496b1631ed12
8 files changed +40 -40
AGENTS.md
+1 -1
@@ -27,7 +27,7 @@
27 - Never commit secrets, `.env` files, API keys, tokens, or private user data.
28 - Preserve authentication and CSRF protections.
29 - Use Linux paths and commands in examples.
30 -- Treat the Docker container exposed at `localhost:32080` as the live plugin/backend runtime when that target is named.
30 +- When a live Dockerized Agent Zero target is explicitly named, verify that exact runtime instead of assuming a fixed localhost port.
31 - Copy live core-plugin changes back into tracked source under `plugins/`.
32 - Develop new custom plugins under ignored `usr/plugins/`; tracked bundled plugins live under `plugins/`.
33 - Use the framework runtime for backend and plugin-hook verification, not the separate agent execution runtime.
docs/guides/a0-cli-connector.md
+4 -3
@@ -173,11 +173,12 @@ explicit remote debugging port and a separate profile:
173 opera --remote-debugging-port=9222 --user-data-dir="$HOME/.config/a0-opera-debug"
174 ```
175
176 -Then choose **Custom endpoint** in Browser settings, run `/browser ws://...` in
177 -A0 CLI, or pass the full DevTools websocket endpoint to A0 CLI:
176 +Then choose **Custom endpoint** in Browser settings, run
177 +`/browser localhost:9222` in A0 CLI, or pass the discovery address to A0 CLI. A
178 +full DevTools WebSocket endpoint also works:
179
180 ```bash
180 -export A0_HOST_BROWSER_REMOTE_DEBUGGING_ENDPOINTS="ws://127.0.0.1:9222/devtools/browser/..."
181 +export A0_HOST_BROWSER_REMOTE_DEBUGGING_ENDPOINTS="http://localhost:9222"
182 ```
183
184 ### Browser Profiles
docs/guides/browser.md
+4 -3
@@ -169,11 +169,12 @@ directory:
169 opera --remote-debugging-port=9222 --user-data-dir="$HOME/.config/a0-opera-debug"
170 ```
171
172 -Then choose **Custom endpoint** in Browser settings, or pass the full DevTools
173 -websocket endpoint to the CLI:
172 +Then choose **Custom endpoint** in Browser settings and enter `localhost:9222`
173 +or `http://localhost:9222`. A full DevTools WebSocket endpoint also works. The
174 +same forms can be passed to A0 CLI:
175
176 ```bash
176 -export A0_HOST_BROWSER_REMOTE_DEBUGGING_ENDPOINTS="ws://127.0.0.1:9222/devtools/browser/..."
177 +export A0_HOST_BROWSER_REMOTE_DEBUGGING_ENDPOINTS="http://localhost:9222"
178 ```
179
180 ![Host browser remote debugging setting](../res/usage/browser/host-browser-remote-debugging-setting.png)
plugins/_browser/AGENTS.md
+2 -2
@@ -24,9 +24,9 @@
24 - Keep Browser viewer frame transport capability-negotiated: updated clients may request binary/slim screencast frames, while older clients must keep the base64/full-metadata fallback. Do not let the WebUI advertise binary frames unless its Socket.IO client reconstructs attachments as real `Blob`, `ArrayBuffer`, or typed-array values.
25 - Keep WebUI Browser tabs scoped to the active chat context by default; aggregate tabs from other AgentContext runtimes only when the Browser settings tab scope is `shared`.
26 - Keep narrow WebUI Browser controls usable by grouping navigation with Annotate/settings above a full-width address bar.
27 -- 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`.
27 +- For Bring Your Own Browser with an existing host profile, `host_browser_selection` may target automatic CLI selection, a browser family/id, an HTTP CDP discovery address, or a full DevTools WebSocket endpoint and must be forwarded to the connector runtime as `browser_selection`.
28 - 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.
29 -- 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.
29 +- 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. Preserve endpoint path/query case and let A0 CLI resolve discovery addresses on the host.
30 - Browser URL-intent handling must only claim web URL schemes and leave custom Agent Zero schemes to their owning surfaces.
31 - Prefer DOM/CDP browser actions with refs, selectors, frame-chain refs, and screenshots over viewport coordinate input. Coordinates remain a visual fallback.
32 - Do not hardcode user-specific browser paths or secrets.
plugins/_browser/helpers/config.py
+5 -1
@@ -63,8 +63,12 @@ def _normalize_host_browser_selection(value: Any) -> str:
63 raw = str(value or "").strip()
64 if not raw:
65 return ""
66 + endpoint_like = "://" in raw or (
67 + raw.rpartition(":")[0] and raw.rpartition(":")[2].isdigit()
68 + )
69 + if endpoint_like:
70 + return "".join(ch for ch in raw if ch.isprintable() and not ch.isspace())[:2048]
71 normalized = raw.lower().replace(" ", "_")
67 - # Keep explicit CLI ids/ports/endpoints usable while avoiding control characters.
72 return "".join(ch for ch in normalized if ch.isalnum() or ch in {"_", "-", ":", ".", "/"})[:200]
73
74
plugins/_browser/webui/browser-config-store.js
+16 -27
@@ -70,19 +70,27 @@ function normalizeRuntimeBackend(value) {
70 }
71
72 function normalizeHostBrowserSelection(value) {
73 - return String(value || "").trim().toLowerCase().replace(/\s+/g, "_").slice(0, 200);
73 + const raw = String(value || "").trim();
74 + if (raw.includes("://") || /^(?:\[[^\]]+\]|[^/:\s]+):\d+$/.test(raw)) {
75 + return raw.replace(/\s+/g, "").slice(0, 2048);
76 + }
77 + return raw.toLowerCase().replace(/\s+/g, "_").slice(0, 200);
78 }
79
80 function normalizeCustomHostBrowserEndpoint(value) {
81 const raw = String(value || "").trim();
82 if (!raw) return "";
79 - const candidate = raw.includes("://") ? raw : `ws://${raw}`;
83 + const candidate = raw.includes("://") ? raw : `http://${raw}`;
84 try {
85 const url = new URL(candidate);
82 - if (!["ws:", "wss:"].includes(url.protocol) || !url.host || !url.pathname.startsWith("/devtools/browser/")) {
83 - return "";
86 + if (!url.host) return "";
87 + if (["http:", "https:"].includes(url.protocol)) {
88 + if (!["/", "/json/version"].includes(url.pathname)) return "";
89 + const path = url.pathname === "/" ? "" : url.pathname;
90 + return normalizeHostBrowserSelection(`${url.protocol}//${url.host}${path}${url.search || ""}`);
91 }
85 - return normalizeHostBrowserSelection(`${url.protocol}//${url.host}${url.pathname}${url.search || ""}`);
92 + if (!["ws:", "wss:"].includes(url.protocol)) return "";
93 + return normalizeHostBrowserSelection(`${url.protocol}//${url.host}${url.pathname === "/" ? "" : url.pathname}${url.search || ""}`);
94 } catch (_error) {
95 return "";
96 }
@@ -92,20 +100,6 @@ function isCustomHostBrowserEndpoint(value) {
100 return Boolean(normalizeCustomHostBrowserEndpoint(value));
101 }
102
95 -function debugPortVersionUrl(value) {
96 - const raw = String(value || "").trim();
97 - if (!raw) return "";
98 - const candidate = raw.includes("://") ? raw : `ws://${raw}`;
99 - try {
100 - const url = new URL(candidate);
101 - if (!["ws:", "wss:"].includes(url.protocol) || !url.host) return "";
102 - if (url.pathname && url.pathname !== "/") return "";
103 - return `http://${url.host}/json/version`;
104 - } catch (_error) {
105 - return "";
106 - }
107 -}
108 -
103 function normalizeBoolean(value, fallback = true) {
104 if (value === undefined || value === null || value === "") return fallback;
105 if (typeof value === "boolean") return value;
@@ -314,9 +308,8 @@ export const store = createStore("browserConfig", {
308 const safeConfig = ensureConfig(this.config);
309 if (!safeConfig) return;
310 const endpoint = normalizeCustomHostBrowserEndpoint(this.hostBrowserCustomEndpoint);
317 - if (endpoint || !this.hostBrowserCustomEndpoint) {
318 - safeConfig.host_browser_selection = endpoint;
319 - }
311 + safeConfig.host_browser_selection = endpoint
312 + || normalizeHostBrowserSelection(this.hostBrowserCustomEndpoint);
313 },
314
315 customHostBrowserEndpointDiagnostic() {
@@ -325,11 +318,7 @@ export const store = createStore("browserConfig", {
318 }
319 const endpoint = normalizeCustomHostBrowserEndpoint(this.hostBrowserCustomEndpoint);
320 if (endpoint) return `Using ${endpoint}`;
328 - const versionUrl = debugPortVersionUrl(this.hostBrowserCustomEndpoint);
329 - if (versionUrl) {
330 - return `This looks like a debug port. Open ${versionUrl} and copy webSocketDebuggerUrl.`;
331 - }
332 - return "Endpoint must be a ws:// or wss:// URL ending in /devtools/browser/...";
321 + return "Use host:port, an http(s):// discovery address, or a ws(s):// browser endpoint.";
322 },
323
324 hostBrowserProfileModeLabel() {
plugins/_browser/webui/config.html
+1 -1
@@ -106,7 +106,7 @@
106 >
107 <span class="material-symbols-outlined">info</span>
108 <span>
109 - For an already-open browser, Chrome, Edge, Brave, Vivaldi, and Chromium use chrome://inspect/#remote-debugging; Opera uses opera://inspect/#remote-debugging. Enable "Allow remote debugging for this browser instance", then restart or reconnect A0 CLI if the browser does not appear. If needed, launch the browser with --remote-debugging-port=9222 and --user-data-dir=&lt;profile-dir&gt;, or set A0_HOST_BROWSER_REMOTE_DEBUGGING_ENDPOINTS to the full ws://.../devtools/browser/... endpoint.
109 + For an already-open browser, Chrome, Edge, Brave, Vivaldi, and Chromium use chrome://inspect/#remote-debugging; Opera uses opera://inspect/#remote-debugging. Enable "Allow remote debugging for this browser instance", then restart or reconnect A0 CLI if the browser does not appear. If needed, launch the browser with --remote-debugging-port=9222 and --user-data-dir=&lt;profile-dir&gt;, then use localhost:9222 or the full ws://.../devtools/browser/... endpoint here or in A0_HOST_BROWSER_REMOTE_DEBUGGING_ENDPOINTS.
110 </span>
111 </div>
112
tests/test_browser_agent_regressions.py
+7 -2
@@ -197,11 +197,14 @@ def test_browser_config_normalizes_host_browser_selection():
197 == "chrome"
198 )
199 assert (
200 - normalize_browser_config({"host_browser_selection": "ws://127.0.0.1:9222/devtools"})[
200 + normalize_browser_config({"host_browser_selection": "ws://127.0.0.1:9222/devtools/Browser/AbC?token=XyZ"})[
201 "host_browser_selection"
202 ]
203 - == "ws://127.0.0.1:9222/devtools"
203 + == "ws://127.0.0.1:9222/devtools/Browser/AbC?token=XyZ"
204 )
205 + assert normalize_browser_config({"host_browser_selection": "localhost:9222"})[
206 + "host_browser_selection"
207 + ] == "localhost:9222"
208 assert normalize_browser_config({"host_browser_selection": "bad\x00value"})[
209 "host_browser_selection"
210 ] == "badvalue"
@@ -1029,7 +1032,9 @@ def test_browser_tool_does_not_auto_open_canvas_policy_is_documented():
1032 assert "opera://inspect/#remote-debugging" in config_html
1033 assert "A0_HOST_BROWSER_REMOTE_DEBUGGING_ENDPOINTS" in config_html
1034 assert "Custom endpoint" in config_html
1035 + assert "localhost:9222" in config_html
1036 assert "customHostBrowserEndpointDiagnostic" in config_store_js
1037 + assert "http(s):// discovery address" in config_store_js
1038 assert "HOST_BROWSER_STATUS_REFRESH_MS = 1000" in config_store_js
1039
1040