Compare commits

...
Author SHA1 Message Date
damocles
6c886d3fa6 hive-agent: inline todo dispatch arms instead of a sub-match + unreachable!
per mara's review on #2679: replace the separate dispatch_todo sub-match
(with its trailing unreachable! arm) with four small handler functions
called directly from dispatch's existing match. same behavior, no
unreachable! left in the todo path.
2026-07-25 18:42:10 +02:00
damocles
2dcbb78b40 hive-agent: add debug logging around todo upsert/clear/mark-done
diagnostic instrumentation for #2678 (phantom 'you have todos' wakes
after clearing bash-task todos). logs subsystem/key/id/changed on
UpsertTodo, subsystem/key/all/count on ClearTodo, id/count on
MarkTodoDone, and a marker when the serve loop actually consumes a
todo_wake notification. no behavior change - RUST_LOG=debug only.
2026-07-25 17:40:11 +02:00
2 changed files with 80 additions and 35 deletions

View file

@ -585,7 +585,10 @@ async fn serve_loop<S: Surface>(
}
} {
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

View file

@ -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