add get_logs tool to manager mcp surface

This commit is contained in:
damocles 2026-05-16 20:28:42 +02:00
parent fca480b86e
commit 1023acf69f
3 changed files with 90 additions and 0 deletions

View file

@ -39,6 +39,7 @@ pub enum SocketReply {
Status(u64), Status(u64),
QuestionQueued(i64), QuestionQueued(i64),
Recent(Vec<hive_sh4re::InboxRow>), Recent(Vec<hive_sh4re::InboxRow>),
Logs(String),
} }
impl From<hive_sh4re::AgentResponse> for SocketReply { impl From<hive_sh4re::AgentResponse> for SocketReply {
@ -65,6 +66,7 @@ impl From<hive_sh4re::ManagerResponse> for SocketReply {
hive_sh4re::ManagerResponse::Status { unread } => Self::Status(unread), hive_sh4re::ManagerResponse::Status { unread } => Self::Status(unread),
hive_sh4re::ManagerResponse::QuestionQueued { id } => Self::QuestionQueued(id), hive_sh4re::ManagerResponse::QuestionQueued { id } => Self::QuestionQueued(id),
hive_sh4re::ManagerResponse::Recent { rows } => Self::Recent(rows), hive_sh4re::ManagerResponse::Recent { rows } => Self::Recent(rows),
hive_sh4re::ManagerResponse::Logs { content } => Self::Logs(content),
} }
} }
} }
@ -351,6 +353,15 @@ pub struct RequestApplyCommitArgs {
pub description: Option<String>, pub description: Option<String>,
} }
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct GetLogsArgs {
/// Logical name of the sub-agent container to fetch logs for.
pub agent: String,
/// How many journal lines to return (default: 50, max: 500).
#[serde(default)]
pub lines: Option<u32>,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ManagerServer { pub struct ManagerServer {
socket: PathBuf, socket: PathBuf,
@ -580,6 +591,40 @@ impl ManagerServer {
}) })
.await .await
} }
#[tool(
description = "Fetch recent journal log lines for a sub-agent container. Useful \
for diagnosing MCP server registration failures, startup crashes, plugin install \
errors, or any harness issue you can't see from inside the container. `lines` \
defaults to 50 (max capped at 500 on the host side)."
)]
async fn get_logs(&self, Parameters(args): Parameters<GetLogsArgs>) -> String {
let log = format!("{args:?}");
let agent = args.agent.clone();
run_tool_envelope("get_logs", log, async move {
let lines = args.lines.map(|n| n.min(500));
let (resp, retries) = self
.dispatch(hive_sh4re::ManagerRequest::GetLogs {
agent: agent.clone(),
lines,
})
.await;
let s = match resp {
Ok(SocketReply::Logs(content)) => {
if content.is_empty() {
format!("(no journal output for {agent})")
} else {
content
}
}
Ok(SocketReply::Err(m)) => format!("get_logs failed: {m}"),
Ok(other) => format!("get_logs unexpected response: {other:?}"),
Err(e) => format!("get_logs transport error: {e:#}"),
};
annotate_retries(s, retries)
})
.await
}
} }
#[tool_handler( #[tool_handler(
@ -635,6 +680,7 @@ pub fn allowed_mcp_tools(flavor: Flavor) -> Vec<String> {
"update", "update",
"request_apply_commit", "request_apply_commit",
"ask_operator", "ask_operator",
"get_logs",
], ],
}; };
let mut out: Vec<String> = names let mut out: Vec<String> = names

View file

@ -273,6 +273,35 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc<Coordinator>) -> ManagerResp
}, },
} }
} }
ManagerRequest::GetLogs { agent, lines } => {
let n = lines.unwrap_or(50);
tracing::info!(%agent, %n, "manager: get_logs");
match tokio::process::Command::new("journalctl")
.args([
"-M",
agent,
"-n",
&n.to_string(),
"--no-pager",
"--output=short",
])
.output()
.await
{
Ok(out) => {
let content = if out.status.success() || !out.stdout.is_empty() {
String::from_utf8_lossy(&out.stdout).into_owned()
} else {
let stderr = String::from_utf8_lossy(&out.stderr);
format!("journalctl exited {}: {stderr}", out.status)
};
ManagerResponse::Logs { content }
}
Err(e) => ManagerResponse::Err {
message: format!("journalctl spawn failed: {e:#}"),
},
}
}
ManagerRequest::RequestApplyCommit { ManagerRequest::RequestApplyCommit {
agent, agent,
commit_ref, commit_ref,

View file

@ -475,6 +475,17 @@ pub enum ManagerRequest {
#[serde(default)] #[serde(default)]
ttl_seconds: Option<u64>, ttl_seconds: Option<u64>,
}, },
/// Fetch recent journal lines for a sub-agent container. hive-c0re
/// runs `journalctl -M <agent> -n <lines> --no-pager` and returns
/// the output as a string. Useful for diagnosing MCP registration
/// failures, startup crashes, and harness errors.
///
/// `lines` defaults to 50 when omitted.
GetLogs {
agent: String,
#[serde(default)]
lines: Option<u32>,
},
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -502,4 +513,8 @@ pub enum ManagerResponse {
Recent { Recent {
rows: Vec<InboxRow>, rows: Vec<InboxRow>,
}, },
/// `GetLogs` result: journal lines for the requested container.
Logs {
content: String,
},
} }