Skip to content

MSIX distribution hardening: AppInstaller updates and packaged setup launch#468

Draft
indierawk2k2 wants to merge 59 commits into
openclaw:mainfrom
indierawk2k2:feat/msix-e2e-hardening
Draft

MSIX distribution hardening: AppInstaller updates and packaged setup launch#468
indierawk2k2 wants to merge 59 commits into
openclaw:mainfrom
indierawk2k2:feat/msix-e2e-hardening

Conversation

@indierawk2k2

@indierawk2k2 indierawk2k2 commented May 19, 2026

Copy link
Copy Markdown
Contributor

Simplifies the MSIX packaging PR while keeping the AppInstaller work:

  • Restores the legacy Inno installer, portable ZIP, and Updatum release surfaces so this PR adds MSIX/AppInstaller without sunsetting existing distribution.
  • Moves the tray MSIX to framework-dependent Windows App SDK packaging and publishes the matching Microsoft.WindowsAppRuntime.2 framework packages as AppInstaller dependencies.
  • Fixes packaged first-run setup launch: SetupEngine is copied as a child payload, published framework-dependent with WindowsAppSdkBootstrapInitialize=false, XAML resources are injected after packaging, and the tray launches it directly with UseShellExecute=false.
  • Keeps release signing/verification for both outer MSIX packages and inner executable/script payloads.

Validation completed locally:

  • ./build.ps1
  • dotnet test ./tests/OpenClaw.Shared.Tests/OpenClaw.Shared.Tests.csproj --no-restore
  • dotnet test ./tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj --no-restore
  • Real x64 Add-AppxPackage -Path install/launch validation with trusted test cert: tray launched and SetupEngine showed OpenClaw Setup.

Fresh fork test release assets are available at:
https://github.com/indierawk2k2/openclaw-windows-node/releases/tag/msix-production-feed-test-2026-05-27

Mike Harsh and others added 15 commits May 18, 2026 16:59
- Tray Package.appxmanifest: add uap5 namespace and windows.startupTask extension
  with TaskId=OpenClawCompanionStartup (default disabled, user opts in via Settings).
- AutoStartManager: branch on PackageHelper.IsPackaged. Packaged path uses
  Windows.ApplicationModel.StartupTask.RequestEnableAsync/Disable so the user sees
  the proper one-time Windows consent dialog. Unpackaged path keeps the legacy
  HKCU\\...\\Run entry for dev/debug builds only.
- CommandPalette Package.appxmanifest: drop the VS-template placeholders
  (CN=Microsoft Corporation, `A Lone Developer`, Windows.Universal device family),
  default-publish under the tray's publisher subject and at MaxVersionTested 26100.0.
- CI: new `Patch CommandPalette MSIX manifest metadata` step that re-asserts the
  CommandPalette manifest is in lockstep with the tray (identity name, publisher,
  publisher display name, version) before the build/sign chain runs.
- tests/OpenClaw.Tray.Tests/MsixManifestAssertionTests.cs: new assertion suite
  pinning the audited capability set, openclaw protocol, StartupTask TaskId, 4-part
  version, Publisher prefix, plus CommandPalette manifest sanity (no Microsoft
  placeholder, namespaced under tray, matching publisher, desktop-only).

Validation: ./build.ps1 OK, Shared.Tests 1776 passed / 28 skipped, Tray.Tests 1100 passed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- PermissionChecker.CheckCameraAsync / CheckMicrophoneAsync / CheckLocation now
  branch on PackageHelper.IsPackaged. The packaged path goes through
  Windows.Security.Authorization.AppCapabilityAccess.AppCapability.Create(<name>),
  which is the only API that reports the per-package consent state Windows
  surfaces under our package name in Settings > Privacy. The previous registry +
  DeviceAccessInformation path returned the wrong answer for MSIX users (it
  reads the global `Desktop apps` bucket, not our package-specific state).
- SubscribeToAccessChanges likewise branches on PackageHelper. Packaged path
  subscribes AppCapability.AccessChanged for webcam, microphone, and location
  so the onboarding row strip live-updates when the user toggles consent in
  Settings > Privacy. Defense-in-depth: if any AppCapability.Create throws on
  an older Windows build, we unwind the partial subscription and hand back a
  no-op disposer (callers must not crash).
- New MapAppCapabilityAccessStatus internal helper centralizes the AccessStatus
  -> PermissionStatus mapping with explicit arms for Allowed,
  UserPromptRequired, DeniedByUser, DeniedBySystem and a safe Unknown default.
  Unknown is deliberate so a future SDK enum value never silently bypasses
  capability consent.
- tests/OpenClaw.Tray.Tests/PermissionCheckerPackagedMappingTests.cs: pins the
  packaged-branch contract via source-text assertions (the test target is
  net10.0 so cannot resolve WinRT types; we follow the InstallerIssAssertionTests
  precedent of reading the source and asserting structural invariants).

Validation: ./build.ps1 OK, Shared.Tests 1776 passed / 28 skipped, Tray.Tests 1108 passed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- docs/WINNODE_CLI_MSIX_PACKAGING.md: investigation result with the committed
  recommendation to package the worker-node CLI inside the tray MSIX as a
  second <Application> publishing a windows.appExecutionAlias
  (`openclaw-winnode.exe`). Documents why the current Environment.SpecialFolder.ApplicationData
  contract between tray and CLI only works by coincidence under MSIX, lays
  out the three options considered, includes the proposed Package.appxmanifest
  snippet, sketches the required CLI code changes, and pins acceptance criteria
  for the follow-up implementation PR.
- docs/WINDOWS_NODE_ARCHITECTURE.md: link to the new design note so future
  contributors land on it from the Windows-platform umbrella doc.

Doc-only change; build/tests unaffected.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- installer/openclaw-companion.appinstaller.template: AppInstaller XML template
  with placeholders for version, publisher, the two MSIX URIs, and the stable
  AppInstaller URL. UpdateSettings defaults: OnLaunch poll every 24h, ShowPrompt,
  non-blocking, ForceUpdateFromAnyVersion (rollback path), AutomaticBackgroundTask.
- scripts/render-appinstaller.ps1: renders the template per tag with strict
  input validation (4-part version, absolute https URIs) and post-render XML
  parsing so a bad substitution surfaces at CI time, not deploy time.
- .github/workflows/ci.yml: 'Render AppInstaller' step in the release job
  produces both a per-tag .appinstaller AND latest.appinstaller (stable filename
  for the gh-pages URL). Both are attached to the GitHub Release.
- src/OpenClaw.Tray.WinUI/Services/AppInstallerUpdateService.cs: new internal
  service that wraps PackageManager.AddPackageByAppInstallerFileAsync for the
  in-app 'Check for updates' path. Returns a typed UpdateResult so the caller
  can surface UpdateQueued / NoUpdateAvailable / Failed / NotPackaged.
- src/OpenClaw.Tray.WinUI/App.xaml.cs: branch CheckForUpdatesAsync and
  CheckForUpdatesUserInitiatedAsync on PackageHelper.IsPackaged. Packaged
  startup check no-ops (AppInstaller polls automatically); packaged manual check
  calls AppInstallerUpdateService directly. Unpackaged paths still go through
  Updatum until T3 deletes it.
- docs/RELEASING.md: new 'Non-Store auto-update via .appinstaller' section
  documenting the four AppInstaller update triggers and operator caveats.
- tests/OpenClaw.Tray.Tests/AppInstallerTemplateAssertionTests.cs: 9 new
  structural tests pinning template XML well-formedness, placeholder set,
  UpdateSettings values, MainBundle.Name matching production identity, and
  URL alignment between in-app service, CI, and docs.

