diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-22 14:39:00 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-22 14:39:00 +0300 |
| commit | 760529ce3d8a160c1d6ebafe6b53e9c9fe7574ca (patch) | |
| tree | 7fd41983c9ea5c219e2fab07c9b33532c025f312 | |
| parent | 716926f0c68e4e06fc308868cb919fec0bb68c53 (diff) | |
fix: detect kimi code cli states
refs #215
| -rw-r--r-- | docs/next/CHANGELOG.md | 1 | ||||
| -rw-r--r-- | docs/next/README.md | 3 | ||||
| -rw-r--r-- | docs/next/website/src/content/docs/agents.mdx | 3 | ||||
| -rw-r--r-- | src/detect.rs | 112 |
4 files changed, 88 insertions, 31 deletions
diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md index dc1c2d9d..760453ea 100644 --- a/docs/next/CHANGELOG.md +++ b/docs/next/CHANGELOG.md @@ -10,6 +10,7 @@ - Added named-session reattach and stop command hints so detach and update guidance point back to the active session. (#199, thanks @Golden-Pigeon) ### Fixed +- Kimi Code CLI sessions now use structural terminal detection for approval prompts and live thinking/tool status, improving working and blocked state reporting in the Agents panel. (#215) - Antigravity CLI (`agy`) sessions are now detected, and their terminal UI now reports working and blocked states in the Agents panel. (#207) - Cursor Agent sessions launched as `cursor-agent` are now detected, and their terminal UI now reports working and blocked states in the Agents panel. (#225) - `herdr --remote` now offers to restart the remote server after installing or replacing a remote binary, or when the running server version differs, even if the client/server protocol is still compatible. diff --git a/docs/next/README.md b/docs/next/README.md index 2b4683c0..60921cf5 100644 --- a/docs/next/README.md +++ b/docs/next/README.md @@ -181,9 +181,10 @@ automatic detection works out of the box. process name matching plus terminal ou | [hermes agent](https://github.com/NousResearch/hermes-agent) | ✓ | ✓ | ✓ | | cursor agent | ✓ | ✓ | ✓ | | antigravity cli | ✓ | ✓ | ✓ | +| kimi code cli | ✓ | ✓ | ✓ | | [kiro cli](https://kiro.dev/docs/cli/) | ✓ | ✓ | — | -detected but not fully tested: gemini cli, cline, kimi, github copilot cli. +detected but not fully tested: gemini cli, cline, github copilot cli. for agents outside the built-in list, herdr still works as a terminal multiplexer with workspaces, panes, and tiling. custom integrations can report agent labels over the socket api. see the [socket api docs](https://herdr.dev/docs/socket-api/). diff --git a/docs/next/website/src/content/docs/agents.mdx b/docs/next/website/src/content/docs/agents.mdx index d20a862b..559af389 100644 --- a/docs/next/website/src/content/docs/agents.mdx +++ b/docs/next/website/src/content/docs/agents.mdx @@ -21,10 +21,11 @@ Automatic detection works out of the box for common coding agents. | Hermes Agent | yes | yes | yes | | Cursor Agent | yes | yes | yes | | Antigravity CLI | yes | yes | yes | +| Kimi Code CLI | yes | yes | yes | | Kiro CLI | yes | yes | no | | GitHub Copilot CLI | yes | yes | yes | -Detected but less thoroughly tested: Gemini CLI, Cline, and Kimi. +Detected but less thoroughly tested: Gemini CLI and Cline. Unsupported agents still run normally as terminal processes. They just may not get rich state unless you add an integration or report state over the socket API. 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 ---- |
