Compare commits
10 Commits
jochen-g3-
...
jochen-fas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbeaaea2e3 | ||
|
|
7e1ce36a4b | ||
|
|
9f6592efc2 | ||
|
|
99125fc39e | ||
|
|
a2a82a2526 | ||
|
|
5170744099 | ||
|
|
fb0aabb5c4 | ||
|
|
4655516c15 | ||
|
|
c58aa80932 | ||
|
|
c837308148 |
@@ -183,6 +183,7 @@ use rustyline::error::ReadlineError;
|
|||||||
use rustyline::DefaultEditor;
|
use rustyline::DefaultEditor;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::process::exit;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
@@ -298,6 +299,10 @@ pub async fn run() -> Result<()> {
|
|||||||
return run_flock_mode(project_dir.clone(), flock_workspace.clone(), num_segments, cli.flock_max_turns).await;
|
return run_flock_mode(project_dir.clone(), flock_workspace.clone(), num_segments, cli.flock_max_turns).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cli.codebase_fast_start.is_some() {
|
||||||
|
print!("codebase_fast_start is temporarily disabled.");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
// Otherwise, continue with normal mode
|
// Otherwise, continue with normal mode
|
||||||
|
|
||||||
// Only initialize logging if not in retro mode
|
// Only initialize logging if not in retro mode
|
||||||
@@ -1761,16 +1766,6 @@ async fn run_autonomous(
|
|||||||
let loop_start = Instant::now();
|
let loop_start = Instant::now();
|
||||||
output.print("🔄 Starting coach-player feedback loop...");
|
output.print("🔄 Starting coach-player feedback loop...");
|
||||||
|
|
||||||
// Check if implementation files already exist
|
|
||||||
let skip_first_player = project.has_implementation_files();
|
|
||||||
if skip_first_player {
|
|
||||||
output.print("📂 Detected existing implementation files in workspace");
|
|
||||||
output.print("⏭️ Skipping first player turn - proceeding directly to coach review");
|
|
||||||
} else {
|
|
||||||
output.print("📂 No existing implementation files detected");
|
|
||||||
output.print("🎯 Starting with player implementation");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load fast-discovery messages before the loop starts (if enabled)
|
// Load fast-discovery messages before the loop starts (if enabled)
|
||||||
let (discovery_messages, discovery_working_dir): (Vec<g3_providers::Message>, Option<String>) =
|
let (discovery_messages, discovery_working_dir): (Vec<g3_providers::Message>, Option<String>) =
|
||||||
if let Some(ref codebase_path) = codebase_fast_start {
|
if let Some(ref codebase_path) = codebase_fast_start {
|
||||||
@@ -1811,8 +1806,7 @@ async fn run_autonomous(
|
|||||||
loop {
|
loop {
|
||||||
let turn_start_time = Instant::now();
|
let turn_start_time = Instant::now();
|
||||||
let turn_start_tokens = agent.get_context_window().used_tokens;
|
let turn_start_tokens = agent.get_context_window().used_tokens;
|
||||||
// Skip player turn if it's the first turn and implementation files exist
|
|
||||||
if !(turn == 1 && skip_first_player) {
|
|
||||||
output.print(&format!(
|
output.print(&format!(
|
||||||
"\n=== TURN {}/{} - PLAYER MODE ===",
|
"\n=== TURN {}/{} - PLAYER MODE ===",
|
||||||
turn, max_turns
|
turn, max_turns
|
||||||
@@ -2006,7 +2000,6 @@ async fn run_autonomous(
|
|||||||
|
|
||||||
// Give some time for file operations to complete
|
// Give some time for file operations to complete
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new agent instance for coach mode to ensure fresh context
|
// Create a new agent instance for coach mode to ensure fresh context
|
||||||
// Use the same config with overrides that was passed to the player agent
|
// Use the same config with overrides that was passed to the player agent
|
||||||
|
|||||||
@@ -1087,6 +1087,14 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Count how many cache_control annotations exist in the conversation history
|
||||||
|
fn count_cache_controls_in_history(&self) -> usize {
|
||||||
|
self.context_window.conversation_history
|
||||||
|
.iter()
|
||||||
|
.filter(|msg| msg.cache_control.is_some())
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the configured max_tokens for a provider from top-level config
|
/// Get the configured max_tokens for a provider from top-level config
|
||||||
fn provider_max_tokens(config: &Config, provider_name: &str) -> Option<u32> {
|
fn provider_max_tokens(config: &Config, provider_name: &str) -> Option<u32> {
|
||||||
match provider_name {
|
match provider_name {
|
||||||
@@ -1404,7 +1412,21 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add user message to context window
|
// Add user message to context window
|
||||||
let user_message = Message::new(MessageRole::User, format!("Task: {}", description));
|
let user_message = {
|
||||||
|
// Check if we should use cache control (every 10 tool calls)
|
||||||
|
// But only if we haven't already added 4 cache_control annotations
|
||||||
|
let provider = self.providers.get(None)?;
|
||||||
|
if let Some(cache_config) = match provider.name() {
|
||||||
|
"anthropic" => self.config.providers.anthropic.as_ref()
|
||||||
|
.and_then(|c| c.cache_config.as_ref())
|
||||||
|
.and_then(|config| Self::parse_cache_control(config)),
|
||||||
|
_ => None,
|
||||||
|
} {
|
||||||
|
Message::with_cache_control_validated(MessageRole::User, format!("Task: {}", description), cache_config, provider)
|
||||||
|
} else {
|
||||||
|
Message::new(MessageRole::User, format!("Task: {}", description))
|
||||||
|
}
|
||||||
|
};
|
||||||
self.context_window.add_message(user_message);
|
self.context_window.add_message(user_message);
|
||||||
|
|
||||||
// Execute fast-discovery tool calls if provided (immediately after user message)
|
// Execute fast-discovery tool calls if provided (immediately after user message)
|
||||||
@@ -1426,7 +1448,7 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
|
|
||||||
// Add cache_control to the last user message if provider supports it (anthropic)
|
// Add cache_control to the last user message if provider supports it (anthropic)
|
||||||
let is_last = idx == message_count - 1;
|
let is_last = idx == message_count - 1;
|
||||||
let result_message = if is_last && supports_cache {
|
let result_message = if supports_cache && is_last && self.count_cache_controls_in_history() < 4 {
|
||||||
Message::with_cache_control(
|
Message::with_cache_control(
|
||||||
MessageRole::User,
|
MessageRole::User,
|
||||||
format!("Tool result: {}", result),
|
format!("Tool result: {}", result),
|
||||||
@@ -1506,24 +1528,7 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
// Add assistant response to context window only if not empty
|
// Add assistant response to context window only if not empty
|
||||||
// This prevents the "Skipping empty message" warning when only tools were executed
|
// This prevents the "Skipping empty message" warning when only tools were executed
|
||||||
if !response_content.trim().is_empty() {
|
if !response_content.trim().is_empty() {
|
||||||
let assistant_message = {
|
let assistant_message = Message::new(MessageRole::Assistant, response_content.clone());
|
||||||
// Check if we should use cache control (every 10 tool calls)
|
|
||||||
if self.tool_call_count > 0 && self.tool_call_count % 10 == 0 {
|
|
||||||
let provider = self.providers.get(None)?;
|
|
||||||
if let Some(cache_config) = match provider.name() {
|
|
||||||
"anthropic" => self.config.providers.anthropic.as_ref()
|
|
||||||
.and_then(|c| c.cache_config.as_ref())
|
|
||||||
.and_then(|config| Self::parse_cache_control(config)),
|
|
||||||
_ => None,
|
|
||||||
} {
|
|
||||||
Message::with_cache_control_validated(MessageRole::Assistant, response_content.clone(), cache_config, provider)
|
|
||||||
} else {
|
|
||||||
Message::new(MessageRole::Assistant, response_content.clone())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Message::new(MessageRole::Assistant, response_content.clone())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.context_window.add_message(assistant_message);
|
self.context_window.add_message(assistant_message);
|
||||||
} else {
|
} else {
|
||||||
debug!("Assistant response was empty (likely only tool execution), skipping message addition");
|
debug!("Assistant response was empty (likely only tool execution), skipping message addition");
|
||||||
@@ -3372,7 +3377,25 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
tool_call.tool, tool_call.args
|
tool_call.tool, tool_call.args
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
let result_message = Message::new(MessageRole::User, format!("Tool result: {}", tool_result));
|
let result_message = {
|
||||||
|
// Check if we should use cache control (every 10 tool calls)
|
||||||
|
// But only if we haven't already added 4 cache_control annotations
|
||||||
|
if self.tool_call_count > 0 && self.tool_call_count % 10 == 0 && self.count_cache_controls_in_history() < 4 {
|
||||||
|
let provider = self.providers.get(None)?;
|
||||||
|
if let Some(cache_config) = match provider.name() {
|
||||||
|
"anthropic" => self.config.providers.anthropic.as_ref()
|
||||||
|
.and_then(|c| c.cache_config.as_ref())
|
||||||
|
.and_then(|config| Self::parse_cache_control(config)),
|
||||||
|
_ => None,
|
||||||
|
} {
|
||||||
|
Message::with_cache_control_validated(MessageRole::User, format!("Tool result: {}", tool_result), cache_config, provider)
|
||||||
|
} else {
|
||||||
|
Message::new(MessageRole::User, format!("Tool result: {}", tool_result))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Message::new(MessageRole::User, format!("Tool result: {}", tool_result))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
self.context_window.add_message(tool_message);
|
self.context_window.add_message(tool_message);
|
||||||
self.context_window.add_message(result_message);
|
self.context_window.add_message(result_message);
|
||||||
@@ -3722,24 +3745,7 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
.replace("<</SYS>>", "");
|
.replace("<</SYS>>", "");
|
||||||
|
|
||||||
if !raw_clean.trim().is_empty() {
|
if !raw_clean.trim().is_empty() {
|
||||||
let assistant_message = {
|
let assistant_message = Message::new(MessageRole::Assistant, raw_clean);
|
||||||
// Check if we should use cache control (every 10 tool calls)
|
|
||||||
if self.tool_call_count > 0 && self.tool_call_count % 10 == 0 {
|
|
||||||
let provider = self.providers.get(None)?;
|
|
||||||
if let Some(cache_config) = match provider.name() {
|
|
||||||
"anthropic" => self.config.providers.anthropic.as_ref()
|
|
||||||
.and_then(|c| c.cache_config.as_ref())
|
|
||||||
.and_then(|config| Self::parse_cache_control(config)),
|
|
||||||
_ => None,
|
|
||||||
} {
|
|
||||||
Message::with_cache_control_validated(MessageRole::Assistant, raw_clean, cache_config, provider)
|
|
||||||
} else {
|
|
||||||
Message::new(MessageRole::Assistant, raw_clean)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Message::new(MessageRole::Assistant, raw_clean)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.context_window.add_message(assistant_message);
|
self.context_window.add_message(assistant_message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,49 +98,6 @@ impl Project {
|
|||||||
self.requirements_text.is_some() || self.requirements_path.is_some()
|
self.requirements_text.is_some() || self.requirements_path.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if implementation files exist in the workspace
|
|
||||||
pub fn has_implementation_files(&self) -> bool {
|
|
||||||
self.check_dir_for_implementation_files(&self.workspace_dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recursively check a directory for implementation files
|
|
||||||
#[allow(clippy::only_used_in_recursion)]
|
|
||||||
fn check_dir_for_implementation_files(&self, dir: &Path) -> bool {
|
|
||||||
// Common source file extensions
|
|
||||||
let extensions = vec![
|
|
||||||
"swift", "rs", "py", "js", "ts", "java", "cpp", "c",
|
|
||||||
"go", "rb", "php", "cs", "kt", "scala", "m", "h"
|
|
||||||
];
|
|
||||||
|
|
||||||
if let Ok(entries) = std::fs::read_dir(dir) {
|
|
||||||
for entry in entries.flatten() {
|
|
||||||
let path = entry.path();
|
|
||||||
|
|
||||||
if path.is_file() {
|
|
||||||
// Check if it's a source file
|
|
||||||
if let Some(ext) = path.extension() {
|
|
||||||
if let Some(ext_str) = ext.to_str() {
|
|
||||||
if extensions.contains(&ext_str) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if path.is_dir() {
|
|
||||||
// Skip hidden directories and common non-source directories
|
|
||||||
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
|
|
||||||
if !name.starts_with('.') && name != "logs" && name != "target" && name != "node_modules" {
|
|
||||||
// Recursively check subdirectories
|
|
||||||
if self.check_dir_for_implementation_files(&path) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read the requirements file content
|
/// Read the requirements file content
|
||||||
pub fn read_requirements(&self) -> Result<Option<String>> {
|
pub fn read_requirements(&self) -> Result<Option<String>> {
|
||||||
// Prioritize requirements text override
|
// Prioritize requirements text override
|
||||||
|
|||||||
Reference in New Issue
Block a user