Validation: ./build.ps1 OK, Tray.Tests 1117 passed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
We're MSIX-only now. This commit retires every code path that existed only for
the Inno .exe distribution + the Updatum auto-update channel, and lands the
documented recovery path for the case where a user removed the MSIX without
first running the in-app Reset & remove (PR openclaw#310).

Deleted (Inno + portable ZIP + Updatum sunset):
- installer.iss
- scripts/Uninstall-LocalGateway.ps1
- src/OpenClaw.Tray.WinUI/Dialogs/DownloadProgressDialog.cs
- src/OpenClaw.Tray.WinUI/Dialogs/UpdateDialog.cs
- tests/OpenClaw.Tray.Tests/InstallerIssAssertionTests.cs
- tests/PackagingTests/Test-InnoUninstallOrdering.ps1
- docs/uninstall-portable.md
- Updatum PackageReference from src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj
- using Updatum + AppUpdater static + DownloadAndInstallUpdateAsync from App.xaml.cs

CI (.github/workflows/ci.yml):
- Dropped 'Install Inno Setup', 'Build x64 Installer', 'Build arm64 Installer',
  'Sign Installer', 'Create Release ZIPs' steps from the release job.
- Release-notes 'Quick Start' now points users at latest.appinstaller, not the
  raw .msix (raw .msix installs don't wire up auto-update).
- Release attached files reduced to the 4 MSIX-only assets:
  latest.appinstaller, OpenClawCompanion-<v>.appinstaller, the two .msix.

Added (recovery CLI):
- src/OpenClaw.WinNode.Cli/OrphanPurger.cs: detects orphan WSL distros
  (openclaw-* prefix), orphan %APPDATA%/%LOCALAPPDATA% folders, legacy
  openclaw:// URI scheme registration, legacy HKCU Run autostart key. Dry-run
  by default; --confirm-destructive applies. --json-output for machine
  consumption. Exit codes 0 (clean / removed) / 1 (dirty dry-run) / 2 (some
  removals failed).
- OpenClaw.WinNode.Cli --purge-wsl-orphans dispatches to OrphanPurger BEFORE
  the --command required-flag check.
- docs/uninstall-msix.md: replaces the manual recovery PowerShell with the
  CLI flag (one command instead of five) and includes the equivalent
  PowerShell fallback for the case where the CLI itself was removed.

Doc cleanup:
- docs/SETUP.md: install instructions rewritten around the .appinstaller URL.
- DEVELOPMENT.md: Release Process section rewritten for the MSIX-only pipeline.
- scripts/validate-msix-storage-paths.ps1: recovery guidance updated to point
  at the new --purge-wsl-orphans CLI.
- src/OpenClaw.Tray.WinUI/App.xaml.cs: mutex-name comment no longer references
  the deleted installer.iss AppMutex contract.

Note: SettingsManager.SkippedUpdateTag is left in place — the field is
harmless and removing it would force a settings.json migration. It will be
naturally retired when SettingsData gets its next breaking change.

Validation: ./build.ps1 OK, Shared.Tests 1776 passed / 28 skipped,
Tray.Tests 1115 passed (-2 from deleted InstallerIssAssertionTests).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Automation (T6a):
- scripts/test-msix-install.ps1: local-runnable smoke test that walks
  Add-AppxPackage -> assert package presence/publisher/version ->
  Start-Process activation -> wait for OpenClawTray-DeepLink named pipe ->
  send openclaw://health -> Remove-AppxPackage -> orphan check. Used as
  the automated counterpart to runbook scenarios 1 and 6.
- scripts/test-appinstaller-update.ps1: spins up a local HttpListener that
  serves vN.msix, vN+1.msix and the rendered .appinstaller; walks
  Add-AppxPackage -AppInstallerFile -> re-render to vN+1 ->
  PackageManager.AddPackageByAppInstallerFileAsync -> assert Get-AppxPackage
  reports vN+1. Catches .appinstaller XML / template regressions before
  they reach a real GitHub release.
- tests/OpenClaw.Tray.Tests/OrphanPurgerContractTests.cs: 9 source-text
  assertions (same precedent as the historical InstallerIssAssertionTests)
  pinning OrphanWslDistroPrefix, the five orphan-kind names, the exit-code
  policy (0/1/2), and the dry-run-is-default invariant. The recovery CLI
  in OpenClaw.WinNode.Cli is internal so we cannot link the assembly into
  the net10.0 tray-tests target; the source-text approach keeps the
  contract pinned without forcing the CLI to expose internals.

Manual runbook (T6b):
- docs/MSIX_E2E_TEST_RUNBOOK.md: 10-scenario release runbook covering
  clean install, packaged permission consent prompts (with the "package
  name appears in Settings > Privacy" assertion that catches an
  accidental fallback to the unpackaged DeviceAccessInformation surface),
  permission revocation while running (proves AppCapability.AccessChanged
  is wired), StartupTask (proves we did NOT regress to HKCU\\...\\Run),
  local-gateway install + clean uninstall, dirty-uninstall + recovery
  via --purge-wsl-orphans (proves Q1 mitigation works), .appinstaller
  auto-update across all four trigger paths, sideload trust on a
  no-dev-mode box, and an ARM64 cross-check.

Note: the existing build-msix CI job already runs on every PR (gated only
on the test job, not on tags), so PR-time MSIX build coverage is already
present in CI; only the *signing* step is tag-gated, which is correct.

Validation: ./build.ps1 OK, Shared.Tests 1776 passed / 28 skipped,
Tray.Tests 1123 passed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…both URI key cases

While prepping for the manual test pass on Mike's box we found two real bugs
in OrphanPurger that would have caused --purge-wsl-orphans to falsely report
'no orphans' against a real OpenClaw install:

1. WSL distro detection used a case-sensitive 'openclaw-' prefix. The
   historical local-gateway installer registers the distro as
   'OpenClawGateway' (PascalCase, no dash) and we miss it. Pivoted to a
   case-insensitive substring match against an OrphanWslDistroPatterns
   array — currently a single 'openclaw' entry that catches both the
   PascalCase legacy form and the newer kebab-case 'openclaw-local' /
   'openclaw-staging' variants.

2. URI scheme key detection only enumerated
   HKCU\Software\Classes\openclaw. Mike's box has both 'openclaw' AND
   'OpenClaw' keys present simultaneously (the registry is case-insensitive
   for lookup but stores both literals). Switched to an OrphanUriSchemeKeys
   array so both are scrubbed; RemoveAsync now derives the subkey from the
   detected OrphanItem.Name instead of hard-coding the lowercase form.

OrphanWslDistroPrefix const is retained for backward compatibility with
existing OrphanPurgerContractTests assertions and any external recipes
that grep for the symbol; the new code paths use OrphanWslDistroPatterns.

Tests: added two regressions in OrphanPurgerContractTests that pin the
case-insensitive WSL detection (including the 'OpenClawGateway' name in
the docstring) and the two-variant URI scheme coverage.

Validation: ./build.ps1 OK, Shared.Tests 1776 passed / 28 skipped,
Tray.Tests 1125 passed (+2 from the new orphan tests).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

# Conflicts:
#	tests/OpenClaw.Tray.Tests/WinAppSdkGhostWindowCleanup.cs
…ons-list missing entry

Two bugs surfaced during manual MSIX E2E testing on a clean cloud devbox.
Both are in scope for this MSIX-only-distribution branch; the other two
issues Mike found (trackpad scroll, screen-recording-toggle left-nav blip)
will be filed separately.

Bug 1 (Settings -> Privacy icon has blue background)
  Settings > Privacy > Camera/Microphone/Location renders the per-app icon
  at small sizes (16, 20 px). We shipped unplated variants for 24/32/48/256
  but were missing 16 and 20 (and the natural 44 unplated). When Windows
  cannot find a fitting unplated size it falls back to the plated tile with
  the manifest BackgroundColor, which renders as the system accent (blue)
  square behind the lobster.

  Fix: generate Square44x44Logo.targetsize-{16,20,44}_altform-unplated.png
  from the existing 256px master via high-quality bicubic downscale,
  preserving the corner alpha=0 transparency.

Bug 2 (no OpenClaw Companion entry in Settings -> Notifications)
  MSIX packaged apps that do NOT declare windows.toastNotificationActivation
  in their manifest only appear in Settings > Notifications AFTER the first
  toast is delivered under package identity (and even then it is often
  delayed by several minutes). Users who install but have not yet seen a
  toast cannot pre-configure notification preferences.

  Fix: declare the canonical activator pair in Package.appxmanifest:
    - <com:Extension Category="windows.comServer"> registering OpenClaw.Tray.WinUI.exe
      as a COM ExeServer with class id D4E7F816-9D6A-4A49-B1BC-C1CE71282B04
    - <desktop:Extension Category="windows.toastNotificationActivation">
      pointing at the same ToastActivatorCLSID

  App.OnLaunched gains a -ToastActivator short-circuit (Environment.Exit(0)
  before the singleton mutex check) so Windows-spawned activator instances
  do not fight the running tray. We do NOT currently consume toast click
  callbacks (no CoRegisterClassObject) — clicks fall through to the
  standard tray activation path, which is acceptable for now and tracked
  for a follow-up.

Tests (tests/OpenClaw.Tray.Tests/MsixManifestAssertionTests.cs):
  + Tray_DeclaresToastNotificationActivationExtension: pins both halves of
    the COM-server / toast-activation pair and asserts the two CLSIDs match,
    plus that App.xaml.cs has the -ToastActivator short-circuit.
  + Tray_PrivacyListIcon_HasAllRequiredUnplatedTargetSizes: pins the full
    set of unplated PNG variants Windows requests for the Privacy list
    (16, 20, 24, 32, 44, 48, 256). Regression here re-introduces the blue
    background.

Validation: ./build.ps1 OK, Shared.Tests 1776 passed / 28 skipped,
Tray.Tests 1127 passed (+2 from the new manifest tests).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t-uninstall (openclaw#467)

LocalGatewayUninstall Step 5a deleted %LOCALAPPDATA%\OpenClawTray\wsl\<distro>\
but never touched the parent wsl\ or the grandparent OpenClawTray\. After a
clean full uninstall (gateway-remove + MSIX-uninstall) the user sees two phantom
empty folders left behind. Bug reported as openclaw#467 with full repro and root cause
diagnosis.

Fix:
- New Step 5b: 'Prune empty wsl\ parent directory' fires right after the VHD
  cleanup. Empty-guard ensures a sibling distro (e.g., openclaw-staging) under
  the same wsl\ parent is NEVER wiped.
- New Step 12a: 'Prune empty %LOCALAPPDATA%\OpenClawTray\' fires at the very
  end (after Preserve mcp-token.txt, before Compute postconditions). Same
  empty-guard logic: only removes the directory if all per-artifact steps left
  it empty. If non-OpenClaw files remain, the step records Skipped with the
  remaining entry names in Detail so operators can see what's blocking the
  prune (catches new artifact-writers we forgot to wire into explicit deletes).

Both steps are idempotent and safe to re-run.

%APPDATA%\OpenClawTray\ (roaming) is intentionally NOT pruned because
mcp-token.txt lives there and is preserved by design (Step 12).

Tests (LocalGatewayUninstallTests.cs +4 regression tests):
- FullUninstall_PrunesEmptyWslParent_Issue467
- FullUninstall_PrunesEmptyLocalAppDataRoot_Issue467
- FullUninstall_PreservesLocalAppDataRoot_WhenNonEmpty_Issue467 (defensive)
- FullUninstall_PreservesWslParent_WhenSiblingDistroPresent_Issue467 (defensive)

Validation: ./build.ps1 OK, Shared.Tests 1776 passed / 28 skipped,
Tray.Tests 1131 passed (+4).

Closes openclaw#467.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… build.ps1

Mike observed a new ghost Terminal frame appearing during the MSIX-E2E test
pass even though the cherry-picked WinAppSdkGhostWindowCleanup (4 commits from
wsl-keepalive-lifecycle) is in place. Root cause:

  The in-process cleanup only fires inside the testhost lifetime. It does NOT
  catch ghosts created by:
    - msbuild MSIX packaging (MakeAppx.exe, signtool.exe, WindowsAppSDK XAML
      markup compiler) — these run outside the testhost
    - testhost processes killed abnormally (SIGKILL, hung Ctrl+C, OOM) — the
      ProcessExit / AssemblyLoadContext.Unloading hooks never fire

This adds a three-layer safety net so we never strand windows on a developer
workstation OR on a future CI step that builds the MSIX without running tests:

1. scripts/cleanup-ghost-windows.ps1
   - Standalone PowerShell recovery tool. Mirrors the in-process C# cleanup
     filter EXACTLY (CASCADIA_HOSTING_WINDOW_CLASS + title == 'Terminal' +
     owner WindowsTerminal + size >= 1000x500). Uses the proven close
     sequence that survived the 2026-05-19 manual test: ShowWindow(SW_HIDE)
     -> PostMessage(SYSCOMMAND,SC_CLOSE) -> SendMessageTimeout(WM_CLOSE,
     SMTO_ABORTIFHUNG, 1000ms). A plain SendMessage WM_SYSCOMMAND alone
     did NOT close the orphans during testing — Windows Terminal swallows it.
   - Up to 5 passes with a 500ms delay between (mirrors the C#
     CleanupBlankFramesRepeatedly logic).
   - Supports -WhatIf for safe enumeration and -Quiet for use in scripts.

2. build.ps1 hook
   - On a successful build the script runs automatically (-Quiet). MSIX
     packaging ghosts get cleaned before the developer notices.

3. AGENTS.md note
   - Documents the manual recovery path for any future agent / developer who
     sees Terminal windows piling up after an interrupted test run.

Why we did not just make the in-process cleanup more aggressive: the baseline
exclusion is deliberate to protect the developer's REAL Terminal windows
(which start with title 'Terminal' until they type anything). Closing
baseline frames would risk false positives. The standalone script is the
right place for the looser-but-still-safe behavior because it's manually
invoked or build-gated, not automatically firing in every test run.

Tests (tests/OpenClaw.Tray.Tests/GhostWindowCleanupScriptContractTests.cs):
  + FilterMatchesProductionCleanup theory: pins the 4 filter constants that
    the script and in-process cleanup MUST share (class, owner, min size).
  + TitleFilter_IsExactlyTerminal_InBothImplementations: language-native
    quote-style match for the exact title comparison in each file.
  + CloseSequence_MatchesProvenMessageOrder: pins ShowWindow + PostMessage +
    SendMessageTimeout and the 4 message/flag constants by hex value.
  + Script_IsInvokedFromBuildPs1: pins the build.ps1 wiring so a refactor
    can't silently drop it.
  + Script_RejectsTinyWindowsToProtectUserTerminals: pins the 1000x500 min
    size as the safety guard against closing real user Terminals.

Validation: ./build.ps1 OK (auto-cleanup runs at end), Shared.Tests 1776
passed / 28 skipped, Tray.Tests 1139 passed (+8 new contract tests).

Manual verification on Mike's box: before fix, 1 leaked ghost; after running
scripts/cleanup-ghost-windows.ps1, 0 ghosts. Subsequent ./build.ps1 + test
runs left no leaks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ask modes

During PR creation Mike saw two new ghost Terminal frames appear, even though
the cherry-picked WinAppSdkGhostWindowCleanup is in place AND build.ps1 wires
the cleanup script at end-of-build. Root cause was a coverage gap, not a logic
bug:

  On Win11 with Windows Terminal as the default terminal app
  (HKCU\Console\%%Startup\DelegationConsole = WT CLSID — the new Win11
  default), EVERY console-spawning child process allocates a Cascadia
  hosting frame. Most close cleanly. A small fraction leak under timing
  conditions. Our cleanup only fires from triggers we wired:
    1. testhost lifetime (in-process [ModuleInitializer] + xUnit attribute)
    2. build.ps1 end-of-build

  Cascadia frames from gh / git / dotnet / pwsh invoked OUTSIDE of build.ps1
  (e.g., 'gh pr create' creating the PR for this branch) are NOT caught by
  either trigger and leak indefinitely until reboot or manual cleanup.

Fix: give scripts/cleanup-ghost-windows.ps1 two new modes for high-shell-
activity sessions where the wired triggers aren't enough.

  -Daemon [-PollSeconds N]
    Foreground watcher; polls every N seconds (default 30, range 5..3600)
    and cleans any ghosts found. Use this when you're about to do a lot
    of shell work; Ctrl+C to stop. Useful for agent-driven branches like
    this one.

  -InstallScheduledTask
    Registers a Windows scheduled task (OpenClaw-Ghost-Terminal-Cleanup)
    that runs the script every 5 minutes under the current user, hidden
    window, 2-minute execution timeout, no admin needed. Idempotent
    (drops any prior registration first). Survives reboot, no shell
    session needed. Uninstall with -UninstallScheduledTask.

Validated install/uninstall round-trip on Mike's box during commit prep:
  PT5M repetition, State=Ready, clean uninstall.

AGENTS.md documents the Win11-default-terminal-app trigger explicitly and
points future agents at the two escalation modes.

Tests (+5):
  + Script_ExposesEscalationModesForOutOfBandLeaks theory pins
    -Daemon, -PollSeconds, -InstallScheduledTask, -UninstallScheduledTask
    presence in the script source.
  + Script_ScheduledTaskName_IsStable pins the task name so the
    AGENTS.md / support-recipe references can't silently drift between
    installer and uninstaller.

This commit does NOT change the filter logic or close sequence — the
1000x500 + class + title + owner guards still protect real user Terminal
windows. Only adds new invocation modes.

Validation: ./build.ps1 OK, Shared.Tests 1776 passed / 28 skipped,
Tray.Tests 1144 passed (+5).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@indierawk2k2

Copy link
Copy Markdown
Contributor Author

Coverage gap found during PR creation flow — addressed in commit 9fdf999

Two new ghost Terminal frames appeared during the gh pr create invocation that opened this PR. Investigation:

Default terminal app on Win11 (the new default since 24H2) is Windows Terminal:

\
HKCU\Console%%Startup\DelegationConsole = {2EACA947-7F5F-4CFA-BA87-8F7FBEEFBE69} # Windows Terminal CLSID
\\

That means every console-spawning child process — gh, git, dotnet, fresh pwsh sessions, agent-driven tool invocations — allocates a Cascadia hosting frame. Most close cleanly when the child exits; a small fraction leak under timing conditions.

The cleanup we already shipped only fires from triggers we wired:

  1. testhost lifetime (WinAppSdkGhostWindowCleanup [ModuleInitializer] + xUnit before/after attribute)
  2. build.ps1 end-of-build hook

gh pr create matches neither. So those two ghosts were structurally outside our coverage — not a logic bug, a coverage gap.

Fix (commit 9fdf999)

Added two new modes to scripts/cleanup-ghost-windows.ps1 for sessions where the wired triggers aren't enough:

  • -Daemon [-PollSeconds N] — foreground watcher (default 30s, range 5..3600). Use when you're about to do a lot of shell work; Ctrl+C to stop.
  • -InstallScheduledTask — registers a hidden 5-minute task under the current user (no admin needed, idempotent). Uninstall with -UninstallScheduledTask.

Validated install/uninstall round-trip on the test box. Filter logic, close-message sequence, and the 1000×500 safety guard are unchanged — only invocation modes are new.

AGENTS.md updated to explicitly call out the Win11-default-terminal-app trigger and the new escalation paths. GhostWindowCleanupScriptContractTests gains 5 new assertions pinning all four switch names and the scheduled-task name.

Where this leaves the PR

The PR can't fix the underlying Windows Terminal / ConPTY leak (that's a Microsoft-owned bug). What it can do — and now does — is ship a thorough recovery story across the full leak surface:

Source of ghost Caught by
Tray test process lifetime WinAppSdkGhostWindowCleanup (in-process)
msbuild MSIX packaging build.ps1 end-of-build hook
gh / git / ad-hoc shell work -Daemon or -InstallScheduledTask (developer opt-in)
Killed-with-Ctrl+C testhost One-shot scripts/cleanup-ghost-windows.ps1 (manual)
CI runner CI VMs are ephemeral; build.ps1 hook keeps the runner clean for the job's lifetime

Validation after the fix: ./build.ps1 OK, Shared.Tests 1776 / 28 skipped, Tray.Tests 1144 passed (+5). PR is now at 15 commits.

Recommendation for reviewers / your collaborator who's about to do heavy shell work on this branch: ./scripts/cleanup-ghost-windows.ps1 -InstallScheduledTask once, then forget about it (or use -Daemon for the duration of a single session).

Mike Harsh and others added 10 commits May 19, 2026 20:55
Use architecture-specific AppInstaller metadata, embed update settings in the MSIX package, avoid force-shutdown updates by default, and add release-hosting validation coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Seed packaged notification settings with a suppressed toast, make tile plating use a stable non-accent color, initialize tray chrome before optional startup work, and add startup breadcrumbs for post-install launch diagnostics.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drop the DefaultTile ShowName attribute because the MSIX schema used by MakeAppx rejects it during package creation, while keeping the explicit AppListEntry contract that lets Windows launch the packaged app from installer UX. Update the manifest assertion to pin the valid tile shape.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Handle App Installer launch-when-ready races where a prior tray process still owns the single-instance mutex but no live IPC server is reachable. The packaged launch path now waits briefly for the mutex to clear instead of exiting before tray initialization, while normal second launches still exit immediately when the running tray pipe is healthy.\n\nAlso revert the Settings icon background workaround: Windows Settings owns that accent-colored tile, so the manifest returns to transparent and docs/tests no longer claim app assets can suppress it.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wait for packaged status and a short fresh-install grace period before calling WinUI Application.Start, avoiding App Installer launch-before-validation crashes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use a blue lobster icon for the Settings nav item and Settings page header so MSIX auto-update validation has an obvious visual marker.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the blue-lobster update test marker from production UI, classify packages-in-use update results as pending restart, keep toast activator cold launches on the normal startup path, bind a single listener in the AppInstaller smoke script, constrain WSL orphan distro matching, and use the installed package volume for AppInstaller updates.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a maintainer-facing comparison of durable AppInstaller update feed endpoint options and recommend a project-owned custom domain backed by static hosting.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Guard destructive orphan cleanup while the companion package is installed or the tray is running, narrow WSL cleanup to known app-owned distro names, make manual update checks metadata-only, treat missing deployment HRESULTs as failures, and make packaged autostart changes awaitable with result handling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Switch the candidate stable AppInstaller feed to raw GitHub files under installer/appinstaller, add the release follow-up workflow that opens a maintainer-reviewed feed update PR, and teach the renderer and validation scripts about package identity and GitHub-hosted candidate assets.

Also updates docs and source-contract tests for the raw GitHub feed path, pre-release feed blocking, and metadata-only MainPackage version parsing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@clawsweeper

clawsweeper Bot commented May 22, 2026

Copy link
Copy Markdown

Codex review: needs real behavior proof before merge. Reviewed May 28, 2026, 1:58 AM ET / 05:58 UTC.

Summary
The PR adds MSIX/AppInstaller release/feed automation, packaged SetupEngine launch support, payload signing/verification, MSIX recovery scripts, and related docs/tests while preserving the existing installer and portable release channels.

Reproducibility: yes. for the review finding: source inspection shows the packaged manual-update branch returns before the existing dialog-rendering switch runs. The broader distribution behavior still needs real Windows proof for the signed release paths.

Review metrics: 3 noteworthy metrics.

  • Changed Surface: 55 files, +4891/-363. The patch spans release automation, app runtime behavior, manifests, scripts, docs, and tests, so maintainers should treat it as a distribution change rather than a narrow code fix.
  • Workflow Surface: 1 workflow added, 1 workflow changed. Release and feed automation changes can affect signing, published assets, and automatic client updates outside normal unit-test coverage.
  • Validation Scripts: 8 scripts added, 1 script changed, 1 script removed. The release process now depends on new PowerShell packaging, signing, rendering, validation, and smoke-test scripts.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦐 gold shrimp
Patch quality: 🦐 gold shrimp
Result: blocked until stronger real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Show visible packaged update-check results for Current, Failed, Available, and Ready states.
  • Post current-head proof for signed AppInstaller install/update/uninstall and ARM64 behavior, with private data redacted.

Proof guidance:

  • [P1] Needs stronger real behavior proof before merge: The PR has useful x64 install/setup-launch logs, but it still needs current-head proof for signed AppInstaller update/uninstall and ARM64 install/update behavior; redact private data before posting proof. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] Packaged users can still see no obvious response from the tray-menu update check when the AppInstaller feed is current, unavailable, invalid, or failed because the packaged branch only updates AppState and returns.
  • [P1] The branch changes install, first-launch setup, signing, AppInstaller feed, and stable-feed automation behavior that normal unit tests cannot fully validate.
  • [P1] The proof is strong for iterative x64 setup-launch debugging, but it does not yet cover current-head signed AppInstaller update/uninstall behavior or ARM64 install/update behavior.

Maintainer options:

  1. Fix Packaged Update Feedback First (recommended)
    Add a visible packaged AppInstaller result surface for Current, Failed, Available, and Ready outcomes before merging.
  2. Accept Release-Path Risk Deliberately
    Maintainers can choose to land with the current proof gap only if they explicitly own follow-up validation for signed AppInstaller update/uninstall and ARM64 behavior.
  3. Pause Until Full Matrix Proof
    Keep the draft paused until the contributor posts current-head proof for the signed AppInstaller install, update, uninstall, and ARM64 paths.

Next step before merge

  • [P1] Human review is needed because contributor real-behavior proof and release-path signoff remain required even though the update-feedback code defect is narrow.

Security
Cleared: Security-sensitive packaging and signing surfaces were inspected; no concrete secret leak or supply-chain vulnerability was found, though maintainer release-path review remains important.

Review findings

  • [P1] Show a result for packaged update checks — src/OpenClaw.Tray.WinUI/App.xaml.cs:3611-3614
Review details

Best possible solution:

Land the MSIX/AppInstaller work only after packaged update checks surface explicit results and maintainers have current-head proof for the signed install/update/uninstall paths on the supported architectures.

Do we have a high-confidence way to reproduce the issue?

Yes for the review finding: source inspection shows the packaged manual-update branch returns before the existing dialog-rendering switch runs. The broader distribution behavior still needs real Windows proof for the signed release paths.

Is this the best way to solve the issue?

No: the packaging direction may be right, but the packaged update-check path should reuse or add an explicit visible result surface instead of only mutating AppState.

Full review comments:

  • [P1] Show a result for packaged update checks — src/OpenClaw.Tray.WinUI/App.xaml.cs:3611-3614
    For packaged builds this returns immediately after CheckForPackagedAppInstallerUpdateAsync(), so the existing Current/Failed dialog handling below never runs. A tray-menu click can still appear to do nothing when the feed is current, 404s, is invalid, or otherwise fails unless the user already has a status-observing hub surface open.
    Confidence: 0.88

Overall correctness: patch is incorrect
Overall confidence: 0.84

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 6a5921883c86.

Label changes

Label justifications:

  • P2: This is significant Windows distribution work with limited blast radius to install/update paths and no active emergency signal.
  • merge-risk: 🚨 compatibility: The PR changes MSIX install/update behavior and release assets while preserving legacy installer and portable channels.
  • merge-risk: 🚨 security-boundary: The diff touches MSIX full-trust capabilities, startup task registration, toast COM activation, inner payload signing, and release credentials.
  • merge-risk: 🚨 availability: A bad package, runtime dependency, SetupEngine launch, or AppInstaller feed can leave the tray or first-run setup unavailable after install.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The PR has useful x64 install/setup-launch logs, but it still needs current-head proof for signed AppInstaller update/uninstall and ARM64 install/update behavior; redact private data before posting proof. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

Acceptance criteria:

  • [P1] ./build.ps1.
  • [P1] dotnet test ./tests/OpenClaw.Shared.Tests/OpenClaw.Shared.Tests.csproj --no-restore.
  • [P1] dotnet test ./tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj --no-restore.

What I checked:

  • Repository policy read: AGENTS.md was read fully; its validation requirements and Windows-node guidance were treated as review policy, while build/tests were not run because this review is read-only. (AGENTS.md:1, 6a5921883c86)
  • Packaged update path skips visible dialogs: The packaged branch in CheckForUpdatesUserInitiatedAsync awaits CheckForPackagedAppInstallerUpdateAsync and returns before the existing Current/Failed dialog switch runs. (src/OpenClaw.Tray.WinUI/App.xaml.cs:3611, e697b5be95a2)
  • Tray menu exposes the affected action: The tray menu routes the Check for Updates action directly to CheckForUpdatesUserInitiatedAsync, so this can be hit without any status-observing hub page open. (src/OpenClaw.Tray.WinUI/Services/TrayMenuStateBuilder.cs:308, e697b5be95a2)
  • Existing feedback behavior provenance: Current main's manual update feedback path was introduced around commit f3d3fd2, which added user feedback to Check for Updates; the PR's packaged branch bypasses that same result-dialog pattern. (src/OpenClaw.Tray.WinUI/App.xaml.cs:3542, f3d3fd2772f3)
  • Release and packaging surface inspected: The diff touches release workflows, AppInstaller feed generation, MSIX signing, SetupEngine packaging, manifests, update services, and recovery CLI behavior across 55 files. (e697b5be95a2)
  • Proof discussion reviewed: The PR body and comments include useful x64 Add-AppxPackage/setup-launch validation and fork release assets, but the current-head proof still does not cover the signed AppInstaller update/uninstall path and ARM64 install/update behavior. (e697b5be95a2)

Likely related people:

  • Regis Brid: Git blame and -S history tie the existing manual Check for Updates result-dialog behavior to commit f3d3fd2, which is the path the PR bypasses for packaged builds. (role: introduced current update-feedback behavior; confidence: high; commits: f3d3fd2772f3; files: src/OpenClaw.Tray.WinUI/App.xaml.cs)
  • ranjeshj: Current main history shows recent work on SetupEngine and CI release/signing surfaces that overlap the MSIX packaging and setup-launch changes. (role: recent setup and release-area contributor; confidence: high; commits: cefce3952ab1, ed126f2aac6d, 6bd98858e618; files: src/OpenClaw.SetupEngine.UI/Program.cs, .github/workflows/ci.yml)
  • shanselman: Current history shows adjacent signing and App.xaml.cs work, making this a reasonable routing candidate for release-surface review. (role: adjacent release/signing contributor; confidence: medium; commits: bd21cd9c547c, 0d4fcbd50ad5; files: .github/workflows/ci.yml, src/OpenClaw.Tray.WinUI/App.xaml.cs)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. labels May 22, 2026
Keep SetupEngine on the direct self-contained launch path, explicitly load the self-contained Windows App Runtime before WinUI starts, and preserve startup retry breadcrumbs for XAML factory failures.

Build the initial setup shell and welcome page in code to avoid native WinUI XAML load crashes observed during first-run setup autolaunch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@indierawk2k2

Copy link
Copy Markdown
Contributor Author

Round 10 local-loop update — setup autolaunch now survives locally

What changed in 207aadf:

  • kept the Round 9/master-compatible direct launch path (SetupEngine\OpenClaw.SetupEngine.UI.exe), no packaged SetupEngine PRAID/AUMID;
  • set the SetupEngine child process working directory to its payload folder;
  • explicitly calls WindowsAppRuntime_EnsureIsLoaded() before WinUI startup;
  • kept bounded Application.Start retry breadcrumbs for 0x80040111 / 0x80004005 XAML factory failures;
  • bypassed the native XAML load crash by building the setup window shell and Welcome page in code instead of calling SetupWindow.InitializeComponent() / WelcomePage.InitializeComponent().

Local evidence:

  • Direct self-contained SetupEngine crashed at SetupWindow.InitializeComponent.
  • Code-built SetupWindow moved the crash to WelcomePage.InitializeComponent.
  • Code-built WelcomePage made direct SetupEngine stay alive.
  • Iter9 loose-package loop launched the tray, the tray auto-launched SetupEngine\OpenClaw.SetupEngine.UI.exe, and both processes stayed alive. setup-engine-startup.log reached SetupWindow.ctor.afterWelcomeNavigate and App.OnLaunched.afterBringToFront.

Validation:

  • ./build.ps1 passed
  • Shared tests: 1958 passed / 29 skipped
  • Tray tests: 911 passed

Quick remote-test artifact:

  • Uploaded red x64 MSIX to the existing fork release msix-red-blue-feed-test-2026-05-22
  • Asset: OpenClawCompanion-red-0.4.90.0-win-x64.msix
  • SHA256: 825EAB38F5337CE10BABB886EE704A5F1A30FDA52CDA8EED99C51D620D8EAD36

Caveat: local command-line install with Add-AppxPackage -Path is still blocked on this box by local-machine certificate trust/elevation, so the local loop used loose package registration plus direct tray launch. That was enough to reproduce and then fix the SetupEngine crash path; the uploaded MSIX is for the dev box to confirm the real signed install/autolaunch path.

@indierawk2k2

Copy link
Copy Markdown
Contributor Author

Round 10 — setup launches! 🎉 Two follow-ups: (1) the "explanation dialog" isn't a bug, it's a UX choice; (2) page-2 crash is CapabilitiesPage.InitializeComponent() — same root cause as Round 1 but the workaround in commit 207aadfd was only half-applied

Excellent progress this round — confirmed that the Tray launches successfully, SetupEngine.UI launches via direct Process.Start (master pattern), and the Welcome page renders cleanly. Build hashes: Tray D6DE2DC5…F96E689, SetupEngine A9F1ADC4…3EB2B147, package 382 MB with full self-contained SetupEngine\ subfolder back at 223 MB.

The two new issues are both well-scoped now.


Issue #1 — the "explanation dialog" on the easy button (not actually a bug, but a UX choice to make)

Source: src/OpenClaw.SetupEngine.UI/Pages/WelcomePage.xaml.cs StartButton_Click. Fetched verbatim from PR head:

private async void StartButton_Click(object sender, RoutedEventArgs e)
{
    var dataDir = Environment.GetEnvironmentVariable("OPENCLAW_TRAY_DATA_DIR")
        ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "OpenClawTray");

    var existing = ExistingConfigDetector.Detect(dataDir, _config!.DistroName);
    var summary = ExistingConfigDetector.BuildReplacementSummary(existing);

    var dialog = new ContentDialog
    {
        Title = existing.HasLocalGateway || existing.HasDistro
            ? "Replace existing WSL gateway?"
            : "Install a new WSL gateway?",
        Content = summary,
        PrimaryButtonText = "Continue",
        CloseButtonText = "Cancel",
        DefaultButton = ContentDialogButton.Close,
        XamlRoot = XamlRoot,
    };

    var result = await dialog.ShowAsync();              // ← unconditional
    if (result == ContentDialogResult.Primary)
        App.MainWindow?.NavigateToCapabilities();
}

