ask_operator tool: non-blocking; operator answer arrives as helper event

new mcp tool on the manager surface that queues a question on the
dashboard and returns the question id immediately. operator submits an
answer via /answer-question/<id>; the dashboard fires
HelperEvent::OperatorAnswered { id, question, answer } into the manager
inbox so the next turn picks it up.

also: fix async-form button stuck on spinner after successful submit
(refreshState skipped re-rendering, so the button was never re-enabled).
This commit is contained in:
müde 2026-05-15 18:44:42 +02:00
parent abfd2cce4b
commit 2770630f33
17 changed files with 426 additions and 79 deletions

View file

@ -11,6 +11,7 @@ use anyhow::{Context, Result};
use crate::agent_server::{self, AgentSocket};
use crate::approvals::Approvals;
use crate::broker::Broker;
use crate::operator_questions::OperatorQuestions;
const AGENT_RUNTIME_ROOT: &str = "/run/hyperhive/agents";
const MANAGER_RUNTIME_ROOT: &str = "/run/hyperhive/manager";
@ -26,6 +27,7 @@ const APPLIED_STATE_ROOT: &str = "/var/lib/hyperhive/applied";
pub struct Coordinator {
pub broker: Arc<Broker>,
pub approvals: Arc<Approvals>,
pub questions: Arc<OperatorQuestions>,
/// URL of the hyperhive flake (no fragment). Inlined into per-agent
/// `flake.nix` files as `inputs.hyperhive.url`.
pub hyperhive_flake: String,
@ -58,9 +60,11 @@ impl Coordinator {
pub fn open(db_path: &Path, hyperhive_flake: String, dashboard_port: u16) -> Result<Self> {
let broker = Broker::open(db_path).context("open broker")?;
let approvals = Approvals::open(db_path).context("open approvals")?;
let questions = OperatorQuestions::open(db_path).context("open operator_questions")?;
Ok(Self {
broker: Arc::new(broker),
approvals: Arc::new(approvals),
questions: Arc::new(questions),
hyperhive_flake,
dashboard_port,
agents: Mutex::new(HashMap::new()),