1mod agent;
114mod analysis;
115mod chain;
116mod checkpoint;
117mod cloud;
118mod commit;
119mod context;
120mod development;
121mod lifecycle;
122mod planning;
123mod rebase;
124mod retry_guidance;
125mod review;
126
127#[cfg(test)]
128mod tests;
129
130use crate::phases::PhaseContext;
131use crate::reducer::effect::{Effect, EffectHandler, EffectResult};
132use crate::reducer::event::{PipelineEvent, PipelinePhase};
133use crate::reducer::state::PipelineState;
134use crate::reducer::ui_event::UIEvent;
135use anyhow::Result;
136
137pub struct MainEffectHandler {
141 pub state: PipelineState,
143 pub event_log: Vec<PipelineEvent>,
145}
146
147impl MainEffectHandler {
148 #[must_use]
150 pub const fn new(state: PipelineState) -> Self {
151 Self {
152 state,
153 event_log: Vec::new(),
154 }
155 }
156}
157
158impl EffectHandler<'_> for MainEffectHandler {
159 fn execute(&mut self, effect: Effect, ctx: &mut PhaseContext<'_>) -> Result<EffectResult> {
160 let result = self.execute_effect(effect, ctx)?;
161 self.event_log.push(result.event.clone());
162 self.event_log
163 .extend(result.additional_events.iter().cloned());
164 Ok(result)
165 }
166}
167
168impl crate::app::event_loop::StatefulHandler for MainEffectHandler {
169 fn update_state(&mut self, state: PipelineState) {
170 self.state = state;
171 }
172}
173
174impl MainEffectHandler {
175 const fn phase_transition_ui(&self, to: PipelinePhase) -> UIEvent {
177 UIEvent::PhaseTransition {
178 from: Some(self.state.phase),
179 to,
180 }
181 }
182
183 fn write_completion_marker(
184 ctx: &PhaseContext<'_>,
185 content: &str,
186 is_failure: bool,
187 ) -> std::result::Result<(), String> {
188 let marker_dir = std::path::Path::new(".agent/tmp");
189 if let Err(err) = ctx.workspace.create_dir_all(marker_dir) {
190 ctx.logger.warn(&format!(
191 "Failed to create completion marker directory: {err}"
192 ));
193 }
194
195 let marker_path = std::path::Path::new(".agent/tmp/completion_marker");
196 match ctx.workspace.write(marker_path, content) {
197 Ok(()) => {
198 ctx.logger.info(&format!(
199 "Completion marker written: {}",
200 if is_failure { "failure" } else { "success" }
201 ));
202 Ok(())
203 }
204 Err(err) => {
205 ctx.logger
206 .warn(&format!("Failed to write completion marker: {err}"));
207 Err(err.to_string())
208 }
209 }
210 }
211
212 fn execute_effect(
213 &mut self,
214 effect: Effect,
215 ctx: &mut PhaseContext<'_>,
216 ) -> Result<EffectResult> {
217 match effect {
218 Effect::AgentInvocation {
219 role,
220 agent,
221 model,
222 prompt,
223 } => self.invoke_agent(
224 ctx,
225 crate::agents::AgentDrain::from(role),
226 role,
227 &agent,
228 model.as_deref(),
229 prompt,
230 ),
231
232 Effect::InitializeAgentChain { drain, .. } => {
233 Ok(self.initialize_agent_chain(ctx, drain))
234 }
235
236 Effect::PreparePlanningPrompt {
237 iteration,
238 prompt_mode,
239 } => self.prepare_planning_prompt(ctx, iteration, prompt_mode),
240
241 Effect::MaterializePlanningInputs { iteration } => {
242 self.materialize_planning_inputs(ctx, iteration)
243 }
244
245 Effect::CleanupRequiredFiles { files } => Ok(self.cleanup_required_files(ctx, &files)),
246
247 Effect::InvokePlanningAgent { iteration } => self.invoke_planning_agent(ctx, iteration),
248
249 Effect::ExtractPlanningXml { iteration } => {
250 Ok(self.extract_planning_xml(ctx, iteration))
251 }
252
253 Effect::ValidatePlanningXml { iteration } => self.validate_planning_xml(ctx, iteration),
254
255 Effect::WritePlanningMarkdown { iteration } => {
256 self.write_planning_markdown(ctx, iteration)
257 }
258
259 Effect::ArchivePlanningXml { iteration } => {
260 Ok(Self::archive_planning_xml(ctx, iteration))
261 }
262
263 Effect::ApplyPlanningOutcome { iteration, valid } => {
264 Ok(self.apply_planning_outcome(ctx, iteration, valid))
265 }
266
267 Effect::PrepareDevelopmentContext { iteration } => {
268 Ok(Self::prepare_development_context(ctx, iteration))
269 }
270
271 Effect::MaterializeDevelopmentInputs { iteration } => {
272 self.materialize_development_inputs(ctx, iteration)
273 }
274
275 Effect::PrepareDevelopmentPrompt {
276 iteration,
277 prompt_mode,
278 } => self.prepare_development_prompt(ctx, iteration, prompt_mode),
279
280 Effect::InvokeDevelopmentAgent { iteration } => {
281 self.invoke_development_agent(ctx, iteration)
282 }
283
284 Effect::InvokeAnalysisAgent { iteration } => self.invoke_analysis_agent(ctx, iteration),
285
286 Effect::ExtractDevelopmentXml { iteration } => {
287 Ok(self.extract_development_xml(ctx, iteration))
288 }
289
290 Effect::ValidateDevelopmentXml { iteration } => {
291 Ok(self.validate_development_xml(ctx, iteration))
292 }
293
294 Effect::ApplyDevelopmentOutcome { iteration } => {
295 self.apply_development_outcome(ctx, iteration)
296 }
297
298 Effect::ArchiveDevelopmentXml { iteration } => {
299 Ok(Self::archive_development_xml(ctx, iteration))
300 }
301
302 Effect::PrepareReviewContext { pass } => Ok(self.prepare_review_context(ctx, pass)),
303
304 Effect::MaterializeReviewInputs { pass } => self.materialize_review_inputs(ctx, pass),
305
306 Effect::PrepareReviewPrompt { pass, prompt_mode } => {
307 self.prepare_review_prompt(ctx, pass, prompt_mode)
308 }
309
310 Effect::InvokeReviewAgent { pass } => self.invoke_review_agent(ctx, pass),
311
312 Effect::ExtractReviewIssuesXml { pass } => {
313 Ok(self.extract_review_issues_xml(ctx, pass))
314 }
315
316 Effect::ValidateReviewIssuesXml { pass } => {
317 Ok(self.validate_review_issues_xml(ctx, pass))
318 }
319
320 Effect::WriteIssuesMarkdown { pass } => self.write_issues_markdown(ctx, pass),
321
322 Effect::ExtractReviewIssueSnippets { pass } => {
323 self.extract_review_issue_snippets(ctx, pass)
324 }
325
326 Effect::ArchiveReviewIssuesXml { pass } => {
327 Ok(Self::archive_review_issues_xml(ctx, pass))
328 }
329
330 Effect::ApplyReviewOutcome {
331 pass,
332 issues_found,
333 clean_no_issues,
334 } => Ok(Self::apply_review_outcome(
335 ctx,
336 pass,
337 issues_found,
338 clean_no_issues,
339 )),
340
341 Effect::PrepareFixPrompt { pass, prompt_mode } => {
342 self.prepare_fix_prompt(ctx, pass, prompt_mode)
343 }
344
345 Effect::InvokeFixAgent { pass } => self.invoke_fix_agent(ctx, pass),
346
347 Effect::InvokeFixAnalysisAgent { pass } => self.invoke_fix_analysis_agent(ctx, pass),
348
349 Effect::ExtractFixResultXml { pass } => Ok(self.extract_fix_result_xml(ctx, pass)),
350
351 Effect::ValidateFixResultXml { pass } => Ok(self.validate_fix_result_xml(ctx, pass)),
352
353 Effect::ApplyFixOutcome { pass } => self.apply_fix_outcome(ctx, pass),
354
355 Effect::ArchiveFixResultXml { pass } => Ok(self.archive_fix_result_xml(ctx, pass)),
356
357 Effect::RunRebase {
358 phase,
359 target_branch,
360 } => self.run_rebase(ctx, phase, &target_branch),
361
362 Effect::ResolveRebaseConflicts { strategy } => {
363 Ok(Self::resolve_rebase_conflicts(ctx, strategy))
364 }
365
366 Effect::PrepareCommitPrompt { prompt_mode } => {
367 self.prepare_commit_prompt(ctx, prompt_mode)
368 }
369
370 Effect::CheckCommitDiff => Self::check_commit_diff(ctx),
371
372 Effect::MaterializeCommitInputs { attempt } => {
373 self.materialize_commit_inputs(ctx, attempt)
374 }
375
376 Effect::InvokeCommitAgent => self.invoke_commit_agent(ctx),
377
378 Effect::ExtractCommitXml => Ok(self.extract_commit_xml(ctx)),
379
380 Effect::ValidateCommitXml => Ok(self.validate_commit_xml(ctx)),
381
382 Effect::ApplyCommitMessageOutcome => self.apply_commit_message_outcome(ctx),
383
384 Effect::ArchiveCommitXml => Ok(self.archive_commit_xml(ctx)),
385
386 Effect::CreateCommit {
387 message,
388 files,
389 excluded_files,
390 } => Self::create_commit(ctx, message, &files, &excluded_files),
391
392 Effect::SkipCommit { reason } => Ok(Self::skip_commit(ctx, reason)),
393
394 Effect::CheckResidualFiles { pass } => Self::check_residual_files(ctx, pass),
395
396 Effect::CheckUncommittedChangesBeforeTermination => {
397 Self::check_uncommitted_changes_before_termination(ctx)
398 }
399
400 Effect::BackoffWait {
401 role,
402 cycle,
403 duration_ms,
404 } => {
405 use std::time::Duration;
406 ctx.registry
407 .retry_timer()
408 .sleep(Duration::from_millis(duration_ms));
409 Ok(EffectResult::event(
410 PipelineEvent::agent_retry_cycle_started(role, cycle),
411 ))
412 }
413
414 Effect::ReportAgentChainExhausted { role, phase, cycle } => {
415 use crate::reducer::event::ErrorEvent;
416 Err(ErrorEvent::AgentChainExhausted { role, phase, cycle }.into())
417 }
418
419 Effect::ValidateFinalState => Ok(self.validate_final_state(ctx)),
420
421 Effect::SaveCheckpoint { trigger } => Ok(self.save_checkpoint(ctx, trigger)),
422
423 Effect::EnsureGitignoreEntries => Ok(Self::ensure_gitignore_entries(ctx)),
424
425 Effect::CleanupContext => Self::cleanup_context(ctx),
426
427 Effect::LockPromptPermissions => Ok(Self::lock_prompt_permissions(ctx)),
428
429 Effect::RestorePromptPermissions => Ok(self.restore_prompt_permissions(ctx)),
430
431 Effect::WriteContinuationContext(ref data) => {
432 development::write_continuation_context_to_workspace(
433 ctx.workspace,
434 ctx.logger,
435 data,
436 )?;
437 Ok(EffectResult::event(
438 PipelineEvent::development_continuation_context_written(
439 data.iteration,
440 data.attempt,
441 ),
442 ))
443 }
444
445 Effect::CleanupContinuationContext => Self::cleanup_continuation_context(ctx),
446
447 Effect::WriteTimeoutContext {
448 role,
449 logfile_path,
450 context_path,
451 } => Self::write_timeout_context(ctx, role, &logfile_path, &context_path),
452
453 Effect::TriggerLoopRecovery {
454 detected_loop,
455 loop_count,
456 } => Ok(Self::trigger_loop_recovery(ctx, &detected_loop, loop_count)),
457
458 Effect::EmitRecoveryReset {
459 reset_type,
460 target_phase,
461 } => Ok(self.emit_recovery_reset(ctx, &reset_type, target_phase)),
462
463 Effect::AttemptRecovery {
464 level,
465 attempt_count,
466 } => Ok(self.attempt_recovery(ctx, level, attempt_count)),
467
468 Effect::EmitRecoverySuccess {
469 level,
470 total_attempts,
471 } => Ok(Self::emit_recovery_success(ctx, level, total_attempts)),
472
473 Effect::TriggerDevFixFlow {
474 failed_phase,
475 failed_role,
476 retry_cycle,
477 } => Ok(self.trigger_dev_fix_flow(ctx, failed_phase, failed_role, retry_cycle)),
478
479 Effect::EmitCompletionMarkerAndTerminate { is_failure, reason } => Ok(
480 Self::emit_completion_marker_and_terminate(ctx, is_failure, reason),
481 ),
482
483 Effect::ConfigureGitAuth { auth_method } => {
485 Ok(Self::handle_configure_git_auth(ctx, &auth_method))
486 }
487
488 Effect::PushToRemote {
489 remote,
490 branch,
491 force,
492 commit_sha,
493 } => Ok(Self::handle_push_to_remote(
494 ctx, remote, branch, force, commit_sha,
495 )),
496
497 Effect::CreatePullRequest {
498 base_branch,
499 head_branch,
500 title,
501 body,
502 } => Ok(Self::handle_create_pull_request(
503 ctx,
504 &base_branch,
505 &head_branch,
506 &title,
507 &body,
508 )),
509 }
510 }
511}