diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock index ab174ad907..bbc7332b57 100644 --- a/desktop/src-tauri/Cargo.lock +++ b/desktop/src-tauri/Cargo.lock @@ -4328,6 +4328,7 @@ dependencies = [ "atomic-write-file", "base64 0.22.1", "chrono", + "dirs", "hex", "infer 0.16.0", "libc", diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index 8618eb075d..2deca5323d 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -25,6 +25,7 @@ windows-sys = { version = "0.59", features = ["Win32_Storage_FileSystem"] } [dependencies] atomic-write-file = "0.3" +dirs = "6" tauri = { version = "2", features = [] } tauri-plugin-opener = "2" tauri-plugin-window-state = "2" diff --git a/desktop/src-tauri/src/commands/agent_models.rs b/desktop/src-tauri/src/commands/agent_models.rs index 97bddecafc..4027650de9 100644 --- a/desktop/src-tauri/src/commands/agent_models.rs +++ b/desktop/src-tauri/src/commands/agent_models.rs @@ -5,8 +5,8 @@ use tauri::{AppHandle, State}; use crate::{ app_state::AppState, managed_agents::{ - build_managed_agent_summary, find_managed_agent_mut, load_managed_agents, - missing_command_message, resolve_command, save_managed_agents, + build_managed_agent_summary, default_agent_workdir, find_managed_agent_mut, + load_managed_agents, missing_command_message, resolve_command, save_managed_agents, sync_managed_agent_processes, AgentModelInfo, AgentModelsResponse, ManagedAgentSummary, UpdateManagedAgentRequest, DEFAULT_AGENT_ARG, }, @@ -63,8 +63,11 @@ pub async fn get_agent_models( // tokio's `process` feature. std::process::Command is synchronous // but fine for a short-lived subprocess (~2-5s). let output = tokio::task::spawn_blocking(move || { - std::process::Command::new(&resolved_acp) - .arg("models") + let mut cmd = std::process::Command::new(&resolved_acp); + if let Some(home) = default_agent_workdir() { + cmd.current_dir(home); + } + cmd.arg("models") .arg("--json") .env("SPROUT_ACP_AGENT_COMMAND", &agent_command) .env("SPROUT_ACP_AGENT_ARGS", agent_args.join(",")) diff --git a/desktop/src-tauri/src/managed_agents/backend.rs b/desktop/src-tauri/src/managed_agents/backend.rs index 2b60104569..e0e19e4698 100644 --- a/desktop/src-tauri/src/managed_agents/backend.rs +++ b/desktop/src-tauri/src/managed_agents/backend.rs @@ -26,7 +26,11 @@ pub fn invoke_provider( serde_json::to_string(request).map_err(|e| e.to_string())? ); - let mut child = std::process::Command::new(binary) + let mut cmd = std::process::Command::new(binary); + if let Some(home) = super::default_agent_workdir() { + cmd.current_dir(home); + } + let mut child = cmd .stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) diff --git a/desktop/src-tauri/src/managed_agents/mod.rs b/desktop/src-tauri/src/managed_agents/mod.rs index 544ccfc3f9..8cf92674ca 100644 --- a/desktop/src-tauri/src/managed_agents/mod.rs +++ b/desktop/src-tauri/src/managed_agents/mod.rs @@ -15,3 +15,17 @@ pub use runtime::*; pub use storage::*; pub use teams::*; pub use types::*; + +/// Returns the user's home directory if it can be resolved and exists. +/// Used as the default working directory for spawned agent processes. +/// +/// Cached for the process lifetime — home directory doesn't change at runtime. +/// Returns `None` in sandboxed/containerized environments where `$HOME` is +/// unset or points to a non-existent path; callers fall back to inheriting +/// the parent's CWD. +pub fn default_agent_workdir() -> Option { + use std::sync::OnceLock; + static HOME: OnceLock> = OnceLock::new(); + HOME.get_or_init(|| dirs::home_dir().filter(|p| p.is_dir())) + .clone() +} diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs index b90028b11a..0399515f0c 100644 --- a/desktop/src-tauri/src/managed_agents/runtime.rs +++ b/desktop/src-tauri/src/managed_agents/runtime.rs @@ -283,6 +283,9 @@ pub fn start_managed_agent_process( .ok_or_else(|| missing_command_message(&record.mcp_command, "MCP server command"))?; let mut command = std::process::Command::new(&resolved_acp_command); + if let Some(home) = super::default_agent_workdir() { + command.current_dir(home); + } command.stdin(std::process::Stdio::null()); command.stdout(std::process::Stdio::from(stdout)); command.stderr(std::process::Stdio::from(stderr)); diff --git a/desktop/src-tauri/src/managed_agents/types.rs b/desktop/src-tauri/src/managed_agents/types.rs index 94df1bd74d..b9db7c8558 100644 --- a/desktop/src-tauri/src/managed_agents/types.rs +++ b/desktop/src-tauri/src/managed_agents/types.rs @@ -310,7 +310,8 @@ pub const DEFAULT_ADMIN_COMMAND: &str = "sprout-admin"; pub const DEFAULT_AGENT_COMMAND: &str = "goose"; pub const DEFAULT_MCP_COMMAND: &str = "sprout-mcp-server"; pub const DEFAULT_AGENT_ARG: &str = "acp"; -pub const DEFAULT_AGENT_TURN_TIMEOUT_SECONDS: u64 = 300; +/// 10 min — agents with tool-heavy turns regularly exceed the previous 5 min default. +pub const DEFAULT_AGENT_TURN_TIMEOUT_SECONDS: u64 = 600; pub const DEFAULT_AGENT_PARALLELISM: u32 = 1; fn default_agent_parallelism() -> u32 {