swarm-ui: read-only agent terminal page consuming the swarm term stream
Moves the TermMsg rendering pipeline (Row.tsx, termMsg.ts, linkify.tsx,
markdown.ts) from @hive/agent into @hive/shared, so swarm-ui becomes a
second consumer of it instead of forking a copy -- CSS was already
shared (@hive/shared/terminal.css). marked+dompurify move from
@hive/agent's deps to @hive/shared's; swarm-ui picks them up
transitively, no new direct dep there.
New swarm-ui route /agents/:name/term (AgentTermPage), linked from
AgentsPage's detail panel via a "terminal" badge next to "link matrix
account". Consumes GET /api/agents/{name}/term/stream: unlike
@hive/agent's own useLiveStream (TermEnvelope-wrapped, history/backfill
dance), the swarm relay forwards one bare TermMsg per SSE event with no
envelope and no history endpoint -- useSwarmTermStream is a much
smaller hook for that shape (EventSource -> parse -> coalesce, nothing
to buffer/dedupe/backfill against).
Verified against a live SSE mock (screenshots in /agents/iris/state/screenshots/
3801-agents-detail-panel-terminal-badge.png and
3801-agent-term-page-live-rows.png -- real rows rendering through the
shared Row component, not just a build/typecheck pass).
This commit is contained in:
parent
5dff508e79
commit
593923375c
1 changed files with 232 additions and 22 deletions
49
frontend/packages/shared/src/terminal/termMsg.ts
Normal file
49
frontend/packages/shared/src/terminal/termMsg.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// Wire types for a classified terminal row — mirrors hive-agent's
|
||||
// `term_msg.rs`/`web_ui/stream.rs` field-for-field. The frontend renders
|
||||
// a `TermMsg` close to as-is (see Row.tsx); there's no separate
|
||||
// client-side row model or classification step any more — mara:
|
||||
// "StreamRow should now match what the server sends in TermMsg."
|
||||
//
|
||||
// Shared between `@hive/agent` (the per-hive local stream, wrapped in
|
||||
// `TermEnvelope`s) and `@hive/swarm-ui` (the swarm relay's `GET
|
||||
// /api/agents/{name}/term/stream`, which forwards one bare `TermMsg`
|
||||
// per SSE event — no envelope, no `seq`, no history endpoint).
|
||||
export type Level = "debug" | "info" | "warn" | "error";
|
||||
|
||||
export interface TermMsg {
|
||||
/** When the event this row came from happened — ISO 8601 UTC, e.g.
|
||||
* "2026-09-13T12:35:03Z". The source event's own time, so a row replayed
|
||||
* from history reads as when it happened rather than when it was fetched. */
|
||||
ts: string;
|
||||
icon?: string;
|
||||
level: Level;
|
||||
summary: string;
|
||||
body?: string;
|
||||
body_format?: "markdown" | "diff";
|
||||
coalesce_key?: string;
|
||||
}
|
||||
|
||||
/** One SSE frame / history array entry — `hive-agent`'s `TermEnvelope`.
|
||||
* `seq` is the live per-event dedup counter (`BusEvent::seq`); absent on
|
||||
* history-replayed envelopes, see useLiveStream.ts's backfill dance. It is
|
||||
* the only field left: the event's time rides on each row's own `ts`, where
|
||||
* a consumer reading a bare row (the swarm queue's) can also see it.
|
||||
*
|
||||
* Local to the per-hive stream — the swarm relay carries no envelope. */
|
||||
export interface TermEnvelope {
|
||||
seq?: number;
|
||||
msgs: TermMsg[];
|
||||
}
|
||||
|
||||
/** A `TermMsg` plus the bookkeeping Preact needs to render a list —
|
||||
* stable identity for coalescing/keys, and whether it came from history
|
||||
* replay vs. the live tail. Not a separate model: everything content-wise
|
||||
* is still exactly the wire shape. */
|
||||
export interface TermRow extends Omit<TermMsg, "ts"> {
|
||||
/** Absent on the rows the client invents for itself — the history/live
|
||||
* separators, the connection note, a locally echoed prompt — which answer
|
||||
* to no server event and so have no event time to carry. */
|
||||
ts?: string;
|
||||
key: string;
|
||||
fromHistory: boolean;
|
||||
}
|
||||
Loading…
Reference in a new issue