use super::types::{AgentConfig, AgentHandle};
use crate::cook::orchestrator::ExecutionEnvironment;
use crate::cook::workflow::WorkflowStep;
use crate::worktree::WorktreeManager;
use async_trait::async_trait;
use std::path::Path;
use std::sync::Arc;
use tracing::{info, warn};
#[derive(Debug, thiserror::Error)]
pub enum LifecycleError {
#[error("Failed to create worktree: {0}")]
WorktreeCreation(String),
#[error("Failed to create branch: {0}")]
BranchCreation(String),
#[error("Failed to merge branch: {0}")]
MergeError(String),
#[error("Git operation failed: {0}")]
GitError(String),
#[error("Cleanup failed: {0}")]
CleanupError(String),
}
pub type LifecycleResult<T> = Result<T, LifecycleError>;
#[async_trait]
#[allow(clippy::too_many_arguments)]
pub trait AgentLifecycleManager: Send + Sync {
async fn create_agent(
&self,
config: AgentConfig,
commands: Vec<WorkflowStep>,
) -> LifecycleResult<AgentHandle>;
async fn create_agent_branch(
&self,
worktree_path: &Path,
branch_name: &str,
) -> LifecycleResult<()>;
async fn merge_agent_to_parent(
&self,
agent_branch: &str,
env: &ExecutionEnvironment,
) -> LifecycleResult<()>;
async fn handle_merge_and_cleanup(
&self,
is_successful: bool,
env: &ExecutionEnvironment,
worktree_path: &Path,
worktree_name: &str,
branch_name: &str,
template_steps: &[WorkflowStep],
item_id: &str,
) -> LifecycleResult<bool>;
async fn cleanup_agent(&self, handle: AgentHandle) -> LifecycleResult<()>;
async fn get_worktree_commits(&self, worktree_path: &Path) -> LifecycleResult<Vec<String>>;
async fn get_modified_files(&self, worktree_path: &Path) -> LifecycleResult<Vec<String>>;
}
pub struct DefaultLifecycleManager {
worktree_manager: Arc<WorktreeManager>,
}
impl DefaultLifecycleManager {
pub fn new(worktree_manager: Arc<WorktreeManager>) -> Self {
Self { worktree_manager }
}
}
#[async_trait]
impl AgentLifecycleManager for DefaultLifecycleManager {
async fn create_agent(
&self,
config: AgentConfig,
commands: Vec<WorkflowStep>,
) -> LifecycleResult<AgentHandle> {
let session_id = format!("mapreduce-agent-{}", config.id);
let worktree_session = self
.worktree_manager
.create_session_with_id(&session_id)
.await
.map_err(|e| LifecycleError::WorktreeCreation(e.to_string()))?;
let handle = AgentHandle::new(config, worktree_session, commands);
Ok(handle)
}
async fn create_agent_branch(
&self,
worktree_path: &Path,
branch_name: &str,
) -> LifecycleResult<()> {
use tokio::process::Command;
let output = Command::new("git")
.args(["checkout", "-b", branch_name])
.current_dir(worktree_path)
.output()
.await
.map_err(|e| LifecycleError::GitError(e.to_string()))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(LifecycleError::BranchCreation(format!(
"Failed to create branch {}: {}",
branch_name, stderr
)));
}
Ok(())
}
async fn merge_agent_to_parent(
&self,
agent_branch: &str,
env: &ExecutionEnvironment,
) -> LifecycleResult<()> {
use tokio::process::Command;
let parent_worktree_path = &env.working_dir;
let output = Command::new("git")
.args(["merge", "--no-ff", agent_branch])
.current_dir(&**parent_worktree_path)
.output()
.await
.map_err(|e| LifecycleError::GitError(e.to_string()))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(LifecycleError::MergeError(format!(
"Failed to merge branch {}: {}",
agent_branch, stderr
)));
}
Ok(())
}
async fn handle_merge_and_cleanup(
&self,
is_successful: bool,
env: &ExecutionEnvironment,
worktree_path: &Path,
worktree_name: &str,
branch_name: &str,
template_steps: &[WorkflowStep],
item_id: &str,
) -> LifecycleResult<bool> {
if is_successful && env.worktree_name.is_some() {
self.create_agent_branch(worktree_path, branch_name).await?;
match self.merge_agent_to_parent(branch_name, env).await {
Ok(()) => {
info!("Successfully merged agent {} to parent worktree", item_id);
self.worktree_manager
.cleanup_session(worktree_name, true)
.await
.map_err(|e| LifecycleError::CleanupError(e.to_string()))?;
Ok(true)
}
Err(e) => {
warn!("Failed to merge agent {} to parent: {}", item_id, e);
Ok(false)
}
}
} else {
if !template_steps.is_empty() {
self.worktree_manager
.cleanup_session(worktree_name, true)
.await
.map_err(|e| LifecycleError::CleanupError(e.to_string()))?;
}
Ok(false)
}
}
async fn cleanup_agent(&self, handle: AgentHandle) -> LifecycleResult<()> {
self.worktree_manager
.cleanup_session(&handle.worktree_session.name, true)
.await
.map_err(|e| LifecycleError::CleanupError(e.to_string()))?;
Ok(())
}
async fn get_worktree_commits(&self, worktree_path: &Path) -> LifecycleResult<Vec<String>> {
use crate::cook::execution::mapreduce::resources::git_operations::{
GitOperationsConfig, GitOperationsService, GitResultExt,
};
let mut service = GitOperationsService::new(GitOperationsConfig::default());
match service
.get_worktree_commits(worktree_path, None, None)
.await
{
Ok(commits) => Ok(commits.to_string_list()),
Err(e) => {
warn!("Failed to get worktree commits: {}", e);
Ok(vec![])
}
}
}
async fn get_modified_files(&self, worktree_path: &Path) -> LifecycleResult<Vec<String>> {
use crate::cook::execution::mapreduce::resources::git_operations::{
GitOperationsConfig, GitOperationsService, GitResultExt,
};
let mut service = GitOperationsService::new(GitOperationsConfig::default());
match service
.get_worktree_modified_files(worktree_path, None)
.await
{
Ok(files) => Ok(files.to_string_list()),
Err(e) => {
warn!("Failed to get modified files: {}", e);
Ok(vec![])
}
}
}
}