diff --git a/crates/goose/src/temporal_scheduler.rs b/crates/goose/src/temporal_scheduler.rs index 8cc3bd5474e4..5f86f8d405f7 100644 --- a/crates/goose/src/temporal_scheduler.rs +++ b/crates/goose/src/temporal_scheduler.rs @@ -305,13 +305,33 @@ impl TemporalScheduler { // Set the PORT environment variable for the service to use and properly daemonize it // Create a new process group to ensure the service survives parent termination - let mut command = Command::new("./temporal-service"); + let mut command = Command::new(&binary_path); command .current_dir(working_dir) - .env("PORT", self.port_config.http_port.to_string()) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .stdin(std::process::Stdio::null()); + .env("PORT", self.port_config.http_port.to_string()); + + // Platform-specific process configuration based on Electron app approach + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + // On Windows, prevent console window and run detached: + // - Use CREATE_NO_WINDOW (0x08000000) to prevent console window + // - Use DETACHED_PROCESS (0x00000008) for independence + // - Redirect output to null to prevent console attachment + command + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .stdin(std::process::Stdio::null()) + .creation_flags(0x08000000 | 0x00000008); // CREATE_NO_WINDOW | DETACHED_PROCESS + } + + #[cfg(not(windows))] + { + command + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .stdin(std::process::Stdio::null()); + } // On Unix systems, create a new process group #[cfg(unix)] @@ -333,12 +353,27 @@ impl TemporalScheduler { pid, self.port_config.http_port ); - // Give the process a moment to start up - sleep(Duration::from_millis(100)).await; + // Platform-specific process handling + #[cfg(windows)] + { + // On Windows, wait longer for initialization and use unref-like behavior + sleep(Duration::from_millis(1000)).await; // Wait 1 second for Windows initialization + + // Use a different approach - spawn a monitoring thread that waits longer + std::thread::spawn(move || { + // Give the process significant time to initialize on Windows + std::thread::sleep(std::time::Duration::from_secs(5)); + // After 5 seconds, let it run completely independently + let _ = child.wait(); + }); + } - // Verify the process is still running #[cfg(unix)] { + // Give the process a moment to start up + sleep(Duration::from_millis(100)).await; + + // Verify the process is still running use std::process::Command as StdCommand; let ps_check = StdCommand::new("ps") .arg("-p") @@ -359,13 +394,13 @@ impl TemporalScheduler { warn!("Could not verify Temporal service process status: {}", e); } } - } - // Detach the child process by not waiting for it - // This allows it to continue running independently - std::thread::spawn(move || { - let _ = child.wait(); - }); + // Detach the child process by not waiting for it + // This allows it to continue running independently + std::thread::spawn(move || { + let _ = child.wait(); + }); + } Ok(()) } @@ -374,17 +409,16 @@ impl TemporalScheduler { // Try to find the Go service binary by looking for it relative to the current executable // or in common locations - let possible_paths = vec![ - // Relative to current working directory (original behavior) - "./temporal-service/temporal-service", - ]; - - // Also try to find it relative to the current executable path + // First try to find it relative to the current executable path (most common for bundled apps) if let Ok(exe_path) = std::env::current_exe() { if let Some(exe_dir) = exe_path.parent() { // Try various relative paths from the executable directory let exe_relative_paths = vec![ - // First check in the same directory as the executable (bundled location) + // First check in resources/bin subdirectory (bundled Electron app location) + exe_dir.join("resources/bin/temporal-service"), + exe_dir.join("resources/bin/temporal-service.exe"), // Windows version + exe_dir.join("resources\\bin\\temporal-service.exe"), // Windows with backslashes + // Then check in the same directory as the executable exe_dir.join("temporal-service"), exe_dir.join("temporal-service.exe"), // Windows version // Then check in temporal-service subdirectory @@ -399,21 +433,45 @@ impl TemporalScheduler { for path in exe_relative_paths { if path.exists() { + tracing::debug!("Found temporal-service binary at: {}", path.display()); return Ok(path.to_string_lossy().to_string()); } } } } - // Try the original relative paths + // Try relative to current working directory (original behavior) + let possible_paths = vec![ + "./temporal-service/temporal-service", + "./temporal-service.exe", // Windows in current dir + "./resources/bin/temporal-service.exe", // Windows bundled in current dir + ]; + for path in &possible_paths { if std::path::Path::new(path).exists() { + tracing::debug!("Found temporal-service binary at: {}", path); return Ok(path.to_string()); } } + // Check environment variable override + if let Ok(binary_path) = std::env::var("GOOSE_TEMPORAL_BIN") { + if std::path::Path::new(&binary_path).exists() { + tracing::info!( + "Using temporal-service binary from GOOSE_TEMPORAL_BIN: {}", + binary_path + ); + return Ok(binary_path); + } else { + tracing::warn!( + "GOOSE_TEMPORAL_BIN points to non-existent file: {}", + binary_path + ); + } + } + Err(SchedulerError::SchedulerInternalError( - "Go service binary not found. Tried paths relative to current executable and working directory. Please ensure the temporal-service binary is built and available.".to_string() + "Go service binary not found. Tried paths relative to current executable and working directory. Please ensure the temporal-service binary is built and available, or set GOOSE_TEMPORAL_BIN environment variable.".to_string() )) } diff --git a/temporal-service/main.go b/temporal-service/main.go index fcc1dc39cc57..f9ce2398b84d 100644 --- a/temporal-service/main.go +++ b/temporal-service/main.go @@ -331,12 +331,33 @@ func ensureTemporalServerRunning(ports *PortConfig) error { func main() { log.Println("Starting Temporal service...") + log.Printf("Runtime OS: %s", runtime.GOOS) + log.Printf("Runtime ARCH: %s", runtime.GOARCH) + + // Log current working directory for debugging + if cwd, err := os.Getwd(); err == nil { + log.Printf("Current working directory: %s", cwd) + } + + // Log environment variables that might affect behavior + if port := os.Getenv("PORT"); port != "" { + log.Printf("PORT environment variable: %s", port) + } + if rustLog := os.Getenv("RUST_LOG"); rustLog != "" { + log.Printf("RUST_LOG environment variable: %s", rustLog) + } + if temporalLog := os.Getenv("TEMPORAL_LOG_LEVEL"); temporalLog != "" { + log.Printf("TEMPORAL_LOG_LEVEL environment variable: %s", temporalLog) + } // Create Temporal service (this will find available ports automatically) + log.Println("Creating Temporal service...") service, err := NewTemporalService() if err != nil { + log.Printf("ERROR: Failed to create Temporal service: %v", err) log.Fatalf("Failed to create Temporal service: %v", err) } + log.Println("✓ Temporal service created successfully") // Use the dynamically assigned HTTP port httpPort := service.GetHTTPPort() diff --git a/temporal-service/temporal-service b/temporal-service/temporal-service index ed48911ce62b..2e7d5f0133d7 100755 Binary files a/temporal-service/temporal-service and b/temporal-service/temporal-service differ diff --git a/ui/desktop/src/goosed.ts b/ui/desktop/src/goosed.ts index 62ed3ed5b4c2..3b0b5e1cb2bd 100644 --- a/ui/desktop/src/goosed.ts +++ b/ui/desktop/src/goosed.ts @@ -27,9 +27,17 @@ export const findAvailablePort = (): Promise => { // Check if goosed server is ready by polling the status endpoint const checkServerStatus = async ( port: number, - maxAttempts: number = 80, + maxAttempts?: number, interval: number = 100 ): Promise => { + if (maxAttempts === undefined) { + const isTemporalEnabled = process.env.GOOSE_SCHEDULER_TYPE === 'temporal'; + maxAttempts = isTemporalEnabled ? 200 : 80; + log.info( + `Using ${maxAttempts} max attempts (temporal scheduling: ${isTemporalEnabled ? 'enabled' : 'disabled'})` + ); + } + const statusUrl = `http://127.0.0.1:${port}/status`; log.info(`Checking server status at ${statusUrl}`);