Fix --project flag not working in agent mode

- Add CommonFlags struct to group flags that apply across all modes
- Refactor run_agent_mode() to accept CommonFlags instead of individual params
- Add project loading logic for agent chat mode
- Add integration tests for --project with agent mode

This refactor prevents future bugs where new flags work in one mode
but are forgotten in another.
This commit is contained in:
Dhanji R. Prasanna
2026-01-30 11:28:48 +11:00
parent 51d22b3282
commit 2e21502357
4 changed files with 123 additions and 30 deletions

View File

@@ -1,7 +1,6 @@
//! Agent mode for G3 CLI - runs specialized agents with custom prompts.
use anyhow::Result;
use std::path::PathBuf;
use tracing::debug;
use g3_core::ui_writer::UiWriter;
@@ -15,21 +14,17 @@ use crate::embedded_agents::load_agent_prompt;
use crate::ui_writer_impl::ConsoleUiWriter;
use crate::interactive::run_interactive;
use crate::template::process_template;
use crate::project::{Project, load_and_validate_project};
use crate::cli_args::CommonFlags;
/// Run agent mode - loads a specialized agent prompt and executes a single task.
///
/// Uses `CommonFlags` for flags that apply across all modes, ensuring consistency.
pub async fn run_agent_mode(
agent_name: &str,
workspace: Option<PathBuf>,
config_path: Option<&str>,
_quiet: bool,
new_session: bool,
task: Option<String>,
chrome_headless: bool,
safari: bool,
chat: bool,
include_prompt_path: Option<PathBuf>,
no_auto_memory: bool,
acd_enabled: bool,
flags: CommonFlags,
) -> Result<()> {
use g3_core::find_incomplete_agent_session;
use g3_core::get_agent_system_prompt;
@@ -40,7 +35,7 @@ pub async fn run_agent_mode(
let output = SimpleOutput::new();
// Determine workspace directory (current dir if not specified)
let workspace_dir = workspace.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
let workspace_dir = flags.workspace.clone().unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
// Change to the workspace directory first so session scanning works correctly
std::env::set_current_dir(&workspace_dir)?;
@@ -49,7 +44,7 @@ pub async fn run_agent_mode(
// Skip session resume entirely when in chat mode (--agent --chat)
let resuming_session = if chat {
None // Chat mode always starts fresh
} else if new_session {
} else if flags.new_session {
if !chat {
output.print("\n🆕 Starting new session (--new-session flag set)");
output.print("");
@@ -97,16 +92,16 @@ pub async fn run_agent_mode(
print_workspace_path(&workspace_dir);
// Load config
let mut config = g3_config::Config::load(config_path)?;
let mut config = g3_config::Config::load(flags.config.as_deref())?;
// Apply chrome-headless flag override
if chrome_headless {
if flags.chrome_headless {
config.webdriver.enabled = true;
config.webdriver.browser = g3_config::WebDriverBrowser::ChromeHeadless;
}
// Apply safari flag override
if safari {
if flags.safari {
config.webdriver.enabled = true;
config.webdriver.browser = g3_config::WebDriverBrowser::Safari;
}
@@ -120,10 +115,10 @@ pub async fn run_agent_mode(
let memory_content_opt = read_workspace_memory(&workspace_dir);
// Read include prompt early so we can show it in the status line
let include_prompt = read_include_prompt(include_prompt_path.as_deref());
let include_prompt = read_include_prompt(flags.include_prompt.as_deref());
// Build and print status line showing what was loaded
let include_filename = include_prompt_path.as_ref()
let include_filename = flags.include_prompt.as_ref()
.filter(|_| include_prompt.is_some())
.and_then(|p| p.file_name())
.map(|s| s.to_string_lossy().to_string());
@@ -181,10 +176,10 @@ pub async fn run_agent_mode(
// Auto-memory is enabled by default in agent mode (unless --no-auto-memory is set)
// This prompts the LLM to save discoveries to workspace memory after each turn
agent.set_auto_memory(!no_auto_memory);
agent.set_auto_memory(!flags.no_auto_memory);
// Enable ACD (Aggressive Context Dehydration) if requested
if acd_enabled {
if flags.acd {
agent.set_acd_enabled(true);
}
@@ -244,15 +239,43 @@ pub async fn run_agent_mode(
// If chat mode is enabled, run interactive loop instead of single task
if chat {
// Load project if --project flag was specified
let initial_project: Option<Project> = if let Some(ref proj_path) = flags.project {
match load_and_validate_project(&proj_path.to_string_lossy(), &workspace_dir) {
Ok(cli_project) => {
// Set project content in agent's system message
if agent.set_project_content(Some(cli_project.content.clone())) {
// Set project path on UI writer for path shortening
let project_name = cli_project.path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("project")
.to_string();
agent.ui_writer().set_project_path(cli_project.path.clone(), project_name);
Some(cli_project)
} else {
eprintln!("Warning: Failed to set project content in agent context.");
None
}
}
Err(e) => {
eprintln!("Error loading project: {}", e);
std::process::exit(1);
}
}
} else {
None
};
return run_interactive(
agent,
false, // show_prompt
false, // show_code
combined_content,
&workspace_dir,
new_session,
flags.new_session,
Some(agent_name), // agent name for prompt (e.g., "butler>")
None, // initial_project (not supported in agent mode yet)
initial_project,
)
.await;
}

View File

@@ -3,6 +3,35 @@
use clap::Parser;
use std::path::PathBuf;
/// Flags that apply across all execution modes (interactive, agent, autonomous).
///
/// When adding a new flag that should work in all modes, add it here instead of
/// passing individual parameters to mode functions. This prevents bugs where a
/// flag works in one mode but is forgotten in another.
#[derive(Clone, Debug, Default)]
pub struct CommonFlags {
/// Workspace directory
pub workspace: Option<PathBuf>,
/// Configuration file path
pub config: Option<String>,
/// Skip session resumption and force a new session
pub new_session: bool,
/// Suppress output/logging
pub quiet: bool,
/// Use Chrome in headless mode for WebDriver
pub chrome_headless: bool,
/// Use Safari for WebDriver
pub safari: bool,
/// Include additional prompt content from a file
pub include_prompt: Option<PathBuf>,
/// Disable automatic memory update reminder
pub no_auto_memory: bool,
/// Enable aggressive context dehydration
pub acd: bool,
/// Load a project from the given path at startup
pub project: Option<PathBuf>,
}
#[derive(Parser, Clone)]
#[command(name = "g3")]
#[command(about = "A modular, composable AI coding agent")]
@@ -127,3 +156,22 @@ pub struct Cli {
#[arg(long, value_name = "PATH")]
pub project: Option<PathBuf>,
}
impl Cli {
/// Extract common flags that apply across all execution modes.
/// This ensures flags like --project, --acd, --include-prompt work consistently.
pub fn common_flags(&self) -> CommonFlags {
CommonFlags {
workspace: self.workspace.clone(),
config: self.config.clone(),
new_session: self.new_session,
quiet: self.quiet,
chrome_headless: self.chrome_headless,
safari: self.safari,
include_prompt: self.include_prompt.clone(),
no_auto_memory: self.no_auto_memory,
acd: self.acd,
project: self.project.clone(),
}
}
}

View File

@@ -90,17 +90,9 @@ pub async fn run() -> Result<()> {
if let Some(agent_name) = &cli.agent {
return run_agent_mode(
agent_name,
cli.workspace.clone(),
cli.config.as_deref(),
cli.quiet,
cli.new_session,
cli.task.clone(),
cli.chrome_headless,
cli.safari,
cli.chat,
cli.include_prompt.clone(),
cli.no_auto_memory,
cli.acd,
cli.common_flags(),
)
.await;
}

View File

@@ -305,3 +305,33 @@ fn test_no_auto_memory_in_help_output() {
"Help output should mention --no-auto-memory flag"
);
}
// =============================================================================
// Test: Project option is accepted (including with agent mode)
// =============================================================================
#[test]
fn test_project_option_accepted() {
let output = Command::new(get_g3_binary())
.args(["--project", "/tmp/myproject", "--help"])
.output()
.expect("Failed to execute g3 with project option");
assert!(
output.status.success(),
"--project option should be recognized"
);
}
#[test]
fn test_project_option_with_agent_mode_accepted() {
let output = Command::new(get_g3_binary())
.args(["--agent", "butler", "--chat", "--project", "/tmp/myproject", "--help"])
.output()
.expect("Failed to execute g3 with agent and project options");
assert!(
output.status.success(),
"--project option should work with --agent --chat"
);
}