diff --git a/plugins/_a0_connector/api/v1/browser_runtime.py b/plugins/_a0_connector/api/v1/browser_runtime.py index 62a875427..72467e954 100644 --- a/plugins/_a0_connector/api/v1/browser_runtime.py +++ b/plugins/_a0_connector/api/v1/browser_runtime.py @@ -6,7 +6,7 @@ import plugins._a0_connector.api.v1.base as connector_base _PRIVACY_NOTICE = ( - "For GDPR/content policy, visit Agent Zero WebUI > Browser settings to choose " + "For Browser model-use settings, visit Agent Zero WebUI > Browser settings to choose " "Local models only, Warn when using cloud, or Allow." ) @@ -24,6 +24,15 @@ def _normalize_requested_backend(value: object) -> str: return "" +def _normalize_profile_mode(value: object) -> str: + normalized = _string(value).lower().replace("-", "_").replace(" ", "_") + if normalized in {"agent", "clean", "clean_agent", "a0", "dedicated"}: + return "agent" + if normalized in {"existing", "user", "personal", "current"}: + return "existing" + return "" + + def _runtime_label(value: str) -> str: if value == "host_required": return "Bring Your Own Browser" @@ -59,12 +68,30 @@ class BrowserRuntime(connector_base.ProtectedConnectorApiHandler): mimetype="application/json", ) settings["runtime_backend"] = runtime_backend + if "host_browser_profile_mode" in input or "profile_mode" in input: + profile_mode = _normalize_profile_mode( + input.get("host_browser_profile_mode", input.get("profile_mode")) + ) + if not profile_mode: + return Response( + response='{"error":"host_browser_profile_mode must be existing or agent"}', + status=400, + mimetype="application/json", + ) + settings["host_browser_profile_mode"] = profile_mode + settings["host_browser_profile_mode"] = ( + _normalize_profile_mode(settings.get("host_browser_profile_mode")) or "existing" + ) self._save_browser_config(project_name, settings) + runtime_backend = settings.get("runtime_backend") or "container" + profile_mode = _normalize_profile_mode(settings.get("host_browser_profile_mode")) or "existing" + return { "ok": True, - "runtime_backend": settings["runtime_backend"], - "label": _runtime_label(settings["runtime_backend"]), + "runtime_backend": runtime_backend, + "host_browser_profile_mode": profile_mode, + "label": _runtime_label(runtime_backend), "project_name": project_name, "agent_profile": "", "privacy_notice": _PRIVACY_NOTICE, diff --git a/plugins/_browser/default_config.yaml b/plugins/_browser/default_config.yaml index 377ae5f65..bb03541e4 100644 --- a/plugins/_browser/default_config.yaml +++ b/plugins/_browser/default_config.yaml @@ -19,6 +19,11 @@ runtime_backend: "container" # - allow: allow without warning. host_browser_privacy_policy: "enforce_local" +# Host-browser profile preference: +# - existing: use the user's authorized existing browser profile when available. +# - agent: use a clean A0-controlled browser profile on the host. +host_browser_profile_mode: "existing" + # Optional _model_config preset used by Browser-owned model helpers. # Empty uses the effective Main Model. model_preset: "" diff --git a/plugins/_browser/helpers/config.py b/plugins/_browser/helpers/config.py index f3e2b55ec..f53d7f0c9 100644 --- a/plugins/_browser/helpers/config.py +++ b/plugins/_browser/helpers/config.py @@ -13,8 +13,10 @@ DEFAULT_HOMEPAGE_KEY = "default_homepage" AUTOFOCUS_ACTIVE_PAGE_KEY = "autofocus_active_page" RUNTIME_BACKEND_KEY = "runtime_backend" HOST_BROWSER_PRIVACY_POLICY_KEY = "host_browser_privacy_policy" +HOST_BROWSER_PROFILE_MODE_KEY = "host_browser_profile_mode" RUNTIME_BACKENDS = {"container", "host_required"} HOST_BROWSER_PRIVACY_POLICIES = {"enforce_local", "warn", "allow"} +HOST_BROWSER_PROFILE_MODES = {"existing", "agent"} BASE_BROWSER_ARGS = [ "--no-sandbox", "--disable-dev-shm-usage", @@ -110,6 +112,11 @@ def normalize_browser_config(settings: dict[str, Any] | None) -> dict[str, Any]: allowed=HOST_BROWSER_PRIVACY_POLICIES, default="enforce_local", ), + HOST_BROWSER_PROFILE_MODE_KEY: _normalize_choice( + raw.get(HOST_BROWSER_PROFILE_MODE_KEY, "existing"), + allowed=HOST_BROWSER_PROFILE_MODES, + default="existing", + ), MODEL_PRESET_KEY: _normalize_model_preset(raw.get(MODEL_PRESET_KEY, "")), } diff --git a/plugins/_browser/helpers/connector_runtime.py b/plugins/_browser/helpers/connector_runtime.py index 0639b5039..03fba70ec 100644 --- a/plugins/_browser/helpers/connector_runtime.py +++ b/plugins/_browser/helpers/connector_runtime.py @@ -34,10 +34,7 @@ from plugins._a0_connector.helpers.ws_runtime import ( select_host_browser_target_sid, store_pending_browser_op, ) -from plugins._browser.helpers.config import ( - HOST_BROWSER_PRIVACY_POLICY_KEY, - get_browser_config, -) +from plugins._browser.helpers import config as browser_config from plugins._browser.helpers.url import normalize_url @@ -47,6 +44,17 @@ HOST_BROWSER_SCREENSHOT_DIR = ("tmp", "browser", "host-screenshots") CONTENT_HELPER_PATH = Path(__file__).resolve().parents[1] / "assets" / "browser-page-content.js" MAX_ARTIFACT_SIZE_BYTES = 25 * 1024 * 1024 BASE64_DECODE_CHARS_PER_CHUNK = 64 * 1024 +HOST_BROWSER_PRIVACY_POLICY_KEY = getattr( + browser_config, + "HOST_BROWSER_PRIVACY_POLICY_KEY", + "host_browser_privacy_policy", +) +HOST_BROWSER_PROFILE_MODE_KEY = getattr( + browser_config, + "HOST_BROWSER_PROFILE_MODE_KEY", + "host_browser_profile_mode", +) +get_browser_config = browser_config.get_browser_config _LOCAL_PROVIDERS = {"ollama", "lm_studio"} _LOCAL_HOSTS = {"localhost", "127.0.0.1", "::1", "host.docker.internal"} _SENSITIVE_ACTIONS = {"content", "detail", "evaluate", "screenshot", "screenshot_file"} @@ -79,6 +87,7 @@ class ConnectorBrowserRuntime: "op_id": str(uuid.uuid4()), "context_id": self.context_id, "action": action, + "profile_mode": self._host_browser_profile_mode(), } if action == "open": @@ -188,6 +197,7 @@ class ConnectorBrowserRuntime: return normalized_calls async def _dispatch(self, payload: dict[str, Any]) -> Any: + payload.setdefault("profile_mode", self._host_browser_profile_mode()) self._enforce_privacy(payload) sid = self._select_sid() if not sid: @@ -207,6 +217,7 @@ class ConnectorBrowserRuntime: "op_id": str(uuid.uuid4()), "context_id": self.context_id, "action": "ensure", + "profile_mode": self._host_browser_profile_mode(), }, ), ) @@ -214,6 +225,11 @@ class ConnectorBrowserRuntime: return await self._send_browser_op(sid, self._with_content_helper(sid, payload)) + def _host_browser_profile_mode(self) -> str: + config = get_browser_config(self.agent) + mode = str(config.get(HOST_BROWSER_PROFILE_MODE_KEY) or "existing").strip().lower() + return "agent" if mode == "agent" else "existing" + def _with_content_helper(self, sid: str, payload: dict[str, Any]) -> dict[str, Any]: metadata = host_browser_metadata_for_sid(sid) or {} if str(metadata.get("content_helper_sha256") or "").strip().lower() == _content_helper_sha256(): diff --git a/plugins/_browser/webui/browser-config-store.js b/plugins/_browser/webui/browser-config-store.js index a40932405..136cae043 100644 --- a/plugins/_browser/webui/browser-config-store.js +++ b/plugins/_browser/webui/browser-config-store.js @@ -5,6 +5,7 @@ const BROWSER_EXTENSIONS_API = "/plugins/_browser/extensions"; const BROWSER_STATUS_API = "/plugins/_browser/status"; const RUNTIME_BACKENDS = new Set(["container", "host_required"]); const HOST_PRIVACY_POLICIES = new Set(["enforce_local", "warn", "allow"]); +const HOST_PROFILE_MODES = new Set(["existing", "agent"]); function normalizePathList(value) { const source = Array.isArray(value) @@ -32,6 +33,11 @@ function ensureConfig(config) { HOST_PRIVACY_POLICIES, "enforce_local", ); + config.host_browser_profile_mode = normalizeChoice( + config.host_browser_profile_mode, + HOST_PROFILE_MODES, + "existing", + ); config.model_preset = String(config.model_preset || "").trim(); delete config.model; return config; @@ -139,6 +145,12 @@ export const store = createStore("browserConfig", { return "Local Models Only"; }, + hostBrowserProfileModeLabel() { + const value = this.config?.host_browser_profile_mode || "existing"; + if (value === "agent") return "Clean Agent Profile"; + return "Existing Browser Profile"; + }, + async loadHostBrowserStatus() { if (this.hostBrowserStatusLoading) return; this.hostBrowserStatusLoading = true; diff --git a/plugins/_browser/webui/config.html b/plugins/_browser/webui/config.html index 7da0bea68..16f9d689a 100644 --- a/plugins/_browser/webui/config.html +++ b/plugins/_browser/webui/config.html @@ -35,6 +35,34 @@ + + +