#if WINDOWS using System.Collections.Concurrent; using CommunityToolkit.WinUI.Notifications; using MeetingAssistant.Recording; namespace MeetingAssistant.Notifications; public sealed class WindowsAgentApprovalPromptService(ILogger logger) : IAgentApprovalPromptService, IDisposable { private const string Source = "summary-agent-approval"; private readonly ConcurrentDictionary> pending = new(); private readonly object gate = new(); private bool registered; private bool disposed; public async Task ConfirmAsync(AgentApprovalPrompt prompt, CancellationToken cancellationToken) { if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17763)) return false; cancellationToken.ThrowIfCancellationRequested(); var id = Guid.NewGuid().ToString("N"); var response = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); try { lock (gate) { if (disposed) return false; if (!registered) { ToastNotificationManagerCompat.OnActivated += OnActivated; registered = true; } pending[id] = response; new ToastContentBuilder() .AddArgument("source", Source).AddArgument("requestId", id) .SetToastScenario(ToastScenario.Reminder) .SetToastDuration(ToastDuration.Long) .AddText(prompt.Title) .AddText(prompt.Message) .AddButton(new ToastButton().SetContent("Yes") .AddArgument("source", Source).AddArgument("requestId", id) .AddArgument("response", "yes").SetBackgroundActivation()) .AddButton(new ToastButton().SetContent("No") .AddArgument("source", Source).AddArgument("requestId", id) .AddArgument("response", "no").SetBackgroundActivation()) .Show(toast => { toast.Tag = id; toast.Group = Source; toast.ExpirationTime = DateTimeOffset.Now.Add(prompt.Timeout); }); } return await response.Task.WaitAsync(cancellationToken); } finally { pending.TryRemove(id, out _); try { ToastNotificationManagerCompat.History.Remove(id, Source); } catch (Exception exception) { logger.LogDebug(exception, "Could not remove approval notification {RequestId}", id); } } } private void OnActivated(ToastNotificationActivatedEventArgsCompat args) { var arguments = NotificationActivationArguments.Parse(args.Argument); if (arguments.GetValueOrDefault("source") != Source || !arguments.TryGetValue("requestId", out var id) || !pending.TryRemove(id, out var response)) return; response.TrySetResult(arguments.GetValueOrDefault("response") == "yes"); } public void Dispose() { lock (gate) { disposed = true; if (registered) ToastNotificationManagerCompat.OnActivated -= OnActivated; registered = false; foreach (var response in pending.Values) response.TrySetResult(false); pending.Clear(); } } } #endif