using MeetingAssistant.Notifications; using MeetingAssistant.Workflow; using System.Threading.Channels; namespace MeetingAssistant.Summary; public sealed class WorkflowChangeRequestService( AgentApprovalService approvals, IWorkflowRulesEditorWindowService editor, ILogger logger) : BackgroundService { private readonly Channel requests = Channel.CreateUnbounded( new UnboundedChannelOptions { SingleReader = true }); public string Request(string intention, string detailedPrompt, string meetingNotePath) { if (string.IsNullOrWhiteSpace(intention) || intention.Length > 100 || intention.Contains('\r') || intention.Contains('\n')) return "Refused: intention must be a nonempty single line of at most 100 characters."; if (string.IsNullOrWhiteSpace(detailedPrompt)) return "Refused: supply a detailed change prompt, preferably a scenario or specification."; var request = new RequestDetails(Guid.NewGuid().ToString("N"), intention, detailedPrompt, meetingNotePath); return requests.Writer.TryWrite(request) ? $"Requested: {request.Id}. User approval is pending." : "Refused: application is stopping."; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var pending = new List(); try { await foreach (var request in requests.Reader.ReadAllAsync(stoppingToken)) { pending.RemoveAll(task => task.IsCompleted); pending.Add(HandleAsync(request, stoppingToken)); } } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { } finally { await Task.WhenAll(pending); } } private async Task HandleAsync(RequestDetails request, CancellationToken cancellationToken) { try { if (await approvals.ConfirmAsync($"Workflow change requested: {request.Intention}", $"The summarizer for {Path.GetFileName(request.MeetingNotePath)} requests a workflow change. Open the settings agent to apply it?", cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); editor.ShowWithPrompt(request.DetailedPrompt); } } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { } catch (Exception exception) { logger.LogWarning(exception, "Workflow request {RequestId} could not be handled", request.Id); } } public override Task StopAsync(CancellationToken cancellationToken) { requests.Writer.TryComplete(); return base.StopAsync(cancellationToken); } private sealed record RequestDetails(string Id, string Intention, string DetailedPrompt, string MeetingNotePath); }