cli: switch to clap, add notify-timeout, improve status display
Replace manual arg parsing with clap derive for the full command set. Single source of truth for command names, args, and help text. Add notify_timeout (default 2min) — controls how long after last response before notifications inject via tmux instead of waiting for the hook. Separate from idle_timeout (5min) which controls autonomous prompts. Improve `poc-daemon status` to show both timers with elapsed/configured and block reason, replacing the terse one-liner. Add new Status fields over capnp: idleTimeout, notifyTimeout, sinceActivity, sinceUser, blockReason. ExecStart in poc-daemon.service now uses `daemon` subcommand. Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
This commit is contained in:
parent
eab656aa64
commit
d0080698f3
7 changed files with 416 additions and 170 deletions
121
Cargo.lock
generated
121
Cargo.lock
generated
|
|
@ -31,6 +31,56 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.102"
|
version = "1.0.102"
|
||||||
|
|
@ -260,6 +310,46 @@ dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.5.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.5.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.5.55"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cmake"
|
name = "cmake"
|
||||||
version = "0.1.57"
|
version = "0.1.57"
|
||||||
|
|
@ -269,6 +359,12 @@ dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
|
|
@ -1183,6 +1279,12 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.17"
|
version = "1.0.17"
|
||||||
|
|
@ -1471,6 +1573,12 @@ version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
|
|
@ -1597,6 +1705,7 @@ dependencies = [
|
||||||
"capnp-rpc",
|
"capnp-rpc",
|
||||||
"capnpc",
|
"capnpc",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"clap",
|
||||||
"faer",
|
"faer",
|
||||||
"futures",
|
"futures",
|
||||||
"jobkit",
|
"jobkit",
|
||||||
|
|
@ -2304,6 +2413,12 @@ version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
|
|
@ -2742,6 +2857,12 @@ version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.21.0"
|
version = "1.21.0"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ serde_json = "1"
|
||||||
bincode = "1"
|
bincode = "1"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
clap = { version = "4", features = ["derive"] }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
faer = "0.24.0"
|
faer = "0.24.0"
|
||||||
rkyv = { version = "0.7", features = ["validation", "std"] }
|
rkyv = { version = "0.7", features = ["validation", "std"] }
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,11 @@ struct Status {
|
||||||
uptime @9 :Float64;
|
uptime @9 :Float64;
|
||||||
activity @10 :Activity;
|
activity @10 :Activity;
|
||||||
pendingCount @11 :UInt32;
|
pendingCount @11 :UInt32;
|
||||||
|
idleTimeout @12 :Float64; # configured idle timeout (secs)
|
||||||
|
notifyTimeout @13 :Float64; # configured notify-via-tmux timeout (secs)
|
||||||
|
sinceActivity @14 :Float64; # secs since max(lastUserMsg, lastResponse)
|
||||||
|
sinceUser @15 :Float64; # secs since lastUserMsg
|
||||||
|
blockReason @16 :Text; # why idle timer hasn't fired
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Daemon {
|
interface Daemon {
|
||||||
|
|
@ -62,6 +67,7 @@ interface Daemon {
|
||||||
setThreshold @14 (type :Text, level :UInt8) -> ();
|
setThreshold @14 (type :Text, level :UInt8) -> ();
|
||||||
|
|
||||||
idleTimeout @16 (seconds :Float64) -> ();
|
idleTimeout @16 (seconds :Float64) -> ();
|
||||||
|
notifyTimeout @19 (seconds :Float64) -> ();
|
||||||
save @17 () -> ();
|
save @17 () -> ();
|
||||||
debug @18 () -> (json :Text);
|
debug @18 () -> (json :Text);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ use std::fs;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
const DEFAULT_PAUSE_SECS: f64 = 5.0 * 60.0;
|
const DEFAULT_IDLE_TIMEOUT: f64 = 5.0 * 60.0;
|
||||||
|
const DEFAULT_NOTIFY_TIMEOUT: f64 = 2.0 * 60.0;
|
||||||
const SESSION_ACTIVE_SECS: f64 = 15.0 * 60.0;
|
const SESSION_ACTIVE_SECS: f64 = 15.0 * 60.0;
|
||||||
const DREAM_INTERVAL_HOURS: u64 = 18;
|
const DREAM_INTERVAL_HOURS: u64 = 18;
|
||||||
|
|
||||||
|
|
@ -30,6 +31,8 @@ struct Persisted {
|
||||||
claude_pane: Option<String>,
|
claude_pane: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
idle_timeout: f64,
|
idle_timeout: f64,
|
||||||
|
#[serde(default)]
|
||||||
|
notify_timeout: f64,
|
||||||
// Human-readable mirrors — written but not consumed on load
|
// Human-readable mirrors — written but not consumed on load
|
||||||
#[serde(default, skip_deserializing)]
|
#[serde(default, skip_deserializing)]
|
||||||
last_user_msg_time: String,
|
last_user_msg_time: String,
|
||||||
|
|
@ -75,6 +78,7 @@ pub struct State {
|
||||||
pub dream_start: f64,
|
pub dream_start: f64,
|
||||||
pub fired: bool,
|
pub fired: bool,
|
||||||
pub idle_timeout: f64,
|
pub idle_timeout: f64,
|
||||||
|
pub notify_timeout: f64,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub running: bool,
|
pub running: bool,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
|
|
@ -95,7 +99,8 @@ impl State {
|
||||||
dreaming: false,
|
dreaming: false,
|
||||||
dream_start: 0.0,
|
dream_start: 0.0,
|
||||||
fired: false,
|
fired: false,
|
||||||
idle_timeout: DEFAULT_PAUSE_SECS,
|
idle_timeout: DEFAULT_IDLE_TIMEOUT,
|
||||||
|
notify_timeout: DEFAULT_NOTIFY_TIMEOUT,
|
||||||
running: true,
|
running: true,
|
||||||
start_time: now(),
|
start_time: now(),
|
||||||
notifications: notify::NotifyState::new(),
|
notifications: notify::NotifyState::new(),
|
||||||
|
|
@ -112,6 +117,9 @@ impl State {
|
||||||
if p.idle_timeout > 0.0 {
|
if p.idle_timeout > 0.0 {
|
||||||
self.idle_timeout = p.idle_timeout;
|
self.idle_timeout = p.idle_timeout;
|
||||||
}
|
}
|
||||||
|
if p.notify_timeout > 0.0 {
|
||||||
|
self.notify_timeout = p.notify_timeout;
|
||||||
|
}
|
||||||
// Suppress immediate fire after restart — wait for fresh
|
// Suppress immediate fire after restart — wait for fresh
|
||||||
// user/response signal before allowing the idle timer
|
// user/response signal before allowing the idle timer
|
||||||
self.fired = true;
|
self.fired = true;
|
||||||
|
|
@ -140,6 +148,7 @@ impl State {
|
||||||
saved_at: epoch_to_iso(now()),
|
saved_at: epoch_to_iso(now()),
|
||||||
fired: self.fired,
|
fired: self.fired,
|
||||||
idle_timeout: self.idle_timeout,
|
idle_timeout: self.idle_timeout,
|
||||||
|
notify_timeout: self.notify_timeout,
|
||||||
uptime: now() - self.start_time,
|
uptime: now() - self.start_time,
|
||||||
};
|
};
|
||||||
if let Ok(json) = serde_json::to_string_pretty(&p) {
|
if let Ok(json) = serde_json::to_string_pretty(&p) {
|
||||||
|
|
@ -180,7 +189,7 @@ impl State {
|
||||||
// If we've responded recently, the session is active —
|
// If we've responded recently, the session is active —
|
||||||
// notifications will arrive via hook, no need to wake us
|
// notifications will arrive via hook, no need to wake us
|
||||||
let since_response = now() - self.last_response;
|
let since_response = now() - self.last_response;
|
||||||
if since_response < self.idle_timeout {
|
if since_response < self.notify_timeout {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let effective = self.notifications.threshold_for(ntype);
|
let effective = self.notifications.threshold_for(ntype);
|
||||||
|
|
@ -195,6 +204,12 @@ impl State {
|
||||||
info!("idle timeout = {secs}s");
|
info!("idle timeout = {secs}s");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_notify_timeout(&mut self, secs: f64) {
|
||||||
|
self.notify_timeout = secs;
|
||||||
|
self.save();
|
||||||
|
info!("notify timeout = {secs}s");
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle_sleep(&mut self, until: f64) {
|
pub fn handle_sleep(&mut self, until: f64) {
|
||||||
if until == 0.0 {
|
if until == 0.0 {
|
||||||
self.sleep_until = Some(0.0);
|
self.sleep_until = Some(0.0);
|
||||||
|
|
@ -223,23 +238,16 @@ impl State {
|
||||||
(now() - self.last_user_msg) < SESSION_ACTIVE_SECS
|
(now() - self.last_user_msg) < SESSION_ACTIVE_SECS
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Full debug dump as JSON with computed values.
|
/// Seconds since the most recent of user message or response.
|
||||||
pub fn debug_json(&self) -> String {
|
pub fn since_activity(&self) -> f64 {
|
||||||
let t = now();
|
|
||||||
let reference = self.last_response.max(self.last_user_msg);
|
let reference = self.last_response.max(self.last_user_msg);
|
||||||
let since_user = t - self.last_user_msg;
|
if reference > 0.0 { now() - reference } else { 0.0 }
|
||||||
let since_response = t - self.last_response;
|
}
|
||||||
let since_reference = if reference > 0.0 { t - reference } else { 0.0 };
|
|
||||||
|
|
||||||
let would_fire = !self.fired
|
/// Why the idle timer hasn't fired (or "none" if it would fire now).
|
||||||
&& self.sleep_until.is_none()
|
pub fn block_reason(&self) -> &'static str {
|
||||||
&& t >= self.quiet_until
|
let t = now();
|
||||||
&& !self.consolidating
|
if self.fired {
|
||||||
&& !self.dreaming
|
|
||||||
&& reference > 0.0
|
|
||||||
&& since_reference >= self.idle_timeout;
|
|
||||||
|
|
||||||
let block_reason = if self.fired {
|
|
||||||
"already fired"
|
"already fired"
|
||||||
} else if self.sleep_until.is_some() {
|
} else if self.sleep_until.is_some() {
|
||||||
"sleeping"
|
"sleeping"
|
||||||
|
|
@ -249,30 +257,37 @@ impl State {
|
||||||
"consolidating"
|
"consolidating"
|
||||||
} else if self.dreaming {
|
} else if self.dreaming {
|
||||||
"dreaming"
|
"dreaming"
|
||||||
} else if reference == 0.0 {
|
} else if self.last_response.max(self.last_user_msg) == 0.0 {
|
||||||
"no activity yet"
|
"no activity yet"
|
||||||
} else if since_reference < self.idle_timeout {
|
} else if self.since_activity() < self.idle_timeout {
|
||||||
"not idle long enough"
|
"not idle long enough"
|
||||||
} else {
|
} else {
|
||||||
"none — would fire"
|
"none — would fire"
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Full debug dump as JSON with computed values.
|
||||||
|
pub fn debug_json(&self) -> String {
|
||||||
|
let t = now();
|
||||||
|
let since_user = t - self.last_user_msg;
|
||||||
|
let since_response = t - self.last_response;
|
||||||
|
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"now": t,
|
"now": t,
|
||||||
"uptime": t - self.start_time,
|
"uptime": t - self.start_time,
|
||||||
"idle_timeout": self.idle_timeout,
|
"idle_timeout": self.idle_timeout,
|
||||||
|
"notify_timeout": self.notify_timeout,
|
||||||
"last_user_msg": self.last_user_msg,
|
"last_user_msg": self.last_user_msg,
|
||||||
"last_user_msg_ago": since_user,
|
"last_user_msg_ago": since_user,
|
||||||
"last_user_msg_time": epoch_to_iso(self.last_user_msg),
|
"last_user_msg_time": epoch_to_iso(self.last_user_msg),
|
||||||
"last_response": self.last_response,
|
"last_response": self.last_response,
|
||||||
"last_response_ago": since_response,
|
"last_response_ago": since_response,
|
||||||
"last_response_time": epoch_to_iso(self.last_response),
|
"last_response_time": epoch_to_iso(self.last_response),
|
||||||
"reference_ago": since_reference,
|
"since_activity": self.since_activity(),
|
||||||
"kent_present": self.kent_present(),
|
"kent_present": self.kent_present(),
|
||||||
"claude_pane": self.claude_pane,
|
"claude_pane": self.claude_pane,
|
||||||
"fired": self.fired,
|
"fired": self.fired,
|
||||||
"would_fire": would_fire,
|
"block_reason": self.block_reason(),
|
||||||
"block_reason": block_reason,
|
|
||||||
"sleep_until": self.sleep_until,
|
"sleep_until": self.sleep_until,
|
||||||
"quiet_until": self.quiet_until,
|
"quiet_until": self.quiet_until,
|
||||||
"consolidating": self.consolidating,
|
"consolidating": self.consolidating,
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,6 @@
|
||||||
// communication modules (IRC, Telegram) for Claude Code sessions.
|
// communication modules (IRC, Telegram) for Claude Code sessions.
|
||||||
// Listens on a Unix domain socket with a Cap'n Proto RPC interface.
|
// Listens on a Unix domain socket with a Cap'n Proto RPC interface.
|
||||||
// Same binary serves as both daemon and CLI client.
|
// Same binary serves as both daemon and CLI client.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
// poc-daemon Start the daemon
|
|
||||||
// poc-daemon status Query daemon status
|
|
||||||
// poc-daemon user [pane] Signal user activity
|
|
||||||
// poc-daemon response [pane] Signal Claude response
|
|
||||||
// poc-daemon notify <type> <urgency> <message>
|
|
||||||
// poc-daemon notifications Get pending notifications
|
|
||||||
// poc-daemon notify-types List all notification types
|
|
||||||
// poc-daemon notify-threshold <type> <level>
|
|
||||||
// poc-daemon sleep [timestamp] Sleep (0 or omit = indefinite)
|
|
||||||
// poc-daemon wake Cancel sleep
|
|
||||||
// poc-daemon quiet [seconds] Suppress prompts
|
|
||||||
// poc-daemon irc <command> IRC module commands
|
|
||||||
// poc-daemon stop Shut down daemon
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod context;
|
mod context;
|
||||||
|
|
@ -38,6 +23,7 @@ use std::rc::Rc;
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use capnp_rpc::{rpc_twoparty_capnp, twoparty, RpcSystem};
|
use capnp_rpc::{rpc_twoparty_capnp, twoparty, RpcSystem};
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
use futures::AsyncReadExt;
|
use futures::AsyncReadExt;
|
||||||
use tokio::net::UnixListener;
|
use tokio::net::UnixListener;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
@ -61,9 +47,111 @@ fn pid_path() -> PathBuf {
|
||||||
home().join(".claude/hooks/idle-daemon.pid")
|
home().join(".claude/hooks/idle-daemon.pid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── CLI ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "poc-daemon", about = "Notification routing and idle management daemon")]
|
||||||
|
struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Option<Command>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum Command {
|
||||||
|
/// Start the daemon (foreground)
|
||||||
|
Daemon,
|
||||||
|
/// Query daemon status
|
||||||
|
Status,
|
||||||
|
/// Signal user activity
|
||||||
|
User {
|
||||||
|
/// tmux pane identifier
|
||||||
|
pane: Option<String>,
|
||||||
|
},
|
||||||
|
/// Signal Claude response
|
||||||
|
Response {
|
||||||
|
/// tmux pane identifier
|
||||||
|
pane: Option<String>,
|
||||||
|
},
|
||||||
|
/// Sleep (suppress idle timer). 0 or omit = indefinite
|
||||||
|
Sleep {
|
||||||
|
/// Wake timestamp (epoch seconds), 0 = indefinite
|
||||||
|
until: Option<f64>,
|
||||||
|
},
|
||||||
|
/// Cancel sleep
|
||||||
|
Wake,
|
||||||
|
/// Suppress prompts for N seconds (default 300)
|
||||||
|
Quiet {
|
||||||
|
/// Duration in seconds
|
||||||
|
seconds: Option<u32>,
|
||||||
|
},
|
||||||
|
/// Set idle timeout in seconds (how long before autonomous prompt)
|
||||||
|
IdleTimeout {
|
||||||
|
/// Timeout in seconds
|
||||||
|
seconds: f64,
|
||||||
|
},
|
||||||
|
/// Set notify timeout in seconds (how long before tmux notification injection)
|
||||||
|
NotifyTimeout {
|
||||||
|
/// Timeout in seconds
|
||||||
|
seconds: f64,
|
||||||
|
},
|
||||||
|
/// Signal consolidation started
|
||||||
|
Consolidating,
|
||||||
|
/// Signal consolidation ended
|
||||||
|
Consolidated,
|
||||||
|
/// Signal dream started
|
||||||
|
DreamStart,
|
||||||
|
/// Signal dream ended
|
||||||
|
DreamEnd,
|
||||||
|
/// Force state persistence to disk
|
||||||
|
Save,
|
||||||
|
/// Dump full internal state as JSON
|
||||||
|
Debug,
|
||||||
|
/// Shut down daemon
|
||||||
|
Stop,
|
||||||
|
/// Submit a notification
|
||||||
|
Notify {
|
||||||
|
/// Notification type (e.g. "irc", "telegram")
|
||||||
|
#[arg(name = "type")]
|
||||||
|
ntype: String,
|
||||||
|
/// Urgency level (ambient/low/medium/high/critical or 0-4)
|
||||||
|
urgency: String,
|
||||||
|
/// Message text
|
||||||
|
message: Vec<String>,
|
||||||
|
},
|
||||||
|
/// Get pending notifications
|
||||||
|
Notifications {
|
||||||
|
/// Minimum urgency filter
|
||||||
|
min_urgency: Option<String>,
|
||||||
|
},
|
||||||
|
/// List all notification types
|
||||||
|
NotifyTypes,
|
||||||
|
/// Set notification threshold for a type
|
||||||
|
NotifyThreshold {
|
||||||
|
/// Notification type
|
||||||
|
#[arg(name = "type")]
|
||||||
|
ntype: String,
|
||||||
|
/// Urgency level threshold
|
||||||
|
level: String,
|
||||||
|
},
|
||||||
|
/// IRC module commands
|
||||||
|
Irc {
|
||||||
|
/// Subcommand (join, leave, send, status, log, nick)
|
||||||
|
command: String,
|
||||||
|
/// Arguments
|
||||||
|
args: Vec<String>,
|
||||||
|
},
|
||||||
|
/// Telegram module commands
|
||||||
|
Telegram {
|
||||||
|
/// Subcommand
|
||||||
|
command: String,
|
||||||
|
/// Arguments
|
||||||
|
args: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// ── Client mode ──────────────────────────────────────────────────
|
// ── Client mode ──────────────────────────────────────────────────
|
||||||
|
|
||||||
async fn client_main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
async fn client_main(cmd: Command) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let sock = sock_path();
|
let sock = sock_path();
|
||||||
if !sock.exists() {
|
if !sock.exists() {
|
||||||
eprintln!("daemon not running (no socket at {})", sock.display());
|
eprintln!("daemon not running (no socket at {})", sock.display());
|
||||||
|
|
@ -87,105 +175,126 @@ async fn client_main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>
|
||||||
|
|
||||||
tokio::task::spawn_local(rpc_system);
|
tokio::task::spawn_local(rpc_system);
|
||||||
|
|
||||||
let cmd = args.get(1).map(|s| s.as_str()).unwrap_or("status");
|
|
||||||
|
|
||||||
match cmd {
|
match cmd {
|
||||||
"status" => {
|
Command::Daemon => unreachable!("handled in main"),
|
||||||
|
Command::Status => {
|
||||||
let reply = daemon.status_request().send().promise.await?;
|
let reply = daemon.status_request().send().promise.await?;
|
||||||
let status = reply.get()?.get_status()?;
|
let s = reply.get()?.get_status()?;
|
||||||
println!(
|
|
||||||
"uptime={:.0}s pane={} kent={} activity={:?} pending={} fired={} sleep={} quiet={} dreaming={} consolidating={}",
|
let fmt_secs = |s: f64| -> String {
|
||||||
status.get_uptime(),
|
if s < 60.0 { format!("{:.0}s", s) }
|
||||||
status.get_claude_pane()?.to_str().unwrap_or("none"),
|
else if s < 3600.0 { format!("{:.0}m", s / 60.0) }
|
||||||
status.get_kent_present(),
|
else { format!("{:.1}h", s / 3600.0) }
|
||||||
status.get_activity()?,
|
};
|
||||||
status.get_pending_count(),
|
|
||||||
status.get_fired(),
|
println!("uptime: {} pane: {} activity: {:?} pending: {}",
|
||||||
status.get_sleep_until(),
|
fmt_secs(s.get_uptime()),
|
||||||
status.get_quiet_until(),
|
s.get_claude_pane()?.to_str().unwrap_or("none"),
|
||||||
status.get_dreaming(),
|
s.get_activity()?,
|
||||||
status.get_consolidating(),
|
s.get_pending_count(),
|
||||||
);
|
);
|
||||||
|
println!("idle timer: {}/{} ({})",
|
||||||
|
fmt_secs(s.get_since_activity()),
|
||||||
|
fmt_secs(s.get_idle_timeout()),
|
||||||
|
s.get_block_reason()?.to_str()?,
|
||||||
|
);
|
||||||
|
println!("notify timer: {}/{}",
|
||||||
|
fmt_secs(s.get_since_activity()),
|
||||||
|
fmt_secs(s.get_notify_timeout()),
|
||||||
|
);
|
||||||
|
println!("kent: {} (last {})",
|
||||||
|
if s.get_kent_present() { "present" } else { "away" },
|
||||||
|
fmt_secs(s.get_since_user()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let sleep = s.get_sleep_until();
|
||||||
|
if sleep != 0.0 {
|
||||||
|
if sleep < 0.0 {
|
||||||
|
println!("sleep: indefinite");
|
||||||
|
} else {
|
||||||
|
println!("sleep: until {sleep:.0}");
|
||||||
}
|
}
|
||||||
"user" => {
|
}
|
||||||
let pane = args.get(2).map(|s| s.as_str()).unwrap_or("");
|
if s.get_consolidating() { println!("consolidating"); }
|
||||||
|
if s.get_dreaming() { println!("dreaming"); }
|
||||||
|
}
|
||||||
|
Command::User { pane } => {
|
||||||
|
let pane = pane.as_deref().unwrap_or("");
|
||||||
let mut req = daemon.user_request();
|
let mut req = daemon.user_request();
|
||||||
req.get().set_pane(pane);
|
req.get().set_pane(pane);
|
||||||
req.send().promise.await?;
|
req.send().promise.await?;
|
||||||
}
|
}
|
||||||
"response" => {
|
Command::Response { pane } => {
|
||||||
let pane = args.get(2).map(|s| s.as_str()).unwrap_or("");
|
let pane = pane.as_deref().unwrap_or("");
|
||||||
let mut req = daemon.response_request();
|
let mut req = daemon.response_request();
|
||||||
req.get().set_pane(pane);
|
req.get().set_pane(pane);
|
||||||
req.send().promise.await?;
|
req.send().promise.await?;
|
||||||
}
|
}
|
||||||
"sleep" => {
|
Command::Sleep { until } => {
|
||||||
let until: f64 = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(0.0);
|
|
||||||
let mut req = daemon.sleep_request();
|
let mut req = daemon.sleep_request();
|
||||||
req.get().set_until(until);
|
req.get().set_until(until.unwrap_or(0.0));
|
||||||
req.send().promise.await?;
|
req.send().promise.await?;
|
||||||
}
|
}
|
||||||
"wake" => {
|
Command::Wake => {
|
||||||
daemon.wake_request().send().promise.await?;
|
daemon.wake_request().send().promise.await?;
|
||||||
}
|
}
|
||||||
"quiet" => {
|
Command::Quiet { seconds } => {
|
||||||
let secs: u32 = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(300);
|
|
||||||
let mut req = daemon.quiet_request();
|
let mut req = daemon.quiet_request();
|
||||||
req.get().set_seconds(secs);
|
req.get().set_seconds(seconds.unwrap_or(300));
|
||||||
req.send().promise.await?;
|
req.send().promise.await?;
|
||||||
}
|
}
|
||||||
"idle-timeout" => {
|
Command::IdleTimeout { seconds } => {
|
||||||
let secs: f64 = args.get(2)
|
|
||||||
.and_then(|s| s.parse().ok())
|
|
||||||
.ok_or("usage: poc-daemon idle-timeout <seconds>")?;
|
|
||||||
let mut req = daemon.idle_timeout_request();
|
let mut req = daemon.idle_timeout_request();
|
||||||
req.get().set_seconds(secs);
|
req.get().set_seconds(seconds);
|
||||||
req.send().promise.await?;
|
req.send().promise.await?;
|
||||||
println!("idle timeout = {secs}s");
|
println!("idle timeout = {seconds}s");
|
||||||
}
|
}
|
||||||
"consolidating" => {
|
Command::NotifyTimeout { seconds } => {
|
||||||
|
let mut req = daemon.notify_timeout_request();
|
||||||
|
req.get().set_seconds(seconds);
|
||||||
|
req.send().promise.await?;
|
||||||
|
println!("notify timeout = {seconds}s");
|
||||||
|
}
|
||||||
|
Command::Consolidating => {
|
||||||
daemon.consolidating_request().send().promise.await?;
|
daemon.consolidating_request().send().promise.await?;
|
||||||
}
|
}
|
||||||
"consolidated" => {
|
Command::Consolidated => {
|
||||||
daemon.consolidated_request().send().promise.await?;
|
daemon.consolidated_request().send().promise.await?;
|
||||||
}
|
}
|
||||||
"dream-start" => {
|
Command::DreamStart => {
|
||||||
daemon.dream_start_request().send().promise.await?;
|
daemon.dream_start_request().send().promise.await?;
|
||||||
}
|
}
|
||||||
"dream-end" => {
|
Command::DreamEnd => {
|
||||||
daemon.dream_end_request().send().promise.await?;
|
daemon.dream_end_request().send().promise.await?;
|
||||||
}
|
}
|
||||||
"save" => {
|
Command::Save => {
|
||||||
daemon.save_request().send().promise.await?;
|
daemon.save_request().send().promise.await?;
|
||||||
println!("state saved");
|
println!("state saved");
|
||||||
}
|
}
|
||||||
"debug" => {
|
Command::Debug => {
|
||||||
let reply = daemon.debug_request().send().promise.await?;
|
let reply = daemon.debug_request().send().promise.await?;
|
||||||
let json = reply.get()?.get_json()?.to_str()?;
|
let json = reply.get()?.get_json()?.to_str()?;
|
||||||
// Pretty-print
|
|
||||||
if let Ok(v) = serde_json::from_str::<serde_json::Value>(json) {
|
if let Ok(v) = serde_json::from_str::<serde_json::Value>(json) {
|
||||||
println!("{}", serde_json::to_string_pretty(&v).unwrap_or_else(|_| json.to_string()));
|
println!("{}", serde_json::to_string_pretty(&v).unwrap_or_else(|_| json.to_string()));
|
||||||
} else {
|
} else {
|
||||||
println!("{json}");
|
println!("{json}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"stop" => {
|
Command::Stop => {
|
||||||
daemon.stop_request().send().promise.await?;
|
daemon.stop_request().send().promise.await?;
|
||||||
println!("stopping");
|
println!("stopping");
|
||||||
}
|
}
|
||||||
"notify" => {
|
Command::Notify { ntype, urgency, message } => {
|
||||||
let ntype = args.get(2).ok_or("missing type")?;
|
let urgency = notify::parse_urgency(&urgency)
|
||||||
let urgency_str = args.get(3).ok_or("missing urgency")?;
|
.ok_or_else(|| format!("invalid urgency: {urgency}"))?;
|
||||||
let urgency = notify::parse_urgency(urgency_str)
|
let message = message.join(" ");
|
||||||
.ok_or_else(|| format!("invalid urgency: {urgency_str}"))?;
|
|
||||||
let message = args[4..].join(" ");
|
|
||||||
if message.is_empty() {
|
if message.is_empty() {
|
||||||
return Err("missing message".into());
|
return Err("missing message".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut req = daemon.notify_request();
|
let mut req = daemon.notify_request();
|
||||||
let mut n = req.get().init_notification();
|
let mut n = req.get().init_notification();
|
||||||
n.set_type(ntype);
|
n.set_type(&ntype);
|
||||||
n.set_urgency(urgency);
|
n.set_urgency(urgency);
|
||||||
n.set_message(&message);
|
n.set_message(&message);
|
||||||
n.set_timestamp(crate::now());
|
n.set_timestamp(crate::now());
|
||||||
|
|
@ -196,10 +305,10 @@ async fn client_main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>
|
||||||
println!("queued");
|
println!("queued");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"notifications" => {
|
Command::Notifications { min_urgency } => {
|
||||||
let min: u8 = args
|
let min: u8 = min_urgency
|
||||||
.get(2)
|
.as_deref()
|
||||||
.and_then(|s| notify::parse_urgency(s))
|
.and_then(notify::parse_urgency)
|
||||||
.unwrap_or(255);
|
.unwrap_or(255);
|
||||||
|
|
||||||
let mut req = daemon.get_notifications_request();
|
let mut req = daemon.get_notifications_request();
|
||||||
|
|
@ -207,7 +316,6 @@ async fn client_main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>
|
||||||
let reply = req.send().promise.await?;
|
let reply = req.send().promise.await?;
|
||||||
let list = reply.get()?.get_notifications()?;
|
let list = reply.get()?.get_notifications()?;
|
||||||
|
|
||||||
if !list.is_empty() {
|
|
||||||
for n in list.iter() {
|
for n in list.iter() {
|
||||||
println!(
|
println!(
|
||||||
"[{}:{}] {}",
|
"[{}:{}] {}",
|
||||||
|
|
@ -217,8 +325,7 @@ async fn client_main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Command::NotifyTypes => {
|
||||||
"notify-types" => {
|
|
||||||
let reply = daemon.get_types_request().send().promise.await?;
|
let reply = daemon.get_types_request().send().promise.await?;
|
||||||
let list = reply.get()?.get_types()?;
|
let list = reply.get()?.get_types()?;
|
||||||
|
|
||||||
|
|
@ -240,31 +347,40 @@ async fn client_main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"notify-threshold" => {
|
Command::NotifyThreshold { ntype, level } => {
|
||||||
let ntype = args.get(2).ok_or("missing type")?;
|
let level = notify::parse_urgency(&level)
|
||||||
let level_str = args.get(3).ok_or("missing level")?;
|
.ok_or_else(|| format!("invalid level: {level}"))?;
|
||||||
let level = notify::parse_urgency(level_str)
|
|
||||||
.ok_or_else(|| format!("invalid level: {level_str}"))?;
|
|
||||||
|
|
||||||
let mut req = daemon.set_threshold_request();
|
let mut req = daemon.set_threshold_request();
|
||||||
req.get().set_type(ntype);
|
req.get().set_type(&ntype);
|
||||||
req.get().set_level(level);
|
req.get().set_level(level);
|
||||||
req.send().promise.await?;
|
req.send().promise.await?;
|
||||||
println!("{ntype} threshold={}", notify::urgency_name(level));
|
println!("{ntype} threshold={}", notify::urgency_name(level));
|
||||||
}
|
}
|
||||||
// Module commands: "irc join #foo", "telegram send hello"
|
Command::Irc { command, args } => {
|
||||||
"irc" | "telegram" => {
|
module_command(&daemon, "irc", &command, &args).await?;
|
||||||
let module = cmd;
|
}
|
||||||
let module_cmd = args.get(2).ok_or(
|
Command::Telegram { command, args } => {
|
||||||
format!("usage: poc-daemon {module} <command> [args...]"),
|
module_command(&daemon, "telegram", &command, &args).await?;
|
||||||
)?;
|
}
|
||||||
let module_args: Vec<&str> = args[3..].iter().map(|s| s.as_str()).collect();
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn module_command(
|
||||||
|
daemon: &daemon_capnp::daemon::Client,
|
||||||
|
module: &str,
|
||||||
|
command: &str,
|
||||||
|
args: &[String],
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut req = daemon.module_command_request();
|
let mut req = daemon.module_command_request();
|
||||||
req.get().set_module(module);
|
req.get().set_module(module);
|
||||||
req.get().set_command(module_cmd);
|
req.get().set_command(command);
|
||||||
let mut args_builder = req.get().init_args(module_args.len() as u32);
|
let mut args_builder = req.get().init_args(args.len() as u32);
|
||||||
for (i, a) in module_args.iter().enumerate() {
|
for (i, a) in args.iter().enumerate() {
|
||||||
args_builder.set(i as u32, a);
|
args_builder.set(i as u32, a);
|
||||||
}
|
}
|
||||||
let reply = req.send().promise.await?;
|
let reply = req.send().promise.await?;
|
||||||
|
|
@ -272,16 +388,7 @@ async fn client_main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>
|
||||||
if !result.is_empty() {
|
if !result.is_empty() {
|
||||||
println!("{result}");
|
println!("{result}");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
eprintln!("unknown command: {cmd}");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Server mode ──────────────────────────────────────────────────
|
// ── Server mode ──────────────────────────────────────────────────
|
||||||
|
|
@ -440,33 +547,14 @@ async fn server_main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let args: Vec<String> = std::env::args().collect();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
if args.len() < 2 {
|
match cli.command {
|
||||||
return server_main().await;
|
Some(Command::Daemon) => server_main().await,
|
||||||
}
|
Some(cmd) => client_main(cmd).await,
|
||||||
|
None => {
|
||||||
match args[1].as_str() {
|
Cli::parse_from(["poc-daemon", "--help"]);
|
||||||
"status" | "user" | "response" | "sleep" | "wake" | "quiet"
|
Ok(())
|
||||||
| "consolidating" | "consolidated" | "dream-start" | "dream-end"
|
|
||||||
| "stop" | "save" | "notify" | "notifications" | "notify-types"
|
|
||||||
| "notify-threshold" | "idle-timeout"
|
|
||||||
| "irc" | "telegram" => client_main(args).await,
|
|
||||||
_ => {
|
|
||||||
eprintln!("usage: poc-daemon [command]");
|
|
||||||
eprintln!(" (no args) Start daemon");
|
|
||||||
eprintln!(" status Query daemon status");
|
|
||||||
eprintln!(" user [pane] Signal user activity");
|
|
||||||
eprintln!(" response [pane] Signal Claude response");
|
|
||||||
eprintln!(" notify <type> <urgency> <message>");
|
|
||||||
eprintln!(" notifications [min_urgency]");
|
|
||||||
eprintln!(" notify-types List notification types");
|
|
||||||
eprintln!(" notify-threshold <type> <level>");
|
|
||||||
eprintln!(" sleep [timestamp]");
|
|
||||||
eprintln!(" save Force state persistence");
|
|
||||||
eprintln!(" wake / quiet / stop / dream-start / dream-end");
|
|
||||||
eprintln!(" irc <join|leave|send|status|log|nick> [args]");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,16 @@ impl daemon::Server for DaemonImpl {
|
||||||
Promise::ok(())
|
Promise::ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn notify_timeout(
|
||||||
|
&mut self,
|
||||||
|
params: daemon::NotifyTimeoutParams,
|
||||||
|
_results: daemon::NotifyTimeoutResults,
|
||||||
|
) -> Promise<(), capnp::Error> {
|
||||||
|
let secs = pry!(params.get()).get_seconds();
|
||||||
|
self.state.borrow_mut().handle_notify_timeout(secs);
|
||||||
|
Promise::ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn save(
|
fn save(
|
||||||
&mut self,
|
&mut self,
|
||||||
_params: daemon::SaveParams,
|
_params: daemon::SaveParams,
|
||||||
|
|
@ -196,6 +206,11 @@ impl daemon::Server for DaemonImpl {
|
||||||
notify::Activity::Sleeping => crate::daemon_capnp::Activity::Sleeping,
|
notify::Activity::Sleeping => crate::daemon_capnp::Activity::Sleeping,
|
||||||
});
|
});
|
||||||
status.set_pending_count(s.notifications.pending.len() as u32);
|
status.set_pending_count(s.notifications.pending.len() as u32);
|
||||||
|
status.set_idle_timeout(s.idle_timeout);
|
||||||
|
status.set_notify_timeout(s.notify_timeout);
|
||||||
|
status.set_since_activity(s.since_activity());
|
||||||
|
status.set_since_user(crate::now() - s.last_user_msg);
|
||||||
|
status.set_block_reason(s.block_reason());
|
||||||
|
|
||||||
Promise::ok(())
|
Promise::ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -679,7 +679,7 @@ After=default.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
ExecStart={exe}
|
ExecStart={exe} daemon
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=10
|
RestartSec=10
|
||||||
Environment=HOME={home}
|
Environment=HOME={home}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue