Files
hermes-agent/tests/gateway/test_session_list_allowed_sources.py
Teknium 5eb6cd82b2 fix(sessions): /save lands under $HERMES_HOME, widen browse+TUI picker, force-refresh ollama-cloud on setup (#16296)
Four independent session-UX bugs reported by an external user (#16294).

/save wrote hermes_conversation_<ts>.json to CWD — invisible to
'hermes sessions browse' and easy to lose. Snapshots now write under
~/.hermes/sessions/saved/ and the command prints the absolute path plus
a 'hermes --resume <id>' hint for the live DB-indexed session.

'hermes sessions browse' default --limit raised from 50 to 500. With the
old ceiling, users with moderately long histories saw only the most
recent 50 rows and assumed older sessions had been lost.

TUI session.list (`/resume` picker) switched from a hardcoded allow-list
of 13 gateway source names to a deny-list of just { 'tool' }. Sessions
tagged acp / webhook / user-defined HERMES_SESSION_SOURCE values and
any newly-added platform now surface. Default limit 20 → 200.

ollama-cloud provider setup passes force_refresh=True to
fetch_ollama_cloud_models() so a user entering their API key sees the
fresh catalog (e.g. deepseek v4 flash, kimi k2.6) immediately instead
of waiting up to an hour for the disk cache TTL to expire.

Closes #16294.
2026-04-26 18:49:48 -07:00

105 lines
3.9 KiB
Python

"""Regression tests for the TUI gateway's ``session.list`` handler.
History:
- The original implementation hardcoded an allow-list of known gateway
sources (``tui, cli, telegram, discord, slack, ...``). New or unlisted
sources (``acp``, ``webhook``, user-defined ``HERMES_SESSION_SOURCE``
values, newly-added platforms) were silently dropped from the resume
picker — users reported "lots of sessions are missing from browse
but exist in .hermes/sessions."
- The handler now deny-lists only the internal/noisy source ``tool``
(sub-agent runs) and surfaces every other source to the picker.
- The default ``limit`` raised from 20 to 200 so longer-running users
can scroll through their history without hitting an artificial cap.
"""
from __future__ import annotations
from tui_gateway import server
class _StubDB:
def __init__(self, rows):
self.rows = rows
self.calls: list[dict] = []
def list_sessions_rich(self, **kwargs):
self.calls.append(kwargs)
return list(self.rows)
def _call(limit: int | None = None):
params: dict = {}
if limit is not None:
params["limit"] = limit
return server.handle_request({
"id": "1",
"method": "session.list",
"params": params,
})
def test_session_list_surfaces_all_user_facing_sources(monkeypatch):
"""acp / webhook / custom sources should all appear; only ``tool`` is hidden."""
rows = [
{"id": "tui-1", "source": "tui", "started_at": 9},
{"id": "tool-1", "source": "tool", "started_at": 8},
{"id": "tg-1", "source": "telegram", "started_at": 7},
{"id": "acp-1", "source": "acp", "started_at": 6},
{"id": "cli-1", "source": "cli", "started_at": 5},
{"id": "webhook-1", "source": "webhook", "started_at": 4},
{"id": "custom-1", "source": "my-custom-source", "started_at": 3},
]
db = _StubDB(rows)
monkeypatch.setattr(server, "_get_db", lambda: db)
resp = _call(limit=10)
ids = [s["id"] for s in resp["result"]["sessions"]]
# Every human-facing source — including previously-hidden acp, webhook,
# and custom sources — must surface in the picker now.
assert "tg-1" in ids
assert "tui-1" in ids
assert "cli-1" in ids
assert "acp-1" in ids, "acp sessions were being hidden by the old allow-list"
assert "webhook-1" in ids, "webhook sessions were being hidden by the old allow-list"
assert "custom-1" in ids, "custom HERMES_SESSION_SOURCE values were being hidden"
# Only internal sub-agent runs stay hidden.
assert "tool-1" not in ids
def test_session_list_default_limit_is_200(monkeypatch):
"""Default limit should be wide enough for long-running users."""
db = _StubDB([{"id": "x", "source": "cli", "started_at": 1}])
monkeypatch.setattr(server, "_get_db", lambda: db)
_call() # no explicit limit
# fetch_limit = max(limit * 2, 200); limit defaults to 200, so 400.
assert db.calls[0].get("limit") == 400, db.calls[0]
def test_session_list_respects_explicit_limit(monkeypatch):
db = _StubDB([{"id": "x", "source": "cli", "started_at": 1}])
monkeypatch.setattr(server, "_get_db", lambda: db)
_call(limit=10)
# fetch_limit = max(limit * 2, 200) = 200 when limit is small.
assert db.calls[0].get("limit") == 200, db.calls[0]
def test_session_list_preserves_ordering_after_filter(monkeypatch):
rows = [
{"id": "newest", "source": "telegram", "started_at": 5},
{"id": "internal", "source": "tool", "started_at": 4},
{"id": "middle", "source": "tui", "started_at": 3},
{"id": "also-visible", "source": "webhook", "started_at": 2},
{"id": "oldest", "source": "discord", "started_at": 1},
]
monkeypatch.setattr(server, "_get_db", lambda: _StubDB(rows))
resp = _call()
ids = [s["id"] for s in resp["result"]["sessions"]]
assert ids == ["newest", "middle", "also-visible", "oldest"]