fix: exempt the hidden canonical Bot Chat from the idle auto-archive sweep
The archived-holder carve-out in _set_session_title lets the next Bot open claim the "Bot Chat" title from an archived canonical row. That is correct for a deliberate sidebar archive, but archive_stale_sessions (the opt-in sessions.auto_archive sweep) could also archive an idle hidden Bot Chat with end_reason NULL, which unarchive_recoverable_session refuses; the next Bot click then stripped the old row's title, retiring the bot's whole history on an idle timer with no way back. Skip the hidden canonical Bot Chat in the sweep SELECT using the same hidden + exact-title predicate set_session_pinned already uses to protect it, so only an explicit archive retires a Bot Chat. Reword the bot-mode doc so it no longer promises the retired (still hidden) chat is reachable from the archive view. Review finding: auto_archive sweep could irreversibly retire the canonical Bot Chat; docs over-promised archive-view reachability.
This commit is contained in:
@@ -247,7 +247,9 @@ class SessionMaintenanceMixin:
|
||||
latest message / ``started_at``); may archive unended sessions. ``archived = 0`` makes
|
||||
repeats no-ops; only lineage tips (``end_reason <> 'compression'``) are candidates — a
|
||||
stale tip archives its chain via :meth:`set_session_archived`, so an old compressed-away
|
||||
root with a recent continuation is never matched."""
|
||||
root with a recent continuation is never matched. The hidden canonical Bot Chat (same
|
||||
predicate as :meth:`set_session_pinned`) is exempt: only a deliberate archive may retire
|
||||
it, since archiving releases its registry title to the next Bot open."""
|
||||
if idle_days is None or idle_days < 0:
|
||||
return 0
|
||||
cutoff = time.time() - float(idle_days) * 86400.0
|
||||
@@ -258,9 +260,10 @@ class SessionMaintenanceMixin:
|
||||
WHERE s.archived = 0
|
||||
AND COALESCE(s.end_reason, '') <> 'compression'
|
||||
{pin_clause}
|
||||
AND NOT (COALESCE(s.hidden, 0) <> 0 AND COALESCE(s.title, '') = ?)
|
||||
AND {_sql_session_last_active("s")} < ?
|
||||
ORDER BY s.started_at ASC
|
||||
""", (cutoff,))
|
||||
""", (self.CANONICAL_BOT_CHAT_TITLE, cutoff))
|
||||
for row in rows:
|
||||
self.set_session_archived(row[0], True)
|
||||
return len(rows)
|
||||
|
||||
@@ -78,6 +78,22 @@ def test_deliberately_archived_canonical_chat_releases_name_for_replacement(db):
|
||||
assert row and row["id"] == "replacement"
|
||||
|
||||
|
||||
def test_auto_archive_sweep_skips_the_canonical_chat(db):
|
||||
"""Only a deliberate archive may retire a Bot Chat; the idle sweep must not
|
||||
(it would strand an unrecoverable, soon-to-be-untitled row)."""
|
||||
import time
|
||||
|
||||
forever = _make_canonical(db)
|
||||
db.create_session("ordinary", source="desktop")
|
||||
stale = time.time() - 10 * 86400
|
||||
db._write_sql("UPDATE sessions SET started_at = ?, last_activity_at = ?", (stale, stale))
|
||||
|
||||
assert db.archive_stale_sessions(3) == 1
|
||||
assert db.get_session("ordinary")["archived"]
|
||||
assert not db.get_session(forever)["archived"]
|
||||
assert db.get_session(forever)["title"] == SessionDB.CANONICAL_BOT_CHAT_TITLE
|
||||
|
||||
|
||||
def test_auto_titler_still_cannot_touch_the_canonical_row(db):
|
||||
# Pre-existing provenance contract, re-pinned here: user-authority title
|
||||
# outranks derived/llm, so the turn-start auto-titler can never displace
|
||||
|
||||
@@ -25,7 +25,7 @@ The roster shows one row per agent profile: avatar, latest-message preview, and
|
||||
:::note The canonical Bot Chat is a forever-chat
|
||||
Typing `/new` (or `/reset`) inside a Bot's canonical chat would fork the relationship into a scratch session — the one thing Bot Mode promises never happens. The composer reroutes it to `/compact` instead: fresh working context, same conversation. Regular sessions on the same profile keep full `/new` freedom.
|
||||
|
||||
Archiving a Bot Chat from the sidebar retires it: the next click on the Bot starts a fresh conversation that becomes the new canonical Bot Chat, and the archived one keeps its history (untitled) in the archive.
|
||||
Archiving a Bot Chat from the sidebar retires it: the next click on the Bot starts a fresh conversation that becomes the new canonical Bot Chat. The retired chat stays archived and hidden — its history is preserved in the database, but it is no longer reachable from the Bot or the archive view. The automatic idle-archive sweep (`sessions.auto_archive`) never retires a Bot Chat; only an explicit archive does.
|
||||
:::
|
||||
|
||||
### Organize bots into sections
|
||||
|
||||
Reference in New Issue
Block a user