diff --git a/Cargo.lock b/Cargo.lock index 01164e81..73689e16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3811,7 +3811,7 @@ dependencies = [ [[package]] name = "openfang-api" -version = "0.4.4" +version = "0.4.5" dependencies = [ "async-trait", "axum", @@ -3851,7 +3851,7 @@ dependencies = [ [[package]] name = "openfang-channels" -version = "0.4.4" +version = "0.4.5" dependencies = [ "aes", "async-trait", @@ -3888,7 +3888,7 @@ dependencies = [ [[package]] name = "openfang-cli" -version = "0.4.4" +version = "0.4.5" dependencies = [ "clap", "clap_complete", @@ -3915,7 +3915,7 @@ dependencies = [ [[package]] name = "openfang-desktop" -version = "0.4.4" +version = "0.4.5" dependencies = [ "axum", "open", @@ -3941,7 +3941,7 @@ dependencies = [ [[package]] name = "openfang-extensions" -version = "0.4.4" +version = "0.4.5" dependencies = [ "aes-gcm", "argon2", @@ -3969,7 +3969,7 @@ dependencies = [ [[package]] name = "openfang-hands" -version = "0.4.4" +version = "0.4.5" dependencies = [ "chrono", "dashmap", @@ -3986,7 +3986,7 @@ dependencies = [ [[package]] name = "openfang-kernel" -version = "0.4.4" +version = "0.4.5" dependencies = [ "async-trait", "chrono", @@ -4024,7 +4024,7 @@ dependencies = [ [[package]] name = "openfang-memory" -version = "0.4.4" +version = "0.4.5" dependencies = [ "async-trait", "chrono", @@ -4043,7 +4043,7 @@ dependencies = [ [[package]] name = "openfang-migrate" -version = "0.4.4" +version = "0.4.5" dependencies = [ "chrono", "dirs 6.0.0", @@ -4062,7 +4062,7 @@ dependencies = [ [[package]] name = "openfang-runtime" -version = "0.4.4" +version = "0.4.5" dependencies = [ "anyhow", "async-trait", @@ -4096,7 +4096,7 @@ dependencies = [ [[package]] name = "openfang-skills" -version = "0.4.4" +version = "0.4.5" dependencies = [ "chrono", "hex", @@ -4119,7 +4119,7 @@ dependencies = [ [[package]] name = "openfang-types" -version = "0.4.4" +version = "0.4.5" dependencies = [ "async-trait", "chrono", @@ -4138,7 +4138,7 @@ dependencies = [ [[package]] name = "openfang-wire" -version = "0.4.4" +version = "0.4.5" dependencies = [ "async-trait", "chrono", @@ -8814,7 +8814,7 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xtask" -version = "0.4.4" +version = "0.4.5" [[package]] name = "yoke" diff --git a/crates/openfang-desktop/src/server.rs b/crates/openfang-desktop/src/server.rs index 984f2316..053c646d 100644 --- a/crates/openfang-desktop/src/server.rs +++ b/crates/openfang-desktop/src/server.rs @@ -64,6 +64,11 @@ impl Drop for ServerHandle { /// any Tauri window is created. The actual axum server runs on a dedicated /// thread with its own tokio runtime. pub fn start_server() -> Result> { + // Load .env and secrets.env into process environment (same as CLI). + // Without this, API keys stored in ~/.openfang/.env are invisible to + // the kernel's provider detection and credential resolver. + load_dotenv_files(); + // Boot kernel (sync — no tokio needed) let kernel = OpenFangKernel::boot(None)?; let kernel = Arc::new(kernel); @@ -145,3 +150,44 @@ async fn run_embedded_server( } } } + +/// Load ~/.openfang/.env and ~/.openfang/secrets.env into the process environment. +/// System env vars take priority — existing vars are NOT overridden. +fn load_dotenv_files() { + let home = if let Ok(h) = std::env::var("OPENFANG_HOME") { + std::path::PathBuf::from(h) + } else { + let user_home = std::env::var("HOME") + .or_else(|_| std::env::var("USERPROFILE")) + .unwrap_or_default(); + if user_home.is_empty() { + return; + } + std::path::PathBuf::from(user_home).join(".openfang") + }; + + for filename in &[".env", "secrets.env"] { + let path = home.join(filename); + if let Ok(content) = std::fs::read_to_string(&path) { + for line in content.lines() { + let trimmed = line.trim(); + if trimmed.is_empty() || trimmed.starts_with('#') { + continue; + } + if let Some((key, value)) = trimmed.split_once('=') { + let key = key.trim(); + let mut value = value.trim().to_string(); + if ((value.starts_with('"') && value.ends_with('"')) + || (value.starts_with('\'') && value.ends_with('\''))) + && value.len() >= 2 + { + value = value[1..value.len() - 1].to_string(); + } + if !key.is_empty() && std::env::var(key).is_err() { + std::env::set_var(key, &value); + } + } + } + } + } +} diff --git a/crates/openfang-kernel/src/kernel.rs b/crates/openfang-kernel/src/kernel.rs index d5682065..816132fb 100644 --- a/crates/openfang-kernel/src/kernel.rs +++ b/crates/openfang-kernel/src/kernel.rs @@ -1258,15 +1258,17 @@ impl OpenFangKernel { fixed_id: Option, ) -> KernelResult { let agent_id = fixed_id.unwrap_or_default(); - let session_id = SessionId::new(); let name = manifest.name.clone(); info!(agent = %name, id = %agent_id, parent = ?parent, "Spawning agent"); - // Create session - self.memory + // Create session — use the returned session_id so the registry + // and database are in sync (fixes duplicate session bug #651). + let session = self + .memory .create_session(agent_id) .map_err(KernelError::OpenFang)?; + let session_id = session.id; // Inherit kernel exec_policy as fallback if agent manifest doesn't have one let mut manifest = manifest;