From 31cf4c42014a8f24473f0ce5962ee57c2a8dcf67 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Fri, 10 Apr 2026 15:14:18 +0300 Subject: [PATCH] fix: harden amp blocked detection --- README.md | 2 +- src/detect.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 40862c42..14a864be 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ the following agents have been tested: | [claude code](https://docs.anthropic.com/en/docs/claude-code) | ✓ | ✓ | ✓ | | [codex](https://github.com/openai/codex) | ✓ | ✓ | ✓ | | [droid](https://factory.ai) | ✓ | ✓ | ✓ | -| [amp](https://ampcode.com) | ✓ | ✓ | partial | +| [amp](https://ampcode.com) | ✓ | ✓ | ✓ | | [opencode](https://github.com/anomalyco/opencode) | ✓ | ✓ | ✓ | heuristics also exist for these agents but have not been fully tested yet: diff --git a/src/detect.rs b/src/detect.rs index a026ddce..237d7270 100644 --- a/src/detect.rs +++ b/src/detect.rs @@ -349,18 +349,37 @@ fn detect_droid(content: &str) -> AgentState { /// Amp (Sourcegraph) detection. /// -/// Screen layout when working: +/// Blocked approval prompts use a shared footer with options like +/// "Approve", "Allow All for This Session", "Allow All for Every Session", +/// "Allow File for Every Session", and "Deny with feedback". The header varies +/// by approval type, for example "Invoke tool ...?", "Run this command?", +/// "Allow editing file:", or "Allow creating file:". +/// +/// Working layout: /// ```text /// ✓ Search Map the core runtime architecture... /// ⋯ Oracle ▼ /// ≈ Running tools... Esc to cancel /// ``` -/// -/// "Esc to cancel" is the reliable working indicator — it only appears -/// while amp is actively running tools or thinking. fn detect_amp(content: &str) -> AgentState { 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?") + || lower.contains("allow editing file:") + || lower.contains("allow creating file:") + || lower.contains("confirm tool call"); + let has_approval_actions = lower.contains("approve") + && (lower.contains("allow all for this session") + || lower.contains("allow all for every session") + || 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; } @@ -1063,6 +1082,30 @@ mod tests { // ---- Amp ---- + #[test] + fn amp_blocked_waiting_for_approval() { + let screen = "Invoke tool shell_command?\n▸● Approve [Alt+1]\n ○ Allow All for This Session [Alt+2]\n ○ Allow All for Every Session [Alt+3]\n ○ Deny with feedback [Alt+4]\nWaiting for approval..."; + assert_eq!(detect_state(Some(Agent::Amp), screen), AgentState::Blocked); + } + + #[test] + fn amp_blocked_run_this_command() { + let screen = "Run this command?\nrg --files\n▸● Approve [Alt+1]\n ○ Allow All for This Session [Alt+2]\n ○ Allow All for Every Session [Alt+3]\n ○ Deny with feedback [Alt+4]"; + assert_eq!(detect_state(Some(Agent::Amp), screen), AgentState::Blocked); + } + + #[test] + fn amp_blocked_allow_editing_file() { + let screen = "Allow editing file:\nsrc/detect.rs\n▸● Approve [Alt+1]\n ○ Allow File for Every Session [Alt+2]\n ○ Allow All for This Session [Alt+3]\n ○ Deny with feedback [Alt+4]"; + assert_eq!(detect_state(Some(Agent::Amp), screen), AgentState::Blocked); + } + + #[test] + fn amp_blocked_allow_creating_file() { + let screen = "Allow creating file:\nsrc/new_file.rs\n▸● Approve [Alt+1]\n ○ Allow File for Every Session [Alt+2]\n ○ Allow All for This Session [Alt+3]\n ○ Deny with feedback [Alt+4]"; + assert_eq!(detect_state(Some(Agent::Amp), screen), AgentState::Blocked); + } + #[test] fn amp_working_running_tools() { let screen = " ✓ Search Map the core runtime architecture\n ⋯ Oracle ▼\n ≈ Running tools... Esc to cancel";