| 55 | # ============================================================ |
| 56 | |
| 57 | class SessionStore: |
| 58 | def __init__(self, storage_dir: Path): |
| 59 | """ |
| 60 | 参数: |
| 61 | storage_dir: 存储目录,如 ~/.claude/projects/<sanitized-cwd>/ |
| 62 | |
| 63 | CC 用 sanitizePath() 把 cwd 转成合法文件名。 |
| 64 | 我们简化为直接接收 storage_dir。 |
| 65 | """ |
| 66 | self._storage_dir = storage_dir |
| 67 | |
| 68 | def _session_path(self, session_id: str) -> Path: |
| 69 | """JSONL 文件路径。CC: ~/.claude/projects/{cwd}/{session_id}.jsonl""" |
| 70 | return self._storage_dir / f"{session_id}.jsonl" |
| 71 | |
| 72 | def save_message( |
| 73 | self, |
| 74 | session_id: str, |
| 75 | message: Message, |
| 76 | parent_uuid: Optional[str] = None, |
| 77 | ) -> str: |
| 78 | """追加一条消息到 JSONL 文件。返回生成的 uuid。 |
| 79 | |
| 80 | CC 的两层设计: |
| 81 | - TS 层用 async queue + 100ms 合并 (高性能) |
| 82 | - Rust 层用 save_to_path (整体写入) |
| 83 | |
| 84 | 我们用简单的同步 append — 保持 JSONL 追加语义。 |
| 85 | """ |
| 86 | path = self._session_path(session_id) |
| 87 | |
| 88 | entry = StorageEntry( |
| 89 | uuid=str(uuid.uuid4()), |
| 90 | parent_uuid=parent_uuid, |
| 91 | message=message.model_dump(), |
| 92 | timestamp=datetime.now(timezone.utc).isoformat(), |
| 93 | ) |
| 94 | |
| 95 | self._append_entry(path, entry) |
| 96 | return entry.uuid |
| 97 | |
| 98 | def load_session(self, session_id: str) -> list[Message]: |
| 99 | """从 JSONL 恢复完整对话。 |
| 100 | |
| 101 | 流程: |
| 102 | 1. 逐行解析 JSONL |
| 103 | 2. 构建 UUID → entry 映射 |
| 104 | 3. 找到叶节点(没有被任何 entry 的 parent_uuid 引用的 entry) |
| 105 | 4. 从叶节点沿 parent_uuid 回溯 |
| 106 | 5. 反转得到时间序 |
| 107 | """ |
| 108 | path = self._session_path(session_id) |
| 109 | entries = self._read_entries(path) |
| 110 | if not entries: |
| 111 | return [] |
| 112 | |
| 113 | chain = self._rebuild_chain(entries) |
| 114 | return [Message.model_validate(e.message) for e in chain] |