dialog.ShowAsync() is unconditional — there's no if (existing.HasLocalGateway || existing.HasDistro) ShowAsync(); else NavigateToCapabilities(); guard. The detector result only changes the title string (and the summary content text). On a fresh machine the dialog still appears with:

Field Value on fresh machine
Title Install a new WSL gateway?
Content A new local WSL gateway will be created. No existing configuration will be affected.
Primary button Continue
Cancel button Cancel
DefaultButton Close (Enter dismisses, surprising)

Verified on the test machine — fresh state, both detector predicates definitely false:

Check Test box state Detector result
File.Exists("%APPDATA%\OpenClawTray\gateways.json") %APPDATA%\OpenClawTray directory absent entirely HasLocalGateway = false
wsl --list --quiet contains OpenClawGateway wsl --list --quiet returns empty; HKCU\…\Lxss has no DistributionName entries; tray log shows [WslKeepAlive] Distro 'OpenClawGateway' not found; skipping keepalive. HasDistro = false

So this isn't a leftover-state false-positive (and #467 isn't biting here in a destructive way). The dialog is appearing by design as a "here's what will happen" confirmation prompt before destructive WSL provisioning. The user reasonably described it as an "explanation dialog" because that's exactly what it reads as.

If the desired behaviour is "skip the prompt on first-run / fresh machines, show only when replacement is needed", this is a 4-line change:

