agent ui: live event panel via SSE + stream-json
This commit is contained in:
parent
3c9d42b2a7
commit
9eab28a716
8 changed files with 277 additions and 33 deletions
63
hive-ag3nt/src/events.rs
Normal file
63
hive-ag3nt/src/events.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
//! Live event stream for the per-agent web UI. The harness emits one
|
||||
//! `LiveEvent` per interesting thing that happens during a turn — wake-up
|
||||
//! (the popped inbox message), every line claude prints on stdout
|
||||
//! (parsed from `--output-format stream-json`), and the turn-end summary.
|
||||
//! The web UI subscribes via SSE and renders rows live.
|
||||
//!
|
||||
//! Channel type is `tokio::sync::broadcast`. New subscribers see only
|
||||
//! future events; the dashboard JS deals with the cold-start case by
|
||||
//! showing "connecting…" until the first event arrives.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
const CHANNEL_CAPACITY: usize = 256;
|
||||
|
||||
/// One row of the agent's live stream. Serialised to JSON for SSE delivery.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum LiveEvent {
|
||||
/// Harness popped a wake-up message and is about to invoke claude.
|
||||
TurnStart { from: String, body: String },
|
||||
/// One line of claude's `--output-format stream-json` stdout, parsed as
|
||||
/// a generic JSON value (so we don't have to track every claude-code
|
||||
/// event variant). The frontend pretty-prints by `type` field.
|
||||
Stream(serde_json::Value),
|
||||
/// Free-form note from the harness (e.g. "claude exited 0",
|
||||
/// "stream-json parse error: ..."). Useful when stream-json itself
|
||||
/// fails so the UI doesn't just go silent.
|
||||
Note(String),
|
||||
/// Turn finished. `ok=false` means claude exited non-zero or the
|
||||
/// harness hit a transport error.
|
||||
TurnEnd { ok: bool, note: Option<String> },
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Bus {
|
||||
tx: Arc<broadcast::Sender<LiveEvent>>,
|
||||
}
|
||||
|
||||
impl Bus {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
let (tx, _) = broadcast::channel(CHANNEL_CAPACITY);
|
||||
Self { tx: Arc::new(tx) }
|
||||
}
|
||||
|
||||
pub fn emit(&self, event: LiveEvent) {
|
||||
// Lagged subscribers drop events — fine; the UI is a tail, not a log.
|
||||
let _ = self.tx.send(event);
|
||||
}
|
||||
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<LiveEvent> {
|
||||
self.tx.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Bus {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue