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"