use crate::constants::defaults::DEFAULT_ID_WIDTH;
use crate::constants::limits::{
DEFAULT_SIZE_WARNING_THRESHOLD_KB, DEFAULT_TASK_COUNT_WARNING_THRESHOLD,
};
use crate::constants::queue::{DEFAULT_DONE_FILE, DEFAULT_QUEUE_FILE};
use crate::constants::timeouts::DEFAULT_SESSION_TIMEOUT_HOURS;
use crate::contracts::model::{Model, ReasoningEffort};
use crate::contracts::runner::{
ClaudePermissionMode, Runner, RunnerApprovalMode, RunnerCliConfigRoot, RunnerCliOptionsPatch,
RunnerOutputFormat, RunnerPlanMode, RunnerSandboxMode, RunnerVerbosity,
UnsupportedOptionPolicy,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::path::PathBuf;
mod agent;
mod enums;
mod loop_;
mod notification;
mod parallel;
mod phase;
mod plugin;
mod profiles;
mod queue;
mod retry;
#[cfg(test)]
mod tests;
mod webhook;
pub use agent::{AgentConfig, CiGateConfig};
pub use enums::{GitPublishMode, GitRevertMode, ProjectType, ScanPromptVersion};
pub use loop_::LoopConfig;
pub use notification::NotificationConfig;
pub use parallel::{ParallelConfig, default_push_backoff_ms};
pub use phase::{PhaseOverrideConfig, PhaseOverrides};
pub use plugin::{PluginConfig, PluginsConfig};
pub(crate) use profiles::{builtin_profile, builtin_profile_names, is_reserved_profile_name};
pub use queue::{QueueAgingThresholds, QueueConfig};
pub use retry::RunnerRetryConfig;
pub use webhook::{WebhookConfig, WebhookEventSubscription, WebhookQueuePolicy};
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default, deny_unknown_fields)]
pub struct Config {
pub version: u32,
pub project_type: Option<ProjectType>,
pub queue: QueueConfig,
pub agent: AgentConfig,
pub parallel: ParallelConfig,
#[serde(rename = "loop")]
pub loop_field: LoopConfig,
pub plugins: PluginsConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub profiles: Option<BTreeMap<String, AgentConfig>>,
}
impl Default for Config {
fn default() -> Self {
Self {
version: 2,
project_type: Some(ProjectType::Code),
queue: QueueConfig {
file: Some(PathBuf::from(DEFAULT_QUEUE_FILE)),
done_file: Some(PathBuf::from(DEFAULT_DONE_FILE)),
id_prefix: Some("RQ".to_string()),
id_width: Some(DEFAULT_ID_WIDTH as u8),
size_warning_threshold_kb: Some(DEFAULT_SIZE_WARNING_THRESHOLD_KB),
task_count_warning_threshold: Some(DEFAULT_TASK_COUNT_WARNING_THRESHOLD),
max_dependency_depth: Some(10),
auto_archive_terminal_after_days: None,
aging_thresholds: Some(QueueAgingThresholds {
warning_days: Some(7),
stale_days: Some(14),
rotten_days: Some(30),
}),
},
agent: AgentConfig {
runner: Some(Runner::Codex),
model: Some(Model::Gpt54),
reasoning_effort: Some(ReasoningEffort::Medium),
iterations: Some(1),
followup_reasoning_effort: None,
codex_bin: Some("codex".to_string()),
opencode_bin: Some("opencode".to_string()),
gemini_bin: Some("gemini".to_string()),
claude_bin: Some("claude".to_string()),
cursor_bin: Some("agent".to_string()),
kimi_bin: Some("kimi".to_string()),
pi_bin: Some("pi".to_string()),
phases: Some(3),
claude_permission_mode: Some(ClaudePermissionMode::AcceptEdits),
runner_cli: Some(RunnerCliConfigRoot {
defaults: RunnerCliOptionsPatch {
output_format: Some(RunnerOutputFormat::StreamJson),
verbosity: Some(RunnerVerbosity::Normal),
approval_mode: Some(RunnerApprovalMode::Default),
sandbox: Some(RunnerSandboxMode::Default),
plan_mode: Some(RunnerPlanMode::Default),
unsupported_option_policy: Some(UnsupportedOptionPolicy::Warn),
},
runners: BTreeMap::from([
(
Runner::Codex,
RunnerCliOptionsPatch {
sandbox: Some(RunnerSandboxMode::Disabled),
..RunnerCliOptionsPatch::default()
},
),
(
Runner::Claude,
RunnerCliOptionsPatch {
verbosity: Some(RunnerVerbosity::Verbose),
..RunnerCliOptionsPatch::default()
},
),
]),
}),
phase_overrides: None,
instruction_files: None,
repoprompt_plan_required: Some(false),
repoprompt_tool_injection: Some(false),
ci_gate: Some(CiGateConfig {
enabled: Some(true),
argv: Some(vec!["make".to_string(), "ci".to_string()]),
}),
git_revert_mode: Some(GitRevertMode::Ask),
git_publish_mode: Some(GitPublishMode::Off),
notification: NotificationConfig {
enabled: Some(true),
notify_on_complete: Some(true),
notify_on_fail: Some(true),
notify_on_loop_complete: Some(true),
suppress_when_active: Some(true),
sound_enabled: Some(false),
sound_path: None,
timeout_ms: Some(8000),
},
webhook: WebhookConfig::default(),
runner_retry: RunnerRetryConfig::default(),
session_timeout_hours: Some(DEFAULT_SESSION_TIMEOUT_HOURS),
scan_prompt_version: Some(ScanPromptVersion::V2),
},
parallel: ParallelConfig {
workers: None,
workspace_root: None,
max_push_attempts: Some(50),
push_backoff_ms: Some(default_push_backoff_ms()),
workspace_retention_hours: Some(24),
},
loop_field: LoopConfig {
wait_when_empty: Some(false),
empty_poll_ms: Some(30_000),
wait_when_blocked: Some(false),
wait_poll_ms: Some(1000),
wait_timeout_seconds: Some(0),
notify_when_unblocked: Some(false),
},
plugins: PluginsConfig::default(),
profiles: None,
}
}
}