Files
codex-controller-loop/src/controller/executor.rs
2026-04-04 12:37:50 +02:00

109 lines
4.0 KiB
Rust

use std::sync::mpsc::Sender;
use anyhow::Result;
use serde_json::{json, Value};
use crate::app::AppEvent;
use crate::model::{ExecutionResponse, Plan, PlanStep, SessionSource, TaskConfig};
use crate::process;
use crate::prompt;
use crate::storage::toon;
pub fn implement(
repo_root: &std::path::Path,
config: &TaskConfig,
plan: &Plan,
step: &PlanStep,
event_tx: &Sender<AppEvent>,
) -> Result<ExecutionResponse> {
let goal_md = toon::read_markdown(&config.goal_file)?;
let standards_md = toon::read_markdown(&config.standards_file)?;
let context = build_execution_context(plan, step);
let prompt = format!(
concat!(
"You are the autonomous execution worker for a Rust TUI-first controller.\n",
"Execution mode only. Do not ask the user questions.\n",
"Complete the active step with the smallest correct change set.\n\n",
"Efficiency rules:\n",
"- Inspect only files likely relevant to this step.\n",
"- Avoid repository-wide searches unless the focused path is exhausted.\n",
"- Prefer targeted verification and targeted tests over broad full-suite runs.\n",
"- Keep output terse. Use short summaries and short notes.\n",
"- If the requested change is already present, return done.\n",
"- If the goal is genuinely ambiguous, set needs_goal_clarification=true.\n\n",
"Return empty arrays for verification_commands, test_commands, or notes when not needed.\n\n",
"Goal summary:\n{goal}\n\n",
"Standards summary:\n{standards}\n\n",
"Execution context:\n{context}\n"
),
goal = prompt::compact_markdown(&goal_md, 8, 1200),
standards = prompt::compact_markdown(&standards_md, 10, 1200),
context = serde_json::to_string_pretty(&context)?,
);
let schema = json!({
"type": "object",
"additionalProperties": false,
"required": ["status", "summary", "verification_commands", "test_commands", "notes", "needs_goal_clarification"],
"properties": {
"status": { "type": "string", "enum": ["done", "blocked", "needs-replan"] },
"summary": { "type": "string" },
"verification_commands": { "type": "array", "items": { "type": "string" } },
"test_commands": { "type": "array", "items": { "type": "string" } },
"notes": { "type": "array", "items": { "type": "string" } },
"needs_goal_clarification": { "type": "boolean" }
}
});
let raw = process::run_codex_with_schema(
repo_root,
&prompt,
&schema,
event_tx,
SessionSource::Executor,
Some(step.id.clone()),
)?;
Ok(serde_json::from_str(&raw)?)
}
fn build_execution_context(plan: &Plan, step: &PlanStep) -> Value {
let dependency_steps = step
.dependencies
.iter()
.filter_map(|dependency| {
plan.steps
.iter()
.find(|candidate| &candidate.id == dependency)
.map(|candidate| {
json!({
"id": candidate.id,
"title": prompt::truncate_text(&candidate.title, 100),
"status": candidate.status,
})
})
})
.collect::<Vec<_>>();
let next_steps = plan
.steps
.iter()
.filter(|candidate| candidate.id != step.id)
.filter(|candidate| !candidate.status.is_done())
.take(3)
.map(|candidate| {
json!({
"id": candidate.id,
"title": prompt::truncate_text(&candidate.title, 100),
"dependencies": prompt::compact_string_vec(&candidate.dependencies, 4, 60),
"status": candidate.status,
})
})
.collect::<Vec<_>>();
json!({
"goal_summary": prompt::truncate_text(&plan.goal_summary, 200),
"active_step": prompt::compact_step(step),
"dependency_steps": dependency_steps,
"next_pending_steps": next_steps,
})
}