feat: add compact styled output for TODO tools
TODO tools (todo_read, todo_write) now display with a cleaner, more compact format: - Styled header: " ● todo_read" or " ● todo_write" - Tree-style prefixes for content lines (│ and └) - Checkbox conversion: "- [ ]" → □, "- [x]" → ■ - Dimmed content for visual distinction - No timing footer (cleaner output) Changes: - Add print_todo_compact() method to UiWriter trait - Implement print_todo_compact() in ConsoleUiWriter - Update todo.rs to call print_todo_compact() instead of line-by-line output - Skip tool header, output header, and timing for TODO tools in agent streaming
This commit is contained in:
@@ -437,6 +437,57 @@ impl UiWriter for ConsoleUiWriter {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_todo_compact(&self, content: Option<&str>, is_write: bool) -> bool {
|
||||||
|
let tool_name = if is_write { "todo_write" } else { "todo_read" };
|
||||||
|
let is_agent_mode = *self.is_agent_mode.lock().unwrap();
|
||||||
|
let tool_color = if is_agent_mode { "\x1b[38;5;250m" } else { "\x1b[32m" };
|
||||||
|
|
||||||
|
// Add blank line if last output was text (for visual separation)
|
||||||
|
let mut last_was_text = self.last_output_was_text.lock().unwrap();
|
||||||
|
if *last_was_text {
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
*last_was_text = false;
|
||||||
|
*self.last_output_was_tool.lock().unwrap() = true;
|
||||||
|
// Reset read_file continuation tracking
|
||||||
|
*self.last_read_file_path.lock().unwrap() = None;
|
||||||
|
|
||||||
|
match content {
|
||||||
|
None => {
|
||||||
|
// Empty TODO
|
||||||
|
println!(" \x1b[2m●\x1b[0m {}{}\x1b[0m \x1b[2m|\x1b[0m \x1b[35mempty\x1b[0m", tool_color, tool_name);
|
||||||
|
}
|
||||||
|
Some(text) => {
|
||||||
|
// Header
|
||||||
|
println!(" \x1b[2m●\x1b[0m {}{}\x1b[0m", tool_color, tool_name);
|
||||||
|
|
||||||
|
let lines: Vec<&str> = text.lines().collect();
|
||||||
|
let last_idx = lines.len().saturating_sub(1);
|
||||||
|
|
||||||
|
for (i, line) in lines.iter().enumerate() {
|
||||||
|
let is_last = i == last_idx;
|
||||||
|
let prefix = if is_last { "└" } else { "│" };
|
||||||
|
|
||||||
|
// Convert checkboxes to styled symbols
|
||||||
|
let styled_line = line
|
||||||
|
.replace("- [x]", "■")
|
||||||
|
.replace("- [X]", "■")
|
||||||
|
.replace("- [ ]", "□");
|
||||||
|
|
||||||
|
// Dim the line content
|
||||||
|
println!(" \x1b[2m{} {}\x1b[0m", prefix, styled_line);
|
||||||
|
}
|
||||||
|
// Add blank line after content for readability
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear tool state
|
||||||
|
self.clear_tool_state();
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn print_tool_timing(&self, duration_str: &str, tokens_delta: u32, context_percentage: f32) {
|
fn print_tool_timing(&self, duration_str: &str, tokens_delta: u32, context_percentage: f32) {
|
||||||
let color_code = duration_color(duration_str);
|
let color_code = duration_color(duration_str);
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ pub enum StreamState {
|
|||||||
|
|
||||||
|
|
||||||
// Re-export StreamingToolParser from its own module
|
// Re-export StreamingToolParser from its own module
|
||||||
pub use streaming_parser::StreamingToolParser;
|
pub use streaming_parser::{StreamingToolParser, sanitize_inline_tool_patterns, LBRACE_HOMOGLYPH};
|
||||||
|
|
||||||
pub struct Agent<W: UiWriter> {
|
pub struct Agent<W: UiWriter> {
|
||||||
providers: ProviderRegistry,
|
providers: ProviderRegistry,
|
||||||
@@ -2131,7 +2131,11 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
// Finish streaming markdown before showing tool output
|
// Finish streaming markdown before showing tool output
|
||||||
self.ui_writer.finish_streaming_markdown();
|
self.ui_writer.finish_streaming_markdown();
|
||||||
|
|
||||||
// Tool call header
|
// Check if this is a TODO tool (they handle their own output)
|
||||||
|
let is_todo_tool = tool_call.tool == "todo_read" || tool_call.tool == "todo_write";
|
||||||
|
|
||||||
|
// Tool call header (skip for TODO tools - they print their own compact header)
|
||||||
|
if !is_todo_tool {
|
||||||
self.ui_writer.print_tool_header(&tool_call.tool, Some(&tool_call.args));
|
self.ui_writer.print_tool_header(&tool_call.tool, Some(&tool_call.args));
|
||||||
if let Some(args_obj) = tool_call.args.as_object() {
|
if let Some(args_obj) = tool_call.args.as_object() {
|
||||||
for (key, value) in args_obj {
|
for (key, value) in args_obj {
|
||||||
@@ -2143,12 +2147,13 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
self.ui_writer.print_tool_arg(key, &value_str);
|
self.ui_writer.print_tool_arg(key, &value_str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if this is a compact tool (file operations)
|
// Check if this is a compact tool (file operations)
|
||||||
let is_compact_tool = matches!(tool_call.tool.as_str(), "read_file" | "write_file" | "str_replace" | "remember" | "take_screenshot" | "code_coverage" | "rehydrate");
|
let is_compact_tool = matches!(tool_call.tool.as_str(), "read_file" | "write_file" | "str_replace" | "remember" | "take_screenshot" | "code_coverage" | "rehydrate");
|
||||||
|
|
||||||
// Only print output header for non-compact tools
|
// Only print output header for non-compact tools
|
||||||
if !is_compact_tool {
|
if !is_compact_tool && !is_todo_tool {
|
||||||
self.ui_writer.print_tool_output_header();
|
self.ui_writer.print_tool_output_header();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2316,20 +2321,23 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
// Closure marker with timing
|
// Closure marker with timing
|
||||||
let tokens_delta = self.context_window.used_tokens.saturating_sub(tokens_before);
|
let tokens_delta = self.context_window.used_tokens.saturating_sub(tokens_before);
|
||||||
|
|
||||||
// Use compact format for file operations, normal format for others
|
// TODO tools handle their own output via print_todo_compact, skip timing
|
||||||
if let Some(summary) = compact_summary {
|
if !is_todo_tool {
|
||||||
self.ui_writer.print_tool_compact(
|
// Use compact format for file operations, normal format for others
|
||||||
&tool_call.tool,
|
if let Some(summary) = compact_summary {
|
||||||
&summary,
|
self.ui_writer.print_tool_compact(
|
||||||
&streaming::format_duration(exec_duration),
|
&tool_call.tool,
|
||||||
tokens_delta,
|
&summary,
|
||||||
self.context_window.percentage_used(),
|
&streaming::format_duration(exec_duration),
|
||||||
);
|
|
||||||
} else {
|
|
||||||
self.ui_writer
|
|
||||||
.print_tool_timing(&streaming::format_duration(exec_duration),
|
|
||||||
tokens_delta,
|
tokens_delta,
|
||||||
self.context_window.percentage_used());
|
self.context_window.percentage_used(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.ui_writer
|
||||||
|
.print_tool_timing(&streaming::format_duration(exec_duration),
|
||||||
|
tokens_delta,
|
||||||
|
self.context_window.percentage_used());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.ui_writer.print_agent_prompt();
|
self.ui_writer.print_agent_prompt();
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ pub async fn execute_todo_read<W: UiWriter>(
|
|||||||
// Also update in-memory content to stay in sync
|
// Also update in-memory content to stay in sync
|
||||||
let mut todo = ctx.todo_content.write().await;
|
let mut todo = ctx.todo_content.write().await;
|
||||||
*todo = String::new();
|
*todo = String::new();
|
||||||
|
ctx.ui_writer.print_todo_compact(None, false);
|
||||||
return Ok("📝 TODO list is empty (no todo.g3.md file found)".to_string());
|
return Ok("📝 TODO list is empty (no todo.g3.md file found)".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,11 +43,10 @@ pub async fn execute_todo_read<W: UiWriter>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if content.trim().is_empty() {
|
if content.trim().is_empty() {
|
||||||
|
ctx.ui_writer.print_todo_compact(None, false);
|
||||||
Ok("📝 TODO list is empty".to_string())
|
Ok("📝 TODO list is empty".to_string())
|
||||||
} else {
|
} else {
|
||||||
for line in content.lines() {
|
ctx.ui_writer.print_todo_compact(Some(&content), false);
|
||||||
ctx.ui_writer.print_tool_output_line(line);
|
|
||||||
}
|
|
||||||
Ok(format!("📝 TODO list:\n{}", content))
|
Ok(format!("📝 TODO list:\n{}", content))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,14 +98,10 @@ pub async fn execute_todo_write<W: UiWriter>(
|
|||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let mut todo = ctx.todo_content.write().await;
|
let mut todo = ctx.todo_content.write().await;
|
||||||
*todo = String::new();
|
*todo = String::new();
|
||||||
// Show the final completed TODOs before deletion
|
// Show the final completed TODOs
|
||||||
let mut result =
|
ctx.ui_writer.print_todo_compact(Some(content_str), true);
|
||||||
String::from("✅ All TODOs completed! Removed todo.g3.md\n\nFinal status:\n");
|
let mut result = String::from("✅ All TODOs completed! Removed todo.g3.md\n\nFinal status:\n");
|
||||||
for line in content_str.lines() {
|
result.push_str(content_str);
|
||||||
ctx.ui_writer.print_tool_output_line(line);
|
|
||||||
result.push_str(line);
|
|
||||||
result.push('\n');
|
|
||||||
}
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
Err(e) => return Ok(format!("❌ Failed to remove todo.g3.md: {}", e)),
|
Err(e) => return Ok(format!("❌ Failed to remove todo.g3.md: {}", e)),
|
||||||
@@ -117,10 +113,7 @@ pub async fn execute_todo_write<W: UiWriter>(
|
|||||||
// Also update in-memory content to stay in sync
|
// Also update in-memory content to stay in sync
|
||||||
let mut todo = ctx.todo_content.write().await;
|
let mut todo = ctx.todo_content.write().await;
|
||||||
*todo = content_str.to_string();
|
*todo = content_str.to_string();
|
||||||
// Print the TODO content to the console (inside the tool frame)
|
ctx.ui_writer.print_todo_compact(Some(content_str), true);
|
||||||
for line in content_str.lines() {
|
|
||||||
ctx.ui_writer.print_tool_output_line(line);
|
|
||||||
}
|
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"✅ TODO list updated ({} chars) and saved to todo.g3.md:\n{}",
|
"✅ TODO list updated ({} chars) and saved to todo.g3.md:\n{}",
|
||||||
char_count, content_str
|
char_count, content_str
|
||||||
|
|||||||
@@ -46,6 +46,15 @@ pub trait UiWriter: Send + Sync {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Print a compact TODO tool output with styled content
|
||||||
|
/// Format: " ● todo_read" header, then styled content lines, no timing
|
||||||
|
/// content: None for empty, Some(content) for TODO content
|
||||||
|
/// is_write: true for todo_write, false for todo_read
|
||||||
|
/// Returns true if handled, false to fall back to normal format
|
||||||
|
fn print_todo_compact(&self, _content: Option<&str>, _is_write: bool) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Print tool execution timing
|
/// Print tool execution timing
|
||||||
fn print_tool_timing(&self, duration_str: &str, tokens_delta: u32, context_percentage: f32);
|
fn print_tool_timing(&self, duration_str: &str, tokens_delta: u32, context_percentage: f32);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user