private async void StartButton_Click(object sender, RoutedEventArgs e)
{
    var dataDir = Environment.GetEnvironmentVariable("OPENCLAW_TRAY_DATA_DIR")
        ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "OpenClawTray");
    var existing = ExistingConfigDetector.Detect(dataDir, _config!.DistroName);

    // Fresh machine — no confirmation needed, just proceed.
    if (!existing.HasLocalGateway && !existing.HasDistro)
    {
        App.MainWindow?.NavigateToCapabilities();
        return;
    }

    // Existing state present — show replacement-confirmation dialog.
    var dialog = new ContentDialog
    {
        Title = "Replace existing WSL gateway?",
        Content = ExistingConfigDetector.BuildReplacementSummary(existing),
        PrimaryButtonText = "Continue",
        CloseButtonText = "Cancel",
        DefaultButton = ContentDialogButton.Close,
        XamlRoot = XamlRoot,
    };
    if (await dialog.ShowAsync() == ContentDialogResult.Primary)
        App.MainWindow?.NavigateToCapabilities();
}

That gives you: no friction on the fresh-install easy-button flow; a clear "we found existing stuff, are you sure?" speed bump only when there's actually existing state to be replaced. The current code is also subtly bug-prone in that DefaultButton = ContentDialogButton.Close means pressing Enter cancels — fine UX defensively but surprising if you intend Enter to confirm; either fix is reasonable, but it's worth a decision either way.

