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.rs46
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).