diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-02 21:20:28 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-02 21:25:32 +0300 |
| commit | 295b09ca25edbccac65401ee84fa420dd3888aad (patch) | |
| tree | d54ed875518e042869ed3f43e95f0b9ee59f5d34 /src/detect/agents | |
| parent | 48641e253f1aa3886b470cbebec60103247a7622 (diff) | |
feat: make claude codex opencode hooks session-only
Diffstat (limited to 'src/detect/agents')
| -rw-r--r-- | src/detect/agents/claude_code.rs | 64 | ||||
| -rw-r--r-- | src/detect/agents/opencode.rs | 31 |
2 files changed, 82 insertions, 13 deletions
diff --git a/src/detect/agents/claude_code.rs b/src/detect/agents/claude_code.rs index e6633eda..819dadf4 100644 --- a/src/detect/agents/claude_code.rs +++ b/src/detect/agents/claude_code.rs @@ -23,30 +23,35 @@ pub(super) fn detect(content: &str) -> AgentState { return AgentState::Idle; } - // --- Blocked detection (full content including prompt box) --- - - if has_claude_blocked_prompt(content, &lower) { + if has_live_blocked_form(content) { return AgentState::Blocked; } - // --- Working detection (content above the prompt box) --- - if has_working_chrome(content) { return AgentState::Working; } + if !has_prompt_box(content) && has_claude_blocked_prompt(content, &lower) { + return AgentState::Blocked; + } + + if has_prompt_box(content) { + return AgentState::Idle; + } + AgentState::Idle } pub(super) fn has_visible_blocker(content: &str) -> bool { let lower = content.to_lowercase(); - lower.contains("do you want to proceed?") - && has_claude_yes_no_choice(content) - && (lower.contains("bash command") - || lower.contains("bash(") - || lower.contains("contains expansion") - || lower.contains("tab to amend") - || lower.contains("ctrl+e to explain")) + has_live_blocked_form(content) + || lower.contains("do you want to proceed?") + && has_claude_yes_no_choice(content) + && (lower.contains("bash command") + || lower.contains("bash(") + || lower.contains("contains expansion") + || lower.contains("tab to amend") + || lower.contains("ctrl+e to explain")) } pub(super) fn has_working_chrome(content: &str) -> bool { @@ -54,6 +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_spinner_activity(above) } @@ -86,6 +92,26 @@ fn has_claude_blocked_prompt(content: &str, lower_content: &str) -> bool { || (has_selection_prompt(content) && has_claude_yes_no_choice(content)) } +fn has_live_blocked_form(content: &str) -> bool { + let region = content_after_last_horizontal_rule(content); + region.lines().any(|line| { + let lower = line.to_lowercase(); + lower.contains("enter to select") + && lower.contains("esc to cancel") + && (lower.contains("tab/arrow keys to navigate") + || lower.contains("arrow keys to navigate")) + }) +} + +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") + }) +} + fn has_claude_yes_no_choice(content: &str) -> bool { content.lines().any(|line| { let trimmed = line @@ -140,6 +166,20 @@ pub(in crate::detect) fn content_above_prompt_box(content: &str) -> &str { content } +fn content_after_last_horizontal_rule(content: &str) -> &str { + let mut last_rule_end = 0usize; + let mut offset = 0usize; + for line in content.lines() { + let next_offset = offset + line.len() + 1; + if is_horizontal_rule(line) { + last_rule_end = next_offset.min(content.len()); + } + offset = next_offset; + } + + &content[last_rule_end..] +} + fn claude_prompt_box_top_border_index(lines: &[&str]) -> Option<usize> { let mut border_count = 0; diff --git a/src/detect/agents/opencode.rs b/src/detect/agents/opencode.rs index 5badcad9..219f69ed 100644 --- a/src/detect/agents/opencode.rs +++ b/src/detect/agents/opencode.rs @@ -7,7 +7,10 @@ pub(super) fn detect(content: &str) -> AgentState { } // Working - if has_interrupt_pattern(&content.to_lowercase()) { + if has_interrupt_pattern(&content.to_lowercase()) + || has_opencode_interrupt_footer(content) + || has_opencode_progress_run(content) + { return AgentState::Working; } @@ -23,3 +26,29 @@ fn has_opencode_question_prompt(content: &str) -> bool { lower.contains("esc dismiss") && has_enter_action && has_question_nav } + +fn has_opencode_interrupt_footer(content: &str) -> bool { + content.lines().any(|line| { + let lower = line.to_lowercase(); + if !(lower.contains("esc interrupt") || lower.contains("esc again to interrupt")) { + return false; + } + + lower.contains("opencode") + }) +} + +fn has_opencode_progress_run(line: &str) -> bool { + let mut run = 0usize; + for ch in line.chars() { + if matches!(ch, '■' | '⬝') { + run += 1; + if run >= 4 { + return true; + } + } else { + run = 0; + } + } + false +} |
