manager: same agent loop, ManagerServer MCP surface
This commit is contained in:
parent
accb1445e3
commit
09787659ab
6 changed files with 422 additions and 142 deletions
96
hive-ag3nt/src/turn.rs
Normal file
96
hive-ag3nt/src/turn.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
//! Per-turn claude invocation shared by `hive-ag3nt` and `hive-m1nd`. The
|
||||
//! two binaries differ only in their MCP `Flavor` (agent surface vs.
|
||||
//! manager surface) and their wake-prompt wording; the spawn shape,
|
||||
//! arg-vector, stdin plumbing, and stream-json pumping are identical.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Stdio;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::events::{Bus, LiveEvent};
|
||||
use crate::mcp;
|
||||
|
||||
/// Drop the MCP config blob claude reads from `--mcp-config <path>`.
|
||||
/// `socket` is the hyperhive per-container socket (forwarded to the child
|
||||
/// as `--socket <path>`); `binary_subcommand` is e.g. `"mcp"` for sub-agents
|
||||
/// or `"mcp"` for the manager (both binaries name their MCP subcommand the
|
||||
/// same — the differentiator is which binary `/proc/self/exe` resolves to).
|
||||
pub async fn write_mcp_config(socket: &Path) -> Result<PathBuf> {
|
||||
let parent = socket.parent().unwrap_or_else(|| Path::new("/run/hive"));
|
||||
tokio::fs::create_dir_all(parent).await.ok();
|
||||
let path = parent.join("claude-mcp-config.json");
|
||||
let exe = std::env::current_exe()
|
||||
.ok()
|
||||
.map_or_else(|| "hive-ag3nt".into(), |p| p.display().to_string());
|
||||
let body = mcp::render_claude_config(&exe, socket);
|
||||
tokio::fs::write(&path, body).await?;
|
||||
tracing::info!(path = %path.display(), "wrote claude MCP config");
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Spawn `claude` for one turn and pump `stream-json` stdout into the
|
||||
/// live event bus. Prompt goes over stdin (variadic
|
||||
/// `--allowedTools`/`--tools` would otherwise eat a trailing positional
|
||||
/// prompt). On non-zero exit returns an error; the caller emits the
|
||||
/// `TurnEnd` event.
|
||||
pub async fn run_turn(
|
||||
prompt: &str,
|
||||
mcp_config: &Path,
|
||||
bus: &Bus,
|
||||
flavor: mcp::Flavor,
|
||||
) -> Result<()> {
|
||||
let mut child = Command::new("claude")
|
||||
.arg("--print")
|
||||
.arg("--verbose")
|
||||
.arg("--output-format")
|
||||
.arg("stream-json")
|
||||
.arg("--model")
|
||||
.arg("haiku")
|
||||
.arg("--mcp-config")
|
||||
.arg(mcp_config)
|
||||
.arg("--tools")
|
||||
.arg(mcp::builtin_tools_arg())
|
||||
.arg("--allowedTools")
|
||||
.arg(mcp::allowed_tools_arg(flavor))
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
if let Some(mut stdin) = child.stdin.take() {
|
||||
stdin.write_all(prompt.as_bytes()).await?;
|
||||
stdin.shutdown().await.ok();
|
||||
drop(stdin);
|
||||
}
|
||||
let stdout = child.stdout.take().expect("piped stdout");
|
||||
let stderr = child.stderr.take().expect("piped stderr");
|
||||
|
||||
let bus_out = bus.clone();
|
||||
let bus_err = bus.clone();
|
||||
let pump_stdout = tokio::spawn(async move {
|
||||
let mut reader = BufReader::new(stdout).lines();
|
||||
while let Ok(Some(line)) = reader.next_line().await {
|
||||
match serde_json::from_str::<serde_json::Value>(&line) {
|
||||
Ok(v) => bus_out.emit(LiveEvent::Stream(v)),
|
||||
Err(_) => bus_out.emit(LiveEvent::Note(format!("(non-json) {line}"))),
|
||||
}
|
||||
}
|
||||
});
|
||||
let pump_stderr = tokio::spawn(async move {
|
||||
let mut reader = BufReader::new(stderr).lines();
|
||||
while let Ok(Some(line)) = reader.next_line().await {
|
||||
bus_err.emit(LiveEvent::Note(format!("stderr: {line}")));
|
||||
}
|
||||
});
|
||||
|
||||
let status = child.wait().await?;
|
||||
let _ = pump_stdout.await;
|
||||
let _ = pump_stderr.await;
|
||||
if !status.success() {
|
||||
bail!("claude exited {status}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue