feat(browser): support externally managed Camofox sessions

Allow integrations to share a visible Camofox identity with Hermes and recover existing tabs without carrying local patches.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Dan Benyamin 2026-05-10 17:54:13 -07:00 committed by Teknium
parent 3955aefced
commit 62fd905340
7 changed files with 255 additions and 10 deletions

View file

@ -193,6 +193,118 @@ class TestManagedPersistenceMode:
assert tab_requests[0]["userId"] == tab_requests[1]["userId"]
class TestConfiguredCamofoxIdentity:
"""Externally managed Camofox sessions can provide their own identity."""
def test_env_identity_overrides_default_identity(self, tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.setenv("CAMOFOX_URL", "http://localhost:9377")
monkeypatch.setenv("CAMOFOX_USER_ID", "shared-camofox")
monkeypatch.setenv("CAMOFOX_SESSION_KEY", "visible-tab")
monkeypatch.setenv("CAMOFOX_ADOPT_EXISTING_TAB", "true")
with patch("tools.browser_camofox._get", return_value={"tabs": []}) as mock_get:
session = _get_session("task-1")
assert session["user_id"] == "shared-camofox"
assert session["session_key"] == "visible-tab"
assert session["managed"] is True
assert session["adopt_existing_tab"] is True
mock_get.assert_called_once_with(
"/tabs",
params={"userId": "shared-camofox"},
timeout=5,
)
def test_config_identity_is_used_when_env_is_absent(self, tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.setenv("CAMOFOX_URL", "http://localhost:9377")
config = {
"browser": {
"camofox": {
"user_id": "config-user",
"session_key": "config-session",
"adopt_existing_tab": False,
}
}
}
with patch("tools.browser_camofox.load_config", return_value=config):
session = _get_session("task-1")
assert session["user_id"] == "config-user"
assert session["session_key"] == "config-session"
assert session["adopt_existing_tab"] is False
def test_env_identity_takes_precedence_over_config(self, tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.setenv("CAMOFOX_URL", "http://localhost:9377")
monkeypatch.setenv("CAMOFOX_USER_ID", "env-user")
monkeypatch.setenv("CAMOFOX_SESSION_KEY", "env-session")
monkeypatch.setenv("CAMOFOX_ADOPT_EXISTING_TAB", "false")
config = {
"browser": {
"camofox": {
"user_id": "config-user",
"session_key": "config-session",
"adopt_existing_tab": True,
}
}
}
with patch("tools.browser_camofox.load_config", return_value=config):
session = _get_session("task-1")
assert session["user_id"] == "env-user"
assert session["session_key"] == "env-session"
assert session["adopt_existing_tab"] is False
def test_adopts_existing_tab_matching_session_key(self, tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.setenv("CAMOFOX_URL", "http://localhost:9377")
monkeypatch.setenv("CAMOFOX_USER_ID", "shared-camofox")
monkeypatch.setenv("CAMOFOX_SESSION_KEY", "visible-tab")
monkeypatch.setenv("CAMOFOX_ADOPT_EXISTING_TAB", "true")
tabs = {
"tabs": [
{"tabId": "tab-other", "listItemId": "other"},
{"tabId": "tab-visible", "listItemId": "visible-tab"},
]
}
with patch("tools.browser_camofox._get", return_value=tabs):
session = _get_session("task-1")
assert session["tab_id"] == "tab-visible"
def test_managed_persistence_can_opt_into_tab_adoption(self, tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.setenv("CAMOFOX_URL", "http://localhost:9377")
config = {"browser": {"camofox": {"managed_persistence": True, "adopt_existing_tab": True}}}
with (
patch("tools.browser_camofox.load_config", return_value=config),
patch("tools.browser_camofox._get", return_value={"tabs": [{"tabId": "tab-1"}]}),
):
session = _get_session("task-1")
assert session["tab_id"] == "tab-1"
def test_soft_cleanup_preserves_externally_managed_session(self, tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.setenv("CAMOFOX_URL", "http://localhost:9377")
monkeypatch.setenv("CAMOFOX_USER_ID", "shared-camofox")
with patch("tools.browser_camofox._get", return_value={"tabs": []}):
_get_session("task-1")
result = camofox_soft_cleanup("task-1")
assert result is True
import tools.browser_camofox as mod
with mod._sessions_lock:
assert "task-1" not in mod._sessions
class TestVncUrlDiscovery:
"""VNC URL is derived from the Camofox health endpoint."""

View file

@ -53,8 +53,11 @@ class TestCamofoxIdentity:
class TestCamofoxConfigDefaults:
def test_default_config_includes_managed_persistence_toggle(self):
def test_default_config_includes_camofox_controls(self):
from hermes_cli.config import DEFAULT_CONFIG
browser_cfg = DEFAULT_CONFIG["browser"]
assert browser_cfg["camofox"]["managed_persistence"] is False
assert browser_cfg["camofox"]["user_id"] == ""
assert browser_cfg["camofox"]["session_key"] == ""
assert browser_cfg["camofox"]["adopt_existing_tab"] is False