[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:
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user