2026-04-21 20:59:52 +08:00
|
|
|
using System.Diagnostics;
|
|
|
|
|
using Avalonia.Threading;
|
|
|
|
|
using LanMountainDesktop.Launcher.Models;
|
2026-05-18 12:26:23 +08:00
|
|
|
using LanMountainDesktop.Launcher.Resources;
|
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;
|
|
|
|
|
using LanMountainDesktop.Shared.Contracts.Launcher;
|
2026-04-22 14:55:30 +08:00
|
|
|
using LanMountainDesktop.Shared.IPC;
|
2026-04-23 00:27:01 +08:00
|
|
|
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
2026-04-21 20:59:52 +08:00
|
|
|
|
|
|
|
|
namespace LanMountainDesktop.Launcher.Services;
|
|
|
|
|
|
|
|
|
|
internal sealed class LauncherFlowCoordinator
|
|
|
|
|
{
|
2026-04-23 23:07:37 +08:00
|
|
|
private static readonly TimeSpan StartupSoftTimeout = TimeSpan.FromSeconds(10);
|
|
|
|
|
private static readonly TimeSpan StartupHardTimeout = TimeSpan.FromSeconds(30);
|
2026-05-18 12:26:23 +08:00
|
|
|
private static readonly string SoftTimeoutStatusMessage = Strings.Coordinator_SlowDeviceMessage;
|
|
|
|
|
private static readonly string SoftTimeoutDetailsMessage = Strings.Coordinator_RunningHostMessage;
|
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 readonly CommandContext _context;
|
|
|
|
|
private readonly DeploymentLocator _deploymentLocator;
|
|
|
|
|
private readonly OobeStateService _oobeStateService;
|
|
|
|
|
private readonly UpdateEngineService _updateEngine;
|
|
|
|
|
private readonly PluginInstallerService _pluginInstallerService;
|
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 readonly StartupAttemptRegistry _startupAttemptRegistry;
|
2026-04-23 09:45:05 +08:00
|
|
|
private readonly LauncherCoordinatorIpcServer? _coordinatorIpcServer;
|
Launcher fix (#6)
* fix.hy3试图修复中
* Resolve dev paths and fix splash UI thread
Compute a solutionRoot and expand development search paths (LanMountainDesktop and dev-test) in DeploymentLocator, add logging when scanning/finding hosts, and return distinct full paths. Ensure backward-compatible path checks. Fix cross-thread UI calls: invoke splashWindow.DismissAsync on the UI thread in LauncherFlowCoordinator, and make SplashWindow.DismissAsync ensure it runs on the UI thread before closing (simplified Close call). These changes improve development host discovery and prevent UI-thread access issues during shutdown.
* Add configurable data location (portable/system)
Introduce support for choosing and resolving the application's data root (system user dir vs. portable app folder). Adds DataLocationConfig model, DataLocationResolver (load/save/resolve/migrate), a UI prompt (DataLocationPromptWindow) and an OOBE step (DataLocationOobeStep) to let users pick and optionally migrate existing data. Wire the chosen data root into the launcher flow and host launch plan (forwarded via --data-root and LMD_DATA_ROOT), and add AppDataPathProvider to let runtime services read the effective data root (initialized in Program.Main). Update various services (logging, settings, DB, plugin/market, startup registry, etc.) to use the new provider/resolver and register the config type in the JSON context. This enables portable installs, safe migration, and runtime overrides via CLI or environment variable.
* Add dev/debug startup flow and launch profiles
Handle design-time initialization and add a developer debug startup path: App now skips normal startup when in design mode and shows a DevDebugWindow when running in debug (unless a preview or apply-update command). CommandContext.IsDebugMode is extended to include DOTNET_ENVIRONMENT=Development via a new IsDevelopmentEnvironment helper. Program.Main and BuildAvaloniaApp are made public to aid tooling. Added multiple launchSettings profiles for debug and preview commands that set DOTNET_ENVIRONMENT=Development to simplify IDE debugging and UI previewing.
* Simplify splash to fade; add themed about banners
Simplify splash startup visuals by removing the multi-mode/slide behavior and always using a fade animation. Update App to create SplashWindow without a StartupVisualMode parameter and remove related fields, layout configuration, slide animation, and easing helpers from SplashWindow. Clean up unused using. Replace the single about_banner asset with theme-aware variants (about_banner_dark.png and about_banner_light.png), delete the old about_banner.png, and update AboutSettingsPage to use a DynamicResource ImageBrush (AboutBannerBrush) that selects the appropriate banner per theme.
* Use AppJsonContext for startup state serialization
Switch serialization to the source-generated System.Text.Json context: add JsonSerializable(typeof(StartupAttemptRecord)) to AppJsonContext and replace the previous JsonSerializerOptions-based Serialize/Deserialize calls with AppJsonContext.Default.StartupAttemptRecord. Also remove the now-unused SerializerOptions field. Additionally, update .gitignore to exclude /test-aot-publish.
* Add OOBE redesign, theme & data location support
Introduce a redesigned OOBE flow and data-location/theme support across the launcher. Adds a new ThemeService for applying light/dark and accent colors; integrates FluentIcons.Avalonia package for icons. Overhauls OobeWindow (UX animations, typing effect, multi-step theme and data-location pages, Monet options, and final welcome step) and its code-behind to handle step navigation, accent selection, and data-location resolution. Adds DataLocation UI and handlers (DataLocationPromptWindow changes, DataLocation resolver usage) and wires a DevDebug UI for toggling/opening the data-location page. UpdateEngineService now resolves the launcher root via DataLocationResolver. Misc: update various view models, localization entries and remove TrimmerRoots.xml.
* Refactor data location paths and add background service
Refactor DataLocationResolver to centralize data path resolution (ResolveLauncherDataPath, ResolveDesktopDataPath, ResolveConfigPath, ResolveLauncherLogsPath, ResolveLauncherStatePath) and replace usages of the previous ".launcher" layout with a "Launcher" folder. Update API: LoadConfig/SaveConfig reorganized and ApplyLocationChoice now accepts an optional custom path and migration flag; migration logic updated accordingly. Update dependent services and views (Logger, DeploymentLocator, UpdateEngineService, OobeStateService, StartupAttemptRegistry, LauncherDebugSettingsStore, OobeWindow) to use the new resolver APIs and paths. Add LauncherBackgroundService to load/validate/cache a custom splash background image and wire it into SplashWindow (AXAML/Axaml.cs) with UI placeholders and overlay. Misc: minor cleanup of Oobe/Splash XAML and related code adjustments and logging improvements.
2026-04-25 18:41:26 +08:00
|
|
|
private readonly DataLocationResolver _dataLocationResolver;
|
2026-04-21 20:59:52 +08:00
|
|
|
private readonly IReadOnlyList<IOobeStep> _oobeSteps;
|
|
|
|
|
|
|
|
|
|
public LauncherFlowCoordinator(
|
|
|
|
|
CommandContext context,
|
|
|
|
|
DeploymentLocator deploymentLocator,
|
|
|
|
|
OobeStateService oobeStateService,
|
|
|
|
|
UpdateEngineService updateEngine,
|
2026-04-23 09:45:05 +08:00
|
|
|
PluginInstallerService pluginInstallerService,
|
|
|
|
|
StartupAttemptRegistry? startupAttemptRegistry = null,
|
|
|
|
|
LauncherCoordinatorIpcServer? coordinatorIpcServer = null)
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
|
|
|
|
_context = context;
|
|
|
|
|
_deploymentLocator = deploymentLocator;
|
|
|
|
|
_oobeStateService = oobeStateService;
|
|
|
|
|
_updateEngine = updateEngine;
|
|
|
|
|
_pluginInstallerService = pluginInstallerService;
|
2026-04-23 09:45:05 +08:00
|
|
|
_startupAttemptRegistry = startupAttemptRegistry ?? new StartupAttemptRegistry();
|
|
|
|
|
_coordinatorIpcServer = coordinatorIpcServer;
|
Launcher fix (#6)
* fix.hy3试图修复中
* Resolve dev paths and fix splash UI thread
Compute a solutionRoot and expand development search paths (LanMountainDesktop and dev-test) in DeploymentLocator, add logging when scanning/finding hosts, and return distinct full paths. Ensure backward-compatible path checks. Fix cross-thread UI calls: invoke splashWindow.DismissAsync on the UI thread in LauncherFlowCoordinator, and make SplashWindow.DismissAsync ensure it runs on the UI thread before closing (simplified Close call). These changes improve development host discovery and prevent UI-thread access issues during shutdown.
* Add configurable data location (portable/system)
Introduce support for choosing and resolving the application's data root (system user dir vs. portable app folder). Adds DataLocationConfig model, DataLocationResolver (load/save/resolve/migrate), a UI prompt (DataLocationPromptWindow) and an OOBE step (DataLocationOobeStep) to let users pick and optionally migrate existing data. Wire the chosen data root into the launcher flow and host launch plan (forwarded via --data-root and LMD_DATA_ROOT), and add AppDataPathProvider to let runtime services read the effective data root (initialized in Program.Main). Update various services (logging, settings, DB, plugin/market, startup registry, etc.) to use the new provider/resolver and register the config type in the JSON context. This enables portable installs, safe migration, and runtime overrides via CLI or environment variable.
* Add dev/debug startup flow and launch profiles
Handle design-time initialization and add a developer debug startup path: App now skips normal startup when in design mode and shows a DevDebugWindow when running in debug (unless a preview or apply-update command). CommandContext.IsDebugMode is extended to include DOTNET_ENVIRONMENT=Development via a new IsDevelopmentEnvironment helper. Program.Main and BuildAvaloniaApp are made public to aid tooling. Added multiple launchSettings profiles for debug and preview commands that set DOTNET_ENVIRONMENT=Development to simplify IDE debugging and UI previewing.
* Simplify splash to fade; add themed about banners
Simplify splash startup visuals by removing the multi-mode/slide behavior and always using a fade animation. Update App to create SplashWindow without a StartupVisualMode parameter and remove related fields, layout configuration, slide animation, and easing helpers from SplashWindow. Clean up unused using. Replace the single about_banner asset with theme-aware variants (about_banner_dark.png and about_banner_light.png), delete the old about_banner.png, and update AboutSettingsPage to use a DynamicResource ImageBrush (AboutBannerBrush) that selects the appropriate banner per theme.
* Use AppJsonContext for startup state serialization
Switch serialization to the source-generated System.Text.Json context: add JsonSerializable(typeof(StartupAttemptRecord)) to AppJsonContext and replace the previous JsonSerializerOptions-based Serialize/Deserialize calls with AppJsonContext.Default.StartupAttemptRecord. Also remove the now-unused SerializerOptions field. Additionally, update .gitignore to exclude /test-aot-publish.
* Add OOBE redesign, theme & data location support
Introduce a redesigned OOBE flow and data-location/theme support across the launcher. Adds a new ThemeService for applying light/dark and accent colors; integrates FluentIcons.Avalonia package for icons. Overhauls OobeWindow (UX animations, typing effect, multi-step theme and data-location pages, Monet options, and final welcome step) and its code-behind to handle step navigation, accent selection, and data-location resolution. Adds DataLocation UI and handlers (DataLocationPromptWindow changes, DataLocation resolver usage) and wires a DevDebug UI for toggling/opening the data-location page. UpdateEngineService now resolves the launcher root via DataLocationResolver. Misc: update various view models, localization entries and remove TrimmerRoots.xml.
* Refactor data location paths and add background service
Refactor DataLocationResolver to centralize data path resolution (ResolveLauncherDataPath, ResolveDesktopDataPath, ResolveConfigPath, ResolveLauncherLogsPath, ResolveLauncherStatePath) and replace usages of the previous ".launcher" layout with a "Launcher" folder. Update API: LoadConfig/SaveConfig reorganized and ApplyLocationChoice now accepts an optional custom path and migration flag; migration logic updated accordingly. Update dependent services and views (Logger, DeploymentLocator, UpdateEngineService, OobeStateService, StartupAttemptRegistry, LauncherDebugSettingsStore, OobeWindow) to use the new resolver APIs and paths. Add LauncherBackgroundService to load/validate/cache a custom splash background image and wire it into SplashWindow (AXAML/Axaml.cs) with UI placeholders and overlay. Misc: minor cleanup of Oobe/Splash XAML and related code adjustments and logging improvements.
2026-04-25 18:41:26 +08:00
|
|
|
_dataLocationResolver = new DataLocationResolver(deploymentLocator.GetAppRoot());
|
|
|
|
|
_oobeSteps =
|
|
|
|
|
[
|
|
|
|
|
new WelcomeOobeStep(_oobeStateService, _context),
|
|
|
|
|
new DataLocationOobeStep(_dataLocationResolver)
|
|
|
|
|
];
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:45:05 +08:00
|
|
|
public static string ResolveSuccessPolicyKey(CommandContext context)
|
|
|
|
|
{
|
|
|
|
|
return new StartupSuccessTracker(context).PolicyKey;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
public async Task<LauncherResult> RunAsync(SplashWindow? existingSplashWindow = null)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_deploymentLocator.CleanupOldDeployments(minVersionsToKeep: 3);
|
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 oobeDecision = _oobeStateService.Evaluate(_context);
|
|
|
|
|
var launcherContextDetails = BuildLauncherContextDetails(_context, oobeDecision, _deploymentLocator.GetAppRoot());
|
2026-04-21 20:59:52 +08:00
|
|
|
|
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
|
|
|
if (oobeDecision.ShouldShowOobe)
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
|
|
|
|
var legacyInfo = LegacyVersionDetector.DetectLegacyInstallation();
|
2026-04-22 07:31:54 +08:00
|
|
|
if (legacyInfo is not null)
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
var migrationResult = await ShowMigrationPromptAsync(legacyInfo).ConfigureAwait(false);
|
|
|
|
|
Logger.Info($"Migration prompt completed. Result='{migrationResult}'.");
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var splashWindow = existingSplashWindow ?? await Dispatcher.UIThread.InvokeAsync(() =>
|
|
|
|
|
{
|
|
|
|
|
var window = new SplashWindow();
|
|
|
|
|
window.Show();
|
|
|
|
|
return window;
|
|
|
|
|
});
|
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 windowsClosingByCoordinator = false;
|
2026-04-23 00:27:01 +08:00
|
|
|
var versionInfo = _deploymentLocator.GetVersionInfo();
|
|
|
|
|
splashWindow.SetVersionInfo(versionInfo.Version, versionInfo.Codename);
|
2026-04-21 20:59:52 +08:00
|
|
|
var reporter = (ISplashStageReporter)splashWindow;
|
2026-04-22 07:31:54 +08:00
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
LoadingDetailsWindow? loadingDetailsWindow = null;
|
|
|
|
|
if (_context.IsDebugMode || _context.GetOption("show-loading-details") == "true")
|
|
|
|
|
{
|
|
|
|
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
|
|
|
|
{
|
|
|
|
|
loadingDetailsWindow = new LoadingDetailsWindow();
|
|
|
|
|
loadingDetailsWindow.Show();
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-04-22 07:31:54 +08:00
|
|
|
|
2026-04-23 00:27:01 +08:00
|
|
|
var successTcs = new TaskCompletionSource<StartupSuccessState>(TaskCreationOptions.RunContinuationsAsynchronously);
|
2026-04-22 07:31:54 +08:00
|
|
|
var activationFailedTcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
|
|
var lastStage = StartupStage.Initializing;
|
|
|
|
|
var lastStageMessage = "launcher-started";
|
2026-04-23 00:27:01 +08:00
|
|
|
var startupSuccessTracker = new StartupSuccessTracker(_context);
|
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 activationFailureReason = string.Empty;
|
|
|
|
|
var ipcConnected = false;
|
|
|
|
|
var softTimeoutShown = false;
|
|
|
|
|
var attachedToExistingAttempt = false;
|
|
|
|
|
StartupAttemptRecord? trackedAttempt = null;
|
2026-04-23 09:45:05 +08:00
|
|
|
PublicShellStatus? shellStatus = null;
|
|
|
|
|
|
|
|
|
|
void PublishCoordinatorStatus(bool? hostProcessAliveOverride = null, bool completed = false, bool succeeded = false)
|
|
|
|
|
{
|
|
|
|
|
if (_coordinatorIpcServer is null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trackedAttempt = _startupAttemptRegistry.GetOwnedAttempt() ?? trackedAttempt;
|
|
|
|
|
var hostPid = trackedAttempt?.HostPid ?? 0;
|
|
|
|
|
var hostProcessAlive = hostProcessAliveOverride ??
|
|
|
|
|
(hostPid > 0 && TryGetLiveProcess(hostPid, out _));
|
|
|
|
|
var status = new LauncherCoordinatorStatus
|
|
|
|
|
{
|
|
|
|
|
AttemptId = trackedAttempt?.AttemptId ?? string.Empty,
|
|
|
|
|
CoordinatorPid = Environment.ProcessId,
|
|
|
|
|
HostPid = hostPid,
|
|
|
|
|
HostProcessAlive = hostProcessAlive,
|
|
|
|
|
LaunchSource = trackedAttempt?.LaunchSource ?? _context.LaunchSource,
|
|
|
|
|
SuccessPolicy = trackedAttempt?.SuccessPolicy ?? startupSuccessTracker.PolicyKey,
|
|
|
|
|
LastObservedStage = lastStage,
|
|
|
|
|
LastObservedMessage = lastStageMessage,
|
|
|
|
|
PublicIpcConnected = ipcConnected,
|
|
|
|
|
State = trackedAttempt?.State.ToString() ?? StartupAttemptState.Pending.ToString(),
|
|
|
|
|
SoftTimeoutShown = softTimeoutShown,
|
|
|
|
|
Completed = completed,
|
|
|
|
|
Succeeded = succeeded,
|
|
|
|
|
ShellStatus = shellStatus,
|
|
|
|
|
UpdatedAtUtc = DateTimeOffset.UtcNow
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_coordinatorIpcServer.UpdateStatus(status);
|
|
|
|
|
_startupAttemptRegistry.UpdateOwnedCoordinatorHeartbeat(status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trackedAttempt = _startupAttemptRegistry.GetOwnedAttempt();
|
|
|
|
|
PublishCoordinatorStatus();
|
2026-04-22 07:31:54 +08:00
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
var loadingState = new LoadingStateMessage();
|
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
|
|
|
EventHandler? splashClosedHandler = null;
|
|
|
|
|
splashClosedHandler = (_, _) =>
|
|
|
|
|
{
|
|
|
|
|
if (windowsClosingByCoordinator)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedDetachedWaiting();
|
|
|
|
|
Logger.Warn("Splash window was closed manually. Launcher will continue monitoring the current startup attempt.");
|
|
|
|
|
};
|
|
|
|
|
splashWindow.Closed += splashClosedHandler;
|
2026-04-22 14:55:30 +08:00
|
|
|
using var ipcClient = new LanMountainDesktopIpcClient();
|
|
|
|
|
ipcClient.RegisterNotifyHandler<StartupProgressMessage>(IpcRoutedNotifyIds.LauncherStartupProgress, message =>
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
|
|
|
|
Dispatcher.UIThread.Post(() =>
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
ipcConnected = true;
|
2026-04-22 07:31:54 +08:00
|
|
|
lastStage = message.Stage;
|
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
|
|
|
lastStageMessage = message.Message ?? message.Stage.ToString();
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Info($"IPC stage received. Stage='{message.Stage}'; Message='{message.Message ?? string.Empty}'.");
|
|
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
loadingState = loadingState with
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
Stage = message.Stage,
|
|
|
|
|
OverallProgressPercent = message.ProgressPercent,
|
|
|
|
|
Message = message.Message,
|
2026-04-21 20:59:52 +08:00
|
|
|
Timestamp = DateTimeOffset.UtcNow
|
|
|
|
|
};
|
2026-04-22 07:31:54 +08:00
|
|
|
|
|
|
|
|
reporter.Report(MapStartupStageToSplashStage(message.Stage), message.Message ?? message.Stage.ToString());
|
2026-04-21 20:59:52 +08:00
|
|
|
loadingDetailsWindow?.UpdateLoadingState(loadingState);
|
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
|
|
|
_startupAttemptRegistry.UpdateOwnedStage(message.Stage, message.Message, ipcConnected: true);
|
2026-04-23 09:45:05 +08:00
|
|
|
PublishCoordinatorStatus();
|
2026-04-22 07:31:54 +08:00
|
|
|
|
2026-04-23 00:27:01 +08:00
|
|
|
if (startupSuccessTracker.TryResolve(message.Stage, out var successState))
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
2026-04-23 00:27:01 +08:00
|
|
|
successTcs.TrySetResult(successState);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (message.Stage == StartupStage.ActivationFailed)
|
|
|
|
|
{
|
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
|
|
|
activationFailureReason = message.Message ?? "activation_failed";
|
2026-04-23 00:27:01 +08:00
|
|
|
activationFailedTcs.TrySetResult(message.Message ?? "activation_failed");
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Error("IPC progress callback failed.", ex);
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-04-22 14:55:30 +08:00
|
|
|
ipcClient.RegisterNotifyHandler<LoadingStateMessage>(IpcRoutedNotifyIds.LauncherLoadingState, message =>
|
|
|
|
|
{
|
|
|
|
|
Dispatcher.UIThread.Post(() =>
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
loadingState = message;
|
|
|
|
|
loadingDetailsWindow?.UpdateLoadingState(loadingState);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Logger.Error("IPC loading-state callback failed.", ex);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-04-21 20:59:52 +08:00
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2026-04-23 09:45:05 +08:00
|
|
|
if (ShouldProbeExistingHostBeforeLaunch(_context))
|
|
|
|
|
{
|
2026-05-12 16:46:49 +08:00
|
|
|
var multiInstanceBehavior = LoadMultiInstanceLaunchBehavior();
|
|
|
|
|
var existingShellStatus = await TryGetExistingHostStatusAsync(ipcClient, TimeSpan.FromMilliseconds(900))
|
2026-04-23 09:45:05 +08:00
|
|
|
.ConfigureAwait(false);
|
2026-05-12 16:46:49 +08:00
|
|
|
if (IsExistingHostReadyForLauncherDecision(existingShellStatus))
|
2026-04-23 09:45:05 +08:00
|
|
|
{
|
|
|
|
|
ipcConnected = true;
|
2026-05-12 16:46:49 +08:00
|
|
|
shellStatus = existingShellStatus;
|
|
|
|
|
var decisionResult = await ApplyExistingHostBehaviorAsync(
|
|
|
|
|
ipcClient,
|
|
|
|
|
multiInstanceBehavior,
|
|
|
|
|
existingShellStatus!)
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
shellStatus = decisionResult.ActivationResult?.Status ?? existingShellStatus;
|
|
|
|
|
var recoverableActivationFailure = decisionResult.ActivationResult is not null &&
|
|
|
|
|
IsRecoverableActivationFailure(decisionResult.ActivationResult);
|
|
|
|
|
lastStage = decisionResult.Success || recoverableActivationFailure
|
2026-04-23 09:45:05 +08:00
|
|
|
? StartupStage.ActivationRedirected
|
|
|
|
|
: StartupStage.ActivationFailed;
|
2026-05-12 16:46:49 +08:00
|
|
|
lastStageMessage = decisionResult.Message;
|
|
|
|
|
if (decisionResult.Success || recoverableActivationFailure)
|
2026-04-23 09:45:05 +08:00
|
|
|
{
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedSucceeded(lastStage, lastStageMessage);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedFailed(lastStage, lastStageMessage);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 16:46:49 +08:00
|
|
|
PublishCoordinatorStatus(hostProcessAliveOverride: true, completed: true, succeeded: decisionResult.Success);
|
2026-04-23 09:45:05 +08:00
|
|
|
windowsClosingByCoordinator = true;
|
|
|
|
|
await CloseWindowsAsync(splashWindow, loadingDetailsWindow).ConfigureAwait(false);
|
|
|
|
|
return BuildResult(
|
2026-05-12 16:46:49 +08:00
|
|
|
success: decisionResult.Success,
|
2026-04-23 09:45:05 +08:00
|
|
|
stage: "launch",
|
2026-05-12 16:46:49 +08:00
|
|
|
code: decisionResult.Code,
|
|
|
|
|
message: decisionResult.Message,
|
2026-04-23 09:45:05 +08:00
|
|
|
details: MergeDetails(
|
|
|
|
|
launcherContextDetails,
|
|
|
|
|
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
{
|
|
|
|
|
["publicIpcConnected"] = "true",
|
2026-05-12 16:46:49 +08:00
|
|
|
["multiInstanceBehavior"] = multiInstanceBehavior.ToString(),
|
|
|
|
|
["existingHostPid"] = shellStatus?.ProcessId.ToString() ?? string.Empty,
|
|
|
|
|
["existingShellState"] = shellStatus?.ShellState ?? string.Empty,
|
|
|
|
|
["existingTrayState"] = shellStatus?.Tray.State ?? string.Empty,
|
|
|
|
|
["existingTaskbarUsable"] = shellStatus?.Taskbar.IsUsable.ToString() ?? string.Empty,
|
|
|
|
|
["activationAccepted"] = decisionResult.ActivationResult?.Accepted.ToString() ?? string.Empty
|
2026-04-23 09:45:05 +08:00
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
reporter.Report("update", "Checking updates...");
|
2026-04-21 20:59:52 +08:00
|
|
|
var updateResult = await _updateEngine.ApplyPendingUpdateAsync().ConfigureAwait(false);
|
|
|
|
|
if (!updateResult.Success)
|
|
|
|
|
{
|
Launcher fix (#6)
* fix.hy3试图修复中
* Resolve dev paths and fix splash UI thread
Compute a solutionRoot and expand development search paths (LanMountainDesktop and dev-test) in DeploymentLocator, add logging when scanning/finding hosts, and return distinct full paths. Ensure backward-compatible path checks. Fix cross-thread UI calls: invoke splashWindow.DismissAsync on the UI thread in LauncherFlowCoordinator, and make SplashWindow.DismissAsync ensure it runs on the UI thread before closing (simplified Close call). These changes improve development host discovery and prevent UI-thread access issues during shutdown.
* Add configurable data location (portable/system)
Introduce support for choosing and resolving the application's data root (system user dir vs. portable app folder). Adds DataLocationConfig model, DataLocationResolver (load/save/resolve/migrate), a UI prompt (DataLocationPromptWindow) and an OOBE step (DataLocationOobeStep) to let users pick and optionally migrate existing data. Wire the chosen data root into the launcher flow and host launch plan (forwarded via --data-root and LMD_DATA_ROOT), and add AppDataPathProvider to let runtime services read the effective data root (initialized in Program.Main). Update various services (logging, settings, DB, plugin/market, startup registry, etc.) to use the new provider/resolver and register the config type in the JSON context. This enables portable installs, safe migration, and runtime overrides via CLI or environment variable.
* Add dev/debug startup flow and launch profiles
Handle design-time initialization and add a developer debug startup path: App now skips normal startup when in design mode and shows a DevDebugWindow when running in debug (unless a preview or apply-update command). CommandContext.IsDebugMode is extended to include DOTNET_ENVIRONMENT=Development via a new IsDevelopmentEnvironment helper. Program.Main and BuildAvaloniaApp are made public to aid tooling. Added multiple launchSettings profiles for debug and preview commands that set DOTNET_ENVIRONMENT=Development to simplify IDE debugging and UI previewing.
* Simplify splash to fade; add themed about banners
Simplify splash startup visuals by removing the multi-mode/slide behavior and always using a fade animation. Update App to create SplashWindow without a StartupVisualMode parameter and remove related fields, layout configuration, slide animation, and easing helpers from SplashWindow. Clean up unused using. Replace the single about_banner asset with theme-aware variants (about_banner_dark.png and about_banner_light.png), delete the old about_banner.png, and update AboutSettingsPage to use a DynamicResource ImageBrush (AboutBannerBrush) that selects the appropriate banner per theme.
* Use AppJsonContext for startup state serialization
Switch serialization to the source-generated System.Text.Json context: add JsonSerializable(typeof(StartupAttemptRecord)) to AppJsonContext and replace the previous JsonSerializerOptions-based Serialize/Deserialize calls with AppJsonContext.Default.StartupAttemptRecord. Also remove the now-unused SerializerOptions field. Additionally, update .gitignore to exclude /test-aot-publish.
* Add OOBE redesign, theme & data location support
Introduce a redesigned OOBE flow and data-location/theme support across the launcher. Adds a new ThemeService for applying light/dark and accent colors; integrates FluentIcons.Avalonia package for icons. Overhauls OobeWindow (UX animations, typing effect, multi-step theme and data-location pages, Monet options, and final welcome step) and its code-behind to handle step navigation, accent selection, and data-location resolution. Adds DataLocation UI and handlers (DataLocationPromptWindow changes, DataLocation resolver usage) and wires a DevDebug UI for toggling/opening the data-location page. UpdateEngineService now resolves the launcher root via DataLocationResolver. Misc: update various view models, localization entries and remove TrimmerRoots.xml.
* Refactor data location paths and add background service
Refactor DataLocationResolver to centralize data path resolution (ResolveLauncherDataPath, ResolveDesktopDataPath, ResolveConfigPath, ResolveLauncherLogsPath, ResolveLauncherStatePath) and replace usages of the previous ".launcher" layout with a "Launcher" folder. Update API: LoadConfig/SaveConfig reorganized and ApplyLocationChoice now accepts an optional custom path and migration flag; migration logic updated accordingly. Update dependent services and views (Logger, DeploymentLocator, UpdateEngineService, OobeStateService, StartupAttemptRegistry, LauncherDebugSettingsStore, OobeWindow) to use the new resolver APIs and paths. Add LauncherBackgroundService to load/validate/cache a custom splash background image and wire it into SplashWindow (AXAML/Axaml.cs) with UI placeholders and overlay. Misc: minor cleanup of Oobe/Splash XAML and related code adjustments and logging improvements.
2026-04-25 18:41:26 +08:00
|
|
|
Logger.Warn($"Update apply failed, will try to launch existing version. Error='{updateResult.Message}'.");
|
|
|
|
|
reporter.Report("update", "Update failed, launching existing version...");
|
|
|
|
|
// Clean up corrupted update files to prevent repeated failures
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_updateEngine.CleanupIncomingArtifacts();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Logger.Warn($"Failed to cleanup update artifacts after failed update: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
// Continue to launch existing version instead of aborting
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
reporter.Report("plugins", "Applying plugin upgrades...");
|
|
|
|
|
var pluginsDir = _context.GetOption("plugins-dir") ?? Path.Combine(_deploymentLocator.GetAppRoot(), "plugins");
|
2026-04-21 20:59:52 +08:00
|
|
|
var queueResult = new PluginUpgradeQueueService(_pluginInstallerService).ApplyPendingUpgrades(pluginsDir);
|
|
|
|
|
if (!queueResult.Success)
|
|
|
|
|
{
|
Launcher fix (#6)
* fix.hy3试图修复中
* Resolve dev paths and fix splash UI thread
Compute a solutionRoot and expand development search paths (LanMountainDesktop and dev-test) in DeploymentLocator, add logging when scanning/finding hosts, and return distinct full paths. Ensure backward-compatible path checks. Fix cross-thread UI calls: invoke splashWindow.DismissAsync on the UI thread in LauncherFlowCoordinator, and make SplashWindow.DismissAsync ensure it runs on the UI thread before closing (simplified Close call). These changes improve development host discovery and prevent UI-thread access issues during shutdown.
* Add configurable data location (portable/system)
Introduce support for choosing and resolving the application's data root (system user dir vs. portable app folder). Adds DataLocationConfig model, DataLocationResolver (load/save/resolve/migrate), a UI prompt (DataLocationPromptWindow) and an OOBE step (DataLocationOobeStep) to let users pick and optionally migrate existing data. Wire the chosen data root into the launcher flow and host launch plan (forwarded via --data-root and LMD_DATA_ROOT), and add AppDataPathProvider to let runtime services read the effective data root (initialized in Program.Main). Update various services (logging, settings, DB, plugin/market, startup registry, etc.) to use the new provider/resolver and register the config type in the JSON context. This enables portable installs, safe migration, and runtime overrides via CLI or environment variable.
* Add dev/debug startup flow and launch profiles
Handle design-time initialization and add a developer debug startup path: App now skips normal startup when in design mode and shows a DevDebugWindow when running in debug (unless a preview or apply-update command). CommandContext.IsDebugMode is extended to include DOTNET_ENVIRONMENT=Development via a new IsDevelopmentEnvironment helper. Program.Main and BuildAvaloniaApp are made public to aid tooling. Added multiple launchSettings profiles for debug and preview commands that set DOTNET_ENVIRONMENT=Development to simplify IDE debugging and UI previewing.
* Simplify splash to fade; add themed about banners
Simplify splash startup visuals by removing the multi-mode/slide behavior and always using a fade animation. Update App to create SplashWindow without a StartupVisualMode parameter and remove related fields, layout configuration, slide animation, and easing helpers from SplashWindow. Clean up unused using. Replace the single about_banner asset with theme-aware variants (about_banner_dark.png and about_banner_light.png), delete the old about_banner.png, and update AboutSettingsPage to use a DynamicResource ImageBrush (AboutBannerBrush) that selects the appropriate banner per theme.
* Use AppJsonContext for startup state serialization
Switch serialization to the source-generated System.Text.Json context: add JsonSerializable(typeof(StartupAttemptRecord)) to AppJsonContext and replace the previous JsonSerializerOptions-based Serialize/Deserialize calls with AppJsonContext.Default.StartupAttemptRecord. Also remove the now-unused SerializerOptions field. Additionally, update .gitignore to exclude /test-aot-publish.
* Add OOBE redesign, theme & data location support
Introduce a redesigned OOBE flow and data-location/theme support across the launcher. Adds a new ThemeService for applying light/dark and accent colors; integrates FluentIcons.Avalonia package for icons. Overhauls OobeWindow (UX animations, typing effect, multi-step theme and data-location pages, Monet options, and final welcome step) and its code-behind to handle step navigation, accent selection, and data-location resolution. Adds DataLocation UI and handlers (DataLocationPromptWindow changes, DataLocation resolver usage) and wires a DevDebug UI for toggling/opening the data-location page. UpdateEngineService now resolves the launcher root via DataLocationResolver. Misc: update various view models, localization entries and remove TrimmerRoots.xml.
* Refactor data location paths and add background service
Refactor DataLocationResolver to centralize data path resolution (ResolveLauncherDataPath, ResolveDesktopDataPath, ResolveConfigPath, ResolveLauncherLogsPath, ResolveLauncherStatePath) and replace usages of the previous ".launcher" layout with a "Launcher" folder. Update API: LoadConfig/SaveConfig reorganized and ApplyLocationChoice now accepts an optional custom path and migration flag; migration logic updated accordingly. Update dependent services and views (Logger, DeploymentLocator, UpdateEngineService, OobeStateService, StartupAttemptRegistry, LauncherDebugSettingsStore, OobeWindow) to use the new resolver APIs and paths. Add LauncherBackgroundService to load/validate/cache a custom splash background image and wire it into SplashWindow (AXAML/Axaml.cs) with UI placeholders and overlay. Misc: minor cleanup of Oobe/Splash XAML and related code adjustments and logging improvements.
2026-04-25 18:41:26 +08:00
|
|
|
Logger.Warn($"Plugin upgrade failed, continuing startup. Error='{queueResult.Message}'.");
|
|
|
|
|
reporter.Report("plugins", "Plugin upgrade failed, continuing...");
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
|
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
|
|
|
if (oobeDecision.ShouldShowOobe)
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
|
|
|
|
await Dispatcher.UIThread.InvokeAsync(() => splashWindow.Hide());
|
|
|
|
|
foreach (var step in _oobeSteps)
|
|
|
|
|
{
|
|
|
|
|
await step.RunAsync(CancellationToken.None).ConfigureAwait(false);
|
|
|
|
|
}
|
2026-04-22 07:31:54 +08:00
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
await Dispatcher.UIThread.InvokeAsync(() => splashWindow.Show());
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
reporter.Report("launch", "Launching desktop...");
|
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 launchOutcome = default(HostLaunchOutcome);
|
|
|
|
|
var attachableAttempt = _startupAttemptRegistry.TryGetAttachableAttempt(_context.LaunchSource, startupSuccessTracker.PolicyKey);
|
|
|
|
|
if (attachableAttempt is not null &&
|
|
|
|
|
_startupAttemptRegistry.AdoptAttempt(attachableAttempt.AttemptId) &&
|
|
|
|
|
TryGetLiveProcess(attachableAttempt.HostPid, out var attachedProcess))
|
|
|
|
|
{
|
|
|
|
|
trackedAttempt = attachableAttempt;
|
|
|
|
|
attachedToExistingAttempt = true;
|
|
|
|
|
ipcConnected = attachableAttempt.IpcConnected;
|
|
|
|
|
lastStage = attachableAttempt.LastObservedStage;
|
|
|
|
|
lastStageMessage = string.IsNullOrWhiteSpace(attachableAttempt.LastObservedMessage)
|
|
|
|
|
? "Attached to the existing startup attempt."
|
|
|
|
|
: attachableAttempt.LastObservedMessage;
|
|
|
|
|
reporter.Report(MapStartupStageToSplashStage(lastStage), lastStageMessage);
|
2026-04-23 09:45:05 +08:00
|
|
|
PublishCoordinatorStatus(hostProcessAliveOverride: true);
|
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 (startupSuccessTracker.TryResolve(lastStage, out var attachedSuccessState))
|
|
|
|
|
{
|
|
|
|
|
windowsClosingByCoordinator = true;
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedSucceeded(attachedSuccessState.Stage, attachedSuccessState.Message);
|
|
|
|
|
await CloseWindowsAsync(splashWindow, loadingDetailsWindow).ConfigureAwait(false);
|
|
|
|
|
return BuildResult(
|
|
|
|
|
success: true,
|
|
|
|
|
stage: "launch",
|
|
|
|
|
code: attachedSuccessState.Code,
|
|
|
|
|
message: attachedSuccessState.Message,
|
|
|
|
|
details: MergeDetails(
|
|
|
|
|
launcherContextDetails,
|
|
|
|
|
BuildAttemptDetails(
|
|
|
|
|
trackedAttempt,
|
|
|
|
|
attachedToExistingAttempt,
|
|
|
|
|
ipcConnected,
|
|
|
|
|
hostProcessAlive: true,
|
|
|
|
|
lastStage,
|
|
|
|
|
lastStageMessage,
|
|
|
|
|
activationFailureReason,
|
|
|
|
|
softTimeoutShown: false,
|
|
|
|
|
recoveryActivationAttempted: false)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (attachableAttempt.State is StartupAttemptState.SoftTimeout or StartupAttemptState.DetachedWaiting)
|
|
|
|
|
{
|
|
|
|
|
softTimeoutShown = true;
|
|
|
|
|
reporter.Report("delayed", SoftTimeoutStatusMessage);
|
|
|
|
|
loadingState = BuildDelayedLoadingState(
|
|
|
|
|
loadingState,
|
|
|
|
|
SoftTimeoutStatusMessage,
|
|
|
|
|
SoftTimeoutDetailsMessage,
|
|
|
|
|
trackedAttempt.StartedAtUtc);
|
|
|
|
|
loadingDetailsWindow?.UpdateLoadingState(loadingState);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
launchOutcome = HostLaunchOutcome.FromProcess(
|
|
|
|
|
attachedProcess!,
|
|
|
|
|
BuildResult(
|
|
|
|
|
true,
|
|
|
|
|
"launchHost",
|
|
|
|
|
"attached_attempt",
|
|
|
|
|
"Attached to an existing startup attempt.",
|
|
|
|
|
BuildAttemptDetails(
|
|
|
|
|
trackedAttempt,
|
|
|
|
|
attachedToExistingAttempt,
|
|
|
|
|
ipcConnected,
|
|
|
|
|
hostProcessAlive: true,
|
|
|
|
|
lastStage,
|
|
|
|
|
lastStageMessage,
|
|
|
|
|
activationFailureReason,
|
|
|
|
|
softTimeoutShown,
|
|
|
|
|
recoveryActivationAttempted: false)),
|
|
|
|
|
BuildAttemptDetails(
|
|
|
|
|
trackedAttempt,
|
|
|
|
|
attachedToExistingAttempt,
|
|
|
|
|
ipcConnected,
|
|
|
|
|
hostProcessAlive: true,
|
|
|
|
|
lastStage,
|
|
|
|
|
lastStageMessage,
|
|
|
|
|
activationFailureReason,
|
|
|
|
|
softTimeoutShown,
|
|
|
|
|
recoveryActivationAttempted: false));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
launchOutcome = await LaunchHostWithIpcAsync().ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
if (!launchOutcome.Result.Success)
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
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
|
|
|
return WithAdditionalDetails(launchOutcome.Result, launcherContextDetails);
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
if (launchOutcome.ImmediateResult is not null)
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
await CloseWindowsAsync(splashWindow, loadingDetailsWindow).ConfigureAwait(false);
|
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
|
|
|
return WithAdditionalDetails(launchOutcome.ImmediateResult, launcherContextDetails);
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
2026-04-21 20:59:52 +08:00
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
if (launchOutcome.Process is null)
|
|
|
|
|
{
|
|
|
|
|
return BuildResult(
|
|
|
|
|
success: false,
|
|
|
|
|
stage: "launch",
|
|
|
|
|
code: "host_start_failed",
|
|
|
|
|
message: "Host launch did not create a process.",
|
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
|
|
|
details: MergeDetails(
|
|
|
|
|
launcherContextDetails,
|
|
|
|
|
MergeDetails(
|
|
|
|
|
launchOutcome.Details,
|
|
|
|
|
BuildAttemptDetails(
|
|
|
|
|
trackedAttempt,
|
|
|
|
|
attachedToExistingAttempt,
|
|
|
|
|
ipcConnected,
|
|
|
|
|
hostProcessAlive: false,
|
|
|
|
|
lastStage,
|
|
|
|
|
lastStageMessage,
|
|
|
|
|
activationFailureReason,
|
|
|
|
|
softTimeoutShown,
|
|
|
|
|
recoveryActivationAttempted: false))));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!attachedToExistingAttempt)
|
|
|
|
|
{
|
2026-04-23 09:45:05 +08:00
|
|
|
var reservedAttempt = _startupAttemptRegistry.GetOwnedAttempt();
|
|
|
|
|
trackedAttempt = reservedAttempt is { ReservedBeforeHostStart: true }
|
|
|
|
|
? _startupAttemptRegistry.AssignOwnedHostProcess(
|
|
|
|
|
launchOutcome.Process.Id,
|
|
|
|
|
lastStage,
|
|
|
|
|
lastStageMessage)
|
|
|
|
|
: _startupAttemptRegistry.StartOwnedAttempt(
|
|
|
|
|
launchOutcome.Process.Id,
|
|
|
|
|
_context.LaunchSource,
|
|
|
|
|
startupSuccessTracker.PolicyKey,
|
|
|
|
|
lastStage,
|
|
|
|
|
lastStageMessage);
|
|
|
|
|
PublishCoordinatorStatus(hostProcessAliveOverride: true);
|
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
|
|
|
Dictionary<string, string> ComposeLaunchDetails(bool hostProcessAlive, bool recoveryActivationAttempted = false)
|
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
|
|
|
return MergeDetails(
|
|
|
|
|
launcherContextDetails,
|
|
|
|
|
MergeDetails(
|
|
|
|
|
launchOutcome.Details,
|
|
|
|
|
BuildAttemptDetails(
|
|
|
|
|
trackedAttempt,
|
|
|
|
|
attachedToExistingAttempt,
|
|
|
|
|
ipcConnected,
|
|
|
|
|
hostProcessAlive,
|
|
|
|
|
lastStage,
|
|
|
|
|
lastStageMessage,
|
|
|
|
|
activationFailureReason,
|
|
|
|
|
softTimeoutShown,
|
|
|
|
|
recoveryActivationAttempted)));
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 23:07:37 +08:00
|
|
|
async Task<StartupSuccessState?> RefreshShellStatusAsync(string waitingMessage)
|
|
|
|
|
{
|
|
|
|
|
if (!ipcClient.IsConnected)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ipcConnected = true;
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedIpcConnected();
|
|
|
|
|
shellStatus = await TryGetPublicShellStatusAsync(ipcClient).ConfigureAwait(false);
|
|
|
|
|
if (startupSuccessTracker.TryResolve(shellStatus, out var successState))
|
|
|
|
|
{
|
|
|
|
|
return successState;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shellStatus is { DesktopVisible: false })
|
|
|
|
|
{
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedWaitingForShell(waitingMessage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PublishCoordinatorStatus(hostProcessAliveOverride: true);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var connected = await TryConnectToPublicIpcAsync(ipcClient, TimeSpan.FromMilliseconds(1200)).ConfigureAwait(false);
|
|
|
|
|
if (!connected)
|
|
|
|
|
{
|
2026-05-12 16:46:49 +08:00
|
|
|
Logger.Info("Host public IPC is not ready yet. Launcher will keep monitoring the host process and retry.");
|
2026-04-23 23:07:37 +08:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var shellSuccess = await RefreshShellStatusAsync("Host public IPC is ready; waiting for desktop shell.")
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
if (shellSuccess is not null)
|
|
|
|
|
{
|
|
|
|
|
successTcs.TrySetResult(shellSuccess);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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 processExitTask = launchOutcome.Process.WaitForExitAsync();
|
|
|
|
|
var startedAt = trackedAttempt?.StartedAtUtc ?? DateTimeOffset.UtcNow;
|
|
|
|
|
var softTimeoutAt = startedAt + StartupSoftTimeout;
|
|
|
|
|
var hardTimeoutAt = startedAt + StartupHardTimeout;
|
2026-04-23 23:07:37 +08:00
|
|
|
var nextReconnectAttemptAt = DateTimeOffset.UtcNow.AddSeconds(2);
|
|
|
|
|
var nextShellStatusPollAt = DateTimeOffset.UtcNow.AddSeconds(1);
|
2026-04-23 19:04:39 +08:00
|
|
|
var activationRetryAttempted = 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
|
|
|
|
|
|
|
|
while (true)
|
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 (successTcs.Task.IsCompleted)
|
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 successState = await successTcs.Task.ConfigureAwait(false);
|
|
|
|
|
windowsClosingByCoordinator = true;
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedSucceeded(successState.Stage, successState.Message);
|
2026-04-23 09:45:05 +08:00
|
|
|
PublishCoordinatorStatus(!launchOutcome.Process.HasExited, completed: true, succeeded: true);
|
2026-04-22 07:31:54 +08:00
|
|
|
await CloseWindowsAsync(splashWindow, loadingDetailsWindow).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
|
|
|
return BuildResult(
|
|
|
|
|
success: true,
|
|
|
|
|
stage: "launch",
|
|
|
|
|
code: successState.Code,
|
|
|
|
|
message: successState.Message,
|
|
|
|
|
details: ComposeLaunchDetails(!launchOutcome.Process.HasExited));
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
2026-04-22 07:31:54 +08:00
|
|
|
|
2026-04-23 19:04:39 +08:00
|
|
|
if (activationFailedTcs.Task.IsCompleted && !activationRetryAttempted)
|
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-23 19:04:39 +08:00
|
|
|
activationRetryAttempted = true;
|
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
|
|
|
activationFailureReason = await activationFailedTcs.Task.ConfigureAwait(false);
|
|
|
|
|
Logger.Warn($"Activation failure received before startup success. Reason='{activationFailureReason}'.");
|
2026-04-23 23:07:37 +08:00
|
|
|
var activationRecovery = await TryRecoverActivationThroughExistingHostAsync(
|
|
|
|
|
ipcClient,
|
|
|
|
|
startupSuccessTracker,
|
|
|
|
|
TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
|
|
|
|
if (activationRecovery is not null)
|
|
|
|
|
{
|
|
|
|
|
windowsClosingByCoordinator = true;
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedSucceeded(activationRecovery.Stage, activationRecovery.Message);
|
|
|
|
|
PublishCoordinatorStatus(
|
|
|
|
|
hostProcessAliveOverride: !launchOutcome.Process.HasExited,
|
|
|
|
|
completed: true,
|
|
|
|
|
succeeded: true);
|
|
|
|
|
await CloseWindowsAsync(splashWindow, loadingDetailsWindow).ConfigureAwait(false);
|
|
|
|
|
return BuildResult(
|
|
|
|
|
success: true,
|
|
|
|
|
stage: "launch",
|
|
|
|
|
code: activationRecovery.Code,
|
|
|
|
|
message: activationRecovery.Message,
|
|
|
|
|
details: ComposeLaunchDetails(
|
|
|
|
|
!launchOutcome.Process.HasExited,
|
|
|
|
|
recoveryActivationAttempted: true));
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 16:46:49 +08:00
|
|
|
Logger.Info("Activation failure did not recover through public IPC yet. Launcher will keep monitoring the current host attempt.");
|
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-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 (processExitTask.IsCompleted)
|
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
|
|
|
var exitCode = launchOutcome.Process.ExitCode;
|
|
|
|
|
Logger.Warn($"Host exited before startup success criteria were met. ExitCode={exitCode}.");
|
|
|
|
|
|
|
|
|
|
windowsClosingByCoordinator = true;
|
2026-05-12 16:46:49 +08:00
|
|
|
if (IsSuccessfulActivationExitCode(exitCode))
|
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
|
|
|
_startupAttemptRegistry.MarkOwnedSucceeded(StartupStage.ActivationRedirected, "Host redirected activation to the existing desktop instance.");
|
2026-04-23 09:45:05 +08:00
|
|
|
PublishCoordinatorStatus(hostProcessAliveOverride: false, completed: true, succeeded: true);
|
2026-04-22 07:31:54 +08:00
|
|
|
await CloseWindowsAsync(splashWindow, loadingDetailsWindow).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
|
|
|
return BuildResult(
|
|
|
|
|
success: true,
|
|
|
|
|
stage: "launch",
|
|
|
|
|
code: "activation_redirected",
|
|
|
|
|
message: "Host redirected activation to the existing desktop instance.",
|
|
|
|
|
details: MergeDetails(
|
|
|
|
|
ComposeLaunchDetails(hostProcessAlive: false),
|
|
|
|
|
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
{
|
|
|
|
|
["exitCode"] = exitCode.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
|
|
|
|
2026-04-23 19:04:39 +08:00
|
|
|
if (!activationRetryAttempted &&
|
2026-05-12 16:46:49 +08:00
|
|
|
IsFailedActivationExitCode(exitCode))
|
2026-04-23 19:04:39 +08:00
|
|
|
{
|
|
|
|
|
activationRetryAttempted = true;
|
2026-04-23 23:07:37 +08:00
|
|
|
var activationRecovery = await TryRecoverActivationThroughExistingHostAsync(
|
|
|
|
|
ipcClient,
|
|
|
|
|
startupSuccessTracker,
|
|
|
|
|
TimeSpan.FromSeconds(2)).ConfigureAwait(false);
|
|
|
|
|
if (activationRecovery is not null)
|
|
|
|
|
{
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedSucceeded(activationRecovery.Stage, activationRecovery.Message);
|
|
|
|
|
PublishCoordinatorStatus(hostProcessAliveOverride: true, completed: true, succeeded: true);
|
|
|
|
|
await CloseWindowsAsync(splashWindow, loadingDetailsWindow).ConfigureAwait(false);
|
|
|
|
|
return BuildResult(
|
|
|
|
|
success: true,
|
|
|
|
|
stage: "launch",
|
|
|
|
|
code: activationRecovery.Code,
|
|
|
|
|
message: activationRecovery.Message,
|
|
|
|
|
details: MergeDetails(
|
|
|
|
|
ComposeLaunchDetails(hostProcessAlive: true, recoveryActivationAttempted: true),
|
|
|
|
|
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
{
|
|
|
|
|
["exitCode"] = exitCode.ToString()
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 16:46:49 +08:00
|
|
|
Logger.Info("Activation exit code did not recover through public IPC. Launcher will report the activation failure without launching another host.");
|
2026-04-23 19:04:39 +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
|
|
|
_startupAttemptRegistry.MarkOwnedFailed(lastStage, activationFailureReason);
|
2026-04-23 09:45:05 +08:00
|
|
|
PublishCoordinatorStatus(hostProcessAliveOverride: false, completed: true, succeeded: 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
|
|
|
await CloseWindowsAsync(splashWindow, loadingDetailsWindow).ConfigureAwait(false);
|
|
|
|
|
return BuildResult(
|
|
|
|
|
success: false,
|
|
|
|
|
stage: "launch",
|
2026-05-12 16:46:49 +08:00
|
|
|
code: IsFailedActivationExitCode(exitCode)
|
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
|
|
|
? "activation_failed"
|
|
|
|
|
: "host_exited_early",
|
2026-05-12 16:46:49 +08:00
|
|
|
message: IsFailedActivationExitCode(exitCode)
|
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
|
|
|
? $"Host activation handshake failed before the required startup state was reported. ExitCode={exitCode}."
|
|
|
|
|
: $"Host exited before the required startup state was reported. ExitCode={exitCode}.",
|
|
|
|
|
details: MergeDetails(
|
|
|
|
|
ComposeLaunchDetails(hostProcessAlive: false),
|
|
|
|
|
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
{
|
|
|
|
|
["exitCode"] = exitCode.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
|
|
|
var now = DateTimeOffset.UtcNow;
|
2026-04-23 23:07:37 +08:00
|
|
|
if (ipcConnected &&
|
|
|
|
|
!launchOutcome.Process.HasExited &&
|
|
|
|
|
now >= nextShellStatusPollAt)
|
|
|
|
|
{
|
|
|
|
|
var shellSuccess = await RefreshShellStatusAsync("Host public IPC is ready; waiting for desktop shell.")
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
if (shellSuccess is not null)
|
|
|
|
|
{
|
|
|
|
|
successTcs.TrySetResult(shellSuccess);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nextShellStatusPollAt = DateTimeOffset.UtcNow.AddSeconds(1);
|
|
|
|
|
}
|
|
|
|
|
|
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 (!ipcConnected &&
|
|
|
|
|
!launchOutcome.Process.HasExited &&
|
|
|
|
|
now >= nextReconnectAttemptAt)
|
|
|
|
|
{
|
|
|
|
|
connected = await TryConnectToPublicIpcAsync(ipcClient, TimeSpan.FromMilliseconds(800)).ConfigureAwait(false);
|
|
|
|
|
if (connected)
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
2026-04-23 23:07:37 +08:00
|
|
|
var shellSuccess = await RefreshShellStatusAsync("Host public IPC reconnected; waiting for desktop shell.")
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
if (shellSuccess is not null)
|
2026-04-23 19:04:39 +08:00
|
|
|
{
|
2026-04-23 23:07:37 +08:00
|
|
|
successTcs.TrySetResult(shellSuccess);
|
|
|
|
|
continue;
|
2026-04-23 19:04:39 +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
|
|
|
}
|
|
|
|
|
|
2026-04-23 23:07:37 +08:00
|
|
|
nextReconnectAttemptAt = DateTimeOffset.UtcNow.AddSeconds(2);
|
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 (!softTimeoutShown &&
|
|
|
|
|
now >= softTimeoutAt &&
|
|
|
|
|
(!launchOutcome.Process.HasExited || ipcConnected))
|
|
|
|
|
{
|
|
|
|
|
softTimeoutShown = true;
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedSoftTimeout(SoftTimeoutStatusMessage);
|
|
|
|
|
reporter.Report("delayed", SoftTimeoutStatusMessage);
|
|
|
|
|
loadingState = BuildDelayedLoadingState(
|
|
|
|
|
loadingState,
|
|
|
|
|
SoftTimeoutStatusMessage,
|
|
|
|
|
SoftTimeoutDetailsMessage,
|
|
|
|
|
trackedAttempt?.StartedAtUtc ?? startedAt);
|
|
|
|
|
loadingDetailsWindow?.UpdateLoadingState(loadingState);
|
2026-04-23 09:45:05 +08:00
|
|
|
PublishCoordinatorStatus(hostProcessAliveOverride: !launchOutcome.Process.HasExited);
|
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 (now >= hardTimeoutAt)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var nextCheckpointAt = hardTimeoutAt;
|
|
|
|
|
if (!softTimeoutShown && softTimeoutAt < nextCheckpointAt)
|
|
|
|
|
{
|
|
|
|
|
nextCheckpointAt = softTimeoutAt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var delay = nextCheckpointAt - now;
|
|
|
|
|
if (delay > TimeSpan.FromSeconds(1))
|
|
|
|
|
{
|
|
|
|
|
delay = TimeSpan.FromSeconds(1);
|
|
|
|
|
}
|
|
|
|
|
else if (delay < TimeSpan.FromMilliseconds(100))
|
|
|
|
|
{
|
|
|
|
|
delay = TimeSpan.FromMilliseconds(100);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await Task.WhenAny(
|
|
|
|
|
successTcs.Task,
|
|
|
|
|
activationFailedTcs.Task,
|
|
|
|
|
processExitTask,
|
|
|
|
|
Task.Delay(delay)).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var recoveryActivationAttempted = false;
|
|
|
|
|
if (!connected && !launchOutcome.Process.HasExited)
|
|
|
|
|
{
|
|
|
|
|
connected = await TryConnectToPublicIpcAsync(ipcClient, TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
|
|
|
|
if (connected)
|
|
|
|
|
{
|
2026-04-23 23:07:37 +08:00
|
|
|
var shellSuccess = await RefreshShellStatusAsync("Host public IPC is ready; waiting for desktop shell.")
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
if (shellSuccess is not null)
|
2026-04-23 19:04:39 +08:00
|
|
|
{
|
2026-04-23 23:07:37 +08:00
|
|
|
windowsClosingByCoordinator = true;
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedSucceeded(shellSuccess.Stage, shellSuccess.Message);
|
|
|
|
|
PublishCoordinatorStatus(hostProcessAliveOverride: true, completed: true, succeeded: true);
|
|
|
|
|
await CloseWindowsAsync(splashWindow, loadingDetailsWindow).ConfigureAwait(false);
|
|
|
|
|
return BuildResult(
|
|
|
|
|
success: true,
|
|
|
|
|
stage: "launch",
|
|
|
|
|
code: shellSuccess.Code,
|
|
|
|
|
message: shellSuccess.Message,
|
|
|
|
|
details: ComposeLaunchDetails(hostProcessAlive: true));
|
2026-04-23 19:04:39 +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
|
|
|
}
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 00:27:01 +08:00
|
|
|
if (connected && !launchOutcome.Process.HasExited)
|
|
|
|
|
{
|
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
|
|
|
recoveryActivationAttempted = true;
|
2026-04-23 00:27:01 +08:00
|
|
|
var recoveryOutcome = await TryRecoverWithPublicActivationAsync(
|
|
|
|
|
ipcClient,
|
|
|
|
|
launchOutcome.Process,
|
|
|
|
|
successTcs.Task,
|
|
|
|
|
startupSuccessTracker).ConfigureAwait(false);
|
|
|
|
|
if (recoveryOutcome is not 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
|
|
|
windowsClosingByCoordinator = true;
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedSucceeded(recoveryOutcome.Stage, recoveryOutcome.Message);
|
2026-04-23 09:45:05 +08:00
|
|
|
shellStatus = await TryGetPublicShellStatusAsync(ipcClient).ConfigureAwait(false);
|
|
|
|
|
PublishCoordinatorStatus(!launchOutcome.Process.HasExited, completed: true, succeeded: true);
|
2026-04-23 00:27:01 +08:00
|
|
|
await CloseWindowsAsync(splashWindow, loadingDetailsWindow).ConfigureAwait(false);
|
|
|
|
|
return BuildResult(
|
|
|
|
|
success: true,
|
|
|
|
|
stage: "launch",
|
|
|
|
|
code: recoveryOutcome.Code,
|
|
|
|
|
message: recoveryOutcome.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
|
|
|
details: ComposeLaunchDetails(
|
|
|
|
|
!launchOutcome.Process.HasExited,
|
|
|
|
|
recoveryActivationAttempted: true));
|
2026-04-23 00:27:01 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 19:04:39 +08:00
|
|
|
if (connected && !launchOutcome.Process.HasExited)
|
|
|
|
|
{
|
|
|
|
|
windowsClosingByCoordinator = true;
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedWaitingForShell("Host process is still running after the launcher wait window.");
|
|
|
|
|
shellStatus = await TryGetPublicShellStatusAsync(ipcClient).ConfigureAwait(false);
|
2026-04-23 23:07:37 +08:00
|
|
|
if (startupSuccessTracker.TryResolve(shellStatus, out var finalShellSuccess))
|
|
|
|
|
{
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedSucceeded(finalShellSuccess.Stage, finalShellSuccess.Message);
|
|
|
|
|
PublishCoordinatorStatus(hostProcessAliveOverride: true, completed: true, succeeded: true);
|
|
|
|
|
await CloseWindowsAsync(splashWindow, loadingDetailsWindow).ConfigureAwait(false);
|
|
|
|
|
return BuildResult(
|
|
|
|
|
success: true,
|
|
|
|
|
stage: "launch",
|
|
|
|
|
code: finalShellSuccess.Code,
|
|
|
|
|
message: finalShellSuccess.Message,
|
|
|
|
|
details: ComposeLaunchDetails(
|
|
|
|
|
hostProcessAlive: true,
|
|
|
|
|
recoveryActivationAttempted));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PublishCoordinatorStatus(hostProcessAliveOverride: true, completed: true, succeeded: false);
|
|
|
|
|
await CloseWindowsAsync(splashWindow, loadingDetailsWindow).ConfigureAwait(false);
|
|
|
|
|
return BuildResult(
|
|
|
|
|
success: false,
|
|
|
|
|
stage: "launch",
|
|
|
|
|
code: "shell_not_ready",
|
|
|
|
|
message: "Host public IPC is connected, but the desktop shell did not create or show the main window in time.",
|
|
|
|
|
details: ComposeLaunchDetails(
|
|
|
|
|
hostProcessAlive: true,
|
|
|
|
|
recoveryActivationAttempted));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!connected && !launchOutcome.Process.HasExited)
|
|
|
|
|
{
|
|
|
|
|
windowsClosingByCoordinator = true;
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedWaitingForShell("Host process is still running, but public IPC is not ready yet.");
|
|
|
|
|
PublishCoordinatorStatus(hostProcessAliveOverride: true, completed: false, succeeded: true);
|
2026-04-23 19:04:39 +08:00
|
|
|
await CloseWindowsAsync(splashWindow, loadingDetailsWindow).ConfigureAwait(false);
|
|
|
|
|
return BuildResult(
|
|
|
|
|
success: true,
|
|
|
|
|
stage: "launch",
|
|
|
|
|
code: "startup_pending",
|
2026-04-23 23:07:37 +08:00
|
|
|
message: "Host process is still running; Launcher will not start another process while public IPC finishes startup.",
|
2026-04-23 19:04:39 +08:00
|
|
|
details: ComposeLaunchDetails(
|
|
|
|
|
hostProcessAlive: true,
|
|
|
|
|
recoveryActivationAttempted));
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
windowsClosingByCoordinator = true;
|
|
|
|
|
_startupAttemptRegistry.MarkOwnedFailed(lastStage, activationFailureReason);
|
2026-04-23 09:45:05 +08:00
|
|
|
PublishCoordinatorStatus(!launchOutcome.Process.HasExited, completed: true, succeeded: false);
|
2026-04-22 07:31:54 +08:00
|
|
|
await CloseWindowsAsync(splashWindow, loadingDetailsWindow).ConfigureAwait(false);
|
|
|
|
|
return BuildResult(
|
|
|
|
|
success: false,
|
|
|
|
|
stage: "launch",
|
|
|
|
|
code: "desktop_not_visible",
|
2026-04-23 23:07:37 +08:00
|
|
|
message: $"Host process started, but it never reached the required startup state within {StartupHardTimeout.TotalSeconds:0} seconds.",
|
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
|
|
|
details: ComposeLaunchDetails(
|
|
|
|
|
!launchOutcome.Process.HasExited,
|
|
|
|
|
recoveryActivationAttempted));
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
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 (splashClosedHandler is not null)
|
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
|
|
|
splashWindow.Closed -= splashClosedHandler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!windowsClosingByCoordinator)
|
|
|
|
|
{
|
|
|
|
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
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
|
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
|
|
|
if (splashWindow.IsVisible && splashWindow.IsLoaded)
|
|
|
|
|
{
|
|
|
|
|
splashWindow.Close();
|
|
|
|
|
Logger.Info("Splash window closed in coordinator cleanup.");
|
|
|
|
|
}
|
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
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Logger.Error("Failed to close splash window during coordinator cleanup.", ex);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Error("Launcher coordinator failed.", ex);
|
|
|
|
|
return BuildResult(
|
|
|
|
|
success: false,
|
|
|
|
|
stage: "launch",
|
|
|
|
|
code: "exception",
|
|
|
|
|
message: ex.Message,
|
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
|
|
|
details: BuildLauncherContextDetails(_context, _oobeStateService.Evaluate(_context), _deploymentLocator.GetAppRoot()),
|
2026-04-22 07:31:54 +08:00
|
|
|
errorMessage: ex.ToString());
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task CloseWindowsAsync(SplashWindow splashWindow, LoadingDetailsWindow? loadingDetailsWindow)
|
|
|
|
|
{
|
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
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
Launcher fix (#6)
* fix.hy3试图修复中
* Resolve dev paths and fix splash UI thread
Compute a solutionRoot and expand development search paths (LanMountainDesktop and dev-test) in DeploymentLocator, add logging when scanning/finding hosts, and return distinct full paths. Ensure backward-compatible path checks. Fix cross-thread UI calls: invoke splashWindow.DismissAsync on the UI thread in LauncherFlowCoordinator, and make SplashWindow.DismissAsync ensure it runs on the UI thread before closing (simplified Close call). These changes improve development host discovery and prevent UI-thread access issues during shutdown.
* Add configurable data location (portable/system)
Introduce support for choosing and resolving the application's data root (system user dir vs. portable app folder). Adds DataLocationConfig model, DataLocationResolver (load/save/resolve/migrate), a UI prompt (DataLocationPromptWindow) and an OOBE step (DataLocationOobeStep) to let users pick and optionally migrate existing data. Wire the chosen data root into the launcher flow and host launch plan (forwarded via --data-root and LMD_DATA_ROOT), and add AppDataPathProvider to let runtime services read the effective data root (initialized in Program.Main). Update various services (logging, settings, DB, plugin/market, startup registry, etc.) to use the new provider/resolver and register the config type in the JSON context. This enables portable installs, safe migration, and runtime overrides via CLI or environment variable.
* Add dev/debug startup flow and launch profiles
Handle design-time initialization and add a developer debug startup path: App now skips normal startup when in design mode and shows a DevDebugWindow when running in debug (unless a preview or apply-update command). CommandContext.IsDebugMode is extended to include DOTNET_ENVIRONMENT=Development via a new IsDevelopmentEnvironment helper. Program.Main and BuildAvaloniaApp are made public to aid tooling. Added multiple launchSettings profiles for debug and preview commands that set DOTNET_ENVIRONMENT=Development to simplify IDE debugging and UI previewing.
* Simplify splash to fade; add themed about banners
Simplify splash startup visuals by removing the multi-mode/slide behavior and always using a fade animation. Update App to create SplashWindow without a StartupVisualMode parameter and remove related fields, layout configuration, slide animation, and easing helpers from SplashWindow. Clean up unused using. Replace the single about_banner asset with theme-aware variants (about_banner_dark.png and about_banner_light.png), delete the old about_banner.png, and update AboutSettingsPage to use a DynamicResource ImageBrush (AboutBannerBrush) that selects the appropriate banner per theme.
* Use AppJsonContext for startup state serialization
Switch serialization to the source-generated System.Text.Json context: add JsonSerializable(typeof(StartupAttemptRecord)) to AppJsonContext and replace the previous JsonSerializerOptions-based Serialize/Deserialize calls with AppJsonContext.Default.StartupAttemptRecord. Also remove the now-unused SerializerOptions field. Additionally, update .gitignore to exclude /test-aot-publish.
* Add OOBE redesign, theme & data location support
Introduce a redesigned OOBE flow and data-location/theme support across the launcher. Adds a new ThemeService for applying light/dark and accent colors; integrates FluentIcons.Avalonia package for icons. Overhauls OobeWindow (UX animations, typing effect, multi-step theme and data-location pages, Monet options, and final welcome step) and its code-behind to handle step navigation, accent selection, and data-location resolution. Adds DataLocation UI and handlers (DataLocationPromptWindow changes, DataLocation resolver usage) and wires a DevDebug UI for toggling/opening the data-location page. UpdateEngineService now resolves the launcher root via DataLocationResolver. Misc: update various view models, localization entries and remove TrimmerRoots.xml.
* Refactor data location paths and add background service
Refactor DataLocationResolver to centralize data path resolution (ResolveLauncherDataPath, ResolveDesktopDataPath, ResolveConfigPath, ResolveLauncherLogsPath, ResolveLauncherStatePath) and replace usages of the previous ".launcher" layout with a "Launcher" folder. Update API: LoadConfig/SaveConfig reorganized and ApplyLocationChoice now accepts an optional custom path and migration flag; migration logic updated accordingly. Update dependent services and views (Logger, DeploymentLocator, UpdateEngineService, OobeStateService, StartupAttemptRegistry, LauncherDebugSettingsStore, OobeWindow) to use the new resolver APIs and paths. Add LauncherBackgroundService to load/validate/cache a custom splash background image and wire it into SplashWindow (AXAML/Axaml.cs) with UI placeholders and overlay. Misc: minor cleanup of Oobe/Splash XAML and related code adjustments and logging improvements.
2026-04-25 18:41:26 +08:00
|
|
|
await Dispatcher.UIThread.InvokeAsync(() => splashWindow.DismissAsync());
|
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.Error("Failed to dismiss splash window.", ex);
|
|
|
|
|
}
|
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
|
|
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
|
|
|
|
{
|
2026-04-21 20:59:52 +08:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (loadingDetailsWindow is not null && loadingDetailsWindow.IsVisible)
|
|
|
|
|
{
|
|
|
|
|
loadingDetailsWindow.Close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Error("Failed to close loading details window.", ex);
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
private async Task<HostLaunchOutcome> LaunchHostWithIpcAsync(bool forceDirectMode = false, string? retryTag = null)
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
var resolution = _deploymentLocator.ResolveHostExecutable(_context);
|
|
|
|
|
if (!resolution.Success || string.IsNullOrWhiteSpace(resolution.ResolvedHostPath))
|
|
|
|
|
{
|
|
|
|
|
var (errorResult, selectedPath) = await ShowHostNotFoundErrorAsync().ConfigureAwait(false);
|
2026-04-21 20:59:52 +08:00
|
|
|
if (errorResult == ErrorWindowResult.Retry)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
if (!string.IsNullOrWhiteSpace(selectedPath) && File.Exists(selectedPath))
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
return await LaunchHostWithExplicitPathAsync(selectedPath, forceDirectMode, retryTag).ConfigureAwait(false);
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
2026-04-22 07:31:54 +08:00
|
|
|
|
|
|
|
|
return await LaunchHostWithIpcAsync(forceDirectMode, retryTag).ConfigureAwait(false);
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
2026-04-22 07:31:54 +08:00
|
|
|
|
|
|
|
|
return HostLaunchOutcome.FromResult(BuildResult(
|
|
|
|
|
success: false,
|
|
|
|
|
stage: "launchHost",
|
|
|
|
|
code: "host_not_found",
|
|
|
|
|
message: "LanMountainDesktop host executable was not found.",
|
|
|
|
|
details: BuildResolutionDetails(resolution, null, null, "resolve")));
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
return await LaunchHostWithResolvedPathAsync(resolution, forceDirectMode, retryTag).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Task<HostLaunchOutcome> LaunchHostWithExplicitPathAsync(string hostPath, bool forceDirectMode, string? retryTag)
|
|
|
|
|
{
|
|
|
|
|
var resolution = new HostResolutionResult
|
|
|
|
|
{
|
|
|
|
|
Success = true,
|
|
|
|
|
ResolvedHostPath = Path.GetFullPath(hostPath),
|
|
|
|
|
ResolutionSource = "user_selected_path",
|
|
|
|
|
AppRoot = _deploymentLocator.GetAppRoot(),
|
|
|
|
|
ExplicitAppRoot = Path.GetDirectoryName(hostPath),
|
|
|
|
|
SearchedPaths = [Path.GetFullPath(hostPath)]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return LaunchHostWithResolvedPathAsync(resolution, forceDirectMode, retryTag);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<HostLaunchOutcome> LaunchHostWithResolvedPathAsync(
|
|
|
|
|
HostResolutionResult resolution,
|
|
|
|
|
bool forceDirectMode,
|
|
|
|
|
string? retryTag)
|
|
|
|
|
{
|
Launcher fix (#6)
* fix.hy3试图修复中
* Resolve dev paths and fix splash UI thread
Compute a solutionRoot and expand development search paths (LanMountainDesktop and dev-test) in DeploymentLocator, add logging when scanning/finding hosts, and return distinct full paths. Ensure backward-compatible path checks. Fix cross-thread UI calls: invoke splashWindow.DismissAsync on the UI thread in LauncherFlowCoordinator, and make SplashWindow.DismissAsync ensure it runs on the UI thread before closing (simplified Close call). These changes improve development host discovery and prevent UI-thread access issues during shutdown.
* Add configurable data location (portable/system)
Introduce support for choosing and resolving the application's data root (system user dir vs. portable app folder). Adds DataLocationConfig model, DataLocationResolver (load/save/resolve/migrate), a UI prompt (DataLocationPromptWindow) and an OOBE step (DataLocationOobeStep) to let users pick and optionally migrate existing data. Wire the chosen data root into the launcher flow and host launch plan (forwarded via --data-root and LMD_DATA_ROOT), and add AppDataPathProvider to let runtime services read the effective data root (initialized in Program.Main). Update various services (logging, settings, DB, plugin/market, startup registry, etc.) to use the new provider/resolver and register the config type in the JSON context. This enables portable installs, safe migration, and runtime overrides via CLI or environment variable.
* Add dev/debug startup flow and launch profiles
Handle design-time initialization and add a developer debug startup path: App now skips normal startup when in design mode and shows a DevDebugWindow when running in debug (unless a preview or apply-update command). CommandContext.IsDebugMode is extended to include DOTNET_ENVIRONMENT=Development via a new IsDevelopmentEnvironment helper. Program.Main and BuildAvaloniaApp are made public to aid tooling. Added multiple launchSettings profiles for debug and preview commands that set DOTNET_ENVIRONMENT=Development to simplify IDE debugging and UI previewing.
* Simplify splash to fade; add themed about banners
Simplify splash startup visuals by removing the multi-mode/slide behavior and always using a fade animation. Update App to create SplashWindow without a StartupVisualMode parameter and remove related fields, layout configuration, slide animation, and easing helpers from SplashWindow. Clean up unused using. Replace the single about_banner asset with theme-aware variants (about_banner_dark.png and about_banner_light.png), delete the old about_banner.png, and update AboutSettingsPage to use a DynamicResource ImageBrush (AboutBannerBrush) that selects the appropriate banner per theme.
* Use AppJsonContext for startup state serialization
Switch serialization to the source-generated System.Text.Json context: add JsonSerializable(typeof(StartupAttemptRecord)) to AppJsonContext and replace the previous JsonSerializerOptions-based Serialize/Deserialize calls with AppJsonContext.Default.StartupAttemptRecord. Also remove the now-unused SerializerOptions field. Additionally, update .gitignore to exclude /test-aot-publish.
* Add OOBE redesign, theme & data location support
Introduce a redesigned OOBE flow and data-location/theme support across the launcher. Adds a new ThemeService for applying light/dark and accent colors; integrates FluentIcons.Avalonia package for icons. Overhauls OobeWindow (UX animations, typing effect, multi-step theme and data-location pages, Monet options, and final welcome step) and its code-behind to handle step navigation, accent selection, and data-location resolution. Adds DataLocation UI and handlers (DataLocationPromptWindow changes, DataLocation resolver usage) and wires a DevDebug UI for toggling/opening the data-location page. UpdateEngineService now resolves the launcher root via DataLocationResolver. Misc: update various view models, localization entries and remove TrimmerRoots.xml.
* Refactor data location paths and add background service
Refactor DataLocationResolver to centralize data path resolution (ResolveLauncherDataPath, ResolveDesktopDataPath, ResolveConfigPath, ResolveLauncherLogsPath, ResolveLauncherStatePath) and replace usages of the previous ".launcher" layout with a "Launcher" folder. Update API: LoadConfig/SaveConfig reorganized and ApplyLocationChoice now accepts an optional custom path and migration flag; migration logic updated accordingly. Update dependent services and views (Logger, DeploymentLocator, UpdateEngineService, OobeStateService, StartupAttemptRegistry, LauncherDebugSettingsStore, OobeWindow) to use the new resolver APIs and paths. Add LauncherBackgroundService to load/validate/cache a custom splash background image and wire it into SplashWindow (AXAML/Axaml.cs) with UI placeholders and overlay. Misc: minor cleanup of Oobe/Splash XAML and related code adjustments and logging improvements.
2026-04-25 18:41:26 +08:00
|
|
|
var dataRoot = _dataLocationResolver.ResolveDataRoot();
|
|
|
|
|
var plan = HostLaunchPlanBuilder.Build(_context, _deploymentLocator, resolution, dataRoot);
|
2026-04-23 23:07:37 +08:00
|
|
|
var hostPath = plan.HostPath;
|
2026-04-21 20:59:52 +08:00
|
|
|
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
|
|
|
|
{
|
|
|
|
|
EnsureExecutable(hostPath);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 23:07:37 +08:00
|
|
|
var primaryMode = HostStartMode.Direct;
|
|
|
|
|
var fallbackMode = !forceDirectMode && OperatingSystem.IsWindows()
|
|
|
|
|
? HostStartMode.ShellExecute
|
2026-04-22 07:31:54 +08:00
|
|
|
: (HostStartMode?)null;
|
|
|
|
|
|
2026-04-23 23:07:37 +08:00
|
|
|
var firstAttempt = await StartHostProcessAsync(plan, primaryMode, retryTag).ConfigureAwait(false);
|
|
|
|
|
if (firstAttempt.ProcessCreated && firstAttempt.Process is not null)
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
|
|
|
|
var firstDetails = BuildResolutionDetails(resolution, firstAttempt, null, null);
|
|
|
|
|
return HostLaunchOutcome.FromProcess(
|
|
|
|
|
firstAttempt.Process,
|
|
|
|
|
BuildResult(true, "launchHost", "ok", "Host launched.", firstDetails),
|
|
|
|
|
firstDetails);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fallbackMode is null)
|
|
|
|
|
{
|
|
|
|
|
return BuildOutcomeFromAttempt(resolution, firstAttempt, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Logger.Warn(
|
|
|
|
|
$"Primary host start attempt failed. Retrying with fallback mode '{fallbackMode}'. " +
|
|
|
|
|
$"FailureReason='{firstAttempt.FailureReason ?? "unknown"}'; ExitCode='{firstAttempt.ExitCode?.ToString() ?? "<none>"}'.");
|
|
|
|
|
|
2026-04-23 23:07:37 +08:00
|
|
|
var secondAttempt = await StartHostProcessAsync(plan, fallbackMode.Value, retryTag).ConfigureAwait(false);
|
|
|
|
|
if (secondAttempt.ProcessCreated && secondAttempt.Process is not null)
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
|
|
|
|
var details = BuildResolutionDetails(resolution, firstAttempt, secondAttempt, null);
|
|
|
|
|
return HostLaunchOutcome.FromProcess(
|
|
|
|
|
secondAttempt.Process,
|
|
|
|
|
BuildResult(true, "launchHost", "ok", "Host launched.", details),
|
|
|
|
|
details);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return BuildOutcomeFromAttempt(resolution, secondAttempt, firstAttempt);
|
|
|
|
|
}
|
2026-04-21 20:59:52 +08:00
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
private static HostLaunchOutcome BuildOutcomeFromAttempt(
|
|
|
|
|
HostResolutionResult resolution,
|
|
|
|
|
HostStartAttempt finalAttempt,
|
|
|
|
|
HostStartAttempt? previousAttempt)
|
|
|
|
|
{
|
|
|
|
|
var details = BuildResolutionDetails(
|
|
|
|
|
resolution,
|
|
|
|
|
previousAttempt ?? finalAttempt,
|
|
|
|
|
previousAttempt is null ? null : finalAttempt,
|
|
|
|
|
!finalAttempt.ProcessCreated
|
|
|
|
|
? "start"
|
2026-05-12 16:46:49 +08:00
|
|
|
: finalAttempt.ExitCode is int finalExitCode && IsFailedActivationExitCode(finalExitCode)
|
2026-04-22 07:31:54 +08:00
|
|
|
? "activation"
|
|
|
|
|
: "early-exit");
|
|
|
|
|
|
|
|
|
|
if (!finalAttempt.ProcessCreated)
|
|
|
|
|
{
|
|
|
|
|
return HostLaunchOutcome.FromResult(BuildResult(
|
|
|
|
|
false,
|
|
|
|
|
"launchHost",
|
|
|
|
|
"host_start_failed",
|
|
|
|
|
$"Failed to start host using start mode '{finalAttempt.StartMode}'.",
|
|
|
|
|
details));
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 16:46:49 +08:00
|
|
|
if (finalAttempt.ExitCode is not null && IsSuccessfulActivationExitCode(finalAttempt.ExitCode.Value))
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
|
|
|
|
return HostLaunchOutcome.FromImmediateResult(BuildResult(
|
|
|
|
|
true,
|
|
|
|
|
"launch",
|
|
|
|
|
"activation_redirected",
|
|
|
|
|
"Launcher activation was redirected to the existing desktop instance.",
|
|
|
|
|
details));
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 16:46:49 +08:00
|
|
|
if (finalAttempt.ExitCode is not null && IsFailedActivationExitCode(finalAttempt.ExitCode.Value))
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
|
|
|
|
return HostLaunchOutcome.FromResult(BuildResult(
|
|
|
|
|
false,
|
|
|
|
|
"launch",
|
|
|
|
|
"activation_failed",
|
|
|
|
|
$"Host activation handshake failed using start mode '{finalAttempt.StartMode}'.",
|
|
|
|
|
details));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return HostLaunchOutcome.FromResult(BuildResult(
|
|
|
|
|
false,
|
|
|
|
|
"launchHost",
|
|
|
|
|
"host_exited_early",
|
|
|
|
|
$"Host exited early using start mode '{finalAttempt.StartMode}'.",
|
|
|
|
|
details));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<HostStartAttempt> StartHostProcessAsync(
|
2026-04-23 23:07:37 +08:00
|
|
|
HostLaunchPlan plan,
|
2026-04-22 07:31:54 +08:00
|
|
|
HostStartMode startMode,
|
|
|
|
|
string? retryTag)
|
|
|
|
|
{
|
|
|
|
|
var startInfo = new ProcessStartInfo
|
|
|
|
|
{
|
2026-04-23 23:07:37 +08:00
|
|
|
FileName = plan.HostPath,
|
|
|
|
|
WorkingDirectory = plan.WorkingDirectory,
|
2026-04-22 07:31:54 +08:00
|
|
|
UseShellExecute = startMode == HostStartMode.ShellExecute
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (startMode == HostStartMode.Direct)
|
|
|
|
|
{
|
2026-04-23 23:07:37 +08:00
|
|
|
foreach (var argument in plan.Arguments)
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
2026-04-23 23:07:37 +08:00
|
|
|
startInfo.ArgumentList.Add(argument);
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 23:07:37 +08:00
|
|
|
foreach (var pair in plan.EnvironmentVariables)
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
2026-04-23 23:07:37 +08:00
|
|
|
startInfo.EnvironmentVariables[pair.Key] = pair.Value;
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-23 23:07:37 +08:00
|
|
|
else
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
2026-04-23 23:07:37 +08:00
|
|
|
startInfo.Arguments = HostLaunchPlanBuilder.FormatArgumentsForLog(plan.Arguments);
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 23:07:37 +08:00
|
|
|
try
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
2026-04-23 23:07:37 +08:00
|
|
|
var process = Process.Start(startInfo);
|
|
|
|
|
Logger.Info(
|
|
|
|
|
$"Host launch requested. Mode='{startMode}'; RetryTag='{retryTag ?? "<none>"}'; Path='{plan.HostPath}'; " +
|
|
|
|
|
$"PackageRoot='{plan.PackageRoot}'; WorkingDir='{plan.WorkingDirectory}'; Pid={(process is null ? -1 : process.Id)}; " +
|
|
|
|
|
$"Args='{HostLaunchPlanBuilder.FormatArgumentsForLog(plan.Arguments)}'.");
|
2026-04-22 07:31:54 +08:00
|
|
|
|
2026-04-23 23:07:37 +08:00
|
|
|
if (process is null)
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
2026-04-23 23:07:37 +08:00
|
|
|
return HostStartAttempt.StartFailed(startMode, "process_start_returned_null", plan);
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 23:07:37 +08:00
|
|
|
await Task.Yield();
|
|
|
|
|
return HostStartAttempt.Started(startMode, process, plan);
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
2026-04-23 23:07:37 +08:00
|
|
|
catch (Exception ex)
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
2026-04-23 23:07:37 +08:00
|
|
|
Logger.Error($"Host start failed. Mode='{startMode}'.", ex);
|
|
|
|
|
return HostStartAttempt.StartFailed(startMode, ex.GetType().Name, plan);
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<(ErrorWindowResult Result, string? CustomPath)> ShowHostNotFoundErrorAsync()
|
|
|
|
|
{
|
|
|
|
|
ErrorWindow? errorWindow = null;
|
2026-04-22 07:31:54 +08:00
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
errorWindow.ConfigureForHostNotFound();
|
2026-04-22 07:31:54 +08:00
|
|
|
errorWindow.SetErrorMessage("LanMountainDesktop host executable was not found.");
|
2026-04-21 20:59:52 +08:00
|
|
|
errorWindow.Show();
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Warn("Host not found. Showing error window.");
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Error("Failed to show host-not-found error window.", 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
|
|
|
if (errorWindow is null)
|
|
|
|
|
{
|
|
|
|
|
return (ErrorWindowResult.Exit, null);
|
|
|
|
|
}
|
2026-04-22 07:31:54 +08:00
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
ErrorWindowResult result;
|
|
|
|
|
string? customPath;
|
|
|
|
|
try
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
result = await errorWindow.WaitForChoiceAsync().ConfigureAwait(false);
|
2026-04-21 20:59:52 +08:00
|
|
|
customPath = errorWindow.GetCustomHostPath();
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Info($"Host-not-found window result='{result}'; HasCustomPath={!string.IsNullOrWhiteSpace(customPath)}.");
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Error("Failed while waiting for host-not-found window result.", ex);
|
2026-04-21 20:59:52 +08:00
|
|
|
result = ErrorWindowResult.Exit;
|
|
|
|
|
customPath = null;
|
|
|
|
|
}
|
2026-04-22 07:31:54 +08:00
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (errorWindow.IsVisible && errorWindow.IsLoaded)
|
|
|
|
|
{
|
|
|
|
|
errorWindow.Close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Error("Failed to close host-not-found error window.", 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
|
|
|
return (result, customPath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<MigrationResult> ShowMigrationPromptAsync(LegacyVersionInfo legacyInfo)
|
|
|
|
|
{
|
|
|
|
|
MigrationPromptWindow? migrationWindow = null;
|
|
|
|
|
|
|
|
|
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
migrationWindow = new MigrationPromptWindow();
|
|
|
|
|
migrationWindow.SetLegacyInfo(legacyInfo);
|
|
|
|
|
migrationWindow.Show();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Error("Failed to show migration prompt window.", ex);
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (migrationWindow is null)
|
|
|
|
|
{
|
|
|
|
|
return MigrationResult.Skipped;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MigrationResult result;
|
|
|
|
|
try
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
result = await migrationWindow.WaitForChoiceAsync().ConfigureAwait(false);
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Error("Failed while waiting for migration prompt result.", ex);
|
2026-04-21 20:59:52 +08:00
|
|
|
result = MigrationResult.Skipped;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (migrationWindow.IsVisible && migrationWindow.IsLoaded)
|
|
|
|
|
{
|
|
|
|
|
migrationWindow.Close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
Logger.Error("Failed to close migration prompt window.", ex);
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
private static string MapStartupStageToSplashStage(StartupStage stage) => stage switch
|
|
|
|
|
{
|
|
|
|
|
StartupStage.Initializing => "initializing",
|
|
|
|
|
StartupStage.LoadingSettings => "settings",
|
|
|
|
|
StartupStage.LoadingPlugins => "plugins",
|
2026-04-23 00:27:01 +08:00
|
|
|
StartupStage.TrayReady => "shell",
|
2026-04-22 07:31:54 +08:00
|
|
|
StartupStage.InitializingUI => "ui",
|
|
|
|
|
StartupStage.ShellInitialized => "shell",
|
2026-04-23 00:27:01 +08:00
|
|
|
StartupStage.BackgroundReady => "ready",
|
2026-04-22 07:31:54 +08:00
|
|
|
StartupStage.DesktopVisible => "ready",
|
|
|
|
|
StartupStage.ActivationRedirected => "activation",
|
|
|
|
|
StartupStage.ActivationFailed => "error",
|
|
|
|
|
StartupStage.Ready => "ready",
|
|
|
|
|
_ => "launch"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private static LauncherResult BuildResult(
|
|
|
|
|
bool success,
|
|
|
|
|
string stage,
|
|
|
|
|
string code,
|
|
|
|
|
string message,
|
|
|
|
|
Dictionary<string, string>? details = null,
|
|
|
|
|
string? errorMessage = null)
|
|
|
|
|
{
|
|
|
|
|
Logger.Info($"Launcher result prepared. Success={success}; Stage='{stage}'; Code='{code}'.");
|
|
|
|
|
return new LauncherResult
|
|
|
|
|
{
|
|
|
|
|
Success = success,
|
|
|
|
|
Stage = stage,
|
|
|
|
|
Code = code,
|
|
|
|
|
Message = message,
|
|
|
|
|
ErrorMessage = errorMessage,
|
|
|
|
|
Details = details ?? []
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
private static LauncherResult WithAdditionalDetails(LauncherResult result, Dictionary<string, string> details)
|
|
|
|
|
{
|
|
|
|
|
return new LauncherResult
|
|
|
|
|
{
|
|
|
|
|
Success = result.Success,
|
|
|
|
|
Stage = result.Stage,
|
|
|
|
|
Code = result.Code,
|
|
|
|
|
Message = result.Message,
|
|
|
|
|
CurrentVersion = result.CurrentVersion,
|
|
|
|
|
TargetVersion = result.TargetVersion,
|
|
|
|
|
RolledBackTo = result.RolledBackTo,
|
|
|
|
|
Details = MergeDetails(details, result.Details),
|
|
|
|
|
InstalledPackagePath = result.InstalledPackagePath,
|
|
|
|
|
ManifestId = result.ManifestId,
|
|
|
|
|
ManifestName = result.ManifestName,
|
|
|
|
|
ErrorMessage = result.ErrorMessage
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Dictionary<string, string> BuildLauncherContextDetails(
|
|
|
|
|
CommandContext context,
|
|
|
|
|
OobeLaunchDecision oobeDecision,
|
|
|
|
|
string appRoot)
|
|
|
|
|
{
|
|
|
|
|
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
{
|
|
|
|
|
["command"] = context.Command,
|
|
|
|
|
["launchSource"] = context.LaunchSource,
|
|
|
|
|
["isGuiMode"] = context.IsGuiCommand.ToString(),
|
|
|
|
|
["isDebugMode"] = context.IsDebugMode.ToString(),
|
|
|
|
|
["isElevated"] = oobeDecision.IsElevated.ToString(),
|
|
|
|
|
["resolvedAppRoot"] = appRoot,
|
|
|
|
|
["oobeStatePath"] = oobeDecision.StatePath,
|
|
|
|
|
["oobeStateStatus"] = oobeDecision.Status.ToString(),
|
|
|
|
|
["oobeDecision"] = oobeDecision.ShouldShowOobe ? "show" : "skip",
|
|
|
|
|
["oobeSuppressionReason"] = oobeDecision.SuppressionReason,
|
|
|
|
|
["oobeResultCode"] = oobeDecision.ResultCode,
|
|
|
|
|
["userSid"] = oobeDecision.UserSid ?? string.Empty,
|
|
|
|
|
["usedLegacyOobeMarker"] = oobeDecision.UsedLegacyMarker.ToString(),
|
|
|
|
|
["migratedLegacyOobeMarker"] = oobeDecision.MigratedLegacyMarker.ToString(),
|
|
|
|
|
["oobeStateError"] = oobeDecision.ErrorMessage
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
private static Dictionary<string, string> BuildResolutionDetails(
|
|
|
|
|
HostResolutionResult resolution,
|
|
|
|
|
HostStartAttempt? firstAttempt,
|
|
|
|
|
HostStartAttempt? secondAttempt,
|
|
|
|
|
string? failureStage)
|
|
|
|
|
{
|
|
|
|
|
var details = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
{
|
|
|
|
|
["resolvedAppRoot"] = resolution.AppRoot,
|
|
|
|
|
["explicitAppRoot"] = resolution.ExplicitAppRoot ?? string.Empty,
|
|
|
|
|
["resolvedHostPath"] = resolution.ResolvedHostPath ?? string.Empty,
|
|
|
|
|
["resolutionSource"] = resolution.ResolutionSource ?? string.Empty,
|
|
|
|
|
["devModeConfigIgnored"] = resolution.DevModeConfigIgnored.ToString(),
|
|
|
|
|
["searchedPaths"] = string.Join(" | ", resolution.SearchedPaths),
|
|
|
|
|
["failureStage"] = failureStage ?? string.Empty
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (firstAttempt is not null)
|
|
|
|
|
{
|
|
|
|
|
details["startMode"] = firstAttempt.StartMode.ToString();
|
|
|
|
|
details["processCreated"] = firstAttempt.ProcessCreated.ToString();
|
|
|
|
|
details["hostPid"] = firstAttempt.ProcessId?.ToString() ?? string.Empty;
|
2026-04-23 23:07:37 +08:00
|
|
|
details["packageRoot"] = firstAttempt.PackageRoot ?? string.Empty;
|
|
|
|
|
details["workingDirectory"] = firstAttempt.WorkingDirectory ?? string.Empty;
|
|
|
|
|
details["arguments"] = firstAttempt.Arguments ?? string.Empty;
|
2026-04-22 07:31:54 +08:00
|
|
|
details["firstAttemptFailureReason"] = firstAttempt.FailureReason ?? string.Empty;
|
|
|
|
|
details["firstAttemptExitCode"] = firstAttempt.ExitCode?.ToString() ?? string.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (secondAttempt is not null)
|
|
|
|
|
{
|
|
|
|
|
details["fallbackStartMode"] = secondAttempt.StartMode.ToString();
|
|
|
|
|
details["fallbackProcessCreated"] = secondAttempt.ProcessCreated.ToString();
|
|
|
|
|
details["fallbackHostPid"] = secondAttempt.ProcessId?.ToString() ?? string.Empty;
|
2026-04-23 23:07:37 +08:00
|
|
|
details["fallbackPackageRoot"] = secondAttempt.PackageRoot ?? string.Empty;
|
|
|
|
|
details["fallbackWorkingDirectory"] = secondAttempt.WorkingDirectory ?? string.Empty;
|
|
|
|
|
details["fallbackArguments"] = secondAttempt.Arguments ?? string.Empty;
|
2026-04-22 07:31:54 +08:00
|
|
|
details["fallbackFailureReason"] = secondAttempt.FailureReason ?? string.Empty;
|
|
|
|
|
details["fallbackExitCode"] = secondAttempt.ExitCode?.ToString() ?? string.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return details;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Dictionary<string, string> MergeDetails(
|
|
|
|
|
Dictionary<string, string> left,
|
|
|
|
|
Dictionary<string, string> right)
|
|
|
|
|
{
|
|
|
|
|
var merged = new Dictionary<string, string>(left, StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
foreach (var pair in right)
|
|
|
|
|
{
|
|
|
|
|
merged[pair.Key] = pair.Value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return merged;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 20:59:52 +08:00
|
|
|
private static void EnsureExecutable(string path)
|
|
|
|
|
{
|
|
|
|
|
if (OperatingSystem.IsWindows())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var mode = File.GetUnixFileMode(path);
|
|
|
|
|
mode |= UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute;
|
|
|
|
|
File.SetUnixFileMode(path, mode);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 14:55:30 +08:00
|
|
|
private static async Task<bool> TryConnectToPublicIpcAsync(
|
|
|
|
|
LanMountainDesktopIpcClient ipcClient,
|
|
|
|
|
TimeSpan timeout)
|
|
|
|
|
{
|
2026-04-23 09:45:05 +08:00
|
|
|
if (ipcClient.IsConnected)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 23:07:37 +08:00
|
|
|
try
|
2026-04-22 14:55:30 +08:00
|
|
|
{
|
2026-04-23 23:07:37 +08:00
|
|
|
var connectTask = ipcClient.ConnectAsync();
|
|
|
|
|
var completedTask = await Task.WhenAny(connectTask, Task.Delay(timeout)).ConfigureAwait(false);
|
|
|
|
|
if (completedTask != connectTask)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await connectTask.ConfigureAwait(false);
|
|
|
|
|
return ipcClient.IsConnected;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-05-12 16:46:49 +08:00
|
|
|
Logger.Info($"Public IPC is not ready yet: {ex.Message}");
|
2026-04-22 14:55:30 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 16:46:49 +08:00
|
|
|
internal static bool ShouldProbeExistingHostBeforeLaunch(CommandContext context)
|
2026-04-23 09:45:05 +08:00
|
|
|
{
|
|
|
|
|
if (!string.Equals(context.Command, "launch", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (context.IsPreviewCommand || context.IsMaintenanceCommand)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return !string.Equals(context.LaunchSource, "restart", StringComparison.OrdinalIgnoreCase);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 16:46:49 +08:00
|
|
|
private MultiInstanceLaunchBehavior LoadMultiInstanceLaunchBehavior()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var settingsPath = HostAppSettingsOobeMerger.GetSettingsFilePath(_dataLocationResolver.ResolveDataRoot());
|
|
|
|
|
return HostAppSettingsOobeMerger.LoadMultiInstanceLaunchBehavior(settingsPath);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Logger.Warn($"Failed to load multi-instance launch behavior. Falling back to default. {ex.Message}");
|
|
|
|
|
return MultiInstanceLaunchBehavior.NotifyAndOpenDesktop;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal static bool IsExistingHostReadyForLauncherDecision(PublicShellStatus? status)
|
|
|
|
|
{
|
|
|
|
|
return status is { PublicIpcReady: true, ProcessId: > 0 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task<PublicShellStatus?> TryGetExistingHostStatusAsync(
|
|
|
|
|
LanMountainDesktopIpcClient ipcClient,
|
|
|
|
|
TimeSpan timeout)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var connected = ipcClient.IsConnected ||
|
|
|
|
|
await TryConnectToPublicIpcAsync(ipcClient, timeout).ConfigureAwait(false);
|
|
|
|
|
if (!connected)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var shellProxy = ipcClient.CreateProxy<IPublicShellControlService>();
|
|
|
|
|
return await shellProxy.GetShellStatusAsync().ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Logger.Info($"Existing host status probe did not complete: {ex.Message}");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task<ExistingHostBehaviorResult> ApplyExistingHostBehaviorAsync(
|
|
|
|
|
LanMountainDesktopIpcClient ipcClient,
|
|
|
|
|
MultiInstanceLaunchBehavior behavior,
|
|
|
|
|
PublicShellStatus status)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var shellProxy = ipcClient.CreateProxy<IPublicShellControlService>();
|
|
|
|
|
return behavior switch
|
|
|
|
|
{
|
|
|
|
|
MultiInstanceLaunchBehavior.OpenDesktopSilently => await ActivateExistingHostForBehaviorAsync(
|
|
|
|
|
shellProxy,
|
|
|
|
|
showLauncherNotice: false,
|
|
|
|
|
successCode: "existing_host_activated",
|
|
|
|
|
successMessage: "Launcher activated the existing desktop instance.",
|
|
|
|
|
failureCode: "existing_host_activation_failed").ConfigureAwait(false),
|
|
|
|
|
|
|
|
|
|
MultiInstanceLaunchBehavior.NotifyAndOpenDesktop => await ActivateExistingHostForBehaviorAsync(
|
|
|
|
|
shellProxy,
|
|
|
|
|
showLauncherNotice: true,
|
|
|
|
|
successCode: "existing_host_activated_with_notice",
|
|
|
|
|
successMessage: "Launcher activated the existing desktop instance and showed the repeated-launch notice.",
|
|
|
|
|
failureCode: "existing_host_activation_failed").ConfigureAwait(false),
|
|
|
|
|
|
|
|
|
|
MultiInstanceLaunchBehavior.PromptOnly => await ShowPromptOnlyExistingHostAsync(
|
|
|
|
|
shellProxy,
|
|
|
|
|
status).ConfigureAwait(false),
|
|
|
|
|
|
|
|
|
|
MultiInstanceLaunchBehavior.RestartApp => await RestartExistingHostAsync(shellProxy).ConfigureAwait(false),
|
|
|
|
|
|
|
|
|
|
_ => await ActivateExistingHostForBehaviorAsync(
|
|
|
|
|
shellProxy,
|
|
|
|
|
showLauncherNotice: true,
|
|
|
|
|
successCode: "existing_host_activated_with_notice",
|
|
|
|
|
successMessage: "Launcher activated the existing desktop instance and showed the repeated-launch notice.",
|
|
|
|
|
failureCode: "existing_host_activation_failed").ConfigureAwait(false)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Logger.Warn($"Failed to apply multi-instance behavior '{behavior}': {ex.Message}");
|
|
|
|
|
return new ExistingHostBehaviorResult(
|
|
|
|
|
false,
|
|
|
|
|
"multi_instance_behavior_failed",
|
|
|
|
|
$"Failed to apply multi-instance behavior '{behavior}': {ex.Message}",
|
|
|
|
|
null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task<ExistingHostBehaviorResult> ActivateExistingHostForBehaviorAsync(
|
|
|
|
|
IPublicShellControlService shellProxy,
|
|
|
|
|
bool showLauncherNotice,
|
|
|
|
|
string successCode,
|
|
|
|
|
string successMessage,
|
|
|
|
|
string failureCode)
|
|
|
|
|
{
|
|
|
|
|
var activation = await shellProxy.ActivateMainWindowWithStatusAsync().ConfigureAwait(false);
|
|
|
|
|
var success = activation.Accepted || IsRecoverableActivationFailure(activation);
|
|
|
|
|
if (showLauncherNotice && success)
|
|
|
|
|
{
|
|
|
|
|
var promptResult = await ShowMultiInstancePromptAsync(activation.Status).ConfigureAwait(false);
|
|
|
|
|
if (promptResult == MultiInstancePromptResult.OpenDesktop)
|
|
|
|
|
{
|
|
|
|
|
activation = await shellProxy.ActivateMainWindowWithStatusAsync().ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new ExistingHostBehaviorResult(
|
|
|
|
|
success,
|
|
|
|
|
activation.Accepted ? successCode : success ? "existing_host_startup_pending" : failureCode,
|
|
|
|
|
activation.Accepted ? successMessage : activation.Message,
|
|
|
|
|
activation);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task<ExistingHostBehaviorResult> RestartExistingHostAsync(
|
|
|
|
|
IPublicShellControlService shellProxy)
|
|
|
|
|
{
|
|
|
|
|
var accepted = await shellProxy.RestartAsync().ConfigureAwait(false);
|
|
|
|
|
return new ExistingHostBehaviorResult(
|
|
|
|
|
accepted,
|
|
|
|
|
accepted ? "existing_host_restart_requested" : "existing_host_restart_failed",
|
|
|
|
|
accepted
|
|
|
|
|
? "Launcher requested the existing desktop instance to restart."
|
|
|
|
|
: "Launcher could not request restart from the existing desktop instance.",
|
|
|
|
|
null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task<ExistingHostBehaviorResult> ShowPromptOnlyExistingHostAsync(
|
|
|
|
|
IPublicShellControlService shellProxy,
|
|
|
|
|
PublicShellStatus status)
|
|
|
|
|
{
|
|
|
|
|
var promptResult = await ShowMultiInstancePromptAsync(status).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
if (promptResult == MultiInstancePromptResult.OpenDesktop)
|
|
|
|
|
{
|
|
|
|
|
return await ActivateExistingHostForBehaviorAsync(
|
|
|
|
|
shellProxy,
|
|
|
|
|
showLauncherNotice: false,
|
|
|
|
|
successCode: "existing_host_activated_from_prompt",
|
|
|
|
|
successMessage: "Launcher activated the existing desktop instance from the prompt.",
|
|
|
|
|
failureCode: "existing_host_activation_failed").ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new ExistingHostBehaviorResult(
|
|
|
|
|
true,
|
|
|
|
|
"existing_host_prompt_only",
|
|
|
|
|
"Launcher showed the repeated-launch prompt and did not open the desktop automatically.",
|
|
|
|
|
null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task<MultiInstancePromptResult> ShowMultiInstancePromptAsync(PublicShellStatus status)
|
|
|
|
|
{
|
|
|
|
|
return await Dispatcher.UIThread.InvokeAsync(async () =>
|
|
|
|
|
{
|
|
|
|
|
var prompt = new MultiInstancePromptWindow();
|
|
|
|
|
prompt.SetDetails(status.ProcessId, status.ShellState);
|
|
|
|
|
prompt.Show();
|
|
|
|
|
return await prompt.WaitForChoiceAsync().ConfigureAwait(true);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:45:05 +08:00
|
|
|
private static async Task<PublicShellActivationResult?> TryActivateExistingHostWithStatusAsync(
|
|
|
|
|
LanMountainDesktopIpcClient ipcClient,
|
|
|
|
|
TimeSpan timeout)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var connected = ipcClient.IsConnected ||
|
|
|
|
|
await TryConnectToPublicIpcAsync(ipcClient, timeout).ConfigureAwait(false);
|
|
|
|
|
if (!connected)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var shellProxy = ipcClient.CreateProxy<IPublicShellControlService>();
|
|
|
|
|
return await shellProxy.ActivateMainWindowWithStatusAsync().ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-05-12 16:46:49 +08:00
|
|
|
Logger.Info($"Existing host activation probe did not complete: {ex.Message}");
|
2026-04-23 09:45:05 +08:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 23:07:37 +08:00
|
|
|
private static async Task<StartupSuccessState?> TryRecoverActivationThroughExistingHostAsync(
|
|
|
|
|
LanMountainDesktopIpcClient ipcClient,
|
|
|
|
|
StartupSuccessTracker startupSuccessTracker,
|
|
|
|
|
TimeSpan timeout)
|
|
|
|
|
{
|
|
|
|
|
var activation = await TryActivateExistingHostWithStatusAsync(ipcClient, timeout).ConfigureAwait(false);
|
|
|
|
|
if (activation is null)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (startupSuccessTracker.TryResolve(activation.Status, out var shellSuccess))
|
|
|
|
|
{
|
|
|
|
|
return shellSuccess;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (activation.Accepted)
|
|
|
|
|
{
|
|
|
|
|
return startupSuccessTracker.BuildRecoverySuccessState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return IsRecoverableActivationFailure(activation)
|
|
|
|
|
? new StartupSuccessState(
|
|
|
|
|
StartupStage.Ready,
|
|
|
|
|
"startup_pending",
|
|
|
|
|
activation.Message)
|
|
|
|
|
: null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 16:46:49 +08:00
|
|
|
internal static bool IsRecoverableActivationFailure(PublicShellActivationResult activation)
|
2026-04-23 19:04:39 +08:00
|
|
|
{
|
|
|
|
|
if (activation.Accepted)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (string.Equals(activation.Code, "shutdown_in_progress", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return activation.Status.PublicIpcReady &&
|
|
|
|
|
(!activation.Status.MainWindowOpened ||
|
|
|
|
|
!activation.Status.DesktopVisible ||
|
|
|
|
|
string.Equals(activation.Code, "shell_not_ready", StringComparison.OrdinalIgnoreCase) ||
|
|
|
|
|
string.Equals(activation.Code, "startup_pending", StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 16:46:49 +08:00
|
|
|
internal static bool IsSuccessfulActivationExitCode(int exitCode) =>
|
|
|
|
|
exitCode == HostExitCodes.SecondaryActivationSucceeded;
|
|
|
|
|
|
|
|
|
|
internal static bool IsFailedActivationExitCode(int exitCode) =>
|
|
|
|
|
exitCode is HostExitCodes.SecondaryActivationFailed or HostExitCodes.RestartLockNotAcquired;
|
|
|
|
|
|
2026-04-23 09:45:05 +08:00
|
|
|
private static async Task<PublicShellStatus?> TryGetPublicShellStatusAsync(
|
|
|
|
|
LanMountainDesktopIpcClient ipcClient)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var shellProxy = ipcClient.CreateProxy<IPublicShellControlService>();
|
|
|
|
|
return await shellProxy.GetShellStatusAsync().ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Logger.Warn($"Failed to query public shell status: {ex.Message}");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 00:27:01 +08:00
|
|
|
private static async Task<StartupSuccessState?> TryRecoverWithPublicActivationAsync(
|
|
|
|
|
LanMountainDesktopIpcClient ipcClient,
|
|
|
|
|
Process hostProcess,
|
|
|
|
|
Task<StartupSuccessState> successTask,
|
|
|
|
|
StartupSuccessTracker startupSuccessTracker)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var shellProxy = ipcClient.CreateProxy<IPublicShellControlService>();
|
2026-04-23 23:07:37 +08:00
|
|
|
var activation = await shellProxy.ActivateMainWindowWithStatusAsync().ConfigureAwait(false);
|
|
|
|
|
if (startupSuccessTracker.TryResolve(activation.Status, out var shellSuccess))
|
2026-04-23 00:27:01 +08:00
|
|
|
{
|
2026-04-23 23:07:37 +08:00
|
|
|
return shellSuccess;
|
2026-04-23 00:27:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var completedTask = await Task.WhenAny(successTask, Task.Delay(TimeSpan.FromSeconds(5))).ConfigureAwait(false);
|
|
|
|
|
if (completedTask == successTask)
|
|
|
|
|
{
|
|
|
|
|
return await successTask.ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 23:07:37 +08:00
|
|
|
if (!hostProcess.HasExited && (activation.Accepted || IsRecoverableActivationFailure(activation)))
|
2026-04-23 00:27:01 +08:00
|
|
|
{
|
|
|
|
|
return startupSuccessTracker.BuildRecoverySuccessState();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Logger.Warn($"Public activation recovery failed: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
private static LoadingStateMessage BuildDelayedLoadingState(
|
|
|
|
|
LoadingStateMessage loadingState,
|
|
|
|
|
string summaryMessage,
|
|
|
|
|
string detailMessage,
|
|
|
|
|
DateTimeOffset startedAtUtc)
|
|
|
|
|
{
|
|
|
|
|
var delayedItems = loadingState.ActiveItems
|
|
|
|
|
.Where(item => !string.Equals(item.Id, "launcher-soft-timeout", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
delayedItems.Insert(0, new LoadingItem
|
|
|
|
|
{
|
|
|
|
|
Id = "launcher-soft-timeout",
|
|
|
|
|
Type = LoadingItemType.System,
|
|
|
|
|
Name = "Startup still in progress",
|
|
|
|
|
Description = detailMessage,
|
|
|
|
|
State = LoadingState.Delayed,
|
|
|
|
|
ProgressPercent = Math.Max(loadingState.OverallProgressPercent, 1),
|
|
|
|
|
Message = detailMessage,
|
|
|
|
|
StartTime = startedAtUtc
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return loadingState with
|
|
|
|
|
{
|
|
|
|
|
ActiveItems = delayedItems,
|
|
|
|
|
Message = summaryMessage,
|
|
|
|
|
Timestamp = DateTimeOffset.UtcNow,
|
|
|
|
|
TotalCount = Math.Max(loadingState.TotalCount, delayedItems.Count)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Dictionary<string, string> BuildAttemptDetails(
|
|
|
|
|
StartupAttemptRecord? trackedAttempt,
|
|
|
|
|
bool attachedToExistingAttempt,
|
|
|
|
|
bool ipcConnected,
|
|
|
|
|
bool hostProcessAlive,
|
|
|
|
|
StartupStage lastStage,
|
|
|
|
|
string lastStageMessage,
|
|
|
|
|
string? activationFailureReason,
|
|
|
|
|
bool softTimeoutShown,
|
|
|
|
|
bool recoveryActivationAttempted)
|
|
|
|
|
{
|
|
|
|
|
var details = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
{
|
|
|
|
|
["hostProcessAlive"] = hostProcessAlive.ToString(),
|
|
|
|
|
["attachedToExistingAttempt"] = attachedToExistingAttempt.ToString(),
|
|
|
|
|
["ipcConnected"] = ipcConnected.ToString(),
|
|
|
|
|
["ipcStage"] = lastStage.ToString(),
|
|
|
|
|
["ipcMessage"] = lastStageMessage,
|
|
|
|
|
["activationFailureReason"] = activationFailureReason ?? string.Empty,
|
|
|
|
|
["softTimeoutShown"] = softTimeoutShown.ToString(),
|
|
|
|
|
["recoveryActivationAttempted"] = recoveryActivationAttempted.ToString()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (trackedAttempt is not null)
|
|
|
|
|
{
|
|
|
|
|
details["startupAttemptId"] = trackedAttempt.AttemptId;
|
|
|
|
|
details["startupAttemptState"] = trackedAttempt.State.ToString();
|
|
|
|
|
details["startupAttemptStartedAtUtc"] = trackedAttempt.StartedAtUtc.ToString("O");
|
|
|
|
|
details["startupAttemptUpdatedAtUtc"] = trackedAttempt.UpdatedAtUtc.ToString("O");
|
2026-04-23 09:45:05 +08:00
|
|
|
details["startupAttemptHeartbeatAtUtc"] = trackedAttempt.HeartbeatAtUtc.ToString("O");
|
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
|
|
|
details["successPolicy"] = trackedAttempt.SuccessPolicy;
|
|
|
|
|
details["hostPid"] = trackedAttempt.HostPid.ToString();
|
2026-04-23 09:45:05 +08:00
|
|
|
details["coordinatorPid"] = trackedAttempt.CoordinatorPid.ToString();
|
|
|
|
|
details["coordinatorPipeName"] = trackedAttempt.CoordinatorPipeName;
|
|
|
|
|
details["reservedBeforeHostStart"] = trackedAttempt.ReservedBeforeHostStart.ToString();
|
|
|
|
|
details["publicIpcConnected"] = trackedAttempt.PublicIpcConnected.ToString();
|
|
|
|
|
details["shellStatus"] = trackedAttempt.ShellStatus;
|
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 details;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool TryGetLiveProcess(int processId, out Process? process)
|
|
|
|
|
{
|
|
|
|
|
process = null;
|
|
|
|
|
if (processId <= 0)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
process = Process.GetProcessById(processId);
|
|
|
|
|
return !process.HasExited;
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
process?.Dispose();
|
|
|
|
|
process = null;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
private enum HostStartMode
|
2026-04-21 20:59:52 +08:00
|
|
|
{
|
2026-04-22 07:31:54 +08:00
|
|
|
ShellExecute,
|
|
|
|
|
Direct
|
|
|
|
|
}
|
2026-04-21 20:59:52 +08:00
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
private sealed record HostStartAttempt(
|
|
|
|
|
HostStartMode StartMode,
|
|
|
|
|
bool ProcessCreated,
|
|
|
|
|
Process? Process,
|
|
|
|
|
bool ExitedEarly,
|
|
|
|
|
int? ExitCode,
|
2026-04-23 23:07:37 +08:00
|
|
|
string? FailureReason,
|
|
|
|
|
string? PackageRoot,
|
|
|
|
|
string? WorkingDirectory,
|
|
|
|
|
string? Arguments)
|
2026-04-22 07:31:54 +08:00
|
|
|
{
|
|
|
|
|
public int? ProcessId => Process?.Id;
|
2026-04-21 20:59:52 +08:00
|
|
|
|
2026-04-23 23:07:37 +08:00
|
|
|
public static HostStartAttempt Started(HostStartMode startMode, Process process, HostLaunchPlan plan) =>
|
|
|
|
|
new(
|
|
|
|
|
startMode,
|
|
|
|
|
true,
|
|
|
|
|
process,
|
|
|
|
|
false,
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
plan.PackageRoot,
|
|
|
|
|
plan.WorkingDirectory,
|
|
|
|
|
HostLaunchPlanBuilder.FormatArgumentsForLog(plan.Arguments));
|
|
|
|
|
|
|
|
|
|
public static HostStartAttempt EarlyExit(HostStartMode startMode, Process process, int exitCode, HostLaunchPlan plan) =>
|
|
|
|
|
new(
|
|
|
|
|
startMode,
|
|
|
|
|
true,
|
|
|
|
|
process,
|
|
|
|
|
true,
|
|
|
|
|
exitCode,
|
|
|
|
|
null,
|
|
|
|
|
plan.PackageRoot,
|
|
|
|
|
plan.WorkingDirectory,
|
|
|
|
|
HostLaunchPlanBuilder.FormatArgumentsForLog(plan.Arguments));
|
|
|
|
|
|
|
|
|
|
public static HostStartAttempt StartFailed(HostStartMode startMode, string failureReason, HostLaunchPlan? plan = null) =>
|
|
|
|
|
new(
|
|
|
|
|
startMode,
|
|
|
|
|
false,
|
|
|
|
|
null,
|
|
|
|
|
false,
|
|
|
|
|
null,
|
|
|
|
|
failureReason,
|
|
|
|
|
plan?.PackageRoot,
|
|
|
|
|
plan?.WorkingDirectory,
|
|
|
|
|
plan is null ? null : HostLaunchPlanBuilder.FormatArgumentsForLog(plan.Arguments));
|
2026-04-22 07:31:54 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 16:46:49 +08:00
|
|
|
private sealed record ExistingHostBehaviorResult(
|
|
|
|
|
bool Success,
|
|
|
|
|
string Code,
|
|
|
|
|
string Message,
|
|
|
|
|
PublicShellActivationResult? ActivationResult);
|
|
|
|
|
|
2026-04-22 07:31:54 +08:00
|
|
|
private sealed record HostLaunchOutcome(
|
|
|
|
|
LauncherResult Result,
|
|
|
|
|
Process? Process,
|
|
|
|
|
LauncherResult? ImmediateResult,
|
|
|
|
|
Dictionary<string, string> Details)
|
|
|
|
|
{
|
|
|
|
|
public static HostLaunchOutcome FromResult(LauncherResult result) =>
|
|
|
|
|
new(result, null, result.Success ? result : null, result.Details);
|
|
|
|
|
|
|
|
|
|
public static HostLaunchOutcome FromImmediateResult(LauncherResult result) =>
|
|
|
|
|
new(result, null, result, result.Details);
|
|
|
|
|
|
|
|
|
|
public static HostLaunchOutcome FromProcess(Process process, LauncherResult result, Dictionary<string, string> details) =>
|
|
|
|
|
new(result, process, null, details);
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|
2026-04-23 00:27:01 +08:00
|
|
|
|
|
|
|
|
private sealed class StartupSuccessTracker
|
|
|
|
|
{
|
|
|
|
|
private readonly LaunchSuccessPolicy _policy;
|
|
|
|
|
private bool _trayReady;
|
|
|
|
|
private bool _backgroundReady;
|
|
|
|
|
|
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
|
|
|
public string PolicyKey => _policy.ToString();
|
|
|
|
|
|
2026-04-23 00:27:01 +08:00
|
|
|
public StartupSuccessTracker(CommandContext context)
|
|
|
|
|
{
|
|
|
|
|
var restartPresentation = LauncherRuntimeMetadata.GetRestartPresentationMode(context.RawArgs);
|
|
|
|
|
var isRestartLaunch = string.Equals(context.LaunchSource, "restart", StringComparison.OrdinalIgnoreCase);
|
|
|
|
|
|
|
|
|
|
_policy = !isRestartLaunch
|
|
|
|
|
? LaunchSuccessPolicy.Foreground
|
|
|
|
|
: restartPresentation switch
|
|
|
|
|
{
|
|
|
|
|
RestartPresentationMode.Tray => LaunchSuccessPolicy.RestartTray,
|
|
|
|
|
RestartPresentationMode.Minimized => LaunchSuccessPolicy.RestartBackground,
|
|
|
|
|
_ => LaunchSuccessPolicy.Foreground
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool TryResolve(StartupStage stage, out StartupSuccessState successState)
|
|
|
|
|
{
|
|
|
|
|
switch (stage)
|
|
|
|
|
{
|
|
|
|
|
case StartupStage.ActivationRedirected:
|
|
|
|
|
successState = new StartupSuccessState(
|
|
|
|
|
stage,
|
|
|
|
|
"activation_redirected",
|
|
|
|
|
"Launcher activation was redirected to the existing desktop instance.");
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
case StartupStage.DesktopVisible:
|
|
|
|
|
successState = new StartupSuccessState(
|
|
|
|
|
stage,
|
|
|
|
|
_policy == LaunchSuccessPolicy.Foreground ? "ok" : "desktop_visible_fallback",
|
|
|
|
|
_policy == LaunchSuccessPolicy.Foreground
|
|
|
|
|
? "Desktop is visible and ready."
|
|
|
|
|
: "Desktop recovered in a visible state.");
|
|
|
|
|
return true;
|
|
|
|
|
|
2026-04-23 23:07:37 +08:00
|
|
|
case StartupStage.Ready:
|
|
|
|
|
successState = new StartupSuccessState(
|
|
|
|
|
stage,
|
|
|
|
|
_policy == LaunchSuccessPolicy.Foreground ? "ready" : "background_ready",
|
|
|
|
|
"Desktop reported that startup is ready.");
|
|
|
|
|
return true;
|
|
|
|
|
|
2026-04-23 00:27:01 +08:00
|
|
|
case StartupStage.TrayReady:
|
|
|
|
|
_trayReady = true;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case StartupStage.BackgroundReady:
|
|
|
|
|
_backgroundReady = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_policy == LaunchSuccessPolicy.RestartBackground && _backgroundReady)
|
|
|
|
|
{
|
|
|
|
|
successState = new StartupSuccessState(
|
|
|
|
|
StartupStage.BackgroundReady,
|
|
|
|
|
"background_ready",
|
|
|
|
|
"Desktop restart completed in the background.");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_policy == LaunchSuccessPolicy.RestartTray && _trayReady && _backgroundReady)
|
|
|
|
|
{
|
|
|
|
|
successState = new StartupSuccessState(
|
|
|
|
|
StartupStage.BackgroundReady,
|
|
|
|
|
"background_ready",
|
|
|
|
|
"Desktop restart completed with tray recovery ready.");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
successState = default!;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 23:07:37 +08:00
|
|
|
public bool TryResolve(PublicShellStatus? status, out StartupSuccessState successState)
|
|
|
|
|
{
|
|
|
|
|
if (status is not null &&
|
|
|
|
|
(status.DesktopVisible || status.MainWindowVisible || status.MainWindowOpened))
|
|
|
|
|
{
|
|
|
|
|
successState = new StartupSuccessState(
|
|
|
|
|
status.DesktopVisible || status.MainWindowVisible
|
|
|
|
|
? StartupStage.DesktopVisible
|
|
|
|
|
: StartupStage.Ready,
|
|
|
|
|
_policy == LaunchSuccessPolicy.Foreground ? "ok" : "background_ready",
|
|
|
|
|
status.DesktopVisible || status.MainWindowVisible
|
|
|
|
|
? "Desktop shell is visible and ready."
|
|
|
|
|
: "Desktop shell window has opened.");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
successState = default!;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 00:27:01 +08:00
|
|
|
public StartupSuccessState BuildRecoverySuccessState()
|
|
|
|
|
{
|
|
|
|
|
return _policy switch
|
|
|
|
|
{
|
|
|
|
|
LaunchSuccessPolicy.RestartTray => new StartupSuccessState(
|
|
|
|
|
StartupStage.DesktopVisible,
|
|
|
|
|
"recovery_activation_requested",
|
|
|
|
|
"Launcher requested a visible recovery because the background restart never confirmed tray readiness."),
|
|
|
|
|
LaunchSuccessPolicy.RestartBackground => new StartupSuccessState(
|
|
|
|
|
StartupStage.DesktopVisible,
|
|
|
|
|
"recovery_activation_requested",
|
|
|
|
|
"Launcher requested a visible recovery because the background restart never confirmed readiness."),
|
|
|
|
|
_ => new StartupSuccessState(
|
|
|
|
|
StartupStage.DesktopVisible,
|
|
|
|
|
"recovery_activation_requested",
|
|
|
|
|
"Launcher requested a visible recovery from the running desktop instance.")
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private sealed record StartupSuccessState(
|
|
|
|
|
StartupStage Stage,
|
|
|
|
|
string Code,
|
|
|
|
|
string Message);
|
|
|
|
|
|
|
|
|
|
private enum LaunchSuccessPolicy
|
|
|
|
|
{
|
|
|
|
|
Foreground,
|
|
|
|
|
RestartBackground,
|
|
|
|
|
RestartTray
|
|
|
|
|
}
|
2026-04-21 20:59:52 +08:00
|
|
|
}
|