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
|
// This prompts the LLM to save discoveries to project memory after each turn
|
||||||
agent.set_auto_memory(true);
|
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
|
// If resuming a session, restore context and TODO
|
||||||
let initial_task = if let Some(ref incomplete_session) = resuming_session {
|
let initial_task = if let Some(ref incomplete_session) = resuming_session {
|
||||||
// Restore the session context
|
// Restore the session context
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ use g3_core::project::Project;
|
|||||||
use g3_core::{Agent, DiscoveryOptions};
|
use g3_core::{Agent, DiscoveryOptions};
|
||||||
|
|
||||||
use crate::coach_feedback;
|
use crate::coach_feedback;
|
||||||
use crate::machine_ui_writer::MachineUiWriter;
|
|
||||||
use crate::metrics::{format_elapsed_time, generate_turn_histogram, TurnMetrics};
|
use crate::metrics::{format_elapsed_time, generate_turn_histogram, TurnMetrics};
|
||||||
use crate::simple_output::SimpleOutput;
|
use crate::simple_output::SimpleOutput;
|
||||||
use crate::ui_writer_impl::ConsoleUiWriter;
|
use crate::ui_writer_impl::ConsoleUiWriter;
|
||||||
@@ -264,60 +263,6 @@ pub async fn run_autonomous(
|
|||||||
Ok(agent)
|
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 ---
|
// --- Helper types and functions ---
|
||||||
|
|
||||||
enum PlayerTurnResult {
|
enum PlayerTurnResult {
|
||||||
|
|||||||
@@ -55,10 +55,6 @@ pub struct Cli {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub chat: bool,
|
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)
|
/// Override the configured provider (anthropic, databricks, embedded, openai)
|
||||||
#[arg(long, value_name = "PROVIDER")]
|
#[arg(long, value_name = "PROVIDER")]
|
||||||
pub provider: Option<String>,
|
pub provider: Option<String>,
|
||||||
|
|||||||
@@ -10,10 +10,9 @@ use tracing::{debug, error};
|
|||||||
use g3_core::ui_writer::UiWriter;
|
use g3_core::ui_writer::UiWriter;
|
||||||
use g3_core::Agent;
|
use g3_core::Agent;
|
||||||
|
|
||||||
use crate::machine_ui_writer::MachineUiWriter;
|
|
||||||
use crate::project_files::extract_readme_heading;
|
use crate::project_files::extract_readme_heading;
|
||||||
use crate::simple_output::SimpleOutput;
|
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;
|
use crate::utils::display_context_progress;
|
||||||
|
|
||||||
/// Run interactive mode with console output.
|
/// Run interactive mode with console output.
|
||||||
@@ -206,7 +205,7 @@ pub async fn run_interactive<W: UiWriter>(
|
|||||||
&input,
|
&input,
|
||||||
show_prompt,
|
show_prompt,
|
||||||
show_code,
|
show_code,
|
||||||
OutputMode::Console(&output),
|
&output,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -242,7 +241,7 @@ pub async fn run_interactive<W: UiWriter>(
|
|||||||
&input,
|
&input,
|
||||||
show_prompt,
|
show_prompt,
|
||||||
show_code,
|
show_code,
|
||||||
OutputMode::Console(&output),
|
&output,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -287,95 +286,6 @@ pub async fn run_interactive<W: UiWriter>(
|
|||||||
Ok(())
|
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.
|
/// Handle a control command. Returns true if the command was handled and the loop should continue.
|
||||||
async fn handle_command<W: UiWriter>(
|
async fn handle_command<W: UiWriter>(
|
||||||
input: &str,
|
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 cli_args;
|
||||||
mod coach_feedback;
|
mod coach_feedback;
|
||||||
mod interactive;
|
mod interactive;
|
||||||
mod machine_ui_writer;
|
|
||||||
mod simple_output;
|
mod simple_output;
|
||||||
mod task_execution;
|
mod task_execution;
|
||||||
mod ui_writer_impl;
|
mod ui_writer_impl;
|
||||||
@@ -30,9 +29,8 @@ use clap::Parser;
|
|||||||
|
|
||||||
use accumulative::run_accumulative_mode;
|
use accumulative::run_accumulative_mode;
|
||||||
use agent_mode::run_agent_mode;
|
use agent_mode::run_agent_mode;
|
||||||
use autonomous::{run_autonomous, run_autonomous_machine};
|
use autonomous::run_autonomous;
|
||||||
use interactive::{run_interactive, run_interactive_machine};
|
use interactive::run_interactive;
|
||||||
use machine_ui_writer::MachineUiWriter;
|
|
||||||
use project_files::{read_agents_config, read_project_memory, read_project_readme};
|
use project_files::{read_agents_config, read_project_memory, read_project_readme};
|
||||||
use simple_output::SimpleOutput;
|
use simple_output::SimpleOutput;
|
||||||
use ui_writer_impl::ConsoleUiWriter;
|
use ui_writer_impl::ConsoleUiWriter;
|
||||||
@@ -110,12 +108,7 @@ pub async fn run() -> Result<()> {
|
|||||||
// Combine AGENTS.md, README, and memory content
|
// Combine AGENTS.md, README, and memory content
|
||||||
let combined_content = combine_project_content(agents_content, readme_content, memory_content);
|
let combined_content = combine_project_content(agents_content, readme_content, memory_content);
|
||||||
|
|
||||||
// Execute based on mode
|
run_console_mode(cli, config, project, combined_content, workspace_dir).await
|
||||||
if cli.machine {
|
|
||||||
run_machine_mode(cli, config, project, combined_content).await
|
|
||||||
} else {
|
|
||||||
run_console_mode(cli, config, project, combined_content, workspace_dir).await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Helper functions ---
|
// --- Helper functions ---
|
||||||
@@ -123,10 +116,7 @@ pub async fn run() -> Result<()> {
|
|||||||
fn initialize_logging(cli: &Cli) {
|
fn initialize_logging(cli: &Cli) {
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||||
|
|
||||||
let filter = if cli.machine {
|
let filter = if cli.verbose {
|
||||||
// In machine mode, suppress ALL logs
|
|
||||||
EnvFilter::from_default_env().add_directive("off".parse().unwrap())
|
|
||||||
} else if cli.verbose {
|
|
||||||
EnvFilter::from_default_env()
|
EnvFilter::from_default_env()
|
||||||
.add_directive(format!("{}=debug", env!("CARGO_PKG_NAME")).parse().unwrap())
|
.add_directive(format!("{}=debug", env!("CARGO_PKG_NAME")).parse().unwrap())
|
||||||
.add_directive("g3_core=debug".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 {
|
if let Some(ws) = &cli.workspace {
|
||||||
Ok(ws.clone())
|
Ok(ws.clone())
|
||||||
} else if cli.autonomous {
|
} else if cli.autonomous {
|
||||||
setup_workspace_directory(cli.machine)
|
setup_workspace_directory()
|
||||||
} else {
|
} else {
|
||||||
Ok(std::env::current_dir()?)
|
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(
|
async fn run_console_mode(
|
||||||
cli: Cli,
|
cli: Cli,
|
||||||
config: Config,
|
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
|
/// Simple output helper for printing messages
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SimpleOutput {
|
pub struct SimpleOutput;
|
||||||
machine_mode: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SimpleOutput {
|
impl SimpleOutput {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
SimpleOutput {
|
SimpleOutput
|
||||||
machine_mode: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_with_mode(machine_mode: bool) -> Self {
|
|
||||||
SimpleOutput { machine_mode }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print(&self, message: &str) {
|
pub fn print(&self, message: &str) {
|
||||||
if !self.machine_mode {
|
println!("{}", message);
|
||||||
println!("{}", message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_smart(&self, message: &str) {
|
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
|
/// Maximum number of retry attempts for timeout errors
|
||||||
const MAX_TIMEOUT_RETRIES: u32 = 3;
|
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.
|
/// 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>(
|
pub async fn execute_task_with_retry<W: UiWriter>(
|
||||||
agent: &mut Agent<W>,
|
agent: &mut Agent<W>,
|
||||||
input: &str,
|
input: &str,
|
||||||
show_prompt: bool,
|
show_prompt: bool,
|
||||||
show_code: bool,
|
show_code: bool,
|
||||||
mode: OutputMode<'_>,
|
output: &SimpleOutput,
|
||||||
) {
|
) {
|
||||||
let mut attempt = 0;
|
let mut attempt = 0;
|
||||||
|
|
||||||
mode.print_thinking();
|
output.print("🤔 Thinking...");
|
||||||
|
|
||||||
// Create cancellation token for this request
|
// Create cancellation token for this request
|
||||||
let cancellation_token = CancellationToken::new();
|
let cancellation_token = CancellationToken::new();
|
||||||
@@ -123,7 +39,7 @@ pub async fn execute_task_with_retry<W: UiWriter>(
|
|||||||
}
|
}
|
||||||
_ = tokio::signal::ctrl_c() => {
|
_ = tokio::signal::ctrl_c() => {
|
||||||
cancel_token_clone.cancel();
|
cancel_token_clone.cancel();
|
||||||
mode.print_cancelled();
|
output.print("\n⚠️ Operation cancelled by user (Ctrl+C)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -131,17 +47,14 @@ pub async fn execute_task_with_retry<W: UiWriter>(
|
|||||||
match execution_result {
|
match execution_result {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
if attempt > 1 {
|
if attempt > 1 {
|
||||||
mode.print_retry_success(attempt);
|
output.print(&format!("✅ Request succeeded after {} attempts", attempt));
|
||||||
}
|
}
|
||||||
mode.print_response(&result.response, match &mode {
|
output.print_smart(&result.response);
|
||||||
OutputMode::Console(o) => Some(*o),
|
|
||||||
OutputMode::Machine => None,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if e.to_string().contains("cancelled") {
|
if e.to_string().contains("cancelled") {
|
||||||
mode.print_cancelled_simple();
|
output.print("⚠️ Operation cancelled by user");
|
||||||
return;
|
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_ms = 1000 * (2_u64.pow(attempt - 1));
|
||||||
let delay = std::time::Duration::from_millis(delay_ms);
|
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
|
// Wait before retrying
|
||||||
tokio::time::sleep(delay).await;
|
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
|
// For non-timeout errors or after max retries
|
||||||
match &mode {
|
handle_execution_error(&e, input, output, attempt);
|
||||||
OutputMode::Console(output) => {
|
|
||||||
handle_execution_error(&e, input, output, attempt);
|
|
||||||
}
|
|
||||||
OutputMode::Machine => {
|
|
||||||
mode.print_error(&e, attempt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
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.
|
/// Set up the workspace directory for autonomous mode.
|
||||||
/// Uses G3_WORKSPACE environment variable or defaults to ~/tmp/workspace.
|
/// 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") {
|
let workspace_dir = if let Ok(env_workspace) = std::env::var("G3_WORKSPACE") {
|
||||||
PathBuf::from(env_workspace)
|
PathBuf::from(env_workspace)
|
||||||
} else {
|
} else {
|
||||||
@@ -80,7 +80,7 @@ pub fn setup_workspace_directory(machine_mode: bool) -> Result<PathBuf> {
|
|||||||
// Create the directory if it doesn't exist
|
// Create the directory if it doesn't exist
|
||||||
if !workspace_dir.exists() {
|
if !workspace_dir.exists() {
|
||||||
std::fs::create_dir_all(&workspace_dir)?;
|
std::fs::create_dir_all(&workspace_dir)?;
|
||||||
let output = SimpleOutput::new_with_mode(machine_mode);
|
let output = SimpleOutput::new();
|
||||||
output.print(&format!(
|
output.print(&format!(
|
||||||
"📁 Created workspace directory: {}",
|
"📁 Created workspace directory: {}",
|
||||||
workspace_dir.display()
|
workspace_dir.display()
|
||||||
|
|||||||
@@ -275,20 +275,3 @@ fn test_quiet_option_accepted() {
|
|||||||
"--quiet option should be recognized"
|
"--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
|
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
|
// 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
|
// Extract messages to keep: system messages + everything up to (but not including) dehydrate_start
|
||||||
let messages_to_keep: Vec<_> = self.context_window
|
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);
|
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);
|
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.";
|
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