AgentStatusRow: carry active_model + set_status text/timestamp

This commit is contained in:
damocles 2026-09-07 12:46:54 +02:00 committed by mara
commit 5640832116
5 changed files with 46 additions and 0 deletions

View file

@ -63,6 +63,16 @@ pub struct ContainerView {
/// for stopped containers.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub active_model: Option<String>,
/// The agent's current free-text status (`set_status`). `None` when
/// unset, or when the container isn't running — see
/// [`read_agent_status_live`] for why a stopped agent's on-disk
/// status is treated as stale rather than surfaced.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status_text: Option<String>,
/// Unix timestamp (seconds) `status_text` was last set. `None`
/// exactly when `status_text` is `None`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status_set_at: Option<i64>,
/// The agent's turn loop is parked: the container is up and still
/// serving its web UI / MCP daemons, but it drives no turns and its
/// inbox messages queue unacked until it resumes. Sourced from the
@ -155,6 +165,16 @@ pub async fn build_all(hive: &crate::coordinator::HiveEnv) -> Vec<ContainerView>
} else {
None
};
// Same running-gate as `active_model` just above, and the same
// rule `read_agent_status_live` applies for its other callers —
// duplicated inline rather than calling that helper because it
// would re-derive `running` with its own `lifecycle::is_running`
// shell-out when this loop already has it for free.
let (status_text, status_set_at) = if running {
read_agent_status(&logical)
} else {
(None, None)
};
let paused = Coordinator::is_paused(&logical);
let (cpu_quota, memory_max) = crate::resource_limits::effective_from(
&limits,
@ -173,6 +193,8 @@ pub async fn build_all(hive: &crate::coordinator::HiveEnv) -> Vec<ContainerView>
deployed_sha,
parent,
active_model,
status_text,
status_set_at,
paused,
cpu_quota,
memory_max,

View file

@ -817,6 +817,8 @@ mod tests {
deployed_sha: None,
parent: None,
active_model: None,
status_text: None,
status_set_at: None,
paused: false,
cpu_quota: "200%".to_owned(),
memory_max: "4G".to_owned(),

View file

@ -384,6 +384,9 @@ async fn handle_agent_status(coord: &Arc<Coordinator>) -> HostResponse {
pending_reminders: 0,
parent: v.parent,
paused: v.paused,
active_model: v.active_model,
status_text: v.status_text,
status_set_at: v.status_set_at,
})
.collect();
HostResponse::agent_statuses(rows)

View file

@ -254,6 +254,8 @@ mod tests {
deployed_sha: None,
parent: None,
active_model: None,
status_text: None,
status_set_at: None,
paused: false,
cpu_quota: "200%".to_owned(),
memory_max: "4G".to_owned(),

View file

@ -64,6 +64,23 @@ pub struct AgentStatusRow {
/// Parent in the topology tree. `None` marks a root-level agent.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent: Option<String>,
/// The Claude model the agent's harness is currently using. Mirrors
/// `container_view::ContainerView::active_model`: `None` when the
/// agent has never started a turn, the field is absent from its
/// harness state file, or the container isn't running.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub active_model: Option<String>,
/// The agent's current free-text status, set via the `set_status`
/// tool. `None` when unset or the container isn't running — a
/// stopped agent's on-disk status is a stale snapshot from before
/// the stop (same rule `container_view::read_agent_status_live`
/// applies for every other reader of this data).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status_text: Option<String>,
/// Unix timestamp (seconds) `status_text` was last set. `None`
/// exactly when `status_text` is `None`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status_set_at: Option<i64>,
}
/// One matrix identity an agent can act as, surfaced in `GetAgentMeta`'s