Skip to main content

ralph_workflow/pipeline/
mod.rs

1//! Pipeline Execution Module
2//!
3//! This module contains the core pipeline execution infrastructure for running
4//! AI agents with real-time output streaming.
5//!
6//! # Key Types
7//!
8//! - [`AgentPhaseGuard`] - RAII guard for phase cleanup on success/failure
9//! - [`Timer`] - Execution duration tracking with phase support
10//! - [`PipelineRuntime`] - Runtime context for agent execution
11//!
12//! # Features
13//!
14//! - **Single-attempt execution** - One agent invocation per effect
15//! - **Real-time streaming** - Live output from agents during execution
16//! - **Log management** - Structured logging to `.agent/logs/`
17//!
18//! # Module Structure
19//!
20//! - `types` - Pipeline statistics tracking and RAII guards
21//! - [`logfile`] - Unified log file path creation, parsing, and discovery
22//! - [`idle_timeout`] - Timeout handling for stuck agents
23
24#![deny(unsafe_code)]
25
26mod clipboard;
27pub mod idle_timeout;
28pub mod logfile;
29mod prompt;
30mod types;
31
32pub use prompt::{run_with_prompt, PipelineRuntime, PromptCommand};
33pub use types::AgentPhaseGuard;
34
35// ===== Timer Utilities =====
36
37use std::time::{Duration, Instant};
38
39/// Timer for tracking execution duration
40#[derive(Clone)]
41pub struct Timer {
42    start_time: Instant,
43    phase_start: Instant,
44}
45
46impl Timer {
47    /// Create a new timer, starting now
48    pub fn new() -> Self {
49        let now = Instant::now();
50        Self {
51            start_time: now,
52            phase_start: now,
53        }
54    }
55
56    /// Start a new phase timer
57    pub fn start_phase(&mut self) {
58        self.phase_start = Instant::now();
59    }
60
61    /// Get elapsed time since timer start
62    pub fn elapsed(&self) -> Duration {
63        self.start_time.elapsed()
64    }
65
66    /// Get elapsed time since phase start
67    pub fn phase_elapsed(&self) -> Duration {
68        self.phase_start.elapsed()
69    }
70
71    /// Format a duration as "Xm YYs"
72    pub fn format_duration(duration: Duration) -> String {
73        let total_secs = duration.as_secs();
74        let mins = total_secs / 60;
75        let secs = total_secs % 60;
76        format!("{mins}m {secs:02}s")
77    }
78
79    /// Get formatted elapsed time since start
80    pub fn elapsed_formatted(&self) -> String {
81        Self::format_duration(self.elapsed())
82    }
83
84    /// Get formatted elapsed time since phase start
85    pub fn phase_elapsed_formatted(&self) -> String {
86        Self::format_duration(self.phase_elapsed())
87    }
88}
89
90impl Default for Timer {
91    fn default() -> Self {
92        Self::new()
93    }
94}
95
96#[cfg(test)]
97mod timer_tests {
98    use super::*;
99    use std::thread;
100
101    #[test]
102    fn test_format_duration_zero() {
103        let d = Duration::from_secs(0);
104        assert_eq!(Timer::format_duration(d), "0m 00s");
105    }
106
107    #[test]
108    fn test_format_duration_seconds() {
109        let d = Duration::from_secs(30);
110        assert_eq!(Timer::format_duration(d), "0m 30s");
111    }
112
113    #[test]
114    fn test_format_duration_minutes() {
115        let d = Duration::from_secs(65);
116        assert_eq!(Timer::format_duration(d), "1m 05s");
117    }
118
119    #[test]
120    fn test_format_duration_large() {
121        let d = Duration::from_secs(3661);
122        assert_eq!(Timer::format_duration(d), "61m 01s");
123    }
124
125    #[test]
126    fn test_timer_elapsed() {
127        let timer = Timer::new();
128        thread::sleep(Duration::from_millis(10));
129        assert!(timer.elapsed() >= Duration::from_millis(10));
130    }
131
132    #[test]
133    fn test_timer_phase() {
134        let mut timer = Timer::new();
135        thread::sleep(Duration::from_millis(10));
136        timer.start_phase();
137        thread::sleep(Duration::from_millis(10));
138        // Phase elapsed should be less than total elapsed
139        assert!(timer.phase_elapsed() < timer.elapsed());
140    }
141}