diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-03 19:34:39 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-03 19:34:39 +0300 |
| commit | 03208d254e948e3926594647f75da44a12920af2 (patch) | |
| tree | 94491fff8814ed3bd36491445fa67279e24208be /src | |
| parent | 5c86f0660f538553850e2c8fb0493372a48cb511 (diff) | |
fix: detect claude running shell status
Diffstat (limited to 'src')
| -rw-r--r-- | src/detect/agents/claude_code.rs | 85 |
1 files changed, 82 insertions, 3 deletions
diff --git a/src/detect/agents/claude_code.rs b/src/detect/agents/claude_code.rs index d2f12552..910eb679 100644 --- a/src/detect/agents/claude_code.rs +++ b/src/detect/agents/claude_code.rs @@ -59,7 +59,7 @@ pub(super) fn has_working_chrome(content: &str) -> bool { let above_lower = above.to_lowercase(); above_lower.contains("esc to interrupt") || above_lower.contains("ctrl+c to interrupt") - || has_background_agent_wait(above) + || has_running_status_line(above) || has_spinner_activity(above) } @@ -119,7 +119,7 @@ fn has_live_blocked_form(content: &str) -> bool { }) } -fn has_background_agent_wait(content_above_prompt: &str) -> bool { +fn has_running_status_line(content_above_prompt: &str) -> bool { let Some(line) = content_above_prompt .lines() .rev() @@ -128,7 +128,7 @@ fn has_background_agent_wait(content_above_prompt: &str) -> bool { return false; }; - is_background_agent_wait_line(line) + is_background_agent_wait_line(line) || is_still_running_status_line(line) } fn is_background_agent_wait_line(line: &str) -> bool { @@ -158,6 +158,36 @@ fn is_background_agent_wait_line(line: &str) -> bool { rest == "background agent to finish" || rest == "background agents to finish" } +fn is_still_running_status_line(line: &str) -> bool { + let lower = line.to_ascii_lowercase(); + let words: Vec<&str> = lower.split_whitespace().collect(); + + for (index, word) in words.iter().enumerate() { + let Ok(count) = word.parse::<u32>() else { + continue; + }; + if count == 0 { + continue; + } + + if matches!( + words.get(index + 1..index + 4), + Some(["shell" | "shells", "still", "running"]) + ) { + return true; + } + + if matches!( + words.get(index + 1..index + 5), + Some(["local", "agent" | "agents", "still", "running"]) + ) { + return true; + } + } + + false +} + fn has_claude_yes_no_choice(content: &str) -> bool { content.lines().any(|line| { let trimmed = line @@ -289,3 +319,52 @@ fn transcript_control_tail(line: &str) -> bool { || lower.contains("collapse") || lower.contains("verbose") } + +#[cfg(test)] +mod tests { + use super::*; + + fn prompt_box_below(content_above_prompt: &str) -> String { + format!( + "{content_above_prompt}\n────────────────────────────────\n❯ \n────────────────────────────────\n" + ) + } + + #[test] + fn shell_still_running_status_line_is_working() { + let content = prompt_box_below( + "● Started. I'll tell you when it finishes.\n\n✻ Crunched for 7s · 1 shell still running", + ); + + assert_eq!(detect(&content), AgentState::Working); + assert!(has_working_chrome(&content)); + } + + #[test] + fn local_agent_still_running_status_line_is_working() { + let content = prompt_box_below( + "● Hey. What do you want to work on?\n\n✻ Worked for 4s · 2 local agents still running", + ); + + assert_eq!(detect(&content), AgentState::Working); + assert!(has_working_chrome(&content)); + } + + #[test] + fn lower_agent_picker_shell_count_is_not_working_chrome() { + let content = prompt_box_below(" ~/P/herdr ⎇ master ▱▱▱▱▱ 0%\n 1 shell · ← for agents"); + + assert_eq!(detect(&content), AgentState::Idle); + assert!(!has_working_chrome(&content)); + } + + #[test] + fn stale_shell_running_line_above_newer_output_is_not_working_chrome() { + let content = prompt_box_below( + "● Started. I'll tell you when it finishes.\n\n✻ Crunched for 7s · 1 shell still running\n\n● hi", + ); + + assert_eq!(detect(&content), AgentState::Idle); + assert!(!has_working_chrome(&content)); + } +} |
