diff --git a/cli.py b/cli.py index 526f457b48..d7e92069ba 100644 --- a/cli.py +++ b/cli.py @@ -3052,10 +3052,54 @@ class HermesCLI: print(f" Config File: {config_path} {config_status}") print() + def _list_recent_sessions(self, limit: int = 10) -> list[dict[str, Any]]: + """Return recent CLI sessions for in-chat browsing/resume affordances.""" + if not self._session_db: + return [] + try: + sessions = self._session_db.list_sessions_rich( + source="cli", + exclude_sources=["tool"], + limit=limit, + ) + except Exception: + return [] + return [s for s in sessions if s.get("id") != self.session_id] + + def _show_recent_sessions(self, *, reason: str = "history", limit: int = 10) -> bool: + """Render recent sessions inline from the active chat TUI. + + Returns True when something was shown, False if no session list was available. + """ + sessions = self._list_recent_sessions(limit=limit) + if not sessions: + return False + + from hermes_cli.main import _relative_time + + print() + if reason == "history": + print("(._.) No messages in the current chat yet — here are recent sessions you can resume:") + else: + print(" Recent sessions:") + print() + print(f" {'Title':<32} {'Preview':<40} {'Last Active':<13} {'ID'}") + print(f" {'─' * 32} {'─' * 40} {'─' * 13} {'─' * 24}") + for session in sessions: + title = (session.get("title") or "—")[:30] + preview = (session.get("preview") or "")[:38] + last_active = _relative_time(session.get("last_active")) + print(f" {title:<32} {preview:<40} {last_active:<13} {session['id']}") + print() + print(" Use /resume to continue where you left off.") + print() + return True + def show_history(self): """Display conversation history.""" if not self.conversation_history: - print("(._.) No conversation history yet.") + if not self._show_recent_sessions(reason="history"): + print("(._.) No conversation history yet.") return preview_limit = 400 @@ -3180,6 +3224,8 @@ class HermesCLI: if not target: _cprint(" Usage: /resume ") + if self._show_recent_sessions(reason="resume"): + return _cprint(" Tip: Use /history or `hermes sessions list` to find sessions.") return diff --git a/tests/test_cli_init.py b/tests/test_cli_init.py index 9e04096905..b926d55f53 100644 --- a/tests/test_cli_init.py +++ b/tests/test_cli_init.py @@ -191,6 +191,60 @@ class TestHistoryDisplay: assert "A" * 250 in output assert "A" * 250 + "..." not in output + def test_history_shows_recent_sessions_when_current_chat_is_empty(self, capsys): + cli = _make_cli() + cli.session_id = "current" + cli._session_db = MagicMock() + cli._session_db.list_sessions_rich.return_value = [ + { + "id": "current", + "title": "Current", + "preview": "Current preview", + "last_active": 0, + }, + { + "id": "20260401_201329_d85961", + "title": "Checking Running Hermes Agent", + "preview": "check running gateways for hermes agent", + "last_active": 0, + }, + ] + + cli.show_history() + output = capsys.readouterr().out + + assert "No messages in the current chat yet" in output + assert "Checking Running Hermes Agent" in output + assert "20260401_201329_d85961" in output + assert "/resume" in output + assert "Current preview" not in output + + def test_resume_without_target_lists_recent_sessions(self, capsys): + cli = _make_cli() + cli.session_id = "current" + cli._session_db = MagicMock() + cli._session_db.list_sessions_rich.return_value = [ + { + "id": "current", + "title": "Current", + "preview": "Current preview", + "last_active": 0, + }, + { + "id": "20260401_201329_d85961", + "title": "Checking Running Hermes Agent", + "preview": "check running gateways for hermes agent", + "last_active": 0, + }, + ] + + cli._handle_resume_command("/resume") + output = capsys.readouterr().out + + assert "Recent sessions" in output + assert "Checking Running Hermes Agent" in output + assert "Use /resume to continue" in output + class TestRootLevelProviderOverride: """Root-level provider/base_url in config.yaml must NOT override model.provider."""