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
|
|
@ -13,7 +13,8 @@ use std::fs;
|
|||
use tracing::info;
|
||||
|
||||
// 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 DREAM_INTERVAL_HOURS: u64 = 18;
|
||||
|
||||
|
|
@ -30,6 +31,8 @@ struct Persisted {
|
|||
claude_pane: Option<String>,
|
||||
#[serde(default)]
|
||||
idle_timeout: f64,
|
||||
#[serde(default)]
|
||||
notify_timeout: f64,
|
||||
// Human-readable mirrors — written but not consumed on load
|
||||
#[serde(default, skip_deserializing)]
|
||||
last_user_msg_time: String,
|
||||
|
|
@ -75,6 +78,7 @@ pub struct State {
|
|||
pub dream_start: f64,
|
||||
pub fired: bool,
|
||||
pub idle_timeout: f64,
|
||||
pub notify_timeout: f64,
|
||||
#[serde(skip)]
|
||||
pub running: bool,
|
||||
#[serde(skip)]
|
||||
|
|
@ -95,7 +99,8 @@ impl State {
|
|||
dreaming: false,
|
||||
dream_start: 0.0,
|
||||
fired: false,
|
||||
idle_timeout: DEFAULT_PAUSE_SECS,
|
||||
idle_timeout: DEFAULT_IDLE_TIMEOUT,
|
||||
notify_timeout: DEFAULT_NOTIFY_TIMEOUT,
|
||||
running: true,
|
||||
start_time: now(),
|
||||
notifications: notify::NotifyState::new(),
|
||||
|
|
@ -112,6 +117,9 @@ impl State {
|
|||
if p.idle_timeout > 0.0 {
|
||||
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
|
||||
// user/response signal before allowing the idle timer
|
||||
self.fired = true;
|
||||
|
|
@ -140,6 +148,7 @@ impl State {
|
|||
saved_at: epoch_to_iso(now()),
|
||||
fired: self.fired,
|
||||
idle_timeout: self.idle_timeout,
|
||||
notify_timeout: self.notify_timeout,
|
||||
uptime: now() - self.start_time,
|
||||
};
|
||||
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 —
|
||||
// notifications will arrive via hook, no need to wake us
|
||||
let since_response = now() - self.last_response;
|
||||
if since_response < self.idle_timeout {
|
||||
if since_response < self.notify_timeout {
|
||||
return;
|
||||
}
|
||||
let effective = self.notifications.threshold_for(ntype);
|
||||
|
|
@ -195,6 +204,12 @@ impl State {
|
|||
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) {
|
||||
if until == 0.0 {
|
||||
self.sleep_until = Some(0.0);
|
||||
|
|
@ -223,23 +238,16 @@ impl State {
|
|||
(now() - self.last_user_msg) < SESSION_ACTIVE_SECS
|
||||
}
|
||||
|
||||
/// Full debug dump as JSON with computed values.
|
||||
pub fn debug_json(&self) -> String {
|
||||
let t = now();
|
||||
/// Seconds since the most recent of user message or response.
|
||||
pub fn since_activity(&self) -> f64 {
|
||||
let reference = self.last_response.max(self.last_user_msg);
|
||||
let since_user = t - self.last_user_msg;
|
||||
let since_response = t - self.last_response;
|
||||
let since_reference = if reference > 0.0 { t - reference } else { 0.0 };
|
||||
if reference > 0.0 { now() - reference } else { 0.0 }
|
||||
}
|
||||
|
||||
let would_fire = !self.fired
|
||||
&& self.sleep_until.is_none()
|
||||
&& t >= self.quiet_until
|
||||
&& !self.consolidating
|
||||
&& !self.dreaming
|
||||
&& reference > 0.0
|
||||
&& since_reference >= self.idle_timeout;
|
||||
|
||||
let block_reason = if self.fired {
|
||||
/// Why the idle timer hasn't fired (or "none" if it would fire now).
|
||||
pub fn block_reason(&self) -> &'static str {
|
||||
let t = now();
|
||||
if self.fired {
|
||||
"already fired"
|
||||
} else if self.sleep_until.is_some() {
|
||||
"sleeping"
|
||||
|
|
@ -249,30 +257,37 @@ impl State {
|
|||
"consolidating"
|
||||
} else if self.dreaming {
|
||||
"dreaming"
|
||||
} else if reference == 0.0 {
|
||||
} else if self.last_response.max(self.last_user_msg) == 0.0 {
|
||||
"no activity yet"
|
||||
} else if since_reference < self.idle_timeout {
|
||||
} else if self.since_activity() < self.idle_timeout {
|
||||
"not idle long enough"
|
||||
} else {
|
||||
"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!({
|
||||
"now": t,
|
||||
"uptime": t - self.start_time,
|
||||
"idle_timeout": self.idle_timeout,
|
||||
"notify_timeout": self.notify_timeout,
|
||||
"last_user_msg": self.last_user_msg,
|
||||
"last_user_msg_ago": since_user,
|
||||
"last_user_msg_time": epoch_to_iso(self.last_user_msg),
|
||||
"last_response": self.last_response,
|
||||
"last_response_ago": since_response,
|
||||
"last_response_time": epoch_to_iso(self.last_response),
|
||||
"reference_ago": since_reference,
|
||||
"since_activity": self.since_activity(),
|
||||
"kent_present": self.kent_present(),
|
||||
"claude_pane": self.claude_pane,
|
||||
"fired": self.fired,
|
||||
"would_fire": would_fire,
|
||||
"block_reason": block_reason,
|
||||
"block_reason": self.block_reason(),
|
||||
"sleep_until": self.sleep_until,
|
||||
"quiet_until": self.quiet_until,
|
||||
"consolidating": self.consolidating,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue