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(ctx, role, &agent, model.as_deref(), prompt),
224
225 Effect::InitializeAgentChain { role } => Ok(self.initialize_agent_chain(ctx, role)),
226
227 Effect::PreparePlanningPrompt {
228 iteration,
229 prompt_mode,
230 } => self.prepare_planning_prompt(ctx, iteration, prompt_mode),
231
232 Effect::MaterializePlanningInputs { iteration } => {
233 self.materialize_planning_inputs(ctx, iteration)
234 }
235
236 Effect::CleanupRequiredFiles { files } => Ok(self.cleanup_required_files(ctx, &files)),
237
238 Effect::InvokePlanningAgent { iteration } => self.invoke_planning_agent(ctx, iteration),
239
240 Effect::ExtractPlanningXml { iteration } => {
241 Ok(self.extract_planning_xml(ctx, iteration))
242 }
243
244 Effect::ValidatePlanningXml { iteration } => self.validate_planning_xml(ctx, iteration),
245
246 Effect::WritePlanningMarkdown { iteration } => {
247 self.write_planning_markdown(ctx, iteration)
248 }
249
250 Effect::ArchivePlanningXml { iteration } => {
251 Ok(Self::archive_planning_xml(ctx, iteration))
252 }
253
254 Effect::ApplyPlanningOutcome { iteration, valid } => {
255 Ok(self.apply_planning_outcome(ctx, iteration, valid))
256 }
257
258 Effect::PrepareDevelopmentContext { iteration } => {
259 Ok(Self::prepare_development_context(ctx, iteration))
260 }
261
262 Effect::MaterializeDevelopmentInputs { iteration } => {
263 self.materialize_development_inputs(ctx, iteration)
264 }
265
266 Effect::PrepareDevelopmentPrompt {
267 iteration,
268 prompt_mode,
269 } => self.prepare_development_prompt(ctx, iteration, prompt_mode),
270
271 Effect::InvokeDevelopmentAgent { iteration } => {
272 self.invoke_development_agent(ctx, iteration)
273 }
274
275 Effect::InvokeAnalysisAgent { iteration } => self.invoke_analysis_agent(ctx, iteration),
276
277 Effect::ExtractDevelopmentXml { iteration } => {
278 Ok(self.extract_development_xml(ctx, iteration))
279 }
280
281 Effect::ValidateDevelopmentXml { iteration } => {
282 Ok(self.validate_development_xml(ctx, iteration))
283 }
284
285 Effect::ApplyDevelopmentOutcome { iteration } => {
286 self.apply_development_outcome(ctx, iteration)
287 }
288
289 Effect::ArchiveDevelopmentXml { iteration } => {
290 Ok(Self::archive_development_xml(ctx, iteration))
291 }
292
293 Effect::PrepareReviewContext { pass } => Ok(self.prepare_review_context(ctx, pass)),
294
295 Effect::MaterializeReviewInputs { pass } => self.materialize_review_inputs(ctx, pass),
296
297 Effect::PrepareReviewPrompt { pass, prompt_mode } => {
298 self.prepare_review_prompt(ctx, pass, prompt_mode)
299 }
300
301 Effect::InvokeReviewAgent { pass } => self.invoke_review_agent(ctx, pass),
302
303 Effect::ExtractReviewIssuesXml { pass } => {
304 Ok(self.extract_review_issues_xml(ctx, pass))
305 }
306
307 Effect::ValidateReviewIssuesXml { pass } => {
308 Ok(self.validate_review_issues_xml(ctx, pass))
309 }
310
311 Effect::WriteIssuesMarkdown { pass } => self.write_issues_markdown(ctx, pass),
312
313 Effect::ExtractReviewIssueSnippets { pass } => {
314 self.extract_review_issue_snippets(ctx, pass)
315 }
316
317 Effect::ArchiveReviewIssuesXml { pass } => {
318 Ok(Self::archive_review_issues_xml(ctx, pass))
319 }
320
321 Effect::ApplyReviewOutcome {
322 pass,
323 issues_found,
324 clean_no_issues,
325 } => Ok(Self::apply_review_outcome(
326 ctx,
327 pass,
328 issues_found,
329 clean_no_issues,
330 )),
331
332 Effect::PrepareFixPrompt { pass, prompt_mode } => {
333 self.prepare_fix_prompt(ctx, pass, prompt_mode)
334 }
335
336 Effect::InvokeFixAgent { pass } => self.invoke_fix_agent(ctx, pass),
337
338 Effect::ExtractFixResultXml { pass } => Ok(self.extract_fix_result_xml(ctx, pass)),
339
340 Effect::ValidateFixResultXml { pass } => Ok(self.validate_fix_result_xml(ctx, pass)),
341
342 Effect::ApplyFixOutcome { pass } => self.apply_fix_outcome(ctx, pass),
343
344 Effect::ArchiveFixResultXml { pass } => Ok(Self::archive_fix_result_xml(ctx, pass)),
345
346 Effect::RunRebase {
347 phase,
348 target_branch,
349 } => self.run_rebase(ctx, phase, &target_branch),
350
351 Effect::ResolveRebaseConflicts { strategy } => {
352 Ok(Self::resolve_rebase_conflicts(ctx, strategy))
353 }
354
355 Effect::PrepareCommitPrompt { prompt_mode } => {
356 self.prepare_commit_prompt(ctx, prompt_mode)
357 }
358
359 Effect::CheckCommitDiff => Self::check_commit_diff(ctx),
360
361 Effect::MaterializeCommitInputs { attempt } => {
362 self.materialize_commit_inputs(ctx, attempt)
363 }
364
365 Effect::InvokeCommitAgent => self.invoke_commit_agent(ctx),
366
367 Effect::ExtractCommitXml => Ok(self.extract_commit_xml(ctx)),
368
369 Effect::ValidateCommitXml => Ok(self.validate_commit_xml(ctx)),
370
371 Effect::ApplyCommitMessageOutcome => self.apply_commit_message_outcome(ctx),
372
373 Effect::ArchiveCommitXml => Ok(self.archive_commit_xml(ctx)),
374
375 Effect::CreateCommit {
376 message,
377 files,
378 excluded_files,
379 } => Self::create_commit(ctx, message, &files, &excluded_files),
380
381 Effect::SkipCommit { reason } => Ok(Self::skip_commit(ctx, reason)),
382
383 Effect::CheckResidualFiles { pass } => Self::check_residual_files(ctx, pass),
384
385 Effect::CheckUncommittedChangesBeforeTermination => {
386 Self::check_uncommitted_changes_before_termination(ctx)
387 }
388
389 Effect::BackoffWait {
390 role,
391 cycle,
392 duration_ms,
393 } => {
394 use std::time::Duration;
395 ctx.registry
396 .retry_timer()
397 .sleep(Duration::from_millis(duration_ms));
398 Ok(EffectResult::event(
399 PipelineEvent::agent_retry_cycle_started(role, cycle),
400 ))
401 }
402
403 Effect::ReportAgentChainExhausted { role, phase, cycle } => {
404 use crate::reducer::event::ErrorEvent;
405 Err(ErrorEvent::AgentChainExhausted { role, phase, cycle }.into())
406 }
407
408 Effect::ValidateFinalState => Ok(self.validate_final_state(ctx)),
409
410 Effect::SaveCheckpoint { trigger } => Ok(self.save_checkpoint(ctx, trigger)),
411
412 Effect::EnsureGitignoreEntries => Ok(Self::ensure_gitignore_entries(ctx)),
413
414 Effect::CleanupContext => Self::cleanup_context(ctx),
415
416 Effect::LockPromptPermissions => Ok(Self::lock_prompt_permissions(ctx)),
417
418 Effect::RestorePromptPermissions => Ok(self.restore_prompt_permissions(ctx)),
419
420 Effect::WriteContinuationContext(ref data) => {
421 development::write_continuation_context_to_workspace(
422 ctx.workspace,
423 ctx.logger,
424 data,
425 )?;
426 Ok(EffectResult::event(
427 PipelineEvent::development_continuation_context_written(
428 data.iteration,
429 data.attempt,
430 ),
431 ))
432 }
433
434 Effect::CleanupContinuationContext => Self::cleanup_continuation_context(ctx),
435
436 Effect::WriteTimeoutContext {
437 role,
438 logfile_path,
439 context_path,
440 } => Self::write_timeout_context(ctx, role, &logfile_path, &context_path),
441
442 Effect::TriggerLoopRecovery {
443 detected_loop,
444 loop_count,
445 } => Ok(Self::trigger_loop_recovery(ctx, &detected_loop, loop_count)),
446
447 Effect::EmitRecoveryReset {
448 reset_type,
449 target_phase,
450 } => Ok(self.emit_recovery_reset(ctx, &reset_type, target_phase)),
451
452 Effect::AttemptRecovery {
453 level,
454 attempt_count,
455 } => Ok(self.attempt_recovery(ctx, level, attempt_count)),
456
457 Effect::EmitRecoverySuccess {
458 level,
459 total_attempts,
460 } => Ok(Self::emit_recovery_success(ctx, level, total_attempts)),
461
462 Effect::TriggerDevFixFlow {
463 failed_phase,
464 failed_role,
465 retry_cycle,
466 } => Ok(self.trigger_dev_fix_flow(ctx, failed_phase, failed_role, retry_cycle)),
467
468 Effect::EmitCompletionMarkerAndTerminate { is_failure, reason } => Ok(
469 Self::emit_completion_marker_and_terminate(ctx, is_failure, reason),
470 ),
471
472 Effect::ConfigureGitAuth { auth_method } => {
474 Ok(Self::handle_configure_git_auth(ctx, &auth_method))
475 }
476
477 Effect::PushToRemote {
478 remote,
479 branch,
480 force,
481 commit_sha,
482 } => Ok(Self::handle_push_to_remote(
483 ctx, remote, branch, force, commit_sha,
484 )),
485
486 Effect::CreatePullRequest {
487 base_branch,
488 head_branch,
489 title,
490 body,
491 } => Ok(Self::handle_create_pull_request(
492 ctx,
493 &base_branch,
494 &head_branch,
495 &title,
496 &body,
497 )),
498 }
499 }
500}