fix(agent): handle partial room timestamps without crashing

This commit is contained in:
2026-10-25 10:16:37 -07:00
parent b743523fd5
commit a68caac378
7 changed files with 218 additions and 27 deletions
+19
View File
@@ -627,6 +627,25 @@ messages/DOM grow with explicit older loads and live updates. Offscreen web rows
use CSS `content-visibility: auto`, which skips rendering work while retaining
chronological DOM, selection, find and accessibility; it is not DOM virtualization.
Every room message, including protected tool rows and `activity.queuedMessages`,
has a string `id`, `role`, `content`, and ISO-8601 `createdAt`. Privacy filtering
retains this envelope and tool lifecycle metadata (`kind`, `toolCallId`,
`toolName`, `entryId`, `status`, and available `startedAt`/`completedAt`). Protected
tools use their public tool name as `content`; private arguments, previews, and
errors are withheld. Result-only pages may omit initiation metadata.
Historical entries without a known time use `1970-01-01T00:00:00.000Z` as an
unknown-time sentinel. For compatibility with older partial responses, clients
may recover missing/invalid `createdAt` from `metadata.startedAt`, then
`metadata.completedAt`, or use that sentinel; never substitute the current time.
Older redacted tool rows containing only `id`, `role`, and `name` can retain their
identity with a tool-name label until a complete upsert arrives. The web client
uses the same normalization for REST, WebSocket, and queued activity, retains
known times across partial upserts, and orders by time, numeric `entryId` (when
present, before absent), then stable `id`. Unknown times display as “Earlier”
without a clock time. Invalid identities or non-tool message bodies reject the
page before advancing its replay cursor; timestamps never replace cursors.
### `POST /v1/agent/messages`
Body: `{ "content": "Check the oven", "requestId": "client-generated-uuid" }`.
+8
View File
@@ -326,6 +326,14 @@ with `metadata.kind: "tool_call"`, matching Sybil's tool renderer. REST pages an
socket messages are upserts keyed by stable `id`. Save each returned cursor after
applying its entire frame, even when it contains no visible messages.
Both message frames and queued activity use the required message envelope and
historical timestamp compatibility rules in [REST room history](./rest.md#get-v1agentmessages).
Protected tool redaction preserves `createdAt`, a safe string `content`, and
available lifecycle metadata; it does not remove the row's identity or replay
metadata. Partial tool results may omit `args`/`startedAt`; merge them without
losing a known creation time or downgrading a completed tool on replay. Validate
and normalize all rows before applying a frame or advancing its cursor.
Clients may send `{ "type": "ping" }` (receiving `{ "type": "pong" }`) or
`{ "type": "subscribe", "after": "44" }` to change the replay cursor. Transport
ping/pong heartbeats detect dead connections. Other writes belong on REST.