Show errors on failure (#5643)
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -9,7 +9,7 @@ use crate::commands::configure::handle_configure;
|
|||||||
use crate::commands::info::handle_info;
|
use crate::commands::info::handle_info;
|
||||||
use crate::commands::project::{handle_project_default, handle_projects_interactive};
|
use crate::commands::project::{handle_project_default, handle_projects_interactive};
|
||||||
use crate::commands::recipe::{handle_deeplink, handle_list, handle_open, handle_validate};
|
use crate::commands::recipe::{handle_deeplink, handle_list, handle_open, handle_validate};
|
||||||
// Import the new handlers from commands::schedule
|
|
||||||
use crate::commands::schedule::{
|
use crate::commands::schedule::{
|
||||||
handle_schedule_add, handle_schedule_cron_help, handle_schedule_list, handle_schedule_remove,
|
handle_schedule_add, handle_schedule_cron_help, handle_schedule_list, handle_schedule_remove,
|
||||||
handle_schedule_run_now, handle_schedule_services_status, handle_schedule_services_stop,
|
handle_schedule_run_now, handle_schedule_services_status, handle_schedule_services_stop,
|
||||||
|
|||||||
@@ -2,43 +2,73 @@ use anyhow::Result;
|
|||||||
use console::style;
|
use console::style;
|
||||||
use goose::config::paths::Paths;
|
use goose::config::paths::Paths;
|
||||||
use goose::config::Config;
|
use goose::config::Config;
|
||||||
|
use goose::session::session_manager::{DB_NAME, SESSIONS_FOLDER};
|
||||||
use serde_yaml;
|
use serde_yaml;
|
||||||
|
|
||||||
fn print_aligned(label: &str, value: &str, width: usize) {
|
fn print_aligned(label: &str, value: &str, width: usize) {
|
||||||
println!(" {:<width$} {}", label, value, width = width);
|
println!(" {:<width$} {}", label, value, width = width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use goose::config::base::CONFIG_YAML_NAME;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
fn check_path_status(path: &Path) -> String {
|
||||||
|
if path.exists() {
|
||||||
|
"".to_string()
|
||||||
|
} else {
|
||||||
|
let mut current = path.parent();
|
||||||
|
while let Some(parent) = current {
|
||||||
|
if parent.exists() {
|
||||||
|
return match fs::metadata(parent).map(|m| !m.permissions().readonly()) {
|
||||||
|
Ok(true) => style("missing (can create)").dim().to_string(),
|
||||||
|
Ok(false) => style("missing (read-only parent)").red().to_string(),
|
||||||
|
Err(_) => style("missing (cannot check)").red().to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
current = parent.parent();
|
||||||
|
}
|
||||||
|
style("missing (no writable parent)").red().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle_info(verbose: bool) -> Result<()> {
|
pub fn handle_info(verbose: bool) -> Result<()> {
|
||||||
let logs_dir = Paths::in_state_dir("logs");
|
let logs_dir = Paths::in_state_dir("logs");
|
||||||
let sessions_dir = Paths::in_data_dir("sessions");
|
let sessions_dir = Paths::in_data_dir(SESSIONS_FOLDER);
|
||||||
let sessions_db = sessions_dir.join("sessions.db");
|
let sessions_db = sessions_dir.join(DB_NAME);
|
||||||
|
|
||||||
// Get paths using a stored reference to the global config
|
|
||||||
let config = Config::global();
|
let config = Config::global();
|
||||||
let config_dir = Paths::config_dir().display().to_string();
|
let config_dir = Paths::config_dir();
|
||||||
|
let config_yaml_file = config_dir.join(CONFIG_YAML_NAME);
|
||||||
|
|
||||||
// Define the labels and their corresponding path values once.
|
|
||||||
let paths = [
|
let paths = [
|
||||||
("Config dir:", config_dir),
|
("Config dir:", &config_dir),
|
||||||
("Sessions DB (sqlite):", sessions_db.display().to_string()),
|
("Config yaml:", &config_yaml_file),
|
||||||
("Logs dir:", logs_dir.display().to_string()),
|
("Sessions DB (sqlite):", &sessions_db),
|
||||||
|
("Logs dir:", &logs_dir),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Calculate padding: use the max length of the label plus extra space.
|
let label_padding = paths.iter().map(|(l, _)| l.len()).max().unwrap_or(0) + 4;
|
||||||
let basic_padding = paths.iter().map(|(l, _)| l.len()).max().unwrap_or(0) + 4;
|
let path_padding = paths
|
||||||
|
.iter()
|
||||||
|
.map(|(_, p)| p.display().to_string().len())
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0)
|
||||||
|
+ 4;
|
||||||
|
|
||||||
// Print version information
|
|
||||||
println!("{}", style("goose Version:").cyan().bold());
|
println!("{}", style("goose Version:").cyan().bold());
|
||||||
print_aligned("Version:", env!("CARGO_PKG_VERSION"), basic_padding);
|
print_aligned("Version:", env!("CARGO_PKG_VERSION"), label_padding);
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
// Print location information
|
println!("{}", style("Paths:").cyan().bold());
|
||||||
println!("{}", style("goose Locations:").cyan().bold());
|
|
||||||
for (label, path) in &paths {
|
for (label, path) in &paths {
|
||||||
print_aligned(label, path, basic_padding);
|
println!(
|
||||||
|
"{:<label_padding$}{:<path_padding$}{}",
|
||||||
|
label,
|
||||||
|
path.display(),
|
||||||
|
check_path_status(path)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print verbose info if requested
|
|
||||||
if verbose {
|
if verbose {
|
||||||
println!("\n{}", style("goose Configuration:").cyan().bold());
|
println!("\n{}", style("goose Configuration:").cyan().bold());
|
||||||
let values = config.all_values()?;
|
let values = config.all_values()?;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use goose_cli::cli::cli;
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
if let Err(e) = goose_cli::logging::setup_logging(None, None) {
|
if let Err(e) = goose_cli::logging::setup_logging(None, None) {
|
||||||
eprintln!("Warning: Failed to initialize telemetry: {}", e);
|
eprintln!("Warning: Failed to initialize logging: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = cli().await;
|
let result = cli().await;
|
||||||
|
|||||||
@@ -28,13 +28,10 @@ async fn shutdown_signal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run() -> Result<()> {
|
pub async fn run() -> Result<()> {
|
||||||
// Initialize logging and telemetry
|
|
||||||
crate::logging::setup_logging(Some("goosed"))?;
|
crate::logging::setup_logging(Some("goosed"))?;
|
||||||
|
|
||||||
let settings = configuration::Settings::new()?;
|
let settings = configuration::Settings::new()?;
|
||||||
|
|
||||||
// Initialize pricing cache on startup
|
|
||||||
tracing::info!("Initializing pricing cache...");
|
|
||||||
if let Err(e) = initialize_pricing_cache().await {
|
if let Err(e) = initialize_pricing_cache().await {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"Failed to initialize pricing cache: {}. Pricing data may not be available.",
|
"Failed to initialize pricing cache: {}. Pricing data may not be available.",
|
||||||
@@ -61,7 +58,7 @@ pub async fn run() -> Result<()> {
|
|||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(settings.socket_addr()).await?;
|
let listener = tokio::net::TcpListener::bind(settings.socket_addr()).await?;
|
||||||
info!("listening on {}", listener.local_addr()?);
|
info!("listening on {}", listener.local_addr()?);
|
||||||
// Ensure the listener/socket is properly closed on cancellation by using graceful shutdown
|
|
||||||
axum::serve(listener, app)
|
axum::serve(listener, app)
|
||||||
.with_graceful_shutdown(shutdown_signal())
|
.with_graceful_shutdown(shutdown_signal())
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use tracing_appender::rolling::Rotation;
|
use tracing_appender::rolling::Rotation;
|
||||||
use tracing_subscriber::{
|
use tracing_subscriber::{
|
||||||
filter::LevelFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer,
|
filter::LevelFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer,
|
||||||
@@ -20,11 +20,8 @@ pub fn setup_logging(name: Option<&str>) -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
format!("{}.log", timestamp)
|
format!("{}.log", timestamp)
|
||||||
};
|
};
|
||||||
let file_appender = tracing_appender::rolling::RollingFileAppender::new(
|
let file_appender =
|
||||||
Rotation::NEVER, // we do manual rotation via file naming and cleanup_old_logs
|
tracing_appender::rolling::RollingFileAppender::new(Rotation::NEVER, log_dir, log_filename);
|
||||||
log_dir,
|
|
||||||
log_filename,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create JSON file logging layer
|
// Create JSON file logging layer
|
||||||
let file_layer = fmt::layer()
|
let file_layer = fmt::layer()
|
||||||
@@ -34,35 +31,27 @@ pub fn setup_logging(name: Option<&str>) -> Result<()> {
|
|||||||
.with_ansi(false)
|
.with_ansi(false)
|
||||||
.with_file(true);
|
.with_file(true);
|
||||||
|
|
||||||
// Create console logging layer for development - INFO and above only
|
let base_env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||||
|
EnvFilter::new("")
|
||||||
|
.add_directive("mcp_client=info".parse().unwrap())
|
||||||
|
.add_directive("goose=debug".parse().unwrap())
|
||||||
|
.add_directive("goose_server=info".parse().unwrap())
|
||||||
|
.add_directive("tower_http=info".parse().unwrap())
|
||||||
|
.add_directive(LevelFilter::WARN.into())
|
||||||
|
});
|
||||||
|
|
||||||
let console_layer = fmt::layer()
|
let console_layer = fmt::layer()
|
||||||
.with_writer(std::io::stderr)
|
.with_writer(std::io::stderr)
|
||||||
.with_target(true)
|
.with_target(true)
|
||||||
.with_level(true)
|
.with_level(true)
|
||||||
.with_ansi(true)
|
|
||||||
.with_file(true)
|
.with_file(true)
|
||||||
|
.with_ansi(false)
|
||||||
.with_line_number(true)
|
.with_line_number(true)
|
||||||
.pretty();
|
.pretty();
|
||||||
|
|
||||||
// Base filter for all logging
|
|
||||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
|
||||||
// Set default levels for different modules
|
|
||||||
EnvFilter::new("")
|
|
||||||
// Set mcp-client to DEBUG
|
|
||||||
.add_directive("mcp_client=debug".parse().unwrap())
|
|
||||||
// Set goose module to DEBUG
|
|
||||||
.add_directive("goose=debug".parse().unwrap())
|
|
||||||
// Set goose-server to INFO
|
|
||||||
.add_directive("goose_server=info".parse().unwrap())
|
|
||||||
// Set tower-http to INFO for request logging
|
|
||||||
.add_directive("tower_http=info".parse().unwrap())
|
|
||||||
// Set everything else to WARN
|
|
||||||
.add_directive(LevelFilter::WARN.into())
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut layers = vec![
|
let mut layers = vec![
|
||||||
file_layer.with_filter(env_filter).boxed(),
|
file_layer.with_filter(base_env_filter.clone()).boxed(),
|
||||||
console_layer.with_filter(LevelFilter::INFO).boxed(),
|
console_layer.with_filter(base_env_filter).boxed(),
|
||||||
];
|
];
|
||||||
|
|
||||||
if let Ok((otlp_tracing_layer, otlp_metrics_layer, otlp_logs_layer)) = otlp_layer::init_otlp() {
|
if let Ok((otlp_tracing_layer, otlp_metrics_layer, otlp_logs_layer)) = otlp_layer::init_otlp() {
|
||||||
@@ -89,9 +78,7 @@ pub fn setup_logging(name: Option<&str>) -> Result<()> {
|
|||||||
|
|
||||||
let subscriber = Registry::default().with(layers);
|
let subscriber = Registry::default().with(layers);
|
||||||
|
|
||||||
subscriber
|
subscriber.try_init()?;
|
||||||
.try_init()
|
|
||||||
.context("Failed to set global subscriber")?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use thiserror::Error;
|
|||||||
|
|
||||||
const KEYRING_SERVICE: &str = "goose";
|
const KEYRING_SERVICE: &str = "goose";
|
||||||
const KEYRING_USERNAME: &str = "secrets";
|
const KEYRING_USERNAME: &str = "secrets";
|
||||||
|
pub const CONFIG_YAML_NAME: &str = "config.yaml";
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
const TEST_KEYRING_SERVICE: &str = "goose-test";
|
const TEST_KEYRING_SERVICE: &str = "goose-test";
|
||||||
@@ -119,9 +120,7 @@ impl Default for Config {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let config_dir = Paths::config_dir();
|
let config_dir = Paths::config_dir();
|
||||||
|
|
||||||
std::fs::create_dir_all(&config_dir).expect("Failed to create config directory");
|
let config_path = config_dir.join(CONFIG_YAML_NAME);
|
||||||
|
|
||||||
let config_path = config_dir.join("config.yaml");
|
|
||||||
|
|
||||||
let secrets = match env::var("GOOSE_DISABLE_KEYRING") {
|
let secrets = match env::var("GOOSE_DISABLE_KEYRING") {
|
||||||
Ok(_) => SecretStorage::File {
|
Ok(_) => SecretStorage::File {
|
||||||
@@ -433,7 +432,6 @@ impl Config {
|
|||||||
// Convert to YAML for storage
|
// Convert to YAML for storage
|
||||||
let yaml_value = serde_yaml::to_string(&values)?;
|
let yaml_value = serde_yaml::to_string(&values)?;
|
||||||
|
|
||||||
// Ensure the directory exists
|
|
||||||
if let Some(parent) = self.config_path.parent() {
|
if let Some(parent) = self.config_path.parent() {
|
||||||
std::fs::create_dir_all(parent)
|
std::fs::create_dir_all(parent)
|
||||||
.map_err(|e| ConfigError::DirectoryError(e.to_string()))?;
|
.map_err(|e| ConfigError::DirectoryError(e.to_string()))?;
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ pub fn prepare_log_directory(component: &str, use_date_subdir: bool) -> Result<P
|
|||||||
component_dir
|
component_dir
|
||||||
};
|
};
|
||||||
|
|
||||||
fs::create_dir_all(&log_dir).context("Failed to create log directory")?;
|
fs::create_dir_all(&log_dir)
|
||||||
|
.with_context(|| format!("Failed to create log directory: {:?}", log_dir))?;
|
||||||
|
|
||||||
Ok(log_dir)
|
Ok(log_dir)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -166,7 +166,6 @@ impl PricingCache {
|
|||||||
|
|
||||||
self.save_to_disk(&cached_data).await?;
|
self.save_to_disk(&cached_data).await?;
|
||||||
|
|
||||||
// Update memory cache
|
|
||||||
{
|
{
|
||||||
let mut cache = self.memory_cache.write().await;
|
let mut cache = self.memory_cache.write().await;
|
||||||
*cache = Some(cached_data);
|
*cache = Some(cached_data);
|
||||||
@@ -179,15 +178,6 @@ impl PricingCache {
|
|||||||
pub async fn initialize(&self) -> Result<()> {
|
pub async fn initialize(&self) -> Result<()> {
|
||||||
// Try loading from disk first
|
// Try loading from disk first
|
||||||
if let Ok(Some(cached)) = self.load_from_disk().await {
|
if let Ok(Some(cached)) = self.load_from_disk().await {
|
||||||
// Log how many models we have cached
|
|
||||||
let total_models: usize = cached.pricing.values().map(|models| models.len()).sum();
|
|
||||||
tracing::debug!(
|
|
||||||
"Loaded {} providers with {} total models from disk cache",
|
|
||||||
cached.pricing.len(),
|
|
||||||
total_models
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update memory cache
|
|
||||||
{
|
{
|
||||||
let mut cache = self.memory_cache.write().await;
|
let mut cache = self.memory_cache.write().await;
|
||||||
*cache = Some(cached);
|
*cache = Some(cached);
|
||||||
@@ -196,8 +186,6 @@ impl PricingCache {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no disk cache, fetch from OpenRouter
|
|
||||||
tracing::info!("Fetching pricing data from OpenRouter API");
|
|
||||||
self.refresh().await
|
self.refresh().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,14 +201,13 @@ lazy_static::lazy_static! {
|
|||||||
static ref PRICING_CACHE: PricingCache = PricingCache::new();
|
static ref PRICING_CACHE: PricingCache = PricingCache::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a properly configured HTTP client for the current runtime
|
fn create_http_client() -> Result<Client> {
|
||||||
fn create_http_client() -> Client {
|
|
||||||
Client::builder()
|
Client::builder()
|
||||||
.timeout(Duration::from_secs(30))
|
.timeout(Duration::from_secs(30))
|
||||||
.pool_idle_timeout(Duration::from_secs(90))
|
.pool_idle_timeout(Duration::from_secs(90))
|
||||||
.pool_max_idle_per_host(10)
|
.pool_max_idle_per_host(10)
|
||||||
.build()
|
.build()
|
||||||
.expect("Failed to create HTTP client")
|
.map_err(|e| anyhow!(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// OpenRouter model pricing information
|
/// OpenRouter model pricing information
|
||||||
@@ -254,7 +241,7 @@ pub struct OpenRouterModelsResponse {
|
|||||||
|
|
||||||
/// Internal function to fetch pricing data
|
/// Internal function to fetch pricing data
|
||||||
async fn fetch_openrouter_pricing_internal() -> Result<HashMap<String, OpenRouterModel>> {
|
async fn fetch_openrouter_pricing_internal() -> Result<HashMap<String, OpenRouterModel>> {
|
||||||
let client = create_http_client();
|
let client = create_http_client()?;
|
||||||
let response = client
|
let response = client
|
||||||
.get("https://openrouter.ai/api/v1/models")
|
.get("https://openrouter.ai/api/v1/models")
|
||||||
.send()
|
.send()
|
||||||
|
|||||||
@@ -190,13 +190,6 @@ impl Scheduler {
|
|||||||
|
|
||||||
let local_tz = Local::now().timezone();
|
let local_tz = Local::now().timezone();
|
||||||
|
|
||||||
tracing::info!(
|
|
||||||
"Creating cron task for job '{}' cron: '{}' in timezone: {:?}",
|
|
||||||
job.id,
|
|
||||||
cron,
|
|
||||||
local_tz
|
|
||||||
);
|
|
||||||
|
|
||||||
Job::new_async_tz(&cron, local_tz, move |_uuid, _l| {
|
Job::new_async_tz(&cron, local_tz, move |_uuid, _l| {
|
||||||
tracing::info!("Cron task triggered for job '{}'", job_for_task.id);
|
tracing::info!("Cron task triggered for job '{}'", job_for_task.id);
|
||||||
let task_job_id = job_for_task.id.clone();
|
let task_job_id = job_for_task.id.clone();
|
||||||
@@ -215,7 +208,6 @@ impl Scheduler {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !should_execute {
|
if !should_execute {
|
||||||
tracing::info!("Skipping paused job '{}'", task_job_id);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -731,15 +723,11 @@ async fn execute_job(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = SessionManager::update_session(&session.id)
|
SessionManager::update_session(&session.id)
|
||||||
.schedule_id(Some(job.id.clone()))
|
.schedule_id(Some(job.id.clone()))
|
||||||
.recipe(Some(recipe))
|
.recipe(Some(recipe))
|
||||||
.apply()
|
.apply()
|
||||||
.await
|
.await?;
|
||||||
{
|
|
||||||
tracing::error!("Failed to update session: {}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(session.id)
|
Ok(session.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ use tracing::{info, warn};
|
|||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
const CURRENT_SCHEMA_VERSION: i32 = 5;
|
const CURRENT_SCHEMA_VERSION: i32 = 5;
|
||||||
|
pub const SESSIONS_FOLDER: &str = "sessions";
|
||||||
|
pub const DB_NAME: &str = "sessions.db";
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, ToSchema, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, ToSchema, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
@@ -335,7 +337,7 @@ pub struct SessionStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_session_dir() -> Result<PathBuf> {
|
pub fn ensure_session_dir() -> Result<PathBuf> {
|
||||||
let session_dir = Paths::data_dir().join("sessions");
|
let session_dir = Paths::data_dir().join(SESSIONS_FOLDER);
|
||||||
|
|
||||||
if !session_dir.exists() {
|
if !session_dir.exists() {
|
||||||
fs::create_dir_all(&session_dir)?;
|
fs::create_dir_all(&session_dir)?;
|
||||||
@@ -439,7 +441,7 @@ impl sqlx::FromRow<'_, sqlx::sqlite::SqliteRow> for Session {
|
|||||||
impl SessionStorage {
|
impl SessionStorage {
|
||||||
async fn new() -> Result<Self> {
|
async fn new() -> Result<Self> {
|
||||||
let session_dir = ensure_session_dir()?;
|
let session_dir = ensure_session_dir()?;
|
||||||
let db_path = session_dir.join("sessions.db");
|
let db_path = session_dir.join(DB_NAME);
|
||||||
|
|
||||||
let storage = if db_path.exists() {
|
let storage = if db_path.exists() {
|
||||||
Self::open(&db_path).await?
|
Self::open(&db_path).await?
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ impl Default for OtlpConfig {
|
|||||||
|
|
||||||
impl OtlpConfig {
|
impl OtlpConfig {
|
||||||
pub fn from_config() -> Option<Self> {
|
pub fn from_config() -> Option<Self> {
|
||||||
// Try to get from goose config system (which checks env vars first, then config file)
|
|
||||||
let config = crate::config::Config::global();
|
let config = crate::config::Config::global();
|
||||||
|
|
||||||
// Try to get the endpoint from config (checks OTEL_EXPORTER_OTLP_ENDPOINT env var first)
|
// Try to get the endpoint from config (checks OTEL_EXPORTER_OTLP_ENDPOINT env var first)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ModeSection } from '../mode/ModeSection';
|
import { ModeSection } from '../mode/ModeSection';
|
||||||
import { ToolSelectionStrategySection } from '../tool_selection_strategy/ToolSelectionStrategySection';
|
import { ToolSelectionStrategySection } from '../tool_selection_strategy/ToolSelectionStrategySection';
|
||||||
import SchedulerSection from '../scheduler/SchedulerSection';
|
|
||||||
import DictationSection from '../dictation/DictationSection';
|
import DictationSection from '../dictation/DictationSection';
|
||||||
import { SecurityToggle } from '../security/SecurityToggle';
|
import { SecurityToggle } from '../security/SecurityToggle';
|
||||||
import { ResponseStylesSection } from '../response_styles/ResponseStylesSection';
|
import { ResponseStylesSection } from '../response_styles/ResponseStylesSection';
|
||||||
@@ -48,9 +47,6 @@ export default function ChatSettingsSection() {
|
|||||||
Choose which scheduling backend to use for scheduled recipes and tasks
|
Choose which scheduling backend to use for scheduled recipes and tasks
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="px-2">
|
|
||||||
<SchedulerSection />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="pb-2 rounded-lg">
|
<Card className="pb-2 rounded-lg">
|
||||||
<CardHeader className="pb-0">
|
<CardHeader className="pb-0">
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { SchedulingEngine, Settings } from '../../../utils/settings';
|
|
||||||
|
|
||||||
interface SchedulingEngineOption {
|
|
||||||
key: SchedulingEngine;
|
|
||||||
label: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const schedulingEngineOptions: SchedulingEngineOption[] = [
|
|
||||||
{
|
|
||||||
key: 'builtin-cron',
|
|
||||||
label: 'Built-in Cron (Default)',
|
|
||||||
description:
|
|
||||||
"Uses Goose's built-in cron scheduler. Simple and reliable for basic scheduling needs.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'temporal',
|
|
||||||
label: 'Temporal',
|
|
||||||
description:
|
|
||||||
'Uses Temporal workflow engine for advanced scheduling features. Requires Temporal CLI to be installed.',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
interface SchedulerSectionProps {
|
|
||||||
onSchedulingEngineChange?: (engine: SchedulingEngine) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SchedulerSection({ onSchedulingEngineChange }: SchedulerSectionProps) {
|
|
||||||
const [schedulingEngine, setSchedulingEngine] = useState<SchedulingEngine>('builtin-cron');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const loadSchedulingEngine = async () => {
|
|
||||||
try {
|
|
||||||
const settings = (await window.electron.getSettings()) as Settings | null;
|
|
||||||
if (settings?.schedulingEngine) {
|
|
||||||
setSchedulingEngine(settings.schedulingEngine);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load scheduling engine setting:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadSchedulingEngine();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleEngineChange = async (engine: SchedulingEngine) => {
|
|
||||||
try {
|
|
||||||
setSchedulingEngine(engine);
|
|
||||||
|
|
||||||
await window.electron.setSchedulingEngine(engine);
|
|
||||||
|
|
||||||
if (onSchedulingEngineChange) {
|
|
||||||
onSchedulingEngineChange(engine);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to save scheduling engine setting:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-1">
|
|
||||||
{schedulingEngineOptions.map((option) => {
|
|
||||||
const isChecked = schedulingEngine === option.key;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={option.key} className="group hover:cursor-pointer text-sm">
|
|
||||||
<div
|
|
||||||
className={`flex items-center justify-between text-text-default py-2 px-2 ${
|
|
||||||
isChecked
|
|
||||||
? 'bg-background-muted'
|
|
||||||
: 'bg-background-default hover:bg-background-muted'
|
|
||||||
} rounded-lg transition-all`}
|
|
||||||
onClick={() => handleEngineChange(option.key)}
|
|
||||||
>
|
|
||||||
<div className="flex">
|
|
||||||
<div>
|
|
||||||
<h3 className="text-text-default">{option.label}</h3>
|
|
||||||
<p className="text-xs text-text-muted mt-[2px]">{option.description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative flex items-center gap-2">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="schedulingEngine"
|
|
||||||
value={option.key}
|
|
||||||
checked={isChecked}
|
|
||||||
onChange={() => handleEngineChange(option.key)}
|
|
||||||
className="peer sr-only"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="h-4 w-4 rounded-full border border-border-default
|
|
||||||
peer-checked:border-[6px] peer-checked:border-black dark:peer-checked:border-white
|
|
||||||
peer-checked:bg-white dark:peer-checked:bg-black
|
|
||||||
transition-all duration-200 ease-in-out group-hover:border-border-default"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
<div className="mt-4 p-3 bg-background-subtle rounded-md">
|
|
||||||
<p className="text-xs text-text-muted">
|
|
||||||
<strong>Note:</strong> Changing the scheduling engine will apply to new Goose sessions.
|
|
||||||
You will need to restart Goose for the change to take full effect. <br />
|
|
||||||
The scheduling engines do not share the list of schedules.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
+26
-28
@@ -2,7 +2,6 @@ import { spawn, ChildProcess } from 'child_process';
|
|||||||
import { createServer } from 'net';
|
import { createServer } from 'net';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import fs from 'node:fs';
|
|
||||||
import { getBinaryPath } from './utils/pathUtils';
|
import { getBinaryPath } from './utils/pathUtils';
|
||||||
import log from './utils/logger';
|
import log from './utils/logger';
|
||||||
import { App } from 'electron';
|
import { App } from 'electron';
|
||||||
@@ -11,7 +10,6 @@ import { Buffer } from 'node:buffer';
|
|||||||
import { status } from './api';
|
import { status } from './api';
|
||||||
import { Client } from './api/client';
|
import { Client } from './api/client';
|
||||||
|
|
||||||
// Find an available port to start goosed on
|
|
||||||
export const findAvailablePort = (): Promise<number> => {
|
export const findAvailablePort = (): Promise<number> => {
|
||||||
return new Promise((resolve, _reject) => {
|
return new Promise((resolve, _reject) => {
|
||||||
const server = createServer();
|
const server = createServer();
|
||||||
@@ -27,11 +25,20 @@ export const findAvailablePort = (): Promise<number> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Check if goosed server is ready by polling the status endpoint
|
// Check if goosed server is ready by polling the status endpoint
|
||||||
export const checkServerStatus = async (client: Client): Promise<boolean> => {
|
export const checkServerStatus = async (client: Client, errorLog: string[]): Promise<boolean> => {
|
||||||
const interval = 100; // ms
|
const interval = 100; // ms
|
||||||
const maxAttempts = 1200; // 120s
|
const maxAttempts = 30; // 3s
|
||||||
|
|
||||||
|
const fatal = (line: string) => {
|
||||||
|
const trimmed = line.trim().toLowerCase();
|
||||||
|
return trimmed.startsWith("thread 'main' panicked at") || trimmed.startsWith('error:');
|
||||||
|
};
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||||
|
if (errorLog.some(fatal)) {
|
||||||
|
log.error('Detected fatal error in server logs');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await status({ client, throwOnError: true });
|
await status({ client, throwOnError: true });
|
||||||
return true;
|
return true;
|
||||||
@@ -48,7 +55,7 @@ export const checkServerStatus = async (client: Client): Promise<boolean> => {
|
|||||||
const connectToExternalBackend = async (
|
const connectToExternalBackend = async (
|
||||||
workingDir: string,
|
workingDir: string,
|
||||||
port: number = 3000
|
port: number = 3000
|
||||||
): Promise<[number, string, ChildProcess]> => {
|
): Promise<[number, string, ChildProcess, string[]]> => {
|
||||||
log.info(`Using external goosed backend on port ${port}`);
|
log.info(`Using external goosed backend on port ${port}`);
|
||||||
|
|
||||||
const mockProcess = {
|
const mockProcess = {
|
||||||
@@ -58,7 +65,7 @@ const connectToExternalBackend = async (
|
|||||||
},
|
},
|
||||||
} as ChildProcess;
|
} as ChildProcess;
|
||||||
|
|
||||||
return [port, workingDir, mockProcess];
|
return [port, workingDir, mockProcess, []];
|
||||||
};
|
};
|
||||||
|
|
||||||
interface GooseProcessEnv {
|
interface GooseProcessEnv {
|
||||||
@@ -76,39 +83,23 @@ interface GooseProcessEnv {
|
|||||||
export const startGoosed = async (
|
export const startGoosed = async (
|
||||||
app: App,
|
app: App,
|
||||||
serverSecret: string,
|
serverSecret: string,
|
||||||
dir: string | null = null,
|
dir: string,
|
||||||
env: Partial<GooseProcessEnv> = {}
|
env: Partial<GooseProcessEnv> = {}
|
||||||
): Promise<[number, string, ChildProcess]> => {
|
): Promise<[number, string, ChildProcess, string[]]> => {
|
||||||
const homeDir = os.homedir();
|
|
||||||
const isWindows = process.platform === 'win32';
|
const isWindows = process.platform === 'win32';
|
||||||
|
const homeDir = os.homedir();
|
||||||
if (!dir) {
|
|
||||||
dir = homeDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
dir = path.resolve(path.normalize(dir));
|
dir = path.resolve(path.normalize(dir));
|
||||||
|
|
||||||
if (process.env.GOOSE_EXTERNAL_BACKEND) {
|
if (process.env.GOOSE_EXTERNAL_BACKEND) {
|
||||||
return connectToExternalBackend(dir, 3000);
|
return connectToExternalBackend(dir, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const stats = fs.lstatSync(dir);
|
|
||||||
|
|
||||||
if (!stats.isDirectory()) {
|
|
||||||
log.warn(`Provided path is not a directory, falling back to home directory`);
|
|
||||||
dir = homeDir;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
log.warn(`Directory does not exist, falling back to home directory`);
|
|
||||||
dir = homeDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
let goosedPath = getBinaryPath(app, 'goosed');
|
let goosedPath = getBinaryPath(app, 'goosed');
|
||||||
|
|
||||||
const resolvedGoosedPath = path.resolve(goosedPath);
|
const resolvedGoosedPath = path.resolve(goosedPath);
|
||||||
|
|
||||||
const port = await findAvailablePort();
|
const port = await findAvailablePort();
|
||||||
|
const stderrLines: string[] = [];
|
||||||
|
|
||||||
log.info(`Starting goosed from: ${resolvedGoosedPath} on port ${port} in dir ${dir}`);
|
log.info(`Starting goosed from: ${resolvedGoosedPath} on port ${port} in dir ${dir}`);
|
||||||
|
|
||||||
@@ -183,7 +174,14 @@ export const startGoosed = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
goosedProcess.stderr?.on('data', (data: Buffer) => {
|
goosedProcess.stderr?.on('data', (data: Buffer) => {
|
||||||
log.error(`goosed stderr for port ${port} and dir ${dir}: ${data.toString()}`);
|
const lines = data
|
||||||
|
.toString()
|
||||||
|
.split('\n')
|
||||||
|
.filter((l) => l.trim());
|
||||||
|
lines.forEach((line) => {
|
||||||
|
log.error(`goosed stderr for port ${port} and dir ${dir}: ${line}`);
|
||||||
|
stderrLines.push(line);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
goosedProcess.on('close', (code: number | null) => {
|
goosedProcess.on('close', (code: number | null) => {
|
||||||
@@ -215,5 +213,5 @@ export const startGoosed = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
log.info(`Goosed server successfully started on port ${port}`);
|
log.info(`Goosed server successfully started on port ${port}`);
|
||||||
return [port, dir, goosedProcess];
|
return [port, dir, goosedProcess, stderrLines];
|
||||||
};
|
};
|
||||||
|
|||||||
+20
-58
@@ -33,9 +33,7 @@ import {
|
|||||||
EnvToggles,
|
EnvToggles,
|
||||||
loadSettings,
|
loadSettings,
|
||||||
saveSettings,
|
saveSettings,
|
||||||
SchedulingEngine,
|
|
||||||
updateEnvironmentVariables,
|
updateEnvironmentVariables,
|
||||||
updateSchedulingEngineEnvironment,
|
|
||||||
} from './utils/settings';
|
} from './utils/settings';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
// import electron from "electron";
|
// import electron from "electron";
|
||||||
@@ -502,38 +500,20 @@ const createChat = async (
|
|||||||
scheduledJobId?: string, // Scheduled job ID if applicable
|
scheduledJobId?: string, // Scheduled job ID if applicable
|
||||||
recipeId?: string
|
recipeId?: string
|
||||||
) => {
|
) => {
|
||||||
// Initialize variables for process and configuration
|
updateEnvironmentVariables(envToggles);
|
||||||
let port = 0;
|
|
||||||
let workingDir = '';
|
|
||||||
let goosedProcess: import('child_process').ChildProcess | null = null;
|
|
||||||
|
|
||||||
{
|
const envVars = {
|
||||||
// Apply current environment settings before creating chat
|
GOOSE_PATH_ROOT: process.env.GOOSE_PATH_ROOT,
|
||||||
updateEnvironmentVariables(envToggles);
|
};
|
||||||
|
const [port, workingDir, goosedProcess, errorLog] = await startGoosed(
|
||||||
|
app,
|
||||||
|
SERVER_SECRET,
|
||||||
|
dir || os.homedir(),
|
||||||
|
envVars
|
||||||
|
);
|
||||||
|
|
||||||
// Apply scheduling engine setting
|
|
||||||
const settings = loadSettings();
|
|
||||||
updateSchedulingEngineEnvironment(settings.schedulingEngine);
|
|
||||||
|
|
||||||
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,
|
|
||||||
SERVER_SECRET,
|
|
||||||
dir,
|
|
||||||
envVars
|
|
||||||
);
|
|
||||||
port = newPort;
|
|
||||||
workingDir = newWorkingDir;
|
|
||||||
goosedProcess = newGoosedProcess;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create window config with loading state for recipe deeplinks
|
|
||||||
// Load and manage window state
|
|
||||||
const mainWindowState = windowStateKeeper({
|
const mainWindowState = windowStateKeeper({
|
||||||
defaultWidth: 940, // large enough to show the sidebar on launch
|
defaultWidth: 940,
|
||||||
defaultHeight: 800,
|
defaultHeight: 800,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -594,23 +574,21 @@ const createChat = async (
|
|||||||
);
|
);
|
||||||
goosedClients.set(mainWindow.id, goosedClient);
|
goosedClients.set(mainWindow.id, goosedClient);
|
||||||
|
|
||||||
console.log('[Main] Waiting for backend server to be ready...');
|
const serverReady = await checkServerStatus(goosedClient, errorLog);
|
||||||
const serverReady = await checkServerStatus(goosedClient);
|
|
||||||
if (!serverReady) {
|
if (!serverReady) {
|
||||||
throw new Error('Backend server failed to start in time');
|
dialog.showMessageBoxSync({
|
||||||
|
type: 'error',
|
||||||
|
title: 'Goose Failed to Start',
|
||||||
|
message: 'The backend server failed to start.',
|
||||||
|
detail: errorLog.join('\n'),
|
||||||
|
buttons: ['OK'],
|
||||||
|
});
|
||||||
|
app.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let windowStateKeeper manage the window
|
// Let windowStateKeeper manage the window
|
||||||
mainWindowState.manage(mainWindow);
|
mainWindowState.manage(mainWindow);
|
||||||
|
|
||||||
// Enable spellcheck / right and ctrl + click on mispelled word
|
|
||||||
//
|
|
||||||
// NOTE: We could use webContents.session.availableSpellCheckerLanguages to include
|
|
||||||
// all languages in the list of spell checked words, but it diminishes the times you
|
|
||||||
// get red squigglies back for mispelled english words. Given the rest of Goose only
|
|
||||||
// renders in english right now, this feels like the correct set of language codes
|
|
||||||
// for the moment.
|
|
||||||
//
|
|
||||||
mainWindow.webContents.session.setSpellCheckerLanguages(['en-US', 'en-GB']);
|
mainWindow.webContents.session.setSpellCheckerLanguages(['en-US', 'en-GB']);
|
||||||
mainWindow.webContents.on('context-menu', (_event, params) => {
|
mainWindow.webContents.on('context-menu', (_event, params) => {
|
||||||
const menu = new Menu();
|
const menu = new Menu();
|
||||||
@@ -1167,22 +1145,6 @@ ipcMain.handle('get-goosed-host-port', async (event) => {
|
|||||||
return client.getConfig().baseUrl || null;
|
return client.getConfig().baseUrl || null;
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('set-scheduling-engine', async (_event, engine: string) => {
|
|
||||||
try {
|
|
||||||
const settings = loadSettings();
|
|
||||||
settings.schedulingEngine = engine as SchedulingEngine;
|
|
||||||
saveSettings(settings);
|
|
||||||
|
|
||||||
// Update the environment variable immediately
|
|
||||||
updateSchedulingEngineEnvironment(settings.schedulingEngine);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error setting scheduling engine:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle menu bar icon visibility
|
// Handle menu bar icon visibility
|
||||||
ipcMain.handle('set-menu-bar-icon', async (_event, show: boolean) => {
|
ipcMain.handle('set-menu-bar-icon', async (_event, show: boolean) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ type ElectronAPI = {
|
|||||||
getSettings: () => Promise<unknown | null>;
|
getSettings: () => Promise<unknown | null>;
|
||||||
getSecretKey: () => Promise<string>;
|
getSecretKey: () => Promise<string>;
|
||||||
getGoosedHostPort: () => Promise<string | null>;
|
getGoosedHostPort: () => Promise<string | null>;
|
||||||
setSchedulingEngine: (engine: string) => Promise<boolean>;
|
|
||||||
setWakelock: (enable: boolean) => Promise<boolean>;
|
setWakelock: (enable: boolean) => Promise<boolean>;
|
||||||
getWakelockState: () => Promise<boolean>;
|
getWakelockState: () => Promise<boolean>;
|
||||||
openNotificationsSettings: () => Promise<boolean>;
|
openNotificationsSettings: () => Promise<boolean>;
|
||||||
@@ -186,7 +185,6 @@ const electronAPI: ElectronAPI = {
|
|||||||
getSettings: () => ipcRenderer.invoke('get-settings'),
|
getSettings: () => ipcRenderer.invoke('get-settings'),
|
||||||
getSecretKey: () => ipcRenderer.invoke('get-secret-key'),
|
getSecretKey: () => ipcRenderer.invoke('get-secret-key'),
|
||||||
getGoosedHostPort: () => ipcRenderer.invoke('get-goosed-host-port'),
|
getGoosedHostPort: () => ipcRenderer.invoke('get-goosed-host-port'),
|
||||||
setSchedulingEngine: (engine: string) => ipcRenderer.invoke('set-scheduling-engine', engine),
|
|
||||||
setWakelock: (enable: boolean) => ipcRenderer.invoke('set-wakelock', enable),
|
setWakelock: (enable: boolean) => ipcRenderer.invoke('set-wakelock', enable),
|
||||||
getWakelockState: () => ipcRenderer.invoke('get-wakelock-state'),
|
getWakelockState: () => ipcRenderer.invoke('get-wakelock-state'),
|
||||||
openNotificationsSettings: () => ipcRenderer.invoke('open-notifications-settings'),
|
openNotificationsSettings: () => ipcRenderer.invoke('open-notifications-settings'),
|
||||||
|
|||||||
@@ -2,23 +2,18 @@ import { app } from 'electron';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
// Types
|
|
||||||
export interface EnvToggles {
|
export interface EnvToggles {
|
||||||
GOOSE_SERVER__MEMORY: boolean;
|
GOOSE_SERVER__MEMORY: boolean;
|
||||||
GOOSE_SERVER__COMPUTER_CONTROLLER: boolean;
|
GOOSE_SERVER__COMPUTER_CONTROLLER: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SchedulingEngine = 'builtin-cron' | 'temporal';
|
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
envToggles: EnvToggles;
|
envToggles: EnvToggles;
|
||||||
showMenuBarIcon: boolean;
|
showMenuBarIcon: boolean;
|
||||||
showDockIcon: boolean;
|
showDockIcon: boolean;
|
||||||
schedulingEngine: SchedulingEngine;
|
|
||||||
enableWakelock: boolean;
|
enableWakelock: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constants
|
|
||||||
const SETTINGS_FILE = path.join(app.getPath('userData'), 'settings.json');
|
const SETTINGS_FILE = path.join(app.getPath('userData'), 'settings.json');
|
||||||
|
|
||||||
const defaultSettings: Settings = {
|
const defaultSettings: Settings = {
|
||||||
@@ -28,7 +23,6 @@ const defaultSettings: Settings = {
|
|||||||
},
|
},
|
||||||
showMenuBarIcon: true,
|
showMenuBarIcon: true,
|
||||||
showDockIcon: true,
|
showDockIcon: true,
|
||||||
schedulingEngine: 'builtin-cron',
|
|
||||||
enableWakelock: false,
|
enableWakelock: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,7 +47,6 @@ export function saveSettings(settings: Settings): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Environment management
|
|
||||||
export function updateEnvironmentVariables(envToggles: EnvToggles): void {
|
export function updateEnvironmentVariables(envToggles: EnvToggles): void {
|
||||||
if (envToggles.GOOSE_SERVER__MEMORY) {
|
if (envToggles.GOOSE_SERVER__MEMORY) {
|
||||||
process.env.GOOSE_SERVER__MEMORY = 'true';
|
process.env.GOOSE_SERVER__MEMORY = 'true';
|
||||||
@@ -67,12 +60,3 @@ export function updateEnvironmentVariables(envToggles: EnvToggles): void {
|
|||||||
delete process.env.GOOSE_SERVER__COMPUTER_CONTROLLER;
|
delete process.env.GOOSE_SERVER__COMPUTER_CONTROLLER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateSchedulingEngineEnvironment(schedulingEngine: SchedulingEngine): void {
|
|
||||||
// Set GOOSE_SCHEDULER_TYPE based on the scheduling engine setting
|
|
||||||
if (schedulingEngine === 'temporal') {
|
|
||||||
process.env.GOOSE_SCHEDULER_TYPE = 'temporal';
|
|
||||||
} else {
|
|
||||||
process.env.GOOSE_SCHEDULER_TYPE = 'legacy';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user