aboutsummaryrefslogtreecommitdiff
path: root/src/detect/agents/claude_code.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-02 21:20:28 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-02 21:25:32 +0300
commit295b09ca25edbccac65401ee84fa420dd3888aad (patch)
treed54ed875518e042869ed3f43e95f0b9ee59f5d34 /src/detect/agents/claude_code.rs
parent48641e253f1aa3886b470cbebec60103247a7622 (diff)
feat: make claude codex opencode hooks session-only
Diffstat (limited to 'src/detect/agents/claude_code.rs')
-rw-r--r--src/detect/agents/claude_code.rs64
1 files changed, 52 insertions, 12 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;