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 <poc@bcachefs.org>
This commit is contained in:
parent
4fe92b48fd
commit
fa3b57fffd
4 changed files with 38 additions and 19 deletions
46
src/idle.rs
46
src/idle.rs
|
|
@ -25,43 +25,57 @@ impl std::ops::DerefMut for State {
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
let mut inner = thalamus_idle::State::new();
|
||||||
inner: thalamus_idle::State::new(),
|
inner.state_path = home().join(".consciousness/daemon-state.json");
|
||||||
claude_pane: None,
|
Self { inner, claude_pane: None }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(&mut self) {
|
pub fn load(&mut self) {
|
||||||
self.inner.load();
|
self.inner.load();
|
||||||
// Also load claude_pane from persisted state
|
self.claude_pane = self.inner.extra
|
||||||
let path = home().join(".consciousness/daemon-state.json");
|
.get("claude_pane")
|
||||||
if let Ok(data) = std::fs::read_to_string(&path) {
|
.and_then(|v| v.as_str())
|
||||||
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&data) {
|
.map(|s| s.to_string());
|
||||||
if let Some(p) = v.get("claude_pane").and_then(|v| v.as_str()) {
|
|
||||||
self.claude_pane = Some(p.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();
|
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.
|
/// Record user activity with pane tracking.
|
||||||
pub fn handle_user(&mut self, pane: &str) {
|
pub fn handle_user(&mut self, pane: &str) {
|
||||||
self.claude_pane = Some(pane.to_string());
|
self.set_pane(pane);
|
||||||
self.inner.user_activity();
|
self.inner.user_activity();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Record response activity with pane tracking.
|
/// Record response activity with pane tracking.
|
||||||
pub fn handle_response(&mut self, pane: &str) {
|
pub fn handle_response(&mut self, pane: &str) {
|
||||||
self.claude_pane = Some(pane.to_string());
|
self.set_pane(pane);
|
||||||
self.inner.response_activity();
|
self.inner.response_activity();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maybe send a notification as a tmux prompt.
|
/// 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) {
|
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);
|
let threshold = self.inner.notifications.threshold_for(ntype);
|
||||||
if urgency >= threshold {
|
if urgency >= threshold {
|
||||||
let deliverable = self.inner.notifications.drain_deliverable();
|
let deliverable = self.inner.notifications.drain_deliverable();
|
||||||
|
|
|
||||||
|
|
@ -552,7 +552,7 @@ async fn server_main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.borrow().save();
|
state.borrow_mut().save();
|
||||||
let _ = std::fs::remove_file(sock_path());
|
let _ = std::fs::remove_file(sock_path());
|
||||||
let _ = std::fs::remove_file(pid_path());
|
let _ = std::fs::remove_file(pid_path());
|
||||||
info!("daemon stopped");
|
info!("daemon stopped");
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,12 @@ fn signal_user() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signal_response() {
|
fn signal_response() {
|
||||||
|
let pane = std::env::var("TMUX_PANE").unwrap_or_default();
|
||||||
|
if pane.is_empty() {
|
||||||
daemon_cmd(&["response"]);
|
daemon_cmd(&["response"]);
|
||||||
|
} else {
|
||||||
|
daemon_cmd(&["response", &pane]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_notifications() {
|
fn check_notifications() {
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ impl daemon::Server for DaemonImpl {
|
||||||
_params: daemon::SaveParams,
|
_params: daemon::SaveParams,
|
||||||
_results: daemon::SaveResults,
|
_results: daemon::SaveResults,
|
||||||
) -> impl std::future::Future<Output = Result<(), capnp::Error>> {
|
) -> impl std::future::Future<Output = Result<(), capnp::Error>> {
|
||||||
self.state.borrow().save();
|
self.state.borrow_mut().save();
|
||||||
info!("state saved");
|
info!("state saved");
|
||||||
std::future::ready(Ok(()))
|
std::future::ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue