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::{
33    extract_error_identifier_from_logfile, extract_error_message_from_logfile, run_with_prompt,
34    PipelineRuntime, PromptCommand,
35};
36pub use types::AgentPhaseGuard;
37
38// ===== Timer Utilities =====
39
40use std::time::{Duration, Instant};
41
42/// Timer for tracking execution duration
43#[derive(Clone)]
44pub struct Timer {
45    start_time: Instant,
46    phase_start: Instant,
47}
48
49impl Timer {
50    /// Create a new timer, starting now
51    #[must_use]
52    pub fn new() -> Self {
53        let now = Instant::now();
54        Self {
55            start_time: now,
56            phase_start: now,
57        }
58    }
59
60    /// Start a new phase timer
61    pub fn start_phase(&mut self) {
62        self.phase_start = Instant::now();
63    }
64
65    /// Get elapsed time since timer start
66    #[must_use]
67    pub fn elapsed(&self) -> Duration {
68        self.start_time.elapsed()
69    }
70
71    /// Get elapsed time since phase start
72    #[must_use]
73    pub fn phase_elapsed(&self) -> Duration {
74        self.phase_start.elapsed()
75    }
76
77    /// Format a duration as "Xm YYs"
78    #[must_use]
79    pub fn format_duration(duration: Duration) -> String {
80        let total_secs = duration.as_secs();
81        let mins = total_secs / 60;
82        let secs = total_secs % 60;
83        format!("{mins}m {secs:02}s")
84    }
85
86    /// Get formatted elapsed time since start
87    #[must_use]
88    pub fn elapsed_formatted(&self) -> String {
89        Self::format_duration(self.elapsed())
90    }
91
92    /// Get formatted elapsed time since phase start
93    #[must_use]
94    pub fn phase_elapsed_formatted(&self) -> String {
95        Self::format_duration(self.phase_elapsed())
96    }
97}
98
99impl Default for Timer {
100    fn default() -> Self {
101        Self::new()
102    }
103}
104
105#[cfg(test)]
106mod timer_tests {
107    use super::*;
108
109    #[test]
110    fn test_format_duration_zero() {
111        let d = Duration::from_secs(0);
112        assert_eq!(Timer::format_duration(d), "0m 00s");
113    }
114
115    #[test]
116    fn test_format_duration_seconds() {
117        let d = Duration::from_secs(30);
118        assert_eq!(Timer::format_duration(d), "0m 30s");
119    }
120
121    #[test]
122    fn test_format_duration_minutes() {
123        let d = Duration::from_secs(65);
124        assert_eq!(Timer::format_duration(d), "1m 05s");
125    }
126
127    #[test]
128    fn test_format_duration_large() {
129        let d = Duration::from_secs(3661);
130        assert_eq!(Timer::format_duration(d), "61m 01s");
131    }
132}