[eric] sessions: get_all_sessions promotes only disk sessions the layout still cards, so deleted chats stop resurrecting on reopen

This commit is contained in:
ciregenz
2026-06-16 04:44:15 -07:00
parent a99e310940
commit 3998a8d6d9
2 changed files with 56 additions and 8 deletions
+23 -8
View File
@@ -4639,17 +4639,20 @@ class AgentManager:
def get_all_sessions(self, dashboard_id: str | None = None) -> list[AgentSession]:
if not dashboard_id:
return list(self.sessions.values())
# Memory first, then promote any on-disk sessions for this dashboard
# that aren't loaded yet. Imported sessions (and ones not resumed since
# a restart) live on disk but not in memory, so without the disk pass
# their cards render blank, the frontend's AgentCard returns null when
# a card's session is missing from the agents slice. Promoting into
# self.sessions bounds the disk read to once per session per run, like
# resume_session. Mirrors get_browser_agent_children's memory+disk walk.
# Memory first, then promote on-disk sessions for this dashboard, but
# ONLY ones the dashboard's layout still has a card for. A session keeps
# its dashboard_id when its card is deleted, so promoting by tag alone
# resurrected deleted chats on every reopen; the layout's cards are the
# real source of truth for what's on the board. Imported sessions ARE in
# the layout, so they still surface, and this bounds the disk read to
# once per session per run, like resume_session.
result = [s for s in self.sessions.values() if s.dashboard_id == dashboard_id]
seen = {s.id for s in result}
card_ids = self._dashboard_card_ids(dashboard_id)
for sid, data in _load_all_session_data():
if sid in seen or data.get("dashboard_id") != dashboard_id:
if sid in seen or sid not in card_ids:
continue
if data.get("dashboard_id") != dashboard_id:
continue
try:
sess = AgentSession(**data)
@@ -4661,6 +4664,18 @@ class AgentManager:
result.append(sess)
return result
def _dashboard_card_ids(self, dashboard_id: str) -> set[str]:
"""Session ids the dashboard's layout currently has agent cards for.
Read straight off disk (no dashboards-module import, avoids a cycle)."""
try:
import os
import backend.config.paths as _paths
from backend.config.json_store import read_json_or_none
d = read_json_or_none(os.path.join(_paths.DASHBOARDS_DIR, f"{dashboard_id}.json")) or {}
return set((d.get("layout", {}).get("cards") or {}).keys())
except Exception:
return set()
def get_session(self, session_id: str) -> Optional[AgentSession]:
return self.sessions.get(session_id)
+33
View File
@@ -323,6 +323,39 @@ def test_dashboard_export_import_carries_agent_cards_and_transcript(tmp_path, mo
assert sum(len(s.messages) for s in found) == 2, "and with their transcripts"
def test_get_all_sessions_does_not_resurrect_deleted_cards(tmp_path, monkeypatch):
# Deleting a card removes it from the layout but the session keeps its
# dashboard_id on disk. get_all_sessions must surface only sessions the
# layout still has a card for, or deleted chats come back on every reopen.
from backend.apps.agents import agent_manager as am
import backend.config.paths as paths
sdir = tmp_path / "sessions"
ddir = tmp_path / "dashboards"
sdir.mkdir()
ddir.mkdir()
monkeypatch.setattr(am, "SESSIONS_DIR", str(sdir))
monkeypatch.setattr(paths, "DASHBOARDS_DIR", str(ddir))
monkeypatch.setattr(am.agent_manager, "sessions", {})
did = "d1"
def sess(sid):
return {
"id": sid, "name": sid, "status": "completed", "model": "sonnet",
"mode": "agent", "messages": [], "branches": {}, "active_branch_id": "main",
"dashboard_id": did,
}
(sdir / "kept.json").write_text(json.dumps(sess("kept")))
(sdir / "deleted.json").write_text(json.dumps(sess("deleted"))) # still tagged, card gone
# The layout has a card only for "kept" (the user deleted "deleted"'s card).
(ddir / f"{did}.json").write_text(json.dumps({"id": did, "layout": {"cards": {"kept": {"session_id": "kept"}}}}))
ids = {s.id for s in am.agent_manager.get_all_sessions(dashboard_id=did)}
assert "kept" in ids, "a session the layout still has a card for must surface"
assert "deleted" not in ids, "a session whose card was deleted must NOT resurrect"
def test_dashboard_serialize_rewrites_refs_to_bundle_ids():
from backend.apps.swarm.entities.dashboards import DashboardExportable
from backend.apps.swarm.models import EntityType