Watch
0
0
Fork
You've already forked hyperhive
0

remove hive and agent apis nothing calls: build-log routes, /api/compact, host.sock list, mcp.sock wake

This commit is contained in:
atlas 2026-11-13 02:48:37 +01:00
commit 36c7781245
5 changed files with 115 additions and 801 deletions

View file

@ -251,8 +251,7 @@ pub enum TurnState {
Idle,
/// `claude --print` is running for a turn.
Thinking,
/// Operator-triggered `/compact` is running on the persistent
/// session.
/// A `/compact` is running on the persistent session.
Compacting,
}
@ -316,9 +315,8 @@ pub struct Bus {
/// boundary keeps compaction from racing a live claude process mid-turn.
/// `Some(request)` when a compact is pending; `request.wake_prompt` is
/// what to drive as a synthetic follow-up turn once the compaction
/// actually completes (`None` = pending but no follow-up wake wanted,
/// e.g. the operator dashboard's `/compact` button). `None` = no compact
/// pending. Wrapped in [`CompactRequest`] rather than
/// actually completes (`None` = pending but no follow-up wake wanted). `None` = no
/// compact pending. Wrapped in [`CompactRequest`] rather than
/// `Option<Option<String>>` (clippy pedantic's `option_option` lint,
/// and the named field reads clearer at call sites than a bare nested
/// `Option`) so "pending" and "what to wake with" can never desync.
@ -491,8 +489,7 @@ impl Bus {
/// serviced just overwrites `wake_prompt` with the latest ask. `Some
/// (wake_prompt)` schedules a synthetic follow-up turn (driven with
/// `wake_prompt` as its body) once the compaction actually completes;
/// `None` requests a plain compact with no follow-up wake (the operator
/// dashboard's `/compact` button).
/// `None` requests a plain compact with no follow-up wake.
pub fn request_compact(&self, wake_prompt: Option<String>) {
*self.compact_pending.lock().unwrap() = Some(CompactRequest { wake_prompt });
}

View file

@ -11,9 +11,8 @@
//! reminder op then returns `Response::Err`); a reminder *firing* is a
//! separate path (`reminder_timer`), not driven through this socket.
//! `Request::Compact` is the odd one out — it doesn't touch any store, just
//! the harness's [`Bus`] (gate-checked context usage, then the same
//! deferred `compact_pending` flag the operator dashboard's `/compact`
//! button sets). No hive-c0re round-trip, no broker long-poll, no marker
//! the harness's [`Bus`] (gate-checked context usage, then the deferred
//! `compact_pending` flag). No hive-c0re round-trip, no broker long-poll, no marker
//! files.
//!
//! One request/response line per connection, matching the producers'
@ -39,8 +38,7 @@ use crate::todos::{Todo, Todos};
/// Context-usage floor for `Request::Compact`: below this fraction of the
/// effective context window, an agent-initiated compact is refused (nothing
/// meaningful to reclaim yet, and it'd just burn a compaction pass on a
/// near-empty session). Same number a human operator would eyeball off the
/// dashboard's `ctx·Nk` badge before hitting the `/compact` button.
/// near-empty session).
const COMPACT_MIN_USAGE_FRACTION: f64 = 0.66;
/// Pause after a failed `accept`, so a failure that persists (fd exhaustion)
@ -434,13 +432,10 @@ fn mark_todos_done(store: &Todos, ids: &[i64]) -> Response {
}
}
/// `Request::Compact` handler: gate on context usage, then queue the same
/// deferred `compact_pending` flag the operator's `/compact` button sets.
/// Mirrors `hive-agent::web_ui::actions::post_compact` but reachable from
/// the agent's own MCP tool instead of the dashboard, and refuses below
/// [`COMPACT_MIN_USAGE_FRACTION`] instead of always honouring the request —
/// an agent can call this speculatively, a human clicking the dashboard
/// button already made the judgment call. `wake_prompt`, when set, is
/// `Request::Compact` handler: gate on context usage, then queue the
/// deferred `compact_pending` flag. Refuses below
/// [`COMPACT_MIN_USAGE_FRACTION`] because an agent can call this
/// speculatively. `wake_prompt`, when set, is
/// forwarded to `Bus::request_compact` so the turn loop drives a synthetic
/// follow-up turn once the compaction actually finishes.
fn compact(bus: &Bus, wake_prompt: Option<String>) -> Response {

View file

@ -92,7 +92,7 @@ and end the turn.";
/// passed here — they live at `/etc/claude-code/managed-settings.json`
/// and claude auto-discovers them.
/// Materialised at harness startup; shared between the turn loop and the
/// operator-driven `/compact` path so both invocations look identical to
/// `/compact` path so both invocations look identical to
/// claude (same MCP surface, same allowed tools, same role prompt — only the
/// stdin payload differs). The MCP config is rewritten before each
/// invocation ([`TurnFiles::refresh_mcp_config`]), since which servers it
@ -161,7 +161,7 @@ pub async fn write_system_prompt(socket: &Path, label: &str) -> Result<PathBuf>
/// One claude turn's outcome: `Ok(compacted)` on success, or a [`TurnError`]
/// the serve loop must act on. The `compacted` bool is `true` when a
/// compaction ran this turn (reactively on overflow, or proactively per the
/// policy — or a successful operator `/compact` at turn end); it's recorded as
/// policy — or a successful agent-requested `/compact` at turn end); it's recorded as
/// `result_kind = "compacted"` in turn stats so the stats page can distinguish
/// those turns. Both `Ok(true)` and `Ok(false)` are ack'd; the error cases
/// each map to a distinct serve-loop action (see [`emit_turn_end`] and the
@ -452,8 +452,7 @@ pub async fn drive_turn(
archive_session(bus, session);
return Err(TurnError::PromptTooLong);
}
// Operator `/compact` (`POST /api/compact`) or an agent's own `compact`
// MCP tool call, deferred to the turn boundary: run it now that the turn
// An agent's own `compact` MCP tool call, deferred to the turn boundary: run it now that the turn
// is done, so it works mid-turn rather than only when the agent is idle.
// Only on a healthy turn — no point spawning a compaction after a
// rate-limited / auth-failed / crashed one. `is_ok()` first: `take_compact()`
@ -485,7 +484,7 @@ pub async fn drive_turn(
}
/// Emit the `/compact done` / `/compact failed: …` note for a finished
/// operator or agent `/compact` and return whether it succeeded. Shared by the
/// agent-requested `/compact` and return whether it succeeded. Shared by the
/// turn-end path in [`drive_turn`] and the idle path in [`run_pending_compact`].
fn report_compact<E: std::fmt::Display>(bus: &Bus, result: Result<(), E>) -> bool {
match result {
@ -620,7 +619,7 @@ pub fn emit_turn_end(bus: &Bus, outcome: &TurnOutcome) {
}
}
/// Service a pending operator `/compact` (`Bus::request_compact`) while the
/// Service a pending `/compact` (`Bus::request_compact`) while the
/// agent is idle — the serve loop calls this when a `recv` returns no message,
/// so a queued `/compact` runs even when no turn is driving. (The in-flight
/// case is handled at the end of [`drive_turn`].) Resume-only via

View file

@ -1,4 +1,4 @@
//! Operator action POST handlers (cancel, compact, reset, todos mark-done).
//! Operator action POST handlers (cancel, reset, todos mark-done).
use axum::{
Form,
@ -16,22 +16,6 @@ pub(super) async fn post_cancel_turn(State(state): State<AppState>) -> Response
(axum::http::StatusCode::OK, "ok").into_response()
}
/// Operator-initiated `/compact`. Deferred: sets the `compact_pending` flag
/// that `turn::drive_turn` consumes at the end of the current/next turn, so it
/// works while a turn is in flight (a mid-turn compaction would race the live
/// claude process) rather than only when the agent is idle. Returns 200
/// immediately; the compaction stream lands in the live panel when it runs.
pub(super) async fn post_compact(State(state): State<AppState>) -> Response {
// No wake prompt: the operator is watching the dashboard, not waiting on
// an inbox message — `request_compact`'s wake-prompt arg exists for the
// agent's own `compact` MCP tool (`todo_server.rs::compact`).
state.bus.request_compact(None);
state.bus.emit(crate::events::LiveEvent::Note {
text: "operator: /compact queued — runs at the end of the current turn".into(),
});
(axum::http::StatusCode::OK, "ok").into_response()
}
/// Request a session reset. The current session is archived (its backing
/// `<uuid>.jsonl` renamed out of claude's resolution glob) at the next turn
/// boundary, so the following turn's `--resume` misses and self-heals into a

View file

@ -76,7 +76,6 @@ fn routes() -> Router<AppState> {
.route("/events/stream", get(stream::events_stream))
.route("/events/history", get(stream::events_history))
.route("/api/cancel", post(actions::post_cancel_turn))
.route("/api/compact", post(actions::post_compact))
.route("/api/new-session", post(actions::post_new_session))
.route("/api/logout", post(auth::post_logout))
.route("/api/todos", get(stats::api_todos))
@ -532,7 +531,7 @@ mod tests {
/// either.
#[test]
fn neither_model_nor_effort_has_a_route() {
let (model, effort, compact) = crate::paths::with_harness_dir(|| {
let (model, effort, existing) = crate::paths::with_harness_dir(|| {
let state = AppState {
label: "test".to_owned(),
login: Arc::new(Mutex::new(LoginState::Online)),
@ -550,17 +549,17 @@ mod tests {
let effort = app.clone().oneshot(form("/api/effort", "effort=high"));
// A route the router does serve, so the 404s above are not
// the router refusing everything.
let compact = app.oneshot(form("/api/compact", ""));
let existing = app.oneshot(form("/api/new-session", ""));
(
model.await.unwrap(),
effort.await.unwrap(),
compact.await.unwrap(),
existing.await.unwrap(),
)
})
});
assert_eq!(model.status(), StatusCode::NOT_FOUND);
assert_eq!(effort.status(), StatusCode::NOT_FOUND);
assert_eq!(compact.status(), StatusCode::OK);
assert_eq!(existing.status(), StatusCode::OK);
}
/// Poll `path` until it holds a complete, newline-terminated pid.