fix: clear chat sessions on cwd change (#172)

This commit is contained in:
banteg
2026-01-20 13:23:07 +04:00
committed by GitHub
parent 0291a066b3
commit e2bb9fb717
4 changed files with 61 additions and 0 deletions
+4
View File
@@ -57,6 +57,10 @@ If you prefer a cleaner chat, hide resume lines:
In group chats, Takopi stores a session per sender, so different people can work independently in the same chat.
## Working directory changes
When `session_mode = "chat"` is enabled, Takopi clears stored chat sessions on startup if the current working directory differs from the one recorded in `telegram_chat_sessions_state.json`. This avoids resuming directory-bound sessions from a different project.
## Related
- [Conversation modes](../tutorials/conversation-modes.md)
+17
View File
@@ -24,6 +24,7 @@ class _ChatState(msgspec.Struct, forbid_unknown_fields=False):
class _ChatSessionsState(msgspec.Struct, forbid_unknown_fields=False):
version: int
cwd: str | None = None
chats: dict[str, _ChatState] = msgspec.field(default_factory=dict)
@@ -64,11 +65,27 @@ class ChatSessionStore(JsonStateStore[_ChatSessionsState]):
return None
return ResumeToken(engine=engine, value=entry.resume)
async def sync_startup_cwd(self, cwd: Path) -> bool:
normalized = str(cwd.expanduser().resolve())
async with self._lock:
self._reload_locked_if_needed()
previous = self._state.cwd
cleared = False
if previous is not None and previous != normalized:
self._state.chats = {}
cleared = True
if previous != normalized:
self._state.cwd = normalized
self._save_locked()
return cleared
async def set_session_resume(
self, chat_id: int, owner_id: int | None, token: ResumeToken
) -> None:
async with self._lock:
self._reload_locked_if_needed()
if self._state.cwd is None:
self._state.cwd = str(Path.cwd().expanduser().resolve())
chat = self._ensure_chat_locked(chat_id, owner_id)
chat.sessions[token.engine] = _SessionState(resume=token.value)
self._save_locked()
+9
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
from collections.abc import AsyncIterator, Awaitable, Callable, Mapping
from dataclasses import dataclass
from functools import partial
from pathlib import Path
from typing import TYPE_CHECKING, cast
import anyio
@@ -964,6 +965,14 @@ async def run_main_loop(
state.chat_session_store = ChatSessionStore(
resolve_sessions_path(config_path)
)
cleared = await state.chat_session_store.sync_startup_cwd(Path.cwd())
if cleared:
logger.info(
"chat_sessions.cleared",
reason="startup_cwd_changed",
cwd=str(Path.cwd()),
state_path=str(resolve_sessions_path(config_path)),
)
logger.info(
"chat_sessions.enabled",
state_path=str(resolve_sessions_path(config_path)),
+31
View File
@@ -1,3 +1,5 @@
from pathlib import Path
import pytest
from takopi.model import ResumeToken
@@ -36,3 +38,32 @@ async def test_chat_sessions_store_clear(tmp_path) -> None:
engine="codex",
value="two",
)
@pytest.mark.anyio
async def test_chat_sessions_store_drops_sessions_on_cwd_change(
tmp_path, monkeypatch
) -> None:
path = tmp_path / "telegram_chat_sessions_state.json"
dir1 = tmp_path / "dir1"
dir2 = tmp_path / "dir2"
dir1.mkdir()
dir2.mkdir()
monkeypatch.chdir(dir1)
store = ChatSessionStore(path)
await store.set_session_resume(1, None, ResumeToken(engine="codex", value="abc123"))
assert await store.get_session_resume(1, None, "codex") == ResumeToken(
engine="codex", value="abc123"
)
store2 = ChatSessionStore(path)
assert await store2.sync_startup_cwd(Path.cwd()) is False
assert await store2.get_session_resume(1, None, "codex") == ResumeToken(
engine="codex", value="abc123"
)
monkeypatch.chdir(dir2)
store3 = ChatSessionStore(path)
assert await store3.sync_startup_cwd(Path.cwd()) is True
assert await store3.get_session_resume(1, None, "codex") is None