feat: add archive_session and unarchive_session to SessionService
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -826,6 +826,37 @@ class SessionService:
|
||||
db.delete(sess)
|
||||
db.commit()
|
||||
|
||||
async def archive_session(self, session_id: int) -> None:
|
||||
"""Archive a session: kill its tmux window but keep the DB row.
|
||||
|
||||
A recoverable "remove from view" — the window/harness process is torn
|
||||
down (freeing resources) but the row, ``harness_session_id``, and any
|
||||
worktree are preserved so the session can be restored or referenced
|
||||
later. No-op if the session does not exist.
|
||||
"""
|
||||
with self.factory() as db:
|
||||
sess = db.get(Session, session_id)
|
||||
if sess is None:
|
||||
return
|
||||
if await self.tmux.window_exists(sess.tmux_session_name):
|
||||
await self.tmux.kill(sess.tmux_session_name)
|
||||
sess.archived = True
|
||||
db.commit()
|
||||
|
||||
async def unarchive_session(self, session_id: int) -> None:
|
||||
"""Restore an archived session by clearing its archived flag.
|
||||
|
||||
The tmux window stays gone (the session reads as dead until resumed);
|
||||
this only makes the row visible in the active list again. No-op if the
|
||||
session does not exist.
|
||||
"""
|
||||
with self.factory() as db:
|
||||
sess = db.get(Session, session_id)
|
||||
if sess is None:
|
||||
return
|
||||
sess.archived = False
|
||||
db.commit()
|
||||
|
||||
async def worktree_state_for(self, session_id: int) -> WorktreeState | None:
|
||||
"""Return the WorktreeState for a session's worktree, or None.
|
||||
|
||||
|
||||
@@ -2170,3 +2170,43 @@ async def test_list_sessions_archived_filter(service, db, tmux):
|
||||
|
||||
archived = await service.list_sessions(project_id=1, archived=True)
|
||||
assert [i.session.id for i in archived] == [arch_id]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_archive_session_kills_window_and_keeps_row(service, db, tmux):
|
||||
await service.create_session(project_id=1, harness_name="claude-code")
|
||||
await service.archive_session(1)
|
||||
|
||||
tmux.kill.assert_called_once_with("hqt-1")
|
||||
db.expire_all()
|
||||
row = db.get(Session, 1)
|
||||
assert row is not None
|
||||
assert row.archived is True
|
||||
# Excluded from the default (active) listing.
|
||||
assert await service.list_sessions(project_id=1) == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_archive_session_no_kill_when_window_missing(service, db, tmux):
|
||||
await service.create_session(project_id=1, harness_name="claude-code")
|
||||
tmux.window_exists = AsyncMock(return_value=False)
|
||||
tmux.kill.reset_mock()
|
||||
|
||||
await service.archive_session(1)
|
||||
|
||||
tmux.kill.assert_not_called()
|
||||
db.expire_all()
|
||||
assert db.get(Session, 1).archived is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unarchive_session_restores_to_active_list(service, db, tmux):
|
||||
await service.create_session(project_id=1, harness_name="claude-code")
|
||||
await service.archive_session(1)
|
||||
|
||||
await service.unarchive_session(1)
|
||||
|
||||
db.expire_all()
|
||||
assert db.get(Session, 1).archived is False
|
||||
active = await service.list_sessions(project_id=1)
|
||||
assert [i.session.id for i in active] == [1]
|
||||
|
||||
Reference in New Issue
Block a user