Skip to main content

ralph/commands/run/
mod.rs

1//! Run command entrypoints and module wiring.
2//!
3//! Responsibilities:
4//! - Define the public `commands::run` API used by CLI and UI clients.
5//! - Re-export stable types used across the crate (e.g., `PhaseType`).
6//! - Centralize shared operator-facing run-event types.
7//!
8//! Not handled here:
9//! - Actual run loop implementation (see `run_loop`).
10//! - Run-one orchestration (see `run_one`).
11//! - Phase execution internals (see `phases`).
12//! - Queue lock management (see `queue_lock`).
13//! - Dry-run UX (see `dry_run`).
14//!
15//! Invariants/assumptions:
16//! - Public entrypoint signatures must remain stable for CLI and interactive flows.
17//! - All orchestration logic lives in submodules; this file is a thin facade plus shared event helpers.
18
19mod 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
35// Re-export types that are used by other modules via crate::commands::run::* paths.
36// These are used by phase modules.
37pub(crate) use queue_lock::{QueueLockCondition, inspect_queue_lock, queue_lock_blocking_state};
38pub(crate) use supervision::{CiFailure, post_run_supervise};
39
40// Re-export PhaseType for use by runner module.
41pub(crate) use phases::PhaseType;
42
43pub use crate::agent::AgentOverrides;
44use crate::contracts::{BlockingReason, BlockingState};
45use crate::progress::ExecutionPhase;
46use std::sync::Arc;
47
48// Re-export parallel state types for UI clients.
49pub use parallel::state::{
50    ParallelStateFile, WorkerLifecycle, WorkerRecord, load_state, state_file_path,
51};
52
53// Re-export run loop types
54pub use run_loop::{RunLoopOptions, run_loop};
55
56// Re-export run-one entrypoints
57pub 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
62// Re-export dry-run functions
63pub use dry_run::{dry_run_loop, dry_run_one};
64
65// Re-export parallel operation commands
66pub(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;