term_msg: carry the source event's time on every row

A terminal row published on `$SWARM.term.<hive>.<agent>` goes out bare,
with no envelope around it and no server-side stamp, so a subscriber had
nothing to place the row in time with beyond its own receipt clock —
wrong by the queue's latency and meaningless for anything read later
than live.

`TermMsg` gains `ts`, ISO 8601 UTC. `classify` takes the event's own
unix-seconds stamp and applies it to every row that event expands into,
so a row replayed out of sqlite says when it happened rather than when
it was read, and a row that sat in a lagging subscriber's buffer does
not lie about its time. The oversize degrade keeps it; only the body is
ever spent.

`TermEnvelope` stops duplicating `ts` and keeps `seq`: the dedup counter
is a real transport concern, the event's time is not, now that it rides
on the row. Nothing in the frontend read `envelope.ts` — only the type
declared it.

Refs #4321
This commit is contained in:
atlas 2026-09-13 14:01:14 +02:00 committed by mara
commit 053340128b
5 changed files with 206 additions and 38 deletions

View file

@ -1,14 +1,22 @@
# Per-agent terminal: row taxonomy (as built)
The per-agent web UI's live pane renders one row per `TermMsg`
(`hive-agent/src/term_msg.rs`): `{icon?, level: debug|info|warn|error,
(`hive-agent/src/term_msg.rs`): `{ts, icon?, level: debug|info|warn|error,
summary, body?, body_format?: markdown|diff, coalesce_key?}`. Classification
(icon, summary text, whether a tool call gets an expandable body) happens
server-side, once — `term_msg.rs` + `stream_enrich.rs` — and both
`GET /api/events/history` and `GET /api/events/stream` serve it identically
as `TermEnvelope { ts, seq?, msgs: TermMsg[] }` frames
as `TermEnvelope { seq?, msgs: TermMsg[] }` frames
(`hive-agent/src/web_ui/stream.rs`).
`ts` is ISO 8601 UTC (`2026-09-13T12:35:03Z`) and is the **source event's**
time, stamped at classify time from the event's own stamp — so a row
replayed from sqlite says when it happened, not when it was fetched. It
lives on the row rather than on the envelope because the swarm queue
publishes rows bare, with no envelope around them
(`hive-agent/src/swarm_term.rs`); a subscriber there has nowhere else to
read the time from.
The frontend renders a `TermMsg` close to as-is
(`frontend/packages/agent/src/components/Row.tsx`): `level` picks the
CSS colour (`terminal.css`'s `.live .level-*`), an empty `summary` +