mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
* fix(tools): skip camofox auto-cleanup when managed persistence is enabled
When managed_persistence is enabled, cleanup_browser() was calling
camofox_close() which destroys the server-side browser context via
DELETE /sessions/{userId}, killing login sessions across cron runs.
Add camofox_soft_cleanup() — a public wrapper that drops only the
in-memory session entry when managed persistence is on, returning True.
When persistence is off it returns False so the caller falls back to
the full camofox_close(). The inactivity reaper still handles idle
resource cleanup.
Also surface a logger.warning() when _managed_persistence_enabled()
fails to load config, replacing a silent except-and-return-False.
Salvaged from #6182 by el-analista (Eduardo Perea Fernandez).
Added public API wrapper to avoid cross-module private imports,
and test coverage for both persistence paths.
Co-authored-by: Eduardo Perea Fernandez <el-analista@users.noreply.github.com>
* fix(doctor): only check the active memory provider, not all providers unconditionally
hermes doctor had hardcoded Honcho Memory and Mem0 Memory sections that
always ran regardless of the user's memory.provider config setting. After
the swappable memory provider update (#4623), users with leftover Honcho
config but no active provider saw false 'broken' errors.
Replaced both sections with a single Memory Provider section that reads
memory.provider from config.yaml and only checks the configured provider.
Users with no external provider see a green 'Built-in memory active' check.
Reported by community user michaelruiz001, confirmed by Eri (Honcho).
---------
Co-authored-by: Eduardo Perea Fernandez <el-analista@users.noreply.github.com>
140 lines
5.7 KiB
Python
140 lines
5.7 KiB
Python
"""Regression tests for browser session cleanup and screenshot recovery."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
|
|
class TestScreenshotPathRecovery:
|
|
def test_extracts_standard_absolute_path(self):
|
|
from tools.browser_tool import _extract_screenshot_path_from_text
|
|
|
|
assert (
|
|
_extract_screenshot_path_from_text("Screenshot saved to /tmp/foo.png")
|
|
== "/tmp/foo.png"
|
|
)
|
|
|
|
def test_extracts_quoted_absolute_path(self):
|
|
from tools.browser_tool import _extract_screenshot_path_from_text
|
|
|
|
assert (
|
|
_extract_screenshot_path_from_text(
|
|
"Screenshot saved to '/Users/david/.hermes/browser_screenshots/shot.png'"
|
|
)
|
|
== "/Users/david/.hermes/browser_screenshots/shot.png"
|
|
)
|
|
|
|
|
|
class TestBrowserCleanup:
|
|
def setup_method(self):
|
|
from tools import browser_tool
|
|
|
|
self.browser_tool = browser_tool
|
|
self.orig_active_sessions = browser_tool._active_sessions.copy()
|
|
self.orig_session_last_activity = browser_tool._session_last_activity.copy()
|
|
self.orig_recording_sessions = browser_tool._recording_sessions.copy()
|
|
self.orig_cleanup_done = browser_tool._cleanup_done
|
|
|
|
def teardown_method(self):
|
|
self.browser_tool._active_sessions.clear()
|
|
self.browser_tool._active_sessions.update(self.orig_active_sessions)
|
|
self.browser_tool._session_last_activity.clear()
|
|
self.browser_tool._session_last_activity.update(self.orig_session_last_activity)
|
|
self.browser_tool._recording_sessions.clear()
|
|
self.browser_tool._recording_sessions.update(self.orig_recording_sessions)
|
|
self.browser_tool._cleanup_done = self.orig_cleanup_done
|
|
|
|
def test_cleanup_browser_clears_tracking_state(self):
|
|
browser_tool = self.browser_tool
|
|
browser_tool._active_sessions["task-1"] = {
|
|
"session_name": "sess-1",
|
|
"bb_session_id": None,
|
|
}
|
|
browser_tool._session_last_activity["task-1"] = 123.0
|
|
|
|
with (
|
|
patch("tools.browser_tool._maybe_stop_recording") as mock_stop,
|
|
patch(
|
|
"tools.browser_tool._run_browser_command",
|
|
return_value={"success": True},
|
|
) as mock_run,
|
|
patch("tools.browser_tool.os.path.exists", return_value=False),
|
|
):
|
|
browser_tool.cleanup_browser("task-1")
|
|
|
|
assert "task-1" not in browser_tool._active_sessions
|
|
assert "task-1" not in browser_tool._session_last_activity
|
|
mock_stop.assert_called_once_with("task-1")
|
|
mock_run.assert_called_once_with("task-1", "close", [], timeout=10)
|
|
|
|
def test_cleanup_camofox_managed_persistence_skips_close(self):
|
|
"""When camofox mode + managed persistence, soft_cleanup fires instead of close."""
|
|
browser_tool = self.browser_tool
|
|
browser_tool._active_sessions["task-1"] = {
|
|
"session_name": "sess-1",
|
|
"bb_session_id": None,
|
|
}
|
|
browser_tool._session_last_activity["task-1"] = 123.0
|
|
|
|
with (
|
|
patch("tools.browser_tool._is_camofox_mode", return_value=True),
|
|
patch("tools.browser_tool._maybe_stop_recording") as mock_stop,
|
|
patch(
|
|
"tools.browser_tool._run_browser_command",
|
|
return_value={"success": True},
|
|
),
|
|
patch("tools.browser_tool.os.path.exists", return_value=False),
|
|
patch(
|
|
"tools.browser_camofox.camofox_soft_cleanup",
|
|
return_value=True,
|
|
) as mock_soft,
|
|
patch("tools.browser_camofox.camofox_close") as mock_close,
|
|
):
|
|
browser_tool.cleanup_browser("task-1")
|
|
|
|
mock_soft.assert_called_once_with("task-1")
|
|
mock_close.assert_not_called()
|
|
|
|
def test_cleanup_camofox_no_persistence_calls_close(self):
|
|
"""When camofox mode but managed persistence is off, camofox_close fires."""
|
|
browser_tool = self.browser_tool
|
|
browser_tool._active_sessions["task-1"] = {
|
|
"session_name": "sess-1",
|
|
"bb_session_id": None,
|
|
}
|
|
browser_tool._session_last_activity["task-1"] = 123.0
|
|
|
|
with (
|
|
patch("tools.browser_tool._is_camofox_mode", return_value=True),
|
|
patch("tools.browser_tool._maybe_stop_recording") as mock_stop,
|
|
patch(
|
|
"tools.browser_tool._run_browser_command",
|
|
return_value={"success": True},
|
|
),
|
|
patch("tools.browser_tool.os.path.exists", return_value=False),
|
|
patch(
|
|
"tools.browser_camofox.camofox_soft_cleanup",
|
|
return_value=False,
|
|
) as mock_soft,
|
|
patch("tools.browser_camofox.camofox_close") as mock_close,
|
|
):
|
|
browser_tool.cleanup_browser("task-1")
|
|
|
|
mock_soft.assert_called_once_with("task-1")
|
|
mock_close.assert_called_once_with("task-1")
|
|
|
|
def test_emergency_cleanup_clears_all_tracking_state(self):
|
|
browser_tool = self.browser_tool
|
|
browser_tool._cleanup_done = False
|
|
browser_tool._active_sessions["task-1"] = {"session_name": "sess-1"}
|
|
browser_tool._active_sessions["task-2"] = {"session_name": "sess-2"}
|
|
browser_tool._session_last_activity["task-1"] = 1.0
|
|
browser_tool._session_last_activity["task-2"] = 2.0
|
|
browser_tool._recording_sessions.update({"task-1", "task-2"})
|
|
|
|
with patch("tools.browser_tool.cleanup_all_browsers") as mock_cleanup_all:
|
|
browser_tool._emergency_cleanup_all_sessions()
|
|
|
|
mock_cleanup_all.assert_called_once_with()
|
|
assert browser_tool._active_sessions == {}
|
|
assert browser_tool._session_last_activity == {}
|
|
assert browser_tool._recording_sessions == set()
|
|
assert browser_tool._cleanup_done is True
|