diff --git a/tests/gateway/test_session_list_all_sources.py b/tests/gateway/test_session_list_all_sources.py new file mode 100644 index 000000000..f354c6029 --- /dev/null +++ b/tests/gateway/test_session_list_all_sources.py @@ -0,0 +1,84 @@ +"""Regression test for the TUI gateway's ``session.list`` handler. + +Reported during the TUI v2 blitz retest: the ``/resume`` modal inside a +TUI session only surfaced ``tui``/``cli`` rows — telegram/discord/whatsapp +sessions stayed hidden even though the user could still paste the id +directly into ``hermes --tui --resume `` and get a working session. + +The fix removes the adapter-kind filter so every session the DB surfaces +appears in the picker, sorted by ``started_at`` like before. +""" + +from __future__ import annotations + +import types + +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 = 20): + return server.handle_request({ + "id": "1", + "method": "session.list", + "params": {"limit": limit}, + }) + + +def test_session_list_does_not_filter_by_source(monkeypatch): + rows = [ + {"id": "tui-1", "source": "tui", "title": "a", "preview": "", "started_at": 3, "message_count": 1}, + {"id": "tg-1", "source": "telegram", "title": "b", "preview": "", "started_at": 2, "message_count": 1}, + {"id": "cli-1", "source": "cli", "title": "c", "preview": "", "started_at": 1, "message_count": 1}, + ] + db = _StubDB(rows) + monkeypatch.setattr(server, "_get_db", lambda: db) + + resp = _call(limit=10) + + assert "result" in resp, resp + assert len(db.calls) == 1 + assert db.calls[0].get("source") is None, db.calls[0] + assert db.calls[0].get("limit") == 10 + + kinds = [s["source"] for s in resp["result"]["sessions"]] + assert "telegram" in kinds and "tui" in kinds and "cli" in kinds, kinds + + +def test_session_list_preserves_ordering(monkeypatch): + rows = [ + {"id": "newest", "source": "telegram", "title": "", "preview": "", "started_at": 5, "message_count": 1}, + {"id": "middle", "source": "tui", "title": "", "preview": "", "started_at": 3, "message_count": 1}, + {"id": "oldest", "source": "discord", "title": "", "preview": "", "started_at": 1, "message_count": 1}, + ] + monkeypatch.setattr(server, "_get_db", lambda: _StubDB(rows)) + + resp = _call() + ids = [s["id"] for s in resp["result"]["sessions"]] + + assert ids == ["newest", "middle", "oldest"] + + +def test_session_list_surfaces_missing_fields_as_empty(monkeypatch): + rows = [{"id": "bare", "source": "whatsapp"}] + monkeypatch.setattr(server, "_get_db", lambda: _StubDB(rows)) + + sess = _call()["result"]["sessions"][0] + + assert sess == { + "id": "bare", + "title": "", + "preview": "", + "started_at": 0, + "message_count": 0, + "source": "whatsapp", + } diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 73bc39ffb..36a7bc6dd 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -1231,12 +1231,12 @@ def _(rid, params: dict) -> dict: @method("session.list") def _(rid, params: dict) -> dict: try: - db = _get_db() - # Show both TUI and CLI sessions — TUI is the successor to the CLI, - # so users expect to resume their old CLI sessions here too. - tui = db.list_sessions_rich(source="tui", limit=params.get("limit", 20)) - cli = db.list_sessions_rich(source="cli", limit=params.get("limit", 20)) - rows = sorted(tui + cli, key=lambda s: s.get("started_at") or 0, reverse=True)[:params.get("limit", 20)] + # Show sessions from every adapter — users resume telegram/discord/etc + # sessions by pasting the id directly, so the picker should surface them + # too. Children (subagents/compression runs) stay filtered out via the + # hermes_state default. + limit = params.get("limit", 20) + rows = _get_db().list_sessions_rich(source=None, limit=limit) return _ok(rid, {"sessions": [ {"id": s["id"], "title": s.get("title") or "", "preview": s.get("preview") or "", "started_at": s.get("started_at") or 0, "message_count": s.get("message_count") or 0,