control commands
This commit is contained in:
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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("]("))
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user