diff options
Diffstat (limited to 'crates/dinggerd/src/hooks.rs')
| -rw-r--r-- | crates/dinggerd/src/hooks.rs | 46 |
1 files changed, 40 insertions, 6 deletions
diff --git a/crates/dinggerd/src/hooks.rs b/crates/dinggerd/src/hooks.rs index c467c98..8b24659 100644 --- a/crates/dinggerd/src/hooks.rs +++ b/crates/dinggerd/src/hooks.rs @@ -10,6 +10,9 @@ //! or `agent_session_id`, herdr's names), which restore uses to resume it. //! A report without a state only records the session (Claude's //! SessionStart hook, `dingger hook claude`); state stays with the screen. +//! A report may also carry the agent's `importance` (`low`, `normal`, +//! `high`): pi's extension sends it with every report, `dingger importance` +//! alone. //! //! Reports are state-change *events*: the watcher applies every one in //! order, so a turn that starts and finishes between two polls is still a @@ -29,6 +32,8 @@ use std::sync::{Condvar, Mutex, MutexGuard, OnceLock}; use std::time::{Duration, Instant}; use detect_core::AgentState; + +use crate::importance::Importance; use serde::Deserialize; use tracing::{debug, info, warn}; @@ -47,6 +52,8 @@ struct PaneReports { pending: Vec<AgentState>, /// The agent's latest session reference. session: Option<String>, + /// The agent's importance, if reported. + importance: Option<Importance>, /// When the last report arrived. at: Option<Instant>, } @@ -111,6 +118,8 @@ struct HookParams { agent_session_path: Option<String>, #[serde(default)] agent_session_id: Option<String>, + #[serde(default)] + importance: Option<String>, } fn handle_stream(stream: UnixStream) { @@ -137,16 +146,23 @@ fn handle_stream(stream: UnixStream) { continue; } }; + let importance = params.importance.as_deref().and_then(|value| { + let level = Importance::parse(value); + if level.is_none() { + debug!(importance = value, "unknown importance"); + } + level + }); match state { - Some(state) => info!(pane = %params.pane_id, ?state, "hook report"), - None => info!(pane = %params.pane_id, "hook session report"), + Some(state) => info!(pane = %params.pane_id, ?state, ?importance, "hook report"), + None => info!(pane = %params.pane_id, ?importance, "hook report without state"), } - record(params.pane_id, state, params.agent_session_path.or(params.agent_session_id)); + record(params.pane_id, state, params.agent_session_path.or(params.agent_session_id), importance); wake(); } } -fn record(pane_id: String, state: Option<AgentState>, session: Option<String>) { +fn record(pane_id: String, state: Option<AgentState>, session: Option<String>, importance: Option<Importance>) { let mut reports = reports(); let pane = reports.entry(pane_id).or_default(); pane.at = Some(Instant::now()); @@ -157,6 +173,9 @@ fn record(pane_id: String, state: Option<AgentState>, session: Option<String>) { if let Some(session) = session.filter(|s| !s.is_empty() && !s.chars().any(char::is_control)) { pane.session = Some(session); } + if importance.is_some() { + pane.importance = importance; + } } /// Start listening; reports accumulate for the watcher to consume. @@ -204,6 +223,11 @@ pub fn session(pane_id: &str) -> Option<String> { reports().get(pane_id).and_then(|pane| pane.session.clone()) } +/// The pane's agent importance, if reported. +pub fn importance(pane_id: &str) -> Option<Importance> { + reports().get(pane_id).and_then(|pane| pane.importance) +} + /// Drop a pane's reports: the agent was not running when the panes were /// listed at `listed`. A report since then is kept: the agent started /// after the listing (Claude reports its session once, as it starts). @@ -225,7 +249,7 @@ mod tests { #[test] fn a_session_report_records_only_the_session() { - record("%t1".into(), None, Some("/p/s.jsonl".into())); + record("%t1".into(), None, Some("/p/s.jsonl".into()), None); assert_eq!(session("%t1").as_deref(), Some("/p/s.jsonl")); assert_eq!(take_observations("%t1"), None, "state stays with the screen"); assert_eq!(last_state("%t1"), None); @@ -234,10 +258,20 @@ mod tests { #[test] fn a_report_after_the_pane_listing_is_not_dropped() { let listed = Instant::now(); - record("%t2".into(), None, Some("/p/s.jsonl".into())); + record("%t2".into(), None, Some("/p/s.jsonl".into()), None); drop_reports("%t2", listed); assert!(session("%t2").is_some(), "the agent started after the listing"); drop_reports("%t2", Instant::now()); assert_eq!(session("%t2"), None, "listed since, without the agent: dropped"); } + + #[test] + fn importance_is_kept_until_reported_again() { + record("%t3".into(), Some(AgentState::Working), None, Some(Importance::High)); + record("%t3".into(), Some(AgentState::Idle), None, None); + assert_eq!(importance("%t3"), Some(Importance::High), "a report without it keeps it"); + record("%t3".into(), None, None, Some(Importance::Normal)); + assert_eq!(importance("%t3"), Some(Importance::Normal)); + assert_eq!(last_state("%t3"), Some(AgentState::Idle), "a report without state keeps the state"); + } } |
