fix(state): an adopted Bot Mode chat stays hidden after import

Stranded-session adoption copies a profile bot's chat from the default store
through import_sessions, which dropped hidden. Bot Mode chats are hidden, and
hidden is what makes the canonical Bot Chat canonical: the adopted copy showed
in every session listing, lost the rename guard on its registry title and
became a stale-archive candidate. hidden joins archived and pinned as a
restored flag.

(cherry picked from commit 1deb08a9cfd08845bc1de25cc502c3bcb51e8569)
This commit is contained in:
John Paul Soliva
2026-10-01 19:54:47 +09:00
committed by kshitij
parent e00b87869c
commit db6ffa8ea6
2 changed files with 29 additions and 6 deletions

View File

@@ -42,7 +42,7 @@ _IMPORT_SESSION_INSERT_SQL = """INSERT INTO sessions (
cwd, git_branch, git_repo_root,
billing_provider, billing_base_url, billing_mode,
estimated_cost_usd, actual_cost_usd, cost_status, cost_source,
pricing_version, title, api_call_count, archived, pinned
pricing_version, title, api_call_count, archived, pinned, hidden
)
VALUES (
:id, :source, :user_id, :model, :model_config,
@@ -53,7 +53,7 @@ _IMPORT_SESSION_INSERT_SQL = """INSERT INTO sessions (
:billing_provider, :billing_base_url, :billing_mode,
:estimated_cost_usd, :actual_cost_usd, :cost_status,
:cost_source, :pricing_version, :title,
:api_call_count, :archived, :pinned
:api_call_count, :archived, :pinned, :hidden
)"""
# Columns copied verbatim from the payload; typed columns are converted below.
_IMPORT_PASSTHROUGH_COLS = (
@@ -66,7 +66,9 @@ _IMPORT_INT_COLS = (
_IMPORT_FLOAT_COLS = ("ended_at", "estimated_cost_usd", "actual_cost_usd")
# Durable user decisions, not live runtime state: a restore that drops ``pinned`` hands the "keep"
# session to the next startup prune and the stale-archive sweep, the two things the pin opts out of.
_IMPORT_FLAG_COLS = ("archived", "pinned")
# ``hidden`` is what makes an adopted Bot Mode chat canonical: without it the chat shows in every
# listing, loses its rename guard and becomes a stale-archive candidate.
_IMPORT_FLAG_COLS = ("archived", "pinned", "hidden")
def _rich_select(select_cols: str, where: str, tail: str = "", prompt_select: Optional[str] = "") -> str:

View File

@@ -1,9 +1,9 @@
"""A restored or adopted session keeps the user's durable flags.
``import_sessions`` (the dashboard import of a ``hermes sessions export`` backup, and stranded-session
adoption) restored ``archived`` but not ``pinned``. Pinned is the "keep" flag the startup prune and the
stale-archive sweep both exempt, so an old pinned session came back unpinned and the next startup
deleted it.
adoption) restored ``archived`` but not ``pinned`` or ``hidden``. Pinned is the "keep" flag the startup
prune and the stale-archive sweep both exempt, so an old pinned session came back unpinned and the next
startup deleted it; an adopted Bot Mode chat came back visible and no longer canonical.
"""
from __future__ import annotations
@@ -39,3 +39,24 @@ def test_restored_pinned_session_survives_the_startup_prune(tmp_path):
finally:
source.close()
target.close()
def test_adopted_bot_chat_stays_the_hidden_canonical_chat(tmp_path):
"""Stranded-session adoption imports the donor's Bot Mode chat into the profile store; Bot Mode
chats are hidden, and hidden is what keeps the canonical one out of listings and the stale sweep."""
donor = SessionDB(db_path=tmp_path / "default.db")
profile = SessionDB(db_path=tmp_path / "profile.db")
try:
donor.create_session(SESSION_ID, source="tui")
donor.set_session_title(SESSION_ID, SessionDB.CANONICAL_BOT_CHAT_TITLE)
donor.set_session_hidden(SESSION_ID, True)
donor.append_message(SESSION_ID, "user", "hi bot")
assert profile.adopt_session_lineage_from(donor, SESSION_ID)["adopted"]
assert profile.get_session(SESSION_ID)["hidden"]
assert SESSION_ID not in {s["id"] for s in profile.list_sessions_rich(limit=50)}
assert profile.archive_stale_sessions(idle_days=0) == 0
finally:
donor.close()
profile.close()