aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-22 14:39:00 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-22 14:39:00 +0300
commit760529ce3d8a160c1d6ebafe6b53e9c9fe7574ca (patch)
tree7fd41983c9ea5c219e2fab07c9b33532c025f312 /src
parent716926f0c68e4e06fc308868cb919fec0bb68c53 (diff)
fix: detect kimi code cli states
refs #215
Diffstat (limited to 'src')
-rw-r--r--src/detect.rs112
1 files changed, 83 insertions, 29 deletions
diff --git a/src/detect.rs b/src/detect.rs
index dd7d21c8..31d40b39 100644
--- a/src/detect.rs
+++ b/src/detect.rs
@@ -68,7 +68,7 @@ pub fn parse_agent_label(agent: &str) -> Option<Agent> {
"cline" => Some(Agent::Cline),
"opencode" | "open-code" => Some(Agent::OpenCode),
"copilot" | "github-copilot" | "ghcs" => Some(Agent::GithubCopilot),
- "kimi" => Some(Agent::Kimi),
+ "kimi" | "kimi-code" | "kimi code" => Some(Agent::Kimi),
"kiro" | "kiro-cli" => Some(Agent::Kiro),
"droid" => Some(Agent::Droid),
"amp" | "amp-local" => Some(Agent::Amp),
@@ -93,7 +93,7 @@ pub fn identify_agent(process_name: &str) -> Option<Agent> {
"cline" => Some(Agent::Cline),
"opencode" | "open-code" => Some(Agent::OpenCode),
"copilot" | "github-copilot" | "ghcs" => Some(Agent::GithubCopilot),
- "kimi" => Some(Agent::Kimi),
+ "kimi" | "kimi-code" | "kimi code" => Some(Agent::Kimi),
"kiro" | "kiro-cli" => Some(Agent::Kiro),
"droid" => Some(Agent::Droid),
"amp" | "amp-local" => Some(Agent::Amp),
@@ -357,33 +357,52 @@ fn detect_github_copilot(content: &str) -> AgentState {
}
fn detect_kimi(content: &str) -> AgentState {
- let lower = content.to_lowercase();
-
- // Blocked
- if lower.contains("allow?")
- || lower.contains("confirm?")
- || lower.contains("approve?")
- || lower.contains("proceed?")
- || lower.contains("[y/n]")
- || lower.contains("(y/n)")
- {
+ if has_kimi_blocked_prompt(content) {
return AgentState::Blocked;
}
- // Working
- if lower.contains("thinking")
- || lower.contains("processing")
- || lower.contains("generating")
- || lower.contains("waiting for response")
- || lower.contains("ctrl+c to cancel")
- || lower.contains("ctrl-c to cancel")
- {
+ if has_kimi_working_status(content) {
return AgentState::Working;
}
AgentState::Idle
}
+fn has_kimi_blocked_prompt(content: &str) -> bool {
+ let lower = content.to_lowercase();
+ lower.contains("requesting approval")
+ && (lower.contains("approve once") || lower.contains("approve for this session"))
+ && lower.contains("reject")
+ && (lower.contains("1/2/3/4 choose") || lower.contains("โ†ต confirm"))
+}
+
+fn has_kimi_working_status(content: &str) -> bool {
+ content.lines().any(|line| {
+ let trimmed = line.trim();
+ if matches!(
+ trimmed,
+ "๐ŸŒ•" | "๐ŸŒ–" | "๐ŸŒ—" | "๐ŸŒ˜" | "๐ŸŒ‘" | "๐ŸŒ’" | "๐ŸŒ“" | "๐ŸŒ”"
+ ) {
+ return true;
+ }
+
+ let mut chars = trimmed.chars();
+ let Some(first) = chars.next() else {
+ return false;
+ };
+ if !('\u{2800}'..='\u{28FF}').contains(&first) {
+ return false;
+ }
+
+ let rest = chars
+ .as_str()
+ .trim_start_matches(|c| ('\u{2800}'..='\u{28FF}').contains(&c))
+ .trim_start()
+ .to_lowercase();
+ rest.starts_with("thinking...") || rest.starts_with("using ")
+ })
+}
+
/// Kiro CLI detection.
///
/// Kiro exposes reliable working and idle terminal markers. Confirmation
@@ -1004,6 +1023,7 @@ mod tests {
assert_eq!(identify_agent("cline"), Some(Agent::Cline));
assert_eq!(identify_agent("opencode"), Some(Agent::OpenCode));
assert_eq!(identify_agent("kimi"), Some(Agent::Kimi));
+ assert_eq!(identify_agent("Kimi Code"), Some(Agent::Kimi));
assert_eq!(identify_agent("kiro"), Some(Agent::Kiro));
assert_eq!(identify_agent("kiro-cli"), Some(Agent::Kiro));
assert_eq!(identify_agent("copilot"), Some(Agent::GithubCopilot));
@@ -1022,6 +1042,7 @@ mod tests {
assert_eq!(parse_agent_label("agy"), Some(Agent::Antigravity));
assert_eq!(parse_agent_label("antigravity"), Some(Agent::Antigravity));
assert_eq!(parse_agent_label("copilot"), Some(Agent::GithubCopilot));
+ assert_eq!(parse_agent_label("kimi-code"), Some(Agent::Kimi));
assert_eq!(
parse_agent_label("github-copilot"),
Some(Agent::GithubCopilot)
@@ -1687,28 +1708,61 @@ mod tests {
// ---- Kimi ----
#[test]
- fn kimi_waiting_approve() {
- assert_eq!(detect_kimi("approve?"), AgentState::Blocked);
+ fn kimi_blocked_approval_prompt_wins_over_spinner() {
+ let screen = "โ ‹ Using Shell (git log --oneline -10)\nโ•ญโ”€ approval โ”€โ•ฎ\nShell is requesting approval to run command:\ngit log --oneline -10\nโ†’ [1] Approve once\n[2] Approve for this session\n[3] Reject\n[4] Reject, tell the model what to do instead\nโ–ฒ/โ–ผ select 1/2/3/4 choose โ†ต confirm";
+ assert_eq!(detect_kimi(screen), AgentState::Blocked);
+ }
+
+ #[test]
+ fn kimi_approval_words_without_prompt_stay_idle() {
+ assert_eq!(detect_kimi("approve?"), AgentState::Idle);
+ assert_eq!(detect_kimi("continue? [y/n]"), AgentState::Idle);
+ }
+
+ #[test]
+ fn kimi_working_braille_thinking() {
+ assert_eq!(
+ detect_kimi("โ ฆ Thinking... <1s ยท 19 tokens"),
+ AgentState::Working
+ );
+ }
+
+ #[test]
+ fn kimi_working_braille_using_tool() {
+ assert_eq!(
+ detect_kimi("โ น Using Shell (git log -20 --name-status)"),
+ AgentState::Working
+ );
}
#[test]
- fn kimi_waiting_yn() {
- assert_eq!(detect_kimi("continue? [y/n]"), AgentState::Blocked);
+ fn kimi_working_moon_spinner() {
+ assert_eq!(detect_kimi("๐ŸŒ•"), AgentState::Working);
+ assert_eq!(detect_kimi("๐ŸŒ—"), AgentState::Working);
+ assert_eq!(detect_kimi("๐ŸŒ˜"), AgentState::Working);
}
#[test]
- fn kimi_working_thinking() {
- assert_eq!(detect_kimi("thinking"), AgentState::Working);
+ fn kimi_working_moon_spinner_above_input_box() {
+ let screen = "โœจ yo\n\n๐ŸŒ—\n\nโ”€โ”€ input โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€";
+ assert_eq!(detect_kimi(screen), AgentState::Working);
}
#[test]
- fn kimi_working_generating() {
- assert_eq!(detect_kimi("generating code"), AgentState::Working);
+ fn kimi_old_transcript_words_stay_idle() {
+ assert_eq!(detect_kimi("thinking"), AgentState::Idle);
+ assert_eq!(detect_kimi("generating code"), AgentState::Idle);
+ assert_eq!(
+ detect_kimi("Used Shell (git log --oneline -10)"),
+ AgentState::Idle
+ );
+ assert_eq!(detect_kimi("some ๐ŸŒ• in prose"), AgentState::Idle);
}
#[test]
fn kimi_idle() {
- assert_eq!(detect_kimi("> "), AgentState::Idle);
+ let screen = "Welcome to Kimi Code CLI!\nโ”€โ”€ input โ”€\nโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\nagent (Kimi-k2.6 โ—) ~/Projects/herdr";
+ assert_eq!(detect_kimi(screen), AgentState::Idle);
}
// ---- Kiro ----