[verified] fix: emit UTC-offset last_active so the app can sort by recency

Session-store updated_at values are naive local datetimes; passing them
through without an offset broke Instant.parse() on the client, dropping
last_active and leaving the chat list unsorted after restart.
This commit is contained in:
2026-09-10 05:44:02 +00:00
parent aa705f7dd0
commit 416095a7f1
2 changed files with 29 additions and 0 deletions
+10
View File
@@ -210,6 +210,16 @@ def _iso(value: Any) -> Optional[str]:
try: try:
if isinstance(value, (int, float)): if isinstance(value, (int, float)):
return dt.datetime.fromtimestamp(value, tz=dt.timezone.utc).isoformat() return dt.datetime.fromtimestamp(value, tz=dt.timezone.utc).isoformat()
if isinstance(value, dt.datetime):
# Session-store timestamps are naive LOCAL datetimes (see
# gateway.session_lifecycle._now). The client parses with
# Instant.parse(), which requires an offset — a bare
# "2026-09-10T05:41:48" fails and last_active is lost, so the
# app can no longer sort by recency. Attach the local offset
# and normalize to UTC.
if value.tzinfo is None:
value = value.astimezone()
return value.astimezone(dt.timezone.utc).isoformat()
return value.isoformat() if value else None return value.isoformat() if value else None
except (AttributeError, OSError, OverflowError, TypeError, ValueError): except (AttributeError, OSError, OverflowError, TypeError, ValueError):
return None return None
+19
View File
@@ -12,6 +12,7 @@ by ``conftest.py``).
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
import datetime as dt
import json import json
import threading import threading
import time import time
@@ -459,6 +460,24 @@ class TestBridge:
assert history[0]["ts"] == "2026-09-10T00:26:40.250000+00:00" assert history[0]["ts"] == "2026-09-10T00:26:40.250000+00:00"
assert history[1]["ts"] == "2026-09-10T00:26:41.500000+00:00" assert history[1]["ts"] == "2026-09-10T00:26:41.500000+00:00"
def test_iso_parses_by_instant_for_naive_local_datetimes(self):
# gateway session-store timestamps are naive LOCAL datetimes
# (gateway.session_lifecycle._now). last_active must come back with a
# UTC offset, or the Kotlin client's Instant.parse() fails and the
# chat list loses its recency ordering.
from pheby import hermes_bridge as hb
naive = dt.datetime(2026, 9, 10, 5, 41, 48, 401094) # naive local
got = hb._iso(naive)
assert got is not None
assert got.endswith("+00:00")
# Java-style Instant.parse equivalent: parse with offset required.
parsed = dt.datetime.fromisoformat(got)
assert parsed.tzinfo is not None
# Aware datetimes pass through normalized to UTC unchanged.
aware = dt.datetime(2026, 9, 10, 12, 0, tzinfo=dt.timezone(dt.timedelta(hours=-5)))
assert hb._iso(aware) == "2026-09-10T17:00:00+00:00"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_tool_start_event_is_structured_not_text(self, tmp_path, async def test_tool_start_event_is_structured_not_text(self, tmp_path,
monkeypatch): monkeypatch):