diff --git a/.gitattributes b/.gitattributes
index 1b8a169a6c..794e109aae 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,2 +1,5 @@
src/UniGetUI.PackageEngine.Managers.Chocolatey/choco-cli/** linguist-vendored
-.githooks/* text eol=lf
\ No newline at end of file
+.githooks/* text eol=lf
+
+policies/samples/** linguist-generated
+policies/schemas/** linguist-generated
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 711cadf3d3..4c20afaa00 100644
--- a/.gitignore
+++ b/.gitignore
@@ -96,3 +96,7 @@ src/UniGetUI.v3.ncrunchsolution
# macOS Finder metadata
.DS_Store
/src/UniGetUI.Avalonia/Generated Files
+
+
+policies/csharp/.vs/*
+src/UniGetUI.PackageEngine.Managers.WinGet
\ No newline at end of file
diff --git a/global.json b/global.json
index c358071f08..abe5820849 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "10.0.103",
- "rollForward": "latestPatch"
+ "rollForward": "latestFeature"
}
}
diff --git a/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs b/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs
index 601c1865f6..e04209e349 100644
--- a/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs
+++ b/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs
@@ -92,6 +92,9 @@ public enum K
DisableClassicMode,
DisableInstallerHostChangeWarning,
BunPreferLatestVersions,
+ // NOTE: Set this to true to delegate package operations to Devolutions Agent broker
+ // instead of using local UAC elevation. Change default here when ready for production.
+ UseAgentBroker,
Test1,
Test2,
@@ -195,6 +198,7 @@ public static string ResolveKey(K key)
K.DisableClassicMode => "DisableClassicMode",
K.DisableInstallerHostChangeWarning => "DisableInstallerHostChangeWarning",
K.BunPreferLatestVersions => "BunPreferLatestVersions",
+ K.UseAgentBroker => "UseAgentBroker",
K.Test1 => "TestSetting1",
K.Test2 => "TestSetting2",
diff --git a/src/UniGetUI.PackageEngine.AgentBroker/BrokerRequestBuilder.cs b/src/UniGetUI.PackageEngine.AgentBroker/BrokerRequestBuilder.cs
new file mode 100644
index 0000000000..eba1960074
--- /dev/null
+++ b/src/UniGetUI.PackageEngine.AgentBroker/BrokerRequestBuilder.cs
@@ -0,0 +1,161 @@
+using Devolutions.UniGetUI.Broker.Client;
+using UniGetUI.PackageEngine.Enums;
+using UniGetUI.PackageEngine.Interfaces;
+using UniGetUI.PackageEngine.Serializable;
+// Aliased to avoid clashing with UniGetUI.PackageEngine.Enums.Architecture.
+using BrokerArchitecture = Devolutions.UniGetUI.Broker.Client.Architecture;
+
+namespace UniGetUI.PackageEngine.AgentBroker;
+
+///
+/// Builds broker protocol requests from UniGetUI domain objects.
+/// Maps IPackage + InstallOptions + OperationType into the canonical
+/// consumed by the Devolutions Agent broker.
+///
+public static class BrokerRequestBuilder
+{
+ private static readonly string ClientVersion =
+ System.Reflection.Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0";
+
+ /// Build a broker request from UniGetUI package operation parameters.
+ public static PackageRequest Build(IPackage package, InstallOptions options, OperationType role)
+ {
+ return new PackageRequest
+ {
+ RequestId = $"req-{Guid.NewGuid():N}",
+ CreatedAt = DateTimeOffset.UtcNow,
+ Operation = MapOperation(role),
+ Manager = new RequestManager
+ {
+ Name = MapManagerName(package.Manager.Name),
+ DisplayName = package.Manager.DisplayName,
+ ExecutableFriendlyName = Path.GetFileName(package.Manager.Status.ExecutablePath),
+ },
+ Source = new RequestSource
+ {
+ Name = package.Source.Name,
+ Url = package.Source.Url?.ToString(),
+ IsVirtualManager = false,
+ },
+ Package = new RequestPackage
+ {
+ Id = package.Id,
+ Name = package.Name,
+ Version = string.IsNullOrEmpty(options.Version) ? null : options.Version,
+ Architecture = MapArchitecture(options.Architecture),
+ },
+ Options = new RequestOptions
+ {
+ Scope = MapScope(options.InstallationScope),
+ Interactive = options.InteractiveInstallation,
+ SkipHashCheck = options.SkipHashCheck,
+ PreRelease = options.PreRelease,
+ CustomParameters = GetCustomParameters(options, role),
+ CustomInstallLocation = string.IsNullOrEmpty(options.CustomInstallLocation) ? null : options.CustomInstallLocation,
+ KillBeforeOperation = options.KillBeforeOperation ?? [],
+ PreOperationCommand = GetPreCommand(options, role),
+ PostOperationCommand = GetPostCommand(options, role),
+ },
+ Broker = new BrokerContext
+ {
+ RequestedElevation = options.RunAsAdministrator ? Elevation.Elevated : Elevation.Standard,
+ EffectiveUser = $"{Environment.UserDomainName}\\{Environment.UserName}",
+ ClientVersion = ClientVersion,
+ ClientProcessPath = Environment.ProcessPath,
+ },
+ };
+ }
+
+ private static Operation MapOperation(OperationType role) => role switch
+ {
+ OperationType.Install => Operation.Install,
+ OperationType.Update => Operation.Update,
+ OperationType.Uninstall => Operation.Uninstall,
+ _ => throw new ArgumentException($"Unsupported operation type: {role}"),
+ };
+
+ ///
+ /// Maps UniGetUI manager names to the broker protocol canonical managers.
+ /// PowerShell 5 and PowerShell 7 are modeled as separate managers.
+ ///
+ private static ManagerName MapManagerName(string managerName)
+ {
+ if (managerName.Equals("Winget", StringComparison.OrdinalIgnoreCase))
+ {
+ return ManagerName.Winget;
+ }
+
+ if (managerName.Equals("PowerShell", StringComparison.OrdinalIgnoreCase))
+ {
+ return ManagerName.PowerShell;
+ }
+
+ if (managerName.Equals("PowerShell7", StringComparison.OrdinalIgnoreCase) ||
+ managerName.Equals("pwsh", StringComparison.OrdinalIgnoreCase))
+ {
+ return ManagerName.PowerShell7;
+ }
+
+ throw new ArgumentException($"Unsupported manager for the broker: {managerName}");
+ }
+
+ private static Scope? MapScope(string? scope)
+ {
+ if (string.IsNullOrEmpty(scope))
+ {
+ return null;
+ }
+
+ return scope.ToLowerInvariant() switch
+ {
+ "user" => Scope.User,
+ "machine" => Scope.Machine,
+ "global" => Scope.Machine,
+ _ => null,
+ };
+ }
+
+ private static BrokerArchitecture? MapArchitecture(string? architecture)
+ {
+ if (string.IsNullOrEmpty(architecture))
+ {
+ return null;
+ }
+
+ return architecture.ToLowerInvariant() switch
+ {
+ "x86" => BrokerArchitecture.X86,
+ "x64" => BrokerArchitecture.X64,
+ "arm64" => BrokerArchitecture.Arm64,
+ "neutral" => BrokerArchitecture.Neutral,
+ _ => null,
+ };
+ }
+
+ private static List GetCustomParameters(InstallOptions options, OperationType role) => role switch
+ {
+ OperationType.Install => options.CustomParameters_Install ?? [],
+ OperationType.Update => options.CustomParameters_Update ?? [],
+ OperationType.Uninstall => options.CustomParameters_Uninstall ?? [],
+ _ => [],
+ };
+
+ private static string? GetPreCommand(InstallOptions options, OperationType role) => role switch
+ {
+ OperationType.Install => NullIfEmpty(options.PreInstallCommand),
+ OperationType.Update => NullIfEmpty(options.PreUpdateCommand),
+ OperationType.Uninstall => NullIfEmpty(options.PreUninstallCommand),
+ _ => null,
+ };
+
+ private static string? GetPostCommand(InstallOptions options, OperationType role) => role switch
+ {
+ OperationType.Install => NullIfEmpty(options.PostInstallCommand),
+ OperationType.Update => NullIfEmpty(options.PostUpdateCommand),
+ OperationType.Uninstall => NullIfEmpty(options.PostUninstallCommand),
+ _ => null,
+ };
+
+ private static string? NullIfEmpty(string? value) =>
+ string.IsNullOrWhiteSpace(value) ? null : value;
+}
diff --git a/src/UniGetUI.PackageEngine.AgentBroker/UniGetUI.PackageEngine.AgentBroker.csproj b/src/UniGetUI.PackageEngine.AgentBroker/UniGetUI.PackageEngine.AgentBroker.csproj
new file mode 100644
index 0000000000..cf2535eecb
--- /dev/null
+++ b/src/UniGetUI.PackageEngine.AgentBroker/UniGetUI.PackageEngine.AgentBroker.csproj
@@ -0,0 +1,25 @@
+
+
+
+ $(SharedTargetFrameworks)
+ UniGetUI.PackageEngine.AgentBroker
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs b/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs
index 56741694d2..e4d290a8f2 100644
--- a/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs
+++ b/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs
@@ -4,6 +4,10 @@
using UniGetUI.Core.SettingsEngine;
using UniGetUI.Core.Tools;
using UniGetUI.Interface.Enums;
+using UniGetUI.PackageEngine.AgentBroker;
+// Aliased to avoid clashing with UniGetUI.PackageEngine.Enums.OperationStatus.
+using BrokerClient = Devolutions.UniGetUI.Broker.Client.BrokerClient;
+using BrokerOperationStatus = Devolutions.UniGetUI.Broker.Client.OperationStatus;
using UniGetUI.PackageEngine.Classes.Packages.Classes;
using UniGetUI.PackageEngine.Enums;
using UniGetUI.PackageEngine.Interfaces;
@@ -148,6 +152,105 @@ protected sealed override void PrepareProcessStartInfo()
);
}
+ ///
+ /// Override to intercept operations and route through the Devolutions Agent broker
+ /// when the UseAgentBroker setting is enabled and the manager supports it (WinGet only for now).
+ /// Falls back to process-based execution otherwise.
+ ///
+ protected override async Task PerformOperation()
+ {
+ if (!ShouldUseAgentBroker())
+ {
+ return await base.PerformOperation();
+ }
+
+ return await PerformBrokerOperation();
+ }
+
+ ///
+ /// Determines whether this operation should be routed through the agent broker.
+ ///
+ private bool ShouldUseAgentBroker()
+ {
+ // NOTE: Change this condition to enable agent broker by default when ready.
+ // Currently opt-in via settings.
+ bool settingEnabled = Settings.Get(Settings.K.UseAgentBroker);
+ bool isWinGet = IsWinGetManager(Package.Manager);
+ Logger.Info($"[AgentBroker] ShouldUseAgentBroker check: setting={settingEnabled}, isWinGet={isWinGet}, manager={Package.Manager.Name}");
+
+ if (!settingEnabled)
+ {
+ return false;
+ }
+
+ // Only WinGet is supported in this iteration.
+ if (!isWinGet)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Perform the package operation through the Devolutions Agent broker.
+ /// Sends the request over named pipe and interprets the response.
+ ///
+ private async Task PerformBrokerOperation()
+ {
+ Line("Routing operation through Devolutions Agent broker...", LineType.Information);
+
+ using var client = new BrokerClient();
+
+ // Check broker availability.
+ if (!await client.IsAvailableAsync())
+ {
+ Line("Agent broker is not available, falling back to local execution.", LineType.Information);
+ Logger.Warn("[AgentBroker] Broker not available, falling back to process execution");
+ return await base.PerformOperation();
+ }
+
+ // Build the broker request.
+ var request = BrokerRequestBuilder.Build(Package, Options, Role);
+
+ Line($"Sending request to broker: {request.RequestId}", LineType.VerboseDetails);
+ Line($" Package: {request.Package.Id} ({request.Operation})", LineType.VerboseDetails);
+ Line($" Manager: {request.Manager.Name}", LineType.VerboseDetails);
+ Line($" User: {request.Broker.EffectiveUser}", LineType.VerboseDetails);
+
+ // Send to broker and poll until completion.
+ var status = await client.ExecuteAndWaitAsync(request);
+
+ if (status is null)
+ {
+ Line("No response from broker — the operation could not be submitted.", LineType.Error);
+ Logger.Error("[AgentBroker] ExecuteAndWaitAsync returned null");
+ Metadata.FailureTitle = CoreTools.Translate("Broker communication error");
+ Metadata.FailureMessage = CoreTools.Translate("The agent broker did not respond. Ensure Devolutions Agent is running.");
+ return OperationVeredict.Failure;
+ }
+
+ // Log status details.
+ Line($"Broker status: {status.Status}, exitCode={status.ExitCode}", LineType.Information);
+ if (!string.IsNullOrWhiteSpace(status.Note))
+ {
+ Line($" Note: {status.Note}", LineType.Information);
+ }
+
+ if (status.Status == BrokerOperationStatus.Completed && status.ExitCode == 0)
+ {
+ Line("Operation completed successfully via agent broker.", LineType.Information);
+ return OperationVeredict.Success;
+ }
+
+ // Operation failed — surface a user-visible error.
+ string reason = status.Note ?? $"Exit code: {status.ExitCode}";
+ Line($"Operation failed via broker: {reason}", LineType.Error);
+ Metadata.FailureTitle = CoreTools.Translate("Operation denied or failed via broker");
+ Metadata.FailureMessage = reason;
+ return OperationVeredict.Failure;
+ }
+
protected sealed override Task GetProcessVeredict(
int ReturnCode,
List Output
diff --git a/src/UniGetUI.PackageEngine.Operations/UniGetUI.PackageEngine.Operations.csproj b/src/UniGetUI.PackageEngine.Operations/UniGetUI.PackageEngine.Operations.csproj
index 6b664b6de2..0c8c3cbec3 100644
--- a/src/UniGetUI.PackageEngine.Operations/UniGetUI.PackageEngine.Operations.csproj
+++ b/src/UniGetUI.PackageEngine.Operations/UniGetUI.PackageEngine.Operations.csproj
@@ -9,6 +9,7 @@
+
diff --git a/src/UniGetUI.Windows.slnx b/src/UniGetUI.Windows.slnx
index d3ecc29b0e..1633590b49 100644
--- a/src/UniGetUI.Windows.slnx
+++ b/src/UniGetUI.Windows.slnx
@@ -118,6 +118,10 @@
+
+
+
+