mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 06:51:16 +08:00
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.
105 lines
3.9 KiB
Python
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"]
|