using System.Diagnostics; using MeetingAssistant.Workflow; using Microsoft.Extensions.Options; namespace MeetingAssistant.MacOs; public sealed class MacOsWorkflowRulesEditorWindowService : IWorkflowRulesEditorWindowService { private readonly IOptions options; private readonly Func getSessions; public MacOsWorkflowRulesEditorWindowService( IOptions options, Func getSessions) { this.options = options; this.getSessions = getSessions; } public void Show() { if (!OperatingSystem.IsMacOS()) { return; } var url = $"{options.Value.Api.PublicBaseUrl.TrimEnd('/')}/diagnostics/settings-and-logs"; Process.Start(new ProcessStartInfo { FileName = "/usr/bin/open", ArgumentList = { url }, UseShellExecute = false }); } public void ShowWithPrompt(string initialPrompt) { getSessions().OpenWithPrompt(initialPrompt); } } public sealed class MacOsWorkflowRulesEditorSession { private readonly WorkflowRulesEditorChatViewModel viewModel; private readonly CancellationToken applicationStopping; private readonly SemaphoreSlim sendLock = new(1, 1); private MacOsWorkflowRulesEditorChatResponse snapshot = new([], false); public MacOsWorkflowRulesEditorSession( WorkflowRulesEditorChatViewModel viewModel, CancellationToken applicationStopping = default) { this.viewModel = viewModel; this.applicationStopping = applicationStopping; } public Task SendAsync( string? message, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrWhiteSpace(message)) { return Task.FromResult(GetSnapshot()); } // The response connection controls only its wait; accepted agent work belongs to the application. return SendCoreAsync(message.Trim()).WaitAsync(cancellationToken); } public MacOsWorkflowRulesEditorChatResponse GetSnapshot() => Volatile.Read(ref snapshot); private async Task SendCoreAsync(string message) { await sendLock.WaitAsync(applicationStopping); try { applicationStopping.ThrowIfCancellationRequested(); var messages = GetSnapshot().Messages .Append(new MacOsWorkflowRulesEditorMessage("user", message)) .ToArray(); Volatile.Write(ref snapshot, new MacOsWorkflowRulesEditorChatResponse( Array.AsReadOnly(messages), true)); try { // Dispatch the agent away from the caller so opening a window or processing a decision stays responsive. await Task.Run(() => viewModel.SendPromptAsync(message, applicationStopping), applicationStopping); } finally { var completedMessages = viewModel.Messages .Where(item => item.Kind == WorkflowRulesEditorConversationItemKind.Message) .Select(item => new MacOsWorkflowRulesEditorMessage( item.Message!.Role.ToString().ToLowerInvariant(), item.Message.Content)) .ToArray(); Volatile.Write(ref snapshot, new MacOsWorkflowRulesEditorChatResponse( Array.AsReadOnly(completedMessages), false)); } return GetSnapshot(); } finally { sendLock.Release(); } } } public sealed record MacOsWorkflowRulesEditorChatRequest(string? Message); public sealed record MacOsWorkflowRulesEditorChatResponse( IReadOnlyList Messages, bool IsThinking = false); public sealed record MacOsWorkflowRulesEditorMessage(string Role, string Content); public static class MacOsWorkflowRulesEditorPage { public const string Html = """ Meeting Summary Agent

Meeting Summary Agent

"""; }