mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-28 23:48:39 +02:00
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:
@@ -73,9 +73,10 @@ function createSession(overrides?: Partial<SessionRecord>): SessionRecord {
|
||||
}
|
||||
|
||||
function createWorkspace(overrides?: Partial<WorkspaceState>): WorkspaceState {
|
||||
return {
|
||||
const workspace: WorkspaceState = {
|
||||
projects: [createProject(), createProject({ id: 'project-scratchpad', name: 'Scratchpad', path: 'C:\\scratchpad' })],
|
||||
patterns: [createPattern(), createPattern({ id: 'pattern-single-chat', name: '1-on-1 Copilot Chat', mode: 'single' })],
|
||||
workflows: [],
|
||||
settings: createWorkspaceSettings(),
|
||||
sessions: [
|
||||
createSession(),
|
||||
@@ -112,6 +113,11 @@ function createWorkspace(overrides?: Partial<WorkspaceState>): WorkspaceState {
|
||||
lastUpdatedAt: '2026-03-23T00:10:00.000Z',
|
||||
...overrides,
|
||||
};
|
||||
|
||||
return {
|
||||
...workspace,
|
||||
workflows: overrides?.workflows ?? workspace.workflows,
|
||||
};
|
||||
}
|
||||
|
||||
describe('session library helpers', () => {
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
|
||||
import {
|
||||
buildWorkflowExecutionPattern,
|
||||
validateWorkflowDefinition,
|
||||
type WorkflowDefinition,
|
||||
} from '@shared/domain/workflow';
|
||||
|
||||
const TIMESTAMP = '2026-04-05T00:00:00.000Z';
|
||||
|
||||
function createWorkflow(): WorkflowDefinition {
|
||||
return {
|
||||
id: 'workflow-1',
|
||||
name: 'Agent Workflow',
|
||||
description: 'A simple workflow.',
|
||||
createdAt: TIMESTAMP,
|
||||
updatedAt: TIMESTAMP,
|
||||
graph: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'start',
|
||||
kind: 'start',
|
||||
label: 'Start',
|
||||
position: { x: 0, y: 0 },
|
||||
config: { kind: 'start' },
|
||||
},
|
||||
{
|
||||
id: 'agent-primary',
|
||||
kind: 'agent',
|
||||
label: 'Primary Agent',
|
||||
position: { x: 200, y: 0 },
|
||||
order: 0,
|
||||
config: {
|
||||
kind: 'agent',
|
||||
id: 'agent-primary',
|
||||
name: 'Primary Agent',
|
||||
description: 'Handles the task.',
|
||||
instructions: 'Do the work.',
|
||||
model: 'gpt-5.4',
|
||||
reasoningEffort: 'medium',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'end',
|
||||
kind: 'end',
|
||||
label: 'End',
|
||||
position: { x: 400, y: 0 },
|
||||
config: { kind: 'end' },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{ id: 'edge-start-agent', source: 'start', target: 'agent-primary', kind: 'direct' },
|
||||
{ id: 'edge-agent-end', source: 'agent-primary', target: 'end', kind: 'direct' },
|
||||
],
|
||||
},
|
||||
settings: {
|
||||
checkpointing: { enabled: false },
|
||||
executionMode: 'off-thread',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('workflow validation', () => {
|
||||
test('accepts a simple start-agent-end workflow', () => {
|
||||
expect(validateWorkflowDefinition(createWorkflow())).toEqual([]);
|
||||
});
|
||||
|
||||
test('rejects unsupported node kinds in phase 1 backend', () => {
|
||||
const workflow = createWorkflow();
|
||||
workflow.graph.nodes.splice(1, 0, {
|
||||
id: 'request-port',
|
||||
kind: 'request-port',
|
||||
label: 'Approval',
|
||||
position: { x: 100, y: 0 },
|
||||
config: {
|
||||
kind: 'request-port',
|
||||
portId: 'approval',
|
||||
requestType: 'Question',
|
||||
responseType: 'Answer',
|
||||
},
|
||||
});
|
||||
|
||||
expect(validateWorkflowDefinition(workflow).some((issue) =>
|
||||
issue.message.includes('not executable yet'))).toBe(true);
|
||||
});
|
||||
|
||||
test('builds a synthetic execution pattern from workflow agents', () => {
|
||||
const pattern = buildWorkflowExecutionPattern(createWorkflow());
|
||||
|
||||
expect(pattern.id).toBe('workflow-1');
|
||||
expect(pattern.agents).toHaveLength(1);
|
||||
expect(pattern.agents[0]?.name).toBe('Primary Agent');
|
||||
expect(pattern.graph?.nodes.map((node) => node.kind)).toEqual(['user-input', 'agent', 'user-output']);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user