swarm-ui: show how recent each subagent is in the details panel
The subagent list endpoint now returns {name, last_row_unix} per
subagent, the publish time of its newest terminal row, read from the
queue controller-side so agents publish nothing new. The panel sorts
newest first, shows a ticking age beside each name, and dims one quiet
for 15 minutes.
Closes #4928
This commit is contained in:
parent
4356dc82a4
commit
84c89de71c
4 changed files with 124 additions and 30 deletions
|
|
@ -8,6 +8,8 @@
|
|||
//! `swarm_queue_client::subagent_term` spells. Subagents are spawned on demand
|
||||
//! under caller-chosen names, so the list is the set of subjects that stream
|
||||
//! holds rows for, read with `STREAM.INFO`. A missing stream lists nothing.
|
||||
//! Each subagent carries the stream's own publish time of its newest row, so
|
||||
//! a reader can tell a subagent that is running from one that has gone quiet.
|
||||
//!
|
||||
//! **No hive lookup.** Unlike [`crate::term_stream`], there is no
|
||||
//! hive-scoped subject to follow: only the agent's own credential is granted
|
||||
|
|
@ -28,7 +30,9 @@ use axum::extract::{Path, State};
|
|||
use axum::http::StatusCode;
|
||||
use axum::response::sse::{Event, KeepAlive, Sse};
|
||||
use futures_util::{Stream, StreamExt as _, TryStreamExt as _};
|
||||
use serde::Serialize;
|
||||
use swarm_queue_client::subagent_term;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::AppState;
|
||||
use crate::wanted::WantedWriter;
|
||||
|
|
@ -84,14 +88,25 @@ async fn live_agents(wanted: &WantedWriter, hives: &[String]) -> BTreeSet<String
|
|||
crate::agent_renewal::live_agents(&declarations)
|
||||
}
|
||||
|
||||
/// One subagent of an agent, as the list endpoint reports it.
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, ToSchema)]
|
||||
pub(crate) struct SubagentEntry {
|
||||
/// The subagent's name, as it appears in its stream subject.
|
||||
pub name: String,
|
||||
/// When its newest row in the stream was published, unix seconds, by the
|
||||
/// stream's own clock. `None` when that row could not be read.
|
||||
pub last_row_unix: Option<i64>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/agents/{name}/subagents",
|
||||
params(("name" = String, Path, description = "agent whose subagents to list")),
|
||||
responses(
|
||||
(status = 200, description = "names of the agent's subagents with terminal rows \
|
||||
in the swarm queue, sorted; empty when the agent has published none",
|
||||
body = Vec<String>),
|
||||
(status = 200, description = "the agent's subagents with terminal rows in the \
|
||||
swarm queue, sorted by name, each with the publish time of its newest row; \
|
||||
empty when the agent has published none",
|
||||
body = Vec<SubagentEntry>),
|
||||
(status = 400, description = "the agent name is not shaped like an identifier \
|
||||
(problem+json)", body = String),
|
||||
(status = 503, description = "no swarm queue is configured on this host, or the \
|
||||
|
|
@ -104,7 +119,7 @@ async fn live_agents(wanted: &WantedWriter, hives: &[String]) -> BTreeSet<String
|
|||
pub(crate) async fn list_subagents(
|
||||
State(state): State<AppState>,
|
||||
Path(agent): Path<String>,
|
||||
) -> Result<Json<Vec<String>>, problem_details::ProblemDetails> {
|
||||
) -> Result<Json<Vec<SubagentEntry>>, problem_details::ProblemDetails> {
|
||||
let agent = ident(&agent).map_err(bad_request)?;
|
||||
let client = connected_client(&state).map_err(|e| unavailable(&e))?;
|
||||
let js = async_nats::jetstream::new(client);
|
||||
|
|
@ -121,10 +136,41 @@ pub(crate) async fn list_subagents(
|
|||
.try_collect()
|
||||
.await
|
||||
.map_err(|e| read_failed(&agent, &e))?;
|
||||
Ok(Json(subagent_term::subagent_names(
|
||||
&agent,
|
||||
subjects.iter().map(|(s, _)| s.as_str()),
|
||||
)))
|
||||
let names = subagent_term::subagent_names(&agent, subjects.iter().map(|(s, _)| s.as_str()));
|
||||
let mut entries = Vec::with_capacity(names.len());
|
||||
for name in names {
|
||||
let last_row_unix = last_row_unix(&stream, &agent, &name).await;
|
||||
entries.push(SubagentEntry {
|
||||
name,
|
||||
last_row_unix,
|
||||
});
|
||||
}
|
||||
Ok(Json(entries))
|
||||
}
|
||||
|
||||
/// The publish time of a subagent's newest row. A row that cannot be read,
|
||||
/// aged out between the listing and this lookup say, is a missing time and
|
||||
/// not a failed list.
|
||||
async fn last_row_unix(
|
||||
stream: &async_nats::jetstream::stream::Stream,
|
||||
agent: &str,
|
||||
subagent: &str,
|
||||
) -> Option<i64> {
|
||||
match stream
|
||||
.get_last_raw_message_by_subject(&subagent_term::subject(agent, subagent))
|
||||
.await
|
||||
{
|
||||
Ok(row) => Some(row.time.unix_timestamp()),
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
%agent,
|
||||
%subagent,
|
||||
error = %swarm_queue_client::chain(&e),
|
||||
"listing subagents: reading the newest row failed"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
|
|
|
|||
Loading…
Reference in a new issue