Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3ac4de8fb | ||
|
|
138f6b6c10 | ||
|
|
02bbff1e34 |
43 changed files with 732 additions and 677 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
|
|
|
|||
|
|
@ -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<_>>()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue