From 7700b2a7ce4315243163424cee663090c4c68336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=AA=E7=88=AA?= Date: Wed, 22 Apr 2026 11:01:47 +0800 Subject: [PATCH] fix(gateway): read /status token usage from SQLite --- gateway/run.py | 19 ++++++++++++++++++- tests/gateway/test_status_command.py | 26 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/gateway/run.py b/gateway/run.py index f68e71c9afb8..1628a0fe99b1 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -4972,6 +4972,21 @@ async def _handle_profile_command(self, event: MessageEvent) -> str: return "\n".join(lines) + def _status_total_tokens(self, session_entry: "SessionEntry") -> int: + """Return total tokens for /status, preferring SQLite session stats.""" + if self._session_db: + try: + row = self._session_db.get_session(session_entry.session_id) + except Exception: + row = None + if row: + input_tokens = int(row.get("input_tokens") or 0) + output_tokens = int(row.get("output_tokens") or 0) + cache_read_tokens = int(row.get("cache_read_tokens") or 0) + cache_write_tokens = int(row.get("cache_write_tokens") or 0) + return input_tokens + output_tokens + cache_read_tokens + cache_write_tokens + return int(getattr(session_entry, "total_tokens", 0) or 0) + async def _handle_status_command(self, event: MessageEvent) -> str: """Handle /status command.""" source = event.source @@ -4990,6 +5005,8 @@ async def _handle_status_command(self, event: MessageEvent) -> str: except Exception: title = None + total_tokens = self._status_total_tokens(session_entry) + lines = [ "📊 **Hermes Gateway Status**", "", @@ -5000,7 +5017,7 @@ async def _handle_status_command(self, event: MessageEvent) -> str: lines.extend([ f"**Created:** {session_entry.created_at.strftime('%Y-%m-%d %H:%M')}", f"**Last Activity:** {session_entry.updated_at.strftime('%Y-%m-%d %H:%M')}", - f"**Tokens:** {session_entry.total_tokens:,}", + f"**Tokens:** {total_tokens:,}", f"**Agent Running:** {'Yes ⚡' if is_running else 'No'}", "", f"**Connected Platforms:** {', '.join(connected_platforms)}", diff --git a/tests/gateway/test_status_command.py b/tests/gateway/test_status_command.py index 50e1c52cc293..35e578c03ac4 100644 --- a/tests/gateway/test_status_command.py +++ b/tests/gateway/test_status_command.py @@ -54,6 +54,7 @@ def _make_runner(session_entry: SessionEntry): runner._pending_messages = {} runner._pending_approvals = {} runner._session_db = MagicMock() + runner._session_db.get_session.return_value = None runner._session_db.get_session_title.return_value = None runner._reasoning_config = None runner._provider_routing = {} @@ -113,6 +114,31 @@ async def test_status_command_includes_session_title_when_present(): assert "**Title:** My titled session" in result +@pytest.mark.asyncio +async def test_status_command_prefers_sqlite_token_counts_over_session_store_entry(): + session_entry = SessionEntry( + session_key=build_session_key(_make_source()), + session_id="sess-1", + created_at=datetime.now(), + updated_at=datetime.now(), + platform=Platform.TELEGRAM, + chat_type="dm", + total_tokens=0, + ) + runner = _make_runner(session_entry) + runner._session_db.get_session.return_value = { + "id": "sess-1", + "input_tokens": 120, + "output_tokens": 45, + "cache_read_tokens": 30, + "cache_write_tokens": 5, + } + + result = await runner._handle_message(_make_event("/status")) + + assert "**Tokens:** 200" in result + + @pytest.mark.asyncio async def test_agents_command_reports_active_agents_and_processes(monkeypatch): session_key = build_session_key(_make_source())