namespace MeetingAssistant.Notifications; public sealed record AgentApprovalPrompt(string Title, string Message, TimeSpan Timeout); public interface IAgentApprovalPromptService { Task ConfirmAsync(AgentApprovalPrompt prompt, CancellationToken cancellationToken); } public sealed class NoopAgentApprovalPromptService : IAgentApprovalPromptService { public Task ConfirmAsync(AgentApprovalPrompt prompt, CancellationToken cancellationToken) => Task.FromResult(false); } public sealed class AgentApprovalService { private readonly IAgentApprovalPromptService prompts; private readonly ILogger logger; private readonly TimeSpan timeout; public AgentApprovalService(IAgentApprovalPromptService prompts, ILogger logger) : this(prompts, logger, TimeSpan.FromMinutes(10)) { } internal AgentApprovalService(IAgentApprovalPromptService prompts, ILogger logger, TimeSpan timeout) { this.prompts = prompts; this.logger = logger; this.timeout = timeout; } public async Task ConfirmAsync(string title, string message, CancellationToken cancellationToken) { using var deadline = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); deadline.CancelAfter(timeout); try { var accepted = await prompts.ConfirmAsync(new AgentApprovalPrompt(title, message, timeout), deadline.Token) .WaitAsync(deadline.Token); cancellationToken.ThrowIfCancellationRequested(); return accepted && !deadline.IsCancellationRequested; } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { return false; } catch (Exception exception) when (exception is not OperationCanceledException) { logger.LogWarning(exception, "Could not obtain user approval for {Title}", title); return false; } finally { await deadline.CancelAsync(); } } }