aboutsummaryrefslogtreecommitdiff
path: root/src/detect/agents
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-08 20:55:51 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-09 04:26:19 +0300
commitee51c5e5fbaea694e65bb0a4908f75bcaf9707d1 (patch)
treef566d2a5921d4112602a7abcffb5cf6eb04d5f24 /src/detect/agents
parenta7cd7806f2fa0251e2dca41ffec61ceafb6f0e18 (diff)
feat: use pty-first agent detection
Diffstat (limited to 'src/detect/agents')
-rw-r--r--src/detect/agents/amp.rs23
-rw-r--r--src/detect/agents/antigravity.rs15
-rw-r--r--src/detect/agents/claude_code.rs10
-rw-r--r--src/detect/agents/cline.rs14
-rw-r--r--src/detect/agents/codex.rs4
-rw-r--r--src/detect/agents/cursor.rs10
-rw-r--r--src/detect/agents/droid.rs27
-rw-r--r--src/detect/agents/gemini.rs35
-rw-r--r--src/detect/agents/github_copilot.rs14
-rw-r--r--src/detect/agents/grok.rs17
-rw-r--r--src/detect/agents/hermes.rs22
-rw-r--r--src/detect/agents/kilo.rs4
-rw-r--r--src/detect/agents/kimi.rs50
-rw-r--r--src/detect/agents/kiro.rs7
-rw-r--r--src/detect/agents/mod.rs28
-rw-r--r--src/detect/agents/opencode.rs6
-rw-r--r--src/detect/agents/qodercli.rs14
17 files changed, 149 insertions, 151 deletions
diff --git a/src/detect/agents/amp.rs b/src/detect/agents/amp.rs
index ad76ca27..b1fcba4d 100644
--- a/src/detect/agents/amp.rs
+++ b/src/detect/agents/amp.rs
@@ -17,6 +17,19 @@ use super::super::AgentState;
pub(super) fn detect(content: &str) -> AgentState {
let lower = content.to_lowercase();
+ if has_visible_blocker(content) {
+ return AgentState::Blocked;
+ }
+
+ if lower.contains("esc to cancel") {
+ return AgentState::Working;
+ }
+
+ AgentState::Idle
+}
+
+pub(super) fn has_visible_blocker(content: &str) -> bool {
+ let lower = content.to_lowercase();
let has_waiting_for_approval = lower.contains("waiting for approval");
let has_approval_header = lower.contains("invoke tool")
|| lower.contains("run this command?")
@@ -29,13 +42,5 @@ pub(super) fn detect(content: &str) -> AgentState {
|| lower.contains("allow file for every session")
|| lower.contains("deny with feedback"));
- if has_approval_actions && (has_waiting_for_approval || has_approval_header) {
- return AgentState::Blocked;
- }
-
- if lower.contains("esc to cancel") {
- return AgentState::Working;
- }
-
- AgentState::Idle
+ has_approval_actions && (has_waiting_for_approval || has_approval_header)
}
diff --git a/src/detect/agents/antigravity.rs b/src/detect/agents/antigravity.rs
index b504a96a..9790c5a4 100644
--- a/src/detect/agents/antigravity.rs
+++ b/src/detect/agents/antigravity.rs
@@ -1,12 +1,7 @@
use super::super::AgentState;
pub(super) fn detect(content: &str) -> AgentState {
- let lower = content.to_lowercase();
-
- let has_permission_request = lower.contains("requesting permission for:");
- let has_permission_question = lower.contains("do you want to proceed?");
- let has_permission_controls = lower.contains("tab amend") && lower.contains("edit command");
- if has_permission_request && (has_permission_question || has_permission_controls) {
+ if has_visible_blocker(content) {
return AgentState::Blocked;
}
@@ -17,6 +12,14 @@ pub(super) fn detect(content: &str) -> AgentState {
AgentState::Idle
}
+pub(super) fn has_visible_blocker(content: &str) -> bool {
+ let lower = content.to_lowercase();
+ let has_permission_request = lower.contains("requesting permission for:");
+ let has_permission_question = lower.contains("do you want to proceed?");
+ let has_permission_controls = lower.contains("tab amend") && lower.contains("edit command");
+ has_permission_request && (has_permission_question || has_permission_controls)
+}
+
fn has_antigravity_spinner(content: &str) -> bool {
content.lines().any(|line| {
let trimmed = line.trim_start();
diff --git a/src/detect/agents/claude_code.rs b/src/detect/agents/claude_code.rs
index 910eb679..a13019a4 100644
--- a/src/detect/agents/claude_code.rs
+++ b/src/detect/agents/claude_code.rs
@@ -27,6 +27,10 @@ pub(super) fn detect(content: &str) -> AgentState {
return AgentState::Blocked;
}
+ if has_dynamic_workflow_prompt(&lower) {
+ return AgentState::Blocked;
+ }
+
if has_working_chrome(content) {
return AgentState::Working;
}
@@ -45,6 +49,7 @@ pub(super) fn detect(content: &str) -> AgentState {
pub(super) fn has_visible_blocker(content: &str) -> bool {
let lower = content.to_lowercase();
has_live_blocked_form(content)
+ || has_dynamic_workflow_prompt(&lower)
|| lower.contains("do you want to proceed?")
&& has_claude_yes_no_choice(content)
&& (lower.contains("bash command")
@@ -96,6 +101,7 @@ fn has_claude_blocked_prompt(content: &str, lower_content: &str) -> bool {
has_confirmation_prompt(lower_content)
|| lower_content.contains("do you want to proceed?")
|| lower_content.contains("would you like to proceed?")
+ || has_dynamic_workflow_prompt(lower_content)
|| lower_content.contains("waiting for permission")
|| lower_content.contains("do you want to allow this connection?")
|| lower_content.contains("tab to amend")
@@ -105,6 +111,10 @@ fn has_claude_blocked_prompt(content: &str, lower_content: &str) -> bool {
|| (has_selection_prompt(content) && has_claude_yes_no_choice(content))
}
+fn has_dynamic_workflow_prompt(lower_content: &str) -> bool {
+ lower_content.contains("run a dynamic workflow?") && lower_content.contains("esc to cancel")
+}
+
fn has_live_blocked_form(content: &str) -> bool {
let region = content_after_last_horizontal_rule(content);
region.lines().any(|line| {
diff --git a/src/detect/agents/cline.rs b/src/detect/agents/cline.rs
index 86b5ce01..13509606 100644
--- a/src/detect/agents/cline.rs
+++ b/src/detect/agents/cline.rs
@@ -4,11 +4,7 @@ pub(super) fn detect(content: &str) -> AgentState {
let lower = content.to_lowercase();
// Blocked
- if lower.contains("let cline use this tool") {
- return AgentState::Blocked;
- }
- // [act mode] or [plan mode] followed by "yes"
- if (lower.contains("[act mode]") || lower.contains("[plan mode]")) && lower.contains("yes") {
+ if has_visible_blocker(content) {
return AgentState::Blocked;
}
@@ -20,3 +16,11 @@ pub(super) fn detect(content: &str) -> AgentState {
// Cline defaults to working (unlike most agents that default to idle)
AgentState::Working
}
+
+pub(super) fn has_visible_blocker(content: &str) -> bool {
+ let lower = content.to_lowercase();
+ lower.contains("let cline use this tool")
+ || ((lower.contains("[act mode]") || lower.contains("[plan mode]"))
+ && (lower.contains("execute command?") || lower.contains("use this tool?"))
+ && lower.contains("yes"))
+}
diff --git a/src/detect/agents/codex.rs b/src/detect/agents/codex.rs
index 0a9bdadb..b77b7d25 100644
--- a/src/detect/agents/codex.rs
+++ b/src/detect/agents/codex.rs
@@ -38,10 +38,6 @@ pub(super) fn has_visible_blocker(content: &str) -> bool {
has_codex_strong_blocked_prompt(content)
}
-pub(super) fn has_prompt(content: &str) -> bool {
- has_codex_current_prompt(content) || content.lines().any(codex_prompt_line)
-}
-
pub(super) fn has_visible_working(content: &str) -> bool {
has_codex_live_working_at_current_prompt(content)
|| (!has_codex_current_prompt(content) && has_codex_visible_working_without_prompt(content))
diff --git a/src/detect/agents/cursor.rs b/src/detect/agents/cursor.rs
index a62e9382..f0f00b9b 100644
--- a/src/detect/agents/cursor.rs
+++ b/src/detect/agents/cursor.rs
@@ -4,7 +4,7 @@ pub(super) fn detect(content: &str) -> AgentState {
let lower = content.to_lowercase();
// Blocked
- if has_cursor_blocked_prompt(content, &lower) {
+ if has_visible_blocker(content) {
return AgentState::Blocked;
}
@@ -19,8 +19,12 @@ pub(super) fn detect(content: &str) -> AgentState {
AgentState::Idle
}
-fn has_cursor_blocked_prompt(content: &str, lower: &str) -> bool {
- if lower.contains("waiting for approval") || lower.contains("run this command?") {
+pub(super) fn has_visible_blocker(content: &str) -> bool {
+ let lower = content.to_lowercase();
+ if lower.contains("waiting for approval")
+ && lower.contains("run this command?")
+ && (lower.contains("run (once) (y)") || lower.contains("skip (esc or n)"))
+ {
return true;
}
diff --git a/src/detect/agents/droid.rs b/src/detect/agents/droid.rs
index ea19da3f..52c5e087 100644
--- a/src/detect/agents/droid.rs
+++ b/src/detect/agents/droid.rs
@@ -9,19 +9,7 @@ use super::super::{has_braille_spinner, AgentState};
pub(super) fn detect(content: &str) -> AgentState {
let lower = content.to_lowercase();
- // Blocked: EXECUTE approval prompt with selection UI chrome
- // Primary (AND): structural keyword + chrome text = certain
- let has_execute = content.contains("EXECUTE");
- let has_selection_chrome = lower.contains("enter to select")
- || lower.contains("↑↓ to navigate")
- || lower.contains("esc to cancel");
- let has_selection_options = lower.contains("> yes, allow") || lower.contains("> no, cancel");
-
- if has_execute && (has_selection_chrome || has_selection_options) {
- return AgentState::Blocked;
- }
- // Secondary: selection chrome + options together (no EXECUTE needed)
- if has_selection_chrome && has_selection_options {
+ if has_visible_blocker(content) {
return AgentState::Blocked;
}
@@ -37,3 +25,16 @@ pub(super) fn detect(content: &str) -> AgentState {
AgentState::Idle
}
+
+pub(super) fn has_visible_blocker(content: &str) -> bool {
+ let lower = content.to_lowercase();
+ // Primary (AND): structural keyword + chrome text = certain
+ let has_execute = content.contains("EXECUTE");
+ let has_selection_chrome = lower.contains("enter to select")
+ || lower.contains("↑↓ to navigate")
+ || lower.contains("esc to cancel");
+ let has_selection_options = lower.contains("> yes, allow") || lower.contains("> no, cancel");
+
+ (has_execute && (has_selection_chrome || has_selection_options))
+ || (has_selection_chrome && has_selection_options)
+}
diff --git a/src/detect/agents/gemini.rs b/src/detect/agents/gemini.rs
index c47355c0..86cf3bca 100644
--- a/src/detect/agents/gemini.rs
+++ b/src/detect/agents/gemini.rs
@@ -1,24 +1,11 @@
-use super::super::{has_confirmation_prompt, AgentState};
+use super::super::AgentState;
pub(super) fn detect(content: &str) -> AgentState {
- let lower = content.to_lowercase();
-
- // Blocked — explicit confirmation
- if lower.contains("waiting for user confirmation") {
- return AgentState::Blocked;
- }
-
- // Blocked — box-drawing confirmation prompts
- if content.contains("│ Apply this change")
- || content.contains("│ Allow execution")
- || content.contains("│ Do you want to proceed")
- {
- return AgentState::Blocked;
- }
- if has_confirmation_prompt(&lower) {
+ if has_visible_blocker(content) {
return AgentState::Blocked;
}
+ let lower = content.to_lowercase();
// Working
if lower.contains("esc to cancel") {
return AgentState::Working;
@@ -26,3 +13,19 @@ pub(super) fn detect(content: &str) -> AgentState {
AgentState::Idle
}
+
+pub(super) fn has_visible_blocker(content: &str) -> bool {
+ let lower = content.to_lowercase();
+ let has_choice = lower.contains("yes") || lower.contains("no");
+
+ content.contains("│ Apply this change")
+ || content.contains("│ Allow execution")
+ || (has_choice
+ && (lower.contains("waiting for user confirmation")
+ || content.contains("│ Do you want to proceed")
+ || lower.contains("do you want to proceed?")))
+ || content.lines().any(|line| {
+ let line = line.trim().to_ascii_lowercase();
+ line.starts_with("❯") && (line.contains("yes") || line.contains("allow"))
+ })
+}
diff --git a/src/detect/agents/github_copilot.rs b/src/detect/agents/github_copilot.rs
index eed03011..0eece95b 100644
--- a/src/detect/agents/github_copilot.rs
+++ b/src/detect/agents/github_copilot.rs
@@ -4,11 +4,7 @@ pub(super) fn detect(content: &str) -> AgentState {
let lower = content.to_lowercase();
// Blocked
- if lower.contains("esc to cancel")
- && (lower.contains("enter to select")
- || lower.contains("enter to confirm")
- || lower.contains("enter to submit"))
- {
+ if has_visible_blocker(content) {
return AgentState::Blocked;
}
@@ -22,3 +18,11 @@ pub(super) fn detect(content: &str) -> AgentState {
AgentState::Idle
}
+
+pub(super) fn has_visible_blocker(content: &str) -> bool {
+ let lower = content.to_lowercase();
+ lower.contains("esc to cancel")
+ && (lower.contains("enter to select")
+ || lower.contains("enter to confirm")
+ || lower.contains("enter to submit"))
+}
diff --git a/src/detect/agents/grok.rs b/src/detect/agents/grok.rs
index 75b5cd35..8a5834a2 100644
--- a/src/detect/agents/grok.rs
+++ b/src/detect/agents/grok.rs
@@ -7,17 +7,11 @@ use super::super::{has_braille_spinner, AgentState};
/// status line such as "⠋ Waiting… 1.8s" plus live controls like
/// "Ctrl+c:cancel" and "Ctrl+Enter:interject".
pub(super) fn detect(content: &str) -> AgentState {
- let lower = content.to_lowercase();
-
- if lower.contains("use ← → to choose permission whitelist scope")
- || lower.contains("yes, proceed")
- || lower.contains("no, reject")
- || lower.contains("ctrl+o:yolo")
- || lower.contains(":scope")
- {
+ if has_visible_blocker(content) {
return AgentState::Blocked;
}
+ let lower = content.to_lowercase();
if has_braille_spinner(content)
&& (lower.contains("waiting")
|| lower.contains("run ")
@@ -34,3 +28,10 @@ pub(super) fn detect(content: &str) -> AgentState {
AgentState::Idle
}
+
+pub(super) fn has_visible_blocker(content: &str) -> bool {
+ let lower = content.to_lowercase();
+ let has_scope_selector = lower.contains("use ← → to choose permission whitelist scope")
+ || lower.contains("←/→:scope");
+ has_scope_selector && lower.contains("yes, proceed") && lower.contains("no, reject")
+}
diff --git a/src/detect/agents/hermes.rs b/src/detect/agents/hermes.rs
index d6c4c97e..474dda5c 100644
--- a/src/detect/agents/hermes.rs
+++ b/src/detect/agents/hermes.rs
@@ -6,21 +6,25 @@ use super::super::AgentState;
/// dialogs for dangerous terminal commands. Prefer the modal controls for
/// blocked detection, then the live interrupt/status controls for working.
pub(super) fn detect(content: &str) -> AgentState {
- let lower = content.to_lowercase();
-
- let has_approval_options = lower.contains("allow once")
- && lower.contains("allow for this session")
- && lower.contains("deny");
- let has_approval_controls = lower.contains("enter to confirm")
- || lower.contains("↑/↓ to select")
- || lower.contains("show full command");
- if (lower.contains("dangerous command") || has_approval_options) && has_approval_controls {
+ if has_visible_blocker(content) {
return AgentState::Blocked;
}
+ let lower = content.to_lowercase();
if lower.contains("msg=interrupt") || lower.contains("ctrl+c cancel") {
return AgentState::Working;
}
AgentState::Idle
}
+
+pub(super) fn has_visible_blocker(content: &str) -> bool {
+ let lower = content.to_lowercase();
+ let has_approval_options = lower.contains("allow once")
+ && lower.contains("allow for this session")
+ && lower.contains("deny");
+ let has_approval_controls = lower.contains("enter to confirm")
+ || lower.contains("↑/↓ to select")
+ || lower.contains("show full command");
+ (lower.contains("dangerous command") || has_approval_options) && has_approval_controls
+}
diff --git a/src/detect/agents/kilo.rs b/src/detect/agents/kilo.rs
index 1ab3651a..46a5601a 100644
--- a/src/detect/agents/kilo.rs
+++ b/src/detect/agents/kilo.rs
@@ -7,3 +7,7 @@ pub(super) fn detect(content: &str) -> AgentState {
super::opencode::detect(content)
}
+
+pub(super) fn has_visible_blocker(content: &str) -> bool {
+ super::opencode::has_visible_blocker(content)
+}
diff --git a/src/detect/agents/kimi.rs b/src/detect/agents/kimi.rs
index b42bf885..9d3821ed 100644
--- a/src/detect/agents/kimi.rs
+++ b/src/detect/agents/kimi.rs
@@ -16,10 +16,6 @@ pub(super) fn has_visible_blocker(content: &str) -> bool {
has_current_approval_panel(content) || has_question_panel(content)
}
-pub(super) fn has_prompt_box(content: &str) -> bool {
- has_editor_prompt_box(content) && has_footer_context(content)
-}
-
pub(super) fn has_visible_working(content: &str) -> bool {
has_kimi_working_status(content)
}
@@ -99,49 +95,3 @@ fn has_question_panel(content: &str) -> bool {
fn has_numeric_choose_hint(lower_content: &str) -> bool {
lower_content.contains(" choose") && lower_content.contains('1') && lower_content.contains('2')
}
-
-fn has_editor_prompt_box(content: &str) -> bool {
- let lines: Vec<&str> = content.lines().collect();
-
- for top_index in 0..lines.len() {
- if !is_editor_top_border(lines[top_index]) {
- continue;
- }
-
- let mut saw_prompt = false;
- for line in lines.iter().skip(top_index + 1) {
- if is_editor_bottom_border(line) {
- if saw_prompt {
- return true;
- }
- break;
- }
-
- saw_prompt |= is_editor_prompt_line(line);
- }
- }
-
- false
-}
-
-fn is_editor_top_border(line: &str) -> bool {
- let trimmed = line.trim();
- ((trimmed.starts_with('╭') && trimmed.ends_with('╮'))
- || (trimmed.starts_with('├') && trimmed.ends_with('┤')))
- && trimmed.contains('─')
-}
-
-fn is_editor_bottom_border(line: &str) -> bool {
- let trimmed = line.trim();
- trimmed.starts_with('╰') && trimmed.ends_with('╯') && trimmed.contains('─')
-}
-
-fn is_editor_prompt_line(line: &str) -> bool {
- let trimmed = line.trim_start();
- let inner = trimmed.strip_prefix('│').unwrap_or(trimmed).trim_start();
- inner.starts_with('>')
-}
-
-fn has_footer_context(content: &str) -> bool {
- content.to_lowercase().contains("context: ")
-}
diff --git a/src/detect/agents/kiro.rs b/src/detect/agents/kiro.rs
index f40b9bcb..889f5315 100644
--- a/src/detect/agents/kiro.rs
+++ b/src/detect/agents/kiro.rs
@@ -7,7 +7,7 @@ use super::super::AgentState;
pub(super) fn detect(content: &str) -> AgentState {
let lower = content.to_lowercase();
- if has_kiro_blocked_prompt(&lower) {
+ if has_visible_blocker(content) {
return AgentState::Blocked;
}
@@ -20,8 +20,9 @@ pub(super) fn detect(content: &str) -> AgentState {
AgentState::Idle
}
-fn has_kiro_blocked_prompt(lower_content: &str) -> bool {
- has_tool_approval_prompt(lower_content) || has_subagent_approval_prompt(lower_content)
+pub(super) fn has_visible_blocker(content: &str) -> bool {
+ let lower_content = content.to_lowercase();
+ has_tool_approval_prompt(&lower_content) || has_subagent_approval_prompt(&lower_content)
}
fn has_tool_approval_prompt(lower_content: &str) -> bool {
diff --git a/src/detect/agents/mod.rs b/src/detect/agents/mod.rs
index 46a25f66..704350df 100644
--- a/src/detect/agents/mod.rs
+++ b/src/detect/agents/mod.rs
@@ -45,7 +45,6 @@ pub(super) fn detect(agent: Agent, screen_content: &str) -> AgentDetection {
state,
skip_state_update: true,
visible_blocker: false,
- visible_idle: false,
visible_working: false,
};
}
@@ -54,7 +53,6 @@ pub(super) fn detect(agent: Agent, screen_content: &str) -> AgentDetection {
state,
skip_state_update: false,
visible_blocker: has_visible_blocker(agent, screen_content, state),
- visible_idle: has_visible_idle(agent, screen_content, state),
visible_working: has_visible_working(agent, screen_content, state),
}
}
@@ -79,20 +77,20 @@ fn has_visible_blocker(agent: Agent, content: &str, state: AgentState) -> bool {
// is known to be structural and live.
Agent::Claude => claude_code::has_visible_blocker(content),
Agent::Codex => codex::has_visible_blocker(content),
+ Agent::Gemini => gemini::has_visible_blocker(content),
+ Agent::Cursor => cursor::has_visible_blocker(content),
+ Agent::Antigravity => antigravity::has_visible_blocker(content),
+ Agent::Cline => cline::has_visible_blocker(content),
+ Agent::OpenCode => opencode::has_visible_blocker(content),
+ Agent::GithubCopilot => github_copilot::has_visible_blocker(content),
Agent::Kimi => kimi::has_visible_blocker(content),
- _ => false,
- }
-}
-
-fn has_visible_idle(agent: Agent, content: &str, state: AgentState) -> bool {
- if state != AgentState::Idle {
- return false;
- }
-
- match agent {
- Agent::Claude => claude_code::has_prompt_box(content),
- Agent::Codex => codex::has_prompt(content),
- Agent::Kimi => kimi::has_prompt_box(content),
+ Agent::Kiro => kiro::has_visible_blocker(content),
+ Agent::Droid => droid::has_visible_blocker(content),
+ Agent::Amp => amp::has_visible_blocker(content),
+ Agent::Grok => grok::has_visible_blocker(content),
+ Agent::Hermes => hermes::has_visible_blocker(content),
+ Agent::Kilo => kilo::has_visible_blocker(content),
+ Agent::Qodercli => qodercli::has_visible_blocker(content),
_ => false,
}
}
diff --git a/src/detect/agents/opencode.rs b/src/detect/agents/opencode.rs
index 219f69ed..ab8a424f 100644
--- a/src/detect/agents/opencode.rs
+++ b/src/detect/agents/opencode.rs
@@ -2,7 +2,7 @@ use super::super::{has_interrupt_pattern, AgentState};
pub(super) fn detect(content: &str) -> AgentState {
// Blocked
- if content.contains("△ Permission required") || has_opencode_question_prompt(content) {
+ if has_visible_blocker(content) {
return AgentState::Blocked;
}
@@ -17,6 +17,10 @@ pub(super) fn detect(content: &str) -> AgentState {
AgentState::Idle
}
+pub(super) fn has_visible_blocker(content: &str) -> bool {
+ content.contains("△ Permission required") || has_opencode_question_prompt(content)
+}
+
fn has_opencode_question_prompt(content: &str) -> bool {
let lower = content.to_lowercase();
let has_enter_action = lower.contains("enter confirm")
diff --git a/src/detect/agents/qodercli.rs b/src/detect/agents/qodercli.rs
index b7da356b..0a4372df 100644
--- a/src/detect/agents/qodercli.rs
+++ b/src/detect/agents/qodercli.rs
@@ -15,7 +15,7 @@ pub(super) fn detect(content: &str) -> AgentState {
return AgentState::Idle;
}
- if has_qodercli_blocked_prompt(&lower) {
+ if has_visible_blocker(content) {
return AgentState::Blocked;
}
@@ -87,9 +87,15 @@ fn has_qodercli_spinner_row(content: &str) -> bool {
/// defensive fallbacks in case the title row scrolls off-screen.
/// * The interactive shell waiting hint emitted by qodercli when an agent
/// spawns a shell that is now parked for user keystrokes.
-fn has_qodercli_blocked_prompt(lower_content: &str) -> bool {
- lower_content.contains("waiting for user confirmation")
- || lower_content.contains("awaiting approval")
+pub(super) fn has_visible_blocker(content: &str) -> bool {
+ let lower_content = content.to_lowercase();
+ (lower_content.contains("waiting for user confirmation")
+ && (lower_content.contains("yes")
+ || lower_content.contains("no")
+ || lower_content.contains("allow")
+ || lower_content.contains("reject")))
+ || (lower_content.contains("awaiting approval")
+ && (lower_content.contains("allow") || lower_content.contains("reject")))
|| lower_content.contains("permission required")
|| lower_content.contains("allow once or always?")
|| lower_content.contains("asking user")