use GOOSE_PATH_ROOT to create isolated app state

This commit is contained in:
Lifei Zhou
2026-04-14 13:57:11 +10:00
parent 7990a52549
commit a4fa1d9931
8 changed files with 59 additions and 54 deletions
+4
View File
@@ -11,6 +11,10 @@ case "$mode" in
esac
profile="${GOOSE2_PROFILE:-}"
if [[ -n "$profile" && -z "${GOOSE_PATH_ROOT:-}" ]]; then
export GOOSE_PATH_ROOT="${TMPDIR:-/tmp}goose2-${profile}"
fi
port_key="$(pwd)::${profile:-default}"
vite_port=$(
PORT_KEY="$port_key" python3 -c \
+2 -8
View File
@@ -7,6 +7,7 @@ use crate::services::acp::{
make_composite_key, search_sessions_via_exports, AcpRunningSession, AcpService, AcpSessionInfo,
AcpSessionRegistry, GooseAcpManager, SessionSearchResult,
};
use crate::services::goose_paths::goose_artifacts_dir;
const DEPRECATED_PROVIDER_IDS: &[&str] = &["claude-code", "codex", "gemini-cli"];
@@ -22,13 +23,6 @@ fn should_include_provider(provider_id: &str) -> bool {
!DEPRECATED_PROVIDER_IDS.contains(&provider_id)
}
fn default_artifacts_working_dir() -> PathBuf {
if let Some(home_dir) = dirs::home_dir() {
return home_dir.join(".goose").join("artifacts");
}
PathBuf::from("/tmp").join(".goose").join("artifacts")
}
fn expand_home_dir(path: PathBuf) -> PathBuf {
let path_string = path.to_string_lossy();
@@ -56,7 +50,7 @@ fn resolve_working_dir(
.map(|dir| dir.trim().to_string())
.filter(|dir| !dir.is_empty())
.map(PathBuf::from)
.unwrap_or_else(default_artifacts_working_dir);
.unwrap_or_else(goose_artifacts_dir);
let working_dir = expand_home_dir(working_dir);
let working_dir = if working_dir.is_relative() {
+2 -1
View File
@@ -1,3 +1,4 @@
use crate::services::goose_paths::goose_avatars_dir;
use crate::services::personas::PersonaStore;
use crate::types::agents::*;
use serde::{Deserialize, Serialize};
@@ -56,7 +57,7 @@ pub fn save_persona_avatar_bytes(
/// Returns the absolute path to the avatars directory (~/.goose/avatars/).
#[tauri::command]
pub fn get_avatars_dir() -> String {
PersonaStore::avatars_dir().to_string_lossy().to_string()
goose_avatars_dir().to_string_lossy().to_string()
}
// --- Sprout-compatible persona import/export ---
+7 -10
View File
@@ -2,10 +2,7 @@ use serde::Deserialize;
use std::fs;
use std::path::{Path, PathBuf};
fn projects_dir() -> Result<PathBuf, String> {
let home = dirs::home_dir().ok_or("Could not determine home directory")?;
Ok(home.join(".goose").join("projects"))
}
use crate::services::goose_paths::goose_projects_dir;
fn generate_id() -> String {
use std::collections::hash_map::DefaultHasher;
@@ -62,7 +59,7 @@ fn slugify(name: &str) -> String {
/// Scan all project directories and find the one whose project.json has the given id.
/// Returns (dir_path, ProjectInfo).
fn find_project_by_id(id: &str) -> Result<(PathBuf, StoredProjectInfo), String> {
let base = projects_dir()?;
let base = goose_projects_dir();
if !base.exists() {
return Err(format!("Project with id \"{}\" not found", id));
}
@@ -195,7 +192,7 @@ fn project_info_from_stored(project_dir: &Path, stored: StoredProjectInfo) -> Pr
#[tauri::command]
pub fn list_projects() -> Result<Vec<ProjectInfo>, String> {
let dir = projects_dir()?;
let dir = goose_projects_dir();
if !dir.exists() {
return Ok(vec![]);
@@ -233,7 +230,7 @@ pub fn list_projects() -> Result<Vec<ProjectInfo>, String> {
#[tauri::command]
pub fn list_archived_projects() -> Result<Vec<ProjectInfo>, String> {
let dir = projects_dir()?;
let dir = goose_projects_dir();
if !dir.exists() {
return Ok(vec![]);
}
@@ -279,7 +276,7 @@ pub fn create_project(
return Err("Project name must not be empty".to_string());
}
let base = projects_dir()?;
let base = goose_projects_dir();
let slug = slugify(&name);
// Determine final directory name, avoiding collisions
@@ -392,7 +389,7 @@ pub fn get_project(id: String) -> Result<ProjectInfo, String> {
#[tauri::command]
pub fn archive_project(id: String) -> Result<(), String> {
let base = projects_dir()?;
let base = goose_projects_dir();
if !base.exists() {
return Err("Projects directory not found".to_string());
}
@@ -425,7 +422,7 @@ pub fn archive_project(id: String) -> Result<(), String> {
#[tauri::command]
pub fn restore_project(id: String) -> Result<(), String> {
let base = projects_dir()?;
let base = goose_projects_dir();
if !base.exists() {
return Err("Projects directory not found".to_string());
}
@@ -6,6 +6,8 @@ use tokio::process::{Child, Command};
use tokio::sync::OnceCell;
use tokio_tungstenite::connect_async;
use crate::services::goose_paths::goose_artifacts_dir;
const GOOSE_SERVE_CONNECT_TIMEOUT: Duration = Duration::from_secs(30);
const GOOSE_SERVE_CONNECT_RETRY_DELAY: Duration = Duration::from_millis(100);
const LOCALHOST: &str = "127.0.0.1";
@@ -61,7 +63,7 @@ impl GooseServeProcess {
// Use a stable working directory for the long-lived server process.
// Individual sessions will set their own cwd via the ACP protocol.
let working_dir = default_serve_working_dir();
let working_dir = goose_artifacts_dir();
std::fs::create_dir_all(&working_dir).map_err(|e| {
format!(
"Failed to create goose serve working directory {}: {e}",
@@ -147,13 +149,6 @@ async fn wait_for_server_ready(ws_url: &str, child: &mut Child) -> Result<(), St
}
}
fn default_serve_working_dir() -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("/tmp"))
.join(".goose")
.join("artifacts")
}
// ---------------------------------------------------------------------------
// Binary resolution
// ---------------------------------------------------------------------------
@@ -0,0 +1,31 @@
use std::path::PathBuf;
pub fn goose_home_dir() -> PathBuf {
if let Ok(root) = std::env::var("GOOSE_PATH_ROOT") {
return PathBuf::from(root);
}
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("/tmp"))
.join(".goose")
}
pub fn goose_artifacts_dir() -> PathBuf {
goose_home_dir().join("artifacts")
}
pub fn goose_projects_dir() -> PathBuf {
goose_home_dir().join("projects")
}
pub fn goose_personas_path() -> PathBuf {
goose_home_dir().join("personas.json")
}
pub fn goose_agents_dir() -> PathBuf {
goose_home_dir().join("agents")
}
pub fn goose_avatars_dir() -> PathBuf {
goose_home_dir().join("avatars")
}
+1
View File
@@ -1,4 +1,5 @@
pub mod acp;
pub mod goose_config;
pub mod goose_paths;
pub mod personas;
pub(crate) mod provider_defs;
+9 -27
View File
@@ -1,6 +1,9 @@
use crate::types::agents::{
builtin_personas, Avatar, CreatePersonaRequest, Persona, UpdatePersonaRequest,
};
use crate::services::goose_paths::{
goose_agents_dir, goose_avatars_dir, goose_personas_path,
};
use log::warn;
use std::collections::HashSet;
use std::path::PathBuf;
@@ -20,7 +23,7 @@ struct MarkdownFrontmatter {
impl PersonaStore {
pub fn new() -> Self {
let store_path = Self::store_path();
let store_path = goose_personas_path();
let stored = Self::load_from_disk(&store_path);
let markdown = Self::load_markdown_personas();
let merged = Self::merge_all(stored, markdown);
@@ -30,19 +33,6 @@ impl PersonaStore {
}
}
fn store_path() -> PathBuf {
let base = dirs::home_dir().expect("home dir");
base.join(".goose").join("personas.json")
}
/// Path to the avatars directory (~/.goose/avatars/).
pub fn avatars_dir() -> PathBuf {
dirs::home_dir()
.expect("home dir")
.join(".goose")
.join("avatars")
}
fn load_from_disk(path: &PathBuf) -> Vec<Persona> {
match std::fs::read_to_string(path) {
Ok(contents) => serde_json::from_str(&contents).unwrap_or_default(),
@@ -86,17 +76,9 @@ impl PersonaStore {
result
}
/// Directory containing markdown persona files.
fn agents_dir() -> PathBuf {
dirs::home_dir()
.expect("home dir")
.join(".goose")
.join("agents")
}
/// Scan `~/.goose/agents/*.md` and parse each into a Persona.
fn load_markdown_personas() -> Vec<Persona> {
let dir = Self::agents_dir();
let dir = goose_agents_dir();
if !dir.is_dir() {
return Vec::new();
}
@@ -309,7 +291,7 @@ impl PersonaStore {
// Clean up local avatar file if present
if let Some(Avatar::Local(filename)) = &persona.avatar {
let path = Self::avatars_dir().join(filename);
let path = goose_avatars_dir().join(filename);
let _ = std::fs::remove_file(path);
}
@@ -321,7 +303,7 @@ impl PersonaStore {
/// Copy an avatar image from a source path to ~/.goose/avatars/{persona_id}.{ext}.
/// Returns the filename (not full path).
pub fn save_avatar_from_path(persona_id: &str, source_path: &str) -> Result<String, String> {
let avatars_dir = Self::avatars_dir();
let avatars_dir = goose_avatars_dir();
std::fs::create_dir_all(&avatars_dir)
.map_err(|e| format!("Failed to create avatars directory: {}", e))?;
@@ -362,7 +344,7 @@ impl PersonaStore {
bytes: &[u8],
extension: &str,
) -> Result<String, String> {
let avatars_dir = Self::avatars_dir();
let avatars_dir = goose_avatars_dir();
std::fs::create_dir_all(&avatars_dir)
.map_err(|e| format!("Failed to create avatars directory: {}", e))?;
@@ -391,7 +373,7 @@ impl PersonaStore {
/// Delete avatar file for a persona.
#[allow(dead_code)]
pub fn delete_avatar_file(filename: &str) {
let path = Self::avatars_dir().join(filename);
let path = goose_avatars_dir().join(filename);
let _ = std::fs::remove_file(path);
}
}