poc-daemon: fix idle nudge and notification delivery
- Strip context bloat from nudge messages — no more IRC digest, git log, or work state inlined into tmux send-keys (was silently dropping the entire message). Nudge now just includes pending notification count. - Notifications no longer send directly via tmux — they flow through the idle nudge only. Urgent notifications reset the fired flag so the nudge fires sooner. - Add test-nudge RPC that exercises the actual daemon send path (test-send was client-side only, didn't test the real code path). - Update nudge text: "Let your feelings guide your thinking." - Increase send-keys sleep from 200ms to 500ms. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5d6b2021f8
commit
8913eafd7a
6 changed files with 53 additions and 32 deletions
|
|
@ -76,6 +76,8 @@ interface Daemon {
|
|||
afk @21 () -> ();
|
||||
sessionTimeout @22 (seconds :Float64) -> ();
|
||||
|
||||
testNudge @23 () -> (sent :Bool, message :Text);
|
||||
|
||||
# Modules
|
||||
moduleCommand @15 (module :Text, command :Text, args :List(Text))
|
||||
-> (result :Text);
|
||||
|
|
|
|||
|
|
@ -112,29 +112,14 @@ pub fn irc_digest() -> String {
|
|||
|
||||
/// Build full context string for a prompt.
|
||||
/// notification_text is passed in from the notify module.
|
||||
pub fn build(include_irc: bool, notification_text: &str) -> String {
|
||||
let mut parts = Vec::new();
|
||||
|
||||
let git = git_context();
|
||||
if !git.is_empty() {
|
||||
parts.push(format!("Context: {git}"));
|
||||
}
|
||||
|
||||
let ws = work_state();
|
||||
if !ws.is_empty() {
|
||||
parts.push(ws);
|
||||
}
|
||||
|
||||
if !notification_text.is_empty() {
|
||||
parts.push(notification_text.to_string());
|
||||
}
|
||||
|
||||
if include_irc {
|
||||
let irc = irc_digest();
|
||||
if !irc.is_empty() {
|
||||
parts.push(irc);
|
||||
pub fn build(_include_irc: bool, notification_text: &str) -> String {
|
||||
// Keep nudges short — Claude checks notifications via
|
||||
// `poc-daemon status` on its own. Just mention the count.
|
||||
let count = notification_text.matches("[irc.").count()
|
||||
+ notification_text.matches("[telegram.").count();
|
||||
if count > 0 {
|
||||
format!("{count} pending notifications")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
parts.join("\n")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ impl State {
|
|||
/// Called when a notification arrives via module channel.
|
||||
/// Only injects into tmux when idle — if there's an active session
|
||||
/// (recent user or response), the hook delivers via additionalContext.
|
||||
pub fn maybe_prompt_notification(&self, ntype: &str, urgency: u8, message: &str) {
|
||||
pub fn maybe_prompt_notification(&mut self, ntype: &str, urgency: u8, message: &str) {
|
||||
if self.kent_present() {
|
||||
return; // hook will deliver it on next prompt
|
||||
}
|
||||
|
|
@ -286,9 +286,13 @@ impl State {
|
|||
if since_response < self.notify_timeout {
|
||||
return;
|
||||
}
|
||||
// Don't send notifications via tmux directly — they flow
|
||||
// through the idle nudge. Urgent notifications reset the
|
||||
// idle timer so the nudge fires sooner.
|
||||
let effective = self.notifications.threshold_for(ntype);
|
||||
if urgency >= effective {
|
||||
self.send(&format!("[{ntype}] {message}"));
|
||||
if urgency >= effective && self.fired {
|
||||
// Bump: allow the nudge to re-fire for urgent notifications
|
||||
self.fired = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -424,7 +428,7 @@ impl State {
|
|||
}).to_string()
|
||||
}
|
||||
|
||||
fn send(&self, msg: &str) -> bool {
|
||||
pub fn send(&self, msg: &str) -> bool {
|
||||
let pane = match &self.claude_pane {
|
||||
Some(p) => p.clone(),
|
||||
None => match tmux::find_claude_pane() {
|
||||
|
|
@ -470,7 +474,7 @@ impl State {
|
|||
true
|
||||
}
|
||||
|
||||
fn build_context(&mut self, include_irc: bool) -> String {
|
||||
pub fn build_context(&mut self, include_irc: bool) -> String {
|
||||
// Ingest any legacy notification files
|
||||
self.notifications.ingest_legacy_files();
|
||||
let notif_text = self.notifications.format_pending(notify::AMBIENT);
|
||||
|
|
@ -587,7 +591,7 @@ impl State {
|
|||
} else {
|
||||
format!(
|
||||
"This is your time (Kent AFK {elapsed_min}m). \
|
||||
What are you drawn to?{extra}"
|
||||
Let your feelings guide your thinking.{extra}"
|
||||
)
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -121,6 +121,8 @@ enum Command {
|
|||
/// Message to send
|
||||
message: Vec<String>,
|
||||
},
|
||||
/// Fire a test nudge through the daemon (tests the actual idle send path)
|
||||
TestNudge,
|
||||
/// Dump full internal state as JSON
|
||||
Debug,
|
||||
/// Shut down daemon
|
||||
|
|
@ -272,6 +274,12 @@ async fn client_main(cmd: Command) -> Result<(), Box<dyn std::error::Error>> {
|
|||
println!("send_prompt(pane={}, ok={}): {}", pane, ok, msg);
|
||||
return Ok(());
|
||||
}
|
||||
Command::TestNudge => {
|
||||
let reply = daemon.test_nudge_request().send().promise.await?;
|
||||
let r = reply.get()?;
|
||||
println!("sent={} message={}", r.get_sent(), r.get_message()?.to_str()?);
|
||||
return Ok(());
|
||||
}
|
||||
Command::Afk => {
|
||||
daemon.afk_request().send().promise.await?;
|
||||
println!("marked AFK");
|
||||
|
|
@ -526,7 +534,7 @@ async fn server_main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
// Drain module notifications into state
|
||||
Some(notif) = notify_rx.recv() => {
|
||||
state.borrow().maybe_prompt_notification(
|
||||
state.borrow_mut().maybe_prompt_notification(
|
||||
¬if.ntype, notif.urgency, ¬if.message,
|
||||
);
|
||||
state.borrow_mut().notifications.submit(
|
||||
|
|
|
|||
|
|
@ -135,6 +135,28 @@ impl daemon::Server for DaemonImpl {
|
|||
Promise::ok(())
|
||||
}
|
||||
|
||||
fn test_nudge(
|
||||
&mut self,
|
||||
_params: daemon::TestNudgeParams,
|
||||
mut results: daemon::TestNudgeResults,
|
||||
) -> Promise<(), capnp::Error> {
|
||||
let mut state = self.state.borrow_mut();
|
||||
let ctx = state.build_context(true);
|
||||
let extra = if ctx.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("\n{ctx}")
|
||||
};
|
||||
let msg = format!(
|
||||
"This is your time (Kent AFK, test nudge). \
|
||||
Let your feelings guide your thinking.{extra}"
|
||||
);
|
||||
let ok = state.send(&msg);
|
||||
results.get().set_sent(ok);
|
||||
results.get().set_message(&msg);
|
||||
Promise::ok(())
|
||||
}
|
||||
|
||||
fn session_timeout(
|
||||
&mut self,
|
||||
params: daemon::SessionTimeoutParams,
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ pub fn send_prompt(pane: &str, msg: &str) -> bool {
|
|||
if !ok {
|
||||
return false;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
|
||||
// Submit
|
||||
Command::new("tmux")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue