mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-25 14:08:44 +02:00
Remove the entire patterns domain model, IPC channels, sidecar services, renderer components, and tests. Sessions now bind exclusively to workflows via workflowId. Builtin workflows replace builtin patterns. Backend: - Make AgentNodeConfig standalone (no longer extends PatternAgentDefinition) - Add WorkflowOrchestrationMode and WorkflowExecutionDefinition - Create builtin workflows (single-agent, sequential, concurrent, handoff, group-chat) - Rewrite session model config helpers for workflow-only - Remove pattern IPC channels, handlers, and preload bindings - Merge createSession/createWorkflowSession into single method - Remove sidecar PatternGraphResolver, PatternValidator, pattern DTOs - Add workspace migration for legacy sessions (patternId -> workflowId) Frontend: - Delete PatternEditor, pattern-graph components, patternGraph lib - Delete NewSessionModal (session creation uses workflows directly) - Remove PatternsSection from SettingsPanel - Update App.tsx, ChatPane, ActivityPanel, Sidebar, RunTimeline, AgentConfigFields, InlinePills, sessionActivity to use workflow types - Delete pattern.ts domain module 78 files changed across backend and frontend. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
165 lines
5.7 KiB
TypeScript
165 lines
5.7 KiB
TypeScript
import { describe, expect, mock, test } from 'bun:test';
|
|
|
|
import { buildAvailableModelCatalog } from '@shared/domain/models';
|
|
import { exportWorkflowDefinition } from '@shared/domain/workflowSerialization';
|
|
import type { WorkflowDefinition } from '@shared/domain/workflow';
|
|
import { createWorkspaceSeed, type WorkspaceState } from '@shared/domain/workspace';
|
|
|
|
mock.module('electron', () => {
|
|
const electronMock = {
|
|
app: {
|
|
isPackaged: false,
|
|
getAppPath: () => 'C:\\workspace\\personal\\repositories\\aryx',
|
|
getPath: () => 'C:\\workspace\\personal\\repositories\\aryx\\tests\\fixtures',
|
|
},
|
|
dialog: {
|
|
showOpenDialog: async () => ({ canceled: true, filePaths: [] }),
|
|
},
|
|
shell: {
|
|
openPath: async () => '',
|
|
},
|
|
};
|
|
|
|
return {
|
|
...electronMock,
|
|
default: electronMock,
|
|
};
|
|
});
|
|
|
|
mock.module('keytar', () => ({
|
|
default: {
|
|
getPassword: async () => null,
|
|
setPassword: async () => undefined,
|
|
deletePassword: async () => false,
|
|
},
|
|
}));
|
|
|
|
const { AryxAppService } = await import('@main/AryxAppService');
|
|
|
|
function createService(workspace: WorkspaceState): InstanceType<typeof AryxAppService> {
|
|
const service = new AryxAppService();
|
|
const internals = service as unknown as Record<string, unknown>;
|
|
internals.loadWorkspace = async () => workspace;
|
|
internals.persistAndBroadcast = async (nextWorkspace: WorkspaceState) => nextWorkspace;
|
|
internals.loadAvailableModelCatalog = async () => buildAvailableModelCatalog();
|
|
return service;
|
|
}
|
|
|
|
function createWorkflow(): WorkflowDefinition {
|
|
return {
|
|
id: 'workflow-test',
|
|
name: 'Workflow Test',
|
|
description: 'Simple workflow',
|
|
createdAt: '2026-04-05T00:00:00.000Z',
|
|
updatedAt: '2026-04-05T00:00:00.000Z',
|
|
graph: {
|
|
nodes: [
|
|
{ id: 'start', kind: 'start', label: 'Start', position: { x: 0, y: 0 }, config: { kind: 'start' } },
|
|
{
|
|
id: 'agent-primary',
|
|
kind: 'agent',
|
|
label: 'Primary',
|
|
position: { x: 200, y: 0 },
|
|
order: 0,
|
|
config: {
|
|
kind: 'agent',
|
|
id: 'agent-primary',
|
|
name: 'Primary',
|
|
description: 'Main agent',
|
|
instructions: 'Help the user',
|
|
model: 'gpt-5.4',
|
|
},
|
|
},
|
|
{ id: 'end', kind: 'end', label: 'End', position: { x: 400, y: 0 }, config: { kind: 'end' } },
|
|
],
|
|
edges: [
|
|
{ id: 'e1', source: 'start', target: 'agent-primary', kind: 'direct' },
|
|
{ id: 'e2', source: 'agent-primary', target: 'end', kind: 'direct' },
|
|
],
|
|
},
|
|
settings: {
|
|
checkpointing: { enabled: false },
|
|
executionMode: 'off-thread',
|
|
},
|
|
};
|
|
}
|
|
|
|
describe('AryxAppService workflow operations', () => {
|
|
test('saves workflows into workspace state', async () => {
|
|
const workspace = createWorkspaceSeed();
|
|
const initialWorkflowCount = workspace.workflows.length;
|
|
const service = createService(workspace);
|
|
|
|
const result = await service.saveWorkflow(createWorkflow());
|
|
|
|
expect(result.workflows).toHaveLength(initialWorkflowCount + 1);
|
|
expect(result.workflows.some((workflow) => workflow.id === 'workflow-test')).toBe(true);
|
|
expect(result.selectedWorkflowId).toBe('workflow-test');
|
|
});
|
|
|
|
test('persists a custom workflow template from a saved workflow', async () => {
|
|
const workspace = createWorkspaceSeed();
|
|
workspace.workflows.push(createWorkflow());
|
|
const service = createService(workspace);
|
|
|
|
const result = await service.saveWorkflowTemplate('workflow-test', {
|
|
name: 'Saved Template',
|
|
description: 'From workflow',
|
|
category: 'human-in-loop',
|
|
});
|
|
|
|
const template = result.workflowTemplates.find((candidate) => candidate.source === 'custom');
|
|
expect(template).toBeDefined();
|
|
expect(template?.name).toBe('Saved Template');
|
|
expect(template?.category).toBe('human-in-loop');
|
|
expect(template?.workflow.id).toBe('workflow-test');
|
|
});
|
|
|
|
test('creates workflows from templates and selects the new workflow', async () => {
|
|
const workspace = createWorkspaceSeed();
|
|
const initialWorkflowCount = workspace.workflows.length;
|
|
const service = createService(workspace);
|
|
|
|
const result = await service.createWorkflowFromTemplate('workflow-template-code-review', {
|
|
name: 'Template Copy',
|
|
});
|
|
|
|
expect(result.workflows).toHaveLength(initialWorkflowCount + 1);
|
|
const createdWorkflow = result.workflows.find((workflow) => workflow.name === 'Template Copy');
|
|
expect(createdWorkflow).toBeDefined();
|
|
expect(result.selectedWorkflowId).toBe(createdWorkflow?.id);
|
|
});
|
|
|
|
test('imports and saves workflows from yaml', async () => {
|
|
const workspace = createWorkspaceSeed();
|
|
const initialWorkflowCount = workspace.workflows.length;
|
|
const service = createService(workspace);
|
|
const yaml = exportWorkflowDefinition(createWorkflow(), 'yaml').content;
|
|
|
|
const result = await service.importWorkflow(yaml, 'yaml', { save: true });
|
|
|
|
expect(result.workspace?.workflows).toHaveLength(initialWorkflowCount + 1);
|
|
expect(result.workspace?.selectedWorkflowId).toBe('workflow-test');
|
|
expect(result.workflow.id).toBe('workflow-test');
|
|
});
|
|
|
|
test('creates workflow-backed sessions', async () => {
|
|
const workspace = createWorkspaceSeed();
|
|
const project = {
|
|
id: 'project-1',
|
|
name: 'Project',
|
|
path: 'C:\\workspace\\project-1',
|
|
addedAt: '2026-04-05T00:00:00.000Z',
|
|
};
|
|
workspace.projects.push(project);
|
|
workspace.workflows.push(createWorkflow());
|
|
const service = createService(workspace);
|
|
|
|
const result = await service.createWorkflowSession(project.id, 'workflow-test');
|
|
const session = result.sessions[0];
|
|
|
|
expect(session?.workflowId).toBe('workflow-test');
|
|
expect(result.selectedWorkflowId).toBe('workflow-test');
|
|
});
|
|
});
|