diff --git a/contributors/emails/B0on@users.noreply.github.com b/contributors/emails/B0on@users.noreply.github.com new file mode 100644 index 000000000000..8fd956faf4cc --- /dev/null +++ b/contributors/emails/B0on@users.noreply.github.com @@ -0,0 +1 @@ +B0on diff --git a/hermes_cli/cli_info_mixin.py b/hermes_cli/cli_info_mixin.py index 0d7fbbd6ca58..1950d3ac2b43 100644 --- a/hermes_cli/cli_info_mixin.py +++ b/hermes_cli/cli_info_mixin.py @@ -772,9 +772,12 @@ def _show_insights(self, command: str = "/insights"): i += 1 try: - from hermes_state import SessionDB + from hermes_state import SessionDB, _default_db_path from agent.insights import InsightsEngine - db = SessionDB() + if not _default_db_path().exists(): + print(" No session data yet.") + return + db = SessionDB(read_only=True) try: engine = InsightsEngine(db) print(engine.format_terminal(engine.generate(days=days, source=source))) diff --git a/hermes_cli/main_agent_cmds.py b/hermes_cli/main_agent_cmds.py index 1b5264703b46..4efc80e597a9 100644 --- a/hermes_cli/main_agent_cmds.py +++ b/hermes_cli/main_agent_cmds.py @@ -105,9 +105,12 @@ def cmd_tools(args): def cmd_insights(args): db = None try: - from hermes_state import SessionDB + from hermes_state import SessionDB, _default_db_path from agent.insights import InsightsEngine - db = SessionDB() + if not _default_db_path().exists(): + print("No session data yet.") + return + db = SessionDB(read_only=True) engine = InsightsEngine(db) report = engine.generate(days=args.days, source=args.source) print(engine.format_terminal(report)) diff --git a/tests/cli/test_cli_insights_command.py b/tests/cli/test_cli_insights_command.py index dea3e094a264..9d623f5d391f 100644 --- a/tests/cli/test_cli_insights_command.py +++ b/tests/cli/test_cli_insights_command.py @@ -1,8 +1,18 @@ from unittest.mock import MagicMock, patch from types import SimpleNamespace +import pytest + from cli import HermesCLI from hermes_cli.main_agent_cmds import cmd_insights +from hermes_state import _default_db_path + + +@pytest.fixture(autouse=True) +def _state_db_exists(): + # insights short-circuits before opening when state.db is absent. + _default_db_path().parent.mkdir(parents=True, exist_ok=True) + _default_db_path().touch() class _InsightsEngineStub: @@ -45,6 +55,30 @@ def test_cli_insights_keeps_days_flag_and_source(capsys): assert "days=14 source=discord" in capsys.readouterr().out +def test_insights_opens_state_db_read_only(): + # `hermes insights` / `/insights` are readers; a read-write SessionDB + # would take a writer connection on the live gateway's state.db. + runs = ( + lambda: HermesCLI.__new__(HermesCLI)._show_insights("/insights 7"), + lambda: cmd_insights(SimpleNamespace(days=30, source=None)), + ) + # Fresh install: no state.db yet → no open at all (read-only open needs an existing file). + _default_db_path().unlink() + for run in runs: + with patch("hermes_state.SessionDB") as ctor: + run() + ctor.assert_not_called() + _default_db_path().touch() + for run in runs: + db = MagicMock() + _InsightsEngineStub.calls = [] + with patch("hermes_state.SessionDB", return_value=db) as ctor, \ + patch("agent.insights.InsightsEngine", _InsightsEngineStub): + run() + ctor.assert_called_once_with(read_only=True) + db.close.assert_called_once() + + def test_subcommand_insights_closes_database_when_generation_fails(capsys): db = MagicMock() with patch("hermes_state.SessionDB", return_value=db), \