ralph/commands/run/
mod.rs1mod context;
20mod dry_run;
21mod execution_history_cli;
22mod execution_timings;
23mod iteration;
24mod logging;
25pub mod parallel;
26mod parallel_ops;
27mod phases;
28mod queue_lock;
29mod run_loop;
30mod run_one;
31mod run_session;
32mod selection;
33mod supervision;
34
35pub(crate) use queue_lock::{QueueLockCondition, inspect_queue_lock, queue_lock_blocking_state};
38pub(crate) use supervision::{CiFailure, post_run_supervise};
39
40pub(crate) use phases::PhaseType;
42
43pub use crate::agent::AgentOverrides;
44use crate::contracts::{BlockingReason, BlockingState};
45use crate::progress::ExecutionPhase;
46use std::sync::Arc;
47
48pub use parallel::state::{
50 ParallelStateFile, WorkerLifecycle, WorkerRecord, load_state, state_file_path,
51};
52
53pub use run_loop::{RunLoopOptions, run_loop};
55
56pub use run_one::{
58 RunOneResumeOptions, RunOutcome, run_one, run_one_parallel_worker, run_one_with_handlers,
59 run_one_with_id, run_one_with_id_locked,
60};
61
62pub use dry_run::{dry_run_loop, dry_run_one};
64
65pub(crate) use parallel_ops::{build_parallel_status_document, parallel_retry, parallel_status};
67
68#[derive(Debug, Clone)]
69pub enum RunEvent {
70 ResumeDecision {
71 decision: crate::session::ResumeDecision,
72 },
73 TaskSelected {
74 task_id: String,
75 title: String,
76 },
77 PhaseEntered {
78 phase: ExecutionPhase,
79 },
80 PhaseCompleted {
81 phase: ExecutionPhase,
82 },
83 BlockedStateChanged {
84 state: BlockingState,
85 },
86 BlockedStateCleared,
87}
88
89pub type RunEventHandler = Arc<Box<dyn Fn(RunEvent) + Send + Sync>>;
90
91pub(crate) fn emit_resume_decision(
92 decision: &crate::session::ResumeDecision,
93 handler: Option<&RunEventHandler>,
94) {
95 if let Some(handler) = handler {
96 handler(RunEvent::ResumeDecision {
97 decision: decision.clone(),
98 });
99 return;
100 }
101
102 eprintln!("{}", decision.message);
103 if !decision.detail.trim().is_empty() {
104 eprintln!(" {}", decision.detail);
105 }
106}
107
108pub(crate) fn should_echo_blocked_state_without_handler(state: &BlockingState) -> bool {
109 !matches!(state.reason, BlockingReason::RunnerRecovery { .. })
110}
111
112pub(crate) fn emit_blocked_state_changed(state: &BlockingState, handler: Option<&RunEventHandler>) {
113 if let Some(handler) = handler {
114 handler(RunEvent::BlockedStateChanged {
115 state: state.clone(),
116 });
117 return;
118 }
119
120 if !should_echo_blocked_state_without_handler(state) {
121 return;
122 }
123
124 eprintln!("{}", state.message);
125 if !state.detail.trim().is_empty() {
126 eprintln!(" {}", state.detail);
127 }
128}
129
130pub(crate) fn emit_blocked_state_cleared(handler: Option<&RunEventHandler>) {
131 if let Some(handler) = handler {
132 handler(RunEvent::BlockedStateCleared);
133 }
134}
135
136#[cfg(test)]
137fn resolve_run_agent_settings(
138 resolved: &crate::config::Resolved,
139 task: &crate::contracts::Task,
140 overrides: &AgentOverrides,
141) -> anyhow::Result<crate::runner::AgentSettings> {
142 crate::runner::resolve_agent_settings(
143 overrides.runner.clone(),
144 overrides.model.clone(),
145 overrides.reasoning_effort,
146 &overrides.runner_cli,
147 task.agent.as_ref(),
148 &resolved.config.agent,
149 )
150}
151
152#[cfg(test)]
153mod tests;