mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-07-09 17:08:29 +00:00
Refine BYOB host browser endpoint selection
Reduce Browser Settings host-browser choices to automatic and advertised debug endpoints, with a validated Custom endpoint input and faster inventory refresh. Update Browser docs and regressions to document the custom endpoint workflow and CLI websocket selection.
This commit is contained in:
parent
c719134537
commit
afa5231a5a
6 changed files with 133 additions and 10 deletions
|
|
@ -162,8 +162,9 @@ control starts when Agent Zero actually needs to use the browser.
|
|||
> control.
|
||||
|
||||
The **Host browser** list in Browser settings comes from the connected local A0
|
||||
CLI, not from the Agent Zero Web UI server. If a newly authorized browser does
|
||||
not appear, restart or reconnect A0 CLI.
|
||||
CLI, not from the Agent Zero Web UI server. It shows Automatic, currently
|
||||
advertised debug endpoints, and **Custom endpoint**. If a newly authorized
|
||||
browser does not appear, restart or reconnect A0 CLI.
|
||||
|
||||
If the inspect checkbox is not enough for your browser build, launch it with an
|
||||
explicit remote debugging port and a separate profile:
|
||||
|
|
@ -172,7 +173,8 @@ explicit remote debugging port and a separate profile:
|
|||
opera --remote-debugging-port=9222 --user-data-dir="$HOME/.config/a0-opera-debug"
|
||||
```
|
||||
|
||||
Then pass the full DevTools websocket endpoint to A0 CLI:
|
||||
Then choose **Custom endpoint** in Browser settings, run `/browser ws://...` in
|
||||
A0 CLI, or pass the full DevTools websocket endpoint to A0 CLI:
|
||||
|
||||
```bash
|
||||
export A0_HOST_BROWSER_REMOTE_DEBUGGING_ENDPOINTS="ws://127.0.0.1:9222/devtools/browser/..."
|
||||
|
|
|
|||
|
|
@ -156,7 +156,8 @@ Remote debugging pages:
|
|||
| Chrome, Edge, Brave, Vivaldi, Chromium | `chrome://inspect/#remote-debugging` |
|
||||
| Opera | `opera://inspect/#remote-debugging` |
|
||||
|
||||
If a browser does not appear in the **Host browser** list after enabling remote
|
||||
The **Host browser** list shows Automatic, currently advertised debug endpoints,
|
||||
and **Custom endpoint**. If a browser does not appear after enabling remote
|
||||
debugging, restart or reconnect the local A0 CLI. Restarting only the Agent Zero
|
||||
Web UI server does not refresh the browser inventory; the list comes from the
|
||||
connected CLI.
|
||||
|
|
@ -168,7 +169,8 @@ directory:
|
|||
opera --remote-debugging-port=9222 --user-data-dir="$HOME/.config/a0-opera-debug"
|
||||
```
|
||||
|
||||
Then pass the full DevTools websocket endpoint to the CLI:
|
||||
Then choose **Custom endpoint** in Browser settings, or pass the full DevTools
|
||||
websocket endpoint to the CLI:
|
||||
|
||||
```bash
|
||||
export A0_HOST_BROWSER_REMOTE_DEBUGGING_ENDPOINTS="ws://127.0.0.1:9222/devtools/browser/..."
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
- Keep narrow WebUI Browser controls usable by grouping navigation with Annotate/settings above a full-width address bar.
|
||||
- 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`.
|
||||
- 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.
|
||||
- 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.
|
||||
- Browser URL-intent handling must only claim web URL schemes and leave custom Agent Zero schemes to their owning surfaces.
|
||||
- Prefer DOM/CDP browser actions with refs, selectors, frame-chain refs, and screenshots over viewport coordinate input. Coordinates remain a visual fallback.
|
||||
- Do not hardcode user-specific browser paths or secrets.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ const HOST_PROFILE_MODES = new Set(["existing", "agent"]);
|
|||
const DEFAULT_MAX_OPEN_TABS = 32;
|
||||
const MIN_MAX_OPEN_TABS = 1;
|
||||
const HARD_MAX_OPEN_TABS = 50;
|
||||
const HOST_BROWSER_STATUS_REFRESH_MS = 3000;
|
||||
const HOST_BROWSER_STATUS_REFRESH_MS = 1000;
|
||||
const CUSTOM_HOST_BROWSER_SELECTION = "__custom_endpoint__";
|
||||
|
||||
function normalizePathList(value) {
|
||||
const source = Array.isArray(value)
|
||||
|
|
@ -72,6 +73,39 @@ function normalizeHostBrowserSelection(value) {
|
|||
return String(value || "").trim().toLowerCase().replace(/\s+/g, "_").slice(0, 200);
|
||||
}
|
||||
|
||||
function normalizeCustomHostBrowserEndpoint(value) {
|
||||
const raw = String(value || "").trim();
|
||||
if (!raw) return "";
|
||||
const candidate = raw.includes("://") ? raw : `ws://${raw}`;
|
||||
try {
|
||||
const url = new URL(candidate);
|
||||
if (!["ws:", "wss:"].includes(url.protocol) || !url.host || !url.pathname.startsWith("/devtools/browser/")) {
|
||||
return "";
|
||||
}
|
||||
return normalizeHostBrowserSelection(`${url.protocol}//${url.host}${url.pathname}${url.search || ""}`);
|
||||
} catch (_error) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function isCustomHostBrowserEndpoint(value) {
|
||||
return Boolean(normalizeCustomHostBrowserEndpoint(value));
|
||||
}
|
||||
|
||||
function debugPortVersionUrl(value) {
|
||||
const raw = String(value || "").trim();
|
||||
if (!raw) return "";
|
||||
const candidate = raw.includes("://") ? raw : `ws://${raw}`;
|
||||
try {
|
||||
const url = new URL(candidate);
|
||||
if (!["ws:", "wss:"].includes(url.protocol) || !url.host) return "";
|
||||
if (url.pathname && url.pathname !== "/") return "";
|
||||
return `http://${url.host}/json/version`;
|
||||
} catch (_error) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeBoolean(value, fallback = true) {
|
||||
if (value === undefined || value === null || value === "") return fallback;
|
||||
if (typeof value === "boolean") return value;
|
||||
|
|
@ -121,6 +155,8 @@ export const store = createStore("browserConfig", {
|
|||
hostBrowserStatus: null,
|
||||
hostBrowserStatusLoading: false,
|
||||
hostBrowserStatusRefreshTimer: null,
|
||||
hostBrowserCustomEndpoint: "",
|
||||
hostBrowserCustomMode: false,
|
||||
|
||||
async init(config) {
|
||||
this.bindConfig(config);
|
||||
|
|
@ -137,6 +173,8 @@ export const store = createStore("browserConfig", {
|
|||
this.extensionDeleteLoadingPath = "";
|
||||
this.hostBrowserStatus = null;
|
||||
this.hostBrowserStatusLoading = false;
|
||||
this.hostBrowserCustomEndpoint = "";
|
||||
this.hostBrowserCustomMode = false;
|
||||
},
|
||||
|
||||
startHostBrowserStatusRefresh() {
|
||||
|
|
@ -158,6 +196,9 @@ export const store = createStore("browserConfig", {
|
|||
if (!safeConfig) return;
|
||||
if (this.config === safeConfig) return;
|
||||
this.config = safeConfig;
|
||||
if (isCustomHostBrowserEndpoint(safeConfig.host_browser_selection)) {
|
||||
this.hostBrowserCustomEndpoint = safeConfig.host_browser_selection;
|
||||
}
|
||||
},
|
||||
|
||||
setAutofocusActivePage(enabled) {
|
||||
|
|
@ -215,29 +256,82 @@ export const store = createStore("browserConfig", {
|
|||
? connector.available_browsers
|
||||
: [];
|
||||
for (const browser of advertised) {
|
||||
const value = normalizeHostBrowserSelection(browser?.id || browser?.family || browser?.cdp_endpoint);
|
||||
const value = normalizeCustomHostBrowserEndpoint(browser?.cdp_endpoint || browser?.id);
|
||||
if (!value || seen.has(value)) continue;
|
||||
seen.add(value);
|
||||
const label = browser?.label || hostBrowserFamilyLabel(browser?.family || value);
|
||||
const status = browser?.status ? ` - ${hostBrowserStatusLabel(browser.status)}` : "";
|
||||
options.push({ value, label: `${label}${status}` });
|
||||
}
|
||||
const fallbackValue = normalizeHostBrowserSelection(connector?.browser_id || connector?.browser_family);
|
||||
const fallbackValue = normalizeCustomHostBrowserEndpoint(connector?.cdp_endpoint || connector?.browser_id);
|
||||
if (fallbackValue && !seen.has(fallbackValue)) {
|
||||
seen.add(fallbackValue);
|
||||
const label = connector?.browser_label || hostBrowserFamilyLabel(connector?.browser_family || fallbackValue);
|
||||
options.push({ value: fallbackValue, label });
|
||||
}
|
||||
}
|
||||
const selected = normalizeHostBrowserSelection(this.config?.host_browser_selection);
|
||||
if (selected && !seen.has(selected) && !isCustomHostBrowserEndpoint(selected)) {
|
||||
seen.add(selected);
|
||||
options.push({ value: selected, label: `Saved: ${selected}` });
|
||||
}
|
||||
options.push({ value: CUSTOM_HOST_BROWSER_SELECTION, label: "Custom endpoint" });
|
||||
return options;
|
||||
},
|
||||
|
||||
hostBrowserSelectValue() {
|
||||
if (this.hostBrowserCustomMode) return CUSTOM_HOST_BROWSER_SELECTION;
|
||||
const selected = normalizeHostBrowserSelection(this.config?.host_browser_selection);
|
||||
if (!selected) return "";
|
||||
if (this.hostBrowserOptions().some((option) => option.value === selected)) return selected;
|
||||
if (isCustomHostBrowserEndpoint(selected)) return CUSTOM_HOST_BROWSER_SELECTION;
|
||||
return selected;
|
||||
},
|
||||
|
||||
setHostBrowserSelection(value) {
|
||||
const safeConfig = ensureConfig(this.config);
|
||||
if (!safeConfig) return;
|
||||
if (value === CUSTOM_HOST_BROWSER_SELECTION) {
|
||||
this.hostBrowserCustomMode = true;
|
||||
if (isCustomHostBrowserEndpoint(safeConfig.host_browser_selection)) {
|
||||
this.hostBrowserCustomEndpoint = safeConfig.host_browser_selection;
|
||||
} else {
|
||||
safeConfig.host_browser_selection = "";
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.hostBrowserCustomMode = false;
|
||||
safeConfig.host_browser_selection = normalizeHostBrowserSelection(value);
|
||||
},
|
||||
|
||||
showCustomHostBrowserEndpoint() {
|
||||
return this.hostBrowserSelectValue() === CUSTOM_HOST_BROWSER_SELECTION;
|
||||
},
|
||||
|
||||
setCustomHostBrowserEndpoint(value) {
|
||||
this.hostBrowserCustomMode = true;
|
||||
this.hostBrowserCustomEndpoint = String(value || "").trim();
|
||||
const safeConfig = ensureConfig(this.config);
|
||||
if (!safeConfig) return;
|
||||
const endpoint = normalizeCustomHostBrowserEndpoint(this.hostBrowserCustomEndpoint);
|
||||
if (endpoint || !this.hostBrowserCustomEndpoint) {
|
||||
safeConfig.host_browser_selection = endpoint;
|
||||
}
|
||||
},
|
||||
|
||||
customHostBrowserEndpointDiagnostic() {
|
||||
if (!this.hostBrowserCustomEndpoint) {
|
||||
return "Paste a ws://.../devtools/browser/... endpoint from the browser inspect page.";
|
||||
}
|
||||
const endpoint = normalizeCustomHostBrowserEndpoint(this.hostBrowserCustomEndpoint);
|
||||
if (endpoint) return `Using ${endpoint}`;
|
||||
const versionUrl = debugPortVersionUrl(this.hostBrowserCustomEndpoint);
|
||||
if (versionUrl) {
|
||||
return `This looks like a debug port. Open ${versionUrl} and copy webSocketDebuggerUrl.`;
|
||||
}
|
||||
return "Endpoint must be a ws:// or wss:// URL ending in /devtools/browser/...";
|
||||
},
|
||||
|
||||
hostBrowserProfileModeLabel() {
|
||||
const value = this.config?.host_browser_profile_mode || "existing";
|
||||
if (value === "agent") return "Clean Agent Profile";
|
||||
|
|
|
|||
|
|
@ -72,14 +72,32 @@
|
|||
>
|
||||
<span class="browser-config-field-label">Host browser</span>
|
||||
<select
|
||||
:value="$store.browserConfig.config.host_browser_selection || ''"
|
||||
:value="$store.browserConfig.hostBrowserSelectValue()"
|
||||
@focus="$store.browserConfig.loadHostBrowserStatus()"
|
||||
@click="$store.browserConfig.loadHostBrowserStatus()"
|
||||
@change="$store.browserConfig.setHostBrowserSelection($event.target.value)"
|
||||
>
|
||||
<template x-for="option in $store.browserConfig.hostBrowserOptions()" :key="option.value">
|
||||
<option :value="option.value" x-text="option.label"></option>
|
||||
</template>
|
||||
</select>
|
||||
<span class="browser-config-field-help">Choose which installed browser/debug endpoint A0 CLI should use when multiple are available.</span>
|
||||
<span class="browser-config-field-help">Choose an allowed debug endpoint, or leave Automatic so A0 CLI picks.</span>
|
||||
</label>
|
||||
|
||||
<label
|
||||
class="browser-config-field"
|
||||
x-show="$store.browserConfig.config.runtime_backend === 'host_required' && $store.browserConfig.config.host_browser_profile_mode !== 'agent' && $store.browserConfig.showCustomHostBrowserEndpoint()"
|
||||
>
|
||||
<span class="browser-config-field-label">Custom endpoint</span>
|
||||
<input
|
||||
type="text"
|
||||
:value="$store.browserConfig.hostBrowserCustomEndpoint"
|
||||
@focus="$store.browserConfig.loadHostBrowserStatus()"
|
||||
@input="$store.browserConfig.setCustomHostBrowserEndpoint($event.target.value)"
|
||||
placeholder="ws://127.0.0.1:9222/devtools/browser/..."
|
||||
autocomplete="off"
|
||||
/>
|
||||
<span class="browser-config-field-help" x-text="$store.browserConfig.customHostBrowserEndpointDiagnostic()"></span>
|
||||
</label>
|
||||
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -981,6 +981,9 @@ def test_browser_tool_does_not_auto_open_canvas_policy_is_documented():
|
|||
config_html = (PROJECT_ROOT / "plugins" / "_browser" / "webui" / "config.html").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
config_store_js = (
|
||||
PROJECT_ROOT / "plugins" / "_browser" / "webui" / "browser-config-store.js"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
assert "must not open a Browser surface automatically" in prompt
|
||||
assert "Use the tool headlessly unless the user opens the Browser surface" in prompt
|
||||
|
|
@ -1002,6 +1005,9 @@ def test_browser_tool_does_not_auto_open_canvas_policy_is_documented():
|
|||
assert "chrome://inspect/#remote-debugging" in config_html
|
||||
assert "opera://inspect/#remote-debugging" in config_html
|
||||
assert "A0_HOST_BROWSER_REMOTE_DEBUGGING_ENDPOINTS" in config_html
|
||||
assert "Custom endpoint" in config_html
|
||||
assert "customHostBrowserEndpointDiagnostic" in config_store_js
|
||||
assert "HOST_BROWSER_STATUS_REFRESH_MS = 1000" in config_store_js
|
||||
|
||||
|
||||
def test_browser_skills_are_plugin_owned_and_progressively_linked():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue