1mod agent;
114mod analysis;
115mod chain;
116mod checkpoint;
117mod commit;
118mod context;
119mod development;
120mod lifecycle;
121mod planning;
122mod rebase;
123mod retry_guidance;
124mod review;
125
126#[cfg(test)]
127mod tests;
128
129use crate::phases::PhaseContext;
130use crate::reducer::effect::{Effect, EffectHandler, EffectResult};
131use crate::reducer::event::{PipelineEvent, PipelinePhase};
132use crate::reducer::state::PipelineState;
133use crate::reducer::ui_event::UIEvent;
134use anyhow::Result;
135
136pub struct MainEffectHandler {
140 pub state: PipelineState,
142 pub event_log: Vec<PipelineEvent>,
144}
145
146impl MainEffectHandler {
147 pub fn new(state: PipelineState) -> Self {
149 Self {
150 state,
151 event_log: Vec::new(),
152 }
153 }
154}
155
156impl<'ctx> EffectHandler<'ctx> for MainEffectHandler {
157 fn execute(&mut self, effect: Effect, ctx: &mut PhaseContext<'_>) -> Result<EffectResult> {
158 let result = self.execute_effect(effect, ctx)?;
159 self.event_log.push(result.event.clone());
160 self.event_log
161 .extend(result.additional_events.iter().cloned());
162 Ok(result)
163 }
164}
165
166impl crate::app::event_loop::StatefulHandler for MainEffectHandler {
167 fn update_state(&mut self, state: PipelineState) {
168 self.state = state;
169 }
170}
171
172impl MainEffectHandler {
173 fn phase_transition_ui(&self, to: PipelinePhase) -> UIEvent {
175 UIEvent::PhaseTransition {
176 from: Some(self.state.phase),
177 to,
178 }
179 }
180
181 fn write_completion_marker(ctx: &PhaseContext<'_>, content: &str, is_failure: bool) -> bool {
182 let marker_dir = std::path::Path::new(".agent/tmp");
183 if let Err(err) = ctx.workspace.create_dir_all(marker_dir) {
184 ctx.logger.warn(&format!(
185 "Failed to create completion marker directory: {}",
186 err
187 ));
188 }
189
190 let marker_path = std::path::Path::new(".agent/tmp/completion_marker");
191 match ctx.workspace.write(marker_path, content) {
192 Ok(()) => {
193 ctx.logger.info(&format!(
194 "Completion marker written: {}",
195 if is_failure { "failure" } else { "success" }
196 ));
197 true
198 }
199 Err(err) => {
200 ctx.logger
201 .warn(&format!("Failed to write completion marker: {}", err));
202 false
203 }
204 }
205 }
206
207 fn execute_effect(
208 &mut self,
209 effect: Effect,
210 ctx: &mut PhaseContext<'_>,
211 ) -> Result<EffectResult> {
212 match effect {
213 Effect::AgentInvocation {
214 role,
215 agent,
216 model,
217 prompt,
218 } => self.invoke_agent(ctx, role, agent, model, prompt),
219
220 Effect::InitializeAgentChain { role } => self.initialize_agent_chain(ctx, role),
221
222 Effect::PreparePlanningPrompt {
223 iteration,
224 prompt_mode,
225 } => self.prepare_planning_prompt(ctx, iteration, prompt_mode),
226
227 Effect::MaterializePlanningInputs { iteration } => {
228 self.materialize_planning_inputs(ctx, iteration)
229 }
230
231 Effect::CleanupPlanningXml { iteration } => self.cleanup_planning_xml(ctx, iteration),
232
233 Effect::InvokePlanningAgent { iteration } => self.invoke_planning_agent(ctx, iteration),
234
235 Effect::ExtractPlanningXml { iteration } => self.extract_planning_xml(ctx, iteration),
236
237 Effect::ValidatePlanningXml { iteration } => self.validate_planning_xml(ctx, iteration),
238
239 Effect::WritePlanningMarkdown { iteration } => {
240 self.write_planning_markdown(ctx, iteration)
241 }
242
243 Effect::ArchivePlanningXml { iteration } => self.archive_planning_xml(ctx, iteration),
244
245 Effect::ApplyPlanningOutcome { iteration, valid } => {
246 self.apply_planning_outcome(ctx, iteration, valid)
247 }
248
249 Effect::PrepareDevelopmentContext { iteration } => {
250 self.prepare_development_context(ctx, iteration)
251 }
252
253 Effect::MaterializeDevelopmentInputs { iteration } => {
254 self.materialize_development_inputs(ctx, iteration)
255 }
256
257 Effect::PrepareDevelopmentPrompt {
258 iteration,
259 prompt_mode,
260 } => self.prepare_development_prompt(ctx, iteration, prompt_mode),
261
262 Effect::CleanupDevelopmentXml { iteration } => {
263 self.cleanup_development_xml(ctx, iteration)
264 }
265
266 Effect::InvokeDevelopmentAgent { iteration } => {
267 self.invoke_development_agent(ctx, iteration)
268 }
269
270 Effect::InvokeAnalysisAgent { iteration } => self.invoke_analysis_agent(ctx, iteration),
271
272 Effect::ExtractDevelopmentXml { iteration } => {
273 self.extract_development_xml(ctx, iteration)
274 }
275
276 Effect::ValidateDevelopmentXml { iteration } => {
277 self.validate_development_xml(ctx, iteration)
278 }
279
280 Effect::ApplyDevelopmentOutcome { iteration } => {
281 self.apply_development_outcome(ctx, iteration)
282 }
283
284 Effect::ArchiveDevelopmentXml { iteration } => {
285 self.archive_development_xml(ctx, iteration)
286 }
287
288 Effect::PrepareReviewContext { pass } => self.prepare_review_context(ctx, pass),
289
290 Effect::MaterializeReviewInputs { pass } => self.materialize_review_inputs(ctx, pass),
291
292 Effect::PrepareReviewPrompt { pass, prompt_mode } => {
293 self.prepare_review_prompt(ctx, pass, prompt_mode)
294 }
295
296 Effect::CleanupReviewIssuesXml { pass } => self.cleanup_review_issues_xml(ctx, pass),
297
298 Effect::InvokeReviewAgent { pass } => self.invoke_review_agent(ctx, pass),
299
300 Effect::ExtractReviewIssuesXml { pass } => self.extract_review_issues_xml(ctx, pass),
301
302 Effect::ValidateReviewIssuesXml { pass } => self.validate_review_issues_xml(ctx, pass),
303
304 Effect::WriteIssuesMarkdown { pass } => self.write_issues_markdown(ctx, pass),
305
306 Effect::ExtractReviewIssueSnippets { pass } => {
307 self.extract_review_issue_snippets(ctx, pass)
308 }
309
310 Effect::ArchiveReviewIssuesXml { pass } => self.archive_review_issues_xml(ctx, pass),
311
312 Effect::ApplyReviewOutcome {
313 pass,
314 issues_found,
315 clean_no_issues,
316 } => self.apply_review_outcome(ctx, pass, issues_found, clean_no_issues),
317
318 Effect::PrepareFixPrompt { pass, prompt_mode } => {
319 self.prepare_fix_prompt(ctx, pass, prompt_mode)
320 }
321
322 Effect::CleanupFixResultXml { pass } => self.cleanup_fix_result_xml(ctx, pass),
323
324 Effect::InvokeFixAgent { pass } => self.invoke_fix_agent(ctx, pass),
325
326 Effect::ExtractFixResultXml { pass } => self.extract_fix_result_xml(ctx, pass),
327
328 Effect::ValidateFixResultXml { pass } => self.validate_fix_result_xml(ctx, pass),
329
330 Effect::ApplyFixOutcome { pass } => self.apply_fix_outcome(ctx, pass),
331
332 Effect::ArchiveFixResultXml { pass } => self.archive_fix_result_xml(ctx, pass),
333
334 Effect::RunRebase {
335 phase,
336 target_branch,
337 } => self.run_rebase(ctx, phase, target_branch),
338
339 Effect::ResolveRebaseConflicts { strategy } => {
340 self.resolve_rebase_conflicts(ctx, strategy)
341 }
342
343 Effect::PrepareCommitPrompt { prompt_mode } => {
344 self.prepare_commit_prompt(ctx, prompt_mode)
345 }
346
347 Effect::CheckCommitDiff => self.check_commit_diff(ctx),
348
349 Effect::MaterializeCommitInputs { attempt } => {
350 self.materialize_commit_inputs(ctx, attempt)
351 }
352
353 Effect::InvokeCommitAgent => self.invoke_commit_agent(ctx),
354
355 Effect::CleanupCommitXml => self.cleanup_commit_xml(ctx),
356
357 Effect::ExtractCommitXml => self.extract_commit_xml(ctx),
358
359 Effect::ValidateCommitXml => self.validate_commit_xml(ctx),
360
361 Effect::ApplyCommitMessageOutcome => self.apply_commit_message_outcome(ctx),
362
363 Effect::ArchiveCommitXml => self.archive_commit_xml(ctx),
364
365 Effect::CreateCommit { message } => self.create_commit(ctx, message),
366
367 Effect::SkipCommit { reason } => self.skip_commit(ctx, reason),
368
369 Effect::BackoffWait {
370 role,
371 cycle,
372 duration_ms,
373 } => {
374 use std::time::Duration;
375 ctx.registry
376 .retry_timer()
377 .sleep(Duration::from_millis(duration_ms));
378 Ok(EffectResult::event(
379 PipelineEvent::agent_retry_cycle_started(role, cycle),
380 ))
381 }
382
383 Effect::ReportAgentChainExhausted { role, phase, cycle } => {
384 use crate::reducer::event::ErrorEvent;
385 Err(ErrorEvent::AgentChainExhausted { role, phase, cycle }.into())
386 }
387
388 Effect::ValidateFinalState => self.validate_final_state(ctx),
389
390 Effect::SaveCheckpoint { trigger } => self.save_checkpoint(ctx, trigger),
391
392 Effect::EnsureGitignoreEntries => self.ensure_gitignore_entries(ctx),
393
394 Effect::CleanupContext => self.cleanup_context(ctx),
395
396 Effect::LockPromptPermissions => self.lock_prompt_permissions(ctx),
397
398 Effect::RestorePromptPermissions => self.restore_prompt_permissions(ctx),
399
400 Effect::WriteContinuationContext(ref data) => {
401 development::write_continuation_context_to_workspace(
402 ctx.workspace,
403 ctx.logger,
404 data,
405 )?;
406 Ok(EffectResult::event(
407 PipelineEvent::development_continuation_context_written(
408 data.iteration,
409 data.attempt,
410 ),
411 ))
412 }
413
414 Effect::CleanupContinuationContext => self.cleanup_continuation_context(ctx),
415
416 Effect::TriggerLoopRecovery {
417 detected_loop,
418 loop_count,
419 } => self.trigger_loop_recovery(ctx, detected_loop, loop_count),
420
421 Effect::TriggerDevFixFlow {
422 failed_phase,
423 failed_role,
424 retry_cycle,
425 } => self.trigger_dev_fix_flow(ctx, failed_phase, failed_role, retry_cycle),
426
427 Effect::EmitCompletionMarkerAndTerminate { is_failure, reason } => {
428 Self::emit_completion_marker_and_terminate(ctx, is_failure, reason)
429 }
430 }
431 }
432}