diff options
| author | Igor Soarez <igor@soarez.org> | 2026-10-30 18:59:43 +0200 |
|---|---|---|
| committer | Igor Soarez <igor@soarez.org> | 2026-10-30 18:59:43 +0200 |
| commit | 959f0d46cac1a09d0821da599d8dff54d1147892 (patch) | |
| tree | 499b38261ccfdc88f60ea8cbac7d279f202709c1 /crates/dinggerd/src/watcher.rs | |
| parent | b0cfd10b80443d3f67d263ff4db177bb53cc90ed (diff) | |
mark forgotten drafts in pi agents' input boxes, and keep them across a crash or restore
Diffstat (limited to 'crates/dinggerd/src/watcher.rs')
| -rw-r--r-- | crates/dinggerd/src/watcher.rs | 64 |
1 files changed, 55 insertions, 9 deletions
diff --git a/crates/dinggerd/src/watcher.rs b/crates/dinggerd/src/watcher.rs index 1c57609..b0acc1d 100644 --- a/crates/dinggerd/src/watcher.rs +++ b/crates/dinggerd/src/watcher.rs @@ -26,6 +26,7 @@ //! - `@agent_session` the agent's session reference, for restore //! - `@dingger_extension`, `@dingger_control` the version of pi's extension //! and its control socket, for reloading agents on an old one +//! - `@dingger_draft` the draft in the agent's input box, as one line use std::collections::HashMap; @@ -194,21 +195,36 @@ pub fn mark_unread(track: PaneTrack, attended: bool, now: u64) -> PaneTrack { PaneTrack { seen: false, held: attended, since: Some(now), ..track } } -/// The order agents are listed in: herdr's status priority; within a -/// status, the more important first; then done and blocked oldest first -/// (the queue: nothing waits forever), working and idle most recent first; -/// then by place. +/// The order agents are listed in: herdr's status priority; idle agents +/// with a forgotten draft before the other idle ones; within a status, the +/// more important first; then done and blocked oldest first (the queue: +/// nothing waits forever), working and idle most recent first; then by +/// place. pub fn list_order( status: Status, + forgotten_draft: bool, importance: Importance, since: Option<u64>, -) -> (std::cmp::Reverse<u8>, std::cmp::Reverse<Importance>, i128) { +) -> (std::cmp::Reverse<u8>, bool, std::cmp::Reverse<Importance>, i128) { let since = since.map(i128::from); let key = match status { Status::Done | Status::Blocked => since.unwrap_or(i128::MAX), _ => since.map(|since| -since).unwrap_or(i128::MAX), }; - (std::cmp::Reverse(status.priority()), std::cmp::Reverse(importance), key) + let draft_last = !(forgotten_draft && status == Status::Idle); + (std::cmp::Reverse(status.priority()), draft_last, std::cmp::Reverse(importance), key) +} + +/// A draft left in an agent's input box while the user looks elsewhere +/// (where they look, they may be typing it). +pub fn forgotten_draft(pane: &tmux::Pane) -> bool { + pane.agent.is_some() && !pane.attended && !draft_of(pane).is_empty() +} + +/// The pane's draft: as last reported, else as written (it survives a +/// daemon restart). +fn draft_of(pane: &tmux::Pane) -> String { + hooks::draft(&pane.pane_id).unwrap_or_else(|| pane.written_draft.clone()) } /// A pane's importance: as its agent last reported it, else as written @@ -493,6 +509,12 @@ impl Watcher { change("@dingger_control", &pane.written_control, String::new()); } } + // The draft goes with the agent too. + match (pane.agent, hooks::draft(&pane.pane_id)) { + (Some(_), Some(draft)) => change("@dingger_draft", &pane.written_draft, draft), + (None, _) => change("@dingger_draft", &pane.written_draft, String::new()), + _ => {} + } if let Err(err) = tmux::set_pane_options(&pane.pane_id, &changes) { warn!(target = %pane.display_target, %err, "failed to write pane options"); } @@ -539,7 +561,10 @@ impl Watcher { } // The queue: what needs the user, in list order. if matches!(track.status(), Status::Blocked | Status::Done) { - queue.push((list_order(track.status(), importance_of(pane), track.since), pane.pane_id.as_str())); + queue.push(( + list_order(track.status(), forgotten_draft(pane), importance_of(pane), track.since), + pane.pane_id.as_str(), + )); } } queue.sort(); @@ -801,7 +826,7 @@ mod tests { ("blocked", Status::Blocked, Some(99)), ("working new", Status::Working, Some(70)), ]; - rows.sort_by_key(|(_, status, since)| list_order(*status, Importance::Normal, *since)); + rows.sort_by_key(|(_, status, since)| list_order(*status, false, Importance::Normal, *since)); let names: Vec<_> = rows.iter().map(|(name, ..)| *name).collect(); assert_eq!( names, @@ -820,7 +845,7 @@ mod tests { ("working high", Status::Working, High, Some(1)), ("working new", Status::Working, Normal, Some(80)), ]; - rows.sort_by_key(|(_, status, importance, since)| list_order(*status, *importance, *since)); + rows.sort_by_key(|(_, status, importance, since)| list_order(*status, false, *importance, *since)); let names: Vec<_> = rows.iter().map(|(name, ..)| *name).collect(); assert_eq!( names, @@ -830,6 +855,27 @@ mod tests { } #[test] + fn a_forgotten_draft_lifts_an_idle_agent_only() { + use Importance::*; + let mut rows = [ + ("idle new", Status::Idle, false, High, Some(90)), + ("idle draft old low", Status::Idle, true, Low, Some(5)), + ("idle draft new", Status::Idle, true, Normal, Some(50)), + ("done old", Status::Done, false, Normal, Some(10)), + ("done draft new", Status::Done, true, Normal, Some(60)), + ("working draft", Status::Working, true, Normal, Some(1)), + ("working new", Status::Working, false, Normal, Some(80)), + ]; + rows.sort_by_key(|(_, status, draft, importance, since)| list_order(*status, *draft, *importance, *since)); + let names: Vec<_> = rows.iter().map(|(name, ..)| *name).collect(); + assert_eq!( + names, + ["done old", "done draft new", "working new", "working draft", "idle draft new", "idle draft old low", "idle new"], + "status first; among idle agents, a draft before importance" + ); + } + + #[test] fn agent_exit_is_unknown() { let done = step(step(PaneTrack::NEW, Working, false), Idle, false); assert_eq!(step(done, Unknown, false).status(), Status::Unknown); |
