2026-07-12 11:28:39 +07:00
|
|
|
//! User-configurable settings persisted as JSON in the store's base directory.
|
|
|
|
|
//!
|
|
|
|
|
//! `Settings::load` / `Settings::save` are the only entry points; every field
|
|
|
|
|
//! falls back to a hardcoded default via `Default for Settings` when the file
|
|
|
|
|
//! is missing or fails to parse.
|
|
|
|
|
|
2026-07-11 13:16:10 +07:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
2026-07-12 11:28:39 +07:00
|
|
|
/// Controls how much network access the agent is permitted during a session.
|
|
|
|
|
///
|
|
|
|
|
/// `Off` disables outbound requests entirely, `ReadOnly` allows fetches but
|
|
|
|
|
/// no mutating calls, `Full` permits everything. Defaults to `Off`.
|
2026-07-11 13:16:10 +07:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
#[derive(Default)]
|
|
|
|
|
pub enum InternetMode {
|
|
|
|
|
#[default]
|
|
|
|
|
Off,
|
|
|
|
|
ReadOnly,
|
|
|
|
|
Full,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-12 11:28:39 +07:00
|
|
|
/// Top-level application settings, serialized to `settings.json` in the store dir.
|
|
|
|
|
///
|
|
|
|
|
/// Why: a single flat struct rather than nested config so the JSON file stays
|
|
|
|
|
/// human-editable; unknown/missing fields on load fall back to `Default`.
|
2026-07-11 13:16:10 +07:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct Settings {
|
|
|
|
|
pub internet_mode: InternetMode,
|
|
|
|
|
pub provider: String,
|
|
|
|
|
pub model: String,
|
2026-07-12 03:14:52 +07:00
|
|
|
pub api_keys: std::collections::HashMap<String, String>,
|
2026-07-12 13:40:58 +07:00
|
|
|
pub max_tokens: Option<u32>,
|
|
|
|
|
pub temperature: Option<f32>,
|
2026-07-11 13:16:10 +07:00
|
|
|
pub review_enabled: bool,
|
|
|
|
|
pub review_max_lessons_per_run: usize,
|
|
|
|
|
pub adaptive_review_max_skip: u32,
|
2026-07-11 21:06:22 +07:00
|
|
|
pub verify_command: Option<String>,
|
|
|
|
|
pub verify_timeout_ms: u64,
|
2026-07-11 13:16:10 +07:00
|
|
|
pub workflow_max_concurrency: usize,
|
|
|
|
|
pub session_archive_enabled: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for Settings {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Settings {
|
|
|
|
|
internet_mode: InternetMode::Off,
|
2026-07-11 22:10:17 +07:00
|
|
|
provider: "zen".to_string(),
|
|
|
|
|
model: "deepseek-v4-flash-free".to_string(),
|
2026-07-12 03:14:52 +07:00
|
|
|
api_keys: std::collections::HashMap::new(),
|
2026-07-12 13:40:58 +07:00
|
|
|
max_tokens: None,
|
|
|
|
|
temperature: None,
|
2026-07-11 13:16:10 +07:00
|
|
|
review_enabled: true,
|
|
|
|
|
review_max_lessons_per_run: 5,
|
|
|
|
|
adaptive_review_max_skip: 3,
|
2026-07-11 21:06:22 +07:00
|
|
|
verify_command: None,
|
|
|
|
|
verify_timeout_ms: 30000,
|
2026-07-11 13:16:10 +07:00
|
|
|
workflow_max_concurrency: 5,
|
|
|
|
|
session_archive_enabled: true,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Settings {
|
2026-07-12 11:28:39 +07:00
|
|
|
/// Load settings from `<store_base_dir>/settings.json`.
|
|
|
|
|
///
|
|
|
|
|
/// Flow: read file → parse JSON → fall back to `Settings::default()` on
|
|
|
|
|
/// any failure (missing file, unreadable, malformed JSON).
|
|
|
|
|
///
|
|
|
|
|
/// Return: always succeeds; never surfaces I/O or parse errors to the caller.
|
2026-07-11 13:16:10 +07:00
|
|
|
pub fn load() -> Self {
|
|
|
|
|
let store = super::store::Store::new();
|
|
|
|
|
let path = store.base_dir.join("settings.json");
|
|
|
|
|
std::fs::read_to_string(path)
|
|
|
|
|
.ok()
|
|
|
|
|
.and_then(|s| serde_json::from_str(&s).ok())
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 11:49:33 +07:00
|
|
|
/// Serialize and write settings to `<store_base_dir>/settings.json`,
|
|
|
|
|
/// using write-then-rename with fsync for crash safety.
|
2026-07-12 11:28:39 +07:00
|
|
|
///
|
2026-07-12 11:49:33 +07:00
|
|
|
/// Flow: ensure base dir exists → pretty-print JSON → write to a temp
|
|
|
|
|
/// file → sync to disk → rename over the real path → sync the directory.
|
2026-07-12 11:28:39 +07:00
|
|
|
///
|
|
|
|
|
/// Return: `Err` if the directory can't be created or the write fails.
|
2026-07-11 13:16:10 +07:00
|
|
|
pub fn save(&self) -> std::io::Result<()> {
|
|
|
|
|
let store = super::store::Store::new();
|
|
|
|
|
std::fs::create_dir_all(&store.base_dir)?;
|
|
|
|
|
let path = store.base_dir.join("settings.json");
|
2026-07-12 11:49:33 +07:00
|
|
|
let tmp = store.base_dir.join("settings.json.tmp");
|
2026-07-11 13:16:10 +07:00
|
|
|
let s = serde_json::to_string_pretty(self)?;
|
2026-07-12 11:49:33 +07:00
|
|
|
std::fs::write(&tmp, s)?;
|
|
|
|
|
let f = std::fs::File::open(&tmp)?;
|
|
|
|
|
f.sync_all()?;
|
|
|
|
|
std::fs::rename(&tmp, path)?;
|
|
|
|
|
let _ = std::fs::File::open(&store.base_dir).and_then(|d| d.sync_all());
|
2026-07-11 13:16:10 +07:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|