swarm UI: an agent that needs a Claude login shows it on its card
The agent's turn-state header ($SWARM.agent-state.<agent>) gains `needs_login`, set by hive-agent from its own LoginState, so the flag reaches the swarm without hive-c0re (mara on #5268: "id prefer the agent container sending the info it needs login without hive"). - swarm-queue-client: `agent_login::Login`, the shared wire type, beside `agent_inference::Effective`. `needs_login: Option<bool>` with `#[serde(default)]`: a header from an older agent decodes as `None` (unknown), never as "needs login" or "logged in". - hive-agent: the header publisher reads the shared LoginState cell. Every writer of that cell emits a status on the bus afterwards, so a login change goes out on the next rebuild, not the 60 s refresh. - swarm-controller: `agent_login::Reported` follows every agent's header and keeps the latest flag; `GET /api/agents/status` merges it into each row as `needs_login` (null when unknown). - swarm-ui: AgentCard shows a warning-tone "login needed" badge when the row's `needs_login` is true. Refs #5268
This commit is contained in:
parent
c335bc0962
commit
afeb0f9794
19 changed files with 365 additions and 15 deletions
|
|
@ -45,6 +45,7 @@ mod agent_icon;
|
|||
mod agent_identity;
|
||||
mod agent_input;
|
||||
mod agent_limits;
|
||||
mod agent_login;
|
||||
mod agent_renewal;
|
||||
mod agent_state_stream;
|
||||
mod agent_status;
|
||||
|
|
@ -894,6 +895,10 @@ struct AppState {
|
|||
/// headers on `status`'s connection. `None` in exactly the state
|
||||
/// `status` is.
|
||||
reported_inference: Option<Arc<inference::Reported>>,
|
||||
/// Whether each agent last said it needs a login, from its turn-state
|
||||
/// headers on `status`'s connection. `None` in exactly the state
|
||||
/// `status` is.
|
||||
agent_login: Option<Arc<agent_login::Reported>>,
|
||||
/// The swarm-level job graph, wrapped in its
|
||||
/// [`hive_jobq::scheduler::Scheduler`] now that something drives it
|
||||
/// (`spawn_jobq_worker`) — the graph alone was enough for the
|
||||
|
|
@ -1260,6 +1265,12 @@ fn reported_inference(
|
|||
Some(inference::Reported::spawn(status?.queue_client()))
|
||||
}
|
||||
|
||||
/// Whether each agent last said it needs a login, read on the status
|
||||
/// reader's connection — same rationale as [`wanted_writer`].
|
||||
fn agent_login(status: Option<&Arc<status::StatusReader>>) -> Option<Arc<agent_login::Reported>> {
|
||||
Some(agent_login::Reported::spawn(status?.queue_client()))
|
||||
}
|
||||
|
||||
/// Same "log and carry on" shape as the queue/bridge/forge wiring in `main`.
|
||||
/// A controller that cannot hold a webhook secret still serves every other
|
||||
/// route; the webhook endpoint answers 503, which is the honest answer
|
||||
|
|
@ -1671,6 +1682,11 @@ async fn get_agents_status(
|
|||
});
|
||||
}
|
||||
}
|
||||
if let Some(login) = state.agent_login.as_ref() {
|
||||
for row in &mut rows {
|
||||
row.needs_login = login.get(&row.name);
|
||||
}
|
||||
}
|
||||
Ok(Json(rows))
|
||||
}
|
||||
Err(e) => {
|
||||
|
|
@ -3334,6 +3350,7 @@ async fn main() -> Result<()> {
|
|||
agent_status: agent_status_reader(status.as_ref()),
|
||||
agent_icons: agent_icon_reader(status.as_ref()),
|
||||
reported_inference: reported_inference(status.as_ref()),
|
||||
agent_login: agent_login(status.as_ref()),
|
||||
status,
|
||||
jobq,
|
||||
webhook_secret,
|
||||
|
|
@ -3534,6 +3551,7 @@ mod tests {
|
|||
agent_status: None,
|
||||
agent_icons: None,
|
||||
reported_inference: None,
|
||||
agent_login: None,
|
||||
jobq: std::sync::Arc::clone(&sched),
|
||||
webhook_secret: None,
|
||||
config_prs: std::sync::Arc::default(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue