2026-04-23 09:45:05 +08:00
|
|
|
using System.Diagnostics;
|
2026-04-21 20:59:52 +08:00
|
|
|
using Avalonia;
|
|
|
|
|
using Avalonia.Controls;
|
|
|
|
|
using Avalonia.Controls.ApplicationLifetimes;
|
|
|
|
|
using Avalonia.Markup.Xaml;
|
|
|
|
|
using Avalonia.Threading;
|
|
|
|
|
using LanMountainDesktop.Launcher.Models;
|
|
|
|
|
using LanMountainDesktop.Launcher.Services;
|
2026-04-23 09:45:05 +08:00
|
|
|
using LanMountainDesktop.Launcher.Services.Ipc;
|
2026-04-21 20:59:52 +08:00
|
|
|
using LanMountainDesktop.Launcher.Views;
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
using LanMountainDesktop.Shared.Contracts.Launcher;
|
|
|
|
|
using LanMountainDesktop.Shared.IPC;
|
|
|
|
|
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
2026-04-21 20:59:52 +08:00
|
|
|
|
|
|
|
|
namespace LanMountainDesktop.Launcher;
|
|
|
|
|
|
|
|
|
|
public partial class App : Application
|
|
|
|
|
{
|
|
|
|
|
public override void Initialize()
|
|
|
|
|
{
|
|
|
|
|
Logger.Initialize();
|
2026-04-22 07:31:54 +08:00
|
|
|
var context = LauncherRuntimeContext.Current;
|
Harden OOBE, launch-source and elevation flow
Introduce a per-user OOBE state model and hardened launch/elevation handling. Adds OobeStateFile/OobeLaunchDecision models, OobeStateService (persisting %LOCALAPPDATA%/.launcher/state/oobe-state.json), and LauncherExecutionContext to capture elevation and user SID. CommandContext now normalizes/infers launch-source values (normal, postinstall, apply-update, plugin-install, debug-preview) and exposes maintenance checks. LauncherFlowCoordinator propagates richer launcher context details for diagnostics and suppresses OOBE for elevated/maintenance contexts. PluginInstallerService avoids requesting elevation for user-scoped installs and returns a clear error when installation target is outside the current user's LocalAppData. LauncherClient maps and surfaces result codes, UpdateWorkflow and installer invocation now pass explicit --launch-source values, and WelcomeOobeStep persists OOBE completion via the new service. Adds unit tests (CommandContext, OobeStateService, PluginInstallerService), docs/specs/checklists for the contract, and makes internals visible to tests.
2026-04-22 09:25:22 +08:00
|
|
|
var execution = LauncherExecutionContext.Capture();
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Info(
|
|
|
|
|
$"Launcher App initialize. Command='{context.Command}'; IsGuiMode={context.IsGuiCommand}; " +
|
|
|
|
|
$"IsPreview={context.IsPreviewCommand}; IsDebugMode={context.IsDebugMode}; " +
|
Harden OOBE, launch-source and elevation flow
Introduce a per-user OOBE state model and hardened launch/elevation handling. Adds OobeStateFile/OobeLaunchDecision models, OobeStateService (persisting %LOCALAPPDATA%/.launcher/state/oobe-state.json), and LauncherExecutionContext to capture elevation and user SID. CommandContext now normalizes/infers launch-source values (normal, postinstall, apply-update, plugin-install, debug-preview) and exposes maintenance checks. LauncherFlowCoordinator propagates richer launcher context details for diagnostics and suppresses OOBE for elevated/maintenance contexts. PluginInstallerService avoids requesting elevation for user-scoped installs and returns a clear error when installation target is outside the current user's LocalAppData. LauncherClient maps and surfaces result codes, UpdateWorkflow and installer invocation now pass explicit --launch-source values, and WelcomeOobeStep persists OOBE completion via the new service. Adds unit tests (CommandContext, OobeStateService, PluginInstallerService), docs/specs/checklists for the contract, and makes internals visible to tests.
2026-04-22 09:25:22 +08:00
|
|
|
$"LaunchSource='{context.LaunchSource}'; IsElevated={execution.IsElevated}; " +
|
|
|
|
|
$"UserSid='{execution.UserSid ?? string.Empty}'; ExplicitAppRoot='{context.ExplicitAppRoot ?? "<none>"}'.");
|
2026-04-22 07:31:54 +08:00
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
AvaloniaXamlLoader.Load(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnFrameworkInitializationCompleted()
|
|
|
|
|
{
|
|
|
|
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
2026-04-21 20:59:52 +08:00
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
var context = LauncherRuntimeContext.Current;
|
Harden OOBE, launch-source and elevation flow
Introduce a per-user OOBE state model and hardened launch/elevation handling. Adds OobeStateFile/OobeLaunchDecision models, OobeStateService (persisting %LOCALAPPDATA%/.launcher/state/oobe-state.json), and LauncherExecutionContext to capture elevation and user SID. CommandContext now normalizes/infers launch-source values (normal, postinstall, apply-update, plugin-install, debug-preview) and exposes maintenance checks. LauncherFlowCoordinator propagates richer launcher context details for diagnostics and suppresses OOBE for elevated/maintenance contexts. PluginInstallerService avoids requesting elevation for user-scoped installs and returns a clear error when installation target is outside the current user's LocalAppData. LauncherClient maps and surfaces result codes, UpdateWorkflow and installer invocation now pass explicit --launch-source values, and WelcomeOobeStep persists OOBE completion via the new service. Adds unit tests (CommandContext, OobeStateService, PluginInstallerService), docs/specs/checklists for the contract, and makes internals visible to tests.
2026-04-22 09:25:22 +08:00
|
|
|
var execution = LauncherExecutionContext.Capture();
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Info(
|
|
|
|
|
$"Framework initialization completed. Command='{context.Command}'; IsPreview={context.IsPreviewCommand}; " +
|
Harden OOBE, launch-source and elevation flow
Introduce a per-user OOBE state model and hardened launch/elevation handling. Adds OobeStateFile/OobeLaunchDecision models, OobeStateService (persisting %LOCALAPPDATA%/.launcher/state/oobe-state.json), and LauncherExecutionContext to capture elevation and user SID. CommandContext now normalizes/infers launch-source values (normal, postinstall, apply-update, plugin-install, debug-preview) and exposes maintenance checks. LauncherFlowCoordinator propagates richer launcher context details for diagnostics and suppresses OOBE for elevated/maintenance contexts. PluginInstallerService avoids requesting elevation for user-scoped installs and returns a clear error when installation target is outside the current user's LocalAppData. LauncherClient maps and surfaces result codes, UpdateWorkflow and installer invocation now pass explicit --launch-source values, and WelcomeOobeStep persists OOBE completion via the new service. Adds unit tests (CommandContext, OobeStateService, PluginInstallerService), docs/specs/checklists for the contract, and makes internals visible to tests.
2026-04-22 09:25:22 +08:00
|
|
|
$"IsDebugMode={context.IsDebugMode}; LaunchSource='{context.LaunchSource}'; " +
|
|
|
|
|
$"IsElevated={execution.IsElevated}; UserSid='{execution.UserSid ?? string.Empty}'.");
|
2026-04-21 20:59:52 +08:00
|
|
|
|
|
|
|
|
if (HandlePreviewCommand(context, desktop))
|
|
|
|
|
{
|
|
|
|
|
base.OnFrameworkInitializationCompleted();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (string.Equals(context.Command, "apply-update", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
var updateWindow = new UpdateWindow();
|
|
|
|
|
updateWindow.Show();
|
|
|
|
|
_ = RunApplyUpdateWithWindowAsync(desktop, context, updateWindow);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
var splashWindow = CreateSplashWindow();
|
2026-04-21 20:59:52 +08:00
|
|
|
splashWindow.Show();
|
|
|
|
|
_ = RunCoordinatorWithSplashAsync(desktop, context, splashWindow);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
base.OnFrameworkInitializationCompleted();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool HandlePreviewCommand(CommandContext context, IClassicDesktopStyleApplicationLifetime desktop)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
switch (context.Command.ToLowerInvariant())
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
|
|
|
|
case "preview-splash":
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
|
|
|
|
Logger.Info("Preview command: splash.");
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
var splashWindow = CreateSplashWindow();
|
2026-04-21 20:59:52 +08:00
|
|
|
splashWindow.SetDebugMode(true);
|
|
|
|
|
splashWindow.Show();
|
|
|
|
|
_ = SimulateSplashPreviewAsync(desktop, splashWindow);
|
|
|
|
|
return true;
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
2026-04-21 20:59:52 +08:00
|
|
|
case "preview-error":
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
|
|
|
|
Logger.Info("Preview command: error.");
|
2026-04-21 20:59:52 +08:00
|
|
|
var errorWindow = new ErrorWindow();
|
2026-04-22 07:31:54 +08:00
|
|
|
errorWindow.SetErrorMessage("[Preview] This is the launcher error window preview.");
|
2026-04-21 20:59:52 +08:00
|
|
|
errorWindow.Show();
|
|
|
|
|
_ = WaitForWindowCloseAsync(desktop, errorWindow);
|
|
|
|
|
return true;
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
2026-04-21 20:59:52 +08:00
|
|
|
case "preview-update":
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
|
|
|
|
Logger.Info("Preview command: update.");
|
2026-04-21 20:59:52 +08:00
|
|
|
var updateWindow = new UpdateWindow();
|
|
|
|
|
updateWindow.SetDebugMode(true);
|
|
|
|
|
updateWindow.Show();
|
|
|
|
|
_ = SimulateUpdatePreviewAsync(desktop, updateWindow);
|
|
|
|
|
return true;
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
2026-04-21 20:59:52 +08:00
|
|
|
case "preview-oobe":
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
|
|
|
|
Logger.Info("Preview command: oobe.");
|
2026-04-21 20:59:52 +08:00
|
|
|
var oobeWindow = new OobeWindow();
|
|
|
|
|
oobeWindow.Show();
|
|
|
|
|
_ = SimulateOobePreviewAsync(desktop, oobeWindow);
|
|
|
|
|
return true;
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
2026-04-21 20:59:52 +08:00
|
|
|
case "preview-debug":
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
|
|
|
|
Logger.Info("Preview command: debug window.");
|
2026-04-21 20:59:52 +08:00
|
|
|
var devDebugWindow = new DevDebugWindow();
|
|
|
|
|
devDebugWindow.Show();
|
|
|
|
|
return true;
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
2026-04-21 20:59:52 +08:00
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
private static SplashWindow CreateSplashWindow()
|
|
|
|
|
{
|
|
|
|
|
var preferences = StartupVisualPreferencesResolver.Resolve();
|
2026-04-23 19:04:39 +08:00
|
|
|
var window = new SplashWindow(preferences.Mode);
|
|
|
|
|
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}");
|
|
|
|
|
}
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
private async Task SimulateSplashPreviewAsync(IClassicDesktopStyleApplicationLifetime desktop, SplashWindow window)
|
|
|
|
|
{
|
|
|
|
|
var stages = new[] { "initializing", "update", "plugins", "launch", "ready" };
|
2026-04-22 07:31:54 +08:00
|
|
|
var messages = new[] { "Initializing...", "Checking updates...", "Checking plugins...", "Launching host...", "Ready" };
|
2026-04-21 20:59:52 +08:00
|
|
|
var reporter = (ISplashStageReporter)window;
|
2026-04-22 07:31:54 +08:00
|
|
|
|
|
|
|
|
for (var i = 0; i < stages.Length; i++)
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
|
|
|
|
reporter.Report(stages[i], messages[i]);
|
2026-04-22 07:31:54 +08:00
|
|
|
await Task.Delay(800).ConfigureAwait(false);
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
2026-04-22 07:31:54 +08:00
|
|
|
|
|
|
|
|
await Task.Delay(5000).ConfigureAwait(false);
|
2026-04-21 20:59:52 +08:00
|
|
|
await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task SimulateUpdatePreviewAsync(IClassicDesktopStyleApplicationLifetime desktop, UpdateWindow window)
|
|
|
|
|
{
|
|
|
|
|
var stages = new[] { "verify", "extract", "apply", "plugins", "cleanup" };
|
2026-04-22 07:31:54 +08:00
|
|
|
|
|
|
|
|
for (var i = 0; i < stages.Length; i++)
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
window.Report(stages[i], $"Processing {stages[i]}...", (i + 1) * 20);
|
|
|
|
|
await Task.Delay(600).ConfigureAwait(false);
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
2026-04-22 07:31:54 +08:00
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
window.ReportComplete(true, null);
|
2026-04-22 07:31:54 +08:00
|
|
|
await Task.Delay(3000).ConfigureAwait(false);
|
2026-04-21 20:59:52 +08:00
|
|
|
await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task SimulateOobePreviewAsync(IClassicDesktopStyleApplicationLifetime desktop, OobeWindow window)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
await window.WaitForEnterAsync().ConfigureAwait(false);
|
|
|
|
|
Logger.Info("OOBE preview completed by user.");
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Error("OOBE preview failed.", ex);
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
2026-04-22 07:31:54 +08:00
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task WaitForWindowCloseAsync(IClassicDesktopStyleApplicationLifetime desktop, Window window)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
|
|
window.Closed += (_, _) => tcs.TrySetResult();
|
|
|
|
|
await tcs.Task.ConfigureAwait(false);
|
2026-04-21 20:59:52 +08:00
|
|
|
await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(0));
|
|
|
|
|
}
|
2026-04-22 07:31:54 +08:00
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
private static async Task RunCoordinatorWithSplashAsync(
|
|
|
|
|
IClassicDesktopStyleApplicationLifetime desktop,
|
|
|
|
|
CommandContext context,
|
|
|
|
|
SplashWindow splashWindow)
|
|
|
|
|
{
|
|
|
|
|
LauncherResult result;
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
SplashWindow? currentSplashWindow = splashWindow;
|
|
|
|
|
var appRoot = Commands.ResolveAppRoot(context);
|
2026-04-23 09:45:05 +08:00
|
|
|
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 coordinatorServer = new LauncherCoordinatorIpcServer(
|
|
|
|
|
coordinatorPipeName,
|
|
|
|
|
BuildCoordinatorStatusFromAttempt(reservedAttempt),
|
|
|
|
|
HandleCoordinatorRequestAsync,
|
|
|
|
|
startupAttemptRegistry.UpdateOwnedCoordinatorHeartbeat);
|
|
|
|
|
coordinatorServer.Start();
|
2026-04-22 07:31:54 +08:00
|
|
|
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
while (true)
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Logger.Info(
|
|
|
|
|
$"Coordinator start. Command='{context.Command}'; AppRoot='{appRoot}'; " +
|
|
|
|
|
$"IsDebugMode={context.IsDebugMode}; LaunchSource='{context.LaunchSource}'; " +
|
|
|
|
|
$"ResultPath='{context.GetOption("result") ?? "<none>"}'.");
|
|
|
|
|
|
|
|
|
|
var deploymentLocator = new DeploymentLocator(appRoot);
|
|
|
|
|
var coordinator = new LauncherFlowCoordinator(
|
|
|
|
|
context,
|
|
|
|
|
deploymentLocator,
|
|
|
|
|
new OobeStateService(appRoot),
|
|
|
|
|
new UpdateEngineService(deploymentLocator),
|
2026-04-23 09:45:05 +08:00
|
|
|
new PluginInstallerService(),
|
|
|
|
|
startupAttemptRegistry,
|
|
|
|
|
coordinatorServer);
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-04-22 07:31:54 +08:00
|
|
|
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
if (result.Success ||
|
|
|
|
|
result.Code == "host_not_found" ||
|
|
|
|
|
(!string.Equals(result.Stage, "launch", StringComparison.OrdinalIgnoreCase) &&
|
|
|
|
|
!string.Equals(result.Stage, "launchHost", StringComparison.OrdinalIgnoreCase)))
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-04-21 20:59:52 +08:00
|
|
|
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
var failureAction = await ShowFailureWindowAsync(result).ConfigureAwait(false);
|
|
|
|
|
if (failureAction == ErrorWindowResult.Exit)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (failureAction == ErrorWindowResult.ActivateExisting &&
|
|
|
|
|
await TryActivateExistingInstanceAsync().ConfigureAwait(false))
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
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();
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(Environment.ExitCode), DispatcherPriority.Background);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:45:05 +08:00
|
|
|
private static async Task<LauncherResult> 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);
|
2026-04-23 19:04:39 +08:00
|
|
|
var success = response.Accepted ||
|
|
|
|
|
IsRecoverableActivationFailure(response.ActivationResult, response.Status);
|
2026-04-23 09:45:05 +08:00
|
|
|
return new LauncherResult
|
|
|
|
|
{
|
2026-04-23 19:04:39 +08:00
|
|
|
Success = success,
|
2026-04-23 09:45:05 +08:00
|
|
|
Stage = "launch",
|
2026-04-23 19:04:39 +08:00
|
|
|
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,
|
2026-04-23 09:45:05 +08:00
|
|
|
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);
|
2026-04-23 19:04:39 +08:00
|
|
|
var success = activation.Accepted || IsRecoverableActivationFailure(activation, null);
|
2026-04-23 09:45:05 +08:00
|
|
|
return new LauncherResult
|
|
|
|
|
{
|
2026-04-23 19:04:39 +08:00
|
|
|
Success = success,
|
2026-04-23 09:45:05 +08:00
|
|
|
Stage = "launch",
|
2026-04-23 19:04:39 +08:00
|
|
|
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,
|
2026-04-23 09:45:05 +08:00
|
|
|
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<string, string>(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<LauncherCoordinatorResponse> 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)
|
|
|
|
|
{
|
2026-04-23 19:04:39 +08:00
|
|
|
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
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:45:05 +08:00
|
|
|
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
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 19:04:39 +08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:45:05 +08:00
|
|
|
private static Dictionary<string, string> BuildCoordinatorResultDetails(
|
|
|
|
|
LauncherCoordinatorStatus? status,
|
|
|
|
|
PublicShellActivationResult? activation)
|
|
|
|
|
{
|
|
|
|
|
var details = new Dictionary<string, string>(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}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
private static async Task<ErrorWindowResult> ShowFailureWindowAsync(LauncherResult result)
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
|
|
|
|
ErrorWindow? errorWindow = null;
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
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;
|
2026-04-22 07:31:54 +08:00
|
|
|
|
|
|
|
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
|
|
|
|
{
|
2026-04-21 20:59:52 +08:00
|
|
|
try
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
errorWindow = new ErrorWindow();
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
if (hostProcessAlive)
|
|
|
|
|
{
|
|
|
|
|
errorWindow.ConfigureForRunningHostFailure(hostPid);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
errorWindow.ConfigureForGenericFailure(allowRetry: true);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
errorWindow.SetErrorMessage(
|
|
|
|
|
$"Failed to start LanMountainDesktop.\n\nStage: {result.Stage}\nCode: {result.Code}\n\n{result.Message}");
|
|
|
|
|
errorWindow.Show();
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
2026-04-22 07:31:54 +08:00
|
|
|
catch (Exception ex)
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Error("Failed to show launcher failure window.", ex);
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
2026-04-22 07:31:54 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (errorWindow is null)
|
|
|
|
|
{
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
return ErrorWindowResult.Exit;
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
return await errorWindow.WaitForChoiceAsync().ConfigureAwait(false);
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Logger.Error("Failure window closed unexpectedly.", ex);
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
return ErrorWindowResult.Exit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task<bool> TryActivateExistingInstanceAsync()
|
2026-04-23 09:45:05 +08:00
|
|
|
{
|
|
|
|
|
var activation = await TryActivateExistingInstanceWithStatusAsync(TimeSpan.FromSeconds(5)).ConfigureAwait(false);
|
|
|
|
|
return activation?.Accepted == true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task<PublicShellActivationResult?> TryActivateExistingInstanceWithStatusAsync(TimeSpan timeout)
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
using var ipcClient = new LanMountainDesktopIpcClient();
|
2026-04-23 09:45:05 +08:00
|
|
|
var connectTask = ipcClient.ConnectAsync();
|
|
|
|
|
var completedTask = await Task.WhenAny(connectTask, Task.Delay(timeout)).ConfigureAwait(false);
|
|
|
|
|
if (completedTask != connectTask)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await connectTask.ConfigureAwait(false);
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
if (!ipcClient.IsConnected)
|
|
|
|
|
{
|
2026-04-23 09:45:05 +08:00
|
|
|
return null;
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var shellProxy = ipcClient.CreateProxy<IPublicShellControlService>();
|
2026-04-23 09:45:05 +08:00
|
|
|
var activationTask = shellProxy.ActivateMainWindowWithStatusAsync();
|
|
|
|
|
completedTask = await Task.WhenAny(activationTask, Task.Delay(timeout)).ConfigureAwait(false);
|
|
|
|
|
if (completedTask != activationTask)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return await activationTask.ConfigureAwait(false);
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Logger.Warn($"Failed to activate the existing desktop instance: {ex.Message}");
|
2026-04-23 09:45:05 +08:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool TryGetLiveProcess(int processId)
|
|
|
|
|
{
|
|
|
|
|
if (processId <= 0)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
using var process = Process.GetProcessById(processId);
|
|
|
|
|
return !process.HasExited;
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
Add startup visual modes and attempt registry
Implement startup visual behavior, de-duplicate startup attempts, and improve failure UX.
Key changes:
- Add spec and docs for startup visuals and timing contract (.trae/specs and docs/LAUNCHER_STARTUP_VISUALS.md).
- Introduce StartupVisualPreferences contract and resolver; create SplashWindow via resolved mode.
- Add StartupAttemptRecord model and a file-backed StartupAttemptRegistry to persist and coordinate in-progress startup attempts (attach/adopt, soft/hard timeouts, IPC/connect state, lifecycle updates).
- Update LauncherFlowCoordinator to: adopt/attach to existing attempts, track IPC connection and soft/hard timeouts (30s/120s), show delayed UI state, attempt foreground recovery via public IPC, compose detailed launch result metadata, and mark registry states (soft timeout, detached waiting, succeeded, failed).
- Add TryActivateExistingInstanceAsync to attempt activating an existing desktop via IPC.
- Change failure flow: ShowFailureWindowAsync now returns user choice; ErrorWindow updated to present Activate/Wait/Open Logs/Exit semantics and new layouts/styles; improved button wiring and debug/dev mode handling.
- Add UI and resource tweaks (ErrorWindow and SplashWindow changes), project asset link for nightly logo, and unit tests for StartupVisualPreferences.
These changes prevent duplicate desktop processes during slow startups, provide clearer UX for delayed startups, and persist startup attempt state across Launcher invocations for safer recovery/attach behavior.
2026-04-23 09:03:35 +08:00
|
|
|
return false;
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
await Dispatcher.UIThread.InvokeAsync(() => window.Report("verify", "Verifying update...", 10));
|
2026-04-21 20:59:52 +08:00
|
|
|
var updateResult = await updateEngine.ApplyPendingUpdateAsync().ConfigureAwait(false);
|
|
|
|
|
if (!updateResult.Success)
|
|
|
|
|
{
|
|
|
|
|
success = false;
|
|
|
|
|
errorMessage = updateResult.Message;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (success)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
await Dispatcher.UIThread.InvokeAsync(() => window.Report("plugins", "Applying plugin upgrades...", 60));
|
|
|
|
|
var pluginsDir = context.GetOption("plugins-dir") ?? Path.Combine(appRoot, "plugins");
|
2026-04-21 20:59:52 +08:00
|
|
|
var queueResult = pluginUpgrades.ApplyPendingUpgrades(pluginsDir);
|
|
|
|
|
if (!queueResult.Success && queueResult.Code != "noop")
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Error($"Plugin upgrade failed during apply-update: {queueResult.Message}");
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (success)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
await Dispatcher.UIThread.InvokeAsync(() => window.Report("cleanup", "Cleaning up old deployments...", 90));
|
2026-04-21 20:59:52 +08:00
|
|
|
deploymentLocator.CleanupOldDeployments(minVersionsToKeep: 3);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
success = false;
|
|
|
|
|
errorMessage = ex.Message;
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Error("Apply-update flow failed.", ex);
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await Dispatcher.UIThread.InvokeAsync(() => window.ReportComplete(success, errorMessage));
|
2026-04-22 07:31:54 +08:00
|
|
|
await Task.Delay(success ? 1500 : 5000).ConfigureAwait(false);
|
2026-04-21 20:59:52 +08:00
|
|
|
|
|
|
|
|
await Commands.WriteResultIfNeededAsync(context.GetOption("result"), new LauncherResult
|
|
|
|
|
{
|
|
|
|
|
Success = success,
|
|
|
|
|
Stage = "apply-update",
|
|
|
|
|
Code = success ? "ok" : "failed",
|
Harden OOBE, launch-source and elevation flow
Introduce a per-user OOBE state model and hardened launch/elevation handling. Adds OobeStateFile/OobeLaunchDecision models, OobeStateService (persisting %LOCALAPPDATA%/.launcher/state/oobe-state.json), and LauncherExecutionContext to capture elevation and user SID. CommandContext now normalizes/infers launch-source values (normal, postinstall, apply-update, plugin-install, debug-preview) and exposes maintenance checks. LauncherFlowCoordinator propagates richer launcher context details for diagnostics and suppresses OOBE for elevated/maintenance contexts. PluginInstallerService avoids requesting elevation for user-scoped installs and returns a clear error when installation target is outside the current user's LocalAppData. LauncherClient maps and surfaces result codes, UpdateWorkflow and installer invocation now pass explicit --launch-source values, and WelcomeOobeStep persists OOBE completion via the new service. Adds unit tests (CommandContext, OobeStateService, PluginInstallerService), docs/specs/checklists for the contract, and makes internals visible to tests.
2026-04-22 09:25:22 +08:00
|
|
|
Message = success ? "Update applied successfully." : (errorMessage ?? "Unknown error"),
|
|
|
|
|
Details = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
{
|
|
|
|
|
["command"] = context.Command,
|
|
|
|
|
["launchSource"] = context.LaunchSource
|
|
|
|
|
}
|
2026-04-21 20:59:52 +08:00
|
|
|
}).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
Environment.ExitCode = success ? 0 : 1;
|
|
|
|
|
await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(Environment.ExitCode), DispatcherPriority.Background);
|
|
|
|
|
}
|
|
|
|
|
}
|