fix: session-export timings tolerate corrupt timestamp cells
`_export_timings` parsed message timestamps with a bare `float()`, so a pre-existing out-of-range cell (e.g. 8.4e252) reached `datetime.fromtimestamp` and raised OverflowError, killing the whole `export_session`/`export_all` call — a regression caught by tests/hermes_state/test_corrupt_row_robustness.py. Route the cell through `coerce_epoch`, the reader every other timestamp surface already uses, so a bad row degrades to a `missing` count (with the usual warning naming the session) and the export still lands.
This commit is contained in:
@@ -81,7 +81,7 @@ def _rich_select(select_cols: str, where: str, tail: str = "", prompt_select: Op
|
||||
_PROMPT_RESOLVED_SQL = "COALESCE(sp.prompt, s.system_prompt) AS _system_prompt_resolved"
|
||||
|
||||
|
||||
def _export_timings(messages: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
def _export_timings(messages: List[Dict[str, Any]], session_id: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""Text-free timing evidence for a session export (port of nearai/ironclaw#7735).
|
||||
|
||||
Exports get attached to bug reports; a reader should not have to infer from raw
|
||||
@@ -89,13 +89,11 @@ def _export_timings(messages: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
intervals. Hermes persists no model/tool stopwatch samples, so message
|
||||
timestamps are the durable floor (``complete`` is therefore always False).
|
||||
Ids, roles, counts and durations only — never prompt text, arguments or results.
|
||||
Corrupt timestamp cells go through ``coerce_epoch`` like every other reader: they
|
||||
count as ``missing`` and never abort the export.
|
||||
"""
|
||||
timestamped = []
|
||||
for msg in messages:
|
||||
try:
|
||||
timestamped.append((msg, float(msg.get("timestamp"))))
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
timestamped = [(msg, ts) for msg in messages
|
||||
if (ts := coerce_epoch(msg.get("timestamp"), session_id=session_id)) is not None]
|
||||
role_counts = Counter(str(msg.get("role") or "unknown") for msg in messages)
|
||||
tool_calls_emitted = sum(
|
||||
len(tc) if isinstance(tc, list) else 1 for tc in (msg.get("tool_calls") for msg in messages) if tc)
|
||||
@@ -280,7 +278,7 @@ class SessionPortabilityMixin:
|
||||
|
||||
def _with_messages(self, session: Dict[str, Any]) -> Dict[str, Any]:
|
||||
messages = self.get_messages(session["id"])
|
||||
return {**session, "messages": messages, "timings": _export_timings(messages)}
|
||||
return {**session, "messages": messages, "timings": _export_timings(messages, session["id"])}
|
||||
|
||||
def export_session(self, session_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Export a single session with all its messages as a dict."""
|
||||
@@ -320,7 +318,7 @@ class SessionPortabilityMixin:
|
||||
self._row_to_message_dict(row, warn_context="get_messages", summary_flag=True)
|
||||
)
|
||||
return [{**session, "messages": messages_by_session[session["id"]],
|
||||
"timings": _export_timings(messages_by_session[session["id"]])} for session in sessions]
|
||||
"timings": _export_timings(messages_by_session[session["id"]], session["id"])} for session in sessions]
|
||||
|
||||
def adopt_session_lineage_from(self, donor_db: Any, session_id: str, *, retire_donor: bool = True) -> Dict[str, Any]:
|
||||
"""Adopt *session_id*'s full compression lineage from *donor_db* (stranded-bot-session
|
||||
|
||||
Reference in New Issue
Block a user