fix(#2632): remove /api/loose-ends endpoint; drop refreshLooseEnds (address argus/mara review)
Both old endpoints removed. refreshLooseEnds() call sites cleaned up; lastLooseEnds stays as empty [] for reconcileAskBinds (no-op now that the loose-ends source is gone).
This commit is contained in:
parent
3780f674f4
commit
88f17320b6
3 changed files with 3 additions and 79 deletions
|
|
@ -839,23 +839,6 @@ window.marked = marked;
|
||||||
renderTodos([]);
|
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
|
/** Latest snapshot kept in module state so the pill click handler
|
||||||
* has fresh data to render into the panel without re-fetching. */
|
* has fresh data to render into the panel without re-fetching. */
|
||||||
let lastTodos = [];
|
let lastTodos = [];
|
||||||
|
|
@ -933,15 +916,6 @@ window.marked = marked;
|
||||||
return wrap;
|
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
|
/** Build the todos side-panel list. Each entry is a LooseEnd::Todo
|
||||||
* (subsystem, summary, source, age_seconds). */
|
* (subsystem, summary, source, age_seconds). */
|
||||||
function buildTodosList(todos) {
|
function buildTodosList(todos) {
|
||||||
|
|
@ -1045,7 +1019,6 @@ window.marked = marked;
|
||||||
});
|
});
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
status.textContent = 'answered ✓';
|
status.textContent = 'answered ✓';
|
||||||
refreshLooseEnds();
|
|
||||||
} else {
|
} else {
|
||||||
status.textContent = 'failed: ' + (await resp.text());
|
status.textContent = 'failed: ' + (await resp.text());
|
||||||
}
|
}
|
||||||
|
|
@ -1342,10 +1315,7 @@ window.marked = marked;
|
||||||
renderModelChip(s.model);
|
renderModelChip(s.model);
|
||||||
renderEffortChip(s.effort);
|
renderEffortChip(s.effort);
|
||||||
renderTokenUsage({ ctx: s.ctx_usage, cost: s.cost_usage });
|
renderTokenUsage({ ctx: s.ctx_usage, cost: s.cost_usage });
|
||||||
// Open-threads: loose-ends (background, for reconcileAskBinds) and
|
// Todos pill: cold-load populate; turn_end refreshes via renderTodos.
|
||||||
// todos (displayed pill). Cold-load fetches both; turn_end refreshes
|
|
||||||
// them via the renderers below.
|
|
||||||
refreshLooseEnds();
|
|
||||||
refreshTodos();
|
refreshTodos();
|
||||||
// Skip the re-render if nothing structurally changed. The most
|
// Skip the re-render if nothing structurally changed. The most
|
||||||
// common case is `online` polling itself — without this guard, the
|
// common case is `online` polling itself — without this guard, the
|
||||||
|
|
@ -1477,11 +1447,7 @@ window.marked = marked;
|
||||||
slot._askQuestion = c._body;
|
slot._askQuestion = c._body;
|
||||||
d.appendChild(slot);
|
d.appendChild(slot);
|
||||||
pendingAskBinds.push(slot);
|
pendingAskBinds.push(slot);
|
||||||
// Mid-turn refresh — the standard `turn_end` refresh
|
if (api.fromHistory) reconcileAskBinds();
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return d;
|
return d;
|
||||||
|
|
@ -1510,15 +1476,6 @@ window.marked = marked;
|
||||||
const sourceName = c.tool_use_id ? toolNameById.get(c.tool_use_id) : null;
|
const sourceName = c.tool_use_id ? toolNameById.get(c.tool_use_id) : null;
|
||||||
const isMessageBearing = sourceName === 'mcp__hyperhive__recv';
|
const isMessageBearing = sourceName === 'mcp__hyperhive__recv';
|
||||||
// When an ask's tool_result lands the broker has just
|
// 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 trimmed = txt.replace(/\s+/g, ' ').trim();
|
||||||
const summaryBody = (() => {
|
const summaryBody = (() => {
|
||||||
if (!trimmed) return '(empty)';
|
if (!trimmed) return '(empty)';
|
||||||
|
|
@ -1738,8 +1695,6 @@ window.marked = marked;
|
||||||
openTurnsFromHistory = Math.max(0, openTurnsFromHistory - 1);
|
openTurnsFromHistory = Math.max(0, openTurnsFromHistory - 1);
|
||||||
} else {
|
} else {
|
||||||
setBannerActive(false); setState('idle');
|
setBannerActive(false); setState('idle');
|
||||||
// Likely answered/asked/scheduled something — refresh both.
|
|
||||||
refreshLooseEnds();
|
|
||||||
refreshTodos();
|
refreshTodos();
|
||||||
}
|
}
|
||||||
const cls = ev.ok ? 'turn-end-ok' : 'turn-end-fail';
|
const cls = ev.ok ? 'turn-end-ok' : 'turn-end-fail';
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,6 @@ pub async fn serve(
|
||||||
.route("/api/effort", post(actions::post_set_effort))
|
.route("/api/effort", post(actions::post_set_effort))
|
||||||
.route("/api/new-session", post(actions::post_new_session))
|
.route("/api/new-session", post(actions::post_new_session))
|
||||||
.route("/api/logout", post(auth::post_logout))
|
.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/todos", get(stats::api_todos))
|
||||||
.route("/api/stats", get(stats::api_stats))
|
.route("/api/stats", get(stats::api_stats))
|
||||||
.route("/screen/ws", get(screen::screen_ws))
|
.route("/screen/ws", get(screen::screen_ws))
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
//! Stats + loose-ends + todos read endpoints.
|
//! Stats + loose-ends + todos read endpoints.
|
||||||
|
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::http::StatusCode;
|
|
||||||
use axum::response::{IntoResponse, Response};
|
use axum::response::{IntoResponse, Response};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use super::{AppState, error_response};
|
use super::AppState;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub(super) struct StatsQuery {
|
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<AppState>) -> 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).
|
/// `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
|
/// Connects to the in-agent harness socket (`HIVE_AGENT_SOCKET`) and calls
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue