diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-03 03:34:10 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-03 03:34:10 +0300 |
| commit | 816cb88cd9df3cba305a50842ebf78db19279dd5 (patch) | |
| tree | 1dc8c444d3357669fecf590bcc13bfa52ceece3a /src | |
| parent | 8968a0a4a8e80761ae6c8dab9b912530f619479c (diff) | |
fix: avoid stale claude background wait detection
Diffstat (limited to 'src')
| -rw-r--r-- | src/detect/agents/claude_code.rs | 42 | ||||
| -rw-r--r-- | src/detect/mod.rs | 52 |
2 files changed, 88 insertions, 6 deletions
diff --git a/src/detect/agents/claude_code.rs b/src/detect/agents/claude_code.rs index 0918f5a2..ace946da 100644 --- a/src/detect/agents/claude_code.rs +++ b/src/detect/agents/claude_code.rs @@ -107,12 +107,42 @@ fn has_live_blocked_form(content: &str) -> bool { } fn has_background_agent_wait(content_above_prompt: &str) -> bool { - content_above_prompt.lines().rev().take(8).any(|line| { - let lower = line.to_lowercase(); - lower.contains("waiting for") - && lower.contains("background agent") - && lower.contains("to finish") - }) + let Some(line) = content_above_prompt + .lines() + .rev() + .find(|line| !line.trim().is_empty()) + else { + return false; + }; + + is_background_agent_wait_line(line) +} + +fn is_background_agent_wait_line(line: &str) -> bool { + let mut text = line.trim(); + if !text.starts_with("Waiting for ") && !text.starts_with("waiting for ") { + let mut chars = text.chars(); + let Some(first) = chars.next() else { + return false; + }; + if first.is_alphanumeric() { + return false; + } + text = chars.as_str().trim_start(); + } + + let lower = text.to_ascii_lowercase(); + let Some(rest) = lower.strip_prefix("waiting for ") else { + return false; + }; + let Some((count, rest)) = rest.split_once(' ') else { + return false; + }; + if count.parse::<u32>().ok().is_none_or(|count| count == 0) { + return false; + } + + rest == "background agent to finish" || rest == "background agents to finish" } fn has_claude_yes_no_choice(content: &str) -> bool { diff --git a/src/detect/mod.rs b/src/detect/mod.rs index a1084e06..58a73d7d 100644 --- a/src/detect/mod.rs +++ b/src/detect/mod.rs @@ -1121,6 +1121,58 @@ mod tests { } #[test] + fn claude_waiting_for_multiple_background_agents_is_working() { + let screen = "● Done. I’ve delegated two investigations.\n\n✻ Waiting for 2 background agents to finish\n\n─────────────────────────────────────────────────────────────────────────────────────────\n❯ \n─────────────────────────────────────────────────────────────────────────────────────────\n ~/P/herdr ⎇ master ▱▱▱▱▱ 0%"; + let detection = detect_agent(Some(Agent::Claude), screen); + + assert_eq!(detection.state, AgentState::Working); + assert!(detection.visible_working); + assert!(!detection.visible_idle); + } + + #[test] + fn claude_completed_background_agent_wait_in_scrollback_is_idle() { + let screen = "❯ please create a background agent that sleeps 15 sec then say hi\n\n Thought for 8s (ctrl+o to expand)\n\n● claude(Sleep then say hi)\n ⎿ Backgrounded agent (↓ to manage · ctrl+o to expand)\n\n● Done. I launched a background agent that will wait 15 seconds and then reply with hi.\n\n✻ Waiting for 1 background agent to finish\n\n● Agent \"Sleep then say hi\" completed · 17s\n\n● hi\n\n✻ Brewed for 28s\n\n─────────────────────────────────────────────────────────────────────────────────────────\n❯ \n─────────────────────────────────────────────────────────────────────────────────────────\n ~/P/herdr ⎇ master ▱▱▱▱▱ 0%"; + let detection = detect_agent(Some(Agent::Claude), screen); + + assert_eq!(detection.state, AgentState::Idle); + assert!(detection.visible_idle); + assert!(!detection.visible_working); + assert!(!detection.visible_blocker); + } + + #[test] + fn claude_completed_background_agent_with_prompt_box_is_idle() { + let screen = "● Done. I’ve started a background agent.\n\n✻ Waiting for 1 background agent to finish\n\n⏺ Agent \"Sleep then say hi\" completed · 31s\n\n⏺ hi\n\n✻ Baked for 42s\n\n─────────────────────────────────────────────────────────────────────────────────────────\n❯ \n─────────────────────────────────────────────────────────────────────────────────────────\n ~/P/herdr ⎇ master ▱▱▱▱▱ 0%\n\n ● main ↑/↓ to select · Enter to view\n ◯ general-purpose Sleep then say hi 31s"; + let detection = detect_agent(Some(Agent::Claude), screen); + + assert_eq!(detection.state, AgentState::Idle); + assert!(detection.visible_idle); + assert!(!detection.visible_working); + assert!(!detection.visible_blocker); + } + + #[test] + fn claude_zero_background_agent_wait_with_prompt_box_is_idle() { + let screen = "✻ Waiting for 0 background agents to finish\n\n─────────────────────────────────────────────────────────────────────────────────────────\n❯ \n─────────────────────────────────────────────────────────────────────────────────────────\n ~/P/herdr ⎇ master ▱▱▱▱▱ 0%"; + let detection = detect_agent(Some(Agent::Claude), screen); + + assert_eq!(detection.state, AgentState::Idle); + assert!(detection.visible_idle); + assert!(!detection.visible_working); + } + + #[test] + fn claude_background_agent_wait_phrase_in_prose_is_idle() { + let screen = "Claude mentioned: Waiting for 1 background agent to finish\n\n─────────────────────────────────────────────────────────────────────────────────────────\n❯ \n─────────────────────────────────────────────────────────────────────────────────────────\n ~/P/herdr ⎇ master ▱▱▱▱▱ 0%"; + let detection = detect_agent(Some(Agent::Claude), screen); + + assert_eq!(detection.state, AgentState::Idle); + assert!(detection.visible_idle); + assert!(!detection.visible_working); + } + + #[test] fn claude_idle_search() { let screen = "⌕ Search…\nsome content"; assert_eq!(detect_claude(screen), AgentState::Idle); |
