fix(cli): improve terminal output formatting and visual clarity (#8025)
Signed-off-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
@@ -187,8 +187,7 @@ async fn offer_extension_debugging_help(
|
|||||||
// Create a debugging prompt with context about the extension failure
|
// Create a debugging prompt with context about the extension failure
|
||||||
let debug_prompt = format!(
|
let debug_prompt = format!(
|
||||||
"I'm having trouble starting an extension called '{}'. Here's the error I encountered:\n\n{}\n\nCan you help me diagnose what might be wrong and suggest how to fix it? Please consider common issues like:\n- Missing dependencies or tools\n- Configuration problems\n- Network connectivity (for remote extensions)\n- Permission issues\n- Path or environment variable problems",
|
"I'm having trouble starting an extension called '{}'. Here's the error I encountered:\n\n{}\n\nCan you help me diagnose what might be wrong and suggest how to fix it? Please consider common issues like:\n- Missing dependencies or tools\n- Configuration problems\n- Network connectivity (for remote extensions)\n- Permission issues\n- Path or environment variable problems",
|
||||||
extension_name,
|
extension_name, error_message
|
||||||
error_message
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a minimal agent for debugging
|
// Create a minimal agent for debugging
|
||||||
|
|||||||
@@ -207,7 +207,9 @@ pub async fn classify_planner_response(
|
|||||||
message_text: String,
|
message_text: String,
|
||||||
provider: Arc<dyn Provider>,
|
provider: Arc<dyn Provider>,
|
||||||
) -> Result<PlannerResponseType> {
|
) -> Result<PlannerResponseType> {
|
||||||
let prompt = format!("The text below is the output from an AI model which can either provide a plan or list of clarifying questions. Based on the text below, decide if the output is a \"plan\" or \"clarifying questions\".\n---\n{message_text}");
|
let prompt = format!(
|
||||||
|
"The text below is the output from an AI model which can either provide a plan or list of clarifying questions. Based on the text below, decide if the output is a \"plan\" or \"clarifying questions\".\n---\n{message_text}"
|
||||||
|
);
|
||||||
|
|
||||||
let message = Message::user().with_text(&prompt);
|
let message = Message::user().with_text(&prompt);
|
||||||
let model_config = provider.get_model_config();
|
let model_config = provider.get_model_config();
|
||||||
@@ -628,6 +630,7 @@ impl CliSession {
|
|||||||
|
|
||||||
let _provider = self.agent.provider().await?;
|
let _provider = self.agent.provider().await?;
|
||||||
|
|
||||||
|
println!();
|
||||||
output::run_status_hook("thinking");
|
output::run_status_hook("thinking");
|
||||||
output::show_thinking();
|
output::show_thinking();
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
@@ -1751,7 +1754,9 @@ fn display_log_notification(
|
|||||||
let _ = progress_bars.hide();
|
let _ = progress_bars.hide();
|
||||||
}
|
}
|
||||||
if !is_json_mode {
|
if !is_json_mode {
|
||||||
print!("{}", formatted_message);
|
for line in formatted_message.lines() {
|
||||||
|
println!(" {}", console::style(line).dim());
|
||||||
|
}
|
||||||
std::io::stdout().flush().unwrap();
|
std::io::stdout().flush().unwrap();
|
||||||
}
|
}
|
||||||
} else if ntype == "shell_output" {
|
} else if ntype == "shell_output" {
|
||||||
@@ -1766,7 +1771,7 @@ fn display_log_notification(
|
|||||||
let _ = progress_bars.hide();
|
let _ = progress_bars.hide();
|
||||||
}
|
}
|
||||||
if !is_json_mode {
|
if !is_json_mode {
|
||||||
println!("{}", formatted_message);
|
println!(" {}", console::style(formatted_message).dim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ pub fn render_message(message: &Message, debug: bool) {
|
|||||||
},
|
},
|
||||||
MessageContent::Text(text) => print_markdown(&text.text, theme),
|
MessageContent::Text(text) => print_markdown(&text.text, theme),
|
||||||
MessageContent::ToolRequest(req) => render_tool_request(req, theme, debug),
|
MessageContent::ToolRequest(req) => render_tool_request(req, theme, debug),
|
||||||
MessageContent::ToolResponse(resp) => render_tool_response(resp, theme, debug),
|
MessageContent::ToolResponse(resp) => render_tool_response(resp, debug),
|
||||||
MessageContent::Image(image) => {
|
MessageContent::Image(image) => {
|
||||||
println!("Image: [data: {}, type: {}]", image.data, image.mime_type);
|
println!("Image: [data: {}, type: {}]", image.data, image.mime_type);
|
||||||
}
|
}
|
||||||
@@ -295,7 +295,7 @@ pub fn render_message_streaming(
|
|||||||
}
|
}
|
||||||
MessageContent::ToolResponse(resp) => {
|
MessageContent::ToolResponse(resp) => {
|
||||||
flush_markdown_buffer(buffer, theme);
|
flush_markdown_buffer(buffer, theme);
|
||||||
render_tool_response(resp, theme, debug);
|
render_tool_response(resp, debug);
|
||||||
}
|
}
|
||||||
MessageContent::ActionRequired(action) => {
|
MessageContent::ActionRequired(action) => {
|
||||||
flush_markdown_buffer(buffer, theme);
|
flush_markdown_buffer(buffer, theme);
|
||||||
@@ -491,7 +491,7 @@ fn render_tool_request(req: &ToolRequest, theme: Theme, debug: bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_tool_response(resp: &ToolResponse, theme: Theme, debug: bool) {
|
fn render_tool_response(resp: &ToolResponse, debug: bool) {
|
||||||
let config = Config::global();
|
let config = Config::global();
|
||||||
|
|
||||||
match &resp.tool_result {
|
match &resp.tool_result {
|
||||||
@@ -519,11 +519,52 @@ fn render_tool_response(resp: &ToolResponse, theme: Theme, debug: bool) {
|
|||||||
if debug {
|
if debug {
|
||||||
println!("{:#?}", content);
|
println!("{:#?}", content);
|
||||||
} else if let Some(text) = content.as_text() {
|
} else if let Some(text) = content.as_text() {
|
||||||
print_markdown(&text.text, theme);
|
print_tool_output(&text.text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => print_markdown(&e.to_string(), theme),
|
Err(e) => {
|
||||||
|
println!(" {}", style(e.to_string()).red().dim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_tool_output(text: &str) {
|
||||||
|
if text.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !std::io::stdout().is_terminal() {
|
||||||
|
print!("{}", text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let max_lines = if get_show_full_tool_output() {
|
||||||
|
usize::MAX
|
||||||
|
} else {
|
||||||
|
20
|
||||||
|
};
|
||||||
|
let lines: Vec<&str> = text.lines().collect();
|
||||||
|
if lines.len() <= max_lines {
|
||||||
|
for line in &lines {
|
||||||
|
println!(" {}", style(line).dim());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let head = max_lines / 2;
|
||||||
|
let tail = max_lines - head;
|
||||||
|
for line in &lines[..head] {
|
||||||
|
println!(" {}", style(line).dim());
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
" {}",
|
||||||
|
style(format!(
|
||||||
|
"... ({} lines hidden, /toggle to show all)",
|
||||||
|
lines.len() - head - tail
|
||||||
|
))
|
||||||
|
.dim()
|
||||||
|
.italic()
|
||||||
|
);
|
||||||
|
for line in &lines[lines.len() - tail..] {
|
||||||
|
println!(" {}", style(line).dim());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -904,6 +945,7 @@ fn print_tool_header(call: &CallToolRequestParams) {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
println!();
|
println!();
|
||||||
|
println!(" {}", style("─".repeat(40)).dim());
|
||||||
println!("{}", tool_header);
|
println!("{}", tool_header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,6 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
const CLEAR_SCREEN: &str = "\x1b[2J\x1b[H";
|
|
||||||
const MOVE_TO_PROGRESS_LINE: &str = "\x1b[4;1H";
|
|
||||||
const CLEAR_TO_EOL: &str = "\x1b[K";
|
|
||||||
const CLEAR_BELOW: &str = "\x1b[J";
|
|
||||||
pub const TASK_EXECUTION_NOTIFICATION_TYPE: &str = "task_execution";
|
pub const TASK_EXECUTION_NOTIFICATION_TYPE: &str = "task_execution";
|
||||||
|
|
||||||
static INITIAL_SHOWN: AtomicBool = AtomicBool::new(false);
|
static INITIAL_SHOWN: AtomicBool = AtomicBool::new(false);
|
||||||
@@ -85,18 +81,14 @@ fn format_tasks_update_from_event(event: &TaskExecutionNotificationEvent) -> Str
|
|||||||
let mut display = String::new();
|
let mut display = String::new();
|
||||||
|
|
||||||
if !INITIAL_SHOWN.swap(true, Ordering::SeqCst) {
|
if !INITIAL_SHOWN.swap(true, Ordering::SeqCst) {
|
||||||
display.push_str(CLEAR_SCREEN);
|
|
||||||
display.push_str("🎯 Task Execution Dashboard\n");
|
display.push_str("🎯 Task Execution Dashboard\n");
|
||||||
display.push_str("═══════════════════════════\n\n");
|
display.push_str("═══════════════════════════\n\n");
|
||||||
} else {
|
|
||||||
display.push_str(MOVE_TO_PROGRESS_LINE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
display.push_str(&format!(
|
display.push_str(&format!(
|
||||||
"📊 Progress: {} total | ⏳ {} pending | 🏃 {} running | ✅ {} completed | ❌ {} failed",
|
"📊 Progress: {} total | ⏳ {} pending | 🏃 {} running | ✅ {} completed | ❌ {} failed\n\n",
|
||||||
stats.total, stats.pending, stats.running, stats.completed, stats.failed
|
stats.total, stats.pending, stats.running, stats.completed, stats.failed
|
||||||
));
|
));
|
||||||
display.push_str(&format!("{}\n\n", CLEAR_TO_EOL));
|
|
||||||
|
|
||||||
let mut sorted_tasks = tasks.clone();
|
let mut sorted_tasks = tasks.clone();
|
||||||
sorted_tasks.sort_by(|a, b| a.id.cmp(&b.id));
|
sorted_tasks.sort_by(|a, b| a.id.cmp(&b.id));
|
||||||
@@ -105,7 +97,6 @@ fn format_tasks_update_from_event(event: &TaskExecutionNotificationEvent) -> Str
|
|||||||
display.push_str(&format_task_display(&task));
|
display.push_str(&format_task_display(&task));
|
||||||
}
|
}
|
||||||
|
|
||||||
display.push_str(CLEAR_BELOW);
|
|
||||||
display
|
display
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
@@ -155,25 +146,22 @@ fn format_task_display(task: &TaskInfo) -> String {
|
|||||||
};
|
};
|
||||||
|
|
||||||
task_display.push_str(&format!(
|
task_display.push_str(&format!(
|
||||||
"{} {} ({}){}\n",
|
"{} {} ({})\n",
|
||||||
status_icon, task.task_name, task.task_type, CLEAR_TO_EOL
|
status_icon, task.task_name, task.task_type
|
||||||
));
|
));
|
||||||
|
|
||||||
if !task.task_metadata.is_empty() {
|
if !task.task_metadata.is_empty() {
|
||||||
task_display.push_str(&format!(
|
task_display.push_str(&format!(" 📋 Parameters: {}\n", task.task_metadata));
|
||||||
" 📋 Parameters: {}{}\n",
|
|
||||||
task.task_metadata, CLEAR_TO_EOL
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(duration_secs) = task.duration_secs {
|
if let Some(duration_secs) = task.duration_secs {
|
||||||
task_display.push_str(&format!(" ⏱️ {:.1}s{}\n", duration_secs, CLEAR_TO_EOL));
|
task_display.push_str(&format!(" ⏱️ {:.1}s\n", duration_secs));
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(task.status, TaskStatus::Running) && !task.current_output.trim().is_empty() {
|
if matches!(task.status, TaskStatus::Running) && !task.current_output.trim().is_empty() {
|
||||||
let processed_output = process_output_for_display(&task.current_output);
|
let processed_output = process_output_for_display(&task.current_output);
|
||||||
if !processed_output.is_empty() {
|
if !processed_output.is_empty() {
|
||||||
task_display.push_str(&format!(" 💬 {}{}\n", processed_output, CLEAR_TO_EOL));
|
task_display.push_str(&format!(" 💬 {}\n", processed_output));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +169,7 @@ fn format_task_display(task: &TaskInfo) -> String {
|
|||||||
if let Some(result_data) = &task.result_data {
|
if let Some(result_data) = &task.result_data {
|
||||||
let result_preview = format_result_data_for_display(result_data);
|
let result_preview = format_result_data_for_display(result_data);
|
||||||
if !result_preview.is_empty() {
|
if !result_preview.is_empty() {
|
||||||
task_display.push_str(&format!(" 📄 {}{}\n", result_preview, CLEAR_TO_EOL));
|
task_display.push_str(&format!(" 📄 {}\n", result_preview));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,14 +177,10 @@ fn format_task_display(task: &TaskInfo) -> String {
|
|||||||
if matches!(task.status, TaskStatus::Failed) {
|
if matches!(task.status, TaskStatus::Failed) {
|
||||||
if let Some(error) = &task.error {
|
if let Some(error) = &task.error {
|
||||||
let error_preview = safe_truncate(error, 80);
|
let error_preview = safe_truncate(error, 80);
|
||||||
task_display.push_str(&format!(
|
task_display.push_str(&format!(" ⚠️ {}\n", error_preview.replace('\n', " ")));
|
||||||
" ⚠️ {}{}\n",
|
|
||||||
error_preview.replace('\n', " "),
|
|
||||||
CLEAR_TO_EOL
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task_display.push_str(&format!("{}\n", CLEAR_TO_EOL));
|
task_display.push('\n');
|
||||||
task_display
|
task_display
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ fn test_format_tasks_update_from_event() {
|
|||||||
|
|
||||||
let result2 = format_tasks_update_from_event(&event);
|
let result2 = format_tasks_update_from_event(&event);
|
||||||
assert!(!result2.contains("🎯 Task Execution Dashboard"));
|
assert!(!result2.contains("🎯 Task Execution Dashboard"));
|
||||||
assert!(result2.contains(MOVE_TO_PROGRESS_LINE));
|
assert!(result2.contains("📊 Progress:"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user