diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/AgentProviderExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/AgentProviderExtensions.cs index 714ce4747d..2dcbe8e87a 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/AgentProviderExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/AgentProviderExtensions.cs @@ -22,9 +22,13 @@ internal static class AgentProviderExtensions { IAsyncEnumerable agentUpdates = agentProvider.InvokeAgentAsync(agentName, null, conversationId, inputMessages, inputArguments, cancellationToken); - // Enable "autoSend" behavior if this is the workflow conversation. + // Determine whether the target conversation is the workflow conversation + // (used below to decide whether to mirror messages into the workflow conversation + // when an agent runs against a different conversation). The caller's autoSend + // value is honored as-is — when the workflow.yaml specifies autoSend: false the + // raw agent output must not be streamed to the caller, even when the agent is + // running on the workflow conversation. bool isWorkflowConversation = context.IsWorkflowConversation(conversationId, out string? workflowConversationId); - autoSend |= isWorkflowConversation; // Process the agent response updates. List updates = []; diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/IWorkflowContextExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/IWorkflowContextExtensions.cs index 1b92235eee..2f57b4494a 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/IWorkflowContextExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/IWorkflowContextExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.AI.Workflows.Declarative.Interpreter; @@ -21,7 +22,7 @@ internal static class IWorkflowContextExtensions context.AddEventAsync(new DeclarativeActionCompletedEvent(action), cancellationToken); public static FormulaValue ReadState(this IWorkflowContext context, PropertyPath variablePath) => - context.ReadState(Throw.IfNull(variablePath.VariableName), Throw.IfNull(variablePath.NamespaceAlias)); + context.ReadState(Throw.IfNull(GetVariableName(variablePath)), GetNamespaceAlias(variablePath)); public static FormulaValue ReadState(this IWorkflowContext context, string key, string? scopeName = null) => DeclarativeContext(context).State.Get(key, scopeName); @@ -33,10 +34,28 @@ internal static class IWorkflowContextExtensions context.SendMessageAsync(new ActionExecutorResult(id, result), targetId: null, cancellationToken); public static ValueTask QueueStateResetAsync(this IWorkflowContext context, PropertyPath variablePath, CancellationToken cancellationToken = default) => - context.QueueStateUpdateAsync(Throw.IfNull(variablePath.VariableName), UnassignedValue.Instance, Throw.IfNull(variablePath.NamespaceAlias), cancellationToken); + context.QueueStateUpdateAsync(Throw.IfNull(GetVariableName(variablePath)), UnassignedValue.Instance, GetNamespaceAlias(variablePath), cancellationToken); public static ValueTask QueueStateUpdateAsync(this IWorkflowContext context, PropertyPath variablePath, TValue? value, CancellationToken cancellationToken = default) => - context.QueueStateUpdateAsync(Throw.IfNull(variablePath.VariableName), value, Throw.IfNull(variablePath.NamespaceAlias), cancellationToken); + context.QueueStateUpdateAsync(Throw.IfNull(GetVariableName(variablePath)), value, GetNamespaceAlias(variablePath), cancellationToken); + + // Workaround for ObjectModel 2026.2.4.1 regression: PropertyPath built from a dotted + // reference such as "Local.Triage" returns null for both NamespaceAlias and VariableName + // even when SegmentCount==2 and IsValid==true. Reconstruct from Segments() in that case. + private static string? GetVariableName(PropertyPath variablePath) => + variablePath.VariableName ?? (variablePath.SegmentCount >= 2 ? variablePath.Segments().ElementAtOrDefault(1).PropertyName : variablePath.SegmentCount == 1 ? variablePath.Segments().ElementAtOrDefault(0).PropertyName : null); + + // Workaround for ObjectModel 2026.2.4.1 regression: in addition to the parser bug above, + // the framework's user-facing scope alias "Local" is no longer recognized by + // VariableScopeNames.IsValidName / GetNamespaceFromName (they only accept the canonical + // names "Topic", "Global", "System", "Env"). Translate the "Local" alias back to its + // canonical "Topic" form so downstream IsManagedScope checks succeed. + private static string? GetNamespaceAlias(PropertyPath variablePath) + { + string? alias = variablePath.NamespaceAlias + ?? (variablePath.SegmentCount >= 2 ? variablePath.Segments().ElementAtOrDefault(0).PropertyName : null); + return string.Equals(alias, "Local", StringComparison.Ordinal) ? VariableScopeNames.Topic : alias; + } public static async ValueTask QueueEnvironmentUpdateAsync(this IWorkflowContext context, string key, TValue? value, CancellationToken cancellationToken = default) {