mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-28 13:47:12 +02:00
feat: show sub-workflow agents and lifecycle in the Activity panel
Deep agent resolution in the sidecar now walks sub-workflow nodes so nested agents carry subworkflowNodeId and subworkflowName on activity events. New subworkflow-started / subworkflow-completed activity types let the frontend track sub-workflow lifecycle. The Activity panel groups nested agents under collapsible sub-workflow cards with status badges, accent-colored left borders, and smooth expand/collapse transitions. Cards auto-expand when a sub-workflow starts running. Workflows without sub-workflow nodes render identically to before. Extracted AgentRow, SubWorkflowGroup, and shared accent constants to a new components/activity/ feature directory. Added resolveWorkflowAgentHierarchy and buildGroupedActivityRows for hierarchical activity grouping with dynamic fallback for unresolved sub-workflow agents. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -59,6 +59,40 @@ public sealed class CopilotTurnExecutionStateTests
|
||||
Assert.Equal("agent-1", observedAgent.AgentId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObserveSessionEvent_AssistantMessageDelta_ForNestedAgent_IncludesSubworkflowContext()
|
||||
{
|
||||
RunTurnCommandDto command = CreateCommandWithReferencedSubworkflow();
|
||||
CopilotTurnExecutionState state = new(command);
|
||||
WorkflowDefinitionDto nestedWorkflow = Assert.Single(command.WorkflowLibrary!);
|
||||
WorkflowNodeDto nestedAgent = Assert.Single(nestedWorkflow.GetAgentNodes());
|
||||
|
||||
state.ObserveSessionEvent(
|
||||
nestedAgent,
|
||||
SessionEvent.FromJson(
|
||||
"""
|
||||
{
|
||||
"type": "assistant.message_delta",
|
||||
"data": {
|
||||
"messageId": "msg-nested-1",
|
||||
"deltaContent": "Reviewing"
|
||||
},
|
||||
"id": "7ef95d90-7ee7-45e2-ac38-cf749caf4f69",
|
||||
"timestamp": "2026-03-27T00:00:00Z"
|
||||
}
|
||||
"""));
|
||||
|
||||
AgentActivityEventDto activity = Assert.Single(state.DrainPendingEvents().OfType<AgentActivityEventDto>());
|
||||
Assert.Equal("thinking", activity.ActivityType);
|
||||
Assert.Equal("agent-reviewer", activity.AgentId);
|
||||
Assert.Equal("Reviewer", activity.AgentName);
|
||||
Assert.Equal("subworkflow-review", activity.SubworkflowNodeId);
|
||||
Assert.Equal("Review Lane", activity.SubworkflowName);
|
||||
Assert.True(state.ActiveAgent.HasValue);
|
||||
Assert.Equal("subworkflow-review", state.ActiveAgent.Value.Subworkflow?.SubworkflowNodeId);
|
||||
Assert.Equal("Review Lane", state.ActiveAgent.Value.Subworkflow?.SubworkflowName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObserveSessionEvent_ToolExecutionStart_TracksToolNameByCallIdAndQueuesToolActivity()
|
||||
{
|
||||
@@ -746,5 +780,79 @@ public sealed class CopilotTurnExecutionStateTests
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private static RunTurnCommandDto CreateCommandWithReferencedSubworkflow()
|
||||
{
|
||||
WorkflowDefinitionDto nestedWorkflow = CreateWorkflow(
|
||||
"nested-review-workflow",
|
||||
[
|
||||
CreateAgent("agent-reviewer", "Reviewer"),
|
||||
]);
|
||||
|
||||
return new RunTurnCommandDto
|
||||
{
|
||||
RequestId = "turn-1",
|
||||
SessionId = "session-1",
|
||||
WorkflowLibrary = [nestedWorkflow],
|
||||
Workflow = CreateWorkflow(
|
||||
"workflow-parent",
|
||||
[
|
||||
CreateSubworkflow("subworkflow-review", "Review Lane", workflowId: nestedWorkflow.Id),
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
private static WorkflowDefinitionDto CreateWorkflow(string id, IReadOnlyList<WorkflowNodeDto> nodes)
|
||||
{
|
||||
return new WorkflowDefinitionDto
|
||||
{
|
||||
Id = id,
|
||||
Name = "Execution State Workflow",
|
||||
Graph = new WorkflowGraphDto
|
||||
{
|
||||
Nodes = [.. nodes],
|
||||
},
|
||||
Settings = new WorkflowSettingsDto
|
||||
{
|
||||
OrchestrationMode = "single",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private static WorkflowNodeDto CreateAgent(string id, string name)
|
||||
{
|
||||
return new WorkflowNodeDto
|
||||
{
|
||||
Id = id,
|
||||
Kind = "agent",
|
||||
Label = name,
|
||||
Config = new WorkflowNodeConfigDto
|
||||
{
|
||||
Kind = "agent",
|
||||
Id = id,
|
||||
Name = name,
|
||||
Model = "gpt-5.4",
|
||||
Instructions = "Help with the request.",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private static WorkflowNodeDto CreateSubworkflow(
|
||||
string id,
|
||||
string label,
|
||||
string? workflowId = null)
|
||||
{
|
||||
return new WorkflowNodeDto
|
||||
{
|
||||
Id = id,
|
||||
Kind = "sub-workflow",
|
||||
Label = label,
|
||||
Config = new WorkflowNodeConfigDto
|
||||
{
|
||||
Kind = "sub-workflow",
|
||||
WorkflowId = workflowId,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user