Keep host browser requests on Browser runtime
Route host/local browser requests through the Browser tool instead of desktop or shell fallbacks. Add remote-debugging setup guidance to Browser runtime errors and document the exact Chrome inspect setting in prompts, skills, and Web UI copy.
Alessandro committed
May 12, 2026 at 15:45 UTC
4bab8da3f5d7d8a95a6ee8970fe451b40f641b06
9 files changed
+121
-10
plugins/_a0_connector/prompts/agent.system.tool.code_execution_remote.md
+7
@@ -11,6 +11,13 @@ connected, remote execution is disabled, or local access is not Read&Write for a
11
mutating command, report that to the user instead of falling back to server-side
12
execution.
13
14
+Do not use this tool as a fallback for host-browser navigation/control. For
15
+"my browser", host browser, local browser/Chrome, or opening a URL in the host
16
+browser, use the `browser` tool. If Browser reports missing Chrome
17
+remote-debugging consent, tell the user to open `chrome://inspect/#remote-debugging`,
18
+enable "Allow remote debugging for this browser instance", run `/browser host on`,
19
+and retry.
20
+
21
## Arguments
22
- `runtime`: one of `terminal`, `python`, `nodejs`, `output`, `reset`
23
- `session`: integer session id (default `0`)
plugins/_a0_connector/skills/host-code-execution/SKILL.md
+2
@@ -11,6 +11,8 @@ Use `code_execution_remote` only for shell-backed execution on the machine where
11
12
If the task belongs inside Agent Zero's own runtime, use the normal server-side execution tool instead.
13
14
+Browser boundary: do not use shell launchers such as `xdg-open`, `sensible-browser`, `open`, `start`, or Python `webbrowser.open` as a fallback for requests to use/control/open a page in the host browser. Use the `browser` tool instead; if it reports missing Chrome remote-debugging consent, stop and relay the `chrome://inspect/#remote-debugging` instructions.
15
+
16
## Access Modes
17
18
- Remote execution can be disabled locally in the CLI. If the tool returns a disabled/no-client error, explain the required CLI toggle instead of falling back to the server runtime.
plugins/_a0_connector/skills/host-computer-use/SKILL.md
+8
-1
@@ -1,6 +1,6 @@
1
---
2
name: host-computer-use
3
-description: Beta desktop control through the connected A0 CLI host. Use when the user asks for computer use, screenshots, screen inspection, menus, browser chrome, native UI tasks, clicking, scrolling, typing, or checking computer_use_remote status.
3
+description: Beta desktop control through the connected A0 CLI host. Use for screenshots, screen inspection, menus, native app UI, OS-level clicking, scrolling, typing, or checking computer_use_remote status. Do not use for ordinary browser navigation; host browser requests should use the browser tool.
4
---
5
6
# Host Computer Use
@@ -15,6 +15,13 @@ If the task is browser-only and the user is flexible, prefer direct browser tool
15
16
If the task needs shell execution on the CLI host, load `host-code-execution` separately rather than treating desktop control and shell execution as one affordance.
17
18
+## Browser Boundary
19
+
20
+- If the user asks to use/open/control their host browser, local browser, Chrome, "my browser", or a URL in the host browser, use the `browser` tool. The Browser plugin chooses Docker or A0 CLI host-browser runtime from Browser settings and can surface Chrome remote-debugging setup.
21
+- Do not start `computer_use_remote` for web-page navigation just because the phrase "host browser" appears. Use this skill only for desktop/browser-chrome tasks that the `browser` tool cannot express.
22
+- If host-browser setup fails or mentions remote debugging, tell the user to open `chrome://inspect/#remote-debugging`, enable "Allow remote debugging for this browser instance", run `/browser host on`, and retry.
23
+- Do not fall back to `code_execution_remote`, `xdg-open`, `sensible-browser`, or Python `webbrowser.open` for host-browser control. Those can launch pages without giving Agent Zero browser control or setup diagnostics.
24
+
25
## Tool Contract
26
27
Use:
plugins/_browser/helpers/connector_runtime.py
+43
-6
@@ -79,6 +79,22 @@ _REQUIRED_API_NAMES_RE = re.compile(
79
r"const\s+REQUIRED_API_NAMES\s*=\s*Object\.freeze\(\[(?P<body>.*?)\]\);",
80
re.S,
81
)
82
+_HOST_BROWSER_REMOTE_DEBUGGING_HELP = (
83
+ 'For an already-open Chrome-family browser, open `chrome://inspect/#remote-debugging`, '
84
+ 'enable "Allow remote debugging for this browser instance", run `/browser host on`, '
85
+ "and retry."
86
+)
87
+_REMOTE_DEBUGGING_ERROR_TOKENS = (
88
+ "remote debugging",
89
+ "remote-debugging",
90
+ "devtoolsactiveport",
91
+ "devtools endpoint",
92
+ "cdp endpoint",
93
+ "cannot connect to the host browser",
94
+ "127.0.0.1:9222",
95
+ "localhost:9222",
96
+ "blocks playwright remote debugging",
97
+)
98
99
100
class ConnectorBrowserRuntime:
@@ -253,11 +269,7 @@ class ConnectorBrowserRuntime:
269
sid = self._select_sid()
270
if not sid:
271
statuses = host_browser_metadata_for_context(self.context_id)
256
- detail = self._format_statuses(statuses)
257
- raise RuntimeError(
258
- "Host browser is required but no subscribed A0 CLI advertises host-browser support"
259
- + (f": {detail}" if detail else ".")
260
- )
272
+ raise RuntimeError(self._host_browser_unavailable_message(statuses))
273
274
if self._needs_prepare(sid, payload):
275
await self._send_browser_op(
@@ -323,7 +335,11 @@ class ConnectorBrowserRuntime:
335
if not isinstance(response, dict):
336
raise RuntimeError(f"Unexpected host browser response: {response!r}")
337
if not response.get("ok"):
326
- raise RuntimeError(str(response.get("error") or "Host browser operation failed"))
338
+ raise RuntimeError(
339
+ self._host_browser_error_message(
340
+ response.get("error") or "Host browser operation failed"
341
+ )
342
+ )
343
return response.get("result")
344
345
def _select_sid(self) -> str | None:
@@ -446,6 +462,27 @@ class ConnectorBrowserRuntime:
462
)
463
return "; ".join(parts)
464
465
+ @classmethod
466
+ def _host_browser_unavailable_message(cls, statuses: list[dict[str, Any]]) -> str:
467
+ detail = cls._format_statuses(statuses)
468
+ message = (
469
+ "Host browser is required but no subscribed A0 CLI advertises host-browser support"
470
+ + (f": {detail}" if detail else ".")
471
+ )
472
+ return cls._host_browser_error_message(message)
473
+
474
+ @staticmethod
475
+ def _host_browser_error_message(error: Any) -> str:
476
+ message = str(error or "Host browser operation failed").strip()
477
+ if not message:
478
+ message = "Host browser operation failed"
479
+ normalized = message.lower()
480
+ if "chrome://inspect/#remote-debugging" in normalized:
481
+ return message
482
+ if any(token in normalized for token in _REMOTE_DEBUGGING_ERROR_TOKENS):
483
+ return f"{message}\n\n{_HOST_BROWSER_REMOTE_DEBUGGING_HELP}"
484
+ return message
485
+
486
487
@lru_cache(maxsize=1)
488
def _content_helper_payload() -> dict[str, Any]:
plugins/_browser/prompts/agent.system.tool.browser.md
+2
@@ -5,6 +5,8 @@ Prefer `search_engine` or `document_query` for plain text research. The tool mus
5
6
The browser may run in Docker container mode or A0 CLI host-browser mode depending on settings. Container-mode paths resolve inside Agent Zero; host-mode paths resolve on the connected A0 CLI host.
7
8
+When the user asks for "my browser", "host browser", "local browser", local Chrome, or opening a URL in their host browser, use this `browser` tool. Do not substitute `computer_use_remote`, `code_execution_remote`, or host shell launchers such as `xdg-open`, `sensible-browser`, or Python `webbrowser.open`. If host-browser setup fails and mentions remote debugging, stop and tell the user to open `chrome://inspect/#remote-debugging`, enable "Allow remote debugging for this browser instance", run `/browser host on`, and retry.
9
+
10
For complex browser workflows, load skill `browser-automation`. For fragile forms, load skill `browser-form-workflows`.
11
12
Actions: `open`, `list`, `state`, `set_active`, `navigate`, `back`, `forward`, `reload`, `content`, `detail`, `screenshot`, `click`, `hover`, `double_click`, `right_click`, `drag`, `type`, `submit`, `type_submit`, `scroll`, `evaluate`, `key_chord`, `mouse`, `wheel`, `keyboard`, `clipboard`, `set_viewport`, `select_option`, `set_checked`, `upload_file`, `multi`, `close`, `close_all`.
plugins/_browser/webui/config.html
+1
-1
@@ -31,7 +31,7 @@
31
class="browser-config-field-help"
32
x-show="$store.browserConfig.config.runtime_backend === 'host_required'"
33
>
34
- Use Google Chrome or a Chromium family browser. Visit chrome://inspect#remote-debugging and enable "Allow remote debugging".
34
+ Use Google Chrome or a Chromium family browser. Visit chrome://inspect/#remote-debugging and enable "Allow remote debugging".
35
</span>
36
</label>
37
tests/test_a0_connector_prompt_gating.py
+37
@@ -350,3 +350,40 @@ def test_remote_tool_stubs_are_self_contained_and_reference_per_tool_skills():
350
assert "a0-cli-remote-workflows" not in text_stub
351
assert "a0-cli-remote-workflows" not in exec_stub
352
assert "a0-cli-remote-workflows" not in computer_skill
353
+
354
+
355
+def test_host_browser_requests_route_to_browser_tool_not_desktop_or_shell_fallbacks():
356
+ browser_prompt = (
357
+ PROJECT_ROOT / "plugins" / "_browser" / "prompts" / "agent.system.tool.browser.md"
358
+ ).read_text(encoding="utf-8")
359
+ exec_stub = (PROMPT_ROOT / "agent.system.tool.code_execution_remote.md").read_text(encoding="utf-8")
360
+ exec_skill = (
361
+ PROJECT_ROOT
362
+ / "plugins"
363
+ / "_a0_connector"
364
+ / "skills"
365
+ / "host-code-execution"
366
+ / "SKILL.md"
367
+ ).read_text(encoding="utf-8")
368
+ computer_skill = (
369
+ PROJECT_ROOT
370
+ / "plugins"
371
+ / "_a0_connector"
372
+ / "skills"
373
+ / "host-computer-use"
374
+ / "SKILL.md"
375
+ ).read_text(encoding="utf-8")
376
+
377
+ assert 'When the user asks for "my browser"' in browser_prompt
378
+ assert "Do not substitute `computer_use_remote`" in browser_prompt
379
+ assert "code_execution_remote" in browser_prompt
380
+ assert "Python `webbrowser.open`" in browser_prompt
381
+ assert "chrome://inspect/#remote-debugging" in browser_prompt
382
+ assert "Do not start `computer_use_remote` for web-page navigation" in computer_skill
383
+ assert (
384
+ "Do not fall back to `code_execution_remote`, `xdg-open`, `sensible-browser`, "
385
+ "or Python `webbrowser.open`"
386
+ ) in computer_skill
387
+ assert "do not use shell launchers" in exec_skill
388
+ assert "Use a shell launcher only when the user explicitly wants" not in exec_skill
389
+ assert "Do not use this tool as a fallback for host-browser navigation/control" in exec_stub
tests/test_browser_agent_regressions.py
+2
@@ -911,8 +911,10 @@ def test_browser_tool_does_not_auto_open_canvas_policy_is_documented():
911
assert "upload_file" in prompt
912
assert "browser-form-workflows" in prompt
913
assert "does not automatically load screenshots" in prompt
914
+ assert "chrome://inspect/#remote-debugging" in prompt
915
assert "already open" in config
916
assert "already-open Browser surface" in config_html
917
+ assert "chrome://inspect/#remote-debugging" in config_html
918
919
920
def test_browser_forms_skill_is_plugin_owned_and_discoverable():
tests/test_host_browser_connector.py
+19
-2
@@ -12,7 +12,6 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
12
if str(PROJECT_ROOT) not in sys.path:
13
sys.path.insert(0, str(PROJECT_ROOT))
14
15
-from helpers.errors import RepairableException
15
from plugins._a0_connector.helpers import ws_runtime
16
from plugins._browser.helpers.connector_runtime import (
17
ConnectorBrowserRuntime,
@@ -33,7 +32,7 @@ def test_host_required_runtime_error_is_repairable(monkeypatch):
32
lambda agent=None: {"runtime_backend": "host_required"},
33
)
34
36
- with pytest.raises(RepairableException, match="Bring Your Own Browser"):
35
+ with pytest.raises(browser_selector.RepairableException, match="Bring Your Own Browser"):
36
asyncio.run(browser_selector.get_tool_runtime(_agent("ctx-host-required-missing")))
37
38
@@ -295,6 +294,24 @@ def test_connector_runtime_forwards_host_profile_mode(monkeypatch):
294
assert runtime._payload_for_call("open", "example.com")["profile_mode"] == "agent"
295
296
297
+def test_connector_runtime_adds_remote_debugging_help_to_cdp_errors():
298
+ runtime = ConnectorBrowserRuntime("ctx-host", _agent("ctx-host"))
299
+
300
+ message = runtime._host_browser_error_message(
301
+ "Cannot connect to the host browser remote-debugging endpoint "
302
+ "ws://127.0.0.1:9222/devtools/browser/test. Original error: refused"
303
+ )
304
+
305
+ assert "chrome://inspect/#remote-debugging" in message
306
+ assert "Allow remote debugging for this browser instance" in message
307
+ assert "/browser host on" in message
308
+ already_helpful = (
309
+ "Open chrome://inspect/#remote-debugging and enable "
310
+ '"Allow remote debugging for this browser instance".'
311
+ )
312
+ assert runtime._host_browser_error_message(already_helpful) == already_helpful
313
+
314
+
315
def test_host_browser_artifacts_materialize_inside_multi_results(monkeypatch, tmp_path):
316
import plugins._browser.helpers.connector_runtime as connector_runtime_module
317