use crate::ui::theme::State;
const KNOWN_AGENTS: &[&str] = &[
"claude", "codex", "gemini", "cursor", "aider", "opencode", "copilot", "amp", "droid", "kimi",
"grok", "qwen", "kiro",
];
const BLOCKED_MARKERS: &[&str] = &[
"do you want to proceed",
"do you want to continue",
"allow this",
"approve?",
"(y/n)",
"[y/n]",
"yes/no",
"press enter to continue",
"waiting for confirmation",
"1. yes",
"❯ 1.",
];
pub struct Detection {
pub state: State,
pub agent: String,
}
pub fn classify(
title: Option<&str>,
bottom: &str,
recent_activity: bool,
base_command: &str,
) -> Detection {
let low = bottom.to_lowercase();
let agent = detect_agent(title, &low).unwrap_or_else(|| base_command.to_string());
let state = if BLOCKED_MARKERS.iter().any(|m| low.contains(m)) {
State::Blocked
} else if recent_activity {
State::Working
} else {
State::Idle
};
Detection { state, agent }
}
fn detect_agent(title: Option<&str>, low_bottom: &str) -> Option<String> {
let mut hay = String::new();
if let Some(t) = title {
hay.push_str(&t.to_lowercase());
hay.push(' ');
}
hay.push_str(low_bottom);
KNOWN_AGENTS
.iter()
.find(|name| hay.contains(*name))
.map(|n| n.to_string())
}
pub fn is_agent(name: &str) -> bool {
let low = name.to_lowercase();
KNOWN_AGENTS.iter().any(|a| low == *a)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blocked_beats_activity() {
let d = classify(None, "Do you want to proceed? (y/n)", true, "zsh");
assert_eq!(d.state, State::Blocked);
}
#[test]
fn activity_is_working() {
let d = classify(Some("claude"), "thinking...", true, "zsh");
assert_eq!(d.state, State::Working);
assert_eq!(d.agent, "claude");
}
#[test]
fn quiet_is_idle() {
let d = classify(None, "$ ", false, "zsh");
assert_eq!(d.state, State::Idle);
assert_eq!(d.agent, "zsh");
}
}