.NET Workflow - Declarative Workflow Event Fix (#765)

* Checkpoint

* Update workflows/DeepResearch.yaml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Comment

* Fix comment

* Update package version

* Fix nuget haxx

* Checkpoint

* Code complete

* Testing

* Message content workaround

* Add sequential flow

* Checkpoint

* Integration test project

* Checkpoint

* Checkpoint cleanup

* Complete

* Checkpoint

* Fix tests

* Comment cleanup

* Namespace

* Formatting

* Analyzer updates

* Workflow update

* Fixed!

* Update dotnet/tests/Microsoft.Agents.Workflows.Declarative.UnitTests/DeclarativeWorkflowTest.cs

Co-authored-by: Tao Chen <taochen@microsoft.com>

* Fix build error

* Purge "immutable" set and dictionary

* Fix as task

* Collection expression

* Another

* Frozen => Readonly (perf)

* Fix

* Namespace

* Checkpoint

* Add test

* Fix

* Rollback workflow edit

* nitty

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Tao Chen <taochen@microsoft.com>
This commit is contained in:
Chris
2025-09-16 18:57:07 +00:00
committed by GitHub
co-authored by Copilot Tao Chen
parent 6b91d41a73
commit b2d2019874
12 changed files with 100 additions and 34 deletions
@@ -8,7 +8,7 @@ namespace Microsoft.Agents.Workflows.Declarative;
/// <summary>
/// Event that indicates a declarative action has been invoked.
/// </summary>
public sealed class DeclarativeActionInvokeEvent : WorkflowEvent
public sealed class DeclarativeActionInvokedEvent : WorkflowEvent
{
/// <summary>
/// The declarative action id.
@@ -30,7 +30,7 @@ public sealed class DeclarativeActionInvokeEvent : WorkflowEvent
/// </summary>
public string? PriorActionId { get; }
internal DeclarativeActionInvokeEvent(DialogAction action, string? priorActionId) : base(action)
internal DeclarativeActionInvokedEvent(DialogAction action, string? priorActionId) : base(action)
{
this.ActionId = action.GetId();
this.ActionType = action.GetType().Name;
@@ -8,7 +8,7 @@ namespace Microsoft.Agents.Workflows.Declarative;
/// <summary>
/// Event that indicates a declarative action has completed.
/// </summary>
public sealed class DeclarativeActionCompleteEvent : WorkflowEvent
public sealed class DeclarativeActionCompletedEvent : WorkflowEvent
{
/// <summary>
/// The declarative action identifier.
@@ -25,7 +25,7 @@ public sealed class DeclarativeActionCompleteEvent : WorkflowEvent
/// </summary>
public string? ParentActionId { get; }
internal DeclarativeActionCompleteEvent(DialogAction action) : base(action)
internal DeclarativeActionCompletedEvent(DialogAction action) : base(action)
{
this.ActionId = action.GetId();
this.ActionType = action.GetType().Name;
@@ -12,10 +12,10 @@ namespace Microsoft.Agents.Workflows.Declarative.Extensions;
internal static class IWorkflowContextExtensions
{
public static ValueTask RaiseInvocationEventAsync(this IWorkflowContext context, DialogAction action, string? priorEventId = null) =>
context.AddEventAsync(new DeclarativeActionInvokeEvent(action, priorEventId));
context.AddEventAsync(new DeclarativeActionInvokedEvent(action, priorEventId));
public static ValueTask RaiseCompletionEventAsync(this IWorkflowContext context, DialogAction action) =>
context.AddEventAsync(new DeclarativeActionCompleteEvent(action));
context.AddEventAsync(new DeclarativeActionCompletedEvent(action));
public static ValueTask SendResultMessageAsync(this IWorkflowContext context, string id, object? result = null, CancellationToken cancellationToken = default) =>
context.SendMessageAsync(new ExecutorResultMessage(id, result));
@@ -68,7 +68,13 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor
{
if (this._workflowModel.GetDepth(item.Id.Value) > 1)
{
string completionId = this.ContinuationFor(item.Id.Value); // End scope
DelegateAction<ExecutorResultMessage>? action = null;
ConditionGroupExecutor? conditionGroup = this._workflowModel.LocateParent<ConditionGroupExecutor>(parentId);
if (conditionGroup is not null)
{
action = conditionGroup.DoneAsync;
}
string completionId = this.ContinuationFor(item.Id.Value, action); // End scope
this._workflowModel.AddLinkFromPeer(item.Id.Value, completionId); // Connect with final action
this._workflowModel.AddLink(completionId, Steps.Post(parentId)); // Merge with parent scope
}
@@ -175,10 +181,10 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor
ForeachExecutor? loopExecutor = this._workflowModel.LocateParent<ForeachExecutor>(item.GetParentId());
if (loopExecutor is not null)
{
string parentId = GetParentId(item);
this.ContinueWith(new DelegateActionExecutor(item.Id.Value, this._workflowState), parentId);
this._workflowModel.AddLink(item.Id.Value, Steps.Post(loopExecutor.Id));
this.RestartAfter(item.Id.Value, parentId);
DefaultActionExecutor breakLoopExecutor = new(item, this._workflowState);
this.ContinueWith(breakLoopExecutor);
this._workflowModel.AddLink(breakLoopExecutor.Id, Steps.Post(loopExecutor.Id));
this.RestartAfter(breakLoopExecutor.Id, breakLoopExecutor.ParentId);
}
}
@@ -189,10 +195,10 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor
ForeachExecutor? loopExecutor = this._workflowModel.LocateParent<ForeachExecutor>(item.GetParentId());
if (loopExecutor is not null)
{
string parentId = GetParentId(item);
this.ContinueWith(new DelegateActionExecutor(item.Id.Value, this._workflowState), parentId);
this._workflowModel.AddLink(item.Id.Value, ForeachExecutor.Steps.Next(loopExecutor.Id));
this.RestartAfter(item.Id.Value, parentId);
DefaultActionExecutor continueLoopExecutor = new(item, this._workflowState);
this.ContinueWith(continueLoopExecutor);
this._workflowModel.AddLink(continueLoopExecutor.Id, Steps.Post(loopExecutor.Id));
this.RestartAfter(continueLoopExecutor.Id, continueLoopExecutor.ParentId);
}
}
@@ -200,9 +206,9 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor
{
this.Trace(item);
string parentId = GetParentId(item);
this.ContinueWith(new DelegateActionExecutor(item.Id.Value, this._workflowState), parentId);
this.RestartAfter(item.Id.Value, parentId);
DefaultActionExecutor endExecutor = new(item, this._workflowState);
this.ContinueWith(endExecutor);
this.RestartAfter(item.Id.Value, endExecutor.ParentId);
}
protected override void Visit(Question item)
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.Workflows.Declarative.Interpreter;
using Microsoft.Agents.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
namespace Microsoft.Agents.Workflows.Declarative.ObjectModel;
internal sealed class DefaultActionExecutor(DialogAction model, WorkflowFormulaState state) :
DeclarativeActionExecutor(model, state)
{
protected override ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
// No action needed - the edge will be followed automatically
return default;
}
}