mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-06 09:28:34 +00:00
fix(session): repair read-only corrupt session paths
This commit is contained in:
parent
efb04a1712
commit
56a779c128
@ -252,6 +252,16 @@ class SessionManager:
|
|||||||
logger.warning("Repair failed for session {}: {}", key, e)
|
logger.warning("Repair failed for session {}: {}", key, e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _session_payload(session: Session) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"key": session.key,
|
||||||
|
"created_at": session.created_at.isoformat(),
|
||||||
|
"updated_at": session.updated_at.isoformat(),
|
||||||
|
"metadata": session.metadata,
|
||||||
|
"messages": session.messages,
|
||||||
|
}
|
||||||
|
|
||||||
def save(self, session: Session) -> None:
|
def save(self, session: Session) -> None:
|
||||||
"""Save a session to disk atomically."""
|
"""Save a session to disk atomically."""
|
||||||
path = self._get_session_path(session.key)
|
path = self._get_session_path(session.key)
|
||||||
@ -335,6 +345,10 @@ class SessionManager:
|
|||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("Failed to read session {}: {}", key, e)
|
logger.warning("Failed to read session {}: {}", key, e)
|
||||||
|
repaired = self._repair(key)
|
||||||
|
if repaired is not None:
|
||||||
|
logger.info("Recovered read-only session view {} from corrupt file", key)
|
||||||
|
return self._session_payload(repaired)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def list_sessions(self) -> list[dict[str, Any]]:
|
def list_sessions(self) -> list[dict[str, Any]]:
|
||||||
@ -347,6 +361,7 @@ class SessionManager:
|
|||||||
sessions = []
|
sessions = []
|
||||||
|
|
||||||
for path in self.sessions_dir.glob("*.jsonl"):
|
for path in self.sessions_dir.glob("*.jsonl"):
|
||||||
|
fallback_key = path.stem.replace("_", ":", 1)
|
||||||
try:
|
try:
|
||||||
# Read just the metadata line
|
# Read just the metadata line
|
||||||
with open(path, encoding="utf-8") as f:
|
with open(path, encoding="utf-8") as f:
|
||||||
@ -362,6 +377,14 @@ class SessionManager:
|
|||||||
"path": str(path)
|
"path": str(path)
|
||||||
})
|
})
|
||||||
except Exception:
|
except Exception:
|
||||||
|
repaired = self._repair(fallback_key)
|
||||||
|
if repaired is not None:
|
||||||
|
sessions.append({
|
||||||
|
"key": repaired.key,
|
||||||
|
"created_at": repaired.created_at.isoformat(),
|
||||||
|
"updated_at": repaired.updated_at.isoformat(),
|
||||||
|
"path": str(path)
|
||||||
|
})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return sorted(sessions, key=lambda x: x.get("updated_at", ""), reverse=True)
|
return sorted(sessions, key=lambda x: x.get("updated_at", ""), reverse=True)
|
||||||
|
|||||||
@ -208,6 +208,49 @@ class TestRepairCorruptFile:
|
|||||||
assert session.last_consolidated == 5
|
assert session.last_consolidated == 5
|
||||||
assert isinstance(session.created_at, datetime)
|
assert isinstance(session.created_at, datetime)
|
||||||
|
|
||||||
|
def test_read_session_file_repairs_corrupt_jsonl(self, tmp_path: Path):
|
||||||
|
mgr = SessionManager(tmp_path)
|
||||||
|
path = mgr._get_session_path("test:read-repair")
|
||||||
|
|
||||||
|
self._write_corrupt_jsonl(path, [
|
||||||
|
json.dumps({
|
||||||
|
"_type": "metadata",
|
||||||
|
"key": "test:read-repair",
|
||||||
|
"created_at": datetime.now().isoformat(),
|
||||||
|
"updated_at": datetime.now().isoformat(),
|
||||||
|
"metadata": {"source": "repair"},
|
||||||
|
"last_consolidated": 0,
|
||||||
|
}),
|
||||||
|
json.dumps({"role": "user", "content": "survived"}),
|
||||||
|
'{"role": "assistant", "content": "partial...',
|
||||||
|
])
|
||||||
|
|
||||||
|
payload = mgr.read_session_file("test:read-repair")
|
||||||
|
assert payload is not None
|
||||||
|
assert payload["key"] == "test:read-repair"
|
||||||
|
assert payload["metadata"] == {"source": "repair"}
|
||||||
|
assert payload["messages"] == [{"role": "user", "content": "survived"}]
|
||||||
|
|
||||||
|
def test_list_sessions_keeps_repaired_corrupt_file(self, tmp_path: Path):
|
||||||
|
mgr = SessionManager(tmp_path)
|
||||||
|
path = mgr._get_session_path("test:list-repair")
|
||||||
|
|
||||||
|
self._write_corrupt_jsonl(path, [
|
||||||
|
"NOT VALID JSON",
|
||||||
|
json.dumps({
|
||||||
|
"_type": "metadata",
|
||||||
|
"key": "test:list-repair",
|
||||||
|
"created_at": datetime.now().isoformat(),
|
||||||
|
"updated_at": datetime.now().isoformat(),
|
||||||
|
"metadata": {},
|
||||||
|
"last_consolidated": 0,
|
||||||
|
}),
|
||||||
|
json.dumps({"role": "user", "content": "hello"}),
|
||||||
|
])
|
||||||
|
|
||||||
|
sessions = mgr.list_sessions()
|
||||||
|
assert any(s["key"] == "test:list-repair" for s in sessions)
|
||||||
|
|
||||||
def test_get_or_create_returns_new_session_for_corrupt_file(self, tmp_path: Path):
|
def test_get_or_create_returns_new_session_for_corrupt_file(self, tmp_path: Path):
|
||||||
mgr = SessionManager(tmp_path)
|
mgr = SessionManager(tmp_path)
|
||||||
path = mgr._get_session_path("test:fallback")
|
path = mgr._get_session_path("test:fallback")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user