remove flavor from AgentServer — dumb dispatcher, tool groups gate access
This commit is contained in:
parent
3c5abd3c85
commit
7261992fda
1 changed files with 14 additions and 60 deletions
|
|
@ -8,13 +8,11 @@
|
||||||
//! broker-routed protocol. Unaffected by this module.
|
//! broker-routed protocol. Unaffected by this module.
|
||||||
//! - **MCP stdio** owned by this module — what claude actually speaks.
|
//! - **MCP stdio** owned by this module — what claude actually speaks.
|
||||||
//!
|
//!
|
||||||
//! One `AgentServer { socket, flavor }` struct handles both flavors:
|
//! One `AgentServer { socket }` struct shared by agent and manager roles.
|
||||||
//! - `Flavor::Agent` — restricted send allow-list, no manager-only tools.
|
|
||||||
//! - `Flavor::Manager` — manager-only tools unlocked via `require_manager()`.
|
|
||||||
//! `ManagerServer` is a backward-compat type alias for `AgentServer`.
|
//! `ManagerServer` is a backward-compat type alias for `AgentServer`.
|
||||||
//!
|
//! Tool access is gated upstream by `--allowedTools` (derived from the
|
||||||
//! All tools live in one `#[tool_router] impl AgentServer`. Both flavors go
|
//! agent's `ToolGroup` config); the server itself is a dumb dispatcher.
|
||||||
//! through the same `run_tool_envelope` helper so logging stays uniform.
|
//! All tools go through the same `run_tool_envelope` helper.
|
||||||
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -578,12 +576,9 @@ pub struct RemindArgs {
|
||||||
/// `dispatch` call covers both sockets — the only real difference is which
|
/// `dispatch` call covers both sockets — the only real difference is which
|
||||||
/// socket path is used and which tools the flavor enables.
|
/// socket path is used and which tools the flavor enables.
|
||||||
///
|
///
|
||||||
/// Manager-only tools return a clear error when called from an agent context.
|
|
||||||
/// In practice the `--allowedTools` gate prevents that — this is belt-and-suspenders.
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AgentServer {
|
pub struct AgentServer {
|
||||||
socket: PathBuf,
|
socket: PathBuf,
|
||||||
flavor: Flavor,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Backward-compat alias — callers that reference `ManagerServer` by name still compile.
|
/// Backward-compat alias — callers that reference `ManagerServer` by name still compile.
|
||||||
|
|
@ -591,8 +586,8 @@ pub type ManagerServer = AgentServer;
|
||||||
|
|
||||||
impl AgentServer {
|
impl AgentServer {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(socket: PathBuf, flavor: Flavor) -> Self {
|
pub fn new(socket: PathBuf) -> Self {
|
||||||
Self { socket, flavor }
|
Self { socket }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Issue any `Request` through the retry-aware client and pull
|
/// Issue any `Request` through the retry-aware client and pull
|
||||||
|
|
@ -610,16 +605,6 @@ impl AgentServer {
|
||||||
Err(e) => (Err(e), 0),
|
Err(e) => (Err(e), 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an error string when called from a non-manager flavor.
|
|
||||||
/// Manager-only tool methods call this first and early-return on Some.
|
|
||||||
fn require_manager(&self) -> Option<String> {
|
|
||||||
if !matches!(self.flavor, Flavor::Manager) {
|
|
||||||
Some("error: this tool is only available to manager agents".to_owned())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IMPORTANT: when adding a new `#[tool]` fn to this impl, also add
|
// IMPORTANT: when adding a new `#[tool]` fn to this impl, also add
|
||||||
|
|
@ -635,12 +620,10 @@ impl AgentServer {
|
||||||
async fn send(&self, Parameters(args): Parameters<SendArgs>) -> String {
|
async fn send(&self, Parameters(args): Parameters<SendArgs>) -> String {
|
||||||
let log = format!("{args:?}");
|
let log = format!("{args:?}");
|
||||||
let to = args.to.clone();
|
let to = args.to.clone();
|
||||||
// Agent flavor: check per-agent allow-list (hyperhive.allowedRecipients).
|
// Check per-agent allow-list (hyperhive.allowedRecipients). When no
|
||||||
// Manager flavor: unrestricted send.
|
// policy file is present (e.g. manager containers) the check is a no-op.
|
||||||
if matches!(self.flavor, Flavor::Agent) {
|
if let Err(refusal) = check_send_allowed(&to) {
|
||||||
if let Err(refusal) = check_send_allowed(&to) {
|
return run_tool_envelope("send", log, async move { refusal }).await;
|
||||||
return run_tool_envelope("send", log, async move { refusal }).await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
run_tool_envelope("send", log, async move {
|
run_tool_envelope("send", log, async move {
|
||||||
let (resp, retries) = self
|
let (resp, retries) = self
|
||||||
|
|
@ -1214,11 +1197,6 @@ impl AgentServer {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Manager-only tools — guarded by require_manager(). The `--allowedTools`
|
|
||||||
// gate prevents agents from ever calling these in practice; the guard is
|
|
||||||
// belt-and-suspenders in case the gate is misconfigured.
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#[tool(
|
#[tool(
|
||||||
description = "Fetch recent journal log lines for a sub-agent container. Useful \
|
description = "Fetch recent journal log lines for a sub-agent container. Useful \
|
||||||
|
|
@ -1228,9 +1206,6 @@ impl AgentServer {
|
||||||
`lines` defaults to 50 (max capped at 500 on the host side)."
|
`lines` defaults to 50 (max capped at 500 on the host side)."
|
||||||
)]
|
)]
|
||||||
async fn get_logs(&self, Parameters(args): Parameters<GetLogsArgs>) -> String {
|
async fn get_logs(&self, Parameters(args): Parameters<GetLogsArgs>) -> String {
|
||||||
if let Some(e) = self.require_manager() {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
let log = format!("{args:?}");
|
let log = format!("{args:?}");
|
||||||
let agent = args.agent.clone();
|
let agent = args.agent.clone();
|
||||||
run_tool_envelope("get_logs", log, async move {
|
run_tool_envelope("get_logs", log, async move {
|
||||||
|
|
@ -1270,9 +1245,6 @@ impl AgentServer {
|
||||||
&self,
|
&self,
|
||||||
Parameters(args): Parameters<UpdateMetaInputsArgs>,
|
Parameters(args): Parameters<UpdateMetaInputsArgs>,
|
||||||
) -> String {
|
) -> String {
|
||||||
if let Some(e) = self.require_manager() {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
let log = format!("{args:?}");
|
let log = format!("{args:?}");
|
||||||
run_tool_envelope("request_update_meta_inputs", log, async move {
|
run_tool_envelope("request_update_meta_inputs", log, async move {
|
||||||
let label = if args.inputs.is_empty() {
|
let label = if args.inputs.is_empty() {
|
||||||
|
|
@ -1316,9 +1288,6 @@ impl AgentServer {
|
||||||
&self,
|
&self,
|
||||||
Parameters(args): Parameters<RequestSchedulePromptArgs>,
|
Parameters(args): Parameters<RequestSchedulePromptArgs>,
|
||||||
) -> String {
|
) -> String {
|
||||||
if let Some(e) = self.require_manager() {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
let log = format!("{args:?}");
|
let log = format!("{args:?}");
|
||||||
run_tool_envelope("request_schedule_prompt", log, async move {
|
run_tool_envelope("request_schedule_prompt", log, async move {
|
||||||
let target_count = args.targets.len();
|
let target_count = args.targets.len();
|
||||||
|
|
@ -1355,9 +1324,6 @@ impl AgentServer {
|
||||||
owned by a sub-agent in your subtree per topology.json."
|
owned by a sub-agent in your subtree per topology.json."
|
||||||
)]
|
)]
|
||||||
async fn fire_schedule_now(&self, Parameters(args): Parameters<FireScheduleNowArgs>) -> String {
|
async fn fire_schedule_now(&self, Parameters(args): Parameters<FireScheduleNowArgs>) -> String {
|
||||||
if let Some(e) = self.require_manager() {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
let log = format!("{args:?}");
|
let log = format!("{args:?}");
|
||||||
run_tool_envelope("fire_schedule_now", log, async move {
|
run_tool_envelope("fire_schedule_now", log, async move {
|
||||||
let id = args.id;
|
let id = args.id;
|
||||||
|
|
@ -1381,9 +1347,6 @@ impl AgentServer {
|
||||||
owner is one of its sub-agents per topology.json. Other owners are refused."
|
owner is one of its sub-agents per topology.json. Other owners are refused."
|
||||||
)]
|
)]
|
||||||
async fn cancel_schedule(&self, Parameters(args): Parameters<CancelScheduleArgs>) -> String {
|
async fn cancel_schedule(&self, Parameters(args): Parameters<CancelScheduleArgs>) -> String {
|
||||||
if let Some(e) = self.require_manager() {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
let log = format!("{args:?}");
|
let log = format!("{args:?}");
|
||||||
run_tool_envelope("cancel_schedule", log, async move {
|
run_tool_envelope("cancel_schedule", log, async move {
|
||||||
let id = args.id;
|
let id = args.id;
|
||||||
|
|
@ -1416,9 +1379,6 @@ impl AgentServer {
|
||||||
Refuses cancelled schedules (the row's terminal — submit a fresh one)."
|
Refuses cancelled schedules (the row's terminal — submit a fresh one)."
|
||||||
)]
|
)]
|
||||||
async fn edit_schedule(&self, Parameters(args): Parameters<EditScheduleArgs>) -> String {
|
async fn edit_schedule(&self, Parameters(args): Parameters<EditScheduleArgs>) -> String {
|
||||||
if let Some(e) = self.require_manager() {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
let log = format!("{args:?}");
|
let log = format!("{args:?}");
|
||||||
run_tool_envelope("edit_schedule", log, async move {
|
run_tool_envelope("edit_schedule", log, async move {
|
||||||
let id = args.id;
|
let id = args.id;
|
||||||
|
|
@ -1449,9 +1409,6 @@ impl AgentServer {
|
||||||
the swarm is going to be woken up about next."
|
the swarm is going to be woken up about next."
|
||||||
)]
|
)]
|
||||||
async fn list_schedules(&self) -> String {
|
async fn list_schedules(&self) -> String {
|
||||||
if let Some(e) = self.require_manager() {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
run_tool_envelope("list_schedules", String::new(), async move {
|
run_tool_envelope("list_schedules", String::new(), async move {
|
||||||
let (resp, retries) = self
|
let (resp, retries) = self
|
||||||
.dispatch(hive_sh4re::Request::ListSchedules)
|
.dispatch(hive_sh4re::Request::ListSchedules)
|
||||||
|
|
@ -1482,8 +1439,8 @@ impl ServerHandler for AgentServer {}
|
||||||
///
|
///
|
||||||
/// Returns an error if the MCP server fails to initialize or the transport
|
/// Returns an error if the MCP server fails to initialize or the transport
|
||||||
/// encounters a fatal error.
|
/// encounters a fatal error.
|
||||||
pub async fn serve_stdio(socket: PathBuf, flavor: Flavor) -> Result<()> {
|
pub async fn serve_stdio(socket: PathBuf) -> Result<()> {
|
||||||
let server = AgentServer::new(socket, flavor);
|
let server = AgentServer::new(socket);
|
||||||
let service = server.serve(stdio()).await?;
|
let service = server.serve(stdio()).await?;
|
||||||
service.waiting().await?;
|
service.waiting().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -1491,15 +1448,12 @@ pub async fn serve_stdio(socket: PathBuf, flavor: Flavor) -> Result<()> {
|
||||||
|
|
||||||
/// Convenience wrapper: run the agent MCP server over stdio.
|
/// Convenience wrapper: run the agent MCP server over stdio.
|
||||||
pub async fn serve_agent_stdio(socket: PathBuf) -> Result<()> {
|
pub async fn serve_agent_stdio(socket: PathBuf) -> Result<()> {
|
||||||
serve_stdio(socket, Flavor::Agent).await
|
serve_stdio(socket).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience wrapper: run the manager MCP server over stdio.
|
/// Convenience wrapper: run the manager MCP server over stdio.
|
||||||
pub async fn serve_manager_stdio(socket: PathBuf) -> Result<()> {
|
pub async fn serve_manager_stdio(socket: PathBuf) -> Result<()> {
|
||||||
let server = AgentServer::new(socket, Flavor::Manager);
|
serve_stdio(socket).await
|
||||||
let service = server.serve(stdio()).await?;
|
|
||||||
service.waiting().await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue