From 4ef105a10c4a448dba10a454e000c2a12c89ad4a Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Mon, 9 Jun 2025 14:29:09 +0300 Subject: [PATCH 01/22] Fix unhandled exception logging (#59) --- .../DropInDirectoryMonitorHostedService.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/modules/dropins/Elsa.DropIns/HostedServices/DropInDirectoryMonitorHostedService.cs b/src/modules/dropins/Elsa.DropIns/HostedServices/DropInDirectoryMonitorHostedService.cs index dda2fec5..61c91404 100644 --- a/src/modules/dropins/Elsa.DropIns/HostedServices/DropInDirectoryMonitorHostedService.cs +++ b/src/modules/dropins/Elsa.DropIns/HostedServices/DropInDirectoryMonitorHostedService.cs @@ -67,7 +67,14 @@ private async void OnChanged(object sender, FileSystemEventArgs e) if (task == null) return; - await task; + try + { + await task; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing change for path {Path}", e.FullPath); + } } private async void OnDeleted(object sender, FileSystemEventArgs e) @@ -77,7 +84,14 @@ private async void OnDeleted(object sender, FileSystemEventArgs e) if (task == null) return; - await task; + try + { + await task; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing delete for path {Path}", e.FullPath); + } } private async Task LoadDropInAssemblyAsync(string fullPath) @@ -140,4 +154,4 @@ public override void Dispose() base.Dispose(); } -} \ No newline at end of file +} From c20c07de3f22e94952c7a91fc7aa174247d6391e Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 12 Jun 2025 21:47:46 +0200 Subject: [PATCH 02/22] Updates base version to 3.7.0 Increments the base version number in the workflow configuration. This ensures that the build process uses the correct version when creating packages. --- .github/workflows/packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 8d8bdeb4..02d7fcf2 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -12,7 +12,7 @@ on: release: types: [ prereleased, published ] env: - base_version: '3.6.0' + base_version: '3.7.0' feedz_feed_source: 'https://f.feedz.io/elsa-workflows/elsa-3/nuget/index.json' nuget_feed_source: 'https://api.nuget.org/v3/index.json' From 22ceac8a84b8c05bb9d6596201170d834fa627df Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 12 Jun 2025 23:12:43 +0200 Subject: [PATCH 03/22] Update ElsaVersion to 3.7.0-preview.3055 in Directory.Build.props --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 5516602b..dde009bb 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -41,7 +41,7 @@ - 3.6.0-preview.3036 + 3.7.0-preview.3055 3.6.0-preview.1029 From dd65fd27263871e63c0d601af0f53b9e1ec4fc00 Mon Sep 17 00:00:00 2001 From: "francisco.puga" Date: Thu, 3 Jul 2025 11:19:09 +0200 Subject: [PATCH 04/22] IKernelFactory --- .../agents/Elsa.Agents.Core/Contracts/IkernelFactory.cs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/modules/agents/Elsa.Agents.Core/Contracts/IkernelFactory.cs diff --git a/src/modules/agents/Elsa.Agents.Core/Contracts/IkernelFactory.cs b/src/modules/agents/Elsa.Agents.Core/Contracts/IkernelFactory.cs new file mode 100644 index 00000000..75a3a0cc --- /dev/null +++ b/src/modules/agents/Elsa.Agents.Core/Contracts/IkernelFactory.cs @@ -0,0 +1,8 @@ +using Microsoft.SemanticKernel; +namespace Elsa.Agents.Contracts; + +public interface IKernelFactory +{ + Kernel CreateKernel(KernelConfig kernelConfig, AgentConfig agentConfig); + Kernel CreateKernel(KernelConfig kernelConfig, string agentName); +} \ No newline at end of file From 4928844bac08fd58e8a70f27fd69fcf7e1db74b7 Mon Sep 17 00:00:00 2001 From: "francisco.puga" Date: Thu, 3 Jul 2025 11:21:28 +0200 Subject: [PATCH 05/22] IKernelFactory --- src/modules/agents/Elsa.Agents.Core/Contracts/IkernelFactory.cs | 2 +- src/modules/agents/Elsa.Agents.Core/Services/KernelFactory.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/agents/Elsa.Agents.Core/Contracts/IkernelFactory.cs b/src/modules/agents/Elsa.Agents.Core/Contracts/IkernelFactory.cs index 75a3a0cc..7b14d61d 100644 --- a/src/modules/agents/Elsa.Agents.Core/Contracts/IkernelFactory.cs +++ b/src/modules/agents/Elsa.Agents.Core/Contracts/IkernelFactory.cs @@ -1,5 +1,5 @@ using Microsoft.SemanticKernel; -namespace Elsa.Agents.Contracts; +namespace Elsa.Agents; public interface IKernelFactory { diff --git a/src/modules/agents/Elsa.Agents.Core/Services/KernelFactory.cs b/src/modules/agents/Elsa.Agents.Core/Services/KernelFactory.cs index bdc738f4..9b8dfaf8 100644 --- a/src/modules/agents/Elsa.Agents.Core/Services/KernelFactory.cs +++ b/src/modules/agents/Elsa.Agents.Core/Services/KernelFactory.cs @@ -7,7 +7,7 @@ namespace Elsa.Agents; -public class KernelFactory(IPluginDiscoverer pluginDiscoverer, IServiceDiscoverer serviceDiscoverer, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, ILogger logger) +public class KernelFactory(IPluginDiscoverer pluginDiscoverer, IServiceDiscoverer serviceDiscoverer, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, ILogger logger) : IKernelFactory { public Kernel CreateKernel(KernelConfig kernelConfig, string agentName) { From df209360f7917a077d9aeef1681de17e49818c34 Mon Sep 17 00:00:00 2001 From: "francisco.puga" Date: Thu, 3 Jul 2025 15:45:21 +0200 Subject: [PATCH 06/22] Implementing SK function that supports external tools --- Directory.Packages.props | 5 ++- .../Activities/AgentActivity.cs | 6 ++- .../Elsa.Agents.Activities.csproj | 3 +- .../Elsa.Agents.Core/Elsa.Agents.Core.csproj | 2 + .../Extensions/AgentConfigExtensions.cs | 1 + .../Extensions/FunctionResultExtensions.cs | 28 ++++++++----- .../Models/InvokeAgentResult.cs | 10 ++--- .../Elsa.Agents.Core/Services/AgentInvoker.cs | 40 ++++++++++++++++--- 8 files changed, 67 insertions(+), 28 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 4b31b218..98f0cab3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,7 @@ false - + @@ -38,6 +38,7 @@ + @@ -45,7 +46,7 @@ - + diff --git a/src/modules/agents/Elsa.Agents.Activities/Activities/AgentActivity.cs b/src/modules/agents/Elsa.Agents.Activities/Activities/AgentActivity.cs index 89f097ae..9dce26cd 100644 --- a/src/modules/agents/Elsa.Agents.Activities/Activities/AgentActivity.cs +++ b/src/modules/agents/Elsa.Agents.Activities/Activities/AgentActivity.cs @@ -47,7 +47,11 @@ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context var agentInvoker = context.GetRequiredService(); var result = await agentInvoker.InvokeAgentAsync(AgentName, functionInput, context.CancellationToken); - var json = result.FunctionResult.GetValue(); + var json = result.ChatMessageContent.Content?.Trim(); + + if (string.IsNullOrWhiteSpace(json)) + throw new InvalidOperationException("The message content is empty or null."); + var outputType = context.ActivityDescriptor.Outputs.Single().Type; // If the target type is object, we want the JSON to be deserialized into an ExpandoObject for dynamic field access. diff --git a/src/modules/agents/Elsa.Agents.Activities/Elsa.Agents.Activities.csproj b/src/modules/agents/Elsa.Agents.Activities/Elsa.Agents.Activities.csproj index 0879dc1d..209abc37 100644 --- a/src/modules/agents/Elsa.Agents.Activities/Elsa.Agents.Activities.csproj +++ b/src/modules/agents/Elsa.Agents.Activities/Elsa.Agents.Activities.csproj @@ -3,7 +3,8 @@ Provides Agent activities elsa extension module agents semantic kernel llm ai - + 3.4.0 + diff --git a/src/modules/agents/Elsa.Agents.Core/Elsa.Agents.Core.csproj b/src/modules/agents/Elsa.Agents.Core/Elsa.Agents.Core.csproj index 28b86c8a..944b0143 100644 --- a/src/modules/agents/Elsa.Agents.Core/Elsa.Agents.Core.csproj +++ b/src/modules/agents/Elsa.Agents.Core/Elsa.Agents.Core.csproj @@ -4,6 +4,7 @@ Provides an agentic framework using Semantic Kernel elsa extension module agents semantic kernel llm ai Elsa.Agents + 4.444.0 @@ -12,6 +13,7 @@ + diff --git a/src/modules/agents/Elsa.Agents.Core/Extensions/AgentConfigExtensions.cs b/src/modules/agents/Elsa.Agents.Core/Extensions/AgentConfigExtensions.cs index 2a4cdc6f..5afbbab3 100644 --- a/src/modules/agents/Elsa.Agents.Core/Extensions/AgentConfigExtensions.cs +++ b/src/modules/agents/Elsa.Agents.Core/Extensions/AgentConfigExtensions.cs @@ -20,6 +20,7 @@ public static OpenAIPromptExecutionSettings ToOpenAIPromptExecutionSettings(this ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions, ResponseFormat = agentConfig.ExecutionSettings.ResponseFormat, ChatSystemPrompt = agentConfig.PromptTemplate, + ServiceId = "default" }; } diff --git a/src/modules/agents/Elsa.Agents.Core/Extensions/FunctionResultExtensions.cs b/src/modules/agents/Elsa.Agents.Core/Extensions/FunctionResultExtensions.cs index ba02314f..90b66954 100644 --- a/src/modules/agents/Elsa.Agents.Core/Extensions/FunctionResultExtensions.cs +++ b/src/modules/agents/Elsa.Agents.Core/Extensions/FunctionResultExtensions.cs @@ -8,18 +8,24 @@ public static class FunctionResultExtensions public static async Task AsJsonElementAsync(this Task resultTask) { var result = await resultTask; - return result.FunctionResult.AsJsonElement(); + return result.ChatMessageContent.AsJsonElement(); } - - public static async Task AsJsonElementAsync(this Task resultTask) - { - var result = await resultTask; - return result.AsJsonElement(); - } - - public static JsonElement AsJsonElement(this FunctionResult result) + + public static JsonElement AsJsonElement(this ChatMessageContent result) { - var response = result.GetValue()!; - return JsonSerializer.Deserialize(response); + var content = result.Content?.Trim(); + + if (string.IsNullOrWhiteSpace(content)) + throw new InvalidOperationException("The message content is empty."); + + try + { + return JsonSerializer.Deserialize(content!); + } + catch (JsonException ex) + { + throw new InvalidOperationException($"Error deserializing the message content as JSON:\n{content}", ex); + } + } } \ No newline at end of file diff --git a/src/modules/agents/Elsa.Agents.Core/Models/InvokeAgentResult.cs b/src/modules/agents/Elsa.Agents.Core/Models/InvokeAgentResult.cs index ee2732b9..d7b032ba 100644 --- a/src/modules/agents/Elsa.Agents.Core/Models/InvokeAgentResult.cs +++ b/src/modules/agents/Elsa.Agents.Core/Models/InvokeAgentResult.cs @@ -3,12 +3,8 @@ namespace Elsa.Agents; -public record InvokeAgentResult(AgentConfig Function, FunctionResult FunctionResult) +public record InvokeAgentResult(AgentConfig Function, ChatMessageContent ChatMessageContent) { - public object? ParseResult() - { - var targetType = Type.GetType(Function.OutputVariable.Type) ?? typeof(JsonElement); - var json = FunctionResult.GetValue(); - return JsonSerializer.Deserialize(json, targetType); - } + + } \ No newline at end of file diff --git a/src/modules/agents/Elsa.Agents.Core/Services/AgentInvoker.cs b/src/modules/agents/Elsa.Agents.Core/Services/AgentInvoker.cs index a40458c5..8dc30959 100644 --- a/src/modules/agents/Elsa.Agents.Core/Services/AgentInvoker.cs +++ b/src/modules/agents/Elsa.Agents.Core/Services/AgentInvoker.cs @@ -1,12 +1,14 @@ using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; +using Microsoft.SemanticKernel.PromptTemplates.Handlebars; #pragma warning disable SKEXP0010 #pragma warning disable SKEXP0001 namespace Elsa.Agents; -public class AgentInvoker(KernelFactory kernelFactory, IKernelConfigProvider kernelConfigProvider) +public class AgentInvoker(IKernelFactory kernelFactory, IKernelConfigProvider kernelConfigProvider) { public async Task InvokeAgentAsync(string agentName, IDictionary input, CancellationToken cancellationToken = default) { @@ -21,9 +23,9 @@ public async Task InvokeAgentAsync(string agentName, IDiction MaxTokens = executionSettings.MaxTokens, PresencePenalty = executionSettings.PresencePenalty, FrequencyPenalty = executionSettings.FrequencyPenalty, - ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions, ResponseFormat = executionSettings.ResponseFormat, ChatSystemPrompt = agentConfig.PromptTemplate, + FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }; var promptExecutionSettingsDictionary = new Dictionary @@ -46,10 +48,36 @@ public async Task InvokeAgentAsync(string agentName, IDiction AllowDangerouslySetContent = true }).ToList() }; - - var kernelFunction = kernel.CreateFunctionFromPrompt(promptTemplateConfig); + + var templateFactory = new HandlebarsPromptTemplateFactory(); + + var manolo = new PromptTemplateConfig + { + Template = agentConfig.PromptTemplate, + TemplateFormat = "handlebars", + Name = agentConfig.FunctionName + }; + + var promptTemplate = templateFactory.Create(manolo); + var kernelArguments = new KernelArguments(input); - var result = await kernelFunction.InvokeAsync(kernel, kernelArguments, cancellationToken: cancellationToken); - return new(agentConfig, result); + string renderedPrompt = await promptTemplate.RenderAsync(kernel, kernelArguments); + + ChatHistory chatHistory = []; + chatHistory.AddUserMessage(renderedPrompt); + + IChatCompletionService chatCompletion = kernel.GetRequiredService(); + + OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new() + { + FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() + }; + + var response = await chatCompletion.GetChatMessageContentAsync( + chatHistory, + executionSettings: openAIPromptExecutionSettings, + kernel: kernel); + + return new(agentConfig, response); } } \ No newline at end of file From 164dfe75996c472f1fbc0baa80f3e8c02deb7a84 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 3 Jul 2025 16:00:24 +0200 Subject: [PATCH 07/22] Add 'develop/*' branch to push configuration in packages.yml --- .github/workflows/packages.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 8d8bdeb4..e1ec508a 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -4,6 +4,7 @@ on: push: branches: - 'main' + - 'develop/*' - 'bug/*' - 'issue/*' - 'patch/*' From 93a6edfc3d81ee133fe5513d3f973de2c47a9961 Mon Sep 17 00:00:00 2001 From: "francisco.puga" Date: Thu, 3 Jul 2025 16:12:24 +0200 Subject: [PATCH 08/22] Restoring .csproj --- .../agents/Elsa.Agents.Activities/Elsa.Agents.Activities.csproj | 1 - src/modules/agents/Elsa.Agents.Core/Elsa.Agents.Core.csproj | 1 - 2 files changed, 2 deletions(-) diff --git a/src/modules/agents/Elsa.Agents.Activities/Elsa.Agents.Activities.csproj b/src/modules/agents/Elsa.Agents.Activities/Elsa.Agents.Activities.csproj index 209abc37..77e8b563 100644 --- a/src/modules/agents/Elsa.Agents.Activities/Elsa.Agents.Activities.csproj +++ b/src/modules/agents/Elsa.Agents.Activities/Elsa.Agents.Activities.csproj @@ -3,7 +3,6 @@ Provides Agent activities elsa extension module agents semantic kernel llm ai - 3.4.0 diff --git a/src/modules/agents/Elsa.Agents.Core/Elsa.Agents.Core.csproj b/src/modules/agents/Elsa.Agents.Core/Elsa.Agents.Core.csproj index 944b0143..bdb4be65 100644 --- a/src/modules/agents/Elsa.Agents.Core/Elsa.Agents.Core.csproj +++ b/src/modules/agents/Elsa.Agents.Core/Elsa.Agents.Core.csproj @@ -4,7 +4,6 @@ Provides an agentic framework using Semantic Kernel elsa extension module agents semantic kernel llm ai Elsa.Agents - 4.444.0 From efaf412a599715d90e16753635059f005d721955 Mon Sep 17 00:00:00 2001 From: "francisco.puga" Date: Thu, 3 Jul 2025 16:18:00 +0200 Subject: [PATCH 09/22] Restoring Directory.Packages.props --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 98f0cab3..7bd645a5 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,7 @@ false - + From a98cb7be6b9d6e8edb38834f019af9c0e659d13b Mon Sep 17 00:00:00 2001 From: "francisco.puga" Date: Thu, 3 Jul 2025 16:21:42 +0200 Subject: [PATCH 10/22] Replace var name --- src/modules/agents/Elsa.Agents.Core/Services/AgentInvoker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/agents/Elsa.Agents.Core/Services/AgentInvoker.cs b/src/modules/agents/Elsa.Agents.Core/Services/AgentInvoker.cs index 8dc30959..907caae4 100644 --- a/src/modules/agents/Elsa.Agents.Core/Services/AgentInvoker.cs +++ b/src/modules/agents/Elsa.Agents.Core/Services/AgentInvoker.cs @@ -51,14 +51,14 @@ public async Task InvokeAgentAsync(string agentName, IDiction var templateFactory = new HandlebarsPromptTemplateFactory(); - var manolo = new PromptTemplateConfig + var promptConfig = new PromptTemplateConfig { Template = agentConfig.PromptTemplate, TemplateFormat = "handlebars", Name = agentConfig.FunctionName }; - var promptTemplate = templateFactory.Create(manolo); + var promptTemplate = templateFactory.Create(promptConfig); var kernelArguments = new KernelArguments(input); string renderedPrompt = await promptTemplate.RenderAsync(kernel, kernelArguments); From 637ac2ee08351ab61514a2e179401e047eb754e7 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 3 Jul 2025 17:56:29 +0200 Subject: [PATCH 11/22] Disable project references in Directory.Build.props and update package versions to 3.6.0-preview.3107 and 3.6.0-preview.1137 --- Directory.Build.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index ffe061f8..e147bfc0 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - true + false Elsa Workflows Community @@ -41,8 +41,8 @@ - 3.6.0-preview.3056 - 3.6.0-preview.1087 + 3.6.0-preview.3107 + 3.6.0-preview.1137 9.0.6 From 166ac5ed767824b8f3dc36452252d6205612358e Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 3 Jul 2025 18:06:59 +0200 Subject: [PATCH 12/22] Update package version prefix logic to include 'develop/' branches --- .github/workflows/packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index e1ec508a..1dfed6aa 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -31,7 +31,7 @@ jobs: PACKAGE_PREFIX=$(echo $BRANCH_NAME | rev | cut -d/ -f1 | rev | tr '_' '-') # If the branch name is main, use the preview version. Otherwise, use the branch name as the version prefix. - if [[ "${BRANCH_NAME}" == "main" || "${BRANCH_NAME}" =~ ^rc/ ]]; then + if [[ "${BRANCH_NAME}" == "main" || "${BRANCH_NAME}" =~ ^develop/ ]]; then PACKAGE_PREFIX="preview" fi From b25ff87656056b93cbe97b1e520ff7b501bac6ae Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 4 Jul 2025 20:14:30 +0200 Subject: [PATCH 13/22] Update AlterationHandlerContext.cs --- .../Contexts/AlterationHandlerContext.cs | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/modules/alterations/Elsa.Alterations.Core/Contexts/AlterationHandlerContext.cs b/src/modules/alterations/Elsa.Alterations.Core/Contexts/AlterationHandlerContext.cs index 3f9a7aa9..f98fc601 100644 --- a/src/modules/alterations/Elsa.Alterations.Core/Contexts/AlterationHandlerContext.cs +++ b/src/modules/alterations/Elsa.Alterations.Core/Contexts/AlterationHandlerContext.cs @@ -99,13 +99,26 @@ public void Succeed(Func commitAction) CommitAction = commitAction; } + /// + /// Marks the alteration as succeeded. + /// + public void Succeed(Action commitAction) + { + Succeed(); + CommitAction = () => + { + commitAction(); + return Task.CompletedTask; + }; + } + /// /// Marks the alteration as succeeded. /// public void Succeed(string message) { HasSucceeded = true; - Log($"Alteration {Alteration.GetType().Name} succeeded", message, LogLevel.Information); + Log($"Alteration {Alteration.GetType().Name} succeeded", message); } /// @@ -116,6 +129,19 @@ public void Succeed(string message, Func commitAction) Succeed(message); CommitAction = commitAction; } + + /// + /// Marks the alteration as succeeded. + /// + public void Succeed(string message, Action commitAction) + { + Succeed(message); + CommitAction = () => + { + commitAction(); + return Task.CompletedTask; + }; + } /// /// Marks the alteration as failed. @@ -126,4 +152,4 @@ public void Fail(string? message = null) HasFailed = true; Log($"Alteration {Alteration.GetType().Name} failed", message ?? $"{Alteration.GetType().Name} failed", LogLevel.Error); } -} \ No newline at end of file +} From 34426b4e713598826197d928bf11648eacb59be2 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 4 Jul 2025 20:15:28 +0200 Subject: [PATCH 14/22] Update BackgroundAlterationJobDispatcher.cs --- .../BackgroundAlterationJobDispatcher.cs | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/modules/alterations/Elsa.Alterations/Services/BackgroundAlterationJobDispatcher.cs b/src/modules/alterations/Elsa.Alterations/Services/BackgroundAlterationJobDispatcher.cs index 65e3b9ac..e9180a56 100644 --- a/src/modules/alterations/Elsa.Alterations/Services/BackgroundAlterationJobDispatcher.cs +++ b/src/modules/alterations/Elsa.Alterations/Services/BackgroundAlterationJobDispatcher.cs @@ -1,34 +1,25 @@ using Elsa.Alterations.Core.Contracts; using Elsa.Mediator.Contracts; +using Microsoft.Extensions.DependencyInjection; namespace Elsa.Alterations.Services; /// /// Dispatches an alteration job for execution using an in-memory channel. /// -public class BackgroundAlterationJobDispatcher : IAlterationJobDispatcher +public class BackgroundAlterationJobDispatcher(IJobQueue jobQueue, IServiceScopeFactory scopeFactory) : IAlterationJobDispatcher { - private readonly IJobQueue _jobQueue; - private readonly IAlterationJobRunner _alterationJobRunner; - - /// - /// Initializes a new instance of the class. - /// - public BackgroundAlterationJobDispatcher(IJobQueue jobQueue, IAlterationJobRunner alterationJobRunner) - { - _jobQueue = jobQueue; - _alterationJobRunner = alterationJobRunner; - } - /// public ValueTask DispatchAsync(string jobId, CancellationToken cancellationToken = default) { - _jobQueue.Enqueue(ct => ExecuteJobAsync(jobId, ct)); + jobQueue.Enqueue(ct => ExecuteJobAsync(jobId, ct)); return default; } private async Task ExecuteJobAsync(string alterationJobId, CancellationToken cancellationToken) { - await _alterationJobRunner.RunAsync(alterationJobId, cancellationToken); + using var scope = scopeFactory.CreateScope(); + var alterationJobRunner = scope.ServiceProvider.GetRequiredService(); + await alterationJobRunner.RunAsync(alterationJobId, cancellationToken); } -} \ No newline at end of file +} From 12b0c03a4e439543bd6825f8900230d557af5088 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 4 Jul 2025 20:18:36 +0200 Subject: [PATCH 15/22] Update WorkflowExecutionLogStore.cs --- .../Modules/Runtime/WorkflowExecutionLogStore.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/modules/persistence/Elsa.Persistence.EFCore/Modules/Runtime/WorkflowExecutionLogStore.cs b/src/modules/persistence/Elsa.Persistence.EFCore/Modules/Runtime/WorkflowExecutionLogStore.cs index a8840b89..fd7067d0 100644 --- a/src/modules/persistence/Elsa.Persistence.EFCore/Modules/Runtime/WorkflowExecutionLogStore.cs +++ b/src/modules/persistence/Elsa.Persistence.EFCore/Modules/Runtime/WorkflowExecutionLogStore.cs @@ -52,8 +52,8 @@ public async Task SaveManyAsync(IEnumerable records, /// public async Task> FindManyAsync(WorkflowExecutionLogRecordFilter filter, PageArgs pageArgs, CancellationToken cancellationToken = default) { - var count = await store.QueryAsync(queryable => Filter(queryable, filter).OrderBy(x => x.Timestamp), cancellationToken).LongCount(); - var results = await store.QueryAsync(queryable => Filter(queryable, filter).Paginate(pageArgs), OnLoadAsync, cancellationToken).ToList(); + var count = await store.QueryAsync(queryable => Filter(queryable, filter), cancellationToken).LongCount(); + var results = await store.QueryAsync(queryable => Filter(queryable, filter).OrderBy(x => x.Timestamp).Paginate(pageArgs), OnLoadAsync, cancellationToken).ToList(); return new(results, count); } @@ -61,7 +61,7 @@ public async Task> FindManyAsync(WorkflowExecut public async Task> FindManyAsync(WorkflowExecutionLogRecordFilter filter, PageArgs pageArgs, WorkflowExecutionLogRecordOrder order, CancellationToken cancellationToken = default) { var count = await store.QueryAsync(queryable => Filter(queryable, filter), cancellationToken).LongCount(); - var results = await store.QueryAsync(queryable => Filter(queryable, filter).Paginate(pageArgs).OrderBy(order), OnLoadAsync, cancellationToken).ToList(); + var results = await store.QueryAsync(queryable => Filter(queryable, filter).OrderBy(order).Paginate(pageArgs), OnLoadAsync, cancellationToken).ToList(); return new(results, count); } @@ -71,10 +71,11 @@ public async Task DeleteManyAsync(WorkflowExecutionLogRecordFilter filter, return await store.DeleteWhereAsync(queryable => Filter(queryable, filter), cancellationToken); } - private async ValueTask OnSaveAsync(RuntimeElsaDbContext dbContext, WorkflowExecutionLogRecord entity, CancellationToken cancellationToken) + private ValueTask OnSaveAsync(RuntimeElsaDbContext dbContext, WorkflowExecutionLogRecord entity, CancellationToken cancellationToken) { entity = entity.SanitizeLogMessage(); dbContext.Entry(entity).Property("SerializedPayload").CurrentValue = ShouldSerializePayload(entity) ? safeSerializer.Serialize(entity.Payload) : null; + return ValueTask.CompletedTask; } private async ValueTask OnLoadAsync(RuntimeElsaDbContext dbContext, WorkflowExecutionLogRecord? entity, CancellationToken cancellationToken) @@ -102,4 +103,4 @@ private bool ShouldSerializePayload(WorkflowExecutionLogRecord source) } private static IQueryable Filter(IQueryable queryable, WorkflowExecutionLogRecordFilter filter) => filter.Apply(queryable); -} \ No newline at end of file +} From a9871f4ad4ee9fefbfcbdd6ed2e8618a97dfbf5c Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 4 Jul 2025 20:20:37 +0200 Subject: [PATCH 16/22] Update AlterationHandlerContext.cs --- .../Contexts/AlterationHandlerContext.cs | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/modules/alterations/Elsa.Alterations.Core/Contexts/AlterationHandlerContext.cs b/src/modules/alterations/Elsa.Alterations.Core/Contexts/AlterationHandlerContext.cs index 3f9a7aa9..f98fc601 100644 --- a/src/modules/alterations/Elsa.Alterations.Core/Contexts/AlterationHandlerContext.cs +++ b/src/modules/alterations/Elsa.Alterations.Core/Contexts/AlterationHandlerContext.cs @@ -99,13 +99,26 @@ public void Succeed(Func commitAction) CommitAction = commitAction; } + /// + /// Marks the alteration as succeeded. + /// + public void Succeed(Action commitAction) + { + Succeed(); + CommitAction = () => + { + commitAction(); + return Task.CompletedTask; + }; + } + /// /// Marks the alteration as succeeded. /// public void Succeed(string message) { HasSucceeded = true; - Log($"Alteration {Alteration.GetType().Name} succeeded", message, LogLevel.Information); + Log($"Alteration {Alteration.GetType().Name} succeeded", message); } /// @@ -116,6 +129,19 @@ public void Succeed(string message, Func commitAction) Succeed(message); CommitAction = commitAction; } + + /// + /// Marks the alteration as succeeded. + /// + public void Succeed(string message, Action commitAction) + { + Succeed(message); + CommitAction = () => + { + commitAction(); + return Task.CompletedTask; + }; + } /// /// Marks the alteration as failed. @@ -126,4 +152,4 @@ public void Fail(string? message = null) HasFailed = true; Log($"Alteration {Alteration.GetType().Name} failed", message ?? $"{Alteration.GetType().Name} failed", LogLevel.Error); } -} \ No newline at end of file +} From f5e1e3c5c282dce33b30e48e2129a0c7487e24a9 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 4 Jul 2025 20:21:18 +0200 Subject: [PATCH 17/22] Update BackgroundAlterationJobDispatcher.cs --- .../BackgroundAlterationJobDispatcher.cs | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/modules/alterations/Elsa.Alterations/Services/BackgroundAlterationJobDispatcher.cs b/src/modules/alterations/Elsa.Alterations/Services/BackgroundAlterationJobDispatcher.cs index 65e3b9ac..e9180a56 100644 --- a/src/modules/alterations/Elsa.Alterations/Services/BackgroundAlterationJobDispatcher.cs +++ b/src/modules/alterations/Elsa.Alterations/Services/BackgroundAlterationJobDispatcher.cs @@ -1,34 +1,25 @@ using Elsa.Alterations.Core.Contracts; using Elsa.Mediator.Contracts; +using Microsoft.Extensions.DependencyInjection; namespace Elsa.Alterations.Services; /// /// Dispatches an alteration job for execution using an in-memory channel. /// -public class BackgroundAlterationJobDispatcher : IAlterationJobDispatcher +public class BackgroundAlterationJobDispatcher(IJobQueue jobQueue, IServiceScopeFactory scopeFactory) : IAlterationJobDispatcher { - private readonly IJobQueue _jobQueue; - private readonly IAlterationJobRunner _alterationJobRunner; - - /// - /// Initializes a new instance of the class. - /// - public BackgroundAlterationJobDispatcher(IJobQueue jobQueue, IAlterationJobRunner alterationJobRunner) - { - _jobQueue = jobQueue; - _alterationJobRunner = alterationJobRunner; - } - /// public ValueTask DispatchAsync(string jobId, CancellationToken cancellationToken = default) { - _jobQueue.Enqueue(ct => ExecuteJobAsync(jobId, ct)); + jobQueue.Enqueue(ct => ExecuteJobAsync(jobId, ct)); return default; } private async Task ExecuteJobAsync(string alterationJobId, CancellationToken cancellationToken) { - await _alterationJobRunner.RunAsync(alterationJobId, cancellationToken); + using var scope = scopeFactory.CreateScope(); + var alterationJobRunner = scope.ServiceProvider.GetRequiredService(); + await alterationJobRunner.RunAsync(alterationJobId, cancellationToken); } -} \ No newline at end of file +} From 279ff59000d5df000759e925514dde696b57b9a4 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 4 Jul 2025 20:22:24 +0200 Subject: [PATCH 18/22] Update WorkflowExecutionLogStore.cs --- .../Modules/Runtime/WorkflowExecutionLogStore.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/persistence/Elsa.Persistence.EFCore/Modules/Runtime/WorkflowExecutionLogStore.cs b/src/modules/persistence/Elsa.Persistence.EFCore/Modules/Runtime/WorkflowExecutionLogStore.cs index ee914012..7885f6bc 100644 --- a/src/modules/persistence/Elsa.Persistence.EFCore/Modules/Runtime/WorkflowExecutionLogStore.cs +++ b/src/modules/persistence/Elsa.Persistence.EFCore/Modules/Runtime/WorkflowExecutionLogStore.cs @@ -103,4 +103,5 @@ private bool ShouldSerializePayload(WorkflowExecutionLogRecord source) } private static IQueryable Filter(IQueryable queryable, WorkflowExecutionLogRecordFilter filter) => filter.Apply(queryable); -} \ No newline at end of file +} +} From 87ffdf1f87b922198fe950203521b705575db667 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 4 Jul 2025 20:28:13 +0200 Subject: [PATCH 19/22] Update MongoDbStore.cs --- .../Elsa.Persistence.MongoDb/Common/MongoDbStore.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/modules/persistence/Elsa.Persistence.MongoDb/Common/MongoDbStore.cs b/src/modules/persistence/Elsa.Persistence.MongoDb/Common/MongoDbStore.cs index 6e6fd31b..af4371a0 100644 --- a/src/modules/persistence/Elsa.Persistence.MongoDb/Common/MongoDbStore.cs +++ b/src/modules/persistence/Elsa.Persistence.MongoDb/Common/MongoDbStore.cs @@ -134,10 +134,11 @@ public async Task SaveManyAsync(IEnumerable documents, string primary await collection.BulkWriteAsync(writes, cancellationToken: cancellationToken); } - public async Task UpdatePartialAsync( + public async Task UpdatePartialAsync( string id, IDictionary updatedFields, string primaryKey = nameof(Entity.Id), + bool throwIfNotFound = true, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(id)) @@ -154,7 +155,14 @@ public async Task UpdatePartialAsync( var updateResult = await collection.UpdateOneAsync(filter, updateDefinition, cancellationToken: cancellationToken); if (updateResult.MatchedCount == 0) + { + if (!throwIfNotFound) + return false; + throw new InvalidOperationException($"No document found with ID '{id}'."); + } + + return updateResult.ModifiedCount > 0; } /// @@ -455,4 +463,4 @@ private void ApplyTenantId(IEnumerable documents) tenantDocument.TenantId = tenantId.EmptyToNull(); } } -} \ No newline at end of file +} From c790230a99939b9a92d6788d021b8080a45c5c57 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 4 Jul 2025 20:30:06 +0200 Subject: [PATCH 20/22] Update MongoDbFeature.cs --- .../Features/MongoDbFeature.cs | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/src/modules/persistence/Elsa.Persistence.MongoDb/Features/MongoDbFeature.cs b/src/modules/persistence/Elsa.Persistence.MongoDb/Features/MongoDbFeature.cs index faa45040..517f6582 100644 --- a/src/modules/persistence/Elsa.Persistence.MongoDb/Features/MongoDbFeature.cs +++ b/src/modules/persistence/Elsa.Persistence.MongoDb/Features/MongoDbFeature.cs @@ -39,6 +39,11 @@ public class MongoDbFeature(IModule module) : FeatureBase(module) /// public Func CollectionNamingStrategy { get; set; } = sp => sp.GetRequiredService(); + public override void ConfigureHostedServices() + { + Module.ConfigureHostedService(-10); + } + /// public override void Apply() { @@ -47,24 +52,13 @@ public override void Apply() var mongoUrl = new MongoUrl(ConnectionString); Services.AddSingleton(sp => CreateMongoClient(sp, mongoUrl)); Services.AddScoped(sp => CreateDatabase(sp, mongoUrl)); - + Services.TryAddScoped(); Services.AddScoped(CollectionNamingStrategy); - RegisterSerializers(); RegisterClassMaps(); } - private static void RegisterSerializers() - { - TryRegisterSerializerOrSkipWhenExist(typeof(object), new PolymorphicSerializer()); - TryRegisterSerializerOrSkipWhenExist(typeof(Type), new TypeSerializer()); - TryRegisterSerializerOrSkipWhenExist(typeof(Variable), new VariableSerializer()); - TryRegisterSerializerOrSkipWhenExist(typeof(Version), new VersionSerializer()); - TryRegisterSerializerOrSkipWhenExist(typeof(JsonElement), new JsonElementSerializer()); - TryRegisterSerializerOrSkipWhenExist(typeof(JsonNode), new JsonNodeBsonConverter()); - } - private static void RegisterClassMaps() { BsonClassMap.TryRegisterClassMap(cm => @@ -78,19 +72,14 @@ private static void RegisterClassMaps() map.SetIgnoreExtraElements(true); // Needed for missing ID property map.MapProperty(x => x.Key); // Needed for non-setter property }); + + BsonClassMap.TryRegisterClassMap(map => + { + map.AutoMap(); + map.SetIgnoreExtraElements(true); + }); } - private static void TryRegisterSerializerOrSkipWhenExist(Type type, IBsonSerializer serializer) - { - try - { - BsonSerializer.TryRegisterSerializer(type, serializer); - } - catch (BsonSerializationException ex) - { - } - } - private static IMongoClient CreateMongoClient(IServiceProvider sp, MongoUrl mongoUrl) { var options = sp.GetRequiredService>().Value; @@ -99,7 +88,7 @@ private static IMongoClient CreateMongoClient(IServiceProvider sp, MongoUrl mong // TODO: Uncomment once https://github.com/jbogard/MongoDB.Driver.Core.Extensions.DiagnosticSources/pull/41 is merged and deployed. //settings.ClusterConfigurator = cb => cb.Subscribe(new DiagnosticsActivityEventSubscriber()); - + settings.ApplicationName = GetApplicationName(settings); settings.WriteConcern = options.WriteConcern; settings.ReadConcern = options.ReadConcern; @@ -121,4 +110,4 @@ private static string GetApplicationName(MongoClientSettings settings) { return string.IsNullOrWhiteSpace(settings.ApplicationName) ? "elsa_workflows" : settings.ApplicationName; } -} \ No newline at end of file +} From 5a8d4adc3f1e9b170a6704a5025cd62082ccbb5d Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 4 Jul 2025 20:32:25 +0200 Subject: [PATCH 21/22] Update ExpressionHelpers.cs --- .../Helpers/ExpressionHelpers.cs | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/modules/persistence/Elsa.Persistence.MongoDb/Helpers/ExpressionHelpers.cs b/src/modules/persistence/Elsa.Persistence.MongoDb/Helpers/ExpressionHelpers.cs index 42a9e255..8c7c977d 100644 --- a/src/modules/persistence/Elsa.Persistence.MongoDb/Helpers/ExpressionHelpers.cs +++ b/src/modules/persistence/Elsa.Persistence.MongoDb/Helpers/ExpressionHelpers.cs @@ -5,10 +5,10 @@ namespace Elsa.Persistence.MongoDb.Helpers; -internal class ExpressionHelpers +internal static class ExpressionHelpers { public static readonly Expression> WorkflowDefinitionSummary = - workflowDefinition => new WorkflowDefinitionSummary + workflowDefinition => new() { Id = workflowDefinition.Id, DefinitionId = workflowDefinition.DefinitionId, @@ -18,11 +18,14 @@ internal class ExpressionHelpers IsLatest = workflowDefinition.IsLatest, IsPublished = workflowDefinition.IsPublished, MaterializerName = workflowDefinition.MaterializerName, - CreatedAt = workflowDefinition.CreatedAt + CreatedAt = workflowDefinition.CreatedAt, + IsReadonly = workflowDefinition.IsReadonly, + ProviderName = workflowDefinition.ProviderName, + ToolVersion = workflowDefinition.ToolVersion }; public static readonly Expression> WorkflowInstanceSummary = - workflowInstance => new WorkflowInstanceSummary + workflowInstance => new() { Id = workflowInstance.Id, DefinitionId = workflowInstance.DefinitionId, @@ -34,16 +37,17 @@ internal class ExpressionHelpers Name = workflowInstance.Name, CreatedAt = workflowInstance.CreatedAt, UpdatedAt = workflowInstance.UpdatedAt, - FinishedAt = workflowInstance.FinishedAt + FinishedAt = workflowInstance.FinishedAt, + IncidentCount = workflowInstance.IncidentCount, }; - public static readonly Expression> WorkflowInstanceId = workflowInstance => new WorkflowInstanceId + public static readonly Expression> WorkflowInstanceId = workflowInstance => new() { Id = workflowInstance.Id }; public static readonly Expression> ActivityExecutionRecordSummary = - workflowInstance => new ActivityExecutionRecordSummary + workflowInstance => new() { Id = workflowInstance.Id, Status = workflowInstance.Status, @@ -53,6 +57,11 @@ internal class ExpressionHelpers ActivityTypeVersion = workflowInstance.ActivityTypeVersion, ActivityName = workflowInstance.ActivityName, StartedAt = workflowInstance.StartedAt, - HasBookmarks = workflowInstance.HasBookmarks + HasBookmarks = workflowInstance.HasBookmarks, + CompletedAt = workflowInstance.CompletedAt, + AggregateFaultCount = workflowInstance.AggregateFaultCount, + Metadata = workflowInstance.Metadata, + WorkflowInstanceId = workflowInstance.WorkflowInstanceId, + TenantId = workflowInstance.TenantId, }; -} \ No newline at end of file +} From d777342c9c47af0335a5bfe798b7daaad2f34d24 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Fri, 4 Jul 2025 20:35:10 +0200 Subject: [PATCH 22/22] Update WorkflowInstanceStore.cs --- .../Modules/Management/WorkflowInstanceStore.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/modules/persistence/Elsa.Persistence.MongoDb/Modules/Management/WorkflowInstanceStore.cs b/src/modules/persistence/Elsa.Persistence.MongoDb/Modules/Management/WorkflowInstanceStore.cs index 4d7043cb..57f70ef6 100644 --- a/src/modules/persistence/Elsa.Persistence.MongoDb/Modules/Management/WorkflowInstanceStore.cs +++ b/src/modules/persistence/Elsa.Persistence.MongoDb/Modules/Management/WorkflowInstanceStore.cs @@ -18,7 +18,7 @@ namespace Elsa.Persistence.MongoDb.Modules.Management; /// A MongoDb implementation of . /// [UsedImplicitly] -public class MongoWorkflowInstanceStore(MongoDbStore mongoDbStore) : IWorkflowInstanceStore +public class MongoWorkflowInstanceStore(MongoDbStore mongoDbStore, ILogger logger) : IWorkflowInstanceStore { /// public async ValueTask FindAsync(WorkflowInstanceFilter filter, CancellationToken cancellationToken = default) @@ -130,7 +130,10 @@ public async Task UpdateUpdatedTimestampAsync(string workflowInstanceId, DateTim [nameof(WorkflowInstance.UpdatedAt)] = value }; - await mongoDbStore.UpdatePartialAsync(workflowInstanceId, props, cancellationToken: cancellationToken); + var updated = await mongoDbStore.UpdatePartialAsync(workflowInstanceId, props, throwIfNotFound: false, cancellationToken: cancellationToken); + + if (!updated) + logger.LogDebug("Failed to update the 'UpdatedAt' timestamp for workflow instance with ID '{WorkflowInstanceId}'. This means this workflow does not yet exist in the DB.", workflowInstanceId); } /// @@ -175,4 +178,4 @@ private IQueryable OrderAndPaginate(IQueryable