MSIX distribution hardening: AppInstaller updates and packaged setup launch#468
MSIX distribution hardening: AppInstaller updates and packaged setup launch#468indierawk2k2 wants to merge 59 commits into
Conversation
- 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>
Coverage gap found during PR creation flow — addressed in commit 9fdf999Two new ghost Terminal frames appeared during the Default terminal app on Win11 (the new default since 24H2) is Windows Terminal: \ That means every console-spawning child process — The cleanup we already shipped only fires from triggers we wired:
Fix (commit
|
| 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).
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>
|
Codex review: needs real behavior proof before merge. Reviewed May 28, 2026, 1:58 AM ET / 05:58 UTC. Summary 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.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review findings
Review detailsBest 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:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model gpt-5.5, reasoning high; reviewed against 6a5921883c86. Label changesLabel justifications:
Evidence reviewedAcceptance criteria:
What I checked:
Likely related people:
What the crustacean ranks mean
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
|
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>
|
Hanselman adversarial review completed after master resync + production MSIX cleanup Models:
Consensus table
Fixes made
Production prep completed
Validation after review fixes
|
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>
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>
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>
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>
… workflow Adopts the publishing-infrastructure pattern from PR #468 (treated as a reference design only, not cherry-picked) and adapts it to our WindowsAppSDKSelfContained=true MSIX. Because the WindowsAppRuntime is bundled inside the .msix, the AppInstaller feed has no <Dependencies> block, no runtime-URI rendering, and no separate runtime MSIX release asset. What this commit adds - installer/openclaw-companion.appinstaller.template — 6 placeholders (VERSION, PUBLISHER, IDENTITY_NAME, PROCESSOR_ARCHITECTURE, MSIX_URI, APPINSTALLER_URI), AutomaticBackgroundTask-only UpdateSettings. - installer/appinstaller/openclaw-{x64,arm64}.appinstaller — bootstrap feed files at version 0.0.0.0; the appinstaller-feed-pr workflow rewrites these on each stable release tag. - installer/appinstaller/README.md — explains the stable-feed model. - scripts/render-appinstaller.ps1 — substitutes placeholders, asserts the rendered XML parses and contains no <Dependencies> block. - scripts/validate-appinstaller-hosting.ps1 — Content-Type / Content- Length / Range checks against the hosted .appinstaller and .msix URLs, with -AllowGitHubContentTypes for raw.githubusercontent.com. - scripts/test-appinstaller-update.ps1 — local HttpListener-backed vN -> vN+1 upgrade smoke using PackageManager .AddPackageByAppInstallerFileAsync. - .github/workflows/appinstaller-feed-pr.yml — workflow_dispatch input takes a release tag, renders the two feed files, validates them, and opens a PR to advance the stable feed. Uses OpenClaw Foundation publisher; rejects pre-release tags; here-string PR body uses Set-Content -Value so the body renders as Markdown (fixes the 8-space-indent code-block bug in PR #468's workflow). - tests/OpenClaw.Tray.Tests/AppInstallerTemplateAssertionTests.cs — 8 test methods covering template shape, the new Template_HasNoDependenciesBlock invariant, the two bootstrap feed files, the validation script, the smoke script, and the feed-update workflow. - README.md — replaces the Phase-7 TODO placeholder with x64 and ARM64 Install links pointing at the raw GitHub .appinstaller URLs. What this commit does NOT add - No in-app "Check for updates" button or AppInstallerUpdateService. Windows AppInstaller's AutomaticBackgroundTask handles all polling at OS level under MSIX. - No Microsoft.WindowsAppRuntime.2 release asset or feed dependency — the runtime is bundled (WindowsAppSDKSelfContained=true). Validation - ./build.ps1: green. - Shared.Tests: 2049 passed / 29 skipped (matches baseline). - Tray.Tests: 957 passed (was 943; +14 effective test cases from the new file). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Master shipped sizes 24/32/48/256; Windows down-scaled larger PNGs for
Start Menu / taskbar / Alt+Tab surfaces that prefer 16/20/44. These three
PNGs fill the gap and are auto-discovered by MakePri via the existing
Assets\**\* content include and the targetsize-NN_altform-unplated
filename convention. No code, csproj, or manifest changes required.
Cherry-picked from origin/pr-468 (Square44x44Logo.targetsize-{16,20,44}_altform-unplated.png).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Delete Inno Setup distribution path now that MSIX is the always-on package type. Phase 3B will follow up with Updatum removal; Phase 4 will rebuild the release pipeline around the .msix file produced by `-p:PackageMsix=true`. Deleted files (5 source + 2 test): - installer.iss (256 lines) - scripts/build-inno-local.ps1 - scripts/Uninstall-LocalGateway.ps1 - src/OpenClaw.Tray.WinUI/app.manifest (orphaned by Phase 2) - tests/PackagingTests/Test-InnoUninstallOrdering.ps1 (parent dir removed) - tests/OpenClaw.Tray.Tests/InstallerIssAssertionTests.cs (9 tests) - tests/OpenClaw.Tray.Tests/ReleaseSigningWorkflowTests.cs (4 tests; Phase 5 will replace with MSIX-signing assertions) ci.yml: drop -p:Unpackaged=true from build/publish (Phase 2 cleanup), retire the Download VC Redist + Install Inno Setup + Build x64/arm64 Installer + Sign Installers steps in the release job, remove .exe entries from the release files: list and body, and update the paused build-msix comment. App.xaml.cs: remove the AppMutex coordination comment that referenced installer.iss; the OpenClawTray mutex itself stays unchanged. SetupEngine.UI/LogFileLauncher.cs: rewrite the "Unpackaged process" comment to cover both the no-package-identity and library-only call sites. Ship-guard ported from installer.iss to MSBuild: two new targets on OpenClaw.Tray.WinUI.csproj (ValidateSetupEngineUiNotShipped after Build, ValidateSetupEngineUiNotPublished after Publish) fail the build if OpenClaw.SetupEngine.UI.exe lands in the tray bin/publish output. Pairs with a new "Hazards" section in docs/SETUP_ENGINE_REDESIGN.md explaining the in-process design and pointing at PR #468's bootstrapper diffs as the worked example anyone splitting SetupEngine into its own process would need to study first. Test-ReleaseNativeDependencies.ps1: remove -RequireInstallerVCRedist / -InstallerVCRedistPath params and their dead handler (only Inno called them); -RequireAppLocalVCRuntime and -SkipNativeLoadProbe stay (still used by ci.yml's Verify Native Runtime Payload steps). Docs: drop the Inno helper section + .exe-installer references from DEVELOPMENT.md, docs/RELEASING.md, docs/VERSIONING.md, and the Inno comment in scripts/validate-msix-storage-paths.ps1. README.md + docs/SETUP.md download tables are replaced with TODO placeholders that Phase 7 will fill with MSIX-flavored content. Validation (on user/kmahone/msix, Windows): - ./build.ps1 green - Shared.Tests: 2049 passed / 29 skipped (unchanged from Phase 2) - Tray.Tests: 945 passed (was 958; -13 = 9 InstallerIssAssertion + 4 ReleaseSigningWorkflow tests removed, as expected) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… workflow Adopts the publishing-infrastructure pattern from PR #468 (treated as a reference design only, not cherry-picked) and adapts it to our WindowsAppSDKSelfContained=true MSIX. Because the WindowsAppRuntime is bundled inside the .msix, the AppInstaller feed has no <Dependencies> block, no runtime-URI rendering, and no separate runtime MSIX release asset. What this commit adds - installer/openclaw-companion.appinstaller.template — 6 placeholders (VERSION, PUBLISHER, IDENTITY_NAME, PROCESSOR_ARCHITECTURE, MSIX_URI, APPINSTALLER_URI), AutomaticBackgroundTask-only UpdateSettings. - installer/appinstaller/openclaw-{x64,arm64}.appinstaller — bootstrap feed files at version 0.0.0.0; the appinstaller-feed-pr workflow rewrites these on each stable release tag. - installer/appinstaller/README.md — explains the stable-feed model. - scripts/render-appinstaller.ps1 — substitutes placeholders, asserts the rendered XML parses and contains no <Dependencies> block. - scripts/validate-appinstaller-hosting.ps1 — Content-Type / Content- Length / Range checks against the hosted .appinstaller and .msix URLs, with -AllowGitHubContentTypes for raw.githubusercontent.com. - scripts/test-appinstaller-update.ps1 — local HttpListener-backed vN -> vN+1 upgrade smoke using PackageManager .AddPackageByAppInstallerFileAsync. - .github/workflows/appinstaller-feed-pr.yml — workflow_dispatch input takes a release tag, renders the two feed files, validates them, and opens a PR to advance the stable feed. Uses OpenClaw Foundation publisher; rejects pre-release tags; here-string PR body uses Set-Content -Value so the body renders as Markdown (fixes the 8-space-indent code-block bug in PR #468's workflow). - tests/OpenClaw.Tray.Tests/AppInstallerTemplateAssertionTests.cs — 8 test methods covering template shape, the new Template_HasNoDependenciesBlock invariant, the two bootstrap feed files, the validation script, the smoke script, and the feed-update workflow. - README.md — replaces the Phase-7 TODO placeholder with x64 and ARM64 Install links pointing at the raw GitHub .appinstaller URLs. What this commit does NOT add - No in-app "Check for updates" button or AppInstallerUpdateService. Windows AppInstaller's AutomaticBackgroundTask handles all polling at OS level under MSIX. - No Microsoft.WindowsAppRuntime.2 release asset or feed dependency — the runtime is bundled (WindowsAppSDKSelfContained=true). Validation - ./build.ps1: green. - Shared.Tests: 2049 passed / 29 skipped (matches baseline). - Tray.Tests: 957 passed (was 943; +14 effective test cases from the new file). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Master shipped sizes 24/32/48/256; Windows down-scaled larger PNGs for
Start Menu / taskbar / Alt+Tab surfaces that prefer 16/20/44. These three
PNGs fill the gap and are auto-discovered by MakePri via the existing
Assets\**\* content include and the targetsize-NN_altform-unplated
filename convention. No code, csproj, or manifest changes required.
Cherry-picked from origin/pr-468 (Square44x44Logo.targetsize-{16,20,44}_altform-unplated.png).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Delete Inno Setup distribution path now that MSIX is the always-on package type. Phase 3B will follow up with Updatum removal; Phase 4 will rebuild the release pipeline around the .msix file produced by `-p:PackageMsix=true`. Deleted files (5 source + 2 test): - installer.iss (256 lines) - scripts/build-inno-local.ps1 - scripts/Uninstall-LocalGateway.ps1 - src/OpenClaw.Tray.WinUI/app.manifest (orphaned by Phase 2) - tests/PackagingTests/Test-InnoUninstallOrdering.ps1 (parent dir removed) - tests/OpenClaw.Tray.Tests/InstallerIssAssertionTests.cs (9 tests) - tests/OpenClaw.Tray.Tests/ReleaseSigningWorkflowTests.cs (4 tests; Phase 5 will replace with MSIX-signing assertions) ci.yml: drop -p:Unpackaged=true from build/publish (Phase 2 cleanup), retire the Download VC Redist + Install Inno Setup + Build x64/arm64 Installer + Sign Installers steps in the release job, remove .exe entries from the release files: list and body, and update the paused build-msix comment. App.xaml.cs: remove the AppMutex coordination comment that referenced installer.iss; the OpenClawTray mutex itself stays unchanged. SetupEngine.UI/LogFileLauncher.cs: rewrite the "Unpackaged process" comment to cover both the no-package-identity and library-only call sites. Ship-guard ported from installer.iss to MSBuild: two new targets on OpenClaw.Tray.WinUI.csproj (ValidateSetupEngineUiNotShipped after Build, ValidateSetupEngineUiNotPublished after Publish) fail the build if OpenClaw.SetupEngine.UI.exe lands in the tray bin/publish output. Pairs with a new "Hazards" section in docs/SETUP_ENGINE_REDESIGN.md explaining the in-process design and pointing at PR #468's bootstrapper diffs as the worked example anyone splitting SetupEngine into its own process would need to study first. Test-ReleaseNativeDependencies.ps1: remove -RequireInstallerVCRedist / -InstallerVCRedistPath params and their dead handler (only Inno called them); -RequireAppLocalVCRuntime and -SkipNativeLoadProbe stay (still used by ci.yml's Verify Native Runtime Payload steps). Docs: drop the Inno helper section + .exe-installer references from DEVELOPMENT.md, docs/RELEASING.md, docs/VERSIONING.md, and the Inno comment in scripts/validate-msix-storage-paths.ps1. README.md + docs/SETUP.md download tables are replaced with TODO placeholders that Phase 7 will fill with MSIX-flavored content. Validation (on user/kmahone/msix, Windows): - ./build.ps1 green - Shared.Tests: 2049 passed / 29 skipped (unchanged from Phase 2) - Tray.Tests: 945 passed (was 958; -13 = 9 InstallerIssAssertion + 4 ReleaseSigningWorkflow tests removed, as expected) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… workflow Adopts the publishing-infrastructure pattern from PR #468 (treated as a reference design only, not cherry-picked) and adapts it to our WindowsAppSDKSelfContained=true MSIX. Because the WindowsAppRuntime is bundled inside the .msix, the AppInstaller feed has no <Dependencies> block, no runtime-URI rendering, and no separate runtime MSIX release asset. What this commit adds - installer/openclaw-companion.appinstaller.template — 6 placeholders (VERSION, PUBLISHER, IDENTITY_NAME, PROCESSOR_ARCHITECTURE, MSIX_URI, APPINSTALLER_URI), AutomaticBackgroundTask-only UpdateSettings. - installer/appinstaller/openclaw-{x64,arm64}.appinstaller — bootstrap feed files at version 0.0.0.0; the appinstaller-feed-pr workflow rewrites these on each stable release tag. - installer/appinstaller/README.md — explains the stable-feed model. - scripts/render-appinstaller.ps1 — substitutes placeholders, asserts the rendered XML parses and contains no <Dependencies> block. - scripts/validate-appinstaller-hosting.ps1 — Content-Type / Content- Length / Range checks against the hosted .appinstaller and .msix URLs, with -AllowGitHubContentTypes for raw.githubusercontent.com. - scripts/test-appinstaller-update.ps1 — local HttpListener-backed vN -> vN+1 upgrade smoke using PackageManager .AddPackageByAppInstallerFileAsync. - .github/workflows/appinstaller-feed-pr.yml — workflow_dispatch input takes a release tag, renders the two feed files, validates them, and opens a PR to advance the stable feed. Uses OpenClaw Foundation publisher; rejects pre-release tags; here-string PR body uses Set-Content -Value so the body renders as Markdown (fixes the 8-space-indent code-block bug in PR #468's workflow). - tests/OpenClaw.Tray.Tests/AppInstallerTemplateAssertionTests.cs — 8 test methods covering template shape, the new Template_HasNoDependenciesBlock invariant, the two bootstrap feed files, the validation script, the smoke script, and the feed-update workflow. - README.md — replaces the Phase-7 TODO placeholder with x64 and ARM64 Install links pointing at the raw GitHub .appinstaller URLs. What this commit does NOT add - No in-app "Check for updates" button or AppInstallerUpdateService. Windows AppInstaller's AutomaticBackgroundTask handles all polling at OS level under MSIX. - No Microsoft.WindowsAppRuntime.2 release asset or feed dependency — the runtime is bundled (WindowsAppSDKSelfContained=true). Validation - ./build.ps1: green. - Shared.Tests: 2049 passed / 29 skipped (matches baseline). - Tray.Tests: 957 passed (was 943; +14 effective test cases from the new file). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Thanks @indierawk2k2 for putting this together. We're going to abandon this draft in favor of #732, which is the newer updated version of this work. |
Simplifies the MSIX packaging PR while keeping the AppInstaller work:
WindowsAppSdkBootstrapInitialize=false, XAML resources are injected after packaging, and the tray launches it directly withUseShellExecute=false.Validation completed locally:
./build.ps1dotnet test ./tests/OpenClaw.Shared.Tests/OpenClaw.Shared.Tests.csproj --no-restoredotnet test ./tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj --no-restoreAdd-AppxPackage -Pathinstall/launch validation with trusted test cert: tray launched and SetupEngine showedOpenClaw 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