1use crate::constants::defaults::DEFAULT_ID_WIDTH;
18use crate::constants::limits::{
19 DEFAULT_SIZE_WARNING_THRESHOLD_KB, DEFAULT_TASK_COUNT_WARNING_THRESHOLD,
20};
21use crate::constants::queue::{DEFAULT_DONE_FILE, DEFAULT_QUEUE_FILE};
22use crate::constants::timeouts::DEFAULT_SESSION_TIMEOUT_HOURS;
23use crate::contracts::model::{Model, ReasoningEffort};
24use crate::contracts::runner::{
25 ClaudePermissionMode, Runner, RunnerApprovalMode, RunnerCliConfigRoot, RunnerCliOptionsPatch,
26 RunnerOutputFormat, RunnerPlanMode, RunnerSandboxMode, RunnerVerbosity,
27 UnsupportedOptionPolicy,
28};
29use schemars::JsonSchema;
30use serde::{Deserialize, Serialize};
31use std::collections::BTreeMap;
32use std::path::PathBuf;
33
34mod agent;
35mod enums;
36mod loop_;
37mod notification;
38mod parallel;
39mod phase;
40mod plugin;
41mod queue;
42mod retry;
43#[cfg(test)]
44mod tests;
45mod webhook;
46
47pub use agent::{AgentConfig, CiGateConfig};
48pub use enums::{GitRevertMode, ProjectType, ScanPromptVersion};
49pub use loop_::LoopConfig;
50pub use notification::NotificationConfig;
51pub use parallel::{ParallelConfig, default_push_backoff_ms};
52pub use phase::{PhaseOverrideConfig, PhaseOverrides};
53pub use plugin::{PluginConfig, PluginsConfig};
54pub use queue::{QueueAgingThresholds, QueueConfig};
55pub use retry::RunnerRetryConfig;
56pub use webhook::{WebhookConfig, WebhookEventSubscription, WebhookQueuePolicy};
57
58#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
60#[serde(default, deny_unknown_fields)]
61pub struct Config {
62 pub version: u32,
64
65 pub project_type: Option<ProjectType>,
67
68 pub queue: QueueConfig,
70
71 pub agent: AgentConfig,
73
74 pub parallel: ParallelConfig,
76
77 #[serde(rename = "loop")]
79 pub loop_field: LoopConfig,
80
81 pub plugins: PluginsConfig,
83
84 #[serde(skip_serializing_if = "Option::is_none")]
89 pub profiles: Option<BTreeMap<String, AgentConfig>>,
90}
91
92impl Default for Config {
93 fn default() -> Self {
94 use std::collections::BTreeMap;
95
96 Self {
97 version: 1,
98 project_type: Some(ProjectType::Code),
99 queue: QueueConfig {
100 file: Some(PathBuf::from(DEFAULT_QUEUE_FILE)),
101 done_file: Some(PathBuf::from(DEFAULT_DONE_FILE)),
102 id_prefix: Some("RQ".to_string()),
103 id_width: Some(DEFAULT_ID_WIDTH as u8),
104 size_warning_threshold_kb: Some(DEFAULT_SIZE_WARNING_THRESHOLD_KB),
105 task_count_warning_threshold: Some(DEFAULT_TASK_COUNT_WARNING_THRESHOLD),
106 max_dependency_depth: Some(10),
107 auto_archive_terminal_after_days: None,
108 aging_thresholds: Some(QueueAgingThresholds {
109 warning_days: Some(7),
110 stale_days: Some(14),
111 rotten_days: Some(30),
112 }),
113 },
114 agent: AgentConfig {
115 runner: Some(Runner::Codex),
116 model: Some(Model::Gpt54),
117 reasoning_effort: Some(ReasoningEffort::Medium),
118 iterations: Some(1),
119 followup_reasoning_effort: None,
120 codex_bin: Some("codex".to_string()),
121 opencode_bin: Some("opencode".to_string()),
122 gemini_bin: Some("gemini".to_string()),
123 claude_bin: Some("claude".to_string()),
124 cursor_bin: Some("agent".to_string()),
125 kimi_bin: Some("kimi".to_string()),
126 pi_bin: Some("pi".to_string()),
127 phases: Some(3),
128 claude_permission_mode: Some(ClaudePermissionMode::BypassPermissions),
129 runner_cli: Some(RunnerCliConfigRoot {
130 defaults: RunnerCliOptionsPatch {
131 output_format: Some(RunnerOutputFormat::StreamJson),
132 verbosity: Some(RunnerVerbosity::Normal),
133 approval_mode: Some(RunnerApprovalMode::Yolo),
134 sandbox: Some(RunnerSandboxMode::Default),
135 plan_mode: Some(RunnerPlanMode::Default),
136 unsupported_option_policy: Some(UnsupportedOptionPolicy::Warn),
137 },
138 runners: BTreeMap::from([
139 (
140 Runner::Codex,
141 RunnerCliOptionsPatch {
142 sandbox: Some(RunnerSandboxMode::Disabled),
143 ..RunnerCliOptionsPatch::default()
144 },
145 ),
146 (
147 Runner::Claude,
148 RunnerCliOptionsPatch {
149 verbosity: Some(RunnerVerbosity::Verbose),
150 ..RunnerCliOptionsPatch::default()
151 },
152 ),
153 (
154 Runner::Kimi,
155 RunnerCliOptionsPatch {
156 approval_mode: Some(RunnerApprovalMode::Yolo),
157 ..RunnerCliOptionsPatch::default()
158 },
159 ),
160 (
161 Runner::Pi,
162 RunnerCliOptionsPatch {
163 approval_mode: Some(RunnerApprovalMode::Yolo),
164 ..RunnerCliOptionsPatch::default()
165 },
166 ),
167 ]),
168 }),
169 phase_overrides: None,
170 instruction_files: None,
171 repoprompt_plan_required: Some(false),
172 repoprompt_tool_injection: Some(false),
173 ci_gate: Some(CiGateConfig {
174 enabled: Some(true),
175 argv: Some(vec!["make".to_string(), "ci".to_string()]),
176 }),
177 git_revert_mode: Some(GitRevertMode::Ask),
178 git_commit_push_enabled: Some(true),
179 notification: NotificationConfig {
180 enabled: Some(true),
181 notify_on_complete: Some(true),
182 notify_on_fail: Some(true),
183 notify_on_loop_complete: Some(true),
184 suppress_when_active: Some(true),
185 sound_enabled: Some(false),
186 sound_path: None,
187 timeout_ms: Some(8000),
188 },
189 webhook: WebhookConfig::default(),
190 runner_retry: RunnerRetryConfig::default(),
191 session_timeout_hours: Some(DEFAULT_SESSION_TIMEOUT_HOURS),
192 scan_prompt_version: Some(ScanPromptVersion::V2),
193 },
194 parallel: ParallelConfig {
195 workers: None,
196 workspace_root: None,
197 max_push_attempts: Some(50),
198 push_backoff_ms: Some(default_push_backoff_ms()),
199 workspace_retention_hours: Some(24),
200 },
201 loop_field: LoopConfig {
202 wait_when_empty: Some(false),
203 empty_poll_ms: Some(30_000),
204 wait_when_blocked: Some(false),
205 wait_poll_ms: Some(1000),
206 wait_timeout_seconds: Some(0),
207 notify_when_unblocked: Some(false),
208 },
209 plugins: PluginsConfig::default(),
210 profiles: None,
211 }
212 }
213}