control commands

This commit is contained in:
Dhanji Prasanna
2025-10-22 22:14:12 +11:00
parent f93844d378
commit c5d6fbef08
12 changed files with 446 additions and 81 deletions

View File

@@ -637,8 +637,8 @@ fn extract_readme_heading(readme_content: &str) -> Option<String> {
let trimmed = line.trim();
// Check for H1 heading (# Title)
if trimmed.starts_with("# ") {
let title = trimmed[2..].trim();
if let Some(stripped) = trimmed.strip_prefix("# ") {
let title = stripped.trim();
if !title.is_empty() {
// Return the full title (including any description after dash)
return Some(title.to_string());
@@ -807,9 +807,8 @@ async fn run_interactive_retro(
let trimmed = input_buffer.trim_end();
// Check if line ends with backslash for continuation
if trimmed.ends_with('\\') {
if let Some(without_backslash) = trimmed.strip_suffix('\\') {
// Remove the backslash and add to buffer
let without_backslash = &trimmed[..trimmed.len() - 1];
multiline_buffer.push_str(without_backslash);
multiline_buffer.push('\n');
in_multiline = true;
@@ -1013,9 +1012,8 @@ async fn run_interactive<W: UiWriter>(
let trimmed = line.trim_end();
// Check if line ends with backslash for continuation
if trimmed.ends_with('\\') {
if let Some(without_backslash) = trimmed.strip_suffix('\\') {
// Remove the backslash and add to buffer
let without_backslash = &trimmed[..trimmed.len() - 1];
multiline_buffer.push_str(without_backslash);
multiline_buffer.push('\n');
in_multiline = true;
@@ -1058,6 +1056,63 @@ async fn run_interactive<W: UiWriter>(
// Add to history
rl.add_history_entry(&input)?;
// Check for control commands
if input.starts_with('/') {
match input.as_str() {
"/help" => {
output.print("");
output.print("📖 Control Commands:");
output.print(" /compact - Trigger auto-summarization (compacts conversation history)");
output.print(" /thinnify - Trigger context thinning (replaces large tool results with file references)");
output.print(" /readme - Reload README.md and AGENTS.md from disk");
output.print(" /stats - Show detailed context and performance statistics");
output.print(" /help - Show this help message");
output.print(" exit/quit - Exit the interactive session");
output.print("");
continue;
}
"/compact" => {
output.print("🗜️ Triggering manual summarization...");
match agent.force_summarize().await {
Ok(true) => {
output.print("✅ Summarization completed successfully");
}
Ok(false) => {
output.print("⚠️ Summarization failed");
}
Err(e) => {
output.print(&format!("❌ Error during summarization: {}", e));
}
}
continue;
}
"/thinnify" => {
output.print("🔧 Triggering manual context thinning...");
let summary = agent.force_thin();
output.print(&summary);
continue;
}
"/readme" => {
output.print("📚 Reloading README.md and AGENTS.md...");
match agent.reload_readme() {
Ok(true) => output.print("✅ README content reloaded successfully"),
Ok(false) => output.print("⚠️ No README was loaded at startup, cannot reload"),
Err(e) => output.print(&format!("❌ Error reloading README: {}", e)),
}
continue;
}
"/stats" => {
let stats = agent.get_stats();
output.print(&stats);
continue;
}
_ => {
output.print(&format!("❌ Unknown command: {}. Type /help for available commands.", input));
continue;
}
}
}
// Process the single line input
execute_task(&mut agent, &input, show_prompt, show_code, &output).await;
}
@@ -1282,7 +1337,7 @@ async fn run_autonomous(
elapsed.as_secs_f64()
));
output.print(&format!("🔄 Turns Taken: 0/{}", max_turns));
output.print(&format!("📝 Final Status: ⚠️ NO REQUIREMENTS FILE"));
output.print("📝 Final Status: ⚠️ NO REQUIREMENTS FILE");
output.print("\n📈 Token Usage Statistics:");
output.print(&format!(" • Used Tokens: {}", context_window.used_tokens));
@@ -1324,7 +1379,7 @@ async fn run_autonomous(
elapsed.as_secs_f64()
));
output.print(&format!("🔄 Turns Taken: 0/{}", max_turns));
output.print(&format!("📝 Final Status: ⚠️ CANNOT READ REQUIREMENTS"));
output.print("📝 Final Status: ⚠️ CANNOT READ REQUIREMENTS");
output.print("\n📈 Token Usage Statistics:");
output.print(&format!(" • Used Tokens: {}", context_window.used_tokens));
@@ -1410,7 +1465,7 @@ async fn run_autonomous(
"📋 Player received coach feedback ({} chars):",
coach_feedback.len()
));
output.print(&format!("{}", coach_feedback));
output.print(&coach_feedback.to_string());
}
output.print(""); // Empty line for readability
@@ -1455,7 +1510,7 @@ async fn run_autonomous(
elapsed.as_secs_f64()
));
output.print(&format!("🔄 Turns Taken: {}/{}", turn, max_turns));
output.print(&format!("📝 Final Status: 💥 PLAYER PANIC"));
output.print("📝 Final Status: 💥 PLAYER PANIC");
output.print("\n📈 Token Usage Statistics:");
output.print(&format!(
@@ -1616,7 +1671,7 @@ Remember: Be clear in your review and concise in your feedback. APPROVE if the i
elapsed.as_secs_f64()
));
output.print(&format!("🔄 Turns Taken: {}/{}", turn, max_turns));
output.print(&format!("📝 Final Status: 💥 COACH PANIC"));
output.print("📝 Final Status: 💥 COACH PANIC");
output.print("\n📈 Token Usage Statistics:");
output.print(&format!(" • Used Tokens: {}", context_window.used_tokens));

View File

@@ -267,23 +267,23 @@ impl TerminalState {
let mut current_text = String::new();
// Check for headers first
if line.starts_with("### ") {
if let Some(stripped) = line.strip_prefix("### ") {
return Line::from(Span::styled(
format!(" {}", &line[4..]),
format!(" {}", stripped),
Style::default()
.fg(self.theme.terminal_cyan.to_color())
.add_modifier(Modifier::BOLD | Modifier::UNDERLINED),
));
} else if line.starts_with("## ") {
} else if let Some(stripped) = line.strip_prefix("## ") {
return Line::from(Span::styled(
format!(" {}", &line[3..]),
format!(" {}", stripped),
Style::default()
.fg(self.theme.terminal_amber.to_color())
.add_modifier(Modifier::BOLD),
));
} else if line.starts_with("# ") {
} else if let Some(stripped) = line.strip_prefix("# ") {
return Line::from(Span::styled(
format!(" {}", &line[2..]),
format!(" {}", stripped),
Style::default()
.fg(self.theme.terminal_green.to_color())
.add_modifier(Modifier::BOLD),
@@ -343,7 +343,7 @@ impl TerminalState {
}
// Find closing *
let mut italic_text = String::new();
while let Some(ch) = chars.next() {
for ch in chars.by_ref() {
if ch == '*' {
break;
}
@@ -367,7 +367,7 @@ impl TerminalState {
}
// Find closing `
let mut code_text = String::new();
while let Some(ch) = chars.next() {
for ch in chars.by_ref() {
if ch == '`' {
break;
}
@@ -612,11 +612,9 @@ impl RetroTui {
}
// Update status blink only if status is "PROCESSING"
if state.status_line == "PROCESSING" {
if state.last_status_blink.elapsed() > Duration::from_millis(500) {
state.status_blink = !state.status_blink;
state.last_status_blink = Instant::now();
}
if state.status_line == "PROCESSING" && state.last_status_blink.elapsed() > Duration::from_millis(500) {
state.status_blink = !state.status_blink;
state.last_status_blink = Instant::now();
}
// Update activity area animation
@@ -771,12 +769,7 @@ impl RetroTui {
let total_cursor_pos = cursor_position;
// Determine the window into the buffer we should show
let window_start = if total_cursor_pos > available_width - 1 {
// Cursor is beyond the visible area, scroll the view
total_cursor_pos - (available_width - 1)
} else {
0
};
let window_start = total_cursor_pos.saturating_sub(available_width - 1);
// Get the visible portion of the buffer
let visible_buffer: String = input_buffer
@@ -1013,9 +1006,9 @@ impl RetroTui {
let fade_color = |color: Color| -> Color {
match color {
Color::Rgb(r, g, b) => {
let faded_r = ((r as f32 * opacity) as u8).max(0);
let faded_g = ((g as f32 * opacity) as u8).max(0);
let faded_b = ((b as f32 * opacity) as u8).max(0);
let faded_r = (r as f32 * opacity) as u8;
let faded_g = (g as f32 * opacity) as u8;
let faded_b = (b as f32 * opacity) as u8;
Color::Rgb(faded_r, faded_g, faded_b)
}
_ => color,
@@ -1098,9 +1091,9 @@ impl RetroTui {
let fade_color = |color: Color| -> Color {
match color {
Color::Rgb(r, g, b) => {
let faded_r = ((r as f32 * opacity) as u8).max(0);
let faded_g = ((g as f32 * opacity) as u8).max(0);
let faded_b = ((b as f32 * opacity) as u8).max(0);
let faded_r = (r as f32 * opacity) as u8;
let faded_g = (g as f32 * opacity) as u8;
let faded_b = (b as f32 * opacity) as u8;
Color::Rgb(faded_r, faded_g, faded_b)
}
_ => color,
@@ -1176,7 +1169,7 @@ impl RetroTui {
}
// Wave characters for smooth animation
let wave_chars = vec!['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
let wave_chars = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
// Build the wave line
let mut wave_line = String::new();
@@ -1190,7 +1183,7 @@ impl RetroTui {
let idx = wave_data.len().saturating_sub(display_width) + i;
if idx < wave_data.len() {
let value = wave_data[idx].min(1.0).max(0.0);
let value = wave_data[idx].clamp(0.0, 1.0);
let char_idx = ((value * 7.0) as usize).min(7);
wave_line.push(wave_chars[char_idx]);
} else {
@@ -1206,8 +1199,6 @@ impl RetroTui {
f.render_widget(wave_paragraph, area);
}
/// Draw the status bar
/// Draw the status bar
fn draw_status_bar(
f: &mut Frame,

View File

@@ -40,7 +40,7 @@ impl SimpleOutput {
trimmed.starts_with("* ") ||
trimmed.starts_with("+ ") ||
(trimmed.len() > 2 &&
trimmed.chars().next().map_or(false, |c| c.is_ascii_digit()) &&
trimmed.chars().next().is_some_and(|c| c.is_ascii_digit()) &&
trimmed.chars().nth(1) == Some('.') &&
trimmed.chars().nth(2) == Some(' ')) ||
(trimmed.contains('[') && trimmed.contains("]("))

View File

@@ -115,7 +115,6 @@ impl UiWriter for ConsoleUiWriter {
// For todo tools, we'll skip the normal header and print a custom one later
if is_todo {
return;
}
}
@@ -404,7 +403,7 @@ impl UiWriter for RetroTuiWriter {
// Add range information for read_file tool calls
let tool_name = self.current_tool_name.lock().unwrap();
let range_suffix = if tool_name.as_ref().map_or(false, |name| name == "read_file") {
let range_suffix = if tool_name.as_ref().is_some_and(|name| name == "read_file") {
// We need to check if start/end args will be provided - for now just check if this is a partial read
// This is a simplified approach since we're building the caption incrementally
String::new() // We'll handle this in print_tool_output_header instead