Save session to disk including a cost block. Ch03 round-2 (R2.1): the ``cost`` key matches the schema read by ``src/services/cost_restore.py:restore_cost_state_for_session`` so a save → load round-trip restores bootstrap counters (`total_cost_usd`, durations, lines a
(self)
| 47 | updated_at: str = field(default_factory=lambda: datetime.now().isoformat()) |
| 48 | |
| 49 | def save(self): |
| 50 | """Save session to disk including a cost block. |
| 51 | |
| 52 | Ch03 round-2 (R2.1): the ``cost`` key matches the schema read by |
| 53 | ``src/services/cost_restore.py:restore_cost_state_for_session`` |
| 54 | so a save → load round-trip restores bootstrap counters |
| 55 | (`total_cost_usd`, durations, lines added/removed, per-model |
| 56 | usage). Previously this method emitted no cost block; the |
| 57 | restore reader hit defaults of 0 unconditionally. |
| 58 | """ |
| 59 | session_dir = Path.home() / ".clawcodex" / "sessions" |
| 60 | session_dir.mkdir(parents=True, exist_ok=True) |
| 61 | |
| 62 | session_file = session_dir / f"{self.session_id}.json" |
| 63 | |
| 64 | cost_block = _snapshot_cost_block() |
| 65 | |
| 66 | session_data = { |
| 67 | "session_id": self.session_id, |
| 68 | "provider": self.provider, |
| 69 | "model": self.model, |
| 70 | "conversation": self.conversation.to_dict(), |
| 71 | "created_at": self.created_at, |
| 72 | "updated_at": datetime.now().isoformat(), |
| 73 | "cost": cost_block, |
| 74 | } |
| 75 | |
| 76 | with open(session_file, 'w') as f: |
| 77 | json.dump(session_data, f, indent=2) |
| 78 | |
| 79 | self.updated_at = datetime.now().isoformat() |
| 80 | |
| 81 | @classmethod |
| 82 | def load(cls, session_id: str) -> Optional['Session']: |