The user's hypothesis ("uninstall left behind some state OR there's a bug in the check") was correct that there's a logic bug — just not a detector false-positive. It's that the dialog is unconditional, so the "no existing state" branch shows it anyway.


Issue #2 — page-2 crash: CapabilitiesPage.InitializeComponent() (workaround from commit 207aadfd wasn't applied to the other 5 pages)

The user identified page 2 as "the gateway setup UX" but the actual navigation chain from SetupWindow.xaml.cs is:

WelcomePage  →  CapabilitiesPage  →  ProgressPage  →  WizardPage  →  PermissionsPage  →  CompletePage
  (#1)            (#2 crash)          (#3 would crash)  (#4 = "Gateway setup")  (#5)            (#6)

WizardPage is the page that has the header text "Gateway setup" — but it's reached after CapabilitiesPage (#2, "Configure capabilities") and ProgressPage (#3, "Setting up locally"). The crash you're seeing happens immediately when navigating to CapabilitiesPage (page 2) — before ProgressPage/WizardPage are ever reached.

Why CapabilitiesPage crashes

// src/OpenClaw.SetupEngine.UI/Pages/CapabilitiesPage.xaml.cs (verbatim from PR HEAD)
public sealed partial class CapabilitiesPage : Page
{
    private SetupConfig? _config;
    private readonly Dictionary<string, ToggleSwitch> _toggles = new();

    private static readonly (string Key, string Name, string Desc, string Glyph)[] Capabilities = [ ... ];

    public CapabilitiesPage()
    {
        InitializeComponent();   // ← crash site — XAML loader → RoGetActivationFactory → E_FAIL → STATUS_STOWED_EXCEPTION
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        _config = e.Parameter as SetupConfig ?? new SetupConfig();
        BuildToggles();
    }
    ...
}

Commit 207aadfd introduced a BuildPageShell() workaround for WelcomePage and BuildWindowShell() for SetupWindow. Both replace InitializeComponent() with imperative C# control construction so the XAML binary loader is never invoked, avoiding the RoGetActivationFactory call that fails in the self-contained-unpackaged-from-package-directory activation context. The same workaround was NOT applied to any of the other five pages, all of which still call InitializeComponent():

Page InitializeComponent()? Status Named-element complexity
WelcomePage No (uses BuildPageShell) ✅ works
SetupWindow No (uses BuildWindowShell) ✅ works
CapabilitiesPage Yes 💥 current crash site trivial — only CapGrid is referenced from code-behind
ProgressPage Yes 💥 will crash next medium — StepsPanel, LogText, LogScroller, LogPanel, SubtitleText, LogToggleButton, OpenLogButton
WizardPage ("Gateway setup") Yes 💥 will crash hard — 12 named fields incl. BusyRing, TitleText, MessagePanel, SelectOptions, MultiOptions, TextInput, SecretInput, ErrorText, StepCard, StatusText, PrimaryButton, SecondaryButton
PermissionsPage Yes 💥 will crash easy — only PermRows
CompletePage Yes 💥 will crash medium — SuccessIcon, FailureIcon, TitleText, SubtitleText, ErrorCard, ErrorText, ViewLogLink, NodeModeBanner, StartupToggle, LaunchButton

Confirmed crash signature from this run

05:10:18.437   SetupWindow.ctor.begin           pid 22260
05:10:18.480   SetupWindow.ctor.afterWindowHandle
05:10:18.564   SetupWindow.ctor.afterBackdrop
05:10:19.218   SetupWindow.ctor.afterLoadConfig    ← config loaded OK
05:10:19.234   SetupWindow.ctor.afterApplyConfig
05:10:19.258   WelcomePage.ctor.begin
05:10:19.319   WelcomePage.ctor.afterBuildPageShell   ← imperative, works fine
05:10:19.324   SetupWindow.ctor.afterWelcomeNavigate
05:10:19.410   App.OnLaunched.afterBringToFront
(18 seconds — user reads dialog, clicks Continue, dialog dismisses, Frame.Navigate fires)
05:10:37.???   💥 Application Error Id 1000
                  Faulting application: OpenClaw.SetupEngine.UI.exe v0.4.90.0
                  Faulting module:      Microsoft.UI.Xaml.dll v3.2.0.0 @ 0x3ac82d
                  Exception code:       0xc000027b   STATUS_STOWED_EXCEPTION
                  Faulting package-relative application ID: App   ← SetupEngine inherits Tray's PRAID
05:10:37.???   💥 WER Id 1001
                  Fault bucket 1570698272745416374
                  P5 combase.dll  P8 0x80004005 (E_FAIL)  P9 0x9cfd4

The praid:App in the WER is the smoking gun for the Process.Start launch pattern: SetupEngine.UI.exe is spawned as a child of the Tray (PRAID=App), inherits the package identity, and its WinRT activation context = the Tray's activation catalog (which has entries for the Tray's binaries but the XAML metadata provider for SetupEngine's OpenClaw.SetupEngine.UI.Pages.CapabilitiesPage type isn't registered there). InitializeComponent() calls into the XAML BAML loader → resolves OpenClaw.SetupEngine.UI.Pages.CapabilitiesPage against IXamlMetadataProviderRoGetActivationFactory for the type-info COM class → not in the catalog under PRAID=App → E_FAIL → WinUI internally stows as STATUS_STOWED_EXCEPTION → process death (the SEH bypass means it never surfaces to the C# retry wrapper around Application.Start).

A second crash at 05:11:04 (pid 28000, same WER bucket) is the user clicking the tray's setup button again, getting the same flow.

Two fix options

Option A — quick / safe: apply BuildPageShell() to the other 5 pages

Smallest diff, same proven workaround already used for WelcomePage/SetupWindow. Start with CapabilitiesPage (only one named field, mostly imperative already):

public CapabilitiesPage()
{
    BuildPageShell();  // not InitializeComponent()
}

private void BuildPageShell()
{
    var root = new Grid { Padding = new Thickness(40, 28, 40, 28) };
    root.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
    root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
    root.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });

    var header = new StackPanel { Spacing = 6, HorizontalAlignment = HorizontalAlignment.Center };
    Grid.SetRow(header, 0);
    header.Children.Add(new TextBlock {
        Text = "Configure capabilities",
        FontSize = 24, FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
        HorizontalAlignment = HorizontalAlignment.Center });
    header.Children.Add(new TextBlock {
        TextWrapping = TextWrapping.Wrap, TextAlignment = TextAlignment.Center,
        FontSize = 13, Opacity = 0.6, MaxWidth = 440,
        Text = "Choose which capabilities this node will advertise. You can change these later." });

    CapGrid = new Grid { ColumnSpacing = 16, RowSpacing = 4 };
    CapGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
    CapGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
    var scroll = new ScrollViewer {
        VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
        Margin = new Thickness(0, 20, 0, 0), Content = CapGrid };
    Grid.SetRow(scroll, 1);

    var continueBtn = new Button {
        Content = "Continue",
        HorizontalAlignment = HorizontalAlignment.Stretch,
        Margin = new Thickness(0, 16, 0, 0), Height = 44, FontSize = 15,
        FontWeight = Microsoft.UI.Text.FontWeights.SemiBold };
    continueBtn.Click += Continue_Click;
    Grid.SetRow(continueBtn, 2);

    root.Children.Add(header); root.Children.Add(scroll); root.Children.Add(continueBtn);
    Content = root;
}

