use tokio::sync::mpsc;
use crate::agent::approval::ApprovalGate;
use crate::agent::context::ConversationContext;
use super::core::run_loop;
use super::{AgentEvent, AgentParams, ExecutePlanParams};
pub async fn run_architect_code_loop(params: AgentParams) {
let AgentParams {
client,
config,
context,
user_msg,
working_dir,
event_tx,
cancel,
lsp_manager,
..
} = params;
let _ = event_tx.send(AgentEvent::PhaseChange {
label: "Architect — analyzing codebase and creating plan...".to_string(),
});
let arch_prompt = format!(
"[ARCHITECT PHASE] Analyze the codebase thoroughly using file_read, search, and bash tools. \
Produce a detailed, step-by-step implementation plan for the following task. \
Include exact file paths, function signatures, and the specific changes required. \
Do NOT write or modify any files — planning only.\n\n{user_msg}"
);
let mut arch_client = client.clone();
if let Some(arch_agent) = config.agents.iter().find(|a| a.name == "architect") {
arch_client.model = arch_agent.model.clone();
}
let (arch_tx, mut arch_rx) = mpsc::unbounded_channel::<AgentEvent>();
let arch_cancel = cancel.clone();
let arch_config = config.clone();
let arch_context = context.clone();
let arch_working_dir = working_dir.clone();
let arch_lsp = lsp_manager.clone();
tokio::spawn(async move {
run_loop(AgentParams {
client: arch_client,
config: arch_config,
context: arch_context,
user_msg: arch_prompt,
working_dir: arch_working_dir,
event_tx: arch_tx,
cancel: arch_cancel,
lsp_manager: arch_lsp,
trust_level: crate::trust::TrustLevel::Full,
approval_gate: ApprovalGate::yolo(),
images: Vec::new(),
})
.await;
});
let mut plan_text = String::new();
let mut arch_final_context: Option<ConversationContext> = None;
while let Some(event) = arch_rx.recv().await {
match event {
AgentEvent::Done { context: ctx, .. } => {
plan_text = ctx
.messages()
.iter()
.rev()
.find(|m| m.role == "assistant")
.and_then(|m| m.content.as_ref().map(|c| c.text_content()))
.unwrap_or_default();
arch_final_context = Some(ctx);
break;
}
other => {
let _ = event_tx.send(other);
}
}
}
if cancel.is_cancelled() || plan_text.is_empty() {
let ctx = arch_final_context.unwrap_or(context);
let _ = event_tx.send(AgentEvent::Done {
context: ctx,
stop_reason: None,
});
return;
}
let final_ctx = arch_final_context.unwrap_or(context);
let _ = event_tx.send(AgentEvent::PlanReady {
plan: plan_text,
context: final_ctx,
user_msg,
});
}
pub async fn execute_plan(params: ExecutePlanParams) {
let ExecutePlanParams {
client,
config,
system_prompt,
plan,
user_msg,
working_dir,
event_tx,
cancel,
lsp_manager,
arch_context,
approval_gate,
} = params;
let _ = event_tx.send(AgentEvent::PhaseChange {
label: "Code — executing implementation plan...".to_string(),
});
let code_context = ConversationContext::new(system_prompt);
let arch_files_hint = arch_context
.as_ref()
.map(extract_arch_files)
.unwrap_or_default();
let files_section = if arch_files_hint.is_empty() {
String::new()
} else {
format!(
"## Files already analyzed by Architect\n\n\
The following files were examined during planning — you can reference \
the plan's findings without re-reading them unless you need to verify \
a specific detail:\n{}\n\n",
arch_files_hint
.iter()
.map(|f| format!("- {f}"))
.collect::<Vec<_>>()
.join("\n")
)
};
let code_msg = format!(
"Execute the following implementation plan.\n\n\
## Plan\n\n{plan}\n\n\
{files_section}\
## Original task\n\n{user_msg}\n\n\
Implement all changes described in the plan step by step."
);
let mut code_client = client;
if let Some(code_agent) = config.agents.iter().find(|a| a.name == "code") {
code_client.model = code_agent.model.clone();
}
run_loop(AgentParams {
client: code_client,
config,
context: code_context,
user_msg: code_msg,
working_dir,
event_tx,
cancel,
lsp_manager,
trust_level: crate::trust::TrustLevel::Full,
approval_gate,
images: Vec::new(),
})
.await;
}
fn extract_arch_files(ctx: &ConversationContext) -> Vec<String> {
let mut seen = std::collections::HashSet::new();
let mut paths = Vec::new();
for msg in ctx.messages() {
if msg.role != "assistant" {
continue;
}
let Some(ref tool_calls) = msg.tool_calls else {
continue;
};
for tc in tool_calls {
if tc.function.name != "file_read" {
continue;
}
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&tc.function.arguments)
&& let Some(path) = v.get("path").and_then(|p| p.as_str())
&& seen.insert(path.to_string())
{
paths.push(path.to_string());
}
}
}
paths
}