feat(#1004,#1006): capability system + read_host_journal / get_host_journal MCP tool
This commit is contained in:
parent
6c75030420
commit
dc8a4e2baf
7 changed files with 345 additions and 3 deletions
|
|
@ -46,6 +46,7 @@ pub enum SocketReply {
|
|||
QuestionQueued(i64),
|
||||
Recent(Vec<hive_sh4re::InboxRow>),
|
||||
Logs(String),
|
||||
HostJournal(String),
|
||||
/// `list_schedules` result — used by the manager surface only;
|
||||
/// `AgentResponse` has no equivalent variant.
|
||||
Schedules(Vec<hive_sh4re::WireSchedule>),
|
||||
|
|
@ -79,6 +80,7 @@ impl From<hive_sh4re::Response> for SocketReply {
|
|||
}
|
||||
hive_sh4re::Response::ReminderRollup(stats) => Self::ReminderRollup(stats),
|
||||
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::AgentMeta {
|
||||
name,
|
||||
|
|
@ -854,6 +856,43 @@ impl AgentServer {
|
|||
})
|
||||
.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
|
||||
// performs a second capability check server-side before running journalctl.
|
||||
#[tool(
|
||||
description = "Fetch recent lines from the host journal (requires `read_host_journal` \
|
||||
capability). All filters are optional — omit to get the last N host journal lines. \
|
||||
`unit`: filter to a systemd unit (e.g. `hive-c0re.service`). \
|
||||
`container`: logical agent name (e.g. `iris`) — adds `-M h-iris`. \
|
||||
`lines`: how many lines (default 100, max 500). \
|
||||
`priority`: minimum syslog level (`err`, `warning`, `info`, `debug`)."
|
||||
)]
|
||||
async fn get_host_journal(
|
||||
&self,
|
||||
Parameters(args): Parameters<GetHostJournalArgs>,
|
||||
) -> String {
|
||||
let log = format!("{args:?}");
|
||||
run_tool_envelope("get_host_journal", log, async move {
|
||||
let (resp, retries) = self
|
||||
.dispatch(hive_sh4re::AgentRequest::GetHostJournal {
|
||||
unit: args.unit,
|
||||
container: args.container,
|
||||
lines: args.lines,
|
||||
priority: args.priority,
|
||||
})
|
||||
.await;
|
||||
let result = match resp {
|
||||
Ok(SocketReply::HostJournal(content)) => content,
|
||||
Ok(SocketReply::Err(m)) => format!("get_host_journal failed: {m}"),
|
||||
Ok(other) => format!("get_host_journal unexpected response: {other:?}"),
|
||||
Err(e) => format!("get_host_journal transport error: {e:#}"),
|
||||
};
|
||||
annotate_retries(result, retries)
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[tool_handler(
|
||||
|
|
@ -1132,6 +1171,24 @@ pub struct GetLogsArgs {
|
|||
pub lines: Option<u32>,
|
||||
}
|
||||
|
||||
/// Arguments for `get_host_journal` (capability-gated: `read_host_journal`).
|
||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||
pub struct GetHostJournalArgs {
|
||||
/// Systemd unit to filter (e.g. `hive-c0re.service`). Omit for all units.
|
||||
#[serde(default)]
|
||||
pub unit: Option<String>,
|
||||
/// Logical agent name (e.g. `iris`) — gets that container's journal via `-M h-iris`.
|
||||
/// Omit for the host journal.
|
||||
#[serde(default)]
|
||||
pub container: Option<String>,
|
||||
/// Number of lines to return (default 100, max 500).
|
||||
#[serde(default)]
|
||||
pub lines: Option<u32>,
|
||||
/// Minimum syslog priority: `err`, `warning`, `info`, `debug`.
|
||||
#[serde(default)]
|
||||
pub priority: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ManagerServer {
|
||||
socket: PathBuf,
|
||||
|
|
@ -1832,6 +1889,36 @@ pub enum Flavor {
|
|||
/// instead of using the hardcoded flavor default. See `docs/conventions.md::Tool groups`.
|
||||
const TOOL_GROUPS_ENV: &str = "HIVE_TOOL_GROUPS";
|
||||
|
||||
/// `HIVE_CAPABILITIES` env var injected by `meta::render_flake` when the
|
||||
/// operator grants capabilities to this agent. Comma-separated
|
||||
/// `hive_sh4re::Capability` snake_case names. Absent = no extra capabilities.
|
||||
const CAPABILITIES_ENV: &str = "HIVE_CAPABILITIES";
|
||||
|
||||
/// Returns the MCP tool names (without `mcp__hyperhive__` prefix) that are
|
||||
/// unlocked by the agent's current capability set. These are added to the
|
||||
/// `--allowedTools` list so claude can call them without prompting, and
|
||||
/// hive-c0re performs a second server-side capability check before executing.
|
||||
fn allowed_capability_tools() -> Vec<String> {
|
||||
let raw = match std::env::var(CAPABILITIES_ENV) {
|
||||
Ok(v) if !v.trim().is_empty() => v,
|
||||
_ => return vec![],
|
||||
};
|
||||
let mut tools = Vec::new();
|
||||
for token in raw.split(',') {
|
||||
let t = token.trim().to_ascii_lowercase();
|
||||
match t.as_str() {
|
||||
"read_host_journal" => tools.push("get_host_journal".to_owned()),
|
||||
// manage_root_agent doesn't expose new MCP tools (it gates
|
||||
// existing lifecycle tools via the topology enforcement).
|
||||
"manage_root_agent" => {}
|
||||
unknown => {
|
||||
tracing::warn!(capability = %unknown, "unrecognised capability in HIVE_CAPABILITIES — skipped");
|
||||
}
|
||||
}
|
||||
}
|
||||
tools
|
||||
}
|
||||
|
||||
/// Resolve the active tool groups for a harness session.
|
||||
///
|
||||
/// Reads `HIVE_TOOL_GROUPS` from the environment first. Each comma-separated
|
||||
|
|
@ -1933,6 +2020,13 @@ pub fn allowed_tools_arg(flavor: Flavor) -> String {
|
|||
.collect();
|
||||
let groups = effective_tool_groups(flavor);
|
||||
all.extend(allowed_mcp_tools(&groups));
|
||||
// Capability-gated tools: added to --allowedTools when HIVE_CAPABILITIES
|
||||
// includes the corresponding capability. hive-c0re performs a second
|
||||
// server-side check, so this is a usability gate (no annoying prompts),
|
||||
// not the security boundary.
|
||||
for tool in allowed_capability_tools() {
|
||||
all.push(format!("mcp__{SERVER_NAME}__{tool}"));
|
||||
}
|
||||
all.join(",")
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue