aboutsummaryrefslogtreecommitdiff
path: root/crates/dinggerd/src/watcher.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/dinggerd/src/watcher.rs')
-rw-r--r--crates/dinggerd/src/watcher.rs64
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);