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

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=<profile-dir>, 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=<profile-dir>, 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