Fix browser URL intent routing

Limit the Browser URL-intent handler to web URL schemes so custom Agent Zero schemes fall through to their owning surfaces. Document the scheme-ownership contract and cover the a0-editor misroute regression in the desktop/editor routing test.

Alessandro committed Jul 3, 2026 at 01:07 UTC 05481c222aa3d8da936fd2a8780597332898456d
3 files changed +20
plugins/_browser/AGENTS.md
+1
@@ -20,6 +20,7 @@
20 - Keep the WebUI Browser inside its own modal/canvas affordance; do not replace it with page-level navigation.
21 - Default the visible WebUI Browser to live CDP screencast for responsiveness. Keep lightweight CDP/DOM state snapshots as the fallback transport.
22 - Keep narrow WebUI Browser controls usable by grouping navigation with Annotate/settings above a full-width address bar.
23 +- Browser URL-intent handling must only claim web URL schemes and leave custom Agent Zero schemes to their owning surfaces.
24 - Prefer DOM/CDP browser actions with refs, selectors, frame-chain refs, and screenshots over viewport coordinate input. Coordinates remain a visual fallback.
25 - Do not hardcode user-specific browser paths or secrets.
26
plugins/_browser/webui/browser-store.js
+13
@@ -2846,8 +2846,21 @@ const model = {
2846
2847 export const store = createStore("browserPage", model);
2848
2849 +const WEB_INTENT_SCHEMES = new Set(["http", "https", "file", "about"]);
2850 +
2851 +function isWebUrlIntent(url = "") {
2852 + const value = String(url || "").trim();
2853 + if (!value) return true;
2854 + const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(value);
2855 + if (!scheme) return true;
2856 + return WEB_INTENT_SCHEMES.has(scheme[1].toLowerCase());
2857 +}
2858 +
2859 registerUrlHandler(async (intent = {}) => {
2860 const url = String(intent.url || "").trim();
2861 + // Custom schemes such as a0-editor: belong to other surfaces; claiming them
2862 + // here would navigate the browser to an unloadable URL.
2863 + if (!isWebUrlIntent(url)) return false;
2864 const payload = { url, source: intent.source || "surface-url-intent" };
2865 await openLatestSurface("browser", payload);
2866 return await store.openUrlIntent(url, { source: payload.source });
tests/test_office_canvas_setup.py
+6
@@ -592,6 +592,7 @@ def test_desktop_text_open_with_routes_to_editor_surface():
592 desktop_session = read("plugins", "_desktop", "helpers", "desktop_session.py")
593 desktop_store = read("plugins", "_desktop", "webui", "desktop-store.js")
594 editor_store = read("plugins", "_editor", "webui", "editor-store.js")
595 + browser_store = read("plugins", "_browser", "webui", "browser-store.js")
596
597 assert 'EDITOR_HANDLER_DESKTOP_ID = "agent-zero-editor.desktop"' in desktop_session
598 assert "def _write_editor_bridge_script" in desktop_session
@@ -607,6 +608,11 @@ def test_desktop_text_open_with_routes_to_editor_surface():
608 assert "handleEditorUrlIntent" in editor_store
609 assert 'openLatestSurface("editor"' in editor_store
610
611 + # The browser surface must not claim a0-editor: intents, otherwise the
612 + # editor "Open With" handler lands on an unloadable about:blank page.
613 + assert "function isWebUrlIntent" in browser_store
614 + assert "if (!isWebUrlIntent(url)) return false;" in browser_store
615 +
616
617 def test_office_and_desktop_skills_are_rehomed_and_renamed():
618 office_skills = PROJECT_ROOT / "plugins" / "_office" / "skills"