Simplify Host Browser config

Remove the ambiguous Use host when ready option from the Browser plugin settings and present the host-required path as Bring Your Own Browser. Add concise Chrome/Chromium remote-debugging guidance, normalize legacy host_when_available values to the BYOB setting, and make missing host-browser connector setup a repairable error with regression coverage.

Alessandro committed May 8, 2026 at 18:18 UTC 001c7e2ccbf5e47ae342e5b6b980c9eaab83cb3a
5 files changed +37 -11
plugins/_browser/default_config.yaml
+1 -2
@@ -10,8 +10,7 @@ autofocus_active_page: true
10
11 # Runtime used by the agent-facing browser tool:
12 # - container: use Agent Zero's Docker/server Playwright browser.
13 -# - host_when_available: use A0 CLI host browser when a subscribed CLI can provide it, otherwise container.
14 -# - host_required: require A0 CLI host browser and prepare it on first browser use when possible.
13 +# - host_required: Bring Your Own Browser through A0 CLI and prepare it on first browser use when possible.
14 runtime_backend: "container"
15
16 # Local-model enforcement for host-browser content:
plugins/_browser/helpers/selector.py
+6 -2
@@ -2,6 +2,7 @@ from __future__ import annotations
2
3 from typing import Any
4
5 +from helpers.errors import RepairableException
6 from plugins._browser.helpers.config import RUNTIME_BACKEND_KEY, get_browser_config
7 from plugins._browser.helpers.runtime import get_runtime as get_container_runtime
8
@@ -22,11 +23,14 @@ async def get_tool_runtime(agent: Any):
23
24 if backend == "host_required":
25 detail = _host_browser_status_detail(context_id)
25 - raise RuntimeError(
26 - "Browser host_required mode is enabled, but no subscribed A0 CLI currently "
26 + message = (
27 + "Bring Your Own Browser mode is enabled, but no subscribed A0 CLI currently "
28 "advertises host-browser support"
29 + (f": {detail}" if detail else ".")
30 )
31 + raise RepairableException(
32 + f"{message} Connect A0 CLI to this chat, allow host browser access, and retry."
33 + )
34
35 return await get_container_runtime(context_id)
36
plugins/_browser/webui/browser-config-store.js
+9 -4
@@ -3,7 +3,7 @@ import { callJsonApi } from "/js/api.js";
3
4 const BROWSER_EXTENSIONS_API = "/plugins/_browser/extensions";
5 const BROWSER_STATUS_API = "/plugins/_browser/status";
6 -const RUNTIME_BACKENDS = new Set(["container", "host_when_available", "host_required"]);
6 +const RUNTIME_BACKENDS = new Set(["container", "host_required"]);
7 const HOST_PRIVACY_POLICIES = new Set(["enforce_local", "warn", "allow"]);
8
9 function normalizePathList(value) {
@@ -26,7 +26,7 @@ function ensureConfig(config) {
26 config.extension_paths = normalizePathList(config.extension_paths);
27 config.default_homepage = String(config.default_homepage || "about:blank").trim() || "about:blank";
28 config.autofocus_active_page = normalizeBoolean(config.autofocus_active_page, true);
29 - config.runtime_backend = normalizeChoice(config.runtime_backend, RUNTIME_BACKENDS, "container");
29 + config.runtime_backend = normalizeRuntimeBackend(config.runtime_backend);
30 config.host_browser_privacy_policy = normalizeChoice(
31 config.host_browser_privacy_policy,
32 HOST_PRIVACY_POLICIES,
@@ -42,6 +42,12 @@ function normalizeChoice(value, allowed, fallback) {
42 return allowed.has(normalized) ? normalized : fallback;
43 }
44
45 +function normalizeRuntimeBackend(value) {
46 + const normalized = String(value || "").trim().toLowerCase().replace(/-/g, "_");
47 + if (normalized === "host_when_available") return "host_required";
48 + return RUNTIME_BACKENDS.has(normalized) ? normalized : "container";
49 +}
50 +
51 function normalizeBoolean(value, fallback = true) {
52 if (value === undefined || value === null || value === "") return fallback;
53 if (typeof value === "boolean") return value;
@@ -122,8 +128,7 @@ export const store = createStore("browserConfig", {
128
129 runtimeBackendLabel() {
130 const value = this.config?.runtime_backend || "container";
125 - if (value === "host_when_available") return "Use Host When Ready";
126 - if (value === "host_required") return "Require Host Browser";
131 + if (value === "host_required") return "Bring Your Own Browser";
132 return "Docker Browser";
133 },
134
plugins/_browser/webui/config.html
+7 -3
@@ -60,10 +60,14 @@
60 <span class="browser-config-field-label">Browser location</span>
61 <select x-model="$store.browserConfig.config.runtime_backend">
62 <option value="container">Docker browser</option>
63 - <option value="host_when_available">Use host when ready</option>
64 - <option value="host_required">Require host browser</option>
63 + <option value="host_required">Bring Your Own Browser</option>
64 </select>
66 - <span class="browser-config-field-help">Require host browser when pages must stay on this computer.</span>
65 + <span
66 + class="browser-config-field-help"
67 + x-show="$store.browserConfig.config.runtime_backend === 'host_required'"
68 + >
69 + Use Google Chrome or a Chromium family browser. Visit chrome://inspect#remote-debugging and enable "Allow remote debugging".
70 + </span>
71 </label>
72
73 <label class="browser-config-field">
tests/test_host_browser_connector.py
+14
@@ -11,6 +11,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
11 if str(PROJECT_ROOT) not in sys.path:
12 sys.path.insert(0, str(PROJECT_ROOT))
13
14 +from helpers.errors import RepairableException
15 from plugins._a0_connector.helpers import ws_runtime
16 from plugins._browser.helpers.connector_runtime import (
17 ConnectorBrowserRuntime,
@@ -22,6 +23,19 @@ def _agent(context_id: str = "ctx-host"):
23 return SimpleNamespace(context=SimpleNamespace(id=context_id))
24
25
26 +def test_host_required_runtime_error_is_repairable(monkeypatch):
27 + from plugins._browser.helpers import selector as browser_selector
28 +
29 + monkeypatch.setattr(
30 + browser_selector,
31 + "get_browser_config",
32 + lambda agent=None: {"runtime_backend": "host_required"},
33 + )
34 +
35 + with pytest.raises(RepairableException, match="Bring Your Own Browser"):
36 + asyncio.run(browser_selector.get_tool_runtime(_agent("ctx-host-required-missing")))
37 +
38 +
39 def test_host_browser_metadata_selection_is_context_scoped():
40 sid = "sid-host-browser"
41 context_id = "ctx-host-browser"