Create a new session DB row when starting fresh from the CLI, reset the agent DB flush cursor and todo state, and update session timing/session ID bookkeeping so follow-up logging stays correct. Also update slash-command descriptions and add regression tests for /new, /reset, and /clear. Supersedes PR #899. Closes #641.
140 lines
5.0 KiB
Python
140 lines
5.0 KiB
Python
"""Regression tests for CLI fresh-session commands."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import os
|
|
import sys
|
|
from datetime import timedelta
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from hermes_state import SessionDB
|
|
from tools.todo_tool import TodoStore
|
|
|
|
|
|
class _FakeAgent:
|
|
def __init__(self, session_id: str, session_start):
|
|
self.session_id = session_id
|
|
self.session_start = session_start
|
|
self.model = "anthropic/claude-opus-4.6"
|
|
self._last_flushed_db_idx = 7
|
|
self._todo_store = TodoStore()
|
|
self._todo_store.write(
|
|
[{"id": "t1", "content": "unfinished task", "status": "in_progress"}]
|
|
)
|
|
self.flush_memories = MagicMock()
|
|
self._invalidate_system_prompt = MagicMock()
|
|
|
|
|
|
def _make_cli(env_overrides=None, config_overrides=None, **kwargs):
|
|
"""Create a HermesCLI instance with minimal mocking."""
|
|
_clean_config = {
|
|
"model": {
|
|
"default": "anthropic/claude-opus-4.6",
|
|
"base_url": "https://openrouter.ai/api/v1",
|
|
"provider": "auto",
|
|
},
|
|
"display": {"compact": False, "tool_progress": "all"},
|
|
"agent": {},
|
|
"terminal": {"env_type": "local"},
|
|
}
|
|
if config_overrides:
|
|
_clean_config.update(config_overrides)
|
|
clean_env = {"LLM_MODEL": "", "HERMES_MAX_ITERATIONS": ""}
|
|
if env_overrides:
|
|
clean_env.update(env_overrides)
|
|
prompt_toolkit_stubs = {
|
|
"prompt_toolkit": MagicMock(),
|
|
"prompt_toolkit.history": MagicMock(),
|
|
"prompt_toolkit.styles": MagicMock(),
|
|
"prompt_toolkit.patch_stdout": MagicMock(),
|
|
"prompt_toolkit.application": MagicMock(),
|
|
"prompt_toolkit.layout": MagicMock(),
|
|
"prompt_toolkit.layout.processors": MagicMock(),
|
|
"prompt_toolkit.filters": MagicMock(),
|
|
"prompt_toolkit.layout.dimension": MagicMock(),
|
|
"prompt_toolkit.layout.menus": MagicMock(),
|
|
"prompt_toolkit.widgets": MagicMock(),
|
|
"prompt_toolkit.key_binding": MagicMock(),
|
|
"prompt_toolkit.completion": MagicMock(),
|
|
"prompt_toolkit.formatted_text": MagicMock(),
|
|
}
|
|
with patch.dict(sys.modules, prompt_toolkit_stubs), patch.dict(
|
|
"os.environ", clean_env, clear=False
|
|
):
|
|
import cli as _cli_mod
|
|
|
|
_cli_mod = importlib.reload(_cli_mod)
|
|
with patch.object(_cli_mod, "get_tool_definitions", return_value=[]), patch.dict(
|
|
_cli_mod.__dict__, {"CLI_CONFIG": _clean_config}
|
|
):
|
|
return _cli_mod.HermesCLI(**kwargs)
|
|
|
|
|
|
def _prepare_cli_with_active_session(tmp_path):
|
|
cli = _make_cli()
|
|
cli._session_db = SessionDB(db_path=tmp_path / "state.db")
|
|
cli._session_db.create_session(session_id=cli.session_id, source="cli", model=cli.model)
|
|
|
|
cli.agent = _FakeAgent(cli.session_id, cli.session_start)
|
|
cli.conversation_history = [{"role": "user", "content": "hello"}]
|
|
|
|
old_session_start = cli.session_start - timedelta(seconds=1)
|
|
cli.session_start = old_session_start
|
|
cli.agent.session_start = old_session_start
|
|
return cli
|
|
|
|
|
|
def test_new_command_creates_real_fresh_session_and_resets_agent_state(tmp_path):
|
|
cli = _prepare_cli_with_active_session(tmp_path)
|
|
old_session_id = cli.session_id
|
|
old_session_start = cli.session_start
|
|
|
|
cli.process_command("/new")
|
|
|
|
assert cli.session_id != old_session_id
|
|
|
|
old_session = cli._session_db.get_session(old_session_id)
|
|
assert old_session is not None
|
|
assert old_session["end_reason"] == "new_session"
|
|
|
|
new_session = cli._session_db.get_session(cli.session_id)
|
|
assert new_session is not None
|
|
|
|
cli._session_db.append_message(cli.session_id, role="user", content="next turn")
|
|
|
|
assert cli.agent.session_id == cli.session_id
|
|
assert cli.agent._last_flushed_db_idx == 0
|
|
assert cli.agent._todo_store.read() == []
|
|
assert cli.session_start > old_session_start
|
|
assert cli.agent.session_start == cli.session_start
|
|
cli.agent.flush_memories.assert_called_once_with([{"role": "user", "content": "hello"}])
|
|
cli.agent._invalidate_system_prompt.assert_called_once()
|
|
|
|
|
|
def test_reset_command_is_alias_for_new_session(tmp_path):
|
|
cli = _prepare_cli_with_active_session(tmp_path)
|
|
old_session_id = cli.session_id
|
|
|
|
cli.process_command("/reset")
|
|
|
|
assert cli.session_id != old_session_id
|
|
assert cli._session_db.get_session(old_session_id)["end_reason"] == "new_session"
|
|
assert cli._session_db.get_session(cli.session_id) is not None
|
|
|
|
|
|
def test_clear_command_starts_new_session_before_redrawing(tmp_path):
|
|
cli = _prepare_cli_with_active_session(tmp_path)
|
|
cli.console = MagicMock()
|
|
cli.show_banner = MagicMock()
|
|
|
|
old_session_id = cli.session_id
|
|
cli.process_command("/clear")
|
|
|
|
assert cli.session_id != old_session_id
|
|
assert cli._session_db.get_session(old_session_id)["end_reason"] == "new_session"
|
|
assert cli._session_db.get_session(cli.session_id) is not None
|
|
cli.console.clear.assert_called_once()
|
|
cli.show_banner.assert_called_once()
|
|
assert cli.conversation_history == []
|