Files
hermes-agent/tests/tui_gateway/test_session_hidden_rpc.py
T
Teknium 66221397a1 fix(bot-mode): always hide Bot Mode sessions from the global Sessions sidebar
Bot Mode's group chats spawned one per-member session per room, and those
"Group: ..." rows (plus canonical Bot Chats when the old eye-toggle pref was
off) flooded the global Sessions sidebar — a 6-bot room dumped six identical
rows into recents (reported with screenshot, Aug 17).

Plugin (apps/desktop/src/plugins/hermes-bots/plugin.js):
- session.create now passes hidden:true UNCONDITIONALLY for both canonical
  Bot Chats and group-room member sessions; the $hideBotChats pref, its eye
  toggle, and its storage hydrate are removed (Bot Mode sessions are plumbing
  or plugin-owned forever-chats, never scratch conversations).
- hideOwnedBotSessions(): idempotent reconciliation sweep over every owned
  session id (bot meta canonical chats + each room's member sessions) via
  session.set_hidden, run on plugin load and on each gateway reconnect, so
  rows born visible under the old pref get cleaned up.
- The Bots session browser and canonical-chat recovery scan pass
  include_hidden:true so they still see the rows they own.

Gateway (tui_gateway/methods_session.py):
- session.list honors an include_hidden param (default off — the resume
  picker and all global callers keep dropping hidden rows).
- session.set_hidden gains a durable fallback: when no LIVE runtime session
  matches, resolve the stored session id in the target profile's state.db
  (via resolve_session_id) and flip the flag there. The sweep holds stored
  ids for chats that aren't live; the old live-only lookup 4001'd them.

Validated E2E with real imports against a temp HERMES_HOME: born-hidden row
(hidden=1), profile-scoped session.list default vs include_hidden (0 vs 1),
and stored-id sweep on a non-live legacy row (hidden=1). Plugin suite
167/167; new RPC regression tests in tests/tui_gateway/test_session_hidden_rpc.py.
2026-08-17 16:02:42 -07:00

72 lines
2.6 KiB
Python

"""RPC-level tests for the generic hidden-session surface (tui_gateway).
Covers the two seams Bot Mode's "sessions are always hidden" policy leans on:
* ``session.set_hidden`` resolves a DURABLE stored session id when no live
runtime session matches — plugins reconciling sessions they own (Bot Mode's
hide sweep) hold stored ids for chats that aren't live right now. The old
live-only lookup failed those with 4001 and the sweep silently no-opped.
* ``session.list`` honors ``include_hidden`` so owning surfaces (the Bots
pane's per-profile browser) can still enumerate the rows they hid, while
every default caller keeps the hidden rows dropped.
"""
import pytest
import tui_gateway.server as srv
import tui_gateway.methods_session # noqa: F401 (registers the RPC methods)
from hermes_state import SessionDB
@pytest.fixture
def db(tmp_path, monkeypatch):
database = SessionDB(tmp_path / "state.db")
monkeypatch.setattr(srv, "_get_db", lambda: database)
try:
yield database
finally:
database.close()
def _call(method: str, params: dict) -> dict:
return srv._methods[method](1, params)
def _seed(db, sid: str) -> None:
db.create_session(sid, source="desktop")
db._conn.execute("UPDATE sessions SET message_count = 1 WHERE id = ?", (sid,))
db._conn.commit()
def test_set_hidden_resolves_stored_id_without_live_session(db):
"""A stored (non-live) session id must be hideable — the sweep path."""
_seed(db, "stored-chat")
assert srv._find_live_session_by_key("stored-chat") is None
envelope = _call("session.set_hidden", {"session_id": "stored-chat", "hidden": True})
assert "error" not in envelope, envelope
assert envelope["result"]["hidden"] is True
assert db.get_session("stored-chat")["hidden"] == 1
# And back — unhide through the same durable path.
envelope = _call("session.set_hidden", {"session_id": "stored-chat", "hidden": False})
assert "error" not in envelope, envelope
assert db.get_session("stored-chat")["hidden"] == 0
def test_set_hidden_unknown_id_still_errors(db):
envelope = _call("session.set_hidden", {"session_id": "no-such-session", "hidden": True})
assert envelope.get("error"), envelope
def test_session_list_include_hidden(db):
_seed(db, "plain-chat")
_seed(db, "bot-chat")
assert db.set_session_hidden("bot-chat", True) is True
default_rows = _call("session.list", {})["result"]["sessions"]
assert {s["id"] for s in default_rows} == {"plain-chat"}
all_rows = _call("session.list", {"include_hidden": True})["result"]["sessions"]
assert {s["id"] for s in all_rows} == {"plain-chat", "bot-chat"}