fix(tui): show Bash for shell work in visible UI

This commit is contained in:
CodeWhale Agent
2026-06-12 16:32:32 -07:00
parent f870eb3e19
commit 738a265197
5 changed files with 62 additions and 32 deletions
+8 -8
View File
@@ -1370,7 +1370,7 @@ fn english(id: MessageId) -> &'static str {
MessageId::CmdInitDescription => "Generate AGENTS.md for project",
MessageId::CmdLspDescription => "Toggle LSP diagnostics on or off",
MessageId::CmdShareDescription => "Export current session as a shareable web URL",
MessageId::CmdJobsDescription => "Inspect and control background commands",
MessageId::CmdJobsDescription => "Inspect and control background Bash jobs",
MessageId::CmdLinksDescription => "Show DeepSeek dashboard and docs links",
MessageId::CmdLoadDescription => "Load session from file",
MessageId::CmdLogoutDescription => "Clear API key and return to setup",
@@ -1700,7 +1700,7 @@ fn english(id: MessageId) -> &'static str {
MessageId::ApprovalRiskDestructive => "DESTRUCTIVE",
MessageId::ApprovalCategorySafe => "Safe",
MessageId::ApprovalCategoryFileWrite => "File Write",
MessageId::ApprovalCategoryShell => "Shell Command",
MessageId::ApprovalCategoryShell => "Bash",
MessageId::ApprovalCategoryNetwork => "Network",
MessageId::ApprovalCategoryMcpRead => "MCP Read",
MessageId::ApprovalCategoryMcpAction => "MCP Action",
@@ -2327,7 +2327,7 @@ fn vietnamese(id: MessageId) -> Option<&'static str> {
MessageId::ApprovalRiskDestructive => "NGUY HẠI",
MessageId::ApprovalCategorySafe => "An toàn",
MessageId::ApprovalCategoryFileWrite => "Ghi Tệp",
MessageId::ApprovalCategoryShell => "Lệnh Shell",
MessageId::ApprovalCategoryShell => "Bash",
MessageId::ApprovalCategoryNetwork => "Mạng",
MessageId::ApprovalCategoryMcpRead => "Đọc MCP",
MessageId::ApprovalCategoryMcpAction => "Hành động MCP",
@@ -2484,7 +2484,7 @@ fn traditional_chinese(id: MessageId) -> Option<&'static str> {
MessageId::ApprovalRiskDestructive => "破壞性",
MessageId::ApprovalCategorySafe => "安全",
MessageId::ApprovalCategoryFileWrite => "檔案寫入",
MessageId::ApprovalCategoryShell => "Shell 命令",
MessageId::ApprovalCategoryShell => "Bash",
MessageId::ApprovalCategoryNetwork => "網路",
MessageId::ApprovalCategoryMcpRead => "MCP 讀取",
MessageId::ApprovalCategoryMcpAction => "MCP 操作",
@@ -3090,7 +3090,7 @@ fn japanese(id: MessageId) -> Option<&'static str> {
MessageId::ApprovalRiskDestructive => "破壊的操作",
MessageId::ApprovalCategorySafe => "安全",
MessageId::ApprovalCategoryFileWrite => "ファイル書き込み",
MessageId::ApprovalCategoryShell => "シェルコマンド",
MessageId::ApprovalCategoryShell => "Bash",
MessageId::ApprovalCategoryNetwork => "ネットワーク",
MessageId::ApprovalCategoryMcpRead => "MCP読み取り",
MessageId::ApprovalCategoryMcpAction => "MCPアクション",
@@ -3619,7 +3619,7 @@ fn chinese_simplified(id: MessageId) -> Option<&'static str> {
MessageId::ApprovalRiskDestructive => "破坏性",
MessageId::ApprovalCategorySafe => "安全",
MessageId::ApprovalCategoryFileWrite => "文件写入",
MessageId::ApprovalCategoryShell => "Shell 命令",
MessageId::ApprovalCategoryShell => "Bash",
MessageId::ApprovalCategoryNetwork => "网络",
MessageId::ApprovalCategoryMcpRead => "MCP 读取",
MessageId::ApprovalCategoryMcpAction => "MCP 操作",
@@ -4214,7 +4214,7 @@ fn portuguese_brazil(id: MessageId) -> Option<&'static str> {
MessageId::ApprovalRiskDestructive => "DESTRUTIVO",
MessageId::ApprovalCategorySafe => "Seguro",
MessageId::ApprovalCategoryFileWrite => "Escrita de Arquivo",
MessageId::ApprovalCategoryShell => "Comando Shell",
MessageId::ApprovalCategoryShell => "Bash",
MessageId::ApprovalCategoryNetwork => "Rede",
MessageId::ApprovalCategoryMcpRead => "Leitura MCP",
MessageId::ApprovalCategoryMcpAction => "Ação MCP",
@@ -4835,7 +4835,7 @@ fn spanish_latin_america(id: MessageId) -> Option<&'static str> {
MessageId::ApprovalRiskDestructive => "DESTRUCTIVO",
MessageId::ApprovalCategorySafe => "Seguro",
MessageId::ApprovalCategoryFileWrite => "Escritura de Archivo",
MessageId::ApprovalCategoryShell => "Comando Shell",
MessageId::ApprovalCategoryShell => "Bash",
MessageId::ApprovalCategoryNetwork => "Red",
MessageId::ApprovalCategoryMcpRead => "Lectura MCP",
MessageId::ApprovalCategoryMcpAction => "Acción MCP",
+16 -2
View File
@@ -5108,11 +5108,12 @@ async fn run_subagent(
);
let mut tool_results: Vec<ContentBlock> = Vec::new();
for (tool_id, tool_name, tool_input) in tool_uses {
let tool_display_name = subagent_progress_tool_display_name(&tool_name);
record_agent_progress(
runtime,
&agent_id,
format!(
"{}: running tool '{tool_name}'",
"{}: running tool '{tool_display_name}'",
format_step_counter(steps, max_steps)
),
);
@@ -5139,7 +5140,7 @@ async fn run_subagent(
runtime,
&agent_id,
format!(
"{}: finished tool '{tool_name}'",
"{}: finished tool '{tool_display_name}'",
format_step_counter(steps, max_steps)
),
);
@@ -6046,6 +6047,19 @@ fn parse_progress_tool_name(message: &str) -> Option<String> {
(!tool.is_empty()).then(|| tool.to_string())
}
fn subagent_progress_tool_display_name(name: &str) -> &str {
match name {
"exec_shell"
| "exec_shell_wait"
| "exec_shell_interact"
| "exec_wait"
| "exec_interact"
| "task_shell_start"
| "task_shell_wait" => "Bash",
_ => name,
}
}
fn emit_agent_progress(
event_tx: Option<&mpsc::Sender<Event>>,
mailbox: Option<&Mailbox>,
+14
View File
@@ -106,6 +106,20 @@ fn headless_worker_record_tracks_lifecycle_without_tui_projection() {
);
}
#[test]
fn subagent_progress_displays_shell_tools_as_bash() {
assert_eq!(subagent_progress_tool_display_name("exec_shell"), "Bash");
assert_eq!(subagent_progress_tool_display_name("exec_wait"), "Bash");
assert_eq!(
subagent_progress_tool_display_name("task_shell_wait"),
"Bash"
);
assert_eq!(
subagent_progress_tool_display_name("read_file"),
"read_file"
);
}
#[test]
fn headless_worker_records_persist_with_subagent_state() {
let tmp = tempdir().expect("tempdir");
+6 -4
View File
@@ -31,11 +31,11 @@ fn format_elapsed(ms: u64) -> String {
pub(super) fn format_shell_job_list(jobs: &[ShellJobSnapshot]) -> String {
if jobs.is_empty() {
return "No live background commands. Commands are process-local; after a restart, inspect durable task artifacts for prior command output.".to_string();
return "No live Bash jobs. Bash jobs are process-local; after a restart, inspect durable task artifacts for prior command output.".to_string();
}
let mut lines = vec![
format!("Background commands ({})", jobs.len()),
format!("Bash jobs ({})", jobs.len()),
"----------------------------------------".to_string(),
];
for job in jobs {
@@ -73,7 +73,7 @@ pub(super) fn format_shell_job_list(jobs: &[ShellJobSnapshot]) -> String {
pub(super) fn format_shell_poll(result: &ShellResult) -> String {
let mut lines = vec![
format!(
"Command {}: {} exit={:?} elapsed={}",
"Bash job {}: {} exit={:?} elapsed={}",
result.task_id.as_deref().unwrap_or("(unknown)"),
status_label(&result.status, false),
result.exit_code,
@@ -104,7 +104,7 @@ pub(super) fn open_shell_job_pager(app: &mut App, detail: &ShellJobDetail) {
.unwrap_or(100)
.saturating_sub(4);
app.view_stack.push(PagerView::from_text(
format!("Shell Job {}", detail.snapshot.id),
format!("Bash Job {}", detail.snapshot.id),
&format_shell_job_detail(detail),
width.max(60),
));
@@ -174,6 +174,8 @@ mod tests {
linked_task_id: Some("task_1".to_string()),
}];
let formatted = format_shell_job_list(&jobs);
assert!(formatted.contains("Bash jobs (1)"));
assert!(!formatted.contains("Background commands"));
assert!(formatted.contains("shell_dead"));
assert!(formatted.contains("stale"));
assert!(formatted.contains("/jobs poll <id>"));
+18 -18
View File
@@ -859,11 +859,11 @@ fn task_panel_rows(
.count();
let done = background_rows.len().saturating_sub(running);
let label = if running == 0 {
format!("Background commands: {done} completed")
format!("Bash jobs: {done} completed")
} else if done == 0 {
format!("Background commands: {running} running")
format!("Bash jobs: {running} running")
} else {
format!("Background commands: {running} running, {done} completed")
format!("Bash jobs: {running} running, {done} completed")
};
lines.push(Line::from(Span::styled(
label,
@@ -985,11 +985,11 @@ fn task_panel_hover_texts(app: &App, max_rows: usize) -> Vec<String> {
.count();
let done = background_rows.len().saturating_sub(running);
let label = if running == 0 {
format!("Background commands: {done} completed")
format!("Bash jobs: {done} completed")
} else if done == 0 {
format!("Background commands: {running} running")
format!("Bash jobs: {running} running")
} else {
format!("Background commands: {running} running, {done} completed")
format!("Bash jobs: {running} running, {done} completed")
};
texts.push(label);
@@ -1136,8 +1136,8 @@ fn background_task_labels(task: &TaskPanelEntry, duration: &str) -> (String, Str
if let Some(command) = task.prompt_summary.strip_prefix("shell: ") {
let command = concise_shell_command_label(command, 96);
return (
format!("{} {} {}", task.status, command, duration),
format!("{} \u{00B7} command", task.id),
format!("Bash {} {} {}", task.status, command, duration),
format!("{} \u{00B7} Bash", task.id),
);
}
@@ -1482,9 +1482,9 @@ fn failure_summary_with_hint(summary: &str) -> String {
fn friendly_generic_tool_name(name: &str) -> &str {
match name {
"task_shell_start" => "start command",
"task_shell_wait" => "wait command",
"task_shell_write" => "write command",
"task_shell_start" => "start Bash",
"task_shell_wait" => "wait Bash",
"task_shell_write" => "write Bash",
_ => name,
}
}
@@ -1493,7 +1493,7 @@ fn generic_tool_sidebar_summary(generic: &GenericToolCell) -> String {
match generic.name.as_str() {
"task_shell_start" => compact_join([
generic.input_summary.clone().unwrap_or_default(),
"background command".to_string(),
"background Bash".to_string(),
]),
"task_shell_wait" => compact_join([
generic.input_summary.clone().unwrap_or_default(),
@@ -1772,7 +1772,7 @@ fn is_ci_poll_row(row: &SidebarToolRow) -> bool {
}
fn is_shell_wait_poll_row(row: &SidebarToolRow) -> bool {
row.status == ToolStatus::Running && row.name == "wait command"
row.status == ToolStatus::Running && row.name == "wait Bash"
}
fn shell_wait_poll_key(row: &SidebarToolRow) -> String {
@@ -3214,7 +3214,7 @@ mod tests {
"running shell command should not render as both live and background: {text:?}"
);
assert!(
!text.iter().any(|line| line.contains("Background commands")),
!text.iter().any(|line| line.contains("Bash jobs")),
"duplicate background shell row should be hidden: {text:?}"
);
}
@@ -3267,7 +3267,7 @@ mod tests {
"reasoning row should show live thinking duration: {text:?}"
);
assert!(
!text.iter().any(|line| line.contains("Background commands")),
!text.iter().any(|line| line.contains("Bash jobs")),
"reasoning must not be counted as a background command: {text:?}"
);
}
@@ -3327,7 +3327,7 @@ mod tests {
let header_idx = text
.iter()
.position(|line| line.starts_with("Background commands"))
.position(|line| line.starts_with("Bash jobs"))
.expect("background header row");
assert!(actions[header_idx].is_none(), "header is not clickable");
@@ -3714,7 +3714,7 @@ mod tests {
let text = lines_to_text(&task_panel_lines(&app, 80, 6));
assert!(
text.iter().any(|line| line.contains("[~] wait command")),
text.iter().any(|line| line.contains("[~] wait Bash")),
"shell helper should render as a user-facing activity: {text:?}"
);
assert!(
@@ -3748,7 +3748,7 @@ mod tests {
assert_eq!(
text.iter()
.filter(|line| line.contains("[~] wait command"))
.filter(|line| line.contains("[~] wait Bash"))
.count(),
1,
"duplicate waits for the same shell job should collapse: {text:?}"