feat(#1178): add list_containers tool to AgentServer (topology-scoped to descendants)
This commit is contained in:
parent
1353fbaf17
commit
94c2d651c0
4 changed files with 98 additions and 0 deletions
|
|
@ -45,6 +45,7 @@ Tools (hyperhive surface):
|
|||
- `mcp__hyperhive__restart(name)` — *(requires `lifecycle` tool group)* restart a direct child sub-agent (stop + start). The server enforces topology: the call is rejected unless `name` is a direct child of yours per `topology.json`. No approval required.
|
||||
- `mcp__hyperhive__kill(name)` — *(requires `lifecycle` tool group)* stop a direct child sub-agent (graceful). Direct children only — server enforces topology. State dir kept; recreating reuses prior config + credentials. No approval required.
|
||||
- `mcp__hyperhive__update(name)` — *(requires `lifecycle` tool group)* rebuild a direct child sub-agent: re-applies the current hyperhive flake + agent.nix and restarts it. Direct children only — server enforces topology. No approval required. Idempotent.
|
||||
- `mcp__hyperhive__list_containers()` — *(requires `lifecycle` tool group)* list all containers that are topological descendants of this agent (children + their subtrees). Returns each name with running/stopped status, ordered parents-first. Useful before kill/update/restart to check what's under you.
|
||||
- `mcp__hyperhive__request_init_config(name, description?)` — *(requires `approvals` tool group)* initialise a brand-new direct child agent's proposed config repo. Queues an `InitConfig` approval; on approval hive-c0re seeds `/agents/<name>/config/agent.nix`. `name` must be a direct child in the topology tree — server enforces. Fails if the config repo already exists (use `request_apply_commit` instead).
|
||||
- `mcp__hyperhive__request_apply_commit(agent, commit_ref, description?)` — *(requires `approvals` tool group)* submit a config commit for a direct child agent, queued for operator approval. `agent` must be a direct child in the topology tree — server enforces. `commit_ref` must be a 7-40 char hex sha (not a branch/tag name). On approval hive-c0re rebuilds the container with the pinned commit.
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ pub enum SocketReply {
|
|||
/// `list_schedules` result — used by the manager surface only;
|
||||
/// `AgentResponse` has no equivalent variant.
|
||||
Schedules(Vec<hive_sh4re::WireSchedule>),
|
||||
/// `list_containers` result — descendant containers with running status.
|
||||
Containers(Vec<hive_sh4re::ContainerInfo>),
|
||||
LooseEnds(Vec<hive_sh4re::LooseEnd>),
|
||||
PendingRemindersCount(u64),
|
||||
ReminderRollup(hive_sh4re::ReminderStats),
|
||||
|
|
@ -81,6 +83,7 @@ impl From<hive_sh4re::Response> for SocketReply {
|
|||
hive_sh4re::Response::Logs { content } => Self::Logs(content),
|
||||
hive_sh4re::Response::HostJournal { content } => Self::HostJournal(content),
|
||||
hive_sh4re::Response::Schedules { schedules } => Self::Schedules(schedules),
|
||||
hive_sh4re::Response::Containers { containers } => Self::Containers(containers),
|
||||
hive_sh4re::Response::AgentMeta {
|
||||
name,
|
||||
running,
|
||||
|
|
@ -1008,6 +1011,45 @@ impl AgentServer {
|
|||
.await
|
||||
}
|
||||
|
||||
// IMPORTANT: this tool is only available when the `lifecycle` tool group
|
||||
// is granted to this agent. Returns all topological descendants of the
|
||||
// calling agent with their running status.
|
||||
#[tool(
|
||||
description = "List all containers that are topological descendants of this agent \
|
||||
(direct children + their subtrees). Requires the `lifecycle` tool group. \
|
||||
Returns every known descendant regardless of running state — check the `running` \
|
||||
field to distinguish live from stopped containers. Ordered by topology depth \
|
||||
(parents before children), then alphabetically within each tier."
|
||||
)]
|
||||
async fn list_containers(&self) -> String {
|
||||
run_tool_envelope("list_containers", String::new(), async move {
|
||||
let (resp, retries) = self
|
||||
.dispatch(hive_sh4re::AgentRequest::ListDescendants)
|
||||
.await;
|
||||
let body = match resp {
|
||||
Ok(SocketReply::Containers(containers)) => {
|
||||
if containers.is_empty() {
|
||||
"no descendant containers".to_owned()
|
||||
} else {
|
||||
containers
|
||||
.iter()
|
||||
.map(|c| {
|
||||
let status = if c.running { "running" } else { "stopped" };
|
||||
format!("{} ({})", c.name, status)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
}
|
||||
Ok(SocketReply::Err(m)) => format!("list_containers failed: {m}"),
|
||||
Ok(other) => format!("list_containers unexpected response: {other:?}"),
|
||||
Err(e) => format!("list_containers transport error: {e:#}"),
|
||||
};
|
||||
annotate_retries(body, retries)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
// IMPORTANT: this tool is capability-gated (`read_host_journal`).
|
||||
// It is added to `--allowedTools` by `allowed_capability_tools` only
|
||||
// when `HIVE_CAPABILITIES` contains `read_host_journal`. hive-c0re
|
||||
|
|
|
|||
|
|
@ -425,6 +425,42 @@ async fn dispatch(req: &AgentRequest, agent: &str, coord: &Arc<Coordinator>) ->
|
|||
coord.emit_rebuild_queue_snapshot();
|
||||
AgentResponse::Ok
|
||||
}
|
||||
AgentRequest::ListDescendants => {
|
||||
tracing::debug!(%agent, "agent: list descendants");
|
||||
// All containers known to nixos-container (running only).
|
||||
let running_set: std::collections::HashSet<String> =
|
||||
match crate::lifecycle::list().await {
|
||||
Ok(names) => names
|
||||
.into_iter()
|
||||
.filter_map(|c| {
|
||||
c.strip_prefix(crate::lifecycle::AGENT_PREFIX)
|
||||
.map(str::to_owned)
|
||||
})
|
||||
.collect(),
|
||||
Err(e) => {
|
||||
return AgentResponse::Err {
|
||||
message: format!("list containers failed: {e:#}"),
|
||||
}
|
||||
}
|
||||
};
|
||||
// Walk the full topology and collect every descendant.
|
||||
let topo = crate::topology::read();
|
||||
let mut names: Vec<String> = topo
|
||||
.keys()
|
||||
.filter(|name| crate::topology::is_descendant_of(name, agent))
|
||||
.cloned()
|
||||
.collect();
|
||||
// Parents before children, then alpha within each tier.
|
||||
crate::auto_update::topology_sort(&mut names, &topo);
|
||||
let containers = names
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
let running = running_set.contains(&name);
|
||||
hive_sh4re::ContainerInfo { name, running }
|
||||
})
|
||||
.collect();
|
||||
AgentResponse::Containers { containers }
|
||||
}
|
||||
AgentRequest::RequestInitConfig { name, description } => {
|
||||
if !crate::topology::children_of(agent)
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -542,6 +542,12 @@ pub enum Request {
|
|||
},
|
||||
/// *(privileged)* List every schedule in the queue.
|
||||
ListSchedules,
|
||||
/// List all containers that are topological descendants of the calling
|
||||
/// agent (direct children + their subtrees). Scoped to the caller's
|
||||
/// subtree; gated by the `lifecycle` tool group. The result includes all
|
||||
/// known descendants regardless of whether the container is currently
|
||||
/// running — use `running` to distinguish.
|
||||
ListDescendants,
|
||||
/// *(privileged)* Fire a scheduled prompt out of band immediately.
|
||||
FireScheduleNow { id: i64 },
|
||||
/// *(privileged)* Edit an existing schedule's mutable fields.
|
||||
|
|
@ -627,12 +633,25 @@ pub enum Response {
|
|||
/// `ListSchedules` result. Snapshot of every schedule.
|
||||
/// Returned on the manager socket only.
|
||||
Schedules { schedules: Vec<WireSchedule> },
|
||||
/// `ListDescendants` result: all descendant containers, with running
|
||||
/// status. Ordered by topology depth (parents before children), then
|
||||
/// alphabetically within each depth tier.
|
||||
Containers { containers: Vec<ContainerInfo> },
|
||||
}
|
||||
|
||||
/// Backwards-compatible response aliases.
|
||||
pub type AgentResponse = Response;
|
||||
pub type ManagerResponse = Response;
|
||||
|
||||
/// One entry in a `ListDescendants` result.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ContainerInfo {
|
||||
/// Logical agent name (no `h-` prefix).
|
||||
pub name: String,
|
||||
/// Whether the container is currently running.
|
||||
pub running: bool,
|
||||
}
|
||||
|
||||
/// Serde default for the `running` field; keeps wire backwards-compat
|
||||
/// with pre-running-field payloads. See
|
||||
/// `docs/conventions.md::Agent metadata`.
|
||||
|
|
|
|||
Loading…
Reference in a new issue