ralph_workflow/reducer/
effect.rs1use crate::agents::AgentRole;
7use crate::phases::PhaseContext;
8use anyhow::Result;
9use serde::{Deserialize, Serialize};
10
11use super::event::{CheckpointTrigger, ConflictStrategy, PipelineEvent, RebasePhase};
12use super::ui_event::UIEvent;
13
14#[derive(Clone, Serialize, Deserialize, Debug)]
19pub enum Effect {
20 AgentInvocation {
21 role: AgentRole,
22 agent: String,
23 model: Option<String>,
24 prompt: String,
25 },
26
27 InitializeAgentChain {
28 role: AgentRole,
29 },
30
31 GeneratePlan {
32 iteration: u32,
33 },
34
35 RunDevelopmentIteration {
36 iteration: u32,
37 },
38
39 RunReviewPass {
40 pass: u32,
41 },
42
43 RunFixAttempt {
44 pass: u32,
45 },
46
47 RunRebase {
48 phase: RebasePhase,
49 target_branch: String,
50 },
51
52 ResolveRebaseConflicts {
53 strategy: ConflictStrategy,
54 },
55
56 GenerateCommitMessage,
57
58 CreateCommit {
59 message: String,
60 },
61
62 SkipCommit {
63 reason: String,
64 },
65
66 ValidateFinalState,
67
68 SaveCheckpoint {
69 trigger: CheckpointTrigger,
70 },
71
72 CleanupContext,
73
74 RestorePromptPermissions,
80}
81
82#[derive(Clone, Debug)]
88pub struct EffectResult {
89 pub event: PipelineEvent,
91 pub ui_events: Vec<UIEvent>,
93}
94
95impl EffectResult {
96 pub fn event(event: PipelineEvent) -> Self {
98 Self {
99 event,
100 ui_events: Vec::new(),
101 }
102 }
103
104 pub fn with_ui(event: PipelineEvent, ui_events: Vec<UIEvent>) -> Self {
106 Self { event, ui_events }
107 }
108
109 pub fn with_ui_event(mut self, ui_event: UIEvent) -> Self {
111 self.ui_events.push(ui_event);
112 self
113 }
114}
115
116pub trait EffectHandler<'ctx> {
121 fn execute(&mut self, effect: Effect, ctx: &mut PhaseContext<'_>) -> Result<EffectResult>;
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::reducer::event::PipelinePhase;
128
129 #[test]
130 fn test_effect_serialization() {
131 let effect = Effect::AgentInvocation {
132 role: AgentRole::Developer,
133 agent: "claude".to_string(),
134 model: None,
135 prompt: "test".to_string(),
136 };
137
138 let json = serde_json::to_string(&effect).unwrap();
139 let deserialized: Effect = serde_json::from_str(&json).unwrap();
140
141 match deserialized {
142 Effect::AgentInvocation {
143 role,
144 agent,
145 model,
146 prompt,
147 } => {
148 assert_eq!(role, AgentRole::Developer);
149 assert_eq!(agent, "claude");
150 assert!(model.is_none());
151 assert_eq!(prompt, "test");
152 }
153 _ => panic!("Expected AgentInvocation effect"),
154 }
155 }
156
157 #[test]
158 fn test_effect_result_event_only() {
159 let event = PipelineEvent::PipelineStarted;
160 let result = EffectResult::event(event.clone());
161
162 assert!(matches!(result.event, PipelineEvent::PipelineStarted));
163 assert!(result.ui_events.is_empty());
164 }
165
166 #[test]
167 fn test_effect_result_with_ui() {
168 let event = PipelineEvent::DevelopmentIterationCompleted {
169 iteration: 1,
170 output_valid: true,
171 };
172 let ui_events = vec![UIEvent::IterationProgress {
173 current: 1,
174 total: 3,
175 }];
176
177 let result = EffectResult::with_ui(event, ui_events);
178
179 assert!(matches!(
180 result.event,
181 PipelineEvent::DevelopmentIterationCompleted { .. }
182 ));
183 assert_eq!(result.ui_events.len(), 1);
184 assert!(matches!(
185 result.ui_events[0],
186 UIEvent::IterationProgress {
187 current: 1,
188 total: 3
189 }
190 ));
191 }
192
193 #[test]
194 fn test_effect_result_with_ui_event_builder() {
195 let event = PipelineEvent::PlanGenerationCompleted {
196 iteration: 1,
197 valid: true,
198 };
199
200 let result = EffectResult::event(event).with_ui_event(UIEvent::PhaseTransition {
201 from: Some(PipelinePhase::Planning),
202 to: PipelinePhase::Development,
203 });
204
205 assert_eq!(result.ui_events.len(), 1);
206 assert!(matches!(
207 result.ui_events[0],
208 UIEvent::PhaseTransition { .. }
209 ));
210 }
211
212 #[test]
213 fn test_effect_result_multiple_ui_events() {
214 let event = PipelineEvent::DevelopmentIterationCompleted {
215 iteration: 2,
216 output_valid: true,
217 };
218
219 let result = EffectResult::event(event)
220 .with_ui_event(UIEvent::IterationProgress {
221 current: 2,
222 total: 5,
223 })
224 .with_ui_event(UIEvent::AgentActivity {
225 agent: "claude".to_string(),
226 message: "Completed iteration".to_string(),
227 });
228
229 assert_eq!(result.ui_events.len(), 2);
230 }
231}