hive-agent: key the boot-announce todo so it dedups across boots
This commit is contained in:
parent
cd61ea4a37
commit
f33a1ed660
1 changed files with 49 additions and 6 deletions
|
|
@ -18,11 +18,16 @@
|
||||||
//! (the old first-spawn message didn't reliably fire anyway, so unifying
|
//! (the old first-spawn message didn't reliably fire anyway, so unifying
|
||||||
//! the two loses nothing).
|
//! the two loses nothing).
|
||||||
//!
|
//!
|
||||||
//! Keyless on purpose: each boot is a genuinely new occurrence, not a
|
//! Keyed on a constant `"boot"` key, not keyless: the store (`Todos`,
|
||||||
//! reconciled condition like `disk_watch`'s, so there's nothing to dedup
|
//! backed by `paths::state_db()`, a sqlite file) outlives the process, so
|
||||||
//! *against* within one store — `Todos::upsert`'s keyless path always
|
//! a keyless insert would never dedup across boots — N harness starts
|
||||||
//! inserts a fresh row and always reports `changed`, which is exactly
|
//! while the agent hasn't acked the previous row would leave N identical
|
||||||
//! "wake me, this happened" with no extra bookkeeping.
|
//! rows, exactly the restart-storm this module replaces. With a constant
|
||||||
|
//! key and the summary text constant too, `Todos::upsert`'s content-
|
||||||
|
//! unchanged path makes a boot while the previous notice is still pending
|
||||||
|
//! a no-op (no second wake), and `reopen_if_acked = true` makes a boot
|
||||||
|
//! after the agent acked it re-open the same row and wake again. One row,
|
||||||
|
//! ever.
|
||||||
|
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
|
|
||||||
|
|
@ -31,6 +36,10 @@ use crate::todos::Todos;
|
||||||
/// Producing subsystem marker for the boot-announce todo row.
|
/// Producing subsystem marker for the boot-announce todo row.
|
||||||
const SUBSYSTEM: &str = "harness";
|
const SUBSYSTEM: &str = "harness";
|
||||||
|
|
||||||
|
/// Dedup key for the boot-announce row — constant, so every boot targets
|
||||||
|
/// the same row instead of inserting a fresh one. See module doc.
|
||||||
|
const KEY: &str = "boot";
|
||||||
|
|
||||||
/// Push the "you were just (re)started" todo and wake the turn loop.
|
/// Push the "you were just (re)started" todo and wake the turn loop.
|
||||||
/// Best-effort: a store error is logged, never fatal to boot.
|
/// Best-effort: a store error is logged, never fatal to boot.
|
||||||
pub fn announce(label: &str, todos: &Todos, wake: &Notify) {
|
pub fn announce(label: &str, todos: &Todos, wake: &Notify) {
|
||||||
|
|
@ -40,8 +49,42 @@ pub fn announce(label: &str, todos: &Todos, wake: &Notify) {
|
||||||
off — claude's `--continue` session is intact, so prior context is \
|
off — claude's `--continue` session is intact, so prior context is \
|
||||||
still in your window."
|
still in your window."
|
||||||
);
|
);
|
||||||
match todos.upsert(SUBSYSTEM, None, &summary, None, false) {
|
match todos.upsert(SUBSYSTEM, Some(KEY), &summary, None, true) {
|
||||||
Ok(_) => wake.notify_one(),
|
Ok(_) => wake.notify_one(),
|
||||||
Err(e) => tracing::warn!(error = ?e, "boot_announce: todo upsert failed"),
|
Err(e) => tracing::warn!(error = ?e, "boot_announce: todo upsert failed"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_boots_without_an_ack_leave_one_row() {
|
||||||
|
let dir = tempfile::tempdir().expect("tempdir");
|
||||||
|
let todos = Todos::open(&dir.path().join("state.sqlite")).expect("open");
|
||||||
|
let wake = Notify::new();
|
||||||
|
|
||||||
|
announce("a", &todos, &wake);
|
||||||
|
let rows = todos.list(Some(SUBSYSTEM)).expect("list");
|
||||||
|
assert_eq!(rows.len(), 1);
|
||||||
|
let id = rows[0].id;
|
||||||
|
|
||||||
|
// A second boot before the first notice is acked: same row, no
|
||||||
|
// duplicate.
|
||||||
|
announce("a", &todos, &wake);
|
||||||
|
let rows = todos.list(Some(SUBSYSTEM)).expect("list");
|
||||||
|
assert_eq!(rows.len(), 1);
|
||||||
|
assert_eq!(rows[0].id, id);
|
||||||
|
|
||||||
|
// Ack it, then boot again: the row re-opens (still the same row,
|
||||||
|
// un-acked again), not a second insert.
|
||||||
|
todos.mark_done(id).expect("mark_done");
|
||||||
|
assert!(todos.list(Some(SUBSYSTEM)).expect("list").is_empty());
|
||||||
|
|
||||||
|
announce("a", &todos, &wake);
|
||||||
|
let rows = todos.list(Some(SUBSYSTEM)).expect("list");
|
||||||
|
assert_eq!(rows.len(), 1);
|
||||||
|
assert_eq!(rows[0].id, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue