hive-agent-mcp, hive-c0re, hive-sh4re: drop agent param from get_loose_ends
get_loose_ends now always returns the caller's own loose ends, for every caller including the manager (ruth) — there is no separate manager surface, ruth is a normal agent with different default capabilities. - AgentGetLooseEndsArgs removed; get_loose_ends takes no args. - Wire Request::GetLooseEnds collapses from an Option<String> target to a unit variant. - hive-c0re's handle_get_loose_ends drops the "*" hive-wide branch and the subtree/capability resolver (resolve_agent_state_target); both are gone since there is no longer a target to resolve. - loose_ends::hive_wide and Capability::QueryAgentState removed as dead code — their only callers were the two functions above. - is_descendant_of is untouched (still used by lifecycle_handlers.rs and schedules.rs independently of this change). - Docs updated: docs/turn-loop/mcp.md, docs/web-ui/dashboard.md, docs/process/conventions.md (Loose-ends wire shape + capabilities table), plus the doc comments in hive-core-agent-sock, mcp_config.rs and capabilities.rs that described the old shape. Refs #4480
This commit is contained in:
parent
67ffb124cb
commit
729c5b4f42
12 changed files with 41 additions and 178 deletions
|
|
@ -181,14 +181,12 @@ privileged bot, etc.) can land later without breaking existing
|
||||||
handlers. Each row carries enough context that the caller renders
|
handlers. Each row carries enough context that the caller renders
|
||||||
it directly as a bulleted list, no follow-up fetch needed.
|
it directly as a bulleted list, no follow-up fetch needed.
|
||||||
|
|
||||||
Per-flavour scoping is uniform across the two cancellable variants:
|
`GetLooseEnds` takes no target — it always returns the caller's own
|
||||||
|
rows, on both the agent and manager sockets alike (the manager, `ruth`,
|
||||||
- **agent-flavour** `GetLooseEnds` only surfaces rows the calling
|
is a normal agent here, just auto-deployed with different default
|
||||||
agent has standing in. `Approval` rows only appear when the
|
capabilities). `Approval` rows only appear when the calling agent is
|
||||||
calling agent is the manager (sub-agents don't submit
|
the manager (sub-agents don't submit approvals). `Reminder` rows are
|
||||||
approvals). `Reminder` rows are scoped to `owner == self`.
|
scoped to `owner == self`.
|
||||||
- **manager-flavour** `GetLooseEnds` lists every pending row in
|
|
||||||
the swarm — full audit view.
|
|
||||||
|
|
||||||
Per-variant fields:
|
Per-variant fields:
|
||||||
|
|
||||||
|
|
@ -364,7 +362,6 @@ that allows the underlying resource access.
|
||||||
|---|---|
|
|---|---|
|
||||||
| `manage_root_agent` | may lifecycle-manage the root/manager agent via `kill`/`start`/`restart` |
|
| `manage_root_agent` | may lifecycle-manage the root/manager agent via `kill`/`start`/`restart` |
|
||||||
| `read_host_journal` | registers the `get_host_journal` MCP tool + serves `GET /journal-host` requests |
|
| `read_host_journal` | registers the `get_host_journal` MCP tool + serves `GET /journal-host` requests |
|
||||||
| `query_agent_state` | may call `get_loose_ends` / `CountPendingReminders` targeting non-child agents |
|
|
||||||
|
|
||||||
**Config storage** — per-agent capabilities live in
|
**Config storage** — per-agent capabilities live in
|
||||||
`/var/lib/hyperhive/meta/capabilities.json` alongside `tool-groups.json`.
|
`/var/lib/hyperhive/meta/capabilities.json` alongside `tool-groups.json`.
|
||||||
|
|
|
||||||
|
|
@ -75,20 +75,15 @@ outcomes](README.md#turn-outcomes)) but via a generic "call
|
||||||
payload shapes and routing logic in
|
payload shapes and routing logic in
|
||||||
[`docs/agent-lifecycle/approvals.md` § Helper events](../agent-lifecycle/approvals.md#helper-events-to-the-submitting-agent).
|
[`docs/agent-lifecycle/approvals.md` § Helper events](../agent-lifecycle/approvals.md#helper-events-to-the-submitting-agent).
|
||||||
|
|
||||||
**Inbox** (`inbox` group): `get_loose_ends(agent?)`,
|
**Inbox** (`inbox` group): `get_loose_ends()`,
|
||||||
`cancel_loose_end(kind, id)`, `remind(message, delay_seconds? |
|
`cancel_loose_end(kind, id)`, `remind(message, delay_seconds? |
|
||||||
at_unix_timestamp?)`.
|
at_unix_timestamp?)`.
|
||||||
|
|
||||||
- `get_loose_ends(agent?)` — list scheduled reminders, pending
|
- `get_loose_ends()` — list scheduled reminders, pending approvals you
|
||||||
approvals you submitted, and active local tasks published by
|
submitted, and active local tasks published by external MCP daemons
|
||||||
external MCP daemons (for example running bash tasks from
|
(for example running bash tasks from `hive-bash-daemon`). Each row
|
||||||
`hive-bash-daemon`). Each row carries an id + kind for
|
carries an id + kind for `cancel_loose_end`. Always your own threads —
|
||||||
`cancel_loose_end`. Omit `agent` to list your own threads. Pass
|
there is no way to target another agent.
|
||||||
`agent: "<name>"` to inspect any agent in your subtree — a child, a
|
|
||||||
child's child, every agent below them (always accessible per topology
|
|
||||||
enforcement); a name outside the subtree requires the
|
|
||||||
`query_agent_state` capability. The `"*"` hive-wide query isn't
|
|
||||||
available on the agent socket.
|
|
||||||
- `cancel_loose_end` — hard-delete a `reminder`, cancel a pending
|
- `cancel_loose_end` — hard-delete a `reminder`, cancel a pending
|
||||||
`approval` row, or clear a `todo` row (loose-ends-v2). Agents may
|
`approval` row, or clear a `todo` row (loose-ends-v2). Agents may
|
||||||
only cancel rows they own; the `approval` kind is further restricted
|
only cancel rows they own; the `approval` kind is further restricted
|
||||||
|
|
|
||||||
|
|
@ -440,7 +440,6 @@ The current capabilities are:
|
||||||
|------|--------|
|
|------|--------|
|
||||||
| `manage_root_agent` | allows the `set_status` / lifecycle tools on the root agent |
|
| `manage_root_agent` | allows the `set_status` / lifecycle tools on the root agent |
|
||||||
| `read_host_journal` | unlocks `get_host_journal` to read journald from inside a container |
|
| `read_host_journal` | unlocks `get_host_journal` to read journald from inside a container |
|
||||||
| `query_agent_state` | allows `get_loose_ends(agent: "<name>")` calls targeting other agents |
|
|
||||||
|
|
||||||
Each row is one agent. Columns are the capability names returned by
|
Each row is one agent. Columns are the capability names returned by
|
||||||
`GET /api/capabilities` as `caps: Vec<String>`. Checking or unchecking
|
`GET /api/capabilities` as `caps: Vec<String>`. Checking or unchecking
|
||||||
|
|
|
||||||
|
|
@ -131,19 +131,6 @@ pub struct CancelLooseEndArgs {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
|
||||||
pub struct AgentGetLooseEndsArgs {
|
|
||||||
/// Whose loose ends to list. Omit (or `null`) for your own. You may
|
|
||||||
/// also pass the name of any agent in your subtree — a child, a child's
|
|
||||||
/// child, and so on down — without any extra capability.
|
|
||||||
/// Pass any other agent name to inspect their threads — requires the
|
|
||||||
/// `query_agent_state` capability; without it the request is rejected
|
|
||||||
/// with an error. The `"*"` hive-wide value is not available on the
|
|
||||||
/// agent socket.
|
|
||||||
#[serde(default)]
|
|
||||||
pub agent: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||||
pub struct UpdateMetaInputsArgs {
|
pub struct UpdateMetaInputsArgs {
|
||||||
/// Flake input names to update (e.g. `["bitburner-agent", "nixpkgs"]`).
|
/// Flake input names to update (e.g. `["bitburner-agent", "nixpkgs"]`).
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ mod args;
|
||||||
mod render;
|
mod render;
|
||||||
|
|
||||||
pub use args::{
|
pub use args::{
|
||||||
AckUntilArgs, AgentGetLooseEndsArgs, CancelLooseEndArgs, CancelScheduleArgs, CompactArgs,
|
AckUntilArgs, CancelLooseEndArgs, CancelScheduleArgs, CompactArgs,
|
||||||
CreateRepoArgs, EditScheduleArgs, FireScheduleNowArgs, GetAgentMetaArgs, GetHostJournalArgs,
|
CreateRepoArgs, EditScheduleArgs, FireScheduleNowArgs, GetAgentMetaArgs, GetHostJournalArgs,
|
||||||
MarkTodosDoneArgs, RecvArgs, RemindArgs, RequestSchedulePromptArgs, SendArgs, SetStatusArgs,
|
MarkTodosDoneArgs, RecvArgs, RemindArgs, RequestSchedulePromptArgs, SendArgs, SetStatusArgs,
|
||||||
UpdateMetaInputsArgs,
|
UpdateMetaInputsArgs,
|
||||||
|
|
@ -248,25 +248,20 @@ impl AgentServer {
|
||||||
the same row on its own regardless of whether you marked it done while it was \
|
the same row on its own regardless of whether you marked it done while it was \
|
||||||
still running, so doing that buys nothing and just costs a redundant call later. \
|
still running, so doing that buys nothing and just costs a redundant call later. \
|
||||||
Todos cap at 40 rows; a trailer line says how many more are pending — mark the \
|
Todos cap at 40 rows; a trailer line says how many more are pending — mark the \
|
||||||
reviewed ones done with `mark_todos_done`, then call again for the rest.\n\
|
reviewed ones done with `mark_todos_done`, then call again for the rest."
|
||||||
Pass `agent: \"<name>\"` to inspect a specific peer agent's threads. Direct \
|
|
||||||
child agents are always accessible. For non-children, the `query_agent_state` \
|
|
||||||
capability is required — without it the request is rejected with an error."
|
|
||||||
)]
|
)]
|
||||||
async fn get_loose_ends(&self, Parameters(args): Parameters<AgentGetLooseEndsArgs>) -> String {
|
async fn get_loose_ends(&self) -> String {
|
||||||
run_tool_envelope("get_loose_ends", String::new(), async move {
|
run_tool_envelope("get_loose_ends", String::new(), async move {
|
||||||
let is_self_query = args.agent.is_none();
|
|
||||||
let (resp, retries) = self
|
let (resp, retries) = self
|
||||||
.dispatch(hive_core_agent_sock::Request::GetLooseEnds { agent: args.agent })
|
.dispatch(hive_core_agent_sock::Request::GetLooseEnds)
|
||||||
.await;
|
.await;
|
||||||
// Extract the vec so we can augment before rendering.
|
// Extract the vec so we can augment before rendering.
|
||||||
let mut loose_ends = match resp {
|
let mut loose_ends = match resp {
|
||||||
Ok(hive_core_agent_sock::Response::LooseEnds { loose_ends }) => loose_ends,
|
Ok(hive_core_agent_sock::Response::LooseEnds { loose_ends }) => loose_ends,
|
||||||
other => return annotate_retries(reply_err(other, "get_loose_ends"), retries),
|
other => return annotate_retries(reply_err(other, "get_loose_ends"), retries),
|
||||||
};
|
};
|
||||||
// Prepend matrix unread entry for self-queries only (can't
|
// Prepend matrix unread entry.
|
||||||
// reach another agent's matrix daemon from here).
|
if let Some(unread_rooms) = matrix_unread_summary().await {
|
||||||
if is_self_query && let Some(unread_rooms) = matrix_unread_summary().await {
|
|
||||||
let total = u32::try_from(unread_rooms.len()).unwrap_or(u32::MAX);
|
let total = u32::try_from(unread_rooms.len()).unwrap_or(u32::MAX);
|
||||||
if total > 0 {
|
if total > 0 {
|
||||||
let summary = format_matrix_summary(&unread_rooms);
|
let summary = format_matrix_summary(&unread_rooms);
|
||||||
|
|
@ -279,16 +274,12 @@ impl AgentServer {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Merge the harness's local todos (loose-ends v2) for self-queries.
|
// Merge the harness's local todos (loose-ends v2).
|
||||||
// The harness owns the todo store in-container; another agent's
|
if let Some(todos) = local_todos().await {
|
||||||
// todos aren't reachable from here (same as matrix above).
|
|
||||||
if is_self_query && let Some(todos) = local_todos().await {
|
|
||||||
loose_ends.extend(todos);
|
loose_ends.extend(todos);
|
||||||
}
|
}
|
||||||
// Merge local pending reminders — same self-query-only
|
// Merge local pending reminders.
|
||||||
// restriction: a manager asking for a child's loose-ends no longer
|
if let Some(reminders) = local_reminders().await {
|
||||||
// sees the child's reminders, matching the todos precedent above).
|
|
||||||
if is_self_query && let Some(reminders) = local_reminders().await {
|
|
||||||
loose_ends.extend(reminders);
|
loose_ends.extend(reminders);
|
||||||
}
|
}
|
||||||
annotate_retries(render_loose_ends(&loose_ends), retries)
|
annotate_retries(render_loose_ends(&loose_ends), retries)
|
||||||
|
|
|
||||||
|
|
@ -358,7 +358,7 @@ impl Surface for AgentSurface {
|
||||||
async fn post_turn_counts(socket: &Path) -> (Option<u64>, Option<u64>) {
|
async fn post_turn_counts(socket: &Path) -> (Option<u64>, Option<u64>) {
|
||||||
let threads = match hive_sock_client::request::<_, Response>(
|
let threads = match hive_sock_client::request::<_, Response>(
|
||||||
socket,
|
socket,
|
||||||
&Request::GetLooseEnds { agent: None },
|
&Request::GetLooseEnds,
|
||||||
CONTROL_SOCKET_RETRY,
|
CONTROL_SOCKET_RETRY,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -49,12 +49,9 @@ fn allowed_capability_tools() -> Vec<String> {
|
||||||
let t = token.trim().to_ascii_lowercase();
|
let t = token.trim().to_ascii_lowercase();
|
||||||
match t.as_str() {
|
match t.as_str() {
|
||||||
"read_host_journal" => tools.push("get_host_journal".to_owned()),
|
"read_host_journal" => tools.push("get_host_journal".to_owned()),
|
||||||
// manage_root_agent / query_agent_state don't expose new MCP
|
// manage_root_agent doesn't expose a new MCP tool: it gates
|
||||||
// tools: manage_root_agent gates existing lifecycle tools via
|
// existing lifecycle tools via topology enforcement instead.
|
||||||
// topology enforcement; query_agent_state unlocks the `agent`
|
"manage_root_agent" => {}
|
||||||
// field in get_loose_ends / count_pending_reminders /
|
|
||||||
// reminder_rollup (c0re enforces the cap server-side).
|
|
||||||
"manage_root_agent" | "query_agent_state" => {}
|
|
||||||
unknown => {
|
unknown => {
|
||||||
tracing::warn!(capability = %unknown, "unrecognised capability in HIVE_CAPABILITIES — skipped");
|
tracing::warn!(capability = %unknown, "unrecognised capability in HIVE_CAPABILITIES — skipped");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,7 @@
|
||||||
//!
|
//!
|
||||||
//! ```json
|
//! ```json
|
||||||
//! {
|
//! {
|
||||||
//! "atlas": ["read_host_journal"],
|
//! "atlas": ["read_host_journal"]
|
||||||
//! "ruth": ["query_agent_state"]
|
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
//! Loose-ends aggregator. Walks the `approvals` table once per call and
|
//! Loose-ends aggregator. Walks the `approvals` table once per call and
|
||||||
//! assembles a `Vec<LooseEnd>` for either a single agent (`for_agent`) or
|
//! assembles a `Vec<LooseEnd>` for a single agent (`for_agent`) — always
|
||||||
//! the whole hive (`hive_wide`). `Request::GetLooseEnds` from either the
|
//! the caller's own. `Request::GetLooseEnds` from either the agent or
|
||||||
//! agent or manager socket lands here so the routing logic + age-seconds
|
//! manager socket lands here so the age-seconds derivation stays in one
|
||||||
//! derivation stay in one place. Reminders are agent-local (in-container
|
//! place. Reminders are agent-local (in-container
|
||||||
//! `hive-agent::reminders` store) and never sourced from here. The
|
//! `hive-agent::reminders` store) and never sourced from here. The
|
||||||
//! `ask`/`answer` MCP tools, their wire protocol (`hive-c0re::questions`,
|
//! `ask`/`answer` MCP tools, their wire protocol (`hive-c0re::questions`,
|
||||||
//! `stores::operator_questions`), and the operator dashboard's questions
|
//! `stores::operator_questions`), and the operator dashboard's questions
|
||||||
|
|
@ -75,21 +75,3 @@ pub fn for_agent(coord: &Coordinator, agent: &str) -> Result<Vec<LooseEnd>> {
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hive-wide loose-ends view: EVERY pending approval. Manager surface
|
|
||||||
/// only; sub-agents can't see each other's threads via the agent surface
|
|
||||||
/// (`for_agent` filters by name).
|
|
||||||
pub fn hive_wide(coord: &Coordinator) -> Result<Vec<LooseEnd>> {
|
|
||||||
let now = Utc::now().timestamp();
|
|
||||||
let mut out = Vec::new();
|
|
||||||
for a in coord.approvals.pending()? {
|
|
||||||
out.push(LooseEnd::Approval {
|
|
||||||
id: a.id,
|
|
||||||
agent: a.agent.to_string(),
|
|
||||||
commit_ref: a.commit_ref,
|
|
||||||
description: a.description,
|
|
||||||
age_seconds: saturating_age(now, a.requested_at.timestamp()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -541,7 +541,6 @@ fn handle_requeue_inflight(
|
||||||
/// Unified dispatch for every socket connection — per-agent sockets and the
|
/// Unified dispatch for every socket connection — per-agent sockets and the
|
||||||
/// (now pure-transport) manager socket alike. There is no privilege bit;
|
/// (now pure-transport) manager socket alike. There is no privilege bit;
|
||||||
/// authority derives uniformly from the caller's identity: hive-wide
|
/// authority derives uniformly from the caller's identity: hive-wide
|
||||||
/// agent-state queries require the `QueryAgentState` capability; hive-wide
|
|
||||||
/// orchestration verbs (schedules / meta-inputs) require the matching
|
/// orchestration verbs (schedules / meta-inputs) require the matching
|
||||||
/// tool-group (the grantable capability).
|
/// tool-group (the grantable capability).
|
||||||
async fn dispatch(req: &Request, agent: &str, coord: &Arc<Coordinator>) -> Response {
|
async fn dispatch(req: &Request, agent: &str, coord: &Arc<Coordinator>) -> Response {
|
||||||
|
|
@ -550,11 +549,7 @@ async fn dispatch(req: &Request, agent: &str, coord: &Arc<Coordinator>) -> Respo
|
||||||
}
|
}
|
||||||
match req {
|
match req {
|
||||||
Request::ListDescendants => handle_list_descendants(coord, agent).await,
|
Request::ListDescendants => handle_list_descendants(coord, agent).await,
|
||||||
// Agent-state queries: own subtree is free; other agents + the
|
Request::GetLooseEnds => handle_get_loose_ends(coord, agent),
|
||||||
// hive-wide `"*"` sweep require `QueryAgentState`.
|
|
||||||
Request::GetLooseEnds { agent: target } => {
|
|
||||||
handle_get_loose_ends(coord, agent, target.as_deref())
|
|
||||||
}
|
|
||||||
// Orchestration verbs — gated per-verb on tool-group membership
|
// Orchestration verbs — gated per-verb on tool-group membership
|
||||||
// (see `dispatch_orchestration`).
|
// (see `dispatch_orchestration`).
|
||||||
_ => dispatch_orchestration(req, agent, coord).await,
|
_ => dispatch_orchestration(req, agent, coord).await,
|
||||||
|
|
@ -652,29 +647,9 @@ fn require_group(agent: &str, group: &str, action: &str) -> Option<Response> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `GetLooseEnds` — read the target's loose ends. `None` / own / a subtree
|
/// `GetLooseEnds` — always the caller's own loose ends.
|
||||||
/// descendant resolve freely (a parent sees its subtree, the root sees all);
|
fn handle_get_loose_ends(coord: &Arc<Coordinator>, agent: &str) -> Response {
|
||||||
/// any other named agent needs `QueryAgentState`; `"*"` is a hive-wide sweep
|
match crate::loose_ends::for_agent(coord, agent) {
|
||||||
/// gated on `QueryAgentState`.
|
|
||||||
fn handle_get_loose_ends(coord: &Arc<Coordinator>, agent: &str, target: Option<&str>) -> Response {
|
|
||||||
let result = if target == Some("*") {
|
|
||||||
if !crate::capabilities::has_cap(
|
|
||||||
agent,
|
|
||||||
hive_sh4re::permissions::Capability::QueryAgentState,
|
|
||||||
) {
|
|
||||||
return Response::Err {
|
|
||||||
message: "query_agent_state capability required for hive-wide loose ends"
|
|
||||||
.to_owned(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
crate::loose_ends::hive_wide(coord)
|
|
||||||
} else {
|
|
||||||
match resolve_agent_state_target(agent, target) {
|
|
||||||
Ok(name) => crate::loose_ends::for_agent(coord, name),
|
|
||||||
Err(message) => return Response::Err { message },
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match result {
|
|
||||||
Ok(loose_ends) => Response::LooseEnds { loose_ends },
|
Ok(loose_ends) => Response::LooseEnds { loose_ends },
|
||||||
Err(e) => Response::Err {
|
Err(e) => Response::Err {
|
||||||
message: format!("{e:#}"),
|
message: format!("{e:#}"),
|
||||||
|
|
@ -773,44 +748,6 @@ fn check_can_cancel_approval(canceller: &str) -> Result<(), String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the target agent name for a *named* `GetLooseEnds` query. Rules:
|
|
||||||
///
|
|
||||||
/// - `None` (or `Some(caller)`) → the caller's own threads (always allowed).
|
|
||||||
/// - `Some(descendant)` in the caller's subtree (the root's subtree is the whole hive) → allowed, no extra capability.
|
|
||||||
/// - `Some(other)` outside the subtree → requires the `query_agent_state` capability; error otherwise.
|
|
||||||
/// - `Some("*")` → rejected here; the hive-wide sweep is handled by `handle_get_loose_ends` under the same `query_agent_state` gate.
|
|
||||||
fn resolve_agent_state_target<'a>(
|
|
||||||
caller: &'a str,
|
|
||||||
target: Option<&'a str>,
|
|
||||||
) -> Result<&'a str, String> {
|
|
||||||
match target {
|
|
||||||
None => Ok(caller),
|
|
||||||
Some("*") => Err(
|
|
||||||
"hive-wide query (agent=\"*\") is only valid for loose-ends; \
|
|
||||||
not available for this query"
|
|
||||||
.to_owned(),
|
|
||||||
),
|
|
||||||
Some(name) => {
|
|
||||||
// Own subtree (the root covers all) is visible without extra
|
|
||||||
// capability; `is_descendant_of` returns true for `name == caller`.
|
|
||||||
if crate::topology::is_descendant_of(name, caller) {
|
|
||||||
return Ok(name);
|
|
||||||
}
|
|
||||||
if crate::capabilities::has_cap(
|
|
||||||
caller,
|
|
||||||
hive_sh4re::permissions::Capability::QueryAgentState,
|
|
||||||
) {
|
|
||||||
Ok(name)
|
|
||||||
} else {
|
|
||||||
Err(format!(
|
|
||||||
"agent `{caller}` cannot query `{name}`: not in its subtree and \
|
|
||||||
`query_agent_state` capability is not granted"
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Field-named journal-query knobs for [`dispatch_host_journal`].
|
/// Field-named journal-query knobs for [`dispatch_host_journal`].
|
||||||
/// Borrows straight from the matched `GetHostJournal` request variant.
|
/// Borrows straight from the matched `GetHostJournal` request variant.
|
||||||
pub struct HostJournalArgs<'a> {
|
pub struct HostJournalArgs<'a> {
|
||||||
|
|
|
||||||
|
|
@ -63,16 +63,11 @@ pub enum Request {
|
||||||
/// Non-mutating — pulls from the broker without delivering. The
|
/// Non-mutating — pulls from the broker without delivering. The
|
||||||
/// per-agent web UI uses this to render its own inbox section.
|
/// per-agent web UI uses this to render its own inbox section.
|
||||||
Recent { limit: u64 },
|
Recent { limit: u64 },
|
||||||
/// Loose-ends view. On the agent socket: `None` = self; direct
|
/// Loose-ends view — always the caller's own. Same shape on the agent
|
||||||
/// children are always accessible; non-children require the
|
/// and manager sockets; the manager (`ruth`) is a normal agent here, it
|
||||||
/// `query_agent_state` capability — rejected with an error otherwise;
|
/// just sees its own threads like anyone else. See
|
||||||
/// `"*"` is always rejected (use the manager socket). On the manager
|
/// `docs/process/conventions.md::Loose-ends wire shape`.
|
||||||
/// socket: `None` = manager self, `"*"` = hive-wide, any name =
|
GetLooseEnds,
|
||||||
/// that agent. See `docs/process/conventions.md::Loose-ends wire shape`.
|
|
||||||
GetLooseEnds {
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
agent: Option<String>,
|
|
||||||
},
|
|
||||||
/// Set a free-text status string visible on the dashboard. The harness
|
/// Set a free-text status string visible on the dashboard. The harness
|
||||||
/// writes `{state_dir}/hyperhive-status` locally before sending this
|
/// writes `{state_dir}/hyperhive-status` locally before sending this
|
||||||
/// request; hive-c0re just triggers a dashboard rescan on receipt.
|
/// request; hive-c0re just triggers a dashboard rescan on receipt.
|
||||||
|
|
|
||||||
|
|
@ -367,25 +367,12 @@ pub enum Capability {
|
||||||
/// MCP tool `get_host_journal` is only registered in the harness
|
/// MCP tool `get_host_journal` is only registered in the harness
|
||||||
/// when this capability is present.
|
/// when this capability is present.
|
||||||
ReadHostJournal,
|
ReadHostJournal,
|
||||||
/// Agent can query agents outside its own subtree via `GetLooseEnds`,
|
|
||||||
/// `CountPendingReminders`, and `ReminderRollup` on the agent
|
|
||||||
/// socket. Without this capability, targeting an agent outside the
|
|
||||||
/// caller's subtree is rejected with an error (the caller itself and
|
|
||||||
/// every descendant are always accessible without any capability).
|
|
||||||
/// The `"*"` hive-wide value is not
|
|
||||||
/// available on the agent socket even with this capability — use the
|
|
||||||
/// manager socket for swarm-wide scans.
|
|
||||||
QueryAgentState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Capability {
|
impl Capability {
|
||||||
/// Every known capability in a stable order. Use this to enumerate
|
/// Every known capability in a stable order. Use this to enumerate
|
||||||
/// columns in the permissions UI or validate incoming capability strings.
|
/// columns in the permissions UI or validate incoming capability strings.
|
||||||
pub const ALL: &'static [Self] = &[
|
pub const ALL: &'static [Self] = &[Self::ManageRootAgent, Self::ReadHostJournal];
|
||||||
Self::ManageRootAgent,
|
|
||||||
Self::ReadHostJournal,
|
|
||||||
Self::QueryAgentState,
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Short human-readable description suitable for a tooltip or help text.
|
/// Short human-readable description suitable for a tooltip or help text.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
@ -395,9 +382,6 @@ impl Capability {
|
||||||
"lifecycle-manage the root/manager agent on hive crash recovery"
|
"lifecycle-manage the root/manager agent on hive crash recovery"
|
||||||
}
|
}
|
||||||
Self::ReadHostJournal => "read host journald via get_host_journal MCP tool",
|
Self::ReadHostJournal => "read host journald via get_host_journal MCP tool",
|
||||||
Self::QueryAgentState => {
|
|
||||||
"query non-child agents' loose ends and reminder state via get_loose_ends"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue