feat(workflows): Phase 1 backend — WorkflowDefinition model, persistence, IPC, sidecar execution

Introduce WorkflowDefinition as a first-class entity alongside PatternDefinition.

Shared domain:
- src/shared/domain/workflow.ts: new WorkflowDefinition, WorkflowGraph,
  WorkflowNode/Edge/Config types, normalization, TS-side validation, and
  buildWorkflowExecutionPattern (synthetic pattern bridge for session plumbing)
- workspace.ts: add workflows[] and selectedWorkflowId
- session.ts: add optional workflowId to SessionRecord
- runTimeline.ts: add optional workflowId/workflowName to run records
- sessionLibrary.ts: workflow-aware session search

IPC / main process:
- contracts/channels.ts, contracts/ipc.ts: workflow CRUD channels
- preload/index.ts: expose saveWorkflow, deleteWorkflow, createWorkflowSession
- ipc/registerIpcHandlers.ts: register workflow handlers
- persistence/workspaceRepository.ts: load/save/normalize workflows
- AryxAppService.ts: saveWorkflow, deleteWorkflow, createWorkflowSession,
  resolveSessionExecutionDefinition, workflow-aware turn/run plumbing

Sidecar protocol:
- contracts/sidecar.ts: ValidateWorkflowCommand, WorkflowValidationEvent,
  optional workflow on RunTurnCommand
- sidecarProcess.ts: validate-workflow command dispatch and event handling
- Contracts/ProtocolModels.cs: workflow DTOs and validate-workflow DTOs
- Services/SidecarProtocolHost.cs: validate-workflow command handler
- Services/CopilotWorkflowRunner.cs: workflow-aware build and checkpoint path
- Services/WorkflowValidator.cs: graph validation (connectivity, start/end,
  fan-out/in arity, path reachability)
- Services/WorkflowRunner.cs: WorkflowDefinitionDto -> Agent Framework Workflow
  builder (direct, fan-out, fan-in edges; agent executor binding)

Project: bump both csproj from net9.0 to net10.0

Tests:
- tests/shared/workflow.test.ts: TS workflow validation and pattern synthesis
- tests/main/appServiceWorkflow.test.ts: saveWorkflow / createWorkflowSession
- tests/Aryx.AgentHost.Tests/SidecarProtocolHostTests.cs: ValidateWorkflowCommand
- Fix two stale workspace fixtures missing workflows field

Validation: tsc clean, bun test 367/367 pass, dotnet test 244/244 pass, bun run build succeeds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-04-05 17:29:03 +02:00
co-authored by Copilot
parent 4c3198a550
commit 19a764d297
25 changed files with 1720 additions and 32 deletions
@@ -12,14 +12,17 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
{
private const string HandoffFunctionPrefix = "handoff_to_";
private readonly PatternValidator _patternValidator;
private readonly WorkflowValidator _workflowValidator;
private readonly WorkflowRunner _workflowRunner = new();
private readonly CopilotApprovalCoordinator _approvalCoordinator = new();
private readonly CopilotUserInputCoordinator _userInputCoordinator = new();
private readonly CopilotMcpOAuthCoordinator _mcpOAuthCoordinator = new();
private readonly CopilotExitPlanModeCoordinator _exitPlanModeCoordinator = new();
public CopilotWorkflowRunner(PatternValidator patternValidator)
public CopilotWorkflowRunner(PatternValidator patternValidator, WorkflowValidator? workflowValidator = null)
{
_patternValidator = patternValidator;
_workflowValidator = workflowValidator ?? new WorkflowValidator();
}
public async Task<IReadOnlyList<ChatMessageDto>> RunTurnAsync(
@@ -32,10 +35,12 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
Func<ExitPlanModeRequestedEventDto, Task> onExitPlanMode,
CancellationToken cancellationToken)
{
PatternValidationIssueDto? validationError = _patternValidator.Validate(command.Pattern).FirstOrDefault();
string? validationError = command.Workflow is null
? _patternValidator.Validate(command.Pattern).FirstOrDefault()?.Message
: _workflowValidator.Validate(command.Workflow).FirstOrDefault()?.Message;
if (validationError is not null)
{
throw new InvalidOperationException(validationError.Message);
throw new InvalidOperationException(validationError);
}
CopilotTurnExecutionState state = new(command);
@@ -79,7 +84,9 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
},
runCancellation.Token);
ConfigureHookLifecycleEventSuppression(state, bundle);
Workflow workflow = bundle.BuildWorkflow(command.Pattern);
Workflow workflow = command.Workflow is null
? bundle.BuildWorkflow(command.Pattern)
: _workflowRunner.BuildWorkflow(command.Workflow, command.Pattern, bundle.Agents);
List<ChatMessage> inputMessages = command.Messages.Select(WorkflowTranscriptProjector.ToChatMessage).ToList();
WorkflowTranscriptProjector.AttachMessageMode(inputMessages, command.MessageMode);
@@ -145,6 +152,11 @@ public sealed class CopilotWorkflowRunner : ITurnWorkflowRunner
internal static bool ShouldEnableWorkflowCheckpointing(RunTurnCommandDto command)
{
ArgumentNullException.ThrowIfNull(command);
if (command.Workflow is not null)
{
return command.Workflow.Settings.Checkpointing.Enabled;
}
return string.Equals(command.Pattern.Mode, "handoff", StringComparison.OrdinalIgnoreCase);
}