CapGrid is the only x:Name field code-behind needs (it's used by BuildToggles()), so this is genuinely trivial for CapabilitiesPage. Then repeat for ProgressPage / WizardPage / PermissionsPage / CompletePage in that order. WizardPage is the biggest job (12 named elements). Add a smoke test that navigates through every page during CI.

Option B — root cause / proper: restore the AUMID launch + <Application Id="SetupEngine"> from commits 05b3376e/43cd21ac, but keep RunWithXamlFactoryRetry

Reading the commit history, the prior PR iteration did implement the right architecture: SetupEngine had its own <Application Id="SetupEngine"> manifest entry with WindowsPackageType=MSIX + WindowsAppSDKSelfContained=true, the tray launched it via shell:AppsFolder\{familyName}!SetupEngine, and Windows gave SetupEngine its own AppX container + activation context. Each app's MSBuild generated its own activatable class entries under its own <Application> element. InitializeComponent() worked on every page because the per-PRAID activation catalog had the right entries.

That approach was reverted by commit 3d8bc4d0 ("restore self-contained setup launch") because it surfaced transient 0x80040111 XAML factory race conditions on first launch right after install. The retry logic from commit 90ec7fc4 (RunWithXamlFactoryRetry) was the right companion to handle that race, and we saw it work successfully (after 1–3 retries) for the Tray.

Restoring the inner-app architecture with the retry kept is the structurally correct fix and avoids having to apply BuildPageShell() to 5 more pages (and to every future page someone adds). The patch is essentially:

  1. Revert the Package.appxmanifest change from 3d8bc4d0 (add the <Application Id="SetupEngine"> element back) — keep Executable="SetupEngine\OpenClaw.SetupEngine.UI.exe" since SetupEngine is now in the subfolder.
  2. Re-add the PackageMsix=true PropertyGroup to OpenClaw.SetupEngine.UI.csproj with <WindowsPackageType>MSIX</WindowsPackageType> + <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained> + <GenerateAppxPackageOnBuild>false</GenerateAppxPackageOnBuild>.
  3. Re-add CreateSetupEngineUiStartInfo / ResolveSetupEnginePackagedAppUserModelId in the tray's App.xaml.cs so it launches SetupEngine via shell:AppsFolder\OpenClaw.Companion_qm13sbsk50jee!SetupEngine when packaged.
  4. Keep RunWithXamlFactoryRetry in SetupEngine's Program.cs (it's already there) as the transient-race guard.
  5. Revert the BuildPageShell() workaround on WelcomePage + the BuildWindowShell() workaround on SetupWindow — they're no longer needed once activation works properly.

