diff --git a/src/plyngent/memory/database/store.py b/src/plyngent/memory/database/store.py index 26e350e..d467473 100644 --- a/src/plyngent/memory/database/store.py +++ b/src/plyngent/memory/database/store.py @@ -1,5 +1,6 @@ from __future__ import annotations +from datetime import UTC, datetime from pathlib import Path from typing import TYPE_CHECKING, Self @@ -124,10 +125,10 @@ class MemoryStore: uid: int | None = None, workspace: str | Path | None = None, ) -> Sequence[Session]: - """List sessions; optionally filter by user and/or bound workspace path.""" + """List sessions (most recently updated first); filter by user/workspace.""" ws = normalize_workspace(workspace) async with self._session_factory() as session: - stmt = select(Session).order_by(Session.sid) + stmt = select(Session).order_by(Session.updated_at.desc(), Session.sid.desc()) if uid is not None: stmt = stmt.where(Session.uid == uid) if ws is not None: @@ -135,6 +136,27 @@ class MemoryStore: result = await session.execute(stmt) return result.scalars().all() + async def get_latest_session( + self, + *, + uid: int | None = None, + workspace: str | Path | None = None, + ) -> Session | None: + """Return the most recently updated session for the filters, if any.""" + sessions = await self.list_sessions(uid=uid, workspace=workspace) + return sessions[0] if sessions else None + + async def touch_session(self, sid: int) -> Session | None: + """Bump ``updated_at`` so the session ranks as lately used.""" + async with self._session_factory() as session: + row = await session.get(Session, sid) + if row is None: + return None + row.updated_at = datetime.now(UTC) + await session.commit() + await session.refresh(row) + return row + async def update_session_workspace( self, sid: int, @@ -148,6 +170,7 @@ class MemoryStore: msg = f"session not found: {sid}" raise ValueError(msg) row.workspace = ws + row.updated_at = datetime.now(UTC) await session.commit() await session.refresh(row) return row @@ -167,6 +190,9 @@ class MemoryStore: seq = 0 if last_seq is None else last_seq + 1 row = Message(sid=sid, seq=seq, data=data) session.add(row) + chat = await session.get(Session, sid) + if chat is not None: + chat.updated_at = datetime.now(UTC) await session.commit() await session.refresh(row) return row diff --git a/tests/test_memory/test_store.py b/tests/test_memory/test_store.py index b330c7b..81fc902 100644 --- a/tests/test_memory/test_store.py +++ b/tests/test_memory/test_store.py @@ -61,6 +61,26 @@ async def test_list_sessions(store: MemoryStore) -> None: assert s2.sid in ids +async def test_list_sessions_newest_first(store: MemoryStore) -> None: + import asyncio + + from plyngent.lmproto.openai_compatible.model import UserChatMessage + + s1 = await store.create_session(name="old") + await asyncio.sleep(0.02) + s2 = await store.create_session(name="new") + latest = await store.get_latest_session() + assert latest is not None + assert latest.sid == s2.sid + # Activity on s1 makes it the latest + _ = await store.append_message(s1.sid, UserChatMessage(content="ping")) + latest2 = await store.get_latest_session() + assert latest2 is not None + assert latest2.sid == s1.sid + ordered = await store.list_sessions() + assert ordered[0].sid == s1.sid + + async def test_session_workspace_binding(store: MemoryStore, tmp_path: object) -> None: from pathlib import Path