Remove machine mode entirely from g3
- Delete machine_ui_writer.rs - Remove --machine CLI flag from cli_args.rs - Remove run_machine_mode(), run_interactive_machine(), run_autonomous_machine() functions - Remove handle_machine_command() function - Simplify OutputMode enum to just use SimpleOutput directly - Simplify SimpleOutput struct (remove machine_mode field) - Remove machine_mode parameter from setup_workspace_directory() - Remove test_machine_option_accepted test - Disable ACD by default in agent_mode (requires --acd flag) - Change 'memory checkpoint' message formatting - Remove dehydration status message
This commit is contained in:
@@ -209,9 +209,6 @@ pub async fn run_agent_mode(
|
||||
// This prompts the LLM to save discoveries to project memory after each turn
|
||||
agent.set_auto_memory(true);
|
||||
|
||||
// Enable ACD in agent mode for longer sessions
|
||||
agent.set_acd_enabled(true);
|
||||
|
||||
// If resuming a session, restore context and TODO
|
||||
let initial_task = if let Some(ref incomplete_session) = resuming_session {
|
||||
// Restore the session context
|
||||
|
||||
@@ -11,7 +11,6 @@ use g3_core::project::Project;
|
||||
use g3_core::{Agent, DiscoveryOptions};
|
||||
|
||||
use crate::coach_feedback;
|
||||
use crate::machine_ui_writer::MachineUiWriter;
|
||||
use crate::metrics::{format_elapsed_time, generate_turn_histogram, TurnMetrics};
|
||||
use crate::simple_output::SimpleOutput;
|
||||
use crate::ui_writer_impl::ConsoleUiWriter;
|
||||
@@ -264,60 +263,6 @@ pub async fn run_autonomous(
|
||||
Ok(agent)
|
||||
}
|
||||
|
||||
/// Run autonomous mode with machine-friendly output.
|
||||
pub async fn run_autonomous_machine(
|
||||
mut agent: Agent<MachineUiWriter>,
|
||||
project: Project,
|
||||
show_prompt: bool,
|
||||
show_code: bool,
|
||||
max_turns: usize,
|
||||
_quiet: bool,
|
||||
_codebase_fast_start: Option<PathBuf>,
|
||||
) -> Result<()> {
|
||||
println!("AUTONOMOUS_MODE_STARTED");
|
||||
println!("WORKSPACE: {}", project.workspace().display());
|
||||
println!("MAX_TURNS: {}", max_turns);
|
||||
|
||||
// Check if requirements exist
|
||||
if !project.has_requirements() {
|
||||
println!("ERROR: requirements.md not found in workspace directory");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Read requirements
|
||||
let requirements = match project.read_requirements()? {
|
||||
Some(content) => content,
|
||||
None => {
|
||||
println!("ERROR: Could not read requirements");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
println!("REQUIREMENTS_LOADED");
|
||||
|
||||
// For now, just execute a simple autonomous loop
|
||||
// This is a simplified version - full implementation would need coach-player loop
|
||||
let task = format!(
|
||||
"You are G3 in implementation mode. Read and implement the following requirements:\n\n{}\n\nImplement this step by step, creating all necessary files and code.",
|
||||
requirements
|
||||
);
|
||||
|
||||
println!("TASK_START");
|
||||
let result = agent
|
||||
.execute_task_with_timing(&task, None, false, show_prompt, show_code, true, None)
|
||||
.await?;
|
||||
println!("AGENT_RESPONSE:");
|
||||
println!("{}", result.response);
|
||||
println!("END_AGENT_RESPONSE");
|
||||
println!("TASK_END");
|
||||
|
||||
// Save session continuation for resume capability
|
||||
agent.save_session_continuation(Some(result.response.clone()));
|
||||
|
||||
println!("AUTONOMOUS_MODE_ENDED");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// --- Helper types and functions ---
|
||||
|
||||
enum PlayerTurnResult {
|
||||
|
||||
@@ -55,10 +55,6 @@ pub struct Cli {
|
||||
#[arg(long)]
|
||||
pub chat: bool,
|
||||
|
||||
/// Enable machine-friendly output mode with JSON markers and stats
|
||||
#[arg(long)]
|
||||
pub machine: bool,
|
||||
|
||||
/// Override the configured provider (anthropic, databricks, embedded, openai)
|
||||
#[arg(long, value_name = "PROVIDER")]
|
||||
pub provider: Option<String>,
|
||||
|
||||
@@ -10,10 +10,9 @@ use tracing::{debug, error};
|
||||
use g3_core::ui_writer::UiWriter;
|
||||
use g3_core::Agent;
|
||||
|
||||
use crate::machine_ui_writer::MachineUiWriter;
|
||||
use crate::project_files::extract_readme_heading;
|
||||
use crate::simple_output::SimpleOutput;
|
||||
use crate::task_execution::{execute_task_with_retry, OutputMode};
|
||||
use crate::task_execution::execute_task_with_retry;
|
||||
use crate::utils::display_context_progress;
|
||||
|
||||
/// Run interactive mode with console output.
|
||||
@@ -206,7 +205,7 @@ pub async fn run_interactive<W: UiWriter>(
|
||||
&input,
|
||||
show_prompt,
|
||||
show_code,
|
||||
OutputMode::Console(&output),
|
||||
&output,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -242,7 +241,7 @@ pub async fn run_interactive<W: UiWriter>(
|
||||
&input,
|
||||
show_prompt,
|
||||
show_code,
|
||||
OutputMode::Console(&output),
|
||||
&output,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -287,95 +286,6 @@ pub async fn run_interactive<W: UiWriter>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run interactive mode with machine-friendly output.
|
||||
pub async fn run_interactive_machine(
|
||||
mut agent: Agent<MachineUiWriter>,
|
||||
show_prompt: bool,
|
||||
show_code: bool,
|
||||
) -> Result<()> {
|
||||
println!("INTERACTIVE_MODE_STARTED");
|
||||
|
||||
// Display provider and model information
|
||||
match agent.get_provider_info() {
|
||||
Ok((provider, model)) => {
|
||||
println!("PROVIDER: {}", provider);
|
||||
println!("MODEL: {}", model);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("ERROR: Failed to get provider info: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize rustyline editor with history
|
||||
let mut rl = DefaultEditor::new()?;
|
||||
|
||||
// Try to load history from a file in the user's home directory
|
||||
let history_file = dirs::home_dir().map(|mut path| {
|
||||
path.push(".g3_history");
|
||||
path
|
||||
});
|
||||
|
||||
if let Some(ref history_path) = history_file {
|
||||
let _ = rl.load_history(history_path);
|
||||
}
|
||||
|
||||
loop {
|
||||
let readline = rl.readline("");
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
let input = line.trim().to_string();
|
||||
|
||||
if input.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if input == "exit" || input == "quit" {
|
||||
break;
|
||||
}
|
||||
|
||||
// Add to history
|
||||
rl.add_history_entry(&input)?;
|
||||
|
||||
// Check for control commands
|
||||
if input.starts_with('/') {
|
||||
if handle_machine_command(&input, &mut agent).await? {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute task
|
||||
println!("TASK_START");
|
||||
execute_task_with_retry(&mut agent, &input, show_prompt, show_code, OutputMode::Machine)
|
||||
.await;
|
||||
|
||||
// Send auto-memory reminder if enabled and tools were called
|
||||
if let Err(e) = agent.send_auto_memory_reminder().await {
|
||||
debug!("Auto-memory reminder failed: {}", e);
|
||||
}
|
||||
|
||||
println!("TASK_END");
|
||||
}
|
||||
Err(ReadlineError::Interrupted) => continue,
|
||||
Err(ReadlineError::Eof) => break,
|
||||
Err(err) => {
|
||||
println!("ERROR: {:?}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save history before exiting
|
||||
if let Some(ref history_path) = history_file {
|
||||
let _ = rl.save_history(history_path);
|
||||
}
|
||||
|
||||
// Save session continuation for resume capability
|
||||
agent.save_session_continuation(None);
|
||||
|
||||
println!("INTERACTIVE_MODE_ENDED");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle a control command. Returns true if the command was handled and the loop should continue.
|
||||
async fn handle_command<W: UiWriter>(
|
||||
input: &str,
|
||||
@@ -648,204 +558,3 @@ async fn handle_command<W: UiWriter>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a control command in machine mode. Returns true if the command was handled.
|
||||
async fn handle_machine_command(
|
||||
input: &str,
|
||||
agent: &mut Agent<MachineUiWriter>,
|
||||
) -> Result<bool> {
|
||||
match input {
|
||||
"/compact" => {
|
||||
println!("COMMAND: compact");
|
||||
match agent.force_compact().await {
|
||||
Ok(true) => println!("RESULT: Compaction completed"),
|
||||
Ok(false) => println!("RESULT: Compaction failed"),
|
||||
Err(e) => println!("ERROR: {}", e),
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
"/thinnify" => {
|
||||
println!("COMMAND: thinnify");
|
||||
let summary = agent.force_thin();
|
||||
println!("{}", summary);
|
||||
Ok(true)
|
||||
}
|
||||
"/skinnify" => {
|
||||
println!("COMMAND: skinnify");
|
||||
let summary = agent.force_thin_all();
|
||||
println!("{}", summary);
|
||||
Ok(true)
|
||||
}
|
||||
"/fragments" => {
|
||||
println!("COMMAND: fragments");
|
||||
if let Some(session_id) = agent.get_session_id() {
|
||||
match g3_core::acd::list_fragments(session_id) {
|
||||
Ok(fragments) => {
|
||||
println!("FRAGMENT_COUNT: {}", fragments.len());
|
||||
for fragment in &fragments {
|
||||
println!("FRAGMENT_ID: {}", fragment.fragment_id);
|
||||
println!("FRAGMENT_MESSAGES: {}", fragment.message_count);
|
||||
println!("FRAGMENT_TOKENS: {}", fragment.estimated_tokens);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("ERROR: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("ERROR: No active session");
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
cmd if cmd.starts_with("/rehydrate") => {
|
||||
println!("COMMAND: rehydrate");
|
||||
let parts: Vec<&str> = cmd.splitn(2, ' ').collect();
|
||||
if parts.len() < 2 || parts[1].trim().is_empty() {
|
||||
println!("ERROR: Usage: /rehydrate <fragment_id>");
|
||||
} else {
|
||||
let fragment_id = parts[1].trim();
|
||||
println!("FRAGMENT_ID: {}", fragment_id);
|
||||
println!("RESULT: Use the rehydrate tool to restore fragment content");
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
"/dump" => {
|
||||
println!("COMMAND: dump");
|
||||
let dump_dir = std::path::Path::new("tmp");
|
||||
if !dump_dir.exists() {
|
||||
if let Err(e) = std::fs::create_dir_all(dump_dir) {
|
||||
println!("ERROR: Failed to create tmp directory: {}", e);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
|
||||
let dump_path = dump_dir.join(format!("context_dump_{}.txt", timestamp));
|
||||
|
||||
let context = agent.get_context_window();
|
||||
let mut dump_content = String::new();
|
||||
dump_content.push_str("# Context Window Dump\n");
|
||||
dump_content.push_str(&format!("# Timestamp: {}\n", chrono::Utc::now()));
|
||||
dump_content.push_str(&format!(
|
||||
"# Messages: {}\n",
|
||||
context.conversation_history.len()
|
||||
));
|
||||
dump_content.push_str(&format!(
|
||||
"# Used tokens: {} / {} ({:.1}%)\n\n",
|
||||
context.used_tokens,
|
||||
context.total_tokens,
|
||||
context.percentage_used()
|
||||
));
|
||||
|
||||
for (i, msg) in context.conversation_history.iter().enumerate() {
|
||||
dump_content.push_str(&format!(
|
||||
"=== Message {} ===\nRole: {:?}\nKind: {:?}\nContent ({} chars):\n{}\n\n",
|
||||
i,
|
||||
msg.role,
|
||||
msg.kind,
|
||||
msg.content.len(),
|
||||
msg.content
|
||||
));
|
||||
}
|
||||
|
||||
match std::fs::write(&dump_path, &dump_content) {
|
||||
Ok(_) => println!("RESULT: Context dumped to {}", dump_path.display()),
|
||||
Err(e) => println!("ERROR: Failed to write dump: {}", e),
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
"/clear" => {
|
||||
println!("COMMAND: clear");
|
||||
agent.clear_session();
|
||||
println!("RESULT: Session cleared");
|
||||
Ok(true)
|
||||
}
|
||||
"/readme" => {
|
||||
println!("COMMAND: readme");
|
||||
match agent.reload_readme() {
|
||||
Ok(true) => println!("RESULT: README content reloaded successfully"),
|
||||
Ok(false) => println!("RESULT: No README was loaded at startup, cannot reload"),
|
||||
Err(e) => println!("ERROR: {}", e),
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
"/stats" => {
|
||||
println!("COMMAND: stats");
|
||||
let stats = agent.get_stats();
|
||||
// Emit stats as structured data (name: value pairs)
|
||||
println!("{}", stats);
|
||||
Ok(true)
|
||||
}
|
||||
"/help" => {
|
||||
println!("COMMAND: help");
|
||||
println!("AVAILABLE_COMMANDS: /compact /thinnify /skinnify /clear /dump /fragments /rehydrate /resume /readme /stats /help");
|
||||
Ok(true)
|
||||
}
|
||||
"/resume" => {
|
||||
println!("COMMAND: resume");
|
||||
match g3_core::list_sessions_for_directory() {
|
||||
Ok(sessions) => {
|
||||
if sessions.is_empty() {
|
||||
println!("RESULT: No sessions found");
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
println!("SESSIONS_START");
|
||||
for (i, session) in sessions.iter().enumerate() {
|
||||
let time_str = g3_core::format_session_time(&session.created_at);
|
||||
let has_todos = if session.has_incomplete_todos() {
|
||||
"true"
|
||||
} else {
|
||||
"false"
|
||||
};
|
||||
println!(
|
||||
"SESSION: {} | {} | {} | {:.0}% | {}",
|
||||
i + 1,
|
||||
session.session_id,
|
||||
time_str,
|
||||
session.context_percentage,
|
||||
has_todos
|
||||
);
|
||||
}
|
||||
println!("SESSIONS_END");
|
||||
println!("HINT: Use /resume <number> to switch to a session");
|
||||
}
|
||||
Err(e) => println!("ERROR: {}", e),
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
_ => {
|
||||
// Check for /resume <number> pattern
|
||||
if input.starts_with("/resume ") {
|
||||
let num_str = input.strip_prefix("/resume ").unwrap().trim();
|
||||
if let Ok(num) = num_str.parse::<usize>() {
|
||||
println!("COMMAND: resume {}", num);
|
||||
match g3_core::list_sessions_for_directory() {
|
||||
Ok(sessions) => {
|
||||
if num >= 1 && num <= sessions.len() {
|
||||
let selected = &sessions[num - 1];
|
||||
match agent.switch_to_session(selected) {
|
||||
Ok(true) => println!(
|
||||
"RESULT: Full context restored from session {}",
|
||||
selected.session_id
|
||||
),
|
||||
Ok(false) => println!(
|
||||
"RESULT: Session {} restored from summary",
|
||||
selected.session_id
|
||||
),
|
||||
Err(e) => println!("ERROR: {}", e),
|
||||
}
|
||||
} else {
|
||||
println!("ERROR: Invalid session number");
|
||||
}
|
||||
}
|
||||
Err(e) => println!("ERROR: {}", e),
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
println!("ERROR: Unknown command: {}", input);
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ mod autonomous;
|
||||
mod cli_args;
|
||||
mod coach_feedback;
|
||||
mod interactive;
|
||||
mod machine_ui_writer;
|
||||
mod simple_output;
|
||||
mod task_execution;
|
||||
mod ui_writer_impl;
|
||||
@@ -30,9 +29,8 @@ use clap::Parser;
|
||||
|
||||
use accumulative::run_accumulative_mode;
|
||||
use agent_mode::run_agent_mode;
|
||||
use autonomous::{run_autonomous, run_autonomous_machine};
|
||||
use interactive::{run_interactive, run_interactive_machine};
|
||||
use machine_ui_writer::MachineUiWriter;
|
||||
use autonomous::run_autonomous;
|
||||
use interactive::run_interactive;
|
||||
use project_files::{read_agents_config, read_project_memory, read_project_readme};
|
||||
use simple_output::SimpleOutput;
|
||||
use ui_writer_impl::ConsoleUiWriter;
|
||||
@@ -110,12 +108,7 @@ pub async fn run() -> Result<()> {
|
||||
// Combine AGENTS.md, README, and memory content
|
||||
let combined_content = combine_project_content(agents_content, readme_content, memory_content);
|
||||
|
||||
// Execute based on mode
|
||||
if cli.machine {
|
||||
run_machine_mode(cli, config, project, combined_content).await
|
||||
} else {
|
||||
run_console_mode(cli, config, project, combined_content, workspace_dir).await
|
||||
}
|
||||
run_console_mode(cli, config, project, combined_content, workspace_dir).await
|
||||
}
|
||||
|
||||
// --- Helper functions ---
|
||||
@@ -123,10 +116,7 @@ pub async fn run() -> Result<()> {
|
||||
fn initialize_logging(cli: &Cli) {
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
|
||||
let filter = if cli.machine {
|
||||
// In machine mode, suppress ALL logs
|
||||
EnvFilter::from_default_env().add_directive("off".parse().unwrap())
|
||||
} else if cli.verbose {
|
||||
let filter = if cli.verbose {
|
||||
EnvFilter::from_default_env()
|
||||
.add_directive(format!("{}=debug", env!("CARGO_PKG_NAME")).parse().unwrap())
|
||||
.add_directive("g3_core=debug".parse().unwrap())
|
||||
@@ -154,7 +144,7 @@ fn determine_workspace_dir(cli: &Cli) -> Result<PathBuf> {
|
||||
if let Some(ws) = &cli.workspace {
|
||||
Ok(ws.clone())
|
||||
} else if cli.autonomous {
|
||||
setup_workspace_directory(cli.machine)
|
||||
setup_workspace_directory()
|
||||
} else {
|
||||
Ok(std::env::current_dir()?)
|
||||
}
|
||||
@@ -243,69 +233,6 @@ fn combine_project_content(
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_machine_mode(
|
||||
cli: Cli,
|
||||
config: Config,
|
||||
project: Project,
|
||||
combined_content: Option<String>,
|
||||
) -> Result<()> {
|
||||
let ui_writer = MachineUiWriter::new();
|
||||
|
||||
let mut agent = if cli.autonomous {
|
||||
Agent::new_autonomous_with_readme_and_quiet(
|
||||
config.clone(),
|
||||
ui_writer,
|
||||
combined_content.clone(),
|
||||
cli.quiet,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
Agent::new_with_readme_and_quiet(
|
||||
config.clone(),
|
||||
ui_writer,
|
||||
combined_content.clone(),
|
||||
cli.quiet,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
if cli.auto_memory {
|
||||
agent.set_auto_memory(true);
|
||||
}
|
||||
if cli.acd {
|
||||
agent.set_acd_enabled(true);
|
||||
}
|
||||
|
||||
if cli.autonomous {
|
||||
run_autonomous_machine(
|
||||
agent,
|
||||
project,
|
||||
cli.show_prompt,
|
||||
cli.show_code,
|
||||
cli.max_turns,
|
||||
cli.quiet,
|
||||
cli.codebase_fast_start.clone(),
|
||||
)
|
||||
.await
|
||||
} else if let Some(task) = cli.task {
|
||||
// Single-shot mode
|
||||
let result = agent
|
||||
.execute_task_with_timing(&task, None, false, cli.show_prompt, cli.show_code, true, None)
|
||||
.await?;
|
||||
println!("AGENT_RESPONSE:");
|
||||
println!("{}", result.response);
|
||||
println!("END_AGENT_RESPONSE");
|
||||
|
||||
if let Err(e) = agent.send_auto_memory_reminder().await {
|
||||
debug!("Auto-memory reminder failed: {}", e);
|
||||
}
|
||||
agent.save_session_continuation(Some(result.response.clone()));
|
||||
Ok(())
|
||||
} else {
|
||||
run_interactive_machine(agent, cli.show_prompt, cli.show_code).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_console_mode(
|
||||
cli: Cli,
|
||||
config: Config,
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
use g3_core::ui_writer::UiWriter;
|
||||
use std::io::{self, Write};
|
||||
|
||||
/// Machine-mode implementation of UiWriter that prints plain, unformatted output
|
||||
/// This is designed for programmatic consumption and outputs everything verbatim
|
||||
pub struct MachineUiWriter;
|
||||
|
||||
impl MachineUiWriter {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl UiWriter for MachineUiWriter {
|
||||
fn print(&self, message: &str) {
|
||||
print!("{}", message);
|
||||
}
|
||||
|
||||
fn println(&self, message: &str) {
|
||||
println!("{}", message);
|
||||
}
|
||||
|
||||
fn print_inline(&self, message: &str) {
|
||||
print!("{}", message);
|
||||
let _ = io::stdout().flush();
|
||||
}
|
||||
|
||||
fn print_system_prompt(&self, prompt: &str) {
|
||||
println!("SYSTEM_PROMPT:");
|
||||
println!("{}", prompt);
|
||||
println!("END_SYSTEM_PROMPT");
|
||||
println!();
|
||||
}
|
||||
|
||||
fn print_context_status(&self, message: &str) {
|
||||
println!("CONTEXT_STATUS: {}", message);
|
||||
}
|
||||
|
||||
fn print_context_thinning(&self, message: &str) {
|
||||
println!("CONTEXT_THINNING: {}", message);
|
||||
}
|
||||
|
||||
fn print_tool_header(&self, tool_name: &str, _tool_args: Option<&serde_json::Value>) {
|
||||
println!("TOOL_CALL: {}", tool_name);
|
||||
}
|
||||
|
||||
fn print_tool_arg(&self, key: &str, value: &str) {
|
||||
println!("TOOL_ARG: {} = {}", key, value);
|
||||
}
|
||||
|
||||
fn print_tool_output_header(&self) {
|
||||
println!("TOOL_OUTPUT:");
|
||||
}
|
||||
|
||||
fn update_tool_output_line(&self, line: &str) {
|
||||
println!("{}", line);
|
||||
}
|
||||
|
||||
fn print_tool_output_line(&self, line: &str) {
|
||||
println!("{}", line);
|
||||
}
|
||||
|
||||
fn print_tool_output_summary(&self, count: usize) {
|
||||
println!("TOOL_OUTPUT_LINES: {}", count);
|
||||
}
|
||||
|
||||
fn print_tool_timing(&self, duration_str: &str, tokens_delta: u32, context_percentage: f32) {
|
||||
println!("TOOL_DURATION: {}", duration_str);
|
||||
println!("TOKENS_DELTA: {}", tokens_delta);
|
||||
println!("CONTEXT_PERCENTAGE: {:.0}", context_percentage);
|
||||
println!("END_TOOL_OUTPUT");
|
||||
println!();
|
||||
}
|
||||
|
||||
fn print_agent_prompt(&self) {
|
||||
println!("AGENT_RESPONSE:");
|
||||
let _ = io::stdout().flush();
|
||||
}
|
||||
|
||||
fn print_agent_response(&self, content: &str) {
|
||||
print!("{}", content);
|
||||
let _ = io::stdout().flush();
|
||||
}
|
||||
|
||||
fn notify_sse_received(&self) {
|
||||
// No-op for machine mode
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
let _ = io::stdout().flush();
|
||||
}
|
||||
|
||||
fn wants_full_output(&self) -> bool {
|
||||
true // Machine mode wants complete, untruncated output
|
||||
}
|
||||
|
||||
fn prompt_user_yes_no(&self, message: &str) -> bool {
|
||||
// In machine mode, we can't interactively prompt, so we log the request and return true
|
||||
// to allow automation to proceed.
|
||||
println!("PROMPT_USER_YES_NO: {}", message);
|
||||
true
|
||||
}
|
||||
|
||||
fn prompt_user_choice(&self, message: &str, options: &[&str]) -> usize {
|
||||
println!("PROMPT_USER_CHOICE: {}", message);
|
||||
println!("OPTIONS: {:?}", options);
|
||||
// Default to first option (index 0) for automation
|
||||
0
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +1,18 @@
|
||||
/// Simple output helper for printing messages
|
||||
#[derive(Clone)]
|
||||
pub struct SimpleOutput {
|
||||
machine_mode: bool,
|
||||
}
|
||||
pub struct SimpleOutput;
|
||||
|
||||
impl SimpleOutput {
|
||||
pub fn new() -> Self {
|
||||
SimpleOutput {
|
||||
machine_mode: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_mode(machine_mode: bool) -> Self {
|
||||
SimpleOutput { machine_mode }
|
||||
SimpleOutput
|
||||
}
|
||||
|
||||
pub fn print(&self, message: &str) {
|
||||
if !self.machine_mode {
|
||||
println!("{}", message);
|
||||
}
|
||||
println!("{}", message);
|
||||
}
|
||||
|
||||
pub fn print_smart(&self, message: &str) {
|
||||
if !self.machine_mode {
|
||||
println!("{}", message);
|
||||
}
|
||||
println!("{}", message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,101 +11,17 @@ use crate::simple_output::SimpleOutput;
|
||||
/// Maximum number of retry attempts for timeout errors
|
||||
const MAX_TIMEOUT_RETRIES: u32 = 3;
|
||||
|
||||
/// Output mode for task execution feedback
|
||||
pub enum OutputMode<'a> {
|
||||
/// Console mode with SimpleOutput for user-friendly messages
|
||||
Console(&'a SimpleOutput),
|
||||
/// Machine mode with structured output markers
|
||||
Machine,
|
||||
}
|
||||
|
||||
impl<'a> OutputMode<'a> {
|
||||
fn print_thinking(&self) {
|
||||
match self {
|
||||
OutputMode::Console(output) => output.print("🤔 Thinking..."),
|
||||
OutputMode::Machine => {} // No thinking indicator in machine mode
|
||||
}
|
||||
}
|
||||
|
||||
fn print_cancelled(&self) {
|
||||
match self {
|
||||
OutputMode::Console(output) => output.print("\n⚠️ Operation cancelled by user (Ctrl+C)"),
|
||||
OutputMode::Machine => println!("CANCELLED"),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_cancelled_simple(&self) {
|
||||
match self {
|
||||
OutputMode::Console(output) => output.print("⚠️ Operation cancelled by user"),
|
||||
OutputMode::Machine => println!("CANCELLED"),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_retry_success(&self, attempt: u32) {
|
||||
match self {
|
||||
OutputMode::Console(output) => {
|
||||
output.print(&format!("✅ Request succeeded after {} attempts", attempt))
|
||||
}
|
||||
OutputMode::Machine => println!("RETRY_SUCCESS: attempt {}", attempt),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_response(&self, response: &str, output: Option<&SimpleOutput>) {
|
||||
match self {
|
||||
OutputMode::Console(o) => o.print_smart(response),
|
||||
OutputMode::Machine => {
|
||||
println!("AGENT_RESPONSE:");
|
||||
println!("{}", response);
|
||||
println!("END_AGENT_RESPONSE");
|
||||
// Ignore the output parameter in machine mode
|
||||
let _ = output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_timeout_retry(&self, attempt: u32, delay: std::time::Duration) {
|
||||
match self {
|
||||
OutputMode::Console(output) => {
|
||||
output.print(&format!(
|
||||
"⏱️ Timeout error detected (attempt {}/{}). Retrying in {:?}...",
|
||||
attempt, MAX_TIMEOUT_RETRIES, delay
|
||||
));
|
||||
}
|
||||
OutputMode::Machine => {
|
||||
println!(
|
||||
"TIMEOUT: attempt {} of {}, retrying in {:?}",
|
||||
attempt, MAX_TIMEOUT_RETRIES, delay
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_error(&self, e: &anyhow::Error, attempt: u32) {
|
||||
match self {
|
||||
OutputMode::Console(_) => {} // Handled by handle_execution_error
|
||||
OutputMode::Machine => {
|
||||
println!("ERROR: {}", e);
|
||||
if attempt > 1 {
|
||||
println!("FAILED_AFTER_RETRIES: {}", attempt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a task with retry logic for timeout errors.
|
||||
///
|
||||
/// This is the unified implementation used by both console and machine modes.
|
||||
pub async fn execute_task_with_retry<W: UiWriter>(
|
||||
agent: &mut Agent<W>,
|
||||
input: &str,
|
||||
show_prompt: bool,
|
||||
show_code: bool,
|
||||
mode: OutputMode<'_>,
|
||||
output: &SimpleOutput,
|
||||
) {
|
||||
let mut attempt = 0;
|
||||
|
||||
mode.print_thinking();
|
||||
output.print("🤔 Thinking...");
|
||||
|
||||
// Create cancellation token for this request
|
||||
let cancellation_token = CancellationToken::new();
|
||||
@@ -123,7 +39,7 @@ pub async fn execute_task_with_retry<W: UiWriter>(
|
||||
}
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
cancel_token_clone.cancel();
|
||||
mode.print_cancelled();
|
||||
output.print("\n⚠️ Operation cancelled by user (Ctrl+C)");
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -131,17 +47,14 @@ pub async fn execute_task_with_retry<W: UiWriter>(
|
||||
match execution_result {
|
||||
Ok(result) => {
|
||||
if attempt > 1 {
|
||||
mode.print_retry_success(attempt);
|
||||
output.print(&format!("✅ Request succeeded after {} attempts", attempt));
|
||||
}
|
||||
mode.print_response(&result.response, match &mode {
|
||||
OutputMode::Console(o) => Some(*o),
|
||||
OutputMode::Machine => None,
|
||||
});
|
||||
output.print_smart(&result.response);
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
if e.to_string().contains("cancelled") {
|
||||
mode.print_cancelled_simple();
|
||||
output.print("⚠️ Operation cancelled by user");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -157,7 +70,10 @@ pub async fn execute_task_with_retry<W: UiWriter>(
|
||||
let delay_ms = 1000 * (2_u64.pow(attempt - 1));
|
||||
let delay = std::time::Duration::from_millis(delay_ms);
|
||||
|
||||
mode.print_timeout_retry(attempt, delay);
|
||||
output.print(&format!(
|
||||
"⏱️ Timeout error detected (attempt {}/{}). Retrying in {:?}...",
|
||||
attempt, MAX_TIMEOUT_RETRIES, delay
|
||||
));
|
||||
|
||||
// Wait before retrying
|
||||
tokio::time::sleep(delay).await;
|
||||
@@ -165,14 +81,7 @@ pub async fn execute_task_with_retry<W: UiWriter>(
|
||||
}
|
||||
|
||||
// For non-timeout errors or after max retries
|
||||
match &mode {
|
||||
OutputMode::Console(output) => {
|
||||
handle_execution_error(&e, input, output, attempt);
|
||||
}
|
||||
OutputMode::Machine => {
|
||||
mode.print_error(&e, attempt);
|
||||
}
|
||||
}
|
||||
handle_execution_error(&e, input, output, attempt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ pub fn display_context_progress<W: UiWriter>(agent: &Agent<W>, _output: &SimpleO
|
||||
|
||||
/// Set up the workspace directory for autonomous mode.
|
||||
/// Uses G3_WORKSPACE environment variable or defaults to ~/tmp/workspace.
|
||||
pub fn setup_workspace_directory(machine_mode: bool) -> Result<PathBuf> {
|
||||
pub fn setup_workspace_directory() -> Result<PathBuf> {
|
||||
let workspace_dir = if let Ok(env_workspace) = std::env::var("G3_WORKSPACE") {
|
||||
PathBuf::from(env_workspace)
|
||||
} else {
|
||||
@@ -80,7 +80,7 @@ pub fn setup_workspace_directory(machine_mode: bool) -> Result<PathBuf> {
|
||||
// Create the directory if it doesn't exist
|
||||
if !workspace_dir.exists() {
|
||||
std::fs::create_dir_all(&workspace_dir)?;
|
||||
let output = SimpleOutput::new_with_mode(machine_mode);
|
||||
let output = SimpleOutput::new();
|
||||
output.print(&format!(
|
||||
"📁 Created workspace directory: {}",
|
||||
workspace_dir.display()
|
||||
|
||||
@@ -275,20 +275,3 @@ fn test_quiet_option_accepted() {
|
||||
"--quiet option should be recognized"
|
||||
);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Test: Machine mode option is accepted
|
||||
// =============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_machine_option_accepted() {
|
||||
let output = Command::new(get_g3_binary())
|
||||
.args(["--machine", "--help"])
|
||||
.output()
|
||||
.expect("Failed to execute g3 with machine option");
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"--machine option should be recognized"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1452,8 +1452,6 @@ impl<W: UiWriter> Agent<W> {
|
||||
return; // Don't modify context if save failed
|
||||
}
|
||||
|
||||
println!("💾 Dehydrated {} messages to fragment {}", fragment.message_count, fragment.fragment_id);
|
||||
|
||||
// Now replace the context: keep system messages + previous stubs/summaries, add new stub, add new summary
|
||||
// Extract messages to keep: system messages + everything up to (but not including) dehydrate_start
|
||||
let messages_to_keep: Vec<_> = self.context_window
|
||||
@@ -1524,7 +1522,7 @@ impl<W: UiWriter> Agent<W> {
|
||||
let tools_called = std::mem::take(&mut self.tool_calls_this_turn);
|
||||
|
||||
debug!("Auto-memory: Sending reminder to LLM ({} tools called this turn: {:?})", tools_called.len(), tools_called);
|
||||
self.ui_writer.print_context_status("\n*memory checkpoint:* ");
|
||||
self.ui_writer.print_context_status("\nMemory checkpoint: ");
|
||||
|
||||
let reminder = "SYSTEM REMINDER: You used tools during this turn. If you discovered any key code locations, patterns, or entry points that aren't already in Project Memory, please call the `remember` tool now to save them. If you didn't discover anything new worth remembering, you can skip this. Respond briefly after deciding.";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user