- AgentCatalog/definitions with strict Markdown frontmatter, delegation graph, fail-closed tool scoping, and signal contracts - structured cancellation (AgentError::Cancelled/TimedOut) across provider streams, tool batches, and sleep; /stop drives the same terminal state - schema v6 run/group/inbox persistence with execution-ID conditional transitions and completion-slot reservations - ExecutionGate separating run quota from provider/tool step permits - background completion inbox with hidden-trigger continuation turns, fairness scheduling, lease release, dead-lettering, and activation recovery - typed TurnMailbox with two-phase steer admission and atomic consumption at turn commit; /stop releases admitted steer events back to pending - emit_signal tool with contract-enforced rate/dedupe/severity/size limits - WS run/event projection (GetAgentRuns, AgentRunUpdated, AgentEventUpdated), /api/agent-runs* management endpoints, /api/tasks union, WebUI run tree and signal cards - ChannelContext.durable_private persisted for continuation delivery reuse Version 1.7.0
75 lines
2.1 KiB
Rust
75 lines
2.1 KiB
Rust
//! Broadcast projection for durable Agent run/event updates.
|
|
//!
|
|
//! The hub follows the WorkManager plan-change broadcast pattern: the
|
|
//! Coordinator publishes a bounded view after each commit; the gateway
|
|
//! relays it to WebSocket clients. A lost broadcast is never fatal — the
|
|
//! client recalibrates with `GetAgentRuns`, whose `revision` comes from
|
|
//! `agent_session_state`.
|
|
|
|
use crate::protocol::{AgentEventView, AgentRunView};
|
|
|
|
/// One projected change for a root session. Exactly one of `run`/`event` is
|
|
/// present.
|
|
#[derive(Debug, Clone)]
|
|
pub struct AgentProjection {
|
|
pub session_id: String,
|
|
pub revision: i64,
|
|
pub run: Option<AgentRunView>,
|
|
pub event: Option<AgentEventView>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct AgentProjectionHub {
|
|
tx: tokio::sync::broadcast::Sender<AgentProjection>,
|
|
}
|
|
|
|
impl Default for AgentProjectionHub {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl AgentProjectionHub {
|
|
pub fn new() -> Self {
|
|
let (tx, _) = tokio::sync::broadcast::channel(256);
|
|
Self { tx }
|
|
}
|
|
|
|
pub fn subscribe(&self) -> tokio::sync::broadcast::Receiver<AgentProjection> {
|
|
self.tx.subscribe()
|
|
}
|
|
|
|
/// Publish a run update. Best-effort: relays lag and drop the event,
|
|
/// clients recalibrate with a full query.
|
|
pub fn publish(&self, projection: AgentProjection) {
|
|
let _ = self.tx.send(projection);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn publish_reaches_subscribers_and_drop_is_nonfatal() {
|
|
let hub = AgentProjectionHub::new();
|
|
let mut rx = hub.subscribe();
|
|
hub.publish(AgentProjection {
|
|
session_id: "cli:test:d".to_string(),
|
|
revision: 1,
|
|
run: None,
|
|
event: None,
|
|
});
|
|
let received = rx.try_recv().unwrap();
|
|
assert_eq!(received.session_id, "cli:test:d");
|
|
// No subscribers after the first is dropped; publish must not panic.
|
|
drop(rx);
|
|
hub.publish(AgentProjection {
|
|
session_id: "cli:test:d".to_string(),
|
|
revision: 2,
|
|
run: None,
|
|
event: None,
|
|
});
|
|
}
|
|
}
|