diff --git a/installer.iss b/installer.iss
index e4a5ea4c5..74cab78fe 100644
--- a/installer.iss
+++ b/installer.iss
@@ -2,6 +2,7 @@
; Pass /DDevBuild=1 to produce a side-by-side dev installer.
#ifdef DevBuild
#define MyAppName "OpenClaw Companion (Dev)"
+ #define MyAppAumid "OpenClaw.Companion.Dev"
#define MyAppId "{{M0LTB0T-TRAY-4PP1-DEV}"
#define MyInstallDir "OpenClawTray-Dev"
#define MyMutex "OpenClawTray-Dev"
@@ -12,6 +13,7 @@
#define MyOutputSuffix "-Dev"
#else
#define MyAppName "OpenClaw Companion"
+ #define MyAppAumid "OpenClaw.Companion"
#define MyAppId "{{M0LTB0T-TRAY-4PP1-D3N7}"
#define MyInstallDir "OpenClawTray"
#define MyMutex "OpenClawTray"
@@ -112,14 +114,14 @@ Root: HKCU; Subkey: "Software\Classes\{#MyProtocol}\DefaultIcon"; ValueType: str
Root: HKCU; Subkey: "Software\Classes\{#MyProtocol}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1"""
[Icons]
-Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
-Name: "{group}\OpenClaw Gateway Setup"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://setup"; IconFilename: "{app}\{#MyAppExeName}"
-Name: "{group}\OpenClaw Companion Settings"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://commandcenter"; IconFilename: "{app}\{#MyAppExeName}"
-Name: "{group}\OpenClaw Chat"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://chat"; IconFilename: "{app}\{#MyAppExeName}"
-Name: "{group}\Check for Updates"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://check-updates"; IconFilename: "{app}\{#MyAppExeName}"
+Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; AppUserModelID: "{#MyAppAumid}"
+Name: "{group}\OpenClaw Gateway Setup"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://setup"; IconFilename: "{app}\{#MyAppExeName}"; AppUserModelID: "{#MyAppAumid}"
+Name: "{group}\OpenClaw Companion Settings"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://commandcenter"; IconFilename: "{app}\{#MyAppExeName}"; AppUserModelID: "{#MyAppAumid}"
+Name: "{group}\OpenClaw Chat"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://chat"; IconFilename: "{app}\{#MyAppExeName}"; AppUserModelID: "{#MyAppAumid}"
+Name: "{group}\Check for Updates"; Filename: "{app}\{#MyAppExeName}"; Parameters: "{#MyProtocol}://check-updates"; IconFilename: "{app}\{#MyAppExeName}"; AppUserModelID: "{#MyAppAumid}"
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
-Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
-Name: "{userstartup}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: startupicon
+Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon; AppUserModelID: "{#MyAppAumid}"
+Name: "{userstartup}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: startupicon; AppUserModelID: "{#MyAppAumid}"
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent; Check: ShouldLaunchTray
diff --git a/src/OpenClaw.Tray.WinUI/App.xaml.cs b/src/OpenClaw.Tray.WinUI/App.xaml.cs
index abbc39cda..6ee2ed033 100644
--- a/src/OpenClaw.Tray.WinUI/App.xaml.cs
+++ b/src/OpenClaw.Tray.WinUI/App.xaml.cs
@@ -475,6 +475,12 @@ private async Task OnLaunchedAsync(LaunchActivatedEventArgs args)
// Store protocol URI for processing after setup
_pendingProtocolUri = protocolUri;
+ var appUserModelIdRegistration = AppUserModelIdRegistrar.RegisterCurrentProcess(AppIdentity.AppUserModelId);
+ if (appUserModelIdRegistration.Attempted && appUserModelIdRegistration.HResult < 0)
+ {
+ Logger.Warn($"Failed to set AppUserModelID '{AppIdentity.AppUserModelId}' (HRESULT=0x{appUserModelIdRegistration.HResult:X8}); toast sender name may fall back to the executable name.");
+ }
+
// Initialize settings before update check so skip selections can be remembered.
_settings = new SettingsManager();
_previousSettingsSnapshot = _settings.ToSettingsData().ToConnectionSnapshot();
diff --git a/src/OpenClaw.Tray.WinUI/AppIdentity.cs b/src/OpenClaw.Tray.WinUI/AppIdentity.cs
index b698cfa7f..b60eeefbf 100644
--- a/src/OpenClaw.Tray.WinUI/AppIdentity.cs
+++ b/src/OpenClaw.Tray.WinUI/AppIdentity.cs
@@ -16,6 +16,9 @@ internal static class AppIdentity
/// MSIX package identity name (must differ from release for side-by-side).
public const string PackageIdentityName = "OpenClaw.Companion.Dev";
+ /// Win32 AppUserModelID used for notifications and shell grouping.
+ public const string AppUserModelId = PackageIdentityName;
+
/// Windows Registry auto-start value name (must differ so both can auto-start).
public const string AutoStartRegistryName = "OpenClawTray-Dev";
@@ -52,6 +55,9 @@ internal static class AppIdentity
/// MSIX package identity name.
public const string PackageIdentityName = "OpenClaw.Companion";
+ /// Win32 AppUserModelID used for notifications and shell grouping.
+ public const string AppUserModelId = PackageIdentityName;
+
/// Windows Registry auto-start value name.
public const string AutoStartRegistryName = "OpenClawTray";
diff --git a/src/OpenClaw.Tray.WinUI/Dialogs/PairingApprovalDialog.cs b/src/OpenClaw.Tray.WinUI/Dialogs/PairingApprovalDialog.cs
index 9d4613276..1c0bdffe6 100644
--- a/src/OpenClaw.Tray.WinUI/Dialogs/PairingApprovalDialog.cs
+++ b/src/OpenClaw.Tray.WinUI/Dialogs/PairingApprovalDialog.cs
@@ -54,7 +54,8 @@ public PairingApprovalDialog(PairingApprovalCoordinator coordinator)
{
_coordinator = coordinator ?? throw new ArgumentNullException(nameof(coordinator));
- Title = LocalizationHelper.GetString("PairingApproval_WindowTitle");
+ var windowTitle = $"{AppIdentity.DisplayName} - {LocalizationHelper.GetString("PairingApproval_WindowTitle")}";
+ Title = windowTitle;
this.SetWindowSize(460, 460);
this.CenterOnScreen();
this.SetIcon("Assets\\openclaw.ico");
@@ -70,7 +71,7 @@ public PairingApprovalDialog(PairingApprovalCoordinator coordinator)
titleBar.Children.Add(titleIcon);
var titleText = new TextBlock
{
- Text = LocalizationHelper.GetString("PairingApproval_WindowTitle"),
+ Text = windowTitle,
FontSize = 13,
VerticalAlignment = VerticalAlignment.Center,
Style = (Style)Application.Current.Resources["CaptionTextBlockStyle"],
diff --git a/src/OpenClaw.Tray.WinUI/Dialogs/RecordingConsentDialog.cs b/src/OpenClaw.Tray.WinUI/Dialogs/RecordingConsentDialog.cs
index db2bdc7e2..7c17ba254 100644
--- a/src/OpenClaw.Tray.WinUI/Dialogs/RecordingConsentDialog.cs
+++ b/src/OpenClaw.Tray.WinUI/Dialogs/RecordingConsentDialog.cs
@@ -38,7 +38,8 @@ public RecordingConsentDialog(RecordingType type)
var descriptionKey = isScreen ? "RecordingConsent_ScreenDescription" : "RecordingConsent_CameraDescription";
var emoji = isScreen ? "🖥️" : "📷";
- Title = LocalizationHelper.GetString("RecordingConsent_WindowTitle");
+ var windowTitle = $"{AppIdentity.DisplayName} - {LocalizationHelper.GetString("RecordingConsent_WindowTitle")}";
+ Title = windowTitle;
this.SetWindowSize(460, 340);
this.CenterOnScreen();
this.SetIcon("Assets\\openclaw.ico");
@@ -67,7 +68,7 @@ public RecordingConsentDialog(RecordingType type)
var titleText = new TextBlock
{
- Text = LocalizationHelper.GetString("RecordingConsent_WindowTitle"),
+ Text = windowTitle,
FontSize = 13,
VerticalAlignment = VerticalAlignment.Center,
Style = (Style)Application.Current.Resources["CaptionTextBlockStyle"]
diff --git a/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj b/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj
index 7db7389ac..8838cba6e 100644
--- a/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj
+++ b/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj
@@ -8,6 +8,9 @@
enable
true
Assets\openclaw.ico
+ OpenClaw Companion
+ OpenClaw Companion
+ OpenClaw Companion
OpenClawTray
en-US
x64;ARM64
@@ -19,6 +22,9 @@
$(DefineConstants);DEV_BUILD
dev
+ OpenClaw Companion (Dev)
+ OpenClaw Companion (Dev)
+ OpenClaw Companion (Dev)
release
diff --git a/src/OpenClaw.Tray.WinUI/Services/AppUserModelIdRegistrar.cs b/src/OpenClaw.Tray.WinUI/Services/AppUserModelIdRegistrar.cs
new file mode 100644
index 000000000..194f25f6d
--- /dev/null
+++ b/src/OpenClaw.Tray.WinUI/Services/AppUserModelIdRegistrar.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace OpenClawTray.Services;
+
+internal readonly record struct AppUserModelIdRegistrationResult(bool Attempted, int HResult);
+
+internal static class AppUserModelIdRegistrar
+{
+ private const int AppModelErrorNoPackage = 15700;
+ private const int ErrorInsufficientBuffer = 122;
+ private const int ErrorSuccess = 0;
+
+ public static AppUserModelIdRegistrationResult RegisterCurrentProcess(string appUserModelId)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(appUserModelId);
+ if (HasPackageIdentity())
+ return new AppUserModelIdRegistrationResult(Attempted: false, HResult: ErrorSuccess);
+
+ return new AppUserModelIdRegistrationResult(
+ Attempted: true,
+ HResult: SetCurrentProcessExplicitAppUserModelID(appUserModelId));
+ }
+
+ private static bool HasPackageIdentity()
+ {
+ var packageFullNameLength = 0;
+ var result = GetCurrentPackageFullName(ref packageFullNameLength, null);
+ if (result == AppModelErrorNoPackage)
+ return false;
+
+ return result is ErrorSuccess or ErrorInsufficientBuffer;
+ }
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
+ private static extern int GetCurrentPackageFullName(
+ ref int packageFullNameLength,
+ [MarshalAs(UnmanagedType.LPWStr)] StringBuilder? packageFullName);
+
+ [DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
+ private static extern int SetCurrentProcessExplicitAppUserModelID(
+ [MarshalAs(UnmanagedType.LPWStr)] string appID);
+}
diff --git a/src/OpenClaw.Tray.WinUI/Services/ExecApprovalPromptService.cs b/src/OpenClaw.Tray.WinUI/Services/ExecApprovalPromptService.cs
index b37936609..19bf14f76 100644
--- a/src/OpenClaw.Tray.WinUI/Services/ExecApprovalPromptService.cs
+++ b/src/OpenClaw.Tray.WinUI/Services/ExecApprovalPromptService.cs
@@ -15,6 +15,7 @@ public sealed class ExecApprovalPromptService : IExecApprovalPromptHandler
private readonly IOpenClawLogger _logger;
private readonly Func? _chatProviderProvider;
private readonly Func? _inlineApprovalAvailable;
+ private static string NativePromptTitle => $"{AppIdentity.DisplayName} - Approve local command?";
public ExecApprovalPromptService(
DispatcherQueue dispatcherQueue,
@@ -161,7 +162,7 @@ private void RaiseDecided(
private static ExecApprovalPromptDecision ShowNativePrompt(ExecApprovalPromptRequest request)
{
var text =
- "A remote agent wants to run a local command on this Windows machine." +
+ $"{AppIdentity.DisplayName} needs approval before a remote agent can run a local command on this Windows machine." +
"\r\n\r\n" +
request.Command +
"\r\n\r\n" +
@@ -190,7 +191,7 @@ private static ExecApprovalPromptDecision ShowMessageBoxFallback(string text)
var result = MessageBoxW(
IntPtr.Zero,
fallbackText,
- "Approve local command?",
+ NativePromptTitle,
MessageBoxFlags.YesNoCancel |
MessageBoxFlags.IconWarning |
MessageBoxFlags.TopMost |
@@ -263,7 +264,7 @@ public ExecApprovalPromptDecision Show()
_hwnd = CreateWindowExW(
WindowExStyleTopMost | WindowExStyleDialogModalFrame,
WindowClassName,
- "Approve local command?",
+ NativePromptTitle,
WindowStyleCaption | WindowStyleSystemMenu | WindowStyleVisible,
x,
y,
@@ -341,7 +342,7 @@ private static void EnsureClassRegistered()
private void CreateControls(IntPtr hwnd)
{
var font = GetStockObject(DefaultGuiFont);
- CreateChild(hwnd, "STATIC", "Approve local command?", 20, 18, 590, 24, StaticStyleLeft, 0, font);
+ CreateChild(hwnd, "STATIC", NativePromptTitle, 20, 18, 590, 24, StaticStyleLeft, 0, font);
CreateChild(
hwnd,
"EDIT",
diff --git a/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw
index a332f9b8b..0320ecfe4 100644
--- a/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw
+++ b/src/OpenClaw.Tray.WinUI/Strings/en-us/Resources.resw
@@ -832,7 +832,7 @@ Use one of these options:
- OpenClaw · Permission Request
+ Permission Request
Allow screen recording?
diff --git a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw
index daacb46bb..c0122184b 100644
--- a/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw
+++ b/src/OpenClaw.Tray.WinUI/Strings/fr-fr/Resources.resw
@@ -789,7 +789,7 @@ Utilisez l'une de ces options :
- OpenClaw · Demande d'autorisation
+ Demande d'autorisation
Autoriser l'enregistrement d'écran ?
diff --git a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw
index 027effb4e..a4b90a586 100644
--- a/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw
+++ b/src/OpenClaw.Tray.WinUI/Strings/nl-nl/Resources.resw
@@ -790,7 +790,7 @@ Gebruik een van deze opties:
- OpenClaw · Toestemmingsverzoek
+ Toestemmingsverzoek
Schermopname toestaan?
diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw
index cb67066e2..e6f218312 100644
--- a/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw
+++ b/src/OpenClaw.Tray.WinUI/Strings/zh-cn/Resources.resw
@@ -789,7 +789,7 @@
- OpenClaw · 权限请求
+ 权限请求
允许屏幕录制?
diff --git a/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw b/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw
index daf97d636..d9af27043 100644
--- a/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw
+++ b/src/OpenClaw.Tray.WinUI/Strings/zh-tw/Resources.resw
@@ -789,7 +789,7 @@
- OpenClaw · 權限請求
+ 權限請求
允許螢幕錄製?
diff --git a/src/OpenClaw.Tray.WinUI/app.manifest b/src/OpenClaw.Tray.WinUI/app.manifest
index 7361ce5c9..54924b624 100644
--- a/src/OpenClaw.Tray.WinUI/app.manifest
+++ b/src/OpenClaw.Tray.WinUI/app.manifest
@@ -1,6 +1,6 @@
-
+
diff --git a/tests/OpenClaw.Tray.Tests/AppRefactorContractTests.cs b/tests/OpenClaw.Tray.Tests/AppRefactorContractTests.cs
index 22df8310f..7d9af313a 100644
--- a/tests/OpenClaw.Tray.Tests/AppRefactorContractTests.cs
+++ b/tests/OpenClaw.Tray.Tests/AppRefactorContractTests.cs
@@ -28,6 +28,8 @@ public void Startup_Order_PreservesInitializationInvariants()
AssertInOrder(
source,
+ "AppUserModelIdRegistrar.RegisterCurrentProcess(AppIdentity.AppUserModelId);",
+ "appUserModelIdRegistration.Attempted",
"_settings = new SettingsManager();",
"CheckForUpdatesAsync();",
"ToastNotificationManagerCompat.OnActivated += OnToastActivated;",
diff --git a/tests/OpenClaw.Tray.Tests/InstallerIssAssertionTests.cs b/tests/OpenClaw.Tray.Tests/InstallerIssAssertionTests.cs
index 673be2ede..33da13085 100644
--- a/tests/OpenClaw.Tray.Tests/InstallerIssAssertionTests.cs
+++ b/tests/OpenClaw.Tray.Tests/InstallerIssAssertionTests.cs
@@ -46,14 +46,24 @@ public void Installer_CreatesStartMenuEntrypointsForTraySetupAndSupport()
var iss = File.ReadAllText(Path.Combine(TestRepositoryPaths.GetRepositoryRoot(), "installer.iss"));
Assert.Contains(@"#define MyAppName ""OpenClaw Companion""", iss);
+ Assert.Contains(@"#define MyAppAumid ""OpenClaw.Companion""", iss);
Assert.Contains(@"#define MyCompression ""lzma""", iss);
Assert.Contains(@"#define MySolidCompression ""yes""", iss);
Assert.Contains("OutputBaseFilename=OpenClawCompanion{#MyOutputSuffix}-Setup-{#MyAppArch}", iss);
- Assert.Contains(@"Name: ""{group}\{#MyAppName}""; Filename: ""{app}\{#MyAppExeName}""", iss);
- Assert.Contains(@"Name: ""{group}\OpenClaw Gateway Setup""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://setup""", iss);
- Assert.Contains(@"Name: ""{group}\OpenClaw Companion Settings""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://commandcenter""", iss);
- Assert.Contains(@"Name: ""{group}\OpenClaw Chat""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://chat""", iss);
- Assert.Contains(@"Name: ""{group}\Check for Updates""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://check-updates""", iss);
+ foreach (var iconEntry in new[]
+ {
+ @"Name: ""{group}\{#MyAppName}""; Filename: ""{app}\{#MyAppExeName}""; AppUserModelID: ""{#MyAppAumid}""",
+ @"Name: ""{group}\OpenClaw Gateway Setup""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://setup""; IconFilename: ""{app}\{#MyAppExeName}""; AppUserModelID: ""{#MyAppAumid}""",
+ @"Name: ""{group}\OpenClaw Companion Settings""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://commandcenter""; IconFilename: ""{app}\{#MyAppExeName}""; AppUserModelID: ""{#MyAppAumid}""",
+ @"Name: ""{group}\OpenClaw Chat""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://chat""; IconFilename: ""{app}\{#MyAppExeName}""; AppUserModelID: ""{#MyAppAumid}""",
+ @"Name: ""{group}\Check for Updates""; Filename: ""{app}\{#MyAppExeName}""; Parameters: ""{#MyProtocol}://check-updates""; IconFilename: ""{app}\{#MyAppExeName}""; AppUserModelID: ""{#MyAppAumid}""",
+ @"Name: ""{autodesktop}\{#MyAppName}""; Filename: ""{app}\{#MyAppExeName}""; Tasks: desktopicon; AppUserModelID: ""{#MyAppAumid}""",
+ @"Name: ""{userstartup}\{#MyAppName}""; Filename: ""{app}\{#MyAppExeName}""; Tasks: startupicon; AppUserModelID: ""{#MyAppAumid}"""
+ })
+ {
+ Assert.Contains(iconEntry, iss);
+ }
+ Assert.DoesNotContain("AppUserModelID: \"OpenClaw.Tray.WinUI\"", iss);
}
[Fact]
@@ -132,6 +142,7 @@ public void DevInstaller_UsesIndependentIdentityAndProtocol()
var iss = File.ReadAllText(Path.Combine(TestRepositoryPaths.GetRepositoryRoot(), "installer.iss"));
Assert.Contains(@"#define MyAppName ""OpenClaw Companion (Dev)""", iss);
+ Assert.Contains(@"#define MyAppAumid ""OpenClaw.Companion.Dev""", iss);
Assert.Contains(@"#define MyInstallDir ""OpenClawTray-Dev""", iss);
Assert.Contains(@"#define MyMutex ""OpenClawTray-Dev""", iss);
Assert.Contains(@"#define MyProtocol ""openclaw-dev""", iss);
diff --git a/tests/OpenClaw.Tray.Tests/Services/AppUserModelIdIdentityTests.cs b/tests/OpenClaw.Tray.Tests/Services/AppUserModelIdIdentityTests.cs
new file mode 100644
index 000000000..76f3f6b7c
--- /dev/null
+++ b/tests/OpenClaw.Tray.Tests/Services/AppUserModelIdIdentityTests.cs
@@ -0,0 +1,91 @@
+using System.IO;
+using OpenClawTray;
+using Xunit;
+
+namespace OpenClaw.Tray.Tests.Services;
+
+public sealed class AppUserModelIdIdentityTests
+{
+ [Fact]
+ public void WinUiProject_UsesHumanReadableMetadataForNotificationSenderFallback()
+ {
+ var project = File.ReadAllText(Path.Combine(
+ TestRepositoryPaths.GetRepositoryRoot(),
+ "src",
+ "OpenClaw.Tray.WinUI",
+ "OpenClaw.Tray.WinUI.csproj"));
+
+ Assert.Contains("OpenClaw Companion", project);
+ Assert.Contains("OpenClaw Companion", project);
+ Assert.Contains("OpenClaw Companion", project);
+ Assert.Contains("OpenClaw Companion (Dev)", project);
+ Assert.Contains("OpenClaw Companion (Dev)", project);
+ Assert.Contains("OpenClaw Companion (Dev)", project);
+ Assert.DoesNotContain("OpenClaw.Tray.WinUI", project);
+ }
+
+ [Fact]
+ public void Registrar_SkipsExplicitAumidWhenMsixPackageIdentityExists()
+ {
+ var source = File.ReadAllText(Path.Combine(
+ TestRepositoryPaths.GetRepositoryRoot(),
+ "src",
+ "OpenClaw.Tray.WinUI",
+ "Services",
+ "AppUserModelIdRegistrar.cs"));
+
+ Assert.Contains("GetCurrentPackageFullName", source);
+ Assert.Contains("AppModelErrorNoPackage", source);
+ Assert.Contains("HResult", source);
+ Assert.Contains("SetCurrentProcessExplicitAppUserModelID", source);
+ }
+
+ [Fact]
+ public void AppUserModelId_UsesCompanionIdentity()
+ {
+ Assert.Equal(AppIdentity.PackageIdentityName, AppIdentity.AppUserModelId);
+ Assert.DoesNotContain("OpenClaw.Tray.WinUI", AppIdentity.AppUserModelId);
+ }
+
+ [Fact]
+ public void InstallerAumid_MatchesRuntimeAppUserModelId()
+ {
+ var iss = File.ReadAllText(Path.Combine(
+ TestRepositoryPaths.GetRepositoryRoot(),
+ "installer.iss"));
+
+ Assert.Contains($@"#define MyAppAumid ""{AppIdentity.AppUserModelId}""", iss);
+ Assert.Contains(@"#define MyAppAumid ""OpenClaw.Companion.Dev""", iss);
+ }
+
+ [Fact]
+ public void UnpackagedManifest_UsesCompanionIdentityForSystemPrompts()
+ {
+ var manifest = File.ReadAllText(Path.Combine(
+ TestRepositoryPaths.GetRepositoryRoot(),
+ "src",
+ "OpenClaw.Tray.WinUI",
+ "app.manifest"));
+
+ Assert.Contains(@"", manifest);
+ Assert.DoesNotContain(@"name=""OpenClaw.Tray.WinUI""", manifest);
+ }
+
+ [Fact]
+ public void ApprovalPopups_UseCompanionDisplayName()
+ {
+ var root = TestRepositoryPaths.GetRepositoryRoot();
+ var pairingDialog = File.ReadAllText(Path.Combine(root, "src", "OpenClaw.Tray.WinUI", "Dialogs", "PairingApprovalDialog.cs"));
+ var recordingDialog = File.ReadAllText(Path.Combine(root, "src", "OpenClaw.Tray.WinUI", "Dialogs", "RecordingConsentDialog.cs"));
+ var execPrompt = File.ReadAllText(Path.Combine(root, "src", "OpenClaw.Tray.WinUI", "Services", "ExecApprovalPromptService.cs"));
+
+ Assert.Contains("AppIdentity.DisplayName", pairingDialog);
+ Assert.Contains("AppIdentity.DisplayName", recordingDialog);
+ Assert.Contains("AppIdentity.DisplayName", execPrompt);
+ Assert.DoesNotContain("OpenClaw · Permission Request", File.ReadAllText(Path.Combine(root, "src", "OpenClaw.Tray.WinUI", "Strings", "en-us", "Resources.resw")));
+ Assert.Contains("NativePromptTitle", execPrompt);
+ Assert.DoesNotContain("OpenClaw.Tray.WinUI", pairingDialog);
+ Assert.DoesNotContain("OpenClaw.Tray.WinUI", recordingDialog);
+ Assert.DoesNotContain("OpenClaw.Tray.WinUI", execPrompt);
+ }
+}