mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-29 16:08:46 +02:00
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>
147 lines
5.3 KiB
TypeScript
147 lines
5.3 KiB
TypeScript
import { mkdir } from 'node:fs/promises';
|
|
|
|
import { createBuiltinPatterns, resolvePatternGraph } from '@shared/domain/pattern';
|
|
import type { PatternDefinition } from '@shared/domain/pattern';
|
|
import { isScratchpadProject, mergeScratchpadProject } from '@shared/domain/project';
|
|
import { normalizeDiscoveredToolingState } from '@shared/domain/discoveredTooling';
|
|
import { normalizeProjectCustomizationState } from '@shared/domain/projectCustomization';
|
|
import { normalizeSessionRunRecords } from '@shared/domain/runTimeline';
|
|
import {
|
|
normalizeChatMessageRecord,
|
|
normalizeSessionBranchOrigin,
|
|
type SessionRecord,
|
|
} from '@shared/domain/session';
|
|
import {
|
|
normalizeSessionToolingSelection,
|
|
normalizeWorkspaceSettings,
|
|
} from '@shared/domain/tooling';
|
|
import { normalizeWorkflowDefinition } from '@shared/domain/workflow';
|
|
import {
|
|
applyDefaultToolApprovalPolicy,
|
|
normalizePendingApprovalState,
|
|
normalizeSessionApprovalSettings,
|
|
} from '@shared/domain/approval';
|
|
import { createWorkspaceSeed, type WorkspaceState } from '@shared/domain/workspace';
|
|
import { nowIso } from '@shared/utils/ids';
|
|
|
|
import {
|
|
getScratchpadDirectoryPath,
|
|
getScratchpadSessionPath,
|
|
getWorkspaceFilePath,
|
|
} from '@main/persistence/appPaths';
|
|
import { readJsonFile, writeJsonFile } from '@main/persistence/jsonStore';
|
|
|
|
function mergePatterns(existingPatterns: PatternDefinition[], deletedBuiltinIds: string[]): PatternDefinition[] {
|
|
const builtinTimestamp = nowIso();
|
|
const builtinPatterns = createBuiltinPatterns(builtinTimestamp);
|
|
const builtinIds = new Set(builtinPatterns.map((pattern) => pattern.id));
|
|
const deletedSet = new Set(deletedBuiltinIds);
|
|
const existingMap = new Map(existingPatterns.map((pattern) => [pattern.id, pattern]));
|
|
|
|
const mergedBuiltins = builtinPatterns
|
|
.filter((builtin) => !deletedSet.has(builtin.id))
|
|
.map((builtin) => {
|
|
const existing = existingMap.get(builtin.id);
|
|
if (!existing) {
|
|
return builtin;
|
|
}
|
|
|
|
return {
|
|
...existing,
|
|
availability: builtin.availability,
|
|
unavailabilityReason: builtin.unavailabilityReason,
|
|
mode: builtin.mode,
|
|
};
|
|
});
|
|
|
|
const customPatterns = existingPatterns.filter((pattern) => !builtinIds.has(pattern.id));
|
|
return [...mergedBuiltins, ...customPatterns];
|
|
}
|
|
|
|
export class WorkspaceRepository {
|
|
readonly filePath = getWorkspaceFilePath();
|
|
readonly scratchpadPath = getScratchpadDirectoryPath();
|
|
|
|
async load(): Promise<WorkspaceState> {
|
|
await mkdir(this.scratchpadPath, { recursive: true });
|
|
|
|
const stored = await readJsonFile<WorkspaceState>(this.filePath);
|
|
if (!stored) {
|
|
const seededBase = createWorkspaceSeed();
|
|
const projects = mergeScratchpadProject([], this.scratchpadPath);
|
|
const seeded: WorkspaceState = {
|
|
...seededBase,
|
|
projects,
|
|
selectedProjectId: projects[0]?.id,
|
|
};
|
|
await this.save(seeded);
|
|
return seeded;
|
|
}
|
|
|
|
const projects = mergeScratchpadProject(
|
|
(stored.projects ?? []).map((project) => ({
|
|
...project,
|
|
discoveredTooling: normalizeDiscoveredToolingState(project.discoveredTooling),
|
|
customization: normalizeProjectCustomizationState(project.customization),
|
|
})),
|
|
this.scratchpadPath,
|
|
);
|
|
const sessions = await Promise.all((stored.sessions ?? []).map(async (session): Promise<SessionRecord> => {
|
|
const normalizedSession: SessionRecord = {
|
|
...session,
|
|
messages: (session.messages ?? []).map(normalizeChatMessageRecord),
|
|
branchOrigin: normalizeSessionBranchOrigin(session.branchOrigin),
|
|
runs: normalizeSessionRunRecords(session.runs),
|
|
tooling: normalizeSessionToolingSelection(session.tooling),
|
|
approvalSettings: normalizeSessionApprovalSettings(session.approvalSettings),
|
|
...normalizePendingApprovalState({
|
|
pendingApproval: session.pendingApproval,
|
|
pendingApprovalQueue: session.pendingApprovalQueue,
|
|
}),
|
|
};
|
|
if (!isScratchpadProject(normalizedSession.projectId)) {
|
|
return normalizedSession;
|
|
}
|
|
|
|
const cwd = normalizedSession.cwd ?? getScratchpadSessionPath(normalizedSession.id);
|
|
await mkdir(cwd, { recursive: true });
|
|
return {
|
|
...normalizedSession,
|
|
cwd,
|
|
};
|
|
}));
|
|
const settings = normalizeWorkspaceSettings(stored.settings);
|
|
|
|
const deletedBuiltinPatternIds = stored.deletedBuiltinPatternIds ?? [];
|
|
|
|
const workspace: WorkspaceState = {
|
|
...stored,
|
|
patterns: mergePatterns(stored.patterns ?? [], deletedBuiltinPatternIds).map((pattern) => ({
|
|
...pattern,
|
|
approvalPolicy: applyDefaultToolApprovalPolicy(pattern.approvalPolicy),
|
|
graph: resolvePatternGraph(pattern),
|
|
})),
|
|
workflows: (stored.workflows ?? []).map(normalizeWorkflowDefinition),
|
|
projects,
|
|
sessions,
|
|
settings,
|
|
deletedBuiltinPatternIds,
|
|
selectedProjectId: projects.some((project) => project.id === stored.selectedProjectId)
|
|
? stored.selectedProjectId
|
|
: projects[0]?.id,
|
|
lastUpdatedAt: stored.lastUpdatedAt ?? nowIso(),
|
|
};
|
|
|
|
await this.save(workspace);
|
|
return workspace;
|
|
}
|
|
|
|
async save(workspace: WorkspaceState): Promise<void> {
|
|
const { mcpProbingServerIds: _mcpProbingServerIds, ...persistedWorkspace } = workspace;
|
|
await writeJsonFile(this.filePath, {
|
|
...persistedWorkspace,
|
|
lastUpdatedAt: nowIso(),
|
|
});
|
|
}
|
|
}
|