diff options
| author | Igor Soarez <igor@soarez.org> | 2026-10-19 19:30:03 +0300 |
|---|---|---|
| committer | Igor Soarez <igor@soarez.org> | 2026-10-19 19:30:03 +0300 |
| commit | fed066bc3908d18ede5490130975203a56c48fc2 (patch) | |
| tree | 17ecccec1a1b5669e1868a0e169f263bdee53c73 /crates/dinggerd/src/watcher.rs | |
| parent | 7081a103443bafa28d902529d502a89c2dccb79a (diff) | |
menu bar: herdr-style colored state glyphs; tmux segment before the clock
the design panel's monochrome template rendering made every row look
alike; restore herdr's agent-panel visual language - green ready, yellow
working, red blocked, filled when unseen in the queue. the tmux segment
moves left of the clock so it is visible at a glance; the status item
logs what it observes for diagnosis.
Diffstat (limited to 'crates/dinggerd/src/watcher.rs')
| -rw-r--r-- | crates/dinggerd/src/watcher.rs | 46 |
1 files changed, 32 insertions, 14 deletions
diff --git a/crates/dinggerd/src/watcher.rs b/crates/dinggerd/src/watcher.rs index 9fd999d..d352ab2 100644 --- a/crates/dinggerd/src/watcher.rs +++ b/crates/dinggerd/src/watcher.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use detect_core::{detect_agent_with_osc, AgentState}; use tracing::{debug, info, warn}; +use crate::hooks; use crate::notify::{play_sound, send_notification, NotificationKind}; use crate::tmux; @@ -93,6 +94,12 @@ impl Watcher { // Forget panes that went away. self.panes .retain(|pane_id, _| panes.iter().any(|pane| pane.pane_id == *pane_id)); + hooks::forget( + &panes + .iter() + .map(|pane| pane.pane_id.clone()) + .collect::<Vec<_>>(), + ); for pane in &panes { let prev_state = self @@ -102,27 +109,34 @@ impl Watcher { .unwrap_or(AgentState::Unknown); // The detection result is an observation of live agent state. - // Viewer screens (transcripts, pickers) return + // Agent-reported state (hook receiver) is authoritative while the + // pane runs the agent; screen detection is the fallback for panes + // that never report. Viewer screens (transcripts, pickers) return // `skip_state_update: true` — they say nothing about state and // leave the previous one standing (herdr's publishing contract). let observed = if pane.dead { Some(AgentState::Idle) } else if let Some(agent) = pane.agent { - let screen = match tmux::capture_pane(&pane.pane_id) { - Ok(screen) => screen, - Err(err) => { - warn!(target = %pane.target, %err, "capture failed"); - continue; - } - }; - // tmux pane_title carries the last OSC title the application - // set; tmux does not re-expose OSC progress sequences. - let detection = - detect_agent_with_osc(Some(agent), &screen, &pane.pane_title, ""); - (!detection.skip_state_update).then_some(detection.state) + if let Some(state) = hooks::hook_state(&pane.pane_id) { + Some(state) + } else { + let screen = match tmux::capture_pane(&pane.pane_id) { + Ok(screen) => screen, + Err(err) => { + warn!(target = %pane.target, %err, "capture failed"); + continue; + } + }; + // tmux pane_title carries the last OSC title the application + // set; tmux does not re-expose OSC progress sequences. + let detection = + detect_agent_with_osc(Some(agent), &screen, &pane.pane_title, ""); + (!detection.skip_state_update).then_some(detection.state) + } } else { // A pane that stops running an agent (agent quit into a - // shell) is no longer an observation. + // shell) is no longer an observation; a stale hook report + // from the dead agent is not authoritative. Some(AgentState::Unknown) }; @@ -130,6 +144,10 @@ impl Watcher { debug!(target = %pane.target, "observation skipped (viewer screen)"); continue; }; + if new_state == AgentState::Unknown { + // The agent is gone; its last hook report must not outlive it. + hooks::drop_report(&pane.pane_id); + } // Keep the pane's agent label current even when the state is // unchanged (e.g. an agent swap between two idle panes). |
