diff --git a/hive-agent/src/web_ui/actions.rs b/hive-agent/src/web_ui/actions.rs index 56c7ea8d..f05905e0 100644 --- a/hive-agent/src/web_ui/actions.rs +++ b/hive-agent/src/web_ui/actions.rs @@ -7,7 +7,7 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use super::{AppState, SigintOutcome, error_response}; @@ -202,5 +202,10 @@ pub(super) async fn post_mark_todos_done(Form(form): Form) -> acked += count; } } - axum::Json(serde_json::json!({ "acked": acked })).into_response() + axum::Json(MarkTodosDoneBody { acked }).into_response() +} + +#[derive(Serialize)] +struct MarkTodosDoneBody { + acked: u64, } diff --git a/hive-agent/src/web_ui/stats.rs b/hive-agent/src/web_ui/stats.rs index f2f83614..9dd6796d 100644 --- a/hive-agent/src/web_ui/stats.rs +++ b/hive-agent/src/web_ui/stats.rs @@ -2,7 +2,7 @@ use axum::extract::State; use axum::response::{IntoResponse, Response}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use super::AppState; @@ -40,6 +40,11 @@ async fn fetch_reminder_stats(window_secs: u64) -> Option, +} + /// `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 @@ -54,5 +59,5 @@ pub(super) async fn api_todos() -> Response { Some(hive_agent_sock::Response::LooseEnds { loose_ends }) => loose_ends, _ => Vec::new(), }; - axum::Json(serde_json::json!({ "todos": todos })).into_response() + axum::Json(TodosBody { todos }).into_response() } diff --git a/hive-agent/src/web_ui/stream.rs b/hive-agent/src/web_ui/stream.rs index 3c881cd4..89c4ba31 100644 --- a/hive-agent/src/web_ui/stream.rs +++ b/hive-agent/src/web_ui/stream.rs @@ -5,11 +5,23 @@ use std::convert::Infallible; use axum::Json; use axum::extract::{Query, State}; use axum::response::sse::{Event, KeepAlive, Sse}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use tokio_stream::{Stream, StreamExt, wrappers::BroadcastStream}; use super::AppState; +/// Response body for `GET /api/events/history`. `seq` is omitted from the +/// wire entirely on a paginated (non-initial) load — matches the old +/// `json!` shape, which only ever set the `"seq"` key when `Some`. +#[derive(Serialize)] +pub(super) struct EventsHistoryBody { + events: Vec, + min_id: Option, + has_more: bool, + #[serde(skip_serializing_if = "Option::is_none")] + seq: Option, +} + /// Query params for the paginated history endpoint. #[derive(Debug, Deserialize)] pub(super) struct HistoryParams { @@ -23,7 +35,7 @@ pub(super) struct HistoryParams { pub(super) async fn events_history( State(state): State, Query(params): Query, -) -> Json { +) -> Json { use crate::events::HISTORY_CAPACITY; let limit = params.limit.unwrap_or(100).min(HISTORY_CAPACITY); let before = params.before; @@ -51,15 +63,12 @@ pub(super) async fn events_history( se }) .collect(); - let mut resp = serde_json::json!({ - "events": events, - "min_id": min_id, - "has_more": has_more, - }); - if let Some(s) = seq { - resp["seq"] = serde_json::json!(s); - } - Json(resp) + Json(EventsHistoryBody { + events, + min_id, + has_more, + seq, + }) } pub(super) async fn events_stream(