Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

...
43 changed files with 732 additions and 677 deletions

View file

@ -643,7 +643,7 @@ live dial of the target agent's in-container todo socket (same
`UpsertTodo` request in-container producers use); the `Spawn` approval
uses this path, not a `HelperEvent`. Legacy approval rows that predate the
submitter column fall back to the
root agent. Variants (`hive_sh4re::HelperEvent`):
root agent. Variants (`hive_sh4re::manager::HelperEvent`):
- `ApprovalResolved { id, agent, commit_ref, status, note }` —
fired by `actions::approve` + `actions::deny` whenever an

View file

@ -318,7 +318,7 @@ straight to `new Date(s)` for display.
## Tool groups
The MCP tool surface an agent receives is derived from a set of named
`ToolGroup` values (`hive_sh4re::ToolGroup`), not from a hardcoded
`ToolGroup` values (`hive_sh4re::permissions::ToolGroup`), not from a hardcoded
binary flavor.
| Group | Tools |

View file

@ -975,7 +975,7 @@ impl AgentServer {
let target_count = args.targets.len();
let (resp, retries) = self
.dispatch(hive_core_agent_sock::Request::RequestSchedulePrompt(
hive_sh4re::SchedulePromptPayload {
hive_sh4re::manager::SchedulePromptPayload {
targets: args.targets,
body: args.body,
first_fire_at_unix: args.first_fire_at_unix,

View file

@ -18,7 +18,7 @@ const SEND_ALLOW_PATH: &str = "/etc/hyperhive/send-allow.json";
/// the refusal as the tool result so claude knows the message didn't
/// land and can react (e.g. route via `<parent>` instead).
pub fn check_send_allowed(to: &str) -> Result<(), String> {
if to == hive_sh4re::PARENT_RECIPIENT {
if to == hive_sh4re::manager::PARENT_RECIPIENT {
// Always allow `<parent>` — the allow-list constrains peer
// chatter, not the structural reporting line; the operator
// can rewire who the parent IS via `set_parent` without
@ -53,6 +53,6 @@ pub fn check_send_allowed(to: &str) -> Result<(), String> {
(configured in agent.nix). Allowed: {allow:?}. Your structural \
parent is always reachable — route through `send(to: \"{}\", …)` \
if you need to reach someone outside the allow-list.",
hive_sh4re::PARENT_RECIPIENT
hive_sh4re::manager::PARENT_RECIPIENT
))
}

View file

@ -147,7 +147,9 @@ pub enum Response {
/// `CountPendingReminders` result.
PendingRemindersCount { count: u64 },
/// `ReminderRollup` result.
ReminderRollup { stats: hive_sh4re::ReminderStats },
ReminderRollup {
stats: hive_sh4re::approvals::ReminderStats,
},
/// Op failed; `message` is operator-facing.
Err { message: String },
}

View file

@ -71,7 +71,7 @@ use crate::turn_stats::TurnStats;
use anyhow::Result;
use clap::Parser;
use hive_core_agent_sock::{Request, Response};
use hive_sh4re::{HelperEvent, SYSTEM_SENDER};
use hive_sh4re::manager::{HelperEvent, SYSTEM_SENDER};
use hive_sock_client::Retry;
#[derive(Parser)]
@ -406,7 +406,7 @@ impl Surface for AgentSurface {
let res = hive_sock_client::request::<_, Response>(
socket,
&Request::Send {
to: hive_sh4re::PARENT_RECIPIENT.into(),
to: hive_sh4re::manager::PARENT_RECIPIENT.into(),
body,
in_reply_to: None,
},

View file

@ -35,14 +35,14 @@ pub const DEFAULT_MCP_HTTP_PORT: u16 = 8790;
pub const ALLOWED_BUILTIN_TOOLS: &[&str] = &["Edit", "Glob", "Grep", "Read", "Skill", "Write"];
/// Env var written by the meta renderer with a comma-separated list of
/// `hive_sh4re::ToolGroup` `snake_case` names (e.g. `"messaging,inbox,meta"`).
/// `hive_sh4re::permissions::ToolGroup` `snake_case` names (e.g. `"messaging,inbox,meta"`).
/// When present, the harness expands the groups into per-tool allow entries
/// 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.
/// `hive_sh4re::permissions::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
@ -87,18 +87,18 @@ fn allowed_capability_tools() -> Vec<String> {
/// (`messaging`, `meta`, `inbox`, `lifecycle`, `approvals`, `scheduling`,
/// `diagnostics`, `execution`). Unrecognised tokens are logged and skipped.
/// Falls back to `AGENT_DEFAULT` when the env var is absent or empty.
fn effective_tool_groups() -> Vec<hive_sh4re::ToolGroup> {
fn effective_tool_groups() -> Vec<hive_sh4re::permissions::ToolGroup> {
let raw = match std::env::var(TOOL_GROUPS_ENV) {
Ok(v) if !v.trim().is_empty() => v,
_ => return hive_sh4re::ToolGroup::AGENT_DEFAULT.to_vec(),
_ => return hive_sh4re::permissions::ToolGroup::AGENT_DEFAULT.to_vec(),
};
let mut groups = Vec::new();
for token in raw.split(',') {
let t = token.trim().to_ascii_lowercase();
// Parse via serde_json (the canonical deserialization path).
if let Ok(g) =
serde_json::from_value::<hive_sh4re::ToolGroup>(serde_json::Value::String(t.clone()))
{
if let Ok(g) = serde_json::from_value::<hive_sh4re::permissions::ToolGroup>(
serde_json::Value::String(t.clone()),
) {
groups.push(g);
} else {
tracing::warn!(token = %t, "{TOOL_GROUPS_ENV}: unknown tool group, skipping");
@ -109,7 +109,7 @@ fn effective_tool_groups() -> Vec<hive_sh4re::ToolGroup> {
"{TOOL_GROUPS_ENV} set but contained no recognised groups; \
falling back to AGENT_DEFAULT"
);
return hive_sh4re::ToolGroup::AGENT_DEFAULT.to_vec();
return hive_sh4re::permissions::ToolGroup::AGENT_DEFAULT.to_vec();
}
groups
}
@ -124,9 +124,9 @@ fn effective_tool_groups() -> Vec<hive_sh4re::ToolGroup> {
/// an out-of-process server has **no** later enforcement point — once it is
/// in the claude MCP config the agent can call it. So this gate, applied at
/// config-render time, is the security boundary for those servers.
fn extra_server_required_group(server: &str) -> Option<hive_sh4re::ToolGroup> {
fn extra_server_required_group(server: &str) -> Option<hive_sh4re::permissions::ToolGroup> {
match server {
"bash" => Some(hive_sh4re::ToolGroup::Execution),
"bash" => Some(hive_sh4re::permissions::ToolGroup::Execution),
_ => None,
}
}
@ -134,14 +134,14 @@ fn extra_server_required_group(server: &str) -> Option<hive_sh4re::ToolGroup> {
/// Whether an extra MCP server should be exposed to claude given the active
/// tool `groups`. A gated server (see [`extra_server_required_group`]) is
/// suppressed when the agent lacks its required group.
fn extra_server_enabled(server: &str, groups: &[hive_sh4re::ToolGroup]) -> bool {
fn extra_server_enabled(server: &str, groups: &[hive_sh4re::permissions::ToolGroup]) -> bool {
extra_server_required_group(server).is_none_or(|required| groups.contains(&required))
}
#[cfg(test)]
mod extra_server_gate_tests {
use super::{extra_server_enabled, extra_server_required_group};
use hive_sh4re::ToolGroup;
use hive_sh4re::permissions::ToolGroup;
#[test]
fn bash_is_gated_behind_execution() {
@ -173,13 +173,13 @@ mod extra_server_gate_tests {
/// requires updating the matching `ToolGroup::tools()` slice in hive-sh4re
/// (single source of truth). See `docs/conventions.md::Tool groups`.
#[must_use]
pub fn allowed_mcp_tools(groups: &[hive_sh4re::ToolGroup]) -> Vec<String> {
pub fn allowed_mcp_tools(groups: &[hive_sh4re::permissions::ToolGroup]) -> Vec<String> {
// Collect all tool names, deduplicating while preserving order.
// Always-on tools (e.g. `set_status`) come first so they're present
// regardless of which groups the agent is granted — a misconfigured
// agent still has to be able to report its dashboard status.
let mut seen = std::collections::HashSet::new();
let mut out: Vec<String> = hive_sh4re::ToolGroup::ALWAYS_ON_TOOLS
let mut out: Vec<String> = hive_sh4re::permissions::ToolGroup::ALWAYS_ON_TOOLS
.iter()
.copied()
.chain(groups.iter().flat_map(|g| g.tools().iter().copied()))
@ -406,7 +406,7 @@ fn build_mcp_servers() -> serde_json::Map<String, serde_json::Value> {
#[cfg(test)]
mod tests {
use super::{SERVER_NAME, allowed_mcp_tools};
use hive_sh4re::ToolGroup;
use hive_sh4re::permissions::ToolGroup;
fn qualified(tool: &str) -> String {
format!("mcp__{SERVER_NAME}__{tool}")

View file

@ -18,7 +18,7 @@ use std::sync::Mutex;
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use hive_sh4re::ReminderStats;
use hive_sh4re::approvals::ReminderStats;
use hive_sh4re::wire_time;
use rusqlite::{Connection, params};

View file

@ -15,7 +15,7 @@ use chrono::Utc;
use rusqlite::{Connection, OpenFlags};
use serde::Serialize;
use hive_sh4re::ReminderStats;
use hive_sh4re::approvals::ReminderStats;
/// Window param accepted by `/api/stats?window=`. Each maps to a
/// total span + the bucket width used to roll up trend series.

View file

@ -29,7 +29,7 @@ pub(super) async fn api_stats(
/// `HIVE_AGENT_SOCKET` — was a broker RPC before reminders moved
/// in-container. Returns `None` on any transport / decode failure or
/// when the socket is unset — the stats are decorative, not authoritative.
async fn fetch_reminder_stats(window_secs: u64) -> Option<hive_sh4re::ReminderStats> {
async fn fetch_reminder_stats(window_secs: u64) -> Option<hive_sh4re::approvals::ReminderStats> {
match crate::todo_server::dial(&hive_agent_sock::Request::ReminderRollup {
since_secs: window_secs,
})

View file

@ -6,7 +6,8 @@
use std::sync::Arc;
use anyhow::{Context as _, Result, bail};
use hive_sh4re::{ApprovalKind, ApprovalStatus, HelperEvent};
use hive_sh4re::approvals::{ApprovalKind, ApprovalStatus};
use hive_sh4re::manager::HelperEvent;
use crate::coordinator::Coordinator;
use crate::lifecycle;
@ -142,7 +143,7 @@ fn rollback_ref(approval_id: i64) -> String {
/// `pr` and `reviewed` are fields of the approval row the operator signed off
/// on — so re-reading is both cheap and the authoritative source of truth.
struct DeployCtx {
approval: hive_sh4re::Approval,
approval: hive_sh4re::approvals::Approval,
/// PR number, parsed from `approval.commit_ref`.
pr: u64,
/// The PR head sha the operator reviewed (`approval.fetched_sha`).
@ -410,7 +411,7 @@ const PR_FAIL_LOG_TAIL_BYTES: usize = 4000;
/// only the error text.
async fn post_merge_failure_to_pr(
coord: &Arc<Coordinator>,
approval: &hive_sh4re::Approval,
approval: &hive_sh4re::approvals::Approval,
err: &anyhow::Error,
) {
let Ok(pr) = approval.commit_ref.parse::<u64>() else {
@ -472,11 +473,12 @@ fn tail_bytes(s: &str, max_bytes: usize) -> String {
/// The worker takes over from here — fan-out at fire time.
async fn run_approval_schedule_prompt(
coord: &Coordinator,
approval: hive_sh4re::Approval,
approval: hive_sh4re::approvals::Approval,
) -> Result<()> {
let result: Result<()> = async {
let payload: hive_sh4re::SchedulePromptPayload = serde_json::from_str(&approval.commit_ref)
.context("decode SchedulePromptPayload from approval.commit_ref")?;
let payload: hive_sh4re::manager::SchedulePromptPayload =
serde_json::from_str(&approval.commit_ref)
.context("decode SchedulePromptPayload from approval.commit_ref")?;
coord
.scheduled_prompts
.submit(&crate::scheduled_prompts::NewSchedule {
@ -598,7 +600,7 @@ fn fetch_approval_for_worker(
coord: &Coordinator,
approval_id: i64,
expected_kind: ApprovalKind,
) -> Result<hive_sh4re::Approval> {
) -> Result<hive_sh4re::approvals::Approval> {
let approval = coord
.approvals
.get(approval_id)
@ -641,7 +643,7 @@ async fn forge_after_first_spawn(coord: &Arc<Coordinator>, agent: &str) {
/// work that doesn't justify a queue card.
async fn run_approval_init_config(
coord: &Coordinator,
approval: hive_sh4re::Approval,
approval: hive_sh4re::approvals::Approval,
proposed_dir: std::path::PathBuf,
claude_dir: std::path::PathBuf,
notes_dir: std::path::PathBuf,
@ -681,7 +683,7 @@ async fn run_approval_init_config(
async fn finish_approval(
coord: &Coordinator,
approval: &hive_sh4re::Approval,
approval: &hive_sh4re::approvals::Approval,
result: Result<()>,
terminal_tag: Option<String>,
) -> Result<()> {
@ -960,7 +962,7 @@ pub async fn destroy(coord: &Arc<Coordinator>, name: &str, purge: bool) -> Resul
drop(guard);
let _ = coord
.push_todo(
hive_sh4re::MANAGER_AGENT,
hive_sh4re::manager::MANAGER_AGENT,
"core",
Some(format!("destroyed:{name}")),
format!("agent '{name}' destroyed"),

View file

@ -3,7 +3,7 @@
//! and `tool-groups.json`.
//!
//! Format: a JSON object mapping agent name to an array of
//! `hive_sh4re::Capability` `snake_case` strings:
//! `hive_sh4re::permissions::Capability` `snake_case` strings:
//!
//! ```json
//! {
@ -52,7 +52,7 @@ pub fn caps_for(name: &str) -> Vec<String> {
/// Check whether an agent holds a specific capability.
#[must_use]
pub fn has_cap(name: &str, cap: hive_sh4re::Capability) -> bool {
pub fn has_cap(name: &str, cap: hive_sh4re::permissions::Capability) -> bool {
caps_for(name)
.iter()
.any(|s| s.eq_ignore_ascii_case(cap.as_str()))

View file

@ -3,7 +3,7 @@
//! and the meta `flake.nix`.
//!
//! Format: a JSON object mapping agent name to an array of
//! `hive_sh4re::ToolGroup` `snake_case` strings:
//! `hive_sh4re::permissions::ToolGroup` `snake_case` strings:
//!
//! ```json
//! {
@ -70,7 +70,7 @@ fn write(map: &BTreeMap<String, Vec<String>>) -> std::io::Result<()> {
/// Returns `Ok(())` when all names are known, or `Err` listing the
/// unrecognised names so callers can surface a useful error message.
pub fn validate_groups(groups: &[String]) -> anyhow::Result<()> {
let valid: std::collections::BTreeSet<&str> = hive_sh4re::ToolGroup::ALL
let valid: std::collections::BTreeSet<&str> = hive_sh4re::permissions::ToolGroup::ALL
.iter()
.map(|g| g.as_str())
.collect();
@ -85,7 +85,7 @@ pub fn validate_groups(groups: &[String]) -> anyhow::Result<()> {
anyhow::bail!(
"unknown tool group(s): {}; valid names are: {}",
unknown.join(", "),
hive_sh4re::ToolGroup::ALL
hive_sh4re::permissions::ToolGroup::ALL
.iter()
.map(|g| g.as_str())
.collect::<Vec<_>>()

View file

@ -104,7 +104,7 @@ pub fn top_level_agents_in(topo: &BTreeMap<String, Option<String>>) -> Vec<Strin
}
/// Resolve a magic recipient sentinel (currently just
/// [`hive_sh4re::PARENT_RECIPIENT`]) to a real broker recipient at
/// [`hive_sh4re::manager::PARENT_RECIPIENT`]) to a real broker recipient at
/// send time. Returns an owned `String` so callers can plug it
/// straight into [`crate::broker::Broker::send`] without
/// borrow-juggling around the temporary lookup.
@ -116,7 +116,7 @@ pub fn top_level_agents_in(topo: &BTreeMap<String, Option<String>>) -> Vec<Strin
pub fn resolve_recipient(sender: &str, to: &str) -> String {
// Early exit: only sentinel recipients need topology lookup. This
// keeps the cost of a normal `send` at one string comparison.
if to != hive_sh4re::PARENT_RECIPIENT {
if to != hive_sh4re::manager::PARENT_RECIPIENT {
return to.to_owned();
}
resolve_recipient_in(&read(), sender, to)
@ -131,11 +131,11 @@ pub fn resolve_recipient_in(
sender: &str,
to: &str,
) -> String {
if to == hive_sh4re::PARENT_RECIPIENT {
if to == hive_sh4re::manager::PARENT_RECIPIENT {
topo.get(sender)
.cloned()
.flatten()
.unwrap_or_else(|| hive_sh4re::OPERATOR_RECIPIENT.to_owned())
.unwrap_or_else(|| hive_sh4re::manager::OPERATOR_RECIPIENT.to_owned())
} else {
to.to_owned()
}
@ -722,8 +722,8 @@ mod tests {
assert_eq!(resolve_recipient_in(&topo, "bob", "alice"), "alice");
assert_eq!(resolve_recipient_in(&topo, "bob", "*"), "*");
assert_eq!(
resolve_recipient_in(&topo, "bob", hive_sh4re::OPERATOR_RECIPIENT),
hive_sh4re::OPERATOR_RECIPIENT
resolve_recipient_in(&topo, "bob", hive_sh4re::manager::OPERATOR_RECIPIENT),
hive_sh4re::manager::OPERATOR_RECIPIENT
);
}
@ -732,12 +732,12 @@ mod tests {
let topo = topo_three_level();
// bob's parent is alice → `<parent>` from bob goes to alice.
assert_eq!(
resolve_recipient_in(&topo, "bob", hive_sh4re::PARENT_RECIPIENT),
resolve_recipient_in(&topo, "bob", hive_sh4re::manager::PARENT_RECIPIENT),
"alice"
);
// alice's parent is the manager — same one-hop rewrite.
assert_eq!(
resolve_recipient_in(&topo, "alice", hive_sh4re::PARENT_RECIPIENT),
resolve_recipient_in(&topo, "alice", hive_sh4re::manager::PARENT_RECIPIENT),
crate::lifecycle::MANAGER_NAME
);
}
@ -752,9 +752,9 @@ mod tests {
resolve_recipient_in(
&topo,
crate::lifecycle::MANAGER_NAME,
hive_sh4re::PARENT_RECIPIENT
hive_sh4re::manager::PARENT_RECIPIENT
),
hive_sh4re::OPERATOR_RECIPIENT
hive_sh4re::manager::OPERATOR_RECIPIENT
);
}
@ -766,8 +766,8 @@ mod tests {
// its row.
let topo = topo_three_level();
assert_eq!(
resolve_recipient_in(&topo, "nobody", hive_sh4re::PARENT_RECIPIENT),
hive_sh4re::OPERATOR_RECIPIENT
resolve_recipient_in(&topo, "nobody", hive_sh4re::manager::PARENT_RECIPIENT),
hive_sh4re::manager::OPERATOR_RECIPIENT
);
}

View file

@ -696,7 +696,7 @@ impl Coordinator {
/// rebuild-queue worker after a `PermChange` / Capabilities entry
/// commits the JSON file, so the P3RM1SS10NS tab updates live.
pub fn emit_capabilities_snapshot(self: &Arc<Self>) {
use hive_sh4re::Capability;
use hive_sh4re::permissions::Capability;
let caps = Capability::ALL.iter().map(|c| c.as_str()).collect();
let descriptions = Capability::ALL
.iter()
@ -723,7 +723,7 @@ impl Coordinator {
/// rebuild-queue worker after a `PermChange` / `ToolGroups` entry
/// commits the JSON file, so the P3RM1SS10NS tab updates live.
pub fn emit_tool_groups_snapshot(self: &Arc<Self>) {
use hive_sh4re::ToolGroup;
use hive_sh4re::permissions::ToolGroup;
let groups = ToolGroup::ALL.iter().map(|g| g.as_str()).collect();
let descriptions = ToolGroup::ALL
.iter()
@ -1025,7 +1025,7 @@ impl Coordinator {
/// `<parent>` sentinel's "root → operator" routing (see
/// `docs/conventions.md::Recipient sentinels`). The
/// notifications fire as ordinary broker messages with
/// `from = hive_sh4re::SYSTEM_SENDER` so the dashboard renders
/// `from = hive_sh4re::manager::SYSTEM_SENDER` so the dashboard renders
/// them under the existing system-source styling.
///
/// First validation failure aborts the whole batch with no disk writes.
@ -1056,7 +1056,7 @@ impl Coordinator {
let new_label = new_parent.unwrap_or("<root>");
if let Some(op) = old_parent.as_deref() {
let _ = self.broker.send(&hive_sh4re::Message {
from: hive_sh4re::trusted_sender(hive_sh4re::SYSTEM_SENDER),
from: hive_sh4re::manager::trusted_sender(hive_sh4re::manager::SYSTEM_SENDER),
to: op.to_owned(),
body: format!("{child} moved out of your subtree to {new_label}"),
in_reply_to: None,
@ -1064,7 +1064,7 @@ impl Coordinator {
}
if let Some(np) = new_parent {
let _ = self.broker.send(&hive_sh4re::Message {
from: hive_sh4re::trusted_sender(hive_sh4re::SYSTEM_SENDER),
from: hive_sh4re::manager::trusted_sender(hive_sh4re::manager::SYSTEM_SENDER),
to: np.to_owned(),
body: format!(
"{child} just moved into your subtree (was previously under {old_label})"
@ -1345,7 +1345,7 @@ impl Coordinator {
still in your window."
);
if let Err(e) = self.broker.send(&hive_sh4re::Message {
from: hive_sh4re::trusted_sender(hive_sh4re::SYSTEM_SENDER),
from: hive_sh4re::manager::trusted_sender(hive_sh4re::manager::SYSTEM_SENDER),
to: name.to_owned(),
body,
in_reply_to: None,
@ -1358,8 +1358,8 @@ impl Coordinator {
/// `Message::body`; sender = `SYSTEM_SENDER`. The manager harness
/// recognises the sender and parses the body. Best-effort: a serde or
/// broker error is logged but does not propagate.
pub fn notify_manager(&self, event: &hive_sh4re::HelperEvent) {
self.notify_agent(hive_sh4re::MANAGER_AGENT, event);
pub fn notify_manager(&self, event: &hive_sh4re::manager::HelperEvent) {
self.notify_agent(hive_sh4re::manager::MANAGER_AGENT, event);
}
/// Route an approval-scoped helper event to the agent that submitted
@ -1367,7 +1367,7 @@ impl Coordinator {
/// time). Legacy rows with no recorded submitter — and any lookup
/// failure — fall back to the root agent, preserving the prior
/// always-root behaviour.
pub fn notify_submitter(&self, approval_id: i64, event: &hive_sh4re::HelperEvent) {
pub fn notify_submitter(&self, approval_id: i64, event: &hive_sh4re::manager::HelperEvent) {
let target = self.submitter_or_manager(approval_id);
self.notify_agent(&target, event);
}
@ -1381,7 +1381,7 @@ impl Coordinator {
self.approvals
.submitter_of(approval_id)
.unwrap_or_default()
.unwrap_or_else(|| hive_sh4re::MANAGER_AGENT.to_owned())
.unwrap_or_else(|| hive_sh4re::manager::MANAGER_AGENT.to_owned())
}
/// Push a todo directly into `agent`'s in-container todo store — a
@ -1471,15 +1471,20 @@ impl Coordinator {
/// body = JSON-encoded event). Used to route `QuestionAnswered`
/// events back to the agent that called `ask`, `QuestionAsked`
/// events to the target of a peer question, etc.
pub fn notify_agent(&self, agent: &str, event: &hive_sh4re::HelperEvent) {
self.notify_agent_from(hive_sh4re::SYSTEM_SENDER, agent, event);
pub fn notify_agent(&self, agent: &str, event: &hive_sh4re::manager::HelperEvent) {
self.notify_agent_from(hive_sh4re::manager::SYSTEM_SENDER, agent, event);
}
/// Same as `notify_agent` but with an explicit sender. Use this
/// when the event originates from a known agent or the operator
/// (e.g. `QuestionAnswered` — the answerer should be the `from`,
/// not `system`) so the recipient's terminal shows the right name.
pub fn notify_agent_from(&self, from: &str, agent: &str, event: &hive_sh4re::HelperEvent) {
pub fn notify_agent_from(
&self,
from: &str,
agent: &str,
event: &hive_sh4re::manager::HelperEvent,
) {
let body = match serde_json::to_string(event) {
Ok(s) => s,
Err(e) => {
@ -1488,7 +1493,7 @@ impl Coordinator {
}
};
if let Err(e) = self.broker.send(&hive_sh4re::Message {
from: hive_sh4re::trusted_sender(from),
from: hive_sh4re::manager::trusted_sender(from),
to: agent.to_owned(),
body,
in_reply_to: None,
@ -1510,7 +1515,7 @@ impl Coordinator {
continue;
}
if let Err(e) = self.broker.send(&hive_sh4re::Message {
from: hive_sh4re::trusted_sender(from),
from: hive_sh4re::manager::trusted_sender(from),
to: agent_name.clone(),
body: broadcast_body.clone(),
in_reply_to: None,

View file

@ -8,7 +8,7 @@ use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
use hive_sh4re::Approval;
use hive_sh4re::approvals::Approval;
use serde::Deserialize;
use utoipa::ToSchema;
@ -87,7 +87,8 @@ pub(super) fn gc_orphans(coord: &Coordinator, approvals: Vec<Approval>) -> Vec<A
// the proposed dir is supposed to be missing.
if matches!(
a.kind,
hive_sh4re::ApprovalKind::Spawn | hive_sh4re::ApprovalKind::InitConfig
hive_sh4re::approvals::ApprovalKind::Spawn
| hive_sh4re::approvals::ApprovalKind::InitConfig
) {
return true;
}

View file

@ -220,7 +220,7 @@ pub(super) async fn post_op_send(
if to == "*" {
let errors = state
.coord
.broadcast_send(hive_sh4re::OPERATOR_RECIPIENT, &body);
.broadcast_send(hive_sh4re::manager::OPERATOR_RECIPIENT, &body);
if !errors.is_empty() {
return error_response(&format!(
"op-send broadcast partial fail: {}",
@ -228,7 +228,7 @@ pub(super) async fn post_op_send(
));
}
} else if let Err(e) = state.coord.broker.send(&hive_sh4re::Message {
from: hive_sh4re::trusted_sender(hive_sh4re::OPERATOR_RECIPIENT),
from: hive_sh4re::manager::trusted_sender(hive_sh4re::manager::OPERATOR_RECIPIENT),
to: to.clone(),
body,
in_reply_to: None,
@ -270,7 +270,7 @@ pub(super) async fn post_request_spawn(
}
match state.coord.approvals.submit_kind(
&name,
hive_sh4re::ApprovalKind::Spawn,
hive_sh4re::approvals::ApprovalKind::Spawn,
"",
None,
"operator",

View file

@ -48,11 +48,11 @@ pub(super) struct ToolGroupsSnapshot {
pub(super) async fn get_tool_groups(
State(state): State<AppState>,
) -> axum::Json<ToolGroupsSnapshot> {
let groups = hive_sh4re::ToolGroup::ALL
let groups = hive_sh4re::permissions::ToolGroup::ALL
.iter()
.map(|g| g.as_str())
.collect();
let descriptions = hive_sh4re::ToolGroup::ALL
let descriptions = hive_sh4re::permissions::ToolGroup::ALL
.iter()
.map(|g| (g.as_str(), g.description()))
.collect();
@ -80,7 +80,7 @@ pub(super) async fn get_tool_groups(
/// the container actually runs with.
#[must_use]
pub(crate) fn tool_group_default_names() -> Vec<&'static str> {
hive_sh4re::ToolGroup::AGENT_DEFAULT
hive_sh4re::permissions::ToolGroup::AGENT_DEFAULT
.iter()
.map(|g| g.as_str())
.collect()
@ -204,7 +204,7 @@ pub(super) struct CapabilitiesSnapshot {
pub(super) async fn get_capabilities(
State(state): State<AppState>,
) -> axum::Json<CapabilitiesSnapshot> {
use hive_sh4re::Capability;
use hive_sh4re::permissions::Capability;
let caps = Capability::ALL.iter().map(|c| c.as_str()).collect();
let descriptions = Capability::ALL
.iter()
@ -256,7 +256,7 @@ pub(super) async fn post_capabilities(
if let Some(reject) = guard_agent_name(&state, &logical).await {
return Ok(reject);
}
let known: Vec<&str> = hive_sh4re::Capability::ALL
let known: Vec<&str> = hive_sh4re::permissions::Capability::ALL
.iter()
.map(|c| c.as_str())
.collect();
@ -334,7 +334,7 @@ pub(super) async fn post_permissions(
State(state): State<AppState>,
axum::Json(body): axum::Json<BatchPermsBody>,
) -> Result<Response, ProblemDetails> {
let known_caps: Vec<&str> = hive_sh4re::Capability::ALL
let known_caps: Vec<&str> = hive_sh4re::permissions::Capability::ALL
.iter()
.map(|c| c.as_str())
.collect();

View file

@ -64,33 +64,34 @@ pub(super) async fn post_answer_question(
.with_detail("answer: required"),
);
}
let resp = match state
.coord
.questions
.answer(id, answer, hive_sh4re::OPERATOR_RECIPIENT)
{
Ok((question, asker, target)) => {
tracing::info!(%id, %asker, "operator answered question");
state.coord.notify_agent(
&asker,
&hive_sh4re::HelperEvent::QuestionAnswered {
let resp =
match state
.coord
.questions
.answer(id, answer, hive_sh4re::manager::OPERATOR_RECIPIENT)
{
Ok((question, asker, target)) => {
tracing::info!(%id, %asker, "operator answered question");
state.coord.notify_agent(
&asker,
&hive_sh4re::manager::HelperEvent::QuestionAnswered {
id,
question,
answer: answer.to_owned(),
answerer: hive_sh4re::manager::OPERATOR_RECIPIENT.to_owned(),
},
);
state.coord.emit_question_resolved(
id,
question,
answer: answer.to_owned(),
answerer: hive_sh4re::OPERATOR_RECIPIENT.to_owned(),
},
);
state.coord.emit_question_resolved(
id,
answer,
hive_sh4re::OPERATOR_RECIPIENT,
false,
target.as_deref(),
);
(StatusCode::OK, "ok").into_response()
}
Err(e) => error_response(&format!("answer {id} failed: {e:#}")),
};
answer,
hive_sh4re::manager::OPERATOR_RECIPIENT,
false,
target.as_deref(),
);
(StatusCode::OK, "ok").into_response()
}
Err(e) => error_response(&format!("answer {id} failed: {e:#}")),
};
with_cors(resp)
}
@ -120,25 +121,25 @@ pub(super) async fn post_cancel_question(
match state
.coord
.questions
.answer(id, SENTINEL, hive_sh4re::OPERATOR_RECIPIENT)
.answer(id, SENTINEL, hive_sh4re::manager::OPERATOR_RECIPIENT)
{
Ok((question, asker, target)) => {
tracing::info!(%id, %asker, "operator cancelled question");
state.coord.emit_question_resolved(
id,
SENTINEL,
hive_sh4re::OPERATOR_RECIPIENT,
hive_sh4re::manager::OPERATOR_RECIPIENT,
true,
target.as_deref(),
);
state.coord.notify_agent_from(
hive_sh4re::OPERATOR_RECIPIENT,
hive_sh4re::manager::OPERATOR_RECIPIENT,
&asker,
&hive_sh4re::HelperEvent::QuestionAnswered {
&hive_sh4re::manager::HelperEvent::QuestionAnswered {
id,
question,
answer: SENTINEL.to_owned(),
answerer: hive_sh4re::OPERATOR_RECIPIENT.to_owned(),
answerer: hive_sh4re::manager::OPERATOR_RECIPIENT.to_owned(),
},
);
(StatusCode::OK, "ok").into_response()

View file

@ -69,7 +69,7 @@ pub(super) async fn api_schedules(State(state): State<AppState>) -> Response {
#[utoipa::path(
post,
path = "/api/schedules",
// `hive_sh4re::SchedulePromptPayload` (the actual body) has no `ToSchema` —
// `hive_sh4re::manager::SchedulePromptPayload` (the actual body) has no `ToSchema` —
// same reasoning as the `Vec<serde_json::Value>` placeholder on
// `api_schedules` above.
request_body(content = serde_json::Value, description = "SchedulePromptPayload wire shape"),
@ -82,7 +82,7 @@ pub(super) async fn api_schedules(State(state): State<AppState>) -> Response {
)]
pub(super) async fn post_schedule_new(
State(state): State<AppState>,
axum::Json(payload): axum::Json<hive_sh4re::SchedulePromptPayload>,
axum::Json(payload): axum::Json<hive_sh4re::manager::SchedulePromptPayload>,
) -> Result<Response, ProblemDetails> {
if payload.targets.is_empty() {
return Err(ProblemDetails::from_status_code(StatusCode::BAD_REQUEST)
@ -97,7 +97,7 @@ pub(super) async fn post_schedule_new(
.with_detail("interval_seconds must be > 0 (use None for one-shot)"));
}
let new = crate::scheduled_prompts::NewSchedule {
owner: hive_sh4re::OPERATOR_RECIPIENT.to_owned(),
owner: hive_sh4re::manager::OPERATOR_RECIPIENT.to_owned(),
targets: payload.targets,
body: payload.body,
first_fire_at_unix: payload.first_fire_at_unix,

Some files were not shown because too many files have changed in this diff Show more