huskies: merge 1207 story fleet_resources MCP tool: on-demand host + per-container disk, load, and CPU/mem
This commit is contained in:
@@ -29,6 +29,8 @@ const GATEWAY_TOOLS: &[&str] = &[
|
||||
"prompt_permission",
|
||||
// One-shot container rebuild: build fresh image, swap container, preserve state.
|
||||
"project_rebuild",
|
||||
// On-demand host + per-container disk/load/CPU/mem snapshot (story 1207).
|
||||
"fleet_resources",
|
||||
// Read sled identity pins vs. live signed identity, and TOFU re-pin.
|
||||
"fleet_identity",
|
||||
];
|
||||
@@ -156,6 +158,31 @@ pub(crate) fn gateway_tool_definitions() -> Vec<Value> {
|
||||
"required": ["name"]
|
||||
}
|
||||
}),
|
||||
json!({
|
||||
"name": "fleet_resources",
|
||||
"description": "On-demand host and per-container resource snapshot: host disk free/total, load averages, core count, and memory; per-container CPU%/mem sourced via `docker stats`; and per-project `target/`+`.huskies/worktrees/` directory sizes sourced via `docker exec ... find` (bounded, skips node_modules/.git, cached briefly per container+path). Flags disk/load conditions past thresholds first so problems lead the response.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"disk_warn_gb": {
|
||||
"type": "integer",
|
||||
"description": "Host free-disk warn threshold in GB (default: 50)."
|
||||
},
|
||||
"disk_critical_gb": {
|
||||
"type": "integer",
|
||||
"description": "Host free-disk critical threshold in GB (default: 20)."
|
||||
},
|
||||
"load_warn_per_core": {
|
||||
"type": "number",
|
||||
"description": "1-minute load average per core above which a warn flag fires (default: 1.0)."
|
||||
},
|
||||
"load_critical_per_core": {
|
||||
"type": "number",
|
||||
"description": "1-minute load average per core above which a critical flag fires (default: 2.0)."
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
json!({
|
||||
"name": "fleet_identity",
|
||||
"description": "Read mode (default): for every registered sled, report project, url, connected, the recorded pin (expected_node_id), the live signature-verified node_id from a signed challenge-response (never the unsigned /identity display field), and whether they match. Repin mode: capture a sled's live verified identity via TOFU and persist it as the new pin, refusing when the signature is missing or does not verify.",
|
||||
@@ -440,6 +467,7 @@ async fn handle_gateway_tool(
|
||||
"agents.list" => handle_agents_list_tool(id),
|
||||
"prompt_permission" => handle_prompt_permission_tool(params, state, id).await,
|
||||
"project_rebuild" => handle_project_rebuild_tool(params, state, id).await,
|
||||
"fleet_resources" => handle_fleet_resources_tool(params, state, id).await,
|
||||
"fleet_identity" => handle_fleet_identity_tool(params, state, id).await,
|
||||
_ => JsonRpcResponse::error(id, -32601, format!("Unknown gateway tool: {tool_name}")),
|
||||
}
|
||||
@@ -943,6 +971,65 @@ async fn handle_project_rebuild_tool(
|
||||
)
|
||||
}
|
||||
|
||||
/// Handle the `fleet_resources` gateway tool (story 1207).
|
||||
///
|
||||
/// Collects host disk/load/cpu/mem, per-container CPU%/mem (via `docker
|
||||
/// stats`), and per-project `target/`/`worktrees/` sizes (via `docker exec
|
||||
/// ... find`) — all sourced gateway-side rather than requiring each project's
|
||||
/// sled to self-report. Thresholds for the leading `flags` list are supplied
|
||||
/// as optional arguments (see `gateway_tool_definitions`), defaulting to
|
||||
/// `ResourceThresholds::default()`.
|
||||
async fn handle_fleet_resources_tool(
|
||||
params: &Value,
|
||||
state: &GatewayState,
|
||||
id: Option<Value>,
|
||||
) -> JsonRpcResponse {
|
||||
use crate::service::gateway::resources::ResourceThresholds;
|
||||
|
||||
let args = params.get("arguments").unwrap_or(params);
|
||||
let defaults = ResourceThresholds::default();
|
||||
let thresholds = ResourceThresholds {
|
||||
disk_warn_gb: args
|
||||
.get("disk_warn_gb")
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(defaults.disk_warn_gb),
|
||||
disk_critical_gb: args
|
||||
.get("disk_critical_gb")
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(defaults.disk_critical_gb),
|
||||
load_warn_per_core: args
|
||||
.get("load_warn_per_core")
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(defaults.load_warn_per_core),
|
||||
load_critical_per_core: args
|
||||
.get("load_critical_per_core")
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(defaults.load_critical_per_core),
|
||||
};
|
||||
|
||||
let project_names: Vec<String> = state.projects.read().await.keys().cloned().collect();
|
||||
|
||||
match crate::service::gateway::resources::io::collect_fleet_resources(
|
||||
&state.config_dir,
|
||||
&project_names,
|
||||
&thresholds,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(resources) => JsonRpcResponse::success(
|
||||
id,
|
||||
json!({
|
||||
"content": [{
|
||||
"type": "text",
|
||||
"text": serde_json::to_string_pretty(&resources).unwrap_or_default()
|
||||
}],
|
||||
"resources": serde_json::to_value(&resources).unwrap_or(json!(null)),
|
||||
}),
|
||||
),
|
||||
Err(e) => JsonRpcResponse::error(id, -32603, format!("fleet_resources failed: {e}")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle the `fleet_identity` gateway tool.
|
||||
///
|
||||
/// Dispatches on the `action` argument: `"read"` (default) reports every
|
||||
|
||||
Reference in New Issue
Block a user