diff options
| author | Igor Soarez <igor@soarez.org> | 2026-11-05 19:21:43 +0200 |
|---|---|---|
| committer | Igor Soarez <igor@soarez.org> | 2026-11-05 19:21:43 +0200 |
| commit | 5842b093cc502b46076423e4f8efdbbc568f86aa (patch) | |
| tree | 35b5a2ab971a2b9abb3188e56e0416033b4f13ea /crates/dinggerd/src/hooks.rs | |
| parent | 3b7b7ed091bae3b0609c38b2d445201cc4b7a026 (diff) | |
pi: a failed run says so: a 'failed' alert with the error, and the error in red until seen
Diffstat (limited to 'crates/dinggerd/src/hooks.rs')
| -rw-r--r-- | crates/dinggerd/src/hooks.rs | 44 |
1 files changed, 41 insertions, 3 deletions
diff --git a/crates/dinggerd/src/hooks.rs b/crates/dinggerd/src/hooks.rs index 709f62b..aeb3285 100644 --- a/crates/dinggerd/src/hooks.rs +++ b/crates/dinggerd/src/hooks.rs @@ -29,6 +29,8 @@ //! //! An idle report may say the run was `aborted` (cancelled, as with //! Escape): the agent is idle, not done, and nobody is told it finished. +//! Or that it failed, with the `error` as one readable line: the agent is +//! done, and the alert says it failed and why. //! //! It also carries the `draft` in the agent's input box ("" for none), for //! marking a draft you left behind; never logged. And `context`: how full @@ -81,6 +83,8 @@ struct PaneReports { draft: Option<String>, /// How full its context window is: whole percent ("" when unknown). context: Option<String>, + /// The error that ended its last failed run, as one line. + error: Option<String>, /// When the last report arrived. at: Option<Instant>, } @@ -183,6 +187,8 @@ struct HookParams { context: Option<String>, #[serde(default)] aborted: Option<bool>, + #[serde(default)] + error: Option<String>, } fn handle_stream(stream: UnixStream) { @@ -252,6 +258,7 @@ fn handle_stream(stream: UnixStream) { draft: params.draft, context: params.context, aborted: params.aborted == Some(true), + error: params.error, }; record(params.pane_id, state, reported); wake(); @@ -269,6 +276,8 @@ struct Reported { context: Option<String>, /// The run this report ends was cancelled. aborted: bool, + /// The error the run this report ends failed with. + error: Option<String>, } /// A reported state, and whether it ends a run the user cancelled. @@ -277,11 +286,13 @@ pub struct Observation { pub state: AgentState, /// Idle after a cancelled run: not done, and no "finished" alert. pub cancelled: bool, + /// Idle after a failed run: done, with a "failed" alert (see [`error`]). + pub failed: bool, } impl Observation { pub fn plain(state: AgentState) -> Self { - Observation { state, cancelled: false } + Observation { state, cancelled: false, failed: false } } } @@ -311,7 +322,12 @@ fn record(pane_id: String, state: Option<AgentState>, reported: Reported) { pane.at = Some(Instant::now()); if let Some(state) = state { pane.last = Some(state); - pane.pending.push(Observation { state, cancelled: reported.aborted && state == AgentState::Idle }); + let idle = state == AgentState::Idle; + let error = reported.error.as_deref().map(draft_line).filter(|error| idle && !error.is_empty()); + pane.pending.push(Observation { state, cancelled: reported.aborted && idle, failed: error.is_some() }); + if error.is_some() { + pane.error = error; + } } if let Some(session) = option_value(reported.session) { pane.session = Some(session); @@ -342,6 +358,11 @@ fn context_percent(value: &str) -> String { } } +/// The error the pane's agent's last failed run ended with. +pub fn error(pane_id: &str) -> Option<String> { + reports().get(pane_id).and_then(|pane| pane.error.clone()) +} + /// How full the pane's agent's context window is ("" when unknown), if its /// extension reports it. pub fn context(pane_id: &str) -> Option<String> { @@ -497,7 +518,7 @@ mod tests { take_observations("%t7"), Some(vec![ Observation::plain(AgentState::Working), - Observation { state: AgentState::Idle, cancelled: true }, + Observation { state: AgentState::Idle, cancelled: true, failed: false }, ]), "only an idle report ends a run" ); @@ -507,6 +528,23 @@ mod tests { } #[test] + fn a_failed_run_ends_in_a_failed_idle_observation_with_its_error() { + let failed = |error: &str| Reported { error: Some(error.into()), ..Default::default() }; + record("%t8".into(), Some(AgentState::Working), failed("not the end of a run")); + record("%t8".into(), Some(AgentState::Idle), failed("401: invalid\nx-api-key")); + assert_eq!( + take_observations("%t8"), + Some(vec![ + Observation::plain(AgentState::Working), + Observation { state: AgentState::Idle, cancelled: false, failed: true }, + ]) + ); + assert_eq!(super::error("%t8").as_deref(), Some("401: invalid x-api-key"), "one line"); + record("%t8".into(), Some(AgentState::Idle), failed("")); + assert_eq!(take_observations("%t8"), Some(vec![Observation::plain(AgentState::Idle)]), "no error, no failure"); + } + + #[test] fn context_use_is_kept_as_whole_percent_until_reported_again() { let context = |text: &str| Reported { context: Some(text.into()), ..Default::default() }; assert_eq!(super::context("%t6"), None, "an extension that does not report it"); |
