diff --git a/tests/tools/test_bot_desktop_browser.py b/tests/tools/test_bot_desktop_browser.py index 227d4267f5..1da352cc7d 100644 --- a/tests/tools/test_bot_desktop_browser.py +++ b/tests/tools/test_bot_desktop_browser.py @@ -6,6 +6,7 @@ from __future__ import annotations import os import socket +from pathlib import Path from tools.bot_desktop import browser, runtime @@ -31,6 +32,24 @@ def test_user_pinned_profile_wins(tmp_path, monkeypatch): assert browser.profile_dir() == tmp_path / "mine" +def test_pinned_profile_honours_tilde_and_resolves_relative_paths_against_hermes_home(tmp_path, monkeypatch): + """Regression for #110029: the docs say setting AGENT_BROWSER_PROFILE pins your own user-data-dir, but only + an absolute value was honoured — `~/pin` and `pin` silently fell back to the default and the human's dock + browser and the agent's browser could end up on different jars. A relative path is anchored where the rest + of this profile's screen state lives (its HERMES_HOME), so two profiles never share one 'pin'.""" + home = tmp_path / "hermes-home" + monkeypatch.setenv("HERMES_HOME", str(home)) + monkeypatch.setattr(runtime, "get_hermes_home", lambda: home) + monkeypatch.setattr(Path, "home", lambda: tmp_path / "user") + monkeypatch.setenv("HOME", str(tmp_path / "user")) + + monkeypatch.setenv("AGENT_BROWSER_PROFILE", "~/pin") + assert browser.profile_dir() == tmp_path / "user" / "pin" + monkeypatch.setenv("AGENT_BROWSER_PROFILE", "pin") + assert browser.profile_dir() == home / "pin" + assert browser.env_for_agent({})["AGENT_BROWSER_PROFILE"] == str(home / "pin"), "agent-browser gets the resolved path" + + def test_dock_browser_advertises_a_devtools_port(): """A human-started instance must be attachable, or the agent can never drive it afterwards.""" assert "--remote-debugging-port=" in browser.dock_argv("/opt/chrome", "/p/dir")[2] diff --git a/tools/bot_desktop/browser.py b/tools/bot_desktop/browser.py index 1da06ad41a..fa44c63719 100644 --- a/tools/bot_desktop/browser.py +++ b/tools/bot_desktop/browser.py @@ -26,10 +26,12 @@ _SYSTEM_BROWSERS = ("google-chrome", "google-chrome-stable", "chromium", "chromi def profile_dir() -> Path: - """User-data-dir the bot's browser uses on this profile's screen (``AGENT_BROWSER_PROFILE`` wins).""" - override = os.environ.get("AGENT_BROWSER_PROFILE", "").strip() - if override and os.path.isabs(override): - return Path(override) + """User-data-dir the bot's browser uses on this profile's screen. ``AGENT_BROWSER_PROFILE`` pins your own: + ``~`` expands, and a relative path is anchored at this profile's HERMES_HOME (where the rest of the screen's + state lives), so ``pin`` means ``/pin`` and two profiles never share one jar by accident.""" + override = os.path.expanduser(os.environ.get("AGENT_BROWSER_PROFILE", "").strip()) + if override: + return Path(override) if os.path.isabs(override) else runtime.get_hermes_home() / override return runtime.state_dir() / "browser-profile" diff --git a/website/docs/user-guide/features/bot-screen.md b/website/docs/user-guide/features/bot-screen.md index 3e25780951..d257a0828a 100644 --- a/website/docs/user-guide/features/bot-screen.md +++ b/website/docs/user-guide/features/bot-screen.md @@ -155,7 +155,9 @@ controller drops back to watching. While the screen runs, the bot's browser tool and the dock's **Browser** icon are the same browser: the Chromium agent-browser drives, with one persistent user-data-dir per bot (`/bot-desktop/browser-profile`; set -`AGENT_BROWSER_PROFILE` to pin your own). Click Browser during a takeover and you +`AGENT_BROWSER_PROFILE` to pin your own — `~` expands, and a relative path such +as `pin` resolves against that bot's `HERMES_HOME`, i.e. `/pin`). +Click Browser during a takeover and you are in the bot's own windows and cookie jar; what you sign in to is what the bot uses afterwards and in every later session, until the site expires the login. Set `browser.headed: true` so the bot's own browsing is visible on the screen too.