diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-09 04:24:12 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-09 04:26:19 +0300 |
| commit | eeacb45b4b6ab4446c5bea1331f743fee5e5b9f8 (patch) | |
| tree | 063e99ff0a40ce7ef00002ac00130aeb3f30f6d7 /src/detect/agents/claude_code.rs | |
| parent | 120001b591e64d80a557d81e3b112460b9987fc6 (diff) | |
fix: remove idle screen patterns from agent detection
Diffstat (limited to 'src/detect/agents/claude_code.rs')
| -rw-r--r-- | src/detect/agents/claude_code.rs | 77 |
1 files changed, 54 insertions, 23 deletions
diff --git a/src/detect/agents/claude_code.rs b/src/detect/agents/claude_code.rs index 1336c480..b159fede 100644 --- a/src/detect/agents/claude_code.rs +++ b/src/detect/agents/claude_code.rs @@ -12,17 +12,6 @@ use super::super::{has_confirmation_prompt, has_selection_prompt, AgentState}; pub(super) fn detect(content: &str) -> AgentState { let lower = content.to_lowercase(); - // Search prompt is always idle - if content.contains("⌕ Search…") { - return AgentState::Idle; - } - - // ctrl+r toggle — don't change state - // (we return Idle as a safe default since we don't have previous state here) - if lower.contains("ctrl+r to toggle") { - return AgentState::Idle; - } - if has_live_blocked_form(content) { return AgentState::Blocked; } @@ -39,14 +28,14 @@ pub(super) fn detect(content: &str) -> AgentState { return AgentState::Blocked; } - if has_prompt_box(content) { - return AgentState::Idle; - } - AgentState::Idle } pub(super) fn has_visible_blocker(content: &str) -> bool { + if has_live_input_prompt_box(content) { + return false; + } + let lower = content.to_lowercase(); has_live_blocked_form(content) || has_dynamic_workflow_prompt(&lower) @@ -106,15 +95,57 @@ pub(super) fn is_transcript_viewer(content: &str) -> bool { } pub(super) fn has_prompt_box(content: &str) -> bool { - let lines: Vec<&str> = content.lines().collect(); - let Some(top_border_index) = claude_prompt_box_top_border_index(&lines) else { - return false; - }; + prompt_box_body_lines(content) + .is_some_and(|lines| lines.iter().any(|line| line.trim_start().starts_with('❯'))) +} - lines[top_border_index + 1..] - .iter() - .take_while(|line| !is_horizontal_rule(line)) - .any(|line| line.trim_start().starts_with('❯')) +fn has_live_input_prompt_box(content: &str) -> bool { + prompt_box_body_lines(content).is_some_and(|lines| { + let non_empty: Vec<&str> = lines + .iter() + .map(|line| line.trim()) + .filter(|line| !line.is_empty()) + .collect(); + let Some(first) = non_empty.first() else { + return false; + }; + first.trim_start().starts_with('❯') + && !non_empty + .iter() + .any(|line| claude_prompt_box_line_is_selector_chrome(line)) + }) +} + +fn claude_prompt_box_line_is_selector_chrome(line: &str) -> bool { + let trimmed = line.trim().trim_start_matches('❯').trim_start(); + let lower = trimmed.to_ascii_lowercase(); + + lower.contains("enter to select") + || lower.contains("enter to confirm") + || lower.contains("enter to submit") + || lower.contains("esc to cancel") + || lower.contains("tab/arrow") + || lower.contains("arrow keys") + || lower.contains("↑/↓") + || lower.contains("↑↓") + || lower.contains("ctrl+g to edit") + || lower.contains("ctrl+e to explain") + || trimmed + .split_once('.') + .is_some_and(|(prefix, _)| prefix.trim().parse::<u32>().is_ok()) +} + +fn prompt_box_body_lines(content: &str) -> Option<Vec<&str>> { + let lines: Vec<&str> = content.lines().collect(); + let top_border_index = claude_prompt_box_top_border_index(&lines)?; + + Some( + lines[top_border_index + 1..] + .iter() + .take_while(|line| !is_horizontal_rule(line)) + .copied() + .collect(), + ) } /// Claude uses the same generic Select and Dialog widgets for both |
