2
0

refactor(config): unify all config to ai.syui.log, remove ai.syui.gpt config dependency

This commit is contained in:
2026-03-24 16:30:35 +09:00
parent 6c1aff6d8d
commit b3e6a2f714
5 changed files with 66 additions and 69 deletions

View File

@@ -292,7 +292,7 @@ impl Drop for Agent {
/// Load user/bot identity from shared config.
fn load_user_context() -> String {
let path = crate::config::shared_config_path();
let path = crate::config::config_path();
let config: serde_json::Value = std::fs::read_to_string(&path).ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default();

View File

@@ -11,21 +11,48 @@ pub fn config_dir() -> String {
}
}
/// Path to shared config: $cfg/ai.syui.log/config.json
pub fn shared_config_path() -> String {
/// Shared config: $cfg/ai.syui.log/config.json
pub fn config_path() -> String {
format!("{}/ai.syui.log/config.json", config_dir())
}
/// Path to aigpt config (same content as shared, may be symlink)
pub fn gpt_config_path() -> String {
format!("{}/ai.syui.gpt/config.json", config_dir())
/// Sessions dir: $cfg/ai.syui.log/sessions
pub fn sessions_dir() -> String {
format!("{}/ai.syui.log/sessions", config_dir())
}
/// Path to sessions dir: $cfg/ai.syui.gpt/sessions
pub fn sessions_dir() -> String {
format!("{}/ai.syui.gpt/sessions", config_dir())
/// Load shared config as JSON Value.
pub fn load_config() -> serde_json::Value {
std::fs::read_to_string(config_path()).ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default()
}
/// Resolve bot data dir from config's bot.path field.
/// Falls back to $cfg/ai.syui.log/at
pub fn bot_data_dir() -> String {
let config = load_config();
let path = config["bot"]["path"].as_str().unwrap_or("");
let did = config["bot"]["did"].as_str().unwrap_or("did");
let expanded = expand_tilde(path);
if expanded.is_empty() {
format!("{}/ai.syui.log/at/{did}", config_dir())
} else {
format!("{expanded}/{did}")
}
}
pub fn expand_tilde(path: &str) -> String {
if let Some(rest) = path.strip_prefix('~') {
if let Ok(home) = std::env::var("HOME") {
return format!("{home}{rest}");
}
}
path.to_string()
}
// ── Agent configs ──────────────────────────────────────────
#[derive(Clone, Deserialize)]
pub struct AgentConfig {
pub name: String,
@@ -40,6 +67,11 @@ fn default_cwd() -> String {
.unwrap_or_else(|_| ".".to_string())
}
pub struct LoadedConfig {
pub agents: Vec<AgentConfig>,
pub interval: Option<u64>,
}
/// Built-in presets.
pub fn preset(name: &str) -> Option<Vec<AgentConfig>> {
let cwd = default_cwd();
@@ -70,10 +102,18 @@ pub fn preset(name: &str) -> Option<Vec<AgentConfig>> {
}
}
/// Parsed config with optional loop interval.
pub struct LoadedConfig {
pub agents: Vec<AgentConfig>,
pub interval: Option<u64>,
// ── File loading ───────────────────────────────────────────
pub fn load(path: &str) -> Vec<AgentConfig> {
load_full(path).agents
}
pub fn load_full(path: &str) -> LoadedConfig {
let path = Path::new(path);
if path.is_dir() {
return LoadedConfig { agents: load_dir(path), interval: None };
}
load_file_full(path)
}
#[derive(Deserialize)]
@@ -85,19 +125,6 @@ struct MultiConfig {
cwd: Option<String>,
}
pub fn load(path: &str) -> Vec<AgentConfig> {
load_full(path).agents
}
/// Load config with metadata (interval etc).
pub fn load_full(path: &str) -> LoadedConfig {
let path = Path::new(path);
if path.is_dir() {
return LoadedConfig { agents: load_dir(path), interval: None };
}
load_file_full(path)
}
fn load_file_full(path: &Path) -> LoadedConfig {
let content = match std::fs::read_to_string(path) {
Ok(c) => c,
@@ -118,35 +145,15 @@ fn load_file_full(path: &Path) -> LoadedConfig {
LoadedConfig { agents, interval: None }
}
/// Legacy: load agents only from file/directory.
fn load_file(path: &Path) -> Vec<AgentConfig> {
let content = match std::fs::read_to_string(path) {
Ok(c) => c,
Err(_) => return Vec::new(),
};
// Try as { "agents": [...] } or single { "name", "task", "cwd" }
if let Ok(multi) = serde_json::from_str::<MultiConfig>(&content) {
if let Some(agents) = multi.agents {
return agents;
}
if let (Some(name), Some(task), Some(cwd)) = (multi.name, multi.task, multi.cwd) {
return vec![AgentConfig { name, task, cwd }];
}
}
// Try as bare array [{ ... }, { ... }]
serde_json::from_str::<Vec<AgentConfig>>(&content).unwrap_or_default()
load_file_full(path).agents
}
fn load_dir(path: &Path) -> Vec<AgentConfig> {
let mut entries: Vec<_> = std::fs::read_dir(path)
.into_iter()
.flatten()
.flatten()
.into_iter().flatten().flatten()
.filter(|e| e.path().extension().is_some_and(|ext| ext == "json"))
.collect();
entries.sort_by_key(|e| e.file_name());
entries.iter().flat_map(|e| load_file(&e.path())).collect()
}

View File

@@ -499,7 +499,7 @@ fn wait_for_loop_signal(running: &Arc<AtomicBool>) -> LoopAction {
// ── Session persistence ────────────────────────────────────
fn save_session(cycle: usize, agents: &[Agent], decision: &str, indices: &[usize]) -> String {
let session_dir = session_base_dir();
let session_dir = config::sessions_dir();
let _ = std::fs::create_dir_all(&session_dir);
let timestamp = std::time::SystemTime::now()
@@ -575,7 +575,7 @@ fn save_to_aigpt_memory(decision: &str, agents: &[serde_json::Value]) {
}
fn aigpt_memory_dir() -> Option<String> {
let config_path = config::shared_config_path();
let config_path = config::config_path();
let content = std::fs::read_to_string(&config_path).ok()?;
let config: serde_json::Value = serde_json::from_str(&content).ok()?;
@@ -621,9 +621,6 @@ fn days_to_ymd(mut days: u64) -> (u64, u64, u64) {
(y, mo + 1, days + 1)
}
fn session_base_dir() -> String {
config::sessions_dir()
}
// ── AI integration ─────────────────────────────────────────
@@ -881,7 +878,7 @@ pub fn signal_quit() {
/// Update cmd_history.json: deduplicated command log across all agents.
fn update_cmd_history(agents: &[serde_json::Value], session_ts: u64) {
let history_path = format!("{}/cmd_history.json", session_base_dir());
let history_path = format!("{}/cmd_history.json", config::sessions_dir());
// Load existing
let mut entries: Vec<serde_json::Value> = std::fs::read_to_string(&history_path)
@@ -953,7 +950,7 @@ fn update_cmd_history(agents: &[serde_json::Value], session_ts: u64) {
}
pub fn history(detail: Option<&str>) {
let dir = session_base_dir();
let dir = config::sessions_dir();
let mut files: Vec<_> = std::fs::read_dir(&dir)
.into_iter().flatten().flatten()
.filter(|e| {
@@ -1037,7 +1034,7 @@ pub fn context() {
// Session info
let dec_path = format!("{STATE_DIR}/decision.json");
let session_dir = session_base_dir();
let session_dir = config::sessions_dir();
let session_count = std::fs::read_dir(&session_dir)
.into_iter().flatten().flatten()
.filter(|e| e.path().extension().is_some_and(|x| x == "json") && e.file_name().to_string_lossy() != "cmd_history.json")
@@ -1137,7 +1134,7 @@ pub fn context() {
}
// Command history (last 10)
let history_path = format!("{}/cmd_history.json", session_base_dir());
let history_path = format!("{}/cmd_history.json", config::sessions_dir());
if let Ok(content) = std::fs::read_to_string(&history_path) {
if let Ok(entries) = serde_json::from_str::<Vec<serde_json::Value>>(&content) {
if !entries.is_empty() {
@@ -1155,7 +1152,7 @@ pub fn context() {
/// Clean old sessions, keep last N.
pub fn history_clean() {
let dir = session_base_dir();
let dir = config::sessions_dir();
let mut files: Vec<_> = std::fs::read_dir(&dir)
.into_iter().flatten().flatten()
.filter(|e| {
@@ -1182,7 +1179,7 @@ pub fn history_cmd(filter: Option<&str>) {
}
fn show_cmd_history(filter: Option<&str>) {
let path = format!("{}/cmd_history.json", session_base_dir());
let path = format!("{}/cmd_history.json", config::sessions_dir());
match std::fs::read_to_string(&path) {
Ok(content) => {
if let Ok(entries) = serde_json::from_str::<Vec<serde_json::Value>>(&content) {
@@ -1279,13 +1276,6 @@ fn create_state_dir() {
}
}
pub fn expand_tilde_pub(path: &str) -> String { expand_tilde(path) }
fn expand_tilde(path: &str) -> String {
if let Some(rest) = path.strip_prefix('~') {
if let Ok(home) = std::env::var("HOME") {
return format!("{home}{rest}");
}
}
path.to_string()
config::expand_tilde(path)
}

View File

@@ -664,7 +664,7 @@ fn parse_agent_commands(text: &str) -> Vec<(String, String, String)> {
/// Load identity context from atproto config + recent chat.
fn load_identity_context() -> String {
let config_path = crate::config::shared_config_path();
let config_path = crate::config::config_path();
let config: serde_json::Value = match std::fs::read_to_string(&config_path) {
Ok(s) => serde_json::from_str(&s).unwrap_or_default(),

View File

@@ -9,7 +9,7 @@ const DEBOUNCE_MS: u64 = 2000;
/// Watch a directory for file changes.
/// On change, write next=true to loop.json with changed file list.
pub fn run(dir: &str, config: Option<&str>) -> Result<(), String> {
let dir = crate::headless::expand_tilde_pub(dir);
let dir = crate::config::expand_tilde(dir);
let dir_path = Path::new(&dir);
if !dir_path.is_dir() {
return Err(format!("not a directory: {dir}"));