From fde8a6304e6d5dfd77e8462e492a3e3a47448902 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:35:36 +0800 Subject: [PATCH] fix(core): don't start a turn from a notification's waiting status Before an agent's first hook, a desktop notification marks its pane waiting. Crush raises one after every turn ("Agent's turn completed"), and its only hook, PreToolUse, is reported as a finished tool, which moves a waiting pane back to working: from the first tool of a session on, the pane read as working for good, as Crush has no Stop to end it. A status set by a notification now gives way to idle when the first hook report arrives, so the report only records the session and its activity. --- crates/tty7-core/src/core/cli_agent.rs | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/crates/tty7-core/src/core/cli_agent.rs b/crates/tty7-core/src/core/cli_agent.rs index b40d7917..39b37313 100644 --- a/crates/tty7-core/src/core/cli_agent.rs +++ b/crates/tty7-core/src/core/cli_agent.rs @@ -1122,6 +1122,14 @@ impl AgentSessionState { } pub fn apply_event(&mut self, ev: &AgentEvent) { + // A status set before the first hook came from a desktop notification + // — Crush's "turn completed", say — which says the pane wants a look, + // not that a question is open. Taken for one, the first tool report + // turned it into a turn running, and nothing would ever end it. + if !self.rich && self.status == AgentStatus::Waiting { + self.status = AgentStatus::Idle; + self.message = None; + } self.rich = true; let guessed = std::mem::take(&mut self.inferred); if let Some(id) = &ev.session_id { @@ -1809,6 +1817,29 @@ mod tests { assert_eq!(s.session_id.as_deref(), Some("sid-1")); } + /// A desktop notification before any hook marks the pane waiting; the + /// first hook report must not read that as a question being answered. + #[test] + fn a_hook_after_a_notification_does_not_start_a_turn() { + let mut s = AgentSessionState { + status: AgentStatus::Waiting, + message: Some("Agent's turn completed".into()), + ..Default::default() + }; + s.apply_event(&AgentEvent { + agent: Some(CLIAgent::Crush), + kind: AgentEventKind::ToolComplete, + session_id: Some("sid".into()), + message: None, + cwd: None, + readout: Default::default(), + }); + assert_eq!(s.status, AgentStatus::Idle); + assert_eq!(s.message, None); + assert!(s.rich); + assert_eq!(s.activity, 1); + } + #[test] fn tool_completions_count_even_when_the_status_holds_still() { let ev = |kind| AgentEvent {