From fa3b57fffdcdac7317af87ab8176206e24957666 Mon Sep 17 00:00:00 2001 From: ProofOfConcept Date: Sat, 11 Apr 2026 14:35:16 -0400 Subject: [PATCH] poc-daemon: fix pane tracking, suppress notifications when user present Three fixes: - Persist claude_pane via thalamus extra map so it survives every save path (not just explicit Save commands) - Don't clobber claude_pane with empty string when TMUX_PANE is unset - signal_response now passes TMUX_PANE like signal_user does - maybe_prompt_notification returns early when user is present, preventing notification spam during active sessions Co-Authored-By: Proof of Concept --- src/idle.rs | 46 ++++++++++++++++++++++++++++++---------------- src/lib.rs | 2 +- src/poc-hook.rs | 7 ++++++- src/rpc.rs | 2 +- 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/idle.rs b/src/idle.rs index f1ad2ee..7f61d31 100644 --- a/src/idle.rs +++ b/src/idle.rs @@ -25,43 +25,57 @@ impl std::ops::DerefMut for State { impl State { pub fn new() -> Self { - Self { - inner: thalamus_idle::State::new(), - claude_pane: None, - } + let mut inner = thalamus_idle::State::new(); + inner.state_path = home().join(".consciousness/daemon-state.json"); + Self { inner, claude_pane: None } } pub fn load(&mut self) { self.inner.load(); - // Also load claude_pane from persisted state - let path = home().join(".consciousness/daemon-state.json"); - if let Ok(data) = std::fs::read_to_string(&path) { - if let Ok(v) = serde_json::from_str::(&data) { - if let Some(p) = v.get("claude_pane").and_then(|v| v.as_str()) { - self.claude_pane = Some(p.to_string()); - } - } - } + self.claude_pane = self.inner.extra + .get("claude_pane") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()); } - pub fn save(&self) { + pub fn save(&mut self) { + // Stash claude_pane in extra so inner.save() persists it + self.inner.extra.insert( + "claude_pane".into(), + serde_json::json!(self.claude_pane), + ); self.inner.save(); } + fn set_pane(&mut self, pane: &str) { + if !pane.is_empty() { + self.claude_pane = Some(pane.to_string()); + self.inner.extra.insert( + "claude_pane".into(), + serde_json::json!(pane), + ); + } + } + /// Record user activity with pane tracking. pub fn handle_user(&mut self, pane: &str) { - self.claude_pane = Some(pane.to_string()); + self.set_pane(pane); self.inner.user_activity(); } /// Record response activity with pane tracking. pub fn handle_response(&mut self, pane: &str) { - self.claude_pane = Some(pane.to_string()); + self.set_pane(pane); self.inner.response_activity(); } /// Maybe send a notification as a tmux prompt. + /// If the user is present, notifications stay queued — the hook + /// picks them up via check_notifications() on the next message. pub fn maybe_prompt_notification(&mut self, ntype: &str, urgency: u8, _message: &str) { + if self.inner.user_present() { + return; + } let threshold = self.inner.notifications.threshold_for(ntype); if urgency >= threshold { let deliverable = self.inner.notifications.drain_deliverable(); diff --git a/src/lib.rs b/src/lib.rs index af47889..285557a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -552,7 +552,7 @@ async fn server_main() -> Result<(), Box> { } } - state.borrow().save(); + state.borrow_mut().save(); let _ = std::fs::remove_file(sock_path()); let _ = std::fs::remove_file(pid_path()); info!("daemon stopped"); diff --git a/src/poc-hook.rs b/src/poc-hook.rs index b5c3291..26df007 100644 --- a/src/poc-hook.rs +++ b/src/poc-hook.rs @@ -66,7 +66,12 @@ fn signal_user() { } fn signal_response() { - daemon_cmd(&["response"]); + let pane = std::env::var("TMUX_PANE").unwrap_or_default(); + if pane.is_empty() { + daemon_cmd(&["response"]); + } else { + daemon_cmd(&["response", &pane]); + } } fn check_notifications() { diff --git a/src/rpc.rs b/src/rpc.rs index 3c7a9cb..9c8d4f0 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -181,7 +181,7 @@ impl daemon::Server for DaemonImpl { _params: daemon::SaveParams, _results: daemon::SaveResults, ) -> impl std::future::Future> { - self.state.borrow().save(); + self.state.borrow_mut().save(); info!("state saved"); std::future::ready(Ok(())) }