Skip to main content

ralph/commands/run/run_one/
mod.rs

1//! Run-one orchestration entrypoints.
2//!
3//! Responsibilities:
4//! - Provide public entrypoints (`run_one*`) used by the CLI and interactive flows.
5//! - Own lock-acquisition policy for run-one execution.
6//! - Define resume-resolution options for direct and loop-driven run-one calls.
7//!
8//! Not handled here:
9//! - Run loop orchestration (see `run_loop`).
10//! - Queue selection helper primitives (see `selection`).
11//! - Phase execution details (see `phases`).
12//!
13//! Invariants/assumptions:
14//! - `run_one_with_id_locked` is called only when the queue lock is already held by the caller.
15//! - Parallel-worker mode resolves queue/done from worker workspace paths.
16
17use crate::agent::AgentOverrides;
18use crate::commands::run::RunEventHandler;
19use crate::config;
20use crate::runner;
21use crate::runutil;
22use anyhow::Result;
23
24mod completion;
25mod context;
26mod execution_setup;
27mod orchestration;
28mod phase_execution;
29mod selection;
30mod webhooks;
31
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub enum QueueLockMode {
34    Acquire,
35    Held,
36    /// Acquire the queue lock but allow creating upstream branches (used by parallel workers).
37    /// This combines the safety of lock acquisition with the push policy of Skip mode.
38    AcquireAllowUpstream,
39}
40
41#[derive(Clone, Debug, Default, Eq, PartialEq)]
42pub struct RunOneResumeOptions {
43    pub auto_resume: bool,
44    pub non_interactive: bool,
45    pub resume_task_id: Option<String>,
46    pub detect_session: bool,
47}
48
49impl RunOneResumeOptions {
50    pub fn detect(auto_resume: bool, non_interactive: bool) -> Self {
51        Self {
52            auto_resume,
53            non_interactive,
54            resume_task_id: None,
55            detect_session: true,
56        }
57    }
58
59    pub fn resolved(resume_task_id: Option<String>) -> Self {
60        Self {
61            auto_resume: false,
62            non_interactive: false,
63            resume_task_id,
64            detect_session: false,
65        }
66    }
67
68    pub fn disabled() -> Self {
69        Self::default()
70    }
71}
72
73/// Outcome of a single task run.
74#[derive(Debug)]
75pub enum RunOutcome {
76    /// No Todo (and no Draft if include_draft is false).
77    NoCandidates,
78    /// Candidates exist, but none are currently runnable (deps/schedule/status flags).
79    Blocked {
80        summary: Box<crate::queue::operations::QueueRunnabilitySummary>,
81        state: Box<crate::contracts::BlockingState>,
82    },
83    Ran {
84        task_id: String,
85    },
86}
87
88/// Run a specific task by ID.
89#[allow(clippy::too_many_arguments)]
90pub fn run_one_with_id(
91    resolved: &config::Resolved,
92    agent_overrides: &AgentOverrides,
93    force: bool,
94    task_id: &str,
95    resume_options: RunOneResumeOptions,
96    output_handler: Option<runner::OutputHandler>,
97    run_event_handler: Option<RunEventHandler>,
98    revert_prompt: Option<runutil::RevertPromptHandler>,
99) -> Result<()> {
100    orchestration::run_one_impl(
101        resolved,
102        agent_overrides,
103        force,
104        QueueLockMode::Acquire,
105        Some(task_id),
106        resume_options,
107        output_handler,
108        run_event_handler,
109        revert_prompt,
110        None,
111    )
112    .map(|_| ())
113}
114
115/// Run a specific task as a parallel worker (acquires queue lock, allows upstream creation).
116pub fn run_one_parallel_worker(
117    resolved: &config::Resolved,
118    agent_overrides: &AgentOverrides,
119    force: bool,
120    task_id: &str,
121    target_branch: &str,
122) -> Result<()> {
123    orchestration::run_one_impl(
124        resolved,
125        agent_overrides,
126        force,
127        QueueLockMode::AcquireAllowUpstream,
128        Some(task_id),
129        RunOneResumeOptions::disabled(),
130        None,
131        None,
132        None,
133        Some(target_branch),
134    )
135    .map(|_| ())
136}
137
138/// Run a specific task when the queue lock is already held by the caller.
139#[allow(clippy::too_many_arguments)]
140pub fn run_one_with_id_locked(
141    resolved: &config::Resolved,
142    agent_overrides: &AgentOverrides,
143    force: bool,
144    task_id: &str,
145    resume_options: RunOneResumeOptions,
146    output_handler: Option<runner::OutputHandler>,
147    run_event_handler: Option<RunEventHandler>,
148    revert_prompt: Option<runutil::RevertPromptHandler>,
149) -> Result<()> {
150    orchestration::run_one_impl(
151        resolved,
152        agent_overrides,
153        force,
154        QueueLockMode::Held,
155        Some(task_id),
156        resume_options,
157        output_handler,
158        run_event_handler,
159        revert_prompt,
160        None,
161    )
162    .map(|_| ())
163}
164
165/// Run the first available todo task.
166pub fn run_one(
167    resolved: &config::Resolved,
168    agent_overrides: &AgentOverrides,
169    force: bool,
170    resume_options: RunOneResumeOptions,
171) -> Result<RunOutcome> {
172    orchestration::run_one_impl(
173        resolved,
174        agent_overrides,
175        force,
176        QueueLockMode::Acquire,
177        None,
178        resume_options,
179        None,
180        None,
181        None,
182        None,
183    )
184}
185
186/// Run the first available task with streaming handlers.
187pub fn run_one_with_handlers(
188    resolved: &config::Resolved,
189    agent_overrides: &AgentOverrides,
190    force: bool,
191    resume_options: RunOneResumeOptions,
192    output_handler: Option<runner::OutputHandler>,
193    run_event_handler: Option<RunEventHandler>,
194) -> Result<RunOutcome> {
195    orchestration::run_one_impl(
196        resolved,
197        agent_overrides,
198        force,
199        QueueLockMode::Acquire,
200        None,
201        resume_options,
202        output_handler,
203        run_event_handler,
204        None,
205        None,
206    )
207}