diff --git a/sidecar/src/Aryx.AgentHost/Services/AgentIdentityResolver.cs b/sidecar/src/Aryx.AgentHost/Services/AgentIdentityResolver.cs index bb2f4b7..2d9277b 100644 --- a/sidecar/src/Aryx.AgentHost/Services/AgentIdentityResolver.cs +++ b/sidecar/src/Aryx.AgentHost/Services/AgentIdentityResolver.cs @@ -350,7 +350,12 @@ internal static class AgentIdentityResolver continue; } - WorkflowDefinitionDto subWorkflow = node.ResolveSubWorkflowDefinition(workflowLibrary); + WorkflowDefinitionDto? subWorkflow = node.TryResolveSubWorkflowDefinition(workflowLibrary); + if (subWorkflow is null) + { + continue; + } + CollectAgentSubworkflowContexts( subWorkflow, workflowLibrary, diff --git a/sidecar/src/Aryx.AgentHost/Services/WorkflowDefinitionExtensions.cs b/sidecar/src/Aryx.AgentHost/Services/WorkflowDefinitionExtensions.cs index ca732b3..4cb2d0c 100644 --- a/sidecar/src/Aryx.AgentHost/Services/WorkflowDefinitionExtensions.cs +++ b/sidecar/src/Aryx.AgentHost/Services/WorkflowDefinitionExtensions.cs @@ -66,6 +66,15 @@ internal static class WorkflowDefinitionExtensions public static WorkflowDefinitionDto ResolveSubWorkflowDefinition( this WorkflowNodeDto node, IReadOnlyDictionary? workflowLibrary) + { + return node.TryResolveSubWorkflowDefinition(workflowLibrary) + ?? throw new InvalidOperationException( + $"Sub-workflow node \"{node.Id}\" references unknown workflow \"{node.Config.WorkflowId}\"."); + } + + public static WorkflowDefinitionDto? TryResolveSubWorkflowDefinition( + this WorkflowNodeDto node, + IReadOnlyDictionary? workflowLibrary) { ArgumentNullException.ThrowIfNull(node); @@ -81,8 +90,7 @@ internal static class WorkflowDefinitionExtensions return workflow; } - throw new InvalidOperationException( - $"Sub-workflow node \"{node.Id}\" references unknown workflow \"{node.Config.WorkflowId}\"."); + return null; } public static bool IsOrchestrationMode(this WorkflowDefinitionDto workflow, string mode) @@ -190,8 +198,11 @@ internal static class WorkflowDefinitionExtensions continue; } - WorkflowDefinitionDto subWorkflow = node.ResolveSubWorkflowDefinition(workflowLibrary); - CollectAgentNodes(subWorkflow, workflowLibrary, agentNodes, visitedWorkflowIds, visitedAnonymousWorkflows); + WorkflowDefinitionDto? subWorkflow = node.TryResolveSubWorkflowDefinition(workflowLibrary); + if (subWorkflow is not null) + { + CollectAgentNodes(subWorkflow, workflowLibrary, agentNodes, visitedWorkflowIds, visitedAnonymousWorkflows); + } } } @@ -227,7 +238,12 @@ internal static class WorkflowDefinitionExtensions return node; } - WorkflowDefinitionDto subWorkflow = node.ResolveSubWorkflowDefinition(workflowLibrary); + WorkflowDefinitionDto? subWorkflow = node.TryResolveSubWorkflowDefinition(workflowLibrary); + if (subWorkflow is null) + { + continue; + } + WorkflowNodeDto? match = FindSubWorkflowNode( subWorkflow, nodeId, diff --git a/sidecar/tests/Aryx.AgentHost.Tests/AgentIdentityResolverTests.cs b/sidecar/tests/Aryx.AgentHost.Tests/AgentIdentityResolverTests.cs index ef40495..cf8ef13 100644 --- a/sidecar/tests/Aryx.AgentHost.Tests/AgentIdentityResolverTests.cs +++ b/sidecar/tests/Aryx.AgentHost.Tests/AgentIdentityResolverTests.cs @@ -174,6 +174,23 @@ public sealed class AgentIdentityResolverTests Assert.Equal("Inner Review", subworkflow.SubworkflowName); } + [Fact] + public void BuildAgentSubworkflowIndex_SkipsUnresolvableSubWorkflowReferences() + { + WorkflowDefinitionDto workflow = CreateWorkflow( + "parent-workflow", + [ + CreateAgent("agent-top-level", "Top Level"), + CreateSubworkflow("subworkflow-missing", "Missing Pipeline", workflowId: "nonexistent-workflow"), + ], + orchestrationMode: "concurrent"); + + IReadOnlyDictionary index = + AgentIdentityResolver.BuildAgentSubworkflowIndex(workflow); + + Assert.Empty(index); + } + private static WorkflowDefinitionDto CreateWorkflow( IReadOnlyList nodes, string orchestrationMode = "concurrent")