My recommendation

Given the timeline pressure to ship the MSIX hardening PR, do Option A for CapabilitiesPage right now (smallest diff, unblocks the immediate flow) and add it to ProgressPage in the same change so a smoke test can reach WizardPage. Then schedule Option B as the architectural fix before the next release — applying BuildPageShell() to every WinUI 3 page is going to bite every time anyone adds a new page or tweaks an existing one's XAML.

If the team would rather do Option B now and skip the workaround proliferation entirely, the existing RunWithXamlFactoryRetry in SetupEngine's Program.cs already handles the transient race that motivated the original revert; we have evidence from the Tray that 3 retries (waitMs=2000, 5000, 10000) reliably get past it.


Diagnostic artifacts

ZIP: C:\Users\mharsh\AppData\Local\Temp\openclaw-round10-page2-crash-20260527-052944.zip

  • setup-engine-startup.log — shows clean lifecycle through WelcomePage.ctor.afterBuildPageShell then App.OnLaunched.afterBringToFront, then nothing (the crash inside CapabilitiesPage's InitializeComponent() happens before any further breadcrumb)
  • round10-crash-events.txt — both Application Error Id 1000 + WER 1001 entries, praid:App confirmed
  • round10-state-proof.txt — fresh-machine state audit proving HasLocalGateway = false and HasDistro = false, ruling out a detector false-positive for Issue Add unit tests and code review for Moltbot.Shared #1
  • AppxManifest.xml — current manifest (<Application Id="App"> only, no <Application Id="SetupEngine">, no <PackageDependency>)
  • openclaw-tray.log.tail50.txt — shows Tray launched SetupEngine via direct path (no shell:AppsFolder invocation this round, confirming master pattern reverted)
  • WER-AppCrash_OpenClaw.Compani_*.wer — current WER reports

Acknowledgements + smaller open items

  • Big win that the Welcome page now renders end-to-end. The BuildPageShell() / BuildWindowShell() pattern definitely works around the activation issue when applied.
  • The MSIX uninstall still doesn't clean %LOCALAPPDATA%\OpenClawTray\ (issue Uninstall leaves behind empty directories in %LOCALAPPDATA% #467) — leftover exec-policy.json from 5/20 + accumulated logs are still surviving across install/uninstall cycles on this box. Not biting today's bug but worth fixing before GA.
  • DefaultButton = ContentDialogButton.Close on the confirmation dialog means Enter dismisses — surprising default. If you keep the dialog at all (existing-state case), consider switching to ContentDialogButton.Primary so Enter confirms.

Posted by Copilot CLI session running on the test machine. Combined test-machine evidence with PR source code at commit head (verbatim WelcomePage.xaml.cs:StartButton_Click, CapabilitiesPage.xaml.cs:ctor, ExistingConfigDetector predicates, navigation chain from SetupWindow.xaml.cs).

Build the first-run setup window, welcome, capabilities, progress, and gateway wizard screens in code so SetupEngine can drive through gateway install and reach wizard.start after MSIX launch.

Use simple text progress markers instead of animated/icon WinUI controls on the setup progress path, which avoids the native Microsoft.UI.Xaml crashes observed during local wizard-loop validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@indierawk2k2

Copy link
Copy Markdown
Contributor Author

Round 11 local gateway-wizard loop update — stop condition reached

Commit pushed: 897b811 fix(msix): keep setup wizard off fragile XAML path

What the local loop proved

  • The previous fix got SetupEngine to Welcome, but the same native WinUI/XAML crash recurred page-by-page after that.
  • Direct controls showed these boundaries:
    • CapabilitiesPage.ctor.begin with no afterInitializeComponent → crash at Capabilities XAML load.
    • after Capabilities was code-built, ProgressPage.ctor.begin with no afterInitializeComponent → crash at Progress XAML load.
    • after Progress was code-built, the page stayed alive, but live ProgressRing/FontIcon status badges still caused a native XAML crash once pipeline progress updates began.
  • The winning shape keeps the master-compatible direct self-contained SetupEngine launch and avoids the fragile XAML/parser/animated-control path for the first-run flow needed to reach Gateway Wizard.

What changed

  • SetupWindow, WelcomePage, CapabilitiesPage, ProgressPage, and WizardPage now build their first-run UI shells in code instead of calling InitializeComponent().
  • Progress uses simple text status markers instead of ProgressRing / FontIcon markers on the setup pipeline path.
  • Added a structural test pinning that these first-run pages stay off InitializeComponent().

Local stop-condition evidence

  • Fresh red x64 loose-package loop: iter7-pr-package-wizard
  • Cleaned package/appdata/WSL state, registered the package, launched tray from the package payload, and drove Welcome → Capabilities → Progress.
  • Setup pipeline completed gateway install, pairing, and connection.
  • Stop condition reached: wizard-start-reached
  • UIA final tree shows Gateway setup, status Answer the gateway setup question, first prompt OpenClaw setup, and an enabled Continue button.
  • Breadcrumbs include WizardPage.StartWizardAsync.afterWizardStart.

Validation

  • ./build.ps1 passed
  • Shared tests: 1958 passed / 29 skipped
  • Tray tests: 912 passed

Note: the first tray-test rerun failed because the successful local loop intentionally left a real OpenClawGateway WSL distro behind; after unregistering it and removing OpenClaw app state, the required validation passed.

Quick remote-test artifact

Caveat remains: this box still cannot run the signed package through Add-AppxPackage -Path because local-machine certificate trust is not available, so the local package loop used loose registration plus tray launch. The loop still exercised the package payload, tray → SetupEngine launch path, gateway install, pairing, connection, and Wizard start.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels May 27, 2026
@indierawk2k2

Copy link
Copy Markdown
Contributor Author

Round 12 full-matrix follow-up — red/blue x64 + ARM64 rebuilt and uploaded

The gateway-wizard fix remains 897b811 on the PR branch. After the local Wizard stop condition, I rebuilt the full quick-test matrix and uploaded all assets to msix-red-blue-feed-test-2026-05-22.

Uploaded assets

Asset SHA256
OpenClawCompanion-red-0.4.91.0-win-x64.msix E4AB0A4ABBC5828074803A72F28FF7E6E10BAA6CAD4760BC52CEEBA986E88351
OpenClawCompanion-red-0.4.91.0-win-arm64.msix 3FD8EB658BAA91DFBC479D76DD1D709477EF334CE73B718E8A3CA1E81119687A
OpenClawCompanion-blue-0.4.92.0-win-x64.msix 901384065F04A56BA3B0147591862DAC6C9B248D0482236595FB0E9A8F2B8DAC
OpenClawCompanion-blue-0.4.92.0-win-arm64.msix 58695FEF49E12368C5C016928E8C80D615C3784CC23EDCCA83258AD014D07198
openclaw-x64.appinstaller 49DB7685BACFDDEF88D68C3E064690C97C51FDF3F449C9FC10ECF85BF6AB8EEA
openclaw-arm64.appinstaller 69649EFBC694CE794661B0EFA8694746BD2011081E516630EB4B736CACFF3BE6

The architecture feeds point at the blue 0.4.92.0 packages, so red 0.4.91.0 can be used as the seed and blue as the update target.

Cleanup/validation

  • Checked tracked source/docs/workflows for test-only red/blue artifact names or temporary versions; none are committed. Existing red/blue mentions are validation-gated docs, not shippable artifact references.
  • Cleaned generated package payloads from the worktree after local builds.
  • Final validation after artifact cleanup passed:
    • ./build.ps1
    • Shared tests: 1958 passed / 29 skipped
    • Tray tests: 912 passed

Mike Harsh and others added 3 commits May 27, 2026 09:13
…ning

# Conflicts:
#	tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj
Require production-named MSIX assets when advancing the stable AppInstaller feed, pin release signing expectations, and remove temporary color-channel test terminology from the PR docs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Retry transient E_FAIL during tray WinUI startup to match the SetupEngine startup gate, and redact SetupEngine startup breadcrumb arguments defensively.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@indierawk2k2

Copy link
Copy Markdown
Contributor Author

Hanselman adversarial review completed after master resync + production MSIX cleanup

Models:

  • Opus: claude-opus-4.6
  • Codex: gpt-5.2-codex

Consensus table

Issue Opus Codex Consensus Fix confidence Outcome
Tray WinUiStartupGate retried 0x80040111 but not observed transient E_FAIL / 0x80004005 while SetupEngine retried both MEDIUM MEDIUM HIGH 95% Fixed in b1a3922
SetupEngine startup breadcrumbs logged raw command-line args LOW LOW 90% Fixed in b1a3922 by applying the same redaction shape as tray startup breadcrumbs
Prerelease MSIX/AppInstaller versions may not be monotonic for x64 alpha builds HIGH LOW / disputed 70% Deferred: stable feed updates are explicitly blocked for prerelease tags in appinstaller-feed-pr.yml; alpha channel policy is intentionally undecided
Prerelease MSIX/AppInstaller versions may not be monotonic for ARM64 alpha builds HIGH LOW / disputed 70% Deferred for the same reason as x64; if/when alpha AppInstaller feeds are enabled, the fourth MSIX version segment needs a monotonic prerelease policy

Fixes made

  • b1a3922 fix(msix): align tray WinUI startup retry
    • Tray startup retry now treats 0x80004005 as transient, matching SetupEngine's observed XAML startup failure set.
    • Added tests for E_FAIL retry and non-retry of unrelated COM errors.
    • SetupEngine startup breadcrumbs now redact command-line arguments before writing setup-engine-startup.log.

Production prep completed

  • Branch resynced with origin/master via merge commit de004ee.
  • Production MSIX/AppInstaller cleanup committed in 31b88c3:
    • stable feed workflow requires production-named release assets (OpenClawCompanion-$versionText-win-x64.msix, OpenClawCompanion-$versionText-win-arm64.msix);
    • temporary color-channel artifact terminology removed from the committed PR diff;
    • release-flow tests pin production naming and Azure Trusted Signing MSIX signing.

Validation after review fixes

  • ./build.ps1 passed
  • Shared tests: 2022 passed / 29 skipped
  • Tray tests: 929 passed

@clawsweeper clawsweeper Bot added merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. and removed merge-risk: 🚨 automation 🚨 Merging this PR could break CI, automerge, proof capture, label sync, or automation. labels May 27, 2026
Mike Harsh and others added 2 commits May 27, 2026 10:12
Remove the temporary setup and update UI workarounds from the PR so the maintainer-ready branch contains packaging/runtime changes only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep tests focused on MSIX packaging and auto-update contracts after restoring user-facing UX surfaces to match master.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels May 27, 2026
Copy SetupEngine XAML resources into publish output, keep them out of the tray PRI during packaging, and inject them into the unsigned MSIX payload before signing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mike Harsh and others added 3 commits May 27, 2026 13:43
Remove extra AppInstaller release artifacts, keep stable feed uploads production-named, and load the self-contained Windows App Runtime before tray WinUI startup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Unpack release MSIX packages, sign inner executable and PowerShell payload files recursively, repack them, then sign and verify the outer MSIX packages.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore the legacy Inno/Updatum release surfaces while keeping the MSIX/AppInstaller update path, move the tray MSIX to a framework-dependent Windows App SDK package with explicit AppInstaller runtime dependency, and fix packaged SetupEngine launch by publishing the child payload framework-dependent with bootstrap auto-initialization disabled.

Validation: ./build.ps1; dotnet test ./tests/OpenClaw.Shared.Tests/OpenClaw.Shared.Tests.csproj --no-restore; dotnet test ./tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj --no-restore; real Add-AppxPackage x64 launch validation with SetupEngine window.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@indierawk2k2 indierawk2k2 changed the title MSIX-only distribution hardening: manifest, permissions, .appinstaller updates, sunset Inno, recovery CLI, test plan MSIX distribution hardening: AppInstaller updates and packaged setup launch May 28, 2026
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels May 28, 2026
Mike Harsh and others added 4 commits May 27, 2026 22:15
Remove temporary SetupEngine startup breadcrumb logging now that packaged launch is stable, keep the targeted WindowsAppRuntime load, and adapt the merged async event-handler guard requirement.

Validation: ./build.ps1; dotnet test ./tests/OpenClaw.Shared.Tests/OpenClaw.Shared.Tests.csproj --no-restore; dotnet test ./tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj --no-restore.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Await the packaged auto-start API from Settings and roll back persisted state when Windows rejects StartupTask changes. Add an explicit MSIX build error when the embedded AppInstaller file is missing, and update the toast activation manifest comment to match the routed activation handler.

Validation: ./build.ps1; dotnet test ./tests/OpenClaw.Shared.Tests/OpenClaw.Shared.Tests.csproj --no-restore; dotnet test ./tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj --no-restore.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the PR-only Windows Terminal ghost-frame cleanup script, build hook, docs, and contract tests so this PR stays focused on packaging.

Validation: ./build.ps1; dotnet test ./tests/OpenClaw.Shared.Tests/OpenClaw.Shared.Tests.csproj --no-restore; dotnet test ./tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj --no-restore.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants