diff --git a/crates/g3-cli/src/ui_writer_impl.rs b/crates/g3-cli/src/ui_writer_impl.rs index dcbfa40..c69034c 100644 --- a/crates/g3-cli/src/ui_writer_impl.rs +++ b/crates/g3-cli/src/ui_writer_impl.rs @@ -10,6 +10,7 @@ pub struct ConsoleUiWriter { current_tool_args: Mutex>, current_output_line: Mutex>, output_line_printed: Mutex, + in_todo_tool: Mutex, } impl ConsoleUiWriter { @@ -19,6 +20,60 @@ impl ConsoleUiWriter { current_tool_args: Mutex::new(Vec::new()), current_output_line: Mutex::new(None), output_line_printed: Mutex::new(false), + in_todo_tool: Mutex::new(false), + } + } + + fn print_todo_line(&self, line: &str) { + // Transform and print todo list lines elegantly + let trimmed = line.trim(); + + // Skip the "📝 TODO list:" prefix line + if trimmed.starts_with("📝 TODO list:") || trimmed == "📝 TODO list is empty" { + return; + } + + // Handle empty lines + if trimmed.is_empty() { + println!(); + return; + } + + // Detect indentation level + let indent_count = line.chars().take_while(|c| c.is_whitespace()).count(); + let indent = " ".repeat(indent_count / 2); // Convert spaces to visual indent + + // Format based on line type + if trimmed.starts_with("- [ ]") { + // Incomplete task + let task = trimmed.strip_prefix("- [ ]").unwrap_or(trimmed).trim(); + println!("{}☐ {}", indent, task); + } else if trimmed.starts_with("- [x]") || trimmed.starts_with("- [X]") { + // Completed task + let task = trimmed.strip_prefix("- [x]") + .or_else(|| trimmed.strip_prefix("- [X]")) + .unwrap_or(trimmed) + .trim(); + println!("{}\x1b[2m☑ {}\x1b[0m", indent, task); + } else if trimmed.starts_with("- ") { + // Regular bullet point + let item = trimmed.strip_prefix("- ").unwrap_or(trimmed).trim(); + println!("{}• {}", indent, item); + } else if trimmed.starts_with("# ") { + // Heading + let heading = trimmed.strip_prefix("# ").unwrap_or(trimmed).trim(); + println!("\n\x1b[1m{}\x1b[0m", heading); + } else if trimmed.starts_with("## ") { + // Subheading + let subheading = trimmed.strip_prefix("## ").unwrap_or(trimmed).trim(); + println!("\n\x1b[1m{}\x1b[0m", subheading); + } else if trimmed.starts_with("**") && trimmed.ends_with("**") { + // Bold text (section marker) + let text = trimmed.trim_start_matches("**").trim_end_matches("**"); + println!("{}\x1b[1m{}\x1b[0m", indent, text); + } else { + // Regular text or note + println!("{}{}", indent, trimmed); } } } @@ -53,6 +108,15 @@ impl UiWriter for ConsoleUiWriter { // Store the tool name and clear args for collection *self.current_tool_name.lock().unwrap() = Some(tool_name.to_string()); self.current_tool_args.lock().unwrap().clear(); + + // Check if this is a todo tool call + let is_todo = tool_name == "todo_read" || tool_name == "todo_write"; + *self.in_todo_tool.lock().unwrap() = is_todo; + + // For todo tools, we'll skip the normal header and print a custom one later + if is_todo { + return; + } } fn print_tool_arg(&self, key: &str, value: &str) { @@ -75,6 +139,12 @@ impl UiWriter for ConsoleUiWriter { } fn print_tool_output_header(&self) { + // Skip normal header for todo tools + if *self.in_todo_tool.lock().unwrap() { + println!(); // Just add a newline + return; + } + println!(); // Now print the tool header with the most important arg in bold green if let Some(tool_name) = self.current_tool_name.lock().unwrap().as_ref() { @@ -144,10 +214,21 @@ impl UiWriter for ConsoleUiWriter { } fn print_tool_output_line(&self, line: &str) { + // Special handling for todo tools + if *self.in_todo_tool.lock().unwrap() { + self.print_todo_line(line); + return; + } + println!("│ \x1b[2m{}\x1b[0m", line); } fn print_tool_output_summary(&self, count: usize) { + // Skip for todo tools + if *self.in_todo_tool.lock().unwrap() { + return; + } + println!( "│ \x1b[2m({} line{})\x1b[0m", count, @@ -156,6 +237,13 @@ impl UiWriter for ConsoleUiWriter { } fn print_tool_timing(&self, duration_str: &str) { + // For todo tools, just print a simple completion message + if *self.in_todo_tool.lock().unwrap() { + println!(); + *self.in_todo_tool.lock().unwrap() = false; + return; + } + // Parse the duration string to determine color // Format is like "1.5s", "500ms", "2m 30.0s" let color_code = if duration_str.ends_with("ms") { diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index 3050f1e..9f10458 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -776,27 +776,6 @@ impl Agent { // For native tool calling providers, use a more explicit system prompt "You are G3, an AI programming agent of the same skill level as a seasoned engineer at a major technology company. You analyze given tasks and write code to achieve goals. -# Task Management - -Use todo_read and todo_write for tasks with 3+ steps, multiple files/components, or uncertain scope. - -Workflow: -- Start: read → write checklist -- During: read → update progress -- End: verify all complete - -Warning: todo_write overwrites entirely; always todo_read first (skipping is an error) - -Keep items short, specific, action-oriented. Not using the todo tools for complex tasks is an error. - -Template: -- [ ] Implement feature X - - [ ] Update API - - [ ] Write tests - - [ ] Run tests - - [ ] Run lint -- [ ] Blocked: waiting on credentials - You have access to tools. When you need to accomplish a task, you MUST use the appropriate tool. Do not just describe what you would do - actually use the tools. IMPORTANT: You must call tools to achieve goals. When you receive a request: @@ -815,18 +794,9 @@ When taking screenshots of specific windows (like \"my Safari window\" or \"my t Do not explain what you're going to do - just do it by calling the tools. -# Response Guidelines - -- Use Markdown formatting for all responses except tool calls. -- Whenever taking actions, use the pronoun 'I' -".to_string() - } else { - // For non-native providers (embedded models), use JSON format instructions - "You are G3, a general-purpose AI agent. Your goal is to analyze and solve problems by writing code. - # Task Management -Use todo_read and todo_write for tasks with 3+ steps, multiple files/components, or uncertain scope. +Use todo_read and todo_write for tasks with 2+ steps, multiple files/components, or uncertain scope. Workflow: - Start: read → write checklist @@ -841,6 +811,21 @@ Template: - [ ] Implement feature X - [ ] Update API - [ ] Write tests + - [ ] Run tests + - [ ] Run lint +- [ ] Blocked: waiting on credentials + + +# Response Guidelines + +- Use Markdown formatting for all responses except tool calls. +- Whenever taking actions, use the pronoun 'I' +".to_string() + } else { + // For non-native providers (embedded models), use JSON format instructions + "You are G3, a general-purpose AI agent. Your goal is to analyze and solve problems by writing code. + +You have access to tools. When you need to accomplish a task, you MUST use the appropriate tool. Do not just describe what you would do - actually use the tools. # Tool Call Format @@ -887,6 +872,24 @@ The tool will execute immediately and you'll receive the result (success or erro 3. STOP when the original request was satisfied 4. Call the final_output tool when done +# Task Management + +Use todo_read and todo_write for tasks with 3+ steps, multiple files/components, or uncertain scope. + +Workflow: +- Start: read → write checklist +- During: read → update progress +- End: verify all complete + +Warning: todo_write overwrites entirely; always todo_read first (skipping is an error) + +Keep items short, specific, action-oriented. Not using the todo tools for complex tasks is an error. + +Template: +- [ ] Implement feature X + - [ ] Update API + - [ ] Write tests + # Response Guidelines - Use Markdown formatting for all responses except tool calls. @@ -1813,14 +1816,21 @@ The tool will execute immediately and you'll receive the result (success or erro const MAX_LINES: usize = 5; const MAX_LINE_WIDTH: usize = 80; let output_len = output_lines.len(); + + // For todo tools, show all lines without truncation + let is_todo_tool = tool_call.tool == "todo_read" || tool_call.tool == "todo_write"; + let max_lines_to_show = if is_todo_tool { output_len } else { MAX_LINES }; - for line in output_lines { + for (idx, line) in output_lines.iter().enumerate() { + if !is_todo_tool && idx >= max_lines_to_show { + break; + } // Clip line to max width let clipped_line = truncate_line(line, MAX_LINE_WIDTH); self.ui_writer.update_tool_output_line(&clipped_line); } - if output_len > MAX_LINES { + if !is_todo_tool && output_len > MAX_LINES { self.ui_writer.print_tool_output_summary(output_len); } }