diff --git a/frontend/packages/agent/src/app.js b/frontend/packages/agent/src/app.js index ba5cfb01..ed41ed00 100644 --- a/frontend/packages/agent/src/app.js +++ b/frontend/packages/agent/src/app.js @@ -839,23 +839,6 @@ window.marked = marked; renderTodos([]); } } - // Loose-ends: fetched silently (background only) for reconcileAskBinds. - // Not displayed as a pill; provides the question/reminder/approval data - // the inline ask-form wiring needs. - async function refreshLooseEnds() { - try { - const resp = await fetch('api/loose-ends'); - if (!resp.ok) { - renderLooseEnds([]); - return; - } - const data = await resp.json(); - renderLooseEnds(data.loose_ends || []); - } catch (err) { - console.warn('loose-ends fetch failed', err); - renderLooseEnds([]); - } - } /** Latest snapshot kept in module state so the pill click handler * has fresh data to render into the panel without re-fetching. */ let lastTodos = []; @@ -933,15 +916,6 @@ window.marked = marked; return wrap; } - /** Loose-ends render: background-only (no pill). Keeps lastLooseEnds - * fresh for reconcileAskBinds (inline ask-form wiring). */ - function renderLooseEnds(threads) { - lastLooseEnds = threads; - // Wire inline answer forms into any `ask → operator` rows - // waiting on a broker-assigned question id. - reconcileAskBinds(); - } - /** Build the todos side-panel list. Each entry is a LooseEnd::Todo * (subsystem, summary, source, age_seconds). */ function buildTodosList(todos) { @@ -1045,7 +1019,6 @@ window.marked = marked; }); if (resp.ok) { status.textContent = 'answered ✓'; - refreshLooseEnds(); } else { status.textContent = 'failed: ' + (await resp.text()); } @@ -1342,10 +1315,7 @@ window.marked = marked; renderModelChip(s.model); renderEffortChip(s.effort); renderTokenUsage({ ctx: s.ctx_usage, cost: s.cost_usage }); - // Open-threads: loose-ends (background, for reconcileAskBinds) and - // todos (displayed pill). Cold-load fetches both; turn_end refreshes - // them via the renderers below. - refreshLooseEnds(); + // Todos pill: cold-load populate; turn_end refreshes via renderTodos. refreshTodos(); // Skip the re-render if nothing structurally changed. The most // common case is `online` polling itself — without this guard, the @@ -1477,11 +1447,7 @@ window.marked = marked; slot._askQuestion = c._body; d.appendChild(slot); pendingAskBinds.push(slot); - // Mid-turn refresh — the standard `turn_end` refresh - // won't fire until the agent's turn finishes; we want - // the form to show up as soon as the ask lands. - if (!api.fromHistory) refreshLooseEnds(); - else reconcileAskBinds(); + if (api.fromHistory) reconcileAskBinds(); } } return d; @@ -1510,15 +1476,6 @@ window.marked = marked; const sourceName = c.tool_use_id ? toolNameById.get(c.tool_use_id) : null; const isMessageBearing = sourceName === 'mcp__hyperhive__recv'; // When an ask's tool_result lands the broker has just - // persisted the question with its assigned id. Refresh - // loose-ends so reconcileAskBinds finds the new entry and - // mounts the inline answer form under the rendered ask row. - // Skipped during history replay (the question's likely - // long-resolved; turn_end refresh on cold-load covers - // reconciliation). - if (sourceName === 'mcp__hyperhive__ask' && !api.fromHistory) { - refreshLooseEnds(); - } const trimmed = txt.replace(/\s+/g, ' ').trim(); const summaryBody = (() => { if (!trimmed) return '(empty)'; @@ -1738,8 +1695,6 @@ window.marked = marked; openTurnsFromHistory = Math.max(0, openTurnsFromHistory - 1); } else { setBannerActive(false); setState('idle'); - // Likely answered/asked/scheduled something — refresh both. - refreshLooseEnds(); refreshTodos(); } const cls = ev.ok ? 'turn-end-ok' : 'turn-end-fail'; diff --git a/hive-agent/src/web_ui/mod.rs b/hive-agent/src/web_ui/mod.rs index 0fad70ec..756a405f 100644 --- a/hive-agent/src/web_ui/mod.rs +++ b/hive-agent/src/web_ui/mod.rs @@ -115,7 +115,6 @@ pub async fn serve( .route("/api/effort", post(actions::post_set_effort)) .route("/api/new-session", post(actions::post_new_session)) .route("/api/logout", post(auth::post_logout)) - .route("/api/loose-ends", get(stats::api_loose_ends)) .route("/api/todos", get(stats::api_todos)) .route("/api/stats", get(stats::api_stats)) .route("/screen/ws", get(screen::screen_ws)) diff --git a/hive-agent/src/web_ui/stats.rs b/hive-agent/src/web_ui/stats.rs index 36854b56..9a239e66 100644 --- a/hive-agent/src/web_ui/stats.rs +++ b/hive-agent/src/web_ui/stats.rs @@ -1,11 +1,10 @@ //! Stats + loose-ends + todos read endpoints. use axum::extract::State; -use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use serde::Deserialize; -use super::{AppState, error_response}; +use super::AppState; #[derive(Deserialize)] pub(super) struct StatsQuery { @@ -47,35 +46,6 @@ async fn fetch_reminder_stats( } } -/// Proxy this agent's loose-ends list via the per-agent socket. The -/// web UI surfaces the result as a collapsible section in the page -/// so the operator can see at a glance what's pending against the -/// agent (questions asked by it, peer questions targeting it, -/// reminders it scheduled, approvals for the manager). Same data -/// the `mcp__hyperhive__get_loose_ends` tool sees from inside the -/// container. -pub(super) async fn api_loose_ends(State(state): State) -> Response { - match super::broker_request( - &state.socket, - &hive_core_agent_sock::Request::GetLooseEnds { agent: None }, - ) - .await - { - Ok(hive_core_agent_sock::Response::LooseEnds { loose_ends }) => { - axum::Json(serde_json::json!({ "loose_ends": loose_ends })).into_response() - } - Ok(hive_core_agent_sock::Response::Err { message }) => error_response( - StatusCode::INTERNAL_SERVER_ERROR, - &format!("get_loose_ends: {message}"), - ), - Ok(other) => error_response( - StatusCode::INTERNAL_SERVER_ERROR, - &format!("get_loose_ends: unexpected response: {other:?}"), - ), - Err(e) => super::broker_error_response(&e, "get_loose_ends"), - } -} - /// `GET /api/todos` — snapshot of this agent's local todos (loose-ends v2). /// /// Connects to the in-agent harness socket (`HIVE_AGENT_SOCKET`) and calls