aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-03-30 13:43:41 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-03-30 13:43:41 +0300
commit0426bf53d798a182bff3a5f315ffb49f1637115a (patch)
treee1b66954ac02183174ce9ba9a8066daeb31cddb0 /src
parente15dfae600a0e6ef319b4244aba0aeaf01dc9b53 (diff)
refactor: rename agent states to working and blocked
Diffstat (limited to 'src')
-rw-r--r--src/api/schema.rs12
-rw-r--r--src/app/actions.rs26
-rw-r--r--src/app/mod.rs4
-rw-r--r--src/app/state.rs4
-rw-r--r--src/cli.rs10
-rw-r--r--src/detect.rs262
-rw-r--r--src/pane.rs58
-rw-r--r--src/sound.rs2
-rw-r--r--src/ui.rs22
-rw-r--r--src/workspace.rs14
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 {
&params.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,
diff --git a/src/cli.rs b/src/cli.rs
index 6ce6853e..bbcd1abf 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -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,
}
diff --git a/src/ui.rs b/src/ui.rs
index 114905a7..99a8d38b 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -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));
}