using System.Diagnostics; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.Threading; using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Services; using LanMountainDesktop.Launcher.Services.AirApp; using LanMountainDesktop.Launcher.Services.Ipc; using LanMountainDesktop.Launcher.Views; using LanMountainDesktop.Shared.Contracts.Launcher; using LanMountainDesktop.Shared.IPC; using LanMountainDesktop.Shared.IPC.Abstractions.Services; namespace LanMountainDesktop.Launcher; public partial class App : Application { public override void Initialize() { if (Design.IsDesignMode) { AvaloniaXamlLoader.Load(this); return; } Logger.Initialize(); var context = LauncherRuntimeContext.Current; var execution = LauncherExecutionContext.Capture(); Logger.Info( $"Launcher App initialize. Command='{context.Command}'; IsGuiMode={context.IsGuiCommand}; " + $"IsPreview={context.IsPreviewCommand}; IsDebugMode={context.IsDebugMode}; " + $"LaunchSource='{context.LaunchSource}'; IsElevated={execution.IsElevated}; " + $"UserSid='{execution.UserSid ?? string.Empty}'; ExplicitAppRoot='{context.ExplicitAppRoot ?? ""}'."); AvaloniaXamlLoader.Load(this); } public override void OnFrameworkInitializationCompleted() { if (Design.IsDesignMode) { base.OnFrameworkInitializationCompleted(); return; } if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown; var context = LauncherRuntimeContext.Current; var execution = LauncherExecutionContext.Capture(); Logger.Info( $"Framework initialization completed. Command='{context.Command}'; IsPreview={context.IsPreviewCommand}; " + $"IsDebugMode={context.IsDebugMode}; LaunchSource='{context.LaunchSource}'; " + $"IsElevated={execution.IsElevated}; UserSid='{execution.UserSid ?? string.Empty}'."); if (HandlePreviewCommand(context, desktop)) { base.OnFrameworkInitializationCompleted(); return; } if (context.IsAirAppBrokerCommand) { _ = RunAirAppBrokerAsync(desktop, context); base.OnFrameworkInitializationCompleted(); return; } // 调试模式:只显示 DevDebugWindow,不走正常启动流程 // 避免启动主程序后 Launcher 自动退出,导致开发者无法预览 UI if (context.IsDebugMode && !context.IsPreviewCommand && !string.Equals(context.Command, "apply-update", StringComparison.OrdinalIgnoreCase)) { Logger.Info("Debug mode active — showing DevDebugWindow instead of normal launch flow."); var devDebugWindow = new DevDebugWindow(); devDebugWindow.Show(); base.OnFrameworkInitializationCompleted(); return; } if (string.Equals(context.Command, "apply-update", StringComparison.OrdinalIgnoreCase)) { var updateWindow = new UpdateWindow(); updateWindow.Show(); _ = RunApplyUpdateWithWindowAsync(desktop, context, updateWindow); } else { var splashWindow = CreateSplashWindow(); splashWindow.Show(); _ = RunCoordinatorWithSplashAsync(desktop, context, splashWindow); } } base.OnFrameworkInitializationCompleted(); } private static async Task RunAirAppBrokerAsync( IClassicDesktopStyleApplicationLifetime desktop, CommandContext context) { var appRoot = Commands.ResolveAppRoot(context); var requesterPid = context.GetIntOption("requester-pid", 0); var dataLocationResolver = new DataLocationResolver(appRoot); Logger.Info($"Air APP broker starting. AppRoot='{appRoot}'; RequesterPid={requesterPid}."); using var airAppIpcHost = new LauncherAirAppLifecycleIpcHost( new LauncherAirAppLifecycleService( new AirAppProcessStarter( new AirAppHostLocator(), () => appRoot, () => null, () => dataLocationResolver.ResolveDataRoot()))); airAppIpcHost.Start(); await WaitForAirAppBrokerExitAsync(requesterPid, airAppIpcHost.LifecycleService).ConfigureAwait(false); Logger.Info("Air APP broker exiting."); await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(0), DispatcherPriority.Background); } internal static async Task WaitForAirAppBrokerExitAsync( int requesterPid, LauncherAirAppLifecycleService airAppLifecycleService) { while (ShouldKeepAirAppBrokerAlive(requesterPid, airAppLifecycleService)) { await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); } } internal static bool ShouldKeepAirAppBrokerAlive( int requesterPid, LauncherAirAppLifecycleService airAppLifecycleService) { return TryGetLiveProcess(requesterPid) || airAppLifecycleService.HasLiveAirApps(); } private bool HandlePreviewCommand(CommandContext context, IClassicDesktopStyleApplicationLifetime desktop) { switch (context.Command.ToLowerInvariant()) { case "preview-splash": { Logger.Info("Preview command: splash."); var splashWindow = CreateSplashWindow(); splashWindow.SetDebugMode(true); splashWindow.Show(); _ = SimulateSplashPreviewAsync(desktop, splashWindow); return true; } case "preview-error": { Logger.Info("Preview command: error."); var errorWindow = new ErrorWindow(); errorWindow.SetErrorMessage("[Preview] This is the launcher error window preview."); errorWindow.Show(); _ = WaitForWindowCloseAsync(desktop, errorWindow); return true; } case "preview-multi-instance": { Logger.Info("Preview command: multi-instance prompt."); var promptWindow = new MultiInstancePromptWindow(); promptWindow.SetDetails(Environment.ProcessId, "ForegroundDesktop"); promptWindow.Show(); _ = WaitForWindowCloseAsync(desktop, promptWindow); return true; } case "preview-update": { Logger.Info("Preview command: update."); var updateWindow = new UpdateWindow(); updateWindow.SetDebugMode(true); updateWindow.Show(); _ = SimulateUpdatePreviewAsync(desktop, updateWindow); return true; } case "preview-oobe": { Logger.Info("Preview command: oobe."); var oobeWindow = new OobeWindow(); oobeWindow.Show(); _ = SimulateOobePreviewAsync(desktop, oobeWindow); return true; } case "preview-debug": { Logger.Info("Preview command: debug window."); var devDebugWindow = new DevDebugWindow(); devDebugWindow.Show(); return true; } default: return false; } } private static SplashWindow CreateSplashWindow() { var window = new SplashWindow(); TrySetSplashVersionInfo(window, LauncherRuntimeContext.Current); return window; } private static void TrySetSplashVersionInfo(SplashWindow window, CommandContext context) { try { var appRoot = Commands.ResolveAppRoot(context); var versionInfo = new DeploymentLocator(appRoot).GetVersionInfo(); window.SetVersionInfo(versionInfo.Version, versionInfo.Codename); } catch (Exception ex) { Logger.Warn($"Failed to set splash version info before coordinator start: {ex.Message}"); } } private async Task SimulateSplashPreviewAsync(IClassicDesktopStyleApplicationLifetime desktop, SplashWindow window) { var stages = new[] { "initializing", "update", "plugins", "launch", "ready" }; var messages = new[] { "Initializing...", "Checking updates...", "Checking plugins...", "Launching host...", "Ready" }; var reporter = (ISplashStageReporter)window; for (var i = 0; i < stages.Length; i++) { reporter.Report(stages[i], messages[i]); await Task.Delay(800).ConfigureAwait(false); } await Task.Delay(5000).ConfigureAwait(false); await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(0)); } private async Task SimulateUpdatePreviewAsync(IClassicDesktopStyleApplicationLifetime desktop, UpdateWindow window) { var stages = new[] { "verify", "extract", "apply", "plugins", "cleanup" }; for (var i = 0; i < stages.Length; i++) { window.Report(stages[i], $"Processing {stages[i]}...", (i + 1) * 20); await Task.Delay(600).ConfigureAwait(false); } window.ReportComplete(true, null); await Task.Delay(3000).ConfigureAwait(false); await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(0)); } private async Task SimulateOobePreviewAsync(IClassicDesktopStyleApplicationLifetime desktop, OobeWindow window) { try { await window.WaitForEnterAsync().ConfigureAwait(false); Logger.Info("OOBE preview completed by user."); } catch (Exception ex) { Logger.Error("OOBE preview failed.", ex); } await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(0)); } private async Task WaitForWindowCloseAsync(IClassicDesktopStyleApplicationLifetime desktop, Window window) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); window.Closed += (_, _) => tcs.TrySetResult(); await tcs.Task.ConfigureAwait(false); await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(0)); } private static async Task RunCoordinatorWithSplashAsync( IClassicDesktopStyleApplicationLifetime desktop, CommandContext context, SplashWindow splashWindow) { LauncherResult result; SplashWindow? currentSplashWindow = splashWindow; var appRoot = Commands.ResolveAppRoot(context); var dataLocationResolver = new DataLocationResolver(appRoot); var startupAttemptRegistry = new StartupAttemptRegistry(); var coordinatorPipeName = LauncherCoordinatorIpcServer.CreatePipeName(); var successPolicy = LauncherFlowCoordinator.ResolveSuccessPolicyKey(context); if (!startupAttemptRegistry.TryReserveCoordinator( context.LaunchSource, successPolicy, coordinatorPipeName, out var reservedAttempt, out var activeCoordinatorAttempt)) { result = await AttachToExistingCoordinatorAsync( context, currentSplashWindow, activeCoordinatorAttempt).ConfigureAwait(false); Logger.Info($"Secondary launcher completed. Success={result.Success}; Code='{result.Code}'."); await WriteLauncherResultAsync(context, result).ConfigureAwait(false); Environment.ExitCode = result.Success ? 0 : 1; await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(Environment.ExitCode), DispatcherPriority.Background); return; } using var airAppIpcHost = new LauncherAirAppLifecycleIpcHost( new LauncherAirAppLifecycleService( new AirAppProcessStarter( new AirAppHostLocator(), () => appRoot, () => null, () => dataLocationResolver.ResolveDataRoot()))); airAppIpcHost.Start(); using var coordinatorServer = new LauncherCoordinatorIpcServer( coordinatorPipeName, BuildCoordinatorStatusFromAttempt(reservedAttempt), HandleCoordinatorRequestAsync, startupAttemptRegistry.UpdateOwnedCoordinatorHeartbeat); coordinatorServer.Start(); while (true) { try { Logger.Info( $"Coordinator start. Command='{context.Command}'; AppRoot='{appRoot}'; " + $"IsDebugMode={context.IsDebugMode}; LaunchSource='{context.LaunchSource}'; " + $"ResultPath='{context.GetOption("result") ?? ""}'."); var deploymentLocator = new DeploymentLocator(appRoot); var coordinator = new LauncherFlowCoordinator( context, deploymentLocator, new OobeStateService(appRoot), new UpdateEngineService(deploymentLocator), new PluginInstallerService(), startupAttemptRegistry, coordinatorServer); result = await coordinator.RunAsync(currentSplashWindow).ConfigureAwait(false); } catch (Exception ex) { Logger.Error("Coordinator threw an unhandled exception.", ex); result = new LauncherResult { Success = false, Stage = "launch", Code = "exception", Message = $"Launcher failed: {ex.Message}", ErrorMessage = ex.ToString() }; } if (result.Success || result.Code == "host_not_found" || (!string.Equals(result.Stage, "launch", StringComparison.OrdinalIgnoreCase) && !string.Equals(result.Stage, "launchHost", StringComparison.OrdinalIgnoreCase))) { break; } var failureAction = await ShowFailureWindowAsync(result).ConfigureAwait(false); if (failureAction == ErrorWindowResult.Exit) { break; } if (failureAction == ErrorWindowResult.ActivateExisting && await TryActivateExistingInstanceAsync().ConfigureAwait(false)) { result = new LauncherResult { Success = true, Stage = "launch", Code = "activation_requested", Message = "Launcher activated the existing desktop instance.", Details = result.Details }; break; } currentSplashWindow = CreateSplashWindow(); currentSplashWindow.Show(); } Logger.Info($"Coordinator completed. Success={result.Success}; Stage='{result.Stage}'; Code='{result.Code}'."); await WriteLauncherResultAsync(context, result).ConfigureAwait(false); Environment.ExitCode = result.Success ? 0 : 1; if (result.Success) { var hostPid = ResolveManagedHostPid(result, startupAttemptRegistry.GetOwnedAttempt()?.HostPid ?? 0); await WaitForManagedProcessesToExitAsync(hostPid, airAppIpcHost.LifecycleService).ConfigureAwait(false); } await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(Environment.ExitCode), DispatcherPriority.Background); } private static int ResolveManagedHostPid(LauncherResult result, int fallbackHostPid) { if (result.Details.TryGetValue("hostPid", out var hostPidText) && int.TryParse(hostPidText, out var hostPid)) { return hostPid; } if (result.Details.TryGetValue("existingHostPid", out var existingHostPidText) && int.TryParse(existingHostPidText, out var existingHostPid)) { return existingHostPid; } return fallbackHostPid; } private static async Task WaitForManagedProcessesToExitAsync( int hostPid, LauncherAirAppLifecycleService airAppLifecycleService) { Logger.Info($"Launcher entering managed background lifetime. HostPid={hostPid}."); while (TryGetLiveProcess(hostPid) || airAppLifecycleService.HasLiveAirApps()) { await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); } Logger.Info("Launcher managed background lifetime completed; no host or Air APP process remains."); } private static async Task AttachToExistingCoordinatorAsync( CommandContext context, SplashWindow? splashWindow, StartupAttemptRecord? activeCoordinatorAttempt) { var reporter = splashWindow as ISplashStageReporter; reporter?.Report("activation", "Connecting to the active launcher..."); if (activeCoordinatorAttempt is not null && !string.IsNullOrWhiteSpace(activeCoordinatorAttempt.CoordinatorPipeName)) { var command = string.Equals(context.LaunchSource, "restart", StringComparison.OrdinalIgnoreCase) ? LauncherCoordinatorCommands.Attach : LauncherCoordinatorCommands.ActivateDesktop; var request = new LauncherCoordinatorRequest { Command = command, LaunchSource = context.LaunchSource, SuccessPolicy = LauncherFlowCoordinator.ResolveSuccessPolicyKey(context) }; var response = await new LauncherCoordinatorIpcClient() .SendAsync(activeCoordinatorAttempt.CoordinatorPipeName, request, TimeSpan.FromSeconds(2)) .ConfigureAwait(false); if (response is not null) { reporter?.Report("activation", response.Message); await DismissSplashIfNeededAsync(splashWindow).ConfigureAwait(false); var success = response.Accepted || IsRecoverableActivationFailure(response.ActivationResult, response.Status); return new LauncherResult { Success = success, Stage = "launch", Code = success && !response.Accepted ? "attached_to_launcher_coordinator" : response.Code, Message = success && !response.Accepted ? "Attached to the active Launcher coordinator; desktop startup is still in progress." : response.Message, Details = BuildCoordinatorResultDetails(response.Status, response.ActivationResult) }; } } var activation = await TryActivateExistingInstanceWithStatusAsync(TimeSpan.FromSeconds(2)).ConfigureAwait(false); if (activation is not null) { reporter?.Report("activation", activation.Message); await DismissSplashIfNeededAsync(splashWindow).ConfigureAwait(false); var success = activation.Accepted || IsRecoverableActivationFailure(activation, null); return new LauncherResult { Success = success, Stage = "launch", Code = activation.Accepted ? "existing_host_activated" : success ? "existing_host_startup_pending" : "existing_host_activation_failed", Message = success && !activation.Accepted ? "Existing desktop process is still starting; Launcher attached without starting another process." : activation.Message, Details = BuildCoordinatorResultDetails(null, activation) }; } await DismissSplashIfNeededAsync(splashWindow).ConfigureAwait(false); return new LauncherResult { Success = false, Stage = "launch", Code = "launcher_coordinator_unavailable", Message = "Another Launcher is coordinating startup, but it did not respond in time.", Details = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["activeCoordinatorPid"] = activeCoordinatorAttempt?.CoordinatorPid.ToString() ?? string.Empty, ["activeCoordinatorPipeName"] = activeCoordinatorAttempt?.CoordinatorPipeName ?? string.Empty, ["activeAttemptId"] = activeCoordinatorAttempt?.AttemptId ?? string.Empty, ["activeHostPid"] = activeCoordinatorAttempt?.HostPid.ToString() ?? string.Empty } }; } private static async Task HandleCoordinatorRequestAsync( LauncherCoordinatorRequest request, LauncherCoordinatorStatus status) { if (string.Equals(request.Command, LauncherCoordinatorCommands.ActivateDesktop, StringComparison.OrdinalIgnoreCase)) { var activation = await TryActivateExistingInstanceWithStatusAsync(TimeSpan.FromSeconds(2)).ConfigureAwait(false); if (activation is not null) { if (!activation.Accepted && IsRecoverableActivationFailure(activation, status)) { return new LauncherCoordinatorResponse { Accepted = true, Code = "attached_to_launcher_coordinator", Message = "Attached to the active Launcher coordinator; desktop startup is still in progress.", Status = status, ActivationResult = activation }; } return new LauncherCoordinatorResponse { Accepted = activation.Accepted, Code = activation.Accepted ? "existing_host_activated" : "existing_host_activation_failed", Message = activation.Message, Status = status, ActivationResult = activation }; } return new LauncherCoordinatorResponse { Accepted = true, Code = "attached_to_launcher_coordinator", Message = "Attached to the active Launcher coordinator; desktop startup is still in progress.", Status = status }; } return new LauncherCoordinatorResponse { Accepted = true, Code = "attached_to_launcher_coordinator", Message = "Attached to the active Launcher coordinator.", Status = status }; } private static LauncherCoordinatorStatus BuildCoordinatorStatusFromAttempt(StartupAttemptRecord attempt) { return new LauncherCoordinatorStatus { AttemptId = attempt.AttemptId, CoordinatorPid = Environment.ProcessId, HostPid = attempt.HostPid, HostProcessAlive = TryGetLiveProcess(attempt.HostPid), LaunchSource = attempt.LaunchSource, SuccessPolicy = attempt.SuccessPolicy, LastObservedStage = attempt.LastObservedStage, LastObservedMessage = attempt.LastObservedMessage, PublicIpcConnected = attempt.PublicIpcConnected || attempt.IpcConnected, State = attempt.State.ToString(), SoftTimeoutShown = attempt.State is StartupAttemptState.SoftTimeout or StartupAttemptState.DetachedWaiting, Completed = attempt.State is StartupAttemptState.Succeeded or StartupAttemptState.Failed, Succeeded = attempt.State == StartupAttemptState.Succeeded, UpdatedAtUtc = attempt.UpdatedAtUtc }; } private static bool IsRecoverableActivationFailure( PublicShellActivationResult? activation, LauncherCoordinatorStatus? status) { if (activation is { Accepted: true }) { return false; } if (status is { Completed: false, HostProcessAlive: true }) { return true; } var shellStatus = activation?.Status; if (shellStatus is null || !shellStatus.PublicIpcReady) { return false; } return !shellStatus.MainWindowOpened || !shellStatus.DesktopVisible || string.Equals(activation?.Code, "shell_not_ready", StringComparison.OrdinalIgnoreCase) || string.Equals(activation?.Code, "startup_pending", StringComparison.OrdinalIgnoreCase); } private static Dictionary BuildCoordinatorResultDetails( LauncherCoordinatorStatus? status, PublicShellActivationResult? activation) { var details = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["coordinatorPid"] = status?.CoordinatorPid.ToString() ?? string.Empty, ["coordinatorAttemptId"] = status?.AttemptId ?? string.Empty, ["hostPid"] = status?.HostPid.ToString() ?? activation?.Status.ProcessId.ToString() ?? string.Empty, ["hostProcessAlive"] = status?.HostProcessAlive.ToString() ?? string.Empty, ["publicIpcConnected"] = (status?.PublicIpcConnected ?? activation is not null).ToString(), ["startupStage"] = status?.LastObservedStage.ToString() ?? string.Empty, ["startupState"] = status?.State ?? string.Empty, ["activationAccepted"] = activation?.Accepted.ToString() ?? string.Empty, ["shellState"] = activation?.Status.ShellState ?? status?.ShellStatus?.ShellState ?? string.Empty, ["trayState"] = activation?.Status.Tray.State ?? status?.ShellStatus?.Tray.State ?? string.Empty, ["taskbarUsable"] = activation?.Status.Taskbar.IsUsable.ToString() ?? status?.ShellStatus?.Taskbar.IsUsable.ToString() ?? string.Empty }; return details; } private static async Task DismissSplashIfNeededAsync(SplashWindow? splashWindow) { if (splashWindow is null) { return; } try { await splashWindow.DismissAsync().ConfigureAwait(false); } catch (Exception ex) { Logger.Warn($"Failed to dismiss splash after coordinator attach: {ex.Message}"); } } private static async Task WriteLauncherResultAsync(CommandContext context, LauncherResult result) { var resultPath = context.GetOption("result"); if (string.IsNullOrWhiteSpace(resultPath)) { return; } try { await Commands.WriteResultIfNeededAsync(resultPath, result).ConfigureAwait(false); Logger.Info($"Launcher result written to '{Path.GetFullPath(resultPath)}'."); } catch (Exception ex) { Logger.Error($"Failed to write launcher result to '{resultPath}'.", ex); } } private static async Task ShowFailureWindowAsync(LauncherResult result) { ErrorWindow? errorWindow = null; var hostProcessAlive = result.Details.TryGetValue("hostProcessAlive", out var hostProcessAliveText) && bool.TryParse(hostProcessAliveText, out var hostProcessAliveValue) && hostProcessAliveValue; var hostPid = result.Details.TryGetValue("hostPid", out var hostPidText) && int.TryParse(hostPidText, out var parsedPid) ? parsedPid : (int?)null; await Dispatcher.UIThread.InvokeAsync(() => { try { errorWindow = new ErrorWindow(); if (hostProcessAlive) { errorWindow.ConfigureForRunningHostFailure(hostPid); } else { errorWindow.ConfigureForGenericFailure(allowRetry: true); } errorWindow.SetErrorMessage( $"Failed to start LanMountainDesktop.\n\nStage: {result.Stage}\nCode: {result.Code}\n\n{result.Message}"); errorWindow.Show(); } catch (Exception ex) { Logger.Error("Failed to show launcher failure window.", ex); } }); if (errorWindow is null) { return ErrorWindowResult.Exit; } try { return await errorWindow.WaitForChoiceAsync().ConfigureAwait(false); } catch (Exception ex) { Logger.Error("Failure window closed unexpectedly.", ex); return ErrorWindowResult.Exit; } } private static async Task TryActivateExistingInstanceAsync() { var activation = await TryActivateExistingInstanceWithStatusAsync(TimeSpan.FromSeconds(5)).ConfigureAwait(false); return activation?.Accepted == true; } private static async Task TryActivateExistingInstanceWithStatusAsync(TimeSpan timeout) { try { using var ipcClient = new LanMountainDesktopIpcClient(); var connectTask = ipcClient.ConnectAsync(); var completedTask = await Task.WhenAny(connectTask, Task.Delay(timeout)).ConfigureAwait(false); if (completedTask != connectTask) { return null; } await connectTask.ConfigureAwait(false); if (!ipcClient.IsConnected) { return null; } var shellProxy = ipcClient.CreateProxy(); var activationTask = shellProxy.ActivateMainWindowWithStatusAsync(); completedTask = await Task.WhenAny(activationTask, Task.Delay(timeout)).ConfigureAwait(false); if (completedTask != activationTask) { return null; } return await activationTask.ConfigureAwait(false); } catch (Exception ex) { Logger.Warn($"Failed to activate the existing desktop instance: {ex.Message}"); return null; } } private static bool TryGetLiveProcess(int processId) { if (processId <= 0) { return false; } try { using var process = Process.GetProcessById(processId); return !process.HasExited; } catch { return false; } } private static async Task RunApplyUpdateWithWindowAsync( IClassicDesktopStyleApplicationLifetime desktop, CommandContext context, UpdateWindow window) { var appRoot = Commands.ResolveAppRoot(context); var deploymentLocator = new DeploymentLocator(appRoot); var updateEngine = new UpdateEngineService(deploymentLocator); var pluginInstaller = new PluginInstallerService(); var pluginUpgrades = new PluginUpgradeQueueService(pluginInstaller); var success = true; string? errorMessage = null; try { await Dispatcher.UIThread.InvokeAsync(() => window.Report("verify", "Verifying update...", 10)); var updateResult = await updateEngine.ApplyPendingUpdateAsync().ConfigureAwait(false); if (!updateResult.Success) { success = false; errorMessage = updateResult.Message; } if (success) { await Dispatcher.UIThread.InvokeAsync(() => window.Report("plugins", "Applying plugin upgrades...", 60)); var pluginsDir = context.GetOption("plugins-dir") ?? Path.Combine(appRoot, "plugins"); var queueResult = pluginUpgrades.ApplyPendingUpgrades(pluginsDir); if (!queueResult.Success && queueResult.Code != "noop") { Logger.Error($"Plugin upgrade failed during apply-update: {queueResult.Message}"); } } if (success) { await Dispatcher.UIThread.InvokeAsync(() => window.Report("cleanup", "Cleaning up old deployments...", 90)); deploymentLocator.CleanupOldDeployments(minVersionsToKeep: 3); } } catch (Exception ex) { success = false; errorMessage = ex.Message; Logger.Error("Apply-update flow failed.", ex); } await Dispatcher.UIThread.InvokeAsync(() => window.ReportComplete(success, errorMessage)); await Task.Delay(success ? 1500 : 5000).ConfigureAwait(false); await Commands.WriteResultIfNeededAsync(context.GetOption("result"), new LauncherResult { Success = success, Stage = "apply-update", Code = success ? "ok" : "failed", Message = success ? "Update applied successfully." : (errorMessage ?? "Unknown error"), Details = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["command"] = context.Command, ["launchSource"] = context.LaunchSource } }).ConfigureAwait(false); Environment.ExitCode = success ? 0 : 1; await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(Environment.ExitCode), DispatcherPriority.Background); } }