[eric] agents: session_store convention-clean (5 funcs off leading-_) + FIX auth/signout _save_session import bug

This commit is contained in:
ciregenz
2026-06-23 12:16:38 -07:00
parent 027c6ac311
commit 9b0116ca41
14 changed files with 52 additions and 44 deletions
+2 -2
View File
@@ -32,8 +32,8 @@ from backend.apps.agents.core.error_classify import (
# call time (dodging a circular import), and the disk-resilience test monkeypatches it here.
from backend.config.paths import SESSIONS_DIR
from backend.apps.agents.manager.session.session_store import (
_save_session as save_session,
_load_session_data as load_session_data,
save_session,
load_session_data,
)
from backend.apps.agents.manager.streaming.state import ThinkingState, TurnState
from backend.apps.agents.manager.streaming.hook_context import HookContext
@@ -18,7 +18,7 @@ from backend.apps.agents.core.models import (
)
from backend.apps.agents.core.ws_manager import ws_manager
from backend.apps.settings.settings import load_settings
from backend.apps.agents.manager.session.session_store import _load_session_data as load_session_data
from backend.apps.agents.manager.session.session_store import load_session_data
from backend.apps.agents.manager.session.apply_context_window import apply_context_window
from backend.apps.agents.manager.session.workspace_git import (
detect_git_identity,
@@ -15,8 +15,8 @@ from backend.apps.agents.core.ws_manager import ws_manager
from backend.apps.settings.settings import load_settings
from backend.apps.agents.manager import browser_dispatch
from backend.apps.agents.manager.session.session_store import (
_load_session_data as load_session_data,
_save_session as save_session,
load_session_data,
save_session,
)
from backend.apps.agents.manager.session.apply_context_window import apply_context_window
from backend.apps.agents.manager.prompt.tool_catalog import get_all_tool_names
@@ -12,7 +12,7 @@ from typeguard import typechecked
from backend.apps.agents.core.models import AgentSession, Message
from backend.apps.agents.core.ws_manager import ws_manager
from backend.apps.agents.manager.session.session_store import _save_session as save_session
from backend.apps.agents.manager.session.session_store import save_session
from backend.apps.settings.settings import load_settings
logger = logging.getLogger(__name__)
@@ -12,9 +12,9 @@ from typeguard import typechecked
from backend.apps.agents.core.models import AgentSession
from backend.apps.agents.core.ws_manager import ws_manager
from backend.apps.agents.manager.session.session_store import (
_delete_session_file as delete_session_file,
_load_all_session_data as load_all_session_data,
_save_session as save_session,
delete_session_file,
load_all_session_data,
save_session,
build_search_text,
)
from backend.apps.agents.manager.session.cloud_sync import sync_session_close
@@ -11,7 +11,7 @@ from typeguard import typechecked
from backend.apps.agents.core.models import AgentSession, Message, MessageBranch
from backend.apps.agents.manager.session.apply_context_window import apply_context_window
from backend.apps.agents.manager.session.session_store import _load_session_data as load_session_data
from backend.apps.agents.manager.session.session_store import load_session_data
@typechecked
@@ -1,45 +1,53 @@
import os
from backend.apps.agents.core.models import AgentSession
from typing import Dict, List, Optional, Tuple
from typeguard import typechecked
from backend.config.json_store import read_json_or_none, atomic_write_json
def _sessions_dir() -> str:
@typechecked
def sessions_dir() -> str:
# Resolve live so test patches on either the paths module or the
# agent_manager facade re-export land on the same directory.
from backend.apps.agents import agent_manager
return agent_manager.SESSIONS_DIR
def _save_session(session_id: str, doc_data: dict):
sessions_dir = _sessions_dir()
os.makedirs(sessions_dir, exist_ok=True)
atomic_write_json(os.path.join(sessions_dir, f"{session_id}.json"), doc_data)
@typechecked
def save_session(session_id: str, doc_data: Dict) -> None:
dir_path = sessions_dir()
os.makedirs(dir_path, exist_ok=True)
atomic_write_json(os.path.join(dir_path, f"{session_id}.json"), doc_data)
def _load_session_data(session_id: str) -> dict | None:
return read_json_or_none(os.path.join(_sessions_dir(), f"{session_id}.json"))
@typechecked
def load_session_data(session_id: str) -> Optional[Dict]:
return read_json_or_none(os.path.join(sessions_dir(), f"{session_id}.json"))
def _delete_session_file(session_id: str):
path = os.path.join(_sessions_dir(), f"{session_id}.json")
@typechecked
def delete_session_file(session_id: str) -> None:
path = os.path.join(sessions_dir(), f"{session_id}.json")
if os.path.exists(path):
os.remove(path)
def _load_all_session_data() -> list[tuple[str, dict]]:
@typechecked
def load_all_session_data() -> List[Tuple[str, Dict]]:
results = []
sessions_dir = _sessions_dir()
if not os.path.exists(sessions_dir):
dir_path = sessions_dir()
if not os.path.exists(dir_path):
return results
for fname in os.listdir(sessions_dir):
for fname in os.listdir(dir_path):
if fname.endswith(".json"):
data = read_json_or_none(os.path.join(sessions_dir, fname))
data = read_json_or_none(os.path.join(dir_path, fname))
if data is not None:
results.append((fname[:-5], data))
return results
@typechecked
def build_search_text(session: AgentSession, max_len: int = 5000) -> str:
"""Build a search-indexing string from the session name and message content."""
parts = [session.name or ""]
+2 -2
View File
@@ -211,7 +211,7 @@ async def signout():
# Best-effort: failures here shouldn't block the sign-out itself.
try:
from backend.apps.agents.agent_manager import agent_manager
from backend.apps.agents.agent_manager import _save_session
from backend.apps.agents.manager.session.session_store import save_session
running = list(agent_manager.tasks.keys())
for session_id in running:
@@ -227,7 +227,7 @@ async def signout():
if sess.sdk_session_id:
sess.sdk_session_id = None
try:
_save_session(sess.id, sess.model_dump(mode="json"))
save_session(sess.id, sess.model_dump(mode="json"))
except Exception as e:
logger.warning("signout: save_session(%s) failed: %s", sess.id, e)
+4 -4
View File
@@ -374,7 +374,7 @@ def _strip_orphan_session_cards(data: dict) -> None:
non-destructive: a wrong check can only hide a card for one response, not
delete it. Drafts have no backend session yet, so they're always kept."""
from backend.apps.agents.agent_manager import agent_manager
from backend.apps.agents.manager.session.session_store import _load_session_data
from backend.apps.agents.manager.session.session_store import load_session_data
layout = data.get("layout")
if not isinstance(layout, dict):
return
@@ -385,7 +385,7 @@ def _strip_orphan_session_cards(data: dict) -> None:
def gone(sid: str) -> bool:
if sid.startswith("draft-") or sid in agent_manager.sessions:
return False
return _load_session_data(sid) is None
return load_session_data(sid) is None
orphans = [sid for sid in cards if gone(sid)]
for sid in orphans:
@@ -463,7 +463,7 @@ async def duplicate_dashboard(dashboard_id: str):
now = datetime.now().isoformat()
from backend.apps.agents.agent_manager import agent_manager
from backend.apps.agents.manager.session.session_store import _save_session
from backend.apps.agents.manager.session.session_store import save_session
source_layout = source_data.get("layout", {}) or {}
source_browser_cards = source_layout.get("browser_cards", {}) or {}
@@ -513,7 +513,7 @@ async def duplicate_dashboard(dashboard_id: str):
new_sess.browser_id = browser_id_remap[old_browser_id]
if old_parent_sid and old_parent_sid in session_id_remap:
new_sess.parent_session_id = session_id_remap[old_parent_sid]
_save_session(new_sess.id, new_sess.model_dump(mode="json"))
save_session(new_sess.id, new_sess.model_dump(mode="json"))
new_cards: dict[str, dict] = {}
for old_sid, card in source_cards.items():
+3 -3
View File
@@ -150,12 +150,12 @@ def _write(did: str, doc: dict) -> None:
def _retag_sessions(session_ids, dashboard_id: str) -> None:
# Best-effort: a hiccup here must not orphan the just-written dashboard.
from backend.apps.agents.manager.session.session_store import _load_session_data, _save_session
from backend.apps.agents.manager.session.session_store import load_session_data, save_session
for sid in session_ids:
try:
d = _load_session_data(sid)
d = load_session_data(sid)
if d is not None:
d["dashboard_id"] = dashboard_id
_save_session(sid, d)
save_session(sid, d)
except Exception:
pass
+6 -6
View File
@@ -45,8 +45,8 @@ class SessionExportable:
if sess is not None:
d = sess.model_dump(mode="json")
else:
from backend.apps.agents.manager.session.session_store import _load_session_data
d = _load_session_data(local_id)
from backend.apps.agents.manager.session.session_store import load_session_data
d = load_session_data(local_id)
if d is None:
return None
return cls(local_id, d.get("name") or "Agent", d)
@@ -85,7 +85,7 @@ class SessionExportable:
@classmethod
def import_(cls, payload: dict, files: dict[str, bytes], remap: RemapTable) -> str:
from backend.apps.agents.manager.session.session_store import _save_session
from backend.apps.agents.manager.session.session_store import save_session
sid = uuid4().hex
now = datetime.now(timezone.utc).isoformat()
# Older bundles (made before transcripts were carried) have no messages;
@@ -118,10 +118,10 @@ class SessionExportable:
"created_at": now,
"closed_at": now,
}
_save_session(sid, doc)
save_session(sid, doc)
return sid
@classmethod
def rollback(cls, local_id: str) -> None:
from backend.apps.agents.manager.session.session_store import _delete_session_file
_delete_session_file(local_id)
from backend.apps.agents.manager.session.session_store import delete_session_file
delete_session_file(local_id)
+2 -2
View File
@@ -77,11 +77,11 @@ def test_read_empty_file_returns_none(tmp_path):
def test_sessions_skip_corrupt_and_roundtrip(tmp_path, monkeypatch):
from backend.apps.agents import agent_manager as am
from backend.apps.agents.manager.session.session_store import _load_all_session_data
from backend.apps.agents.manager.session.session_store import load_all_session_data
monkeypatch.setattr(am, "SESSIONS_DIR", str(tmp_path))
am.save_session("good", {"id": "good", "v": 1})
(tmp_path / "bad.json").write_text("{ truncated session ,,,")
loaded = dict(_load_all_session_data())
loaded = dict(load_all_session_data())
assert loaded == {"good": {"id": "good", "v": 1}}
assert (tmp_path / "bad.json").exists() # corrupt file preserved, not deleted
assert am.load_session_data("good") == {"id": "good", "v": 1}
+2 -2
View File
@@ -204,7 +204,7 @@ def test_session_import_restores_transcript_without_granting_mcp(monkeypatch):
from backend.apps.swarm.exportable import RemapTable
from backend.apps.agents.manager.session import session_store
saved: dict = {}
monkeypatch.setattr(session_store, "_save_session", lambda sid, doc: saved.update({sid: doc}))
monkeypatch.setattr(session_store, "save_session", lambda sid, doc: saved.update({sid: doc}))
payload = {
"name": "A", "model": "sonnet", "mode": "agent",
"messages": [{"id": "m1", "role": "user", "content": "hi", "branch_id": "main"}],
@@ -230,7 +230,7 @@ def test_session_import_old_bundle_without_transcript(monkeypatch):
from backend.apps.swarm.exportable import RemapTable
from backend.apps.agents.manager.session import session_store
saved: dict = {}
monkeypatch.setattr(session_store, "_save_session", lambda sid, doc: saved.update({sid: doc}))
monkeypatch.setattr(session_store, "save_session", lambda sid, doc: saved.update({sid: doc}))
sid = SessionExportable.import_({"name": "Old", "model": "sonnet"}, {}, RemapTable())
doc = saved[sid]
assert doc["messages"] == []
+1 -1
View File
@@ -685,7 +685,7 @@ def test_dashboard_get_strips_only_orphan_session_cards():
fake_mgr = SimpleNamespace(sessions={"live": object()})
on_disk = {"ondisk": {"id": "ondisk"}}
with patch("backend.apps.agents.agent_manager.agent_manager", fake_mgr), \
patch("backend.apps.agents.manager.session.session_store._load_session_data",
patch("backend.apps.agents.manager.session.session_store.load_session_data",
side_effect=lambda sid: on_disk.get(sid)):
D._strip_orphan_session_cards(data)
assert set(data["layout"]["cards"].keys()) == {"live", "ondisk", "draft-1"}, "only the ghost should be dropped"