diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-03-30 13:43:41 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-03-30 13:43:41 +0300 |
| commit | 0426bf53d798a182bff3a5f315ffb49f1637115a (patch) | |
| tree | e1b66954ac02183174ce9ba9a8066daeb31cddb0 /src | |
| parent | e15dfae600a0e6ef319b4244aba0aeaf01dc9b53 (diff) | |
refactor: rename agent states to working and blocked
Diffstat (limited to 'src')
| -rw-r--r-- | src/api/schema.rs | 12 | ||||
| -rw-r--r-- | src/app/actions.rs | 26 | ||||
| -rw-r--r-- | src/app/mod.rs | 4 | ||||
| -rw-r--r-- | src/app/state.rs | 4 | ||||
| -rw-r--r-- | src/cli.rs | 10 | ||||
| -rw-r--r-- | src/detect.rs | 262 | ||||
| -rw-r--r-- | src/pane.rs | 58 | ||||
| -rw-r--r-- | src/sound.rs | 2 | ||||
| -rw-r--r-- | src/ui.rs | 22 | ||||
| -rw-r--r-- | src/workspace.rs | 14 |
10 files changed, 207 insertions, 207 deletions
diff --git a/src/api/schema.rs b/src/api/schema.rs index 7de0bbed..0b807eed 100644 --- a/src/api/schema.rs +++ b/src/api/schema.rs @@ -445,8 +445,8 @@ pub enum EventData { #[serde(rename_all = "snake_case")] pub enum PaneAgentState { Idle, - Busy, - Waiting, + Working, + Blocked, Unknown, } @@ -562,7 +562,7 @@ mod tests { { "type": "pane.agent_state_changed", "pane_id": "p_1_1", - "state": "waiting" + "state": "blocked" } ] } @@ -588,7 +588,7 @@ mod tests { ¶ms.subscriptions[1], Subscription::PaneAgentStateChanged { pane_id, - state: Some(PaneAgentState::Waiting), + state: Some(PaneAgentState::Blocked), } if pane_id == "p_1_1" )); } @@ -656,7 +656,7 @@ mod tests { "match_event": { "event": "pane_agent_state_changed", "pane_id": "p_1", - "state": "waiting" + "state": "blocked" }, "timeout_ms": 30000 } @@ -671,7 +671,7 @@ mod tests { params.match_event, EventMatch::PaneAgentStateChanged { pane_id: "p_1".into(), - state: PaneAgentState::Waiting, + state: PaneAgentState::Blocked, } ); } diff --git a/src/app/actions.rs b/src/app/actions.rs index aa767eb3..6c42b966 100644 --- a/src/app/actions.rs +++ b/src/app/actions.rs @@ -19,7 +19,7 @@ fn notification_sound_for_state_change( } match new_state { - AgentState::Waiting => Some(crate::sound::Sound::Request), + AgentState::Blocked => Some(crate::sound::Sound::Request), AgentState::Idle if prev_state != AgentState::Idle && !is_active_ws => { Some(crate::sound::Sound::Done) } @@ -37,7 +37,7 @@ fn notification_toast_for_state_change( } match new_state { - AgentState::Waiting => Some(ToastKind::NeedsAttention), + AgentState::Blocked => Some(ToastKind::NeedsAttention), AgentState::Idle if prev_state != AgentState::Idle => Some(ToastKind::Finished), _ => None, } @@ -219,7 +219,7 @@ impl AppState { pane.seen = false; } - // Waiting prompts should always make noise; done sounds stay background-only. + // Blocked prompts should always make noise; done sounds stay background-only. if self.sound.allows(agent) { if let Some(sound) = notification_sound_for_state_change(is_active_ws, prev_state, state) @@ -435,11 +435,11 @@ mod tests { state.handle_app_event(AppEvent::StateChanged { pane_id, agent: Some(Agent::Pi), - state: AgentState::Busy, + state: AgentState::Working, }); let pane = state.workspaces[0].panes.get(&pane_id).unwrap(); - assert_eq!(pane.state, AgentState::Busy); + assert_eq!(pane.state, AgentState::Working); assert_eq!(pane.detected_agent, Some(Agent::Pi)); } @@ -449,12 +449,12 @@ mod tests { state.active = Some(0); let bg_pane_id = *state.workspaces[1].panes.keys().next().unwrap(); - // First set it to Busy + // First set it to Working state.workspaces[1] .panes .get_mut(&bg_pane_id) .unwrap() - .state = AgentState::Busy; + .state = AgentState::Working; // Now transition to Idle while in background state.handle_app_event(AppEvent::StateChanged { @@ -470,7 +470,7 @@ mod tests { #[test] fn waiting_sound_plays_even_in_active_workspace() { assert_eq!( - notification_sound_for_state_change(true, AgentState::Busy, AgentState::Waiting), + notification_sound_for_state_change(true, AgentState::Working, AgentState::Blocked), Some(crate::sound::Sound::Request) ); } @@ -478,11 +478,11 @@ mod tests { #[test] fn done_sound_only_plays_in_background() { assert_eq!( - notification_sound_for_state_change(false, AgentState::Busy, AgentState::Idle), + notification_sound_for_state_change(false, AgentState::Working, AgentState::Idle), Some(crate::sound::Sound::Done) ); assert_eq!( - notification_sound_for_state_change(true, AgentState::Busy, AgentState::Idle), + notification_sound_for_state_change(true, AgentState::Working, AgentState::Idle), None ); } @@ -497,7 +497,7 @@ mod tests { state.handle_app_event(AppEvent::StateChanged { pane_id: bg_pane_id, agent: Some(Agent::Pi), - state: AgentState::Waiting, + state: AgentState::Blocked, }); let toast = state.toast.as_ref().unwrap(); @@ -516,7 +516,7 @@ mod tests { .panes .get_mut(&bg_pane_id) .unwrap() - .state = AgentState::Busy; + .state = AgentState::Working; state.handle_app_event(AppEvent::StateChanged { pane_id: bg_pane_id, @@ -540,7 +540,7 @@ mod tests { state.handle_app_event(AppEvent::StateChanged { pane_id, agent: Some(Agent::Pi), - state: AgentState::Waiting, + state: AgentState::Blocked, }); assert!(state.toast.is_none()); diff --git a/src/app/mod.rs b/src/app/mod.rs index 5c5467c5..37f109cf 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1158,8 +1158,8 @@ fn parse_api_key(key: &str) -> Option<crossterm::event::KeyEvent> { fn pane_agent_state(state: crate::detect::AgentState) -> crate::api::schema::PaneAgentState { match state { crate::detect::AgentState::Idle => crate::api::schema::PaneAgentState::Idle, - crate::detect::AgentState::Busy => crate::api::schema::PaneAgentState::Busy, - crate::detect::AgentState::Waiting => crate::api::schema::PaneAgentState::Waiting, + crate::detect::AgentState::Working => crate::api::schema::PaneAgentState::Working, + crate::detect::AgentState::Blocked => crate::api::schema::PaneAgentState::Blocked, crate::detect::AgentState::Unknown => crate::api::schema::PaneAgentState::Unknown, } } diff --git a/src/app/state.rs b/src/app/state.rs index a52b008e..bee6c262 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -38,9 +38,9 @@ pub struct Palette { pub mauve: Color, /// Done / idle states. pub green: Color, - /// Busy / running states. + /// Working / running states. pub yellow: Color, - /// Needs attention / waiting states. + /// Needs attention / blocked states. pub red: Color, /// Unseen / done notification accent. pub blue: Color, @@ -530,7 +530,7 @@ fn wait_output(args: &[String]) -> std::io::Result<i32> { fn wait_agent_state(args: &[String]) -> std::io::Result<i32> { let Some(raw_pane_id) = args.first() else { - eprintln!("usage: herdr wait agent-state <pane_id> --state <idle|busy|waiting|unknown> [--timeout MS]"); + eprintln!("usage: herdr wait agent-state <pane_id> --state <idle|working|blocked|unknown> [--timeout MS]"); return Ok(2); }; @@ -692,11 +692,11 @@ fn parse_read_source(value: &str) -> std::io::Result<ReadSource> { fn parse_agent_state(value: &str) -> std::io::Result<PaneAgentState> { match value { "idle" => Ok(PaneAgentState::Idle), - "busy" => Ok(PaneAgentState::Busy), - "waiting" => Ok(PaneAgentState::Waiting), + "working" => Ok(PaneAgentState::Working), + "blocked" => Ok(PaneAgentState::Blocked), "unknown" => Ok(PaneAgentState::Unknown), _ => Err(std::io::Error::other(format!( - "invalid agent state: {value}" + "invalid agent state: {value} (expected idle, working, blocked, or unknown)" ))), } } @@ -739,7 +739,7 @@ fn print_wait_help() { eprintln!("herdr wait commands:"); eprintln!(" herdr wait output <pane_id> --match <text> [--source visible|recent] [--lines N] [--timeout MS] [--regex]"); eprintln!( - " herdr wait agent-state <pane_id> --state <idle|busy|waiting|unknown> [--timeout MS]" + " herdr wait agent-state <pane_id> --state <idle|working|blocked|unknown> [--timeout MS]" ); } diff --git a/src/detect.rs b/src/detect.rs index 0a38b952..ae7559bd 100644 --- a/src/detect.rs +++ b/src/detect.rs @@ -9,9 +9,9 @@ pub enum AgentState { /// Agent finished, prompt visible, nothing happening. Idle, /// Agent is actively working/processing. - Busy, - /// Agent needs human input — approval, question, error. - Waiting, + Working, + /// Agent needs human input and is blocked on a response. + Blocked, /// Plain shell or unrecognized program. Unknown, } @@ -23,8 +23,8 @@ impl AgentState { match self { Self::Unknown => 0, Self::Idle => 1, - Self::Busy => 2, - Self::Waiting => 3, + Self::Working => 2, + Self::Blocked => 3, } } } @@ -123,7 +123,7 @@ pub fn workspace_state(pane_states: &[AgentState]) -> AgentState { fn detect_pi(content: &str) -> AgentState { // pi shows "Working..." when the agent is processing if content.contains("Working...") { - return AgentState::Busy; + return AgentState::Working; } AgentState::Idle } @@ -151,34 +151,34 @@ fn detect_claude(content: &str) -> AgentState { return AgentState::Idle; } - // --- Waiting detection (full content including prompt box) --- + // --- Blocked detection (full content including prompt box) --- // "Do you want" or "Would you like" followed by yes/❯ if has_confirmation_prompt(&lower) { - return AgentState::Waiting; + return AgentState::Blocked; } // Selection prompt: ❯ followed by numbered option if has_selection_prompt(content) { - return AgentState::Waiting; + return AgentState::Blocked; } // "esc to cancel" indicates permission/confirmation dialog if lower.contains("esc to cancel") { - return AgentState::Waiting; + return AgentState::Blocked; } - // --- Busy detection (content above the prompt box) --- + // --- Working detection (content above the prompt box) --- let above = content_above_prompt_box(content); let above_lower = above.to_lowercase(); if above_lower.contains("esc to interrupt") || above_lower.contains("ctrl+c to interrupt") { - return AgentState::Busy; + return AgentState::Working; } if has_spinner_activity(above) { - return AgentState::Busy; + return AgentState::Working; } AgentState::Idle @@ -187,22 +187,22 @@ fn detect_claude(content: &str) -> AgentState { fn detect_codex(content: &str) -> AgentState { let lower = content.to_lowercase(); - // Waiting patterns + // Blocked patterns if lower.contains("press enter to confirm or esc to cancel") || lower.contains("| enter to submit answer") || lower.contains("allow command?") || lower.contains("[y/n]") || lower.contains("yes (y)") { - return AgentState::Waiting; + return AgentState::Blocked; } if has_confirmation_prompt(&lower) { - return AgentState::Waiting; + return AgentState::Blocked; } - // Busy + // Working if has_interrupt_pattern(&lower) { - return AgentState::Busy; + return AgentState::Working; } AgentState::Idle @@ -211,25 +211,25 @@ fn detect_codex(content: &str) -> AgentState { fn detect_gemini(content: &str) -> AgentState { let lower = content.to_lowercase(); - // Waiting — explicit confirmation + // Blocked — explicit confirmation if lower.contains("waiting for user confirmation") { - return AgentState::Waiting; + return AgentState::Blocked; } - // Waiting — box-drawing confirmation prompts + // Blocked — box-drawing confirmation prompts if content.contains("│ Apply this change") || content.contains("│ Allow execution") || content.contains("│ Do you want to proceed") { - return AgentState::Waiting; + return AgentState::Blocked; } if has_confirmation_prompt(&lower) { - return AgentState::Waiting; + return AgentState::Blocked; } - // Busy + // Working if lower.contains("esc to cancel") { - return AgentState::Busy; + return AgentState::Working; } AgentState::Idle @@ -238,24 +238,24 @@ fn detect_gemini(content: &str) -> AgentState { fn detect_cursor(content: &str) -> AgentState { let lower = content.to_lowercase(); - // Waiting + // Blocked if lower.contains("(y) (enter)") || lower.contains("keep (n)") || lower.contains("skip (esc or n)") { - return AgentState::Waiting; + return AgentState::Blocked; } // "allow ...(y)" or "run ...(y)" patterns if lower.contains("(y)") && (lower.contains("allow") || lower.contains("run")) { - return AgentState::Waiting; + return AgentState::Blocked; } - // Busy + // Working if lower.contains("ctrl+c to stop") { - return AgentState::Busy; + return AgentState::Working; } if has_cursor_spinner(content) { - return AgentState::Busy; + return AgentState::Working; } AgentState::Idle @@ -264,13 +264,13 @@ fn detect_cursor(content: &str) -> AgentState { fn detect_cline(content: &str) -> AgentState { let lower = content.to_lowercase(); - // Waiting + // Blocked if lower.contains("let cline use this tool") { - return AgentState::Waiting; + return AgentState::Blocked; } // [act mode] or [plan mode] followed by "yes" if (lower.contains("[act mode]") || lower.contains("[plan mode]")) && lower.contains("yes") { - return AgentState::Waiting; + return AgentState::Blocked; } // Idle @@ -278,19 +278,19 @@ fn detect_cline(content: &str) -> AgentState { return AgentState::Idle; } - // Cline defaults to busy (unlike most agents that default to idle) - AgentState::Busy + // Cline defaults to working (unlike most agents that default to idle) + AgentState::Working } fn detect_opencode(content: &str) -> AgentState { - // Waiting + // Blocked if content.contains("△ Permission required") { - return AgentState::Waiting; + return AgentState::Blocked; } - // Busy + // Working if has_interrupt_pattern(&content.to_lowercase()) { - return AgentState::Busy; + return AgentState::Working; } AgentState::Idle @@ -299,17 +299,17 @@ fn detect_opencode(content: &str) -> AgentState { fn detect_github_copilot(content: &str) -> AgentState { let lower = content.to_lowercase(); - // Waiting + // Blocked if lower.contains("│ do you want") { - return AgentState::Waiting; + return AgentState::Blocked; } if lower.contains("confirm with") && lower.contains("enter") { - return AgentState::Waiting; + return AgentState::Blocked; } - // Busy + // Working if lower.contains("esc to cancel") { - return AgentState::Busy; + return AgentState::Working; } AgentState::Idle @@ -318,7 +318,7 @@ fn detect_github_copilot(content: &str) -> AgentState { fn detect_kimi(content: &str) -> AgentState { let lower = content.to_lowercase(); - // Waiting + // Blocked if lower.contains("allow?") || lower.contains("confirm?") || lower.contains("approve?") @@ -326,10 +326,10 @@ fn detect_kimi(content: &str) -> AgentState { || lower.contains("[y/n]") || lower.contains("(y/n)") { - return AgentState::Waiting; + return AgentState::Blocked; } - // Busy + // Working if lower.contains("thinking") || lower.contains("processing") || lower.contains("generating") @@ -337,7 +337,7 @@ fn detect_kimi(content: &str) -> AgentState { || lower.contains("ctrl+c to cancel") || lower.contains("ctrl-c to cancel") { - return AgentState::Busy; + return AgentState::Working; } AgentState::Idle @@ -345,14 +345,14 @@ fn detect_kimi(content: &str) -> AgentState { /// Droid detection. /// -/// Busy: braille spinner line (⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏) + "Thinking..." + "(Press ESC to stop)" -/// Waiting: EXECUTE prompt with selection box ("Yes, allow" / "No, cancel") + +/// Working: braille spinner line (⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏) + "Thinking..." + "(Press ESC to stop)" +/// Blocked: EXECUTE prompt with selection box ("Yes, allow" / "No, cancel") + /// "Use ↑↓ to navigate, Enter to select" /// Idle: prompt box visible, no spinner, no selection prompt fn detect_droid(content: &str) -> AgentState { let lower = content.to_lowercase(); - // Waiting: EXECUTE approval prompt with selection UI chrome + // 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") @@ -361,21 +361,21 @@ fn detect_droid(content: &str) -> AgentState { let has_selection_options = lower.contains("> yes, allow") || lower.contains("> no, cancel"); if has_execute && (has_selection_chrome || has_selection_options) { - return AgentState::Waiting; + return AgentState::Blocked; } // Secondary: selection chrome + options together (no EXECUTE needed) if has_selection_chrome && has_selection_options { - return AgentState::Waiting; + return AgentState::Blocked; } - // Busy: braille spinner character at start of a line + "Thinking..." + // Working: braille spinner character at start of a line + "Thinking..." // The braille chars (⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏) are very specific — won't appear in normal content if has_braille_spinner(content) && lower.contains("esc to stop") { - return AgentState::Busy; + return AgentState::Working; } // Fallback: "ESC to stop" alone is still a strong signal (it's UI chrome) if lower.contains("esc to stop") { - return AgentState::Busy; + return AgentState::Working; } AgentState::Idle @@ -383,20 +383,20 @@ fn detect_droid(content: &str) -> AgentState { /// Amp (Sourcegraph) detection. /// -/// Screen layout when busy: +/// Screen layout when working: /// ```text /// ✓ Search Map the core runtime architecture... /// ⋯ Oracle ▼ /// ≈ Running tools... Esc to cancel /// ``` /// -/// "Esc to cancel" is the reliable busy indicator — it only appears +/// "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(); if lower.contains("esc to cancel") { - return AgentState::Busy; + return AgentState::Working; } AgentState::Idle @@ -623,15 +623,15 @@ mod tests { // ---- Workspace state rollup ---- #[test] - fn workspace_state_waiting_wins() { - let states = [AgentState::Idle, AgentState::Busy, AgentState::Waiting]; - assert_eq!(workspace_state(&states), AgentState::Waiting); + fn workspace_state_blocked_wins() { + let states = [AgentState::Idle, AgentState::Working, AgentState::Blocked]; + assert_eq!(workspace_state(&states), AgentState::Blocked); } #[test] - fn workspace_state_busy_over_idle() { - let states = [AgentState::Idle, AgentState::Busy, AgentState::Unknown]; - assert_eq!(workspace_state(&states), AgentState::Busy); + fn workspace_state_working_over_idle() { + let states = [AgentState::Idle, AgentState::Working, AgentState::Unknown]; + assert_eq!(workspace_state(&states), AgentState::Working); } #[test] @@ -649,13 +649,13 @@ mod tests { // ---- Pi ---- #[test] - fn pi_busy_when_working() { - assert_eq!(detect_pi("some output\nWorking..."), AgentState::Busy); + fn pi_working_when_working() { + assert_eq!(detect_pi("some output\nWorking..."), AgentState::Working); } #[test] - fn pi_busy_working_in_middle() { - assert_eq!(detect_pi("line1\nWorking...\nline3"), AgentState::Busy); + fn pi_working_working_in_middle() { + assert_eq!(detect_pi("line1\nWorking...\nline3"), AgentState::Working); } #[test] @@ -671,51 +671,51 @@ mod tests { // ---- Claude Code ---- #[test] - fn claude_busy_esc_to_interrupt() { + fn claude_working_esc_to_interrupt() { let screen = "Reading file src/main.rs\nesc to interrupt\n─────────\n❯ \n─────────"; - assert_eq!(detect_claude(screen), AgentState::Busy); + assert_eq!(detect_claude(screen), AgentState::Working); } #[test] - fn claude_busy_ctrl_c_to_interrupt() { + fn claude_working_ctrl_c_to_interrupt() { let screen = "Editing code\nctrl+c to interrupt\n─────────\n❯ \n─────────"; - assert_eq!(detect_claude(screen), AgentState::Busy); + assert_eq!(detect_claude(screen), AgentState::Working); } #[test] - fn claude_busy_spinner() { + fn claude_working_spinner() { let screen = "✽ Tempering…\n─────────\n❯ \n─────────"; - assert_eq!(detect_claude(screen), AgentState::Busy); + assert_eq!(detect_claude(screen), AgentState::Working); } #[test] - fn claude_busy_spinner_with_detail() { + fn claude_working_spinner_with_detail() { let screen = "✳ Simplifying recompute_tangents…\n─────────\n❯ \n─────────"; - assert_eq!(detect_claude(screen), AgentState::Busy); + assert_eq!(detect_claude(screen), AgentState::Working); } #[test] fn claude_waiting_do_you_want() { let screen = "Do you want to run this command?\n\nYes No"; - assert_eq!(detect_claude(screen), AgentState::Waiting); + assert_eq!(detect_claude(screen), AgentState::Blocked); } #[test] fn claude_waiting_would_you_like() { let screen = "Would you like to apply these changes?\n\n❯ Yes"; - assert_eq!(detect_claude(screen), AgentState::Waiting); + assert_eq!(detect_claude(screen), AgentState::Blocked); } #[test] fn claude_waiting_selection_prompt() { let screen = "Choose an option:\n❯ 1. Apply\n 2. Skip\n 3. Cancel"; - assert_eq!(detect_claude(screen), AgentState::Waiting); + assert_eq!(detect_claude(screen), AgentState::Blocked); } #[test] fn claude_waiting_esc_to_cancel() { let screen = "Allow bash: rm -rf /tmp/test?\n\nesc to cancel"; - assert_eq!(detect_claude(screen), AgentState::Waiting); + assert_eq!(detect_claude(screen), AgentState::Blocked); } #[test] @@ -731,10 +731,10 @@ mod tests { } #[test] - fn claude_busy_not_confused_by_old_prompt() { - // The "esc to interrupt" is ABOVE the prompt box — should be busy + fn claude_working_not_confused_by_old_prompt() { + // The "esc to interrupt" is ABOVE the prompt box — should be working let screen = "✽ Writing…\nesc to interrupt\n──────\n❯ \n──────"; - assert_eq!(detect_claude(screen), AgentState::Busy); + assert_eq!(detect_claude(screen), AgentState::Working); } // ---- Codex ---- @@ -743,28 +743,28 @@ mod tests { fn codex_waiting_confirm() { assert_eq!( detect_codex("press enter to confirm or esc to cancel"), - AgentState::Waiting + AgentState::Blocked ); } #[test] fn codex_waiting_allow_command() { - assert_eq!(detect_codex("allow command?\n[y/n]"), AgentState::Waiting); + assert_eq!(detect_codex("allow command?\n[y/n]"), AgentState::Blocked); } #[test] fn codex_waiting_submit_answer() { assert_eq!( detect_codex("Question about approach\n| enter to submit answer"), - AgentState::Waiting + AgentState::Blocked ); } #[test] - fn codex_busy_interrupt() { + fn codex_working_interrupt() { assert_eq!( detect_codex("generating code\nesc to interrupt"), - AgentState::Busy + AgentState::Working ); } @@ -779,7 +779,7 @@ mod tests { fn gemini_waiting_confirmation() { assert_eq!( detect_gemini("waiting for user confirmation"), - AgentState::Waiting + AgentState::Blocked ); } @@ -787,7 +787,7 @@ mod tests { fn gemini_waiting_apply() { assert_eq!( detect_gemini("│ Apply this change\n│ Yes │ No"), - AgentState::Waiting + AgentState::Blocked ); } @@ -795,15 +795,15 @@ mod tests { fn gemini_waiting_allow_execution() { assert_eq!( detect_gemini("│ Allow execution of: rm test.txt"), - AgentState::Waiting + AgentState::Blocked ); } #[test] - fn gemini_busy() { + fn gemini_working() { assert_eq!( detect_gemini("thinking...\nesc to cancel"), - AgentState::Busy + AgentState::Working ); } @@ -818,25 +818,25 @@ mod tests { fn cursor_waiting_accept() { assert_eq!( detect_cursor("Apply changes? (y) (enter) or keep (n)"), - AgentState::Waiting + AgentState::Blocked ); } #[test] fn cursor_waiting_allow() { - assert_eq!(detect_cursor("allow file edit (y)"), AgentState::Waiting); + assert_eq!(detect_cursor("allow file edit (y)"), AgentState::Blocked); } #[test] - fn cursor_busy_spinner() { - assert_eq!(detect_cursor("⬡ Grepping.."), AgentState::Busy); + fn cursor_working_spinner() { + assert_eq!(detect_cursor("⬡ Grepping.."), AgentState::Working); } #[test] - fn cursor_busy_ctrl_c() { + fn cursor_working_ctrl_c() { assert_eq!( detect_cursor("processing\nctrl+c to stop"), - AgentState::Busy + AgentState::Working ); } @@ -849,14 +849,14 @@ mod tests { #[test] fn cline_waiting_tool_use() { - assert_eq!(detect_cline("let cline use this tool"), AgentState::Waiting); + assert_eq!(detect_cline("let cline use this tool"), AgentState::Blocked); } #[test] fn cline_waiting_act_mode() { assert_eq!( detect_cline("[act mode] execute command?\nyes"), - AgentState::Waiting + AgentState::Blocked ); } @@ -869,9 +869,9 @@ mod tests { } #[test] - fn cline_defaults_to_busy() { - // Cline's default is busy (unlike other agents) - assert_eq!(detect_cline("some random output"), AgentState::Busy); + fn cline_defaults_to_working() { + // Cline's default is working (unlike other agents) + assert_eq!(detect_cline("some random output"), AgentState::Working); } // ---- OpenCode ---- @@ -880,15 +880,15 @@ mod tests { fn opencode_waiting_permission() { assert_eq!( detect_opencode("△ Permission required"), - AgentState::Waiting + AgentState::Blocked ); } #[test] - fn opencode_busy() { + fn opencode_working() { assert_eq!( detect_opencode("running tool\nesc to interrupt"), - AgentState::Busy + AgentState::Working ); } @@ -903,7 +903,7 @@ mod tests { fn copilot_waiting_confirm() { assert_eq!( detect_github_copilot("confirm with enter"), - AgentState::Waiting + AgentState::Blocked ); } @@ -911,15 +911,15 @@ mod tests { fn copilot_waiting_do_you_want() { assert_eq!( detect_github_copilot("│ do you want to apply?"), - AgentState::Waiting + AgentState::Blocked ); } #[test] - fn copilot_busy() { + fn copilot_working() { assert_eq!( detect_github_copilot("generating\nesc to cancel"), - AgentState::Busy + AgentState::Working ); } @@ -932,22 +932,22 @@ mod tests { #[test] fn kimi_waiting_approve() { - assert_eq!(detect_kimi("approve?"), AgentState::Waiting); + assert_eq!(detect_kimi("approve?"), AgentState::Blocked); } #[test] fn kimi_waiting_yn() { - assert_eq!(detect_kimi("continue? [y/n]"), AgentState::Waiting); + assert_eq!(detect_kimi("continue? [y/n]"), AgentState::Blocked); } #[test] - fn kimi_busy_thinking() { - assert_eq!(detect_kimi("thinking"), AgentState::Busy); + fn kimi_working_thinking() { + assert_eq!(detect_kimi("thinking"), AgentState::Working); } #[test] - fn kimi_busy_generating() { - assert_eq!(detect_kimi("generating code"), AgentState::Busy); + fn kimi_working_generating() { + assert_eq!(detect_kimi("generating code"), AgentState::Working); } #[test] @@ -958,16 +958,16 @@ mod tests { // ---- Droid ---- #[test] - fn droid_busy_thinking_with_spinner() { + fn droid_working_thinking_with_spinner() { let screen = "> how u doin\n\n⠴ Thinking... (Press ESC to stop)\n\nAuto (Off)"; - assert_eq!(detect_droid(screen), AgentState::Busy); + assert_eq!(detect_droid(screen), AgentState::Working); } #[test] - fn droid_busy_esc_to_stop_alone() { - // ESC to stop without spinner is still busy (UI chrome) + fn droid_working_esc_to_stop_alone() { + // ESC to stop without spinner is still working (UI chrome) let screen = "Processing\n(Press ESC to stop)"; - assert_eq!(detect_droid(screen), AgentState::Busy); + assert_eq!(detect_droid(screen), AgentState::Working); } #[test] @@ -982,18 +982,18 @@ mod tests { "╰────────────────────╯\n", " Use ↑↓ to navigate, Enter to select, Esc to cancel\n", ); - assert_eq!(detect_droid(screen), AgentState::Waiting); + assert_eq!(detect_droid(screen), AgentState::Blocked); } #[test] fn droid_waiting_selection_with_chrome() { let screen = "│ > Yes, allow │\n│ No, cancel │\n Use ↑↓ to navigate, Enter to select, Esc to cancel"; - assert_eq!(detect_droid(screen), AgentState::Waiting); + assert_eq!(detect_droid(screen), AgentState::Blocked); } #[test] fn droid_not_waiting_on_options_text_alone() { - // "Yes, allow" in normal conversation should NOT trigger waiting + // "Yes, allow" in normal conversation should NOT trigger blocked let screen = "The user said > Yes, allow the changes"; assert_eq!(detect_droid(screen), AgentState::Idle); } @@ -1034,9 +1034,9 @@ mod tests { // ---- Amp ---- #[test] - fn amp_busy_running_tools() { + fn amp_working_running_tools() { let screen = " ✓ Search Map the core runtime architecture\n ⋯ Oracle ▼\n ≈ Running tools... Esc to cancel"; - assert_eq!(detect_state(Some(Agent::Amp), screen), AgentState::Busy); + assert_eq!(detect_state(Some(Agent::Amp), screen), AgentState::Working); } #[test] @@ -1224,7 +1224,7 @@ mod tests { let mut parser = vt100::Parser::new(24, 80, 0); parser.process(b"Working..."); let content = parser.screen().contents(); - assert_eq!(detect_pi(&content), AgentState::Busy); + assert_eq!(detect_pi(&content), AgentState::Working); } #[test] @@ -1233,7 +1233,7 @@ mod tests { // Red "Working..." with ANSI codes — contents() strips formatting parser.process(b"\x1b[31mWorking...\x1b[0m"); let content = parser.screen().contents(); - assert_eq!(detect_pi(&content), AgentState::Busy); + assert_eq!(detect_pi(&content), AgentState::Working); } #[test] diff --git a/src/pane.rs b/src/pane.rs index 6c346f20..9d62a0c9 100644 --- a/src/pane.rs +++ b/src/pane.rs @@ -15,30 +15,30 @@ use crate::events::AppEvent; use crate::layout::PaneId; use crate::pty_callbacks::PtyResponses; -const CLAUDE_BUSY_HOLD: std::time::Duration = std::time::Duration::from_millis(1200); +const CLAUDE_WORKING_HOLD: std::time::Duration = std::time::Duration::from_millis(1200); fn stabilize_agent_state( agent: Option<Agent>, previous: AgentState, raw: AgentState, now: std::time::Instant, - last_claude_busy_at: &mut Option<std::time::Instant>, + last_claude_working_at: &mut Option<std::time::Instant>, ) -> AgentState { if agent != Some(Agent::Claude) { return raw; } match raw { - AgentState::Busy => { - *last_claude_busy_at = Some(now); - AgentState::Busy + AgentState::Working => { + *last_claude_working_at = Some(now); + AgentState::Working } - AgentState::Waiting => AgentState::Waiting, - AgentState::Idle if previous == AgentState::Busy => { - if last_claude_busy_at - .is_some_and(|last_busy| now.duration_since(last_busy) < CLAUDE_BUSY_HOLD) + AgentState::Blocked => AgentState::Blocked, + AgentState::Idle if previous == AgentState::Working => { + if last_claude_working_at + .is_some_and(|last_working| now.duration_since(last_working) < CLAUDE_WORKING_HOLD) { - AgentState::Busy + AgentState::Working } else { AgentState::Idle } @@ -301,7 +301,7 @@ impl PaneRuntime { let mut agent: Option<Agent> = None; let mut state = AgentState::Unknown; let mut last_process_check = Instant::now(); - let mut last_claude_busy_at = None; + let mut last_claude_working_at = None; tokio::time::sleep(Duration::from_millis(50)).await; @@ -359,7 +359,7 @@ impl PaneRuntime { state, raw_state, now, - &mut last_claude_busy_at, + &mut last_claude_working_at, ); if new_state != state || agent_changed { @@ -563,40 +563,40 @@ mod tests { } #[test] - fn claude_busy_is_sticky_for_short_gap() { + fn claude_working_is_sticky_for_short_gap() { let now = std::time::Instant::now(); - let mut last_busy = None; + let mut last_working = None; - let busy = stabilize_agent_state( + let working = stabilize_agent_state( Some(Agent::Claude), AgentState::Idle, - AgentState::Busy, + AgentState::Working, now, - &mut last_busy, + &mut last_working, ); - assert_eq!(busy, AgentState::Busy); + assert_eq!(working, AgentState::Working); - let still_busy = stabilize_agent_state( + let still_working = stabilize_agent_state( Some(Agent::Claude), - AgentState::Busy, + AgentState::Working, AgentState::Idle, now + std::time::Duration::from_millis(400), - &mut last_busy, + &mut last_working, ); - assert_eq!(still_busy, AgentState::Busy); + assert_eq!(still_working, AgentState::Working); } #[test] fn claude_transitions_to_idle_after_hold_expires() { let now = std::time::Instant::now(); - let mut last_busy = Some(now); + let mut last_working = Some(now); let state = stabilize_agent_state( Some(Agent::Claude), - AgentState::Busy, + AgentState::Working, AgentState::Idle, - now + CLAUDE_BUSY_HOLD + std::time::Duration::from_millis(1), - &mut last_busy, + now + CLAUDE_WORKING_HOLD + std::time::Duration::from_millis(1), + &mut last_working, ); assert_eq!(state, AgentState::Idle); } @@ -604,14 +604,14 @@ mod tests { #[test] fn non_claude_states_are_unchanged() { let now = std::time::Instant::now(); - let mut last_busy = None; + let mut last_working = None; let state = stabilize_agent_state( Some(Agent::Codex), - AgentState::Busy, + AgentState::Working, AgentState::Idle, now, - &mut last_busy, + &mut last_working, ); assert_eq!(state, AgentState::Idle); } diff --git a/src/sound.rs b/src/sound.rs index 8f769701..aa6fffe6 100644 --- a/src/sound.rs +++ b/src/sound.rs @@ -14,7 +14,7 @@ static SOUND_REQUEST: &[u8] = include_bytes!("../assets/sounds/request.mp3"); pub enum Sound { /// Agent finished work (transitioned to Idle). Done, - /// Agent needs input (transitioned to Waiting). + /// Agent needs input (transitioned to Blocked). Request, } @@ -1652,12 +1652,12 @@ fn render_toast_notification( /// /// | State | Icon | Color | /// |--------------------|------|--------| -/// | Busy | ● | Yellow | +/// | Working | ● | Yellow | /// | Done (idle+unseen) | ● | Blue | /// | Idle (seen) | ○ | Green | /// | Unknown | · | Gray | /// -/// Filled dot = needs attention (working, or finished unseen). +/// Filled dot = needs attention (blocked/working, or finished unseen). /// Hollow dot = nothing to do here. fn render_config_diagnostic(frame: &mut Frame, area: Rect, message: &str, p: &Palette) { let text = format!(" config warning: {message} "); @@ -1685,8 +1685,8 @@ fn render_config_diagnostic(frame: &mut Frame, area: Rect, message: &str, p: &Pa /// Compact dot icon for workspace-level aggregate state (top section). fn state_dot(state: AgentState, seen: bool, p: &Palette) -> (&'static str, Style) { match (state, seen) { - (AgentState::Waiting, _) => ("●", Style::default().fg(p.red)), - (AgentState::Busy, _) => ("●", Style::default().fg(p.yellow)), + (AgentState::Blocked, _) => ("●", Style::default().fg(p.red)), + (AgentState::Working, _) => ("●", Style::default().fg(p.yellow)), (AgentState::Idle, false) => ("●", Style::default().fg(p.teal)), (AgentState::Idle, true) => ("○", Style::default().fg(p.green)), (AgentState::Unknown, _) => ("·", Style::default().fg(p.overlay0)), @@ -1694,11 +1694,11 @@ fn state_dot(state: AgentState, seen: bool, p: &Palette) -> (&'static str, Style } /// Rich icon for per-pane agent detail (bottom section). -/// Uses animated spinner for busy state. +/// Uses animated spinner for the working state. fn agent_icon(state: AgentState, seen: bool, tick: u32, p: &Palette) -> (&'static str, Style) { match (state, seen) { - (AgentState::Waiting, _) => ("◉", Style::default().fg(p.red)), - (AgentState::Busy, _) => (spinner_frame(tick), Style::default().fg(p.yellow)), + (AgentState::Blocked, _) => ("◉", Style::default().fg(p.red)), + (AgentState::Working, _) => (spinner_frame(tick), Style::default().fg(p.yellow)), (AgentState::Idle, false) => ("●", Style::default().fg(p.teal)), (AgentState::Idle, true) => ("✓", Style::default().fg(p.green)), (AgentState::Unknown, _) => ("○", Style::default().fg(p.overlay0)), @@ -1708,8 +1708,8 @@ fn agent_icon(state: AgentState, seen: bool, tick: u32, p: &Palette) -> (&'stati /// State label for the agent detail panel. fn state_label(state: AgentState, seen: bool) -> &'static str { match (state, seen) { - (AgentState::Waiting, _) => "waiting", - (AgentState::Busy, _) => "running", + (AgentState::Blocked, _) => "blocked", + (AgentState::Working, _) => "working", (AgentState::Idle, false) => "done", (AgentState::Idle, true) => "idle", (AgentState::Unknown, _) => "idle", @@ -1719,8 +1719,8 @@ fn state_label(state: AgentState, seen: bool) -> &'static str { /// Color for the state label text. fn state_label_color(state: AgentState, seen: bool, p: &Palette) -> Color { match (state, seen) { - (AgentState::Waiting, _) => p.red, - (AgentState::Busy, _) => p.yellow, + (AgentState::Blocked, _) => p.red, + (AgentState::Working, _) => p.yellow, (AgentState::Idle, false) => p.teal, (AgentState::Idle, true) => p.green, (AgentState::Unknown, _) => p.overlay0, diff --git a/src/workspace.rs b/src/workspace.rs index 7bda8f23..909c4315 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -233,7 +233,7 @@ impl Workspace { } }) .collect(); - // Sort by urgency: waiting > busy > idle(unseen) > idle > unknown + // Sort by urgency: blocked > working > idle(unseen) > idle > unknown details.sort_by(|a, b| b.urgency().cmp(&a.urgency())); details } @@ -268,8 +268,8 @@ impl PaneDetail { /// Urgency score for sort ordering (higher = more urgent, shown first). fn urgency(&self) -> u8 { match (self.state, self.seen) { - (AgentState::Waiting, _) => 4, - (AgentState::Busy, _) => 3, + (AgentState::Blocked, _) => 4, + (AgentState::Working, _) => 3, (AgentState::Idle, false) => 2, // unseen/done (AgentState::Idle, true) => 1, (AgentState::Unknown, _) => 0, @@ -428,10 +428,10 @@ mod tests { let root_id = *ws.panes.keys().find(|id| **id != id2).unwrap(); ws.panes.get_mut(&root_id).unwrap().state = AgentState::Idle; - ws.panes.get_mut(&id2).unwrap().state = AgentState::Busy; + ws.panes.get_mut(&id2).unwrap().state = AgentState::Working; let (state, _) = ws.aggregate_state(); - assert_eq!(state, AgentState::Busy); + assert_eq!(state, AgentState::Working); } #[test] @@ -471,11 +471,11 @@ mod tests { let mut ws = Workspace::test_new("test"); let id2 = ws.test_split(Direction::Horizontal); - ws.panes.get_mut(&id2).unwrap().state = AgentState::Waiting; + ws.panes.get_mut(&id2).unwrap().state = AgentState::Blocked; let states = ws.pane_states(); assert_eq!(states.len(), 2); - assert!(states.iter().any(|(s, _)| *s == AgentState::Waiting)); + assert!(states.iter().any(|(s, _)| *s == AgentState::Blocked)); assert!(states.iter().any(|(s, _)| *s == AgentState::Unknown)); } |
