wsb-daemon 0.248.6265

wsb's daemon: the workbench, the GitHub mirror's schedule and peer continuity, for every workspace on the machine
use wsb::prelude::*;
use axum::{
    extract::State,
    http::StatusCode,
    response::sse::{Event, KeepAlive, Sse},
    Json,
};
use std::convert::Infallible;

use super::sessions::ErrorResponse;
use crate::web::machine_web::MachineWeb;

/// POST /api/v1/sync — trigger incremental sync, SSE progress
pub async fn trigger_sync(
    State(state): State<MachineWeb>,
) -> Sse<impl futures::Stream<Item = Result<Event, Infallible>>> {
    let stream = async_stream::stream! {
        yield Ok(Event::default()
            .event("progress")
            .data(serde_json::json!({
                "phase": "syncing",
                "sessions_done": 0,
                "sessions_total": 0,
                "messages_indexed": 0
            }).to_string()));

        match state.sync.sync_incremental().await {
            Ok(stats) => {
                yield Ok(Event::default()
                    .event("done")
                    .data(serde_json::to_string(&stats).unwrap_or_default()));
            }
            Err(e) => {
                yield Ok(Event::default()
                    .event("error")
                    .data(serde_json::json!({
                        "message": e.to_string()
                    }).to_string()));
            }
        }
    };

    Sse::new(stream).keep_alive(KeepAlive::default())
}

/// POST /api/v1/resync — trigger full resync
pub async fn trigger_resync(
    State(state): State<MachineWeb>,
) -> Sse<impl futures::Stream<Item = Result<Event, Infallible>>> {
    let stream = async_stream::stream! {
        yield Ok(Event::default()
            .event("progress")
            .data(serde_json::json!({
                "phase": "syncing",
                "sessions_done": 0,
                "sessions_total": 0,
                "messages_indexed": 0
            }).to_string()));

        match state.sync.sync_full().await {
            Ok(stats) => {
                yield Ok(Event::default()
                    .event("done")
                    .data(serde_json::to_string(&stats).unwrap_or_default()));
            }
            Err(e) => {
                yield Ok(Event::default()
                    .event("error")
                    .data(serde_json::json!({
                        "message": e.to_string()
                    }).to_string()));
            }
        }
    };

    Sse::new(stream).keep_alive(KeepAlive::default())
}

/// GET /api/v1/sync/status
pub async fn sync_status(
    State(state): State<MachineWeb>,
) -> Result<Json<serde_json::Value>, (StatusCode, Json<ErrorResponse>)> {
    // last_stats() is a non-blocking read — never queues behind a running sync.
    let stats = state.sync.last_stats();
    Ok(Json(serde_json::json!({
        "last_sync": "",
        "stats": stats
    })))
}