diff --git a/Cargo.lock b/Cargo.lock index cf3088070085..3e417bacd754 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2705,6 +2705,7 @@ dependencies = [ "jsonschema", "nix 0.30.1", "once_cell", + "open", "rand 0.8.5", "regex", "rmcp", @@ -3531,6 +3532,15 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + [[package]] name = "is-terminal" version = "0.4.16" @@ -3542,6 +3552,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -4418,6 +4438,17 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +[[package]] +name = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + [[package]] name = "openssl" version = "0.10.73" diff --git a/Justfile b/Justfile index 14044e89021e..e83f4a2700cc 100644 --- a/Justfile +++ b/Justfile @@ -143,6 +143,15 @@ run-ui: @echo "Running UI..." cd ui/desktop && npm install && npm run start-gui +run-ui-playwright: + #!/usr/bin/env sh + just release-binary + echo "Running UI with Playwright debugging..." + RUN_DIR="$HOME/goose-runs/$(date +%Y%m%d-%H%M%S)" + mkdir -p "$RUN_DIR" + echo "Using isolated directory: $RUN_DIR" + cd ui/desktop && ENABLE_PLAYWRIGHT=true GOOSE_PATH_ROOT="$RUN_DIR" npm run start-gui + run-ui-only: @echo "Running UI..." cd ui/desktop && npm install && npm run start-gui @@ -463,4 +472,4 @@ build-test-tools: record-mcp-tests: build-test-tools GOOSE_RECORD_MCP=1 cargo test --package goose --test mcp_integration_test - git add crates/goose/tests/mcp_replays/ \ No newline at end of file + git add crates/goose/tests/mcp_replays/ diff --git a/crates/goose-cli/src/commands/info.rs b/crates/goose-cli/src/commands/info.rs index e6b22985e509..baf972d76826 100644 --- a/crates/goose-cli/src/commands/info.rs +++ b/crates/goose-cli/src/commands/info.rs @@ -1,6 +1,6 @@ use anyhow::Result; use console::style; -use etcetera::{choose_app_strategy, AppStrategy}; +use goose::config::paths::Paths; use goose::config::Config; use serde_yaml; @@ -9,11 +9,8 @@ fn print_aligned(label: &str, value: &str, width: usize) { } pub fn handle_info(verbose: bool) -> Result<()> { - let data_dir = choose_app_strategy(crate::APP_STRATEGY.clone())?; - let logs_dir = data_dir - .in_state_dir("logs") - .unwrap_or_else(|| data_dir.in_data_dir("logs")); - let sessions_dir = data_dir.in_data_dir("sessions"); + let logs_dir = Paths::in_state_dir("logs"); + let sessions_dir = Paths::in_data_dir("sessions"); // Get paths using a stored reference to the global config let config = Config::global(); diff --git a/crates/goose-cli/src/lib.rs b/crates/goose-cli/src/lib.rs index b2e882fd9cb4..3996e0796d13 100644 --- a/crates/goose-cli/src/lib.rs +++ b/crates/goose-cli/src/lib.rs @@ -1,5 +1,3 @@ -use etcetera::AppStrategyArgs; -use once_cell::sync::Lazy; pub mod cli; pub mod commands; pub mod logging; @@ -11,9 +9,3 @@ pub mod signal; // Re-export commonly used types pub use session::CliSession; - -pub static APP_STRATEGY: Lazy = Lazy::new(|| AppStrategyArgs { - top_level_domain: "Block".to_string(), - author: "Block".to_string(), - app_name: "goose".to_string(), -}); diff --git a/crates/goose-cli/src/project_tracker.rs b/crates/goose-cli/src/project_tracker.rs index 1b11bbf33631..0cafe7291a5d 100644 --- a/crates/goose-cli/src/project_tracker.rs +++ b/crates/goose-cli/src/project_tracker.rs @@ -1,6 +1,6 @@ use anyhow::{Context, Result}; use chrono::{DateTime, Utc}; -use etcetera::{choose_app_strategy, AppStrategy}; +use goose::config::paths::Paths; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; @@ -41,11 +41,7 @@ pub struct ProjectInfoDisplay { impl ProjectTracker { /// Get the path to the projects.json file fn get_projects_file() -> Result { - let projects_file = choose_app_strategy(crate::APP_STRATEGY.clone()) - .context("goose requires a home dir")? - .in_data_dir("projects.json"); - - // Ensure data directory exists + let projects_file = Paths::in_data_dir("projects.json"); if let Some(parent) = projects_file.parent() { if !parent.exists() { fs::create_dir_all(parent)?; diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index cfb2083602fc..cb80d7381fd5 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -25,7 +25,6 @@ use goose::utils::safe_truncate; use anyhow::{Context, Result}; use completion::GooseCompleter; -use etcetera::{choose_app_strategy, AppStrategy}; use goose::agents::extension::{Envs, ExtensionConfig}; use goose::agents::types::RetryConfig; use goose::agents::{Agent, SessionConfig}; @@ -37,6 +36,7 @@ use rmcp::model::PromptMessage; use rmcp::model::ServerNotification; use rmcp::model::{ErrorCode, ErrorData}; +use goose::config::paths::Paths; use goose::conversation::message::{Message, MessageContent}; use rand::{distributions::Alphanumeric, Rng}; use rustyline::EditMode; @@ -413,29 +413,20 @@ impl CliSession { let completer = GooseCompleter::new(self.completion_cache.clone()); editor.set_helper(Some(completer)); - // Create and use a global history file in ~/.config/goose directory - // This allows command history to persist across different chat sessions - // instead of being tied to each individual session's messages - let strategy = - choose_app_strategy(crate::APP_STRATEGY.clone()).expect("goose requires a home dir"); - let config_dir = strategy.config_dir(); - let history_file = config_dir.join("history.txt"); + let history_file = Paths::config_dir().join("history.txt"); - // Ensure config directory exists if let Some(parent) = history_file.parent() { if !parent.exists() { std::fs::create_dir_all(parent)?; } } - // Load history from the global file if history_file.exists() { if let Err(err) = editor.load_history(&history_file) { eprintln!("Warning: Failed to load command history: {}", err); } } - // Helper function to save history after commands let save_history = |editor: &mut rustyline::Editor| { if let Err(err) = editor.save_history(&history_file) { diff --git a/crates/goose-mcp/src/developer/shell.rs b/crates/goose-mcp/src/developer/shell.rs index b007fd8bb722..8f0912cf31fa 100644 --- a/crates/goose-mcp/src/developer/shell.rs +++ b/crates/goose-mcp/src/developer/shell.rs @@ -1,6 +1,6 @@ -use goose::config::get_config_dir; use std::{env, ffi::OsString, process::Stdio}; +use goose::config::paths::Paths; #[cfg(unix)] #[allow(unused_imports)] // False positive: trait is used for process_group method use std::os::unix::process::CommandExt; @@ -30,7 +30,7 @@ impl Default for ShellConfig { // Configure environment based on shell type let envs = if shell_name == "bash" { - let bash_env = get_config_dir().join(".bash_env").into_os_string(); + let bash_env = Paths::config_dir().join(".bash_env").into_os_string(); vec![(OsString::from("BASH_ENV"), bash_env)] } else { vec![] diff --git a/crates/goose-server/src/routes/config_management.rs b/crates/goose-server/src/routes/config_management.rs index cd429b378f9e..0bf758bd4111 100644 --- a/crates/goose-server/src/routes/config_management.rs +++ b/crates/goose-server/src/routes/config_management.rs @@ -5,9 +5,8 @@ use axum::{ routing::{delete, get, post}, Json, Router, }; -use etcetera::{choose_app_strategy, AppStrategy}; +use goose::config::paths::Paths; use goose::config::ExtensionEntry; -use goose::config::APP_STRATEGY; use goose::config::{Config, ConfigError}; use goose::model::ModelConfig; use goose::providers::base::ProviderMetadata; @@ -565,11 +564,7 @@ pub async fn upsert_permissions( ) )] pub async fn backup_config() -> Result, StatusCode> { - let config_dir = choose_app_strategy(APP_STRATEGY.clone()) - .expect("goose requires a home dir") - .config_dir(); - - let config_path = config_dir.join("config.yaml"); + let config_path = Paths::config_dir().join("config.yaml"); if config_path.exists() { let file_name = config_path @@ -630,11 +625,7 @@ pub async fn recover_config() -> Result, StatusCode> { ) )] pub async fn validate_config() -> Result, StatusCode> { - let config_dir = choose_app_strategy(APP_STRATEGY.clone()) - .expect("goose requires a home dir") - .config_dir(); - - let config_path = config_dir.join("config.yaml"); + let config_path = Paths::config_dir().join("config.yaml"); if !config_path.exists() { return Ok(Json("Config file does not exist".to_string())); diff --git a/crates/goose/src/config/base.rs b/crates/goose/src/config/base.rs index ac25ba5c5d5a..3da5fbf95bcd 100644 --- a/crates/goose/src/config/base.rs +++ b/crates/goose/src/config/base.rs @@ -1,7 +1,7 @@ -use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs}; +use crate::config::paths::Paths; use fs2::FileExt; use keyring::Entry; -use once_cell::sync::{Lazy, OnceCell}; +use once_cell::sync::OnceCell; use serde::Deserialize; use serde_json::Value; use std::collections::HashMap; @@ -11,12 +11,6 @@ use std::io::Write; use std::path::{Path, PathBuf}; use thiserror::Error; -pub static APP_STRATEGY: Lazy = Lazy::new(|| AppStrategyArgs { - top_level_domain: "Block".to_string(), - author: "Block".to_string(), - app_name: "goose".to_string(), -}); - const KEYRING_SERVICE: &str = "goose"; const KEYRING_USERNAME: &str = "secrets"; @@ -116,18 +110,9 @@ enum SecretStorage { // Global instance static GLOBAL_CONFIG: OnceCell = OnceCell::new(); -pub fn get_config_dir() -> PathBuf { - choose_app_strategy(APP_STRATEGY.clone()) - .expect("goose requires a home dir") - .config_dir() -} - impl Default for Config { fn default() -> Self { - // choose_app_strategy().config_dir() - // - macOS/Linux: ~/.config/goose/ - // - Windows: ~\AppData\Roaming\Block\goose\config\ - let config_dir = get_config_dir(); + let config_dir = Paths::config_dir(); std::fs::create_dir_all(&config_dir).expect("Failed to create config directory"); diff --git a/crates/goose/src/config/custom_providers.rs b/crates/goose/src/config/custom_providers.rs index 3110771a185e..e486f39f4d24 100644 --- a/crates/goose/src/config/custom_providers.rs +++ b/crates/goose/src/config/custom_providers.rs @@ -1,20 +1,17 @@ -use crate::config::{Config, APP_STRATEGY}; +use crate::config::paths::Paths; +use crate::config::Config; use crate::model::ModelConfig; use crate::providers::anthropic::AnthropicProvider; use crate::providers::base::ModelInfo; use crate::providers::ollama::OllamaProvider; use crate::providers::openai::OpenAiProvider; use anyhow::Result; -use etcetera::{choose_app_strategy, AppStrategy}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::Path; pub fn custom_providers_dir() -> std::path::PathBuf { - choose_app_strategy(APP_STRATEGY.clone()) - .expect("goose requires a home dir") - .config_dir() - .join("custom_providers") + Paths::config_dir().join("custom_providers") } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/crates/goose/src/config/mod.rs b/crates/goose/src/config/mod.rs index c80204205888..bdb4d7678000 100644 --- a/crates/goose/src/config/mod.rs +++ b/crates/goose/src/config/mod.rs @@ -2,12 +2,13 @@ pub mod base; pub mod custom_providers; mod experiments; pub mod extensions; +pub mod paths; pub mod permission; pub mod signup_openrouter; pub mod signup_tetrate; pub use crate::agents::ExtensionConfig; -pub use base::{get_config_dir, Config, ConfigError, APP_STRATEGY}; +pub use base::{Config, ConfigError}; pub use custom_providers::CustomProviderConfig; pub use experiments::ExperimentManager; pub use extensions::{ diff --git a/crates/goose/src/config/paths.rs b/crates/goose/src/config/paths.rs new file mode 100644 index 000000000000..839f8f1b6598 --- /dev/null +++ b/crates/goose/src/config/paths.rs @@ -0,0 +1,60 @@ +use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs}; +use std::path::PathBuf; + +pub struct Paths; + +impl Paths { + fn get_dir(dir_type: DirType) -> PathBuf { + if let Ok(test_root) = std::env::var("GOOSE_PATH_ROOT") { + let base = PathBuf::from(test_root); + match dir_type { + DirType::Config => base.join("config"), + DirType::Data => base.join("data"), + DirType::State => base.join("state"), + } + } else { + let strategy = choose_app_strategy(AppStrategyArgs { + top_level_domain: "Block".to_string(), + author: "Block".to_string(), + app_name: "goose".to_string(), + }) + .expect("goose requires a home dir"); + + match dir_type { + DirType::Config => strategy.config_dir(), + DirType::Data => strategy.data_dir(), + DirType::State => strategy.state_dir().unwrap_or(strategy.data_dir()), + } + } + } + + pub fn config_dir() -> PathBuf { + Self::get_dir(DirType::Config) + } + + pub fn data_dir() -> PathBuf { + Self::get_dir(DirType::Data) + } + + pub fn state_dir() -> PathBuf { + Self::get_dir(DirType::State) + } + + pub fn in_state_dir(subpath: &str) -> PathBuf { + Self::state_dir().join(subpath) + } + + pub fn in_config_dir(subpath: &str) -> PathBuf { + Self::config_dir().join(subpath) + } + + pub fn in_data_dir(subpath: &str) -> PathBuf { + Self::data_dir().join(subpath) + } +} + +enum DirType { + Config, + Data, + State, +} diff --git a/crates/goose/src/config/permission.rs b/crates/goose/src/config/permission.rs index 417cd56d1297..5c184a3e9765 100644 --- a/crates/goose/src/config/permission.rs +++ b/crates/goose/src/config/permission.rs @@ -1,5 +1,4 @@ -use super::APP_STRATEGY; -use etcetera::{choose_app_strategy, AppStrategy}; +use crate::config::paths::Paths; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; @@ -37,14 +36,7 @@ const SMART_APPROVE_PERMISSION: &str = "smart_approve"; /// Implements the default constructor for `PermissionManager`. impl Default for PermissionManager { fn default() -> Self { - // Choose the app strategy and determine the config directory - let config_dir = choose_app_strategy(APP_STRATEGY.clone()) - .expect("goose requires a home dir") - .config_dir(); - - // Ensure the configuration directory exists - std::fs::create_dir_all(&config_dir).expect("Failed to create config directory"); - let config_path = config_dir.join("permission.yaml"); + let config_path = Paths::config_dir().join("permission.yaml"); // Load the existing configuration file or create an empty map if the file doesn't exist let permission_map = if config_path.exists() { diff --git a/crates/goose/src/execution/manager.rs b/crates/goose/src/execution/manager.rs index 0596f8c6cdc3..76ece5c90710 100644 --- a/crates/goose/src/execution/manager.rs +++ b/crates/goose/src/execution/manager.rs @@ -1,12 +1,11 @@ use crate::agents::extension::PlatformExtensionContext; use crate::agents::Agent; -use crate::config::APP_STRATEGY; +use crate::config::paths::Paths; use crate::model::ModelConfig; use crate::providers::create; use crate::scheduler_factory::SchedulerFactory; use crate::scheduler_trait::SchedulerTrait; use anyhow::Result; -use etcetera::{choose_app_strategy, AppStrategy}; use lru::LruCache; use std::num::NonZeroUsize; use std::sync::Arc; @@ -37,10 +36,7 @@ impl AgentManager { // Private constructor - prevents direct instantiation in production async fn new(max_sessions: Option) -> Result { - // Construct scheduler with the standard goose-server path - let schedule_file_path = choose_app_strategy(APP_STRATEGY.clone())? - .data_dir() - .join("schedule.json"); + let schedule_file_path = Paths::data_dir().join("schedule.json"); let scheduler = SchedulerFactory::create(schedule_file_path).await?; diff --git a/crates/goose/src/logging.rs b/crates/goose/src/logging.rs index 90aa977e08c8..a6742d3d1e8f 100644 --- a/crates/goose/src/logging.rs +++ b/crates/goose/src/logging.rs @@ -1,10 +1,8 @@ +use crate::config::paths::Paths; use anyhow::{Context, Result}; -use etcetera::{choose_app_strategy, AppStrategy}; use std::fs; use std::path::PathBuf; -use crate::config::APP_STRATEGY; - /// Returns the directory where log files should be stored for a specific component. /// Creates the directory structure if it doesn't exist. /// @@ -12,17 +10,8 @@ use crate::config::APP_STRATEGY; /// /// * `component` - The component name (e.g., "cli", "server", "debug") /// * `use_date_subdir` - Whether to create a date-based subdirectory -/// -/// # Returns -/// -/// The path to the log directory for the specified component pub fn get_log_directory(component: &str, use_date_subdir: bool) -> Result { - let home_dir = - choose_app_strategy(APP_STRATEGY.clone()).context("HOME environment variable not set")?; - - let base_log_dir = home_dir - .in_state_dir("logs") - .unwrap_or_else(|| home_dir.in_data_dir("logs")); + let base_log_dir = Paths::in_state_dir("logs"); let component_dir = base_log_dir.join(component); diff --git a/crates/goose/src/permission/permission_store.rs b/crates/goose/src/permission/permission_store.rs index d8d7cf3e6425..4dee06ff8a0c 100644 --- a/crates/goose/src/permission/permission_store.rs +++ b/crates/goose/src/permission/permission_store.rs @@ -1,8 +1,8 @@ +use crate::config::paths::Paths; use crate::conversation::message::ToolRequest; use anyhow::Result; use blake3::Hasher; use chrono::Utc; -use etcetera::{choose_app_strategy, AppStrategy}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::time::Duration; @@ -35,14 +35,10 @@ impl Default for ToolPermissionStore { impl ToolPermissionStore { pub fn new() -> Self { - let permissions_dir = choose_app_strategy(crate::config::APP_STRATEGY.clone()) - .map(|strategy| strategy.config_dir()) - .unwrap_or_else(|_| PathBuf::from(".config/goose")); - Self { permissions: HashMap::new(), version: 1, - permissions_dir, + permissions_dir: Paths::config_dir().join("permissions"), } } diff --git a/crates/goose/src/providers/githubcopilot.rs b/crates/goose/src/providers/githubcopilot.rs index a533a5407ab1..3a4a4c55508c 100644 --- a/crates/goose/src/providers/githubcopilot.rs +++ b/crates/goose/src/providers/githubcopilot.rs @@ -1,8 +1,8 @@ +use crate::config::paths::Paths; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use axum::http; use chrono::{DateTime, Utc}; -use etcetera::{choose_app_strategy, AppStrategy}; use reqwest::Client; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -81,9 +81,7 @@ struct DiskCache { impl DiskCache { fn new() -> Self { - let cache_path = choose_app_strategy(crate::config::APP_STRATEGY.clone()) - .expect("goose requires a home dir") - .in_config_dir("githubcopilot/info.json"); + let cache_path = Paths::in_config_dir("githubcopilot/info.json"); Self { cache_path } } diff --git a/crates/goose/src/providers/oauth.rs b/crates/goose/src/providers/oauth.rs index 50c6ee0908b7..c0be3544bd12 100644 --- a/crates/goose/src/providers/oauth.rs +++ b/crates/goose/src/providers/oauth.rs @@ -1,8 +1,8 @@ +use crate::config::paths::Paths; use anyhow::Result; use axum::{extract::Query, response::Html, routing::get, Router}; use base64::Engine; use chrono::{DateTime, Utc}; -use etcetera::{choose_app_strategy, AppStrategy}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -38,12 +38,7 @@ struct TokenCache { } fn get_base_path() -> PathBuf { - // choose_app_strategy().config_dir() - // - macOS/Linux: ~/.config/goose/databricks/oauth - // - Windows: ~\AppData\Roaming\Block\goose\config\databricks\oauth\ - choose_app_strategy(crate::config::APP_STRATEGY.clone()) - .expect("goose requires a home dir") - .in_config_dir("databricks/oauth") + Paths::in_config_dir("databricks/oauth") } impl TokenCache { diff --git a/crates/goose/src/recipe/local_recipes.rs b/crates/goose/src/recipe/local_recipes.rs index ca7725da9df3..b26a592884c8 100644 --- a/crates/goose/src/recipe/local_recipes.rs +++ b/crates/goose/src/recipe/local_recipes.rs @@ -1,10 +1,9 @@ use anyhow::{anyhow, Result}; -use etcetera::{choose_app_strategy, AppStrategy}; use std::env; use std::fs; use std::path::{Path, PathBuf}; -use crate::config::APP_STRATEGY; +use crate::config::paths::Paths; use crate::recipe::read_recipe_file_content::{read_recipe_file, RecipeFile}; use crate::recipe::Recipe; use crate::recipe::RECIPE_FILE_EXTENSIONS; @@ -14,10 +13,7 @@ const GOOSE_RECIPE_PATH_ENV_VAR: &str = "GOOSE_RECIPE_PATH"; pub fn get_recipe_library_dir(is_global: bool) -> PathBuf { if is_global { - choose_app_strategy(APP_STRATEGY.clone()) - .expect("goose requires a home dir") - .config_dir() - .join("recipes") + Paths::config_dir().join("recipes") } else { std::env::current_dir().unwrap().join(".goose/recipes") } diff --git a/crates/goose/src/scheduler.rs b/crates/goose/src/scheduler.rs index 503c86584ada..93b696c820b2 100644 --- a/crates/goose/src/scheduler.rs +++ b/crates/goose/src/scheduler.rs @@ -7,14 +7,14 @@ use std::sync::Arc; use anyhow::{anyhow, Result}; use async_trait::async_trait; use chrono::{DateTime, Utc}; -use etcetera::{choose_app_strategy, AppStrategy}; use serde::{Deserialize, Serialize}; use tokio::sync::Mutex; use tokio_cron_scheduler::{job::JobId, Job, JobScheduler as TokioJobScheduler}; use crate::agents::AgentEvent; use crate::agents::{Agent, SessionConfig}; -use crate::config::{self, Config}; +use crate::config::paths::Paths; +use crate::config::Config; use crate::conversation::message::Message; use crate::conversation::Conversation; use crate::providers::base::Provider as GooseProvider; // Alias to avoid conflict in test section @@ -63,18 +63,13 @@ pub fn normalize_cron_expression(src: &str) -> String { } pub fn get_default_scheduler_storage_path() -> Result { - let strategy = choose_app_strategy(config::APP_STRATEGY.clone()) - .map_err(|e| io::Error::new(io::ErrorKind::NotFound, e.to_string()))?; - let data_dir = strategy.data_dir(); + let data_dir = Paths::data_dir(); fs::create_dir_all(&data_dir)?; Ok(data_dir.join("schedules.json")) } pub fn get_default_scheduled_recipes_dir() -> Result { - let strategy = choose_app_strategy(config::APP_STRATEGY.clone()).map_err(|e| { - SchedulerError::StorageError(io::Error::new(io::ErrorKind::NotFound, e.to_string())) - })?; - let data_dir = strategy.data_dir(); + let data_dir = Paths::data_dir(); let recipes_dir = data_dir.join("scheduled_recipes"); fs::create_dir_all(&recipes_dir).map_err(SchedulerError::StorageError)?; tracing::debug!( diff --git a/crates/goose/src/session/session_manager.rs b/crates/goose/src/session/session_manager.rs index 6b8f362c2423..1a846af5471c 100644 --- a/crates/goose/src/session/session_manager.rs +++ b/crates/goose/src/session/session_manager.rs @@ -1,4 +1,4 @@ -use crate::config::APP_STRATEGY; +use crate::config::paths::Paths; use crate::conversation::message::Message; use crate::conversation::Conversation; use crate::providers::base::{Provider, MSG_COUNT_FOR_SESSION_NAME_GENERATION}; @@ -6,7 +6,6 @@ use crate::recipe::Recipe; use crate::session::extension_data::ExtensionData; use anyhow::Result; use chrono::{DateTime, Utc}; -use etcetera::{choose_app_strategy, AppStrategy}; use rmcp::model::Role; use serde::{Deserialize, Serialize}; use sqlx::sqlite::SqliteConnectOptions; @@ -241,16 +240,13 @@ pub struct SessionStorage { } pub fn ensure_session_dir() -> Result { - let data_dir = choose_app_strategy(APP_STRATEGY.clone()) - .expect("goose requires a home dir") - .data_dir() - .join("sessions"); + let session_dir = Paths::data_dir().join("sessions"); - if !data_dir.exists() { - fs::create_dir_all(&data_dir)?; + if !session_dir.exists() { + fs::create_dir_all(&session_dir)?; } - Ok(data_dir) + Ok(session_dir) } fn role_to_string(role: &Role) -> &'static str { diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index b9a18f59bdfc..1461022d98d9 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -151,6 +151,11 @@ async function ensureTempDirExists(): Promise { if (started) app.quit(); +if (process.env.ENABLE_PLAYWRIGHT) { + console.log('[Main] Enabling Playwright remote debugging on port 9222'); + app.commandLine.appendSwitch('remote-debugging-port', '9222'); +} + // In development mode, force registration as the default protocol client // In production, register normally if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { @@ -518,10 +523,9 @@ const createChat = async ( const settings = loadSettings(); updateSchedulingEngineEnvironment(settings.schedulingEngine); - // Start new Goosed process for regular windows - // Pass through scheduling engine environment variables const envVars = { GOOSE_SCHEDULER_TYPE: process.env.GOOSE_SCHEDULER_TYPE, + GOOSE_PATH_ROOT: process.env.GOOSE_PATH_ROOT, }; const [newPort, newWorkingDir, newGoosedProcess] = await startGoosed( app, @@ -1678,7 +1682,6 @@ async function appMain() { // Ensure Windows shims are available before any MCP processes are spawned await ensureWinShims(); - // Register update IPC handlers once (but don't setup auto-updater yet) registerUpdateIpcHandlers(); // Handle microphone permission requests