diff options
| author | Igor Soarez <igor@soarez.org> | 2026-10-27 14:12:28 +0200 |
|---|---|---|
| committer | Igor Soarez <igor@soarez.org> | 2026-10-27 14:12:28 +0200 |
| commit | 50c433e82c368b9861a89deecb74e5f29fa50353 (patch) | |
| tree | fe6d87b79387f6c53ef9bf615b7b530daf9a8d3a /crates/dinggerd/src/main.rs | |
| parent | 9f3a567d4bb1b7b6e42a744936f993bcd437882d (diff) | |
mark as unread: make an idle agent done again, to come back to it
like "mark as unread" in mail: an agent you have seen (idle) becomes done
again, last among done agents of its importance, with no sound or
notification. it stays done until you come back to it: leave its tmux
window and return, or jump to it. marked while you look at it, it holds
until you look away (`held`); a new turn clears it. only idle agents can
be marked.
- daemon: `pane.mark_unread` on the hook socket; the watcher applies it
after the tick's observations (mark_unread, advance's hold rule).
- `dingger unread [--pane]`: refuses done, working, blocked, and panes
without an agent; returns once the agent shows as done.
- pi: /unread (refused while the agent works or is blocked).
- panel: right-click an idle agent in the full list, "Mark as unread".
- search: ⌘U or right-click marks the selected idle agent; the footer
offers ⌘U when it applies; search stays open.
tests: unit (hold, jump, new turn, idle only), e2e step 22 (queue
position, hold, look away and back, jump, refusals, no sound, menu, the
extension), pi-live in real pi 1.4 (a mark, not a turn: an unknown
command would also end done), swift selftest (menu only for idle, footer).
Diffstat (limited to 'crates/dinggerd/src/main.rs')
| -rw-r--r-- | crates/dinggerd/src/main.rs | 51 |
1 files changed, 51 insertions, 0 deletions
diff --git a/crates/dinggerd/src/main.rs b/crates/dinggerd/src/main.rs index c50fb80..2c36198 100644 --- a/crates/dinggerd/src/main.rs +++ b/crates/dinggerd/src/main.rs @@ -82,6 +82,14 @@ enum Command { #[arg(long)] pane: Option<String>, }, + /// Mark an idle agent unread: it shows as done again, last among done + /// agents of its importance, until you come back to it. In pi, /unread + /// does the same. + Unread { + /// The agent's pane (default: the pane this runs in). + #[arg(long)] + pane: Option<String>, + }, /// Restore tmux sessions saved before tmux last stopped (a crash, /// shutdown, or kill-server): windows, panes, layouts, directories. /// Only adds what is missing; a bare session of the same name (what a @@ -194,6 +202,12 @@ fn main() { std::process::exit(1); } } + Command::Unread { pane } => { + if let Err(err) = mark_unread(pane) { + eprintln!("dingger: {err}"); + std::process::exit(1); + } + } Command::Poke => { // Best effort: a stopped watcher just means nothing to wake. let _ = hooks::poke(); @@ -341,6 +355,43 @@ fn set_importance(level: Option<&str>, pane: Option<String>) -> Result<(), Strin Ok(()) } +/// `dingger unread`: ask the daemon to mark the pane's idle agent unread, +/// and wait until it shows as done. +fn mark_unread(pane: Option<String>) -> Result<(), String> { + let target = pane + .or_else(|| std::env::var("TMUX_PANE").ok()) + .ok_or("not in a tmux pane; pass --pane")?; + let read = || -> Result<(String, String, String), String> { + let info = tmux::run(&["display-message", "-p", "-t", &target, "#{pane_id}\t#{@agent}\t#{@agent_state}"]) + .map_err(|_| format!("no pane {target}"))?; + match info.trim_end_matches('\n').split('\t').collect::<Vec<_>>()[..] { + [id, agent, state] => Ok((id.to_string(), agent.to_string(), state.to_string())), + _ => Err(format!("no pane {target}")), + } + }; + let (pane_id, agent, state) = read()?; + match (agent.as_str(), state.as_str()) { + ("", _) => return Err(format!("no agent in pane {target}")), + (_, "idle") => {} + (_, "done") => return Err("it is unread already (done)".into()), + (_, state) => return Err(format!("only an idle agent can be marked unread; this one is {state}")), + } + let message = serde_json::json!({ "method": "pane.mark_unread", "params": { "pane_id": pane_id } }); + hooks::send(&message.to_string())?; + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(3); + while std::time::Instant::now() < deadline { + match read()?.2.as_str() { + "done" => { + println!("marked unread"); + return Ok(()); + } + "idle" => std::thread::sleep(std::time::Duration::from_millis(50)), + other => return Err(format!("not marked: it turned {other}")), + } + } + Err("not marked: the daemon did not apply it".into()) +} + /// Every agent pane in herdr's priority order (blocked, done, working, /// idle), oldest-waiting first within a status. fn status() -> Result<(), String> { |
