from __future__ import annotations import json from datetime import UTC, datetime from pathlib import Path from threading import Lock from typing import Any class ConversationLogStore: """Append-only local audit log for chat and tool activity.""" def __init__(self, path: str | Path) -> None: self.path = Path(path) self._lock = Lock() def append(self, event: dict[str, Any]) -> None: record = {"timestamp": datetime.now(UTC).isoformat(), **_safe(event)} self.path.parent.mkdir(parents=True, exist_ok=True) with self._lock, self.path.open("a", encoding="utf-8") as stream: stream.write(json.dumps(record, ensure_ascii=True, default=str) + "\n") def _safe(value: Any) -> Any: if isinstance(value, dict): return {str(k): _safe(v) for k, v in value.items()} if isinstance(value, list): return [_safe(v) for v in value] return value if isinstance(value, (str, int, float, bool)) or value is None else str(value)