feat(brahman-handshake): ListSessions endpoint + cliente + UI broker-explorer
Iter 20. Nuevo flujo end-to-end para observabilidad: cualquier módulo
conectado puede pedir al broker la lista de sesiones activas y mostrar
labels + flows in/out por cada una.
brahman-handshake/messages:
- Frame::ListSessions(ListSessions{session}) → Frame::SessionList(SessionList{entries}).
- SessionEntry: session, label, schema_version, outputs, inputs, conscious.
brahman-handshake/server:
- run_post_handshake pasa SessionRegistry a handle_inbound_frame.
- build_session_list helper proyecta el snapshot bajo lock.
- Validación session_id mismatched → Unauthorized.
brahman-handshake/client:
- Client::list_sessions() async, drena MatchEvents intermedios al
pending_events buffer, mismo patrón que ping().
brahman-sidecar/discovery:
- list_sessions / list_sessions_blocking arman Card observer mínima,
piden, Farewell.
brahman-broker-explorer:
- Poll-tick agrega list_sessions_blocking cuando broker está UP*.
- stat_card "Sesiones activas" con count + items ordenados por Ulid:
label · in:[flows] out:[flows] (wit)?.
Test list_sessions_returns_currently_registered: 3 clientes
conectados, observer pide list, verifica labels + schema_version
+ conscious=false. 24 handshake tests + sidecar + broker-explorer
verde.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,8 +26,11 @@
|
||||
use std::path::PathBuf;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use brahman_handshake::messages::SessionList;
|
||||
use brahman_handshake::transport;
|
||||
use brahman_sidecar::{await_provider_blocking, build_consumer_card, ConsumerError};
|
||||
use brahman_sidecar::{
|
||||
await_provider_blocking, build_consumer_card, list_sessions_blocking, ConsumerError,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::*, px, Context, IntoElement, Render, SharedString, Window,
|
||||
};
|
||||
@@ -67,6 +70,9 @@ struct Explorer {
|
||||
state: ProbeState,
|
||||
last_probe_ms: u64,
|
||||
last_probe_at: Option<Instant>,
|
||||
/// Última `SessionList` recibida del broker (None = aún sin pedir
|
||||
/// o último intento falló).
|
||||
sessions: Option<SessionList>,
|
||||
}
|
||||
|
||||
impl Explorer {
|
||||
@@ -110,8 +116,22 @@ impl Explorer {
|
||||
},
|
||||
};
|
||||
|
||||
// Si el broker está reachable (UP*), aprovechar el
|
||||
// round-trip para pedir la lista de sesiones. Si está
|
||||
// DOWN, ni intentar — la lista serviría de nada con
|
||||
// connect failed igual.
|
||||
let sessions_snapshot = match &new_state {
|
||||
ProbeState::Down { .. } | ProbeState::Pending => None,
|
||||
_ => bg
|
||||
.spawn(async move {
|
||||
list_sessions_blocking("brahman-broker-explorer").ok()
|
||||
})
|
||||
.await,
|
||||
};
|
||||
|
||||
let _ = this.update(cx, |me, cx| {
|
||||
me.state = new_state;
|
||||
me.sessions = sessions_snapshot;
|
||||
me.last_probe_ms = elapsed;
|
||||
me.last_probe_at = Some(Instant::now());
|
||||
cx.notify();
|
||||
@@ -129,6 +149,7 @@ impl Explorer {
|
||||
state: ProbeState::Pending,
|
||||
last_probe_ms: 0,
|
||||
last_probe_at: None,
|
||||
sessions: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,6 +199,41 @@ impl Render for Explorer {
|
||||
)),
|
||||
};
|
||||
|
||||
let sessions_items: Vec<String> = self
|
||||
.sessions
|
||||
.as_ref()
|
||||
.map(|list| {
|
||||
let mut entries: Vec<_> = list.entries.iter().collect();
|
||||
// Orden estable por session id (Ulid es ordenable
|
||||
// temporal); útil para que la UI no se reordene
|
||||
// entre ticks aunque el HashMap del server sí.
|
||||
entries.sort_by_key(|e| e.session);
|
||||
entries
|
||||
.iter()
|
||||
.map(|e| {
|
||||
format!(
|
||||
"{} · in:[{}] out:[{}]{}",
|
||||
e.label,
|
||||
e.inputs.join(","),
|
||||
e.outputs.join(","),
|
||||
if e.conscious { " (wit)" } else { "" }
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let sessions_count_value = self
|
||||
.sessions
|
||||
.as_ref()
|
||||
.map(|l| l.entries.len().to_string())
|
||||
.unwrap_or_else(|| "—".into());
|
||||
let sessions_descr = match &self.sessions {
|
||||
None => "lista no disponible (broker DOWN o pendiente)".to_string(),
|
||||
Some(l) if l.entries.is_empty() => "sin sesiones registradas en el broker".into(),
|
||||
Some(_) => "labels visibles + flows in/out · (wit) = consciente".into(),
|
||||
};
|
||||
|
||||
let body = div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
@@ -185,7 +241,17 @@ impl Render for Explorer {
|
||||
.px(px(16.))
|
||||
.py(px(16.))
|
||||
.child(state_card(cx, &self.state, text, text_dim, accent_up,
|
||||
accent_partial, accent_down, accent_pending));
|
||||
accent_partial, accent_down, accent_pending))
|
||||
.child(stat_card(
|
||||
cx,
|
||||
"Sesiones activas",
|
||||
sessions_count_value,
|
||||
&sessions_descr,
|
||||
accent_up,
|
||||
text,
|
||||
text_dim,
|
||||
&sessions_items,
|
||||
));
|
||||
|
||||
div()
|
||||
.flex()
|
||||
|
||||
Reference in New Issue
Block a user