diff --git a/hive-agent/src/main.rs b/hive-agent/src/main.rs index 1206a5f0..60be8018 100644 --- a/hive-agent/src/main.rs +++ b/hive-agent/src/main.rs @@ -585,7 +585,10 @@ async fn serve_loop( } } { RecvOutcome::Message(first) => first, - RecvOutcome::LocalTodo => synthetic_todo_message(), + RecvOutcome::LocalTodo => { + tracing::debug!("todo wake consumed, sending synthetic todo message"); + synthetic_todo_message() + } RecvOutcome::Empty => { // Idle: no message this poll. Service a queued operator // `/compact` here so it runs even when no turn is driving diff --git a/hive-agent/src/todo_server.rs b/hive-agent/src/todo_server.rs index 3bc280a1..1336f1fb 100644 --- a/hive-agent/src/todo_server.rs +++ b/hive-agent/src/todo_server.rs @@ -170,44 +170,21 @@ fn dispatch( key, summary, source, - } => match store.upsert(&subsystem, key.as_deref(), &summary, source.as_deref()) { - Ok((_, changed)) => { - if changed { - wake.notify_one(); - } - Response::Ok - } - Err(e) => err(&e), - }, + } => upsert_todo( + store, + wake, + &subsystem, + key.as_deref(), + &summary, + source.as_deref(), + ), Request::ClearTodo { subsystem, key, all, - } => { - let result = if all { - store.clear_subsystem(&subsystem) - } else { - store.clear(&subsystem, key.as_deref()) - }; - match result { - Ok(count) => Response::Acked { - count: u64::try_from(count).unwrap_or(0), - }, - Err(e) => err(&e), - } - } - Request::ListTodos { subsystem } => match store.list(subsystem.as_deref()) { - Ok(todos) => Response::LooseEnds { - loose_ends: todos.into_iter().map(to_loose_end).collect(), - }, - Err(e) => err(&e), - }, - Request::MarkTodoDone { id } => match store.mark_done(id) { - Ok(count) => Response::Acked { - count: u64::try_from(count).unwrap_or(0), - }, - Err(e) => err(&e), - }, + } => clear_todo(store, &subsystem, key.as_deref(), all), + Request::ListTodos { subsystem } => list_todos(store, subsystem.as_deref()), + Request::MarkTodoDone { id } => mark_todo_done(store, id), Request::StoreReminder { message, timing, @@ -260,6 +237,71 @@ fn dispatch( } } +/// `UpsertTodo` handler: writes/refreshes a todo row, logs the outcome, and +/// fires `wake` on a new-or-changed upsert so the serve loop runs a turn. +fn upsert_todo( + store: &Todos, + wake: &Notify, + subsystem: &str, + key: Option<&str>, + summary: &str, + source: Option<&str>, +) -> Response { + match store.upsert(subsystem, key, summary, source) { + Ok((id, changed)) => { + tracing::debug!(%subsystem, ?key, id, changed, "todo upsert"); + if changed { + wake.notify_one(); + } + Response::Ok + } + Err(e) => err(&e), + } +} + +/// `ClearTodo` handler: drops one keyed todo, or every todo in `subsystem` +/// when `all` is set. +fn clear_todo(store: &Todos, subsystem: &str, key: Option<&str>, all: bool) -> Response { + let result = if all { + store.clear_subsystem(subsystem) + } else { + store.clear(subsystem, key) + }; + match result { + Ok(count) => { + tracing::debug!(%subsystem, ?key, all, count, "todo clear"); + Response::Acked { + count: u64::try_from(count).unwrap_or(0), + } + } + Err(e) => err(&e), + } +} + +/// `ListTodos` handler: read-only, so no debug logging — not relevant to +/// diagnosing wake behaviour. +fn list_todos(store: &Todos, subsystem: Option<&str>) -> Response { + match store.list(subsystem) { + Ok(todos) => Response::LooseEnds { + loose_ends: todos.into_iter().map(to_loose_end).collect(), + }, + Err(e) => err(&e), + } +} + +/// `MarkTodoDone` handler: marks a single todo done by id. +fn mark_todo_done(store: &Todos, id: i64) -> Response { + match store.mark_done(id) { + Ok(count) => { + tracing::debug!(id, count, "todo mark-done"); + Response::Acked { + count: u64::try_from(count).unwrap_or(0), + } + } + Err(e) => err(&e), + } +} + /// `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