From 43a5d27149208e8f9b2365b5e5f1c1003b7620c6 Mon Sep 17 00:00:00 2001 From: "Dhanji R. Prasanna" Date: Mon, 12 Jan 2026 14:45:50 +0530 Subject: [PATCH] Add compact format for remember, take_screenshot, code_coverage, rehydrate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend compact single-line output to additional tools: - remember: shows '📝 memory updated (size)' - take_screenshot: shows '📸 path' - code_coverage: shows '📊 report generated' - rehydrate: shows '🔄 restored fragment_id' Tools without file_path argument use simplified format: ● tool_name | summary | tokens ◉ time --- analysis/memory.md | 6 ++- crates/g3-cli/src/ui_writer_impl.rs | 68 ++++++++++++++++++----------- crates/g3-core/src/lib.rs | 20 ++++++++- crates/g3-core/src/streaming.rs | 52 ++++++++++++++++++++++ 4 files changed, 118 insertions(+), 28 deletions(-) diff --git a/analysis/memory.md b/analysis/memory.md index 3dbeae7..2d2fede 100644 --- a/analysis/memory.md +++ b/analysis/memory.md @@ -1,5 +1,5 @@ # Project Memory -> Updated: 2026-01-12T05:55:00Z | Size: 10.3k chars +> Updated: 2026-01-12T09:14:13Z | Size: 10.5k chars ### Remember Tool Wiring - `crates/g3-core/src/tools/memory.rs` [0..5000] - `execute_remember()`, `get_memory_path()`, `merge_memory()` @@ -176,3 +176,7 @@ if s.chars().count() <= max_len { ... } - Previously was at `.g3/memory.md` (gitignored, ephemeral) - `crates/g3-core/src/tools/memory.rs` - `get_memory_path()` returns `analysis/memory.md` - `crates/g3-cli/src/project_files.rs` - `read_project_memory()` reads from `analysis/memory.md` + +### Compact Tool Output +- `crates/g3-cli/src/ui_writer_impl.rs` - `print_tool_compact()` handles compact display for file ops and other tools +- `crates/g3-core/src/streaming.rs` - `format_*_summary()` functions for each tool type \ No newline at end of file diff --git a/crates/g3-cli/src/ui_writer_impl.rs b/crates/g3-cli/src/ui_writer_impl.rs index 0f0b572..d6a0c4a 100644 --- a/crates/g3-cli/src/ui_writer_impl.rs +++ b/crates/g3-cli/src/ui_writer_impl.rs @@ -266,8 +266,8 @@ impl UiWriter for ConsoleUiWriter { } fn print_tool_compact(&self, tool_name: &str, summary: &str, duration_str: &str, tokens_delta: u32, _context_percentage: f32) -> bool { - // Only handle file operation tools in compact format - let is_compact_tool = matches!(tool_name, "read_file" | "write_file" | "str_replace"); + // Handle file operation tools and other compact tools + let is_compact_tool = matches!(tool_name, "read_file" | "write_file" | "str_replace" | "remember" | "take_screenshot" | "code_coverage" | "rehydrate"); if !is_compact_tool { return false; } @@ -275,23 +275,29 @@ impl UiWriter for ConsoleUiWriter { let args = self.current_tool_args.lock().unwrap(); let is_agent_mode = *self.is_agent_mode.lock().unwrap(); - // Get file path + // Get file path (for file operation tools) let file_path = args .iter() .find(|(k, _)| k == "file_path") .map(|(_, v)| v.as_str()) - .unwrap_or("?"); + .unwrap_or(""); - // Truncate long paths - let display_path = if file_path.len() > 60 { - let truncate_at = file_path - .char_indices() - .nth(57) - .map(|(i, _)| i) - .unwrap_or(file_path.len()); - format!("{}...", &file_path[..truncate_at]) + // For tools without file_path, get other relevant args + let display_arg = if file_path.is_empty() { + // For remember, take_screenshot, etc. - no path to show + String::new() } else { - file_path.to_string() + // Truncate long paths + if file_path.len() > 60 { + let truncate_at = file_path + .char_indices() + .nth(57) + .map(|(i, _)| i) + .unwrap_or(file_path.len()); + format!("{}", &file_path[..truncate_at]) + } else { + file_path.to_string() + } }; // Build range suffix for read_file @@ -320,18 +326,30 @@ impl UiWriter for ConsoleUiWriter { // Color for tool name let tool_color = if is_agent_mode { "\x1b[38;5;250m" } else { "\x1b[32m" }; - // Print compact single line: - // " ● read_file | path [range] | summary | tokens ◉ time" - println!( - " \x1b[2m●\x1b[0m {}{} \x1b[2m|\x1b[0m \x1b[35m{}{}\x1b[0m \x1b[2m| {}\x1b[0m \x1b[2m| {} ◉ {}\x1b[0m", - tool_color, - tool_name, - display_path, - range_suffix, - summary, - tokens_delta, - duration_str - ); + // Print compact single line - different format for tools with/without path + if display_arg.is_empty() { + // Tools without file path: " ● tool_name | summary | tokens ◉ time" + println!( + " \x1b[2m●\x1b[0m {}{} \x1b[2m| {}\x1b[0m \x1b[2m| {} ◉ {}\x1b[0m", + tool_color, + tool_name, + summary, + tokens_delta, + duration_str + ); + } else { + // Tools with file path: " ● tool_name | path [range] | summary | tokens ◉ time" + println!( + " \x1b[2m●\x1b[0m {}{} \x1b[2m|\x1b[0m \x1b[35m{}{}\x1b[0m \x1b[2m| {}\x1b[0m \x1b[2m| {} ◉ {}\x1b[0m", + tool_color, + tool_name, + display_arg, + range_suffix, + summary, + tokens_delta, + duration_str + ); + } // Clear the stored tool info drop(args); // Release the lock before clearing diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index ead45ba..306fce3 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -2144,7 +2144,7 @@ impl Agent { } // 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"); + 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 if !is_compact_tool { @@ -2208,7 +2208,23 @@ impl Agent { let (ins, del) = parse_diff_stats(&tool_result); Some(streaming::format_str_replace_summary(ins, del)) } - _ => Some(streaming::format_read_file_summary(output_len, tool_result.len())) + "remember" => { + // Extract size from result like "Memory updated. Size: 1.2k" + Some(streaming::format_remember_summary(&tool_result)) + } + "take_screenshot" => { + // Extract path from result + Some(streaming::format_screenshot_summary(&tool_result)) + } + "code_coverage" => { + // Show coverage summary + Some(streaming::format_coverage_summary(&tool_result)) + } + "rehydrate" => { + // Show fragment info + Some(streaming::format_rehydrate_summary(&tool_result)) + } + _ => Some(format!("✅ completed")) } } else if is_todo_tool { // Skip - todo tools print their own content diff --git a/crates/g3-core/src/streaming.rs b/crates/g3-core/src/streaming.rs index d12c083..3b03a33 100644 --- a/crates/g3-core/src/streaming.rs +++ b/crates/g3-core/src/streaming.rs @@ -313,6 +313,58 @@ pub fn format_str_replace_summary(insertions: i32, deletions: i32) -> String { } } +/// Format a remember tool result summary. +pub fn format_remember_summary(result: &str) -> String { + // Result format: "Memory updated. Size: 1.2k" or similar + if let Some(size_pos) = result.find("Size: ") { + let size_str = &result[size_pos + 6..]; + let size = size_str.split_whitespace().next().unwrap_or("?"); + format!("📝 memory updated ({})", size) + } else { + "📝 memory updated".to_string() + } +} + +/// Format a take_screenshot result summary. +pub fn format_screenshot_summary(result: &str) -> String { + // Result format: "✅ Screenshot of X saved to: /path/to/file.png" + if let Some(path_pos) = result.find("saved to: ") { + let path = &result[path_pos + 10..].trim(); + format!("📸 {}", path) + } else if result.contains("❌") { + "❌ failed".to_string() + } else { + "📸 saved".to_string() + } +} + +/// Format a code_coverage result summary. +pub fn format_coverage_summary(result: &str) -> String { + // Try to extract coverage percentage from result + if result.contains("❌") { + "❌ failed".to_string() + } else { + "📊 report generated".to_string() + } +} + +/// Format a rehydrate result summary. +pub fn format_rehydrate_summary(result: &str) -> String { + // Result format: "✅ Rehydrated fragment 'abc123' (47 messages, ~18500 tokens)" + if let Some(start) = result.find("fragment '") { + let after = &result[start + 10..]; + if let Some(end) = after.find("'") { + let fragment_id = &after[..end]; + return format!("🔄 restored '{}'", fragment_id); + } + } + if result.contains("❌") { + "❌ failed".to_string() + } else { + "🔄 restored".to_string() + } +} + /// Determine if a response is essentially empty (whitespace or timing only) pub fn is_empty_response(response: &str) -> bool { response.trim().is_empty()