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