@exaudeus/workrail 1.16.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application/services/compiler/feature-registry.js +18 -0
- package/dist/application/services/compiler/prompt-blocks.js +2 -1
- package/dist/application/services/compiler/ref-registry.js +36 -0
- package/dist/application/services/validation-engine.d.ts +1 -0
- package/dist/application/services/validation-engine.js +14 -0
- package/dist/application/services/workflow-validation-pipeline.d.ts +96 -0
- package/dist/application/services/workflow-validation-pipeline.js +94 -0
- package/dist/application/use-cases/raw-workflow-file-scanner.d.ts +18 -0
- package/dist/application/use-cases/raw-workflow-file-scanner.js +91 -0
- package/dist/application/use-cases/validate-workflow-file.d.ts +17 -0
- package/dist/application/use-cases/validate-workflow-file.js +96 -0
- package/dist/application/use-cases/validate-workflow-json.d.ts +2 -1
- package/dist/application/use-cases/validate-workflow-json.js +67 -13
- package/dist/application/use-cases/validate-workflow-registry.d.ts +72 -0
- package/dist/application/use-cases/validate-workflow-registry.js +215 -0
- package/dist/application/validation.d.ts +4 -0
- package/dist/application/validation.js +16 -0
- package/dist/cli/commands/validate.js +15 -0
- package/dist/cli.js +10 -1
- package/dist/infrastructure/storage/caching-workflow-storage.d.ts +1 -0
- package/dist/infrastructure/storage/caching-workflow-storage.js +3 -0
- package/dist/infrastructure/storage/enhanced-multi-source-workflow-storage.d.ts +2 -1
- package/dist/infrastructure/storage/enhanced-multi-source-workflow-storage.js +8 -21
- package/dist/infrastructure/storage/file-workflow-storage.d.ts +0 -1
- package/dist/infrastructure/storage/file-workflow-storage.js +15 -36
- package/dist/infrastructure/storage/schema-validating-workflow-storage.d.ts +1 -0
- package/dist/infrastructure/storage/schema-validating-workflow-storage.js +16 -6
- package/dist/infrastructure/storage/workflow-resolution.d.ts +62 -0
- package/dist/infrastructure/storage/workflow-resolution.js +150 -0
- package/dist/manifest.json +140 -68
- package/dist/mcp/handlers/v2-execution/replay.d.ts +1 -1
- package/dist/mcp/handlers/v2-execution/replay.js +37 -21
- package/dist/mcp/handlers/v2-execution/start.js +35 -13
- package/dist/mcp/handlers/v2-execution-helpers.d.ts +9 -11
- package/dist/mcp/handlers/v2-execution-helpers.js +6 -18
- package/dist/mcp/output-schemas.d.ts +20 -20
- package/dist/mcp/server.d.ts +18 -0
- package/dist/mcp/server.js +8 -1
- package/dist/mcp/transports/http-entry.d.ts +1 -0
- package/dist/mcp/transports/http-entry.js +87 -0
- package/dist/mcp/transports/http-listener.d.ts +9 -0
- package/dist/mcp/transports/http-listener.js +64 -0
- package/dist/mcp/transports/stdio-entry.d.ts +1 -0
- package/dist/mcp/transports/stdio-entry.js +92 -0
- package/dist/mcp/transports/transport-mode.d.ts +7 -0
- package/dist/mcp/transports/transport-mode.js +18 -0
- package/dist/mcp-server.js +21 -5
- package/dist/types/storage.d.ts +1 -0
- package/dist/v2/durable-core/domain/prompt-renderer.js +13 -7
- package/dist/v2/durable-core/domain/start-construction.d.ts +22 -0
- package/dist/v2/durable-core/domain/start-construction.js +31 -0
- package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +8 -8
- package/dist/v2/read-only/v1-to-v2-shim.d.ts +5 -0
- package/dist/v2/read-only/v1-to-v2-shim.js +18 -0
- package/package.json +3 -2
- package/workflows/bug-investigation.agentic.v2.json +134 -0
- package/workflows/mr-review-workflow.agentic.v2.json +238 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Workflow } from '../../types/workflow.js';
|
|
2
|
+
import type { WorkflowSource } from '../../types/workflow.js';
|
|
3
|
+
import type { IWorkflowStorage } from '../../types/storage.js';
|
|
4
|
+
import type { ValidationOutcome, ValidationOutcomePhase1a } from '../services/workflow-validation-pipeline.js';
|
|
5
|
+
import { type ValidationPipelineDepsPhase1a, type SchemaError } from '../services/workflow-validation-pipeline.js';
|
|
6
|
+
import type { ResolutionReason, VariantResolution, SourceRef } from '../../infrastructure/storage/workflow-resolution.js';
|
|
7
|
+
import type { RawWorkflowFile, VariantKind } from './raw-workflow-file-scanner.js';
|
|
8
|
+
export interface RegistrySnapshot {
|
|
9
|
+
readonly sources: readonly WorkflowSource[];
|
|
10
|
+
readonly rawFiles: readonly RawWorkflowFile[];
|
|
11
|
+
readonly candidates: readonly {
|
|
12
|
+
readonly sourceRef: SourceRef;
|
|
13
|
+
readonly workflows: readonly Workflow[];
|
|
14
|
+
readonly variantResolutions: ReadonlyMap<string, VariantResolution>;
|
|
15
|
+
}[];
|
|
16
|
+
readonly resolved: readonly {
|
|
17
|
+
readonly workflow: Workflow;
|
|
18
|
+
readonly resolvedBy: ResolutionReason;
|
|
19
|
+
}[];
|
|
20
|
+
readonly duplicates: readonly {
|
|
21
|
+
readonly workflowId: string;
|
|
22
|
+
readonly sources: readonly SourceRef[];
|
|
23
|
+
}[];
|
|
24
|
+
}
|
|
25
|
+
export type Tier1Outcome = {
|
|
26
|
+
readonly kind: 'tier1_unparseable';
|
|
27
|
+
readonly parseError: string;
|
|
28
|
+
} | {
|
|
29
|
+
readonly kind: 'schema_failed';
|
|
30
|
+
readonly errors: readonly SchemaError[];
|
|
31
|
+
} | {
|
|
32
|
+
readonly kind: 'structural_failed';
|
|
33
|
+
readonly issues: readonly string[];
|
|
34
|
+
} | {
|
|
35
|
+
readonly kind: 'tier1_passed';
|
|
36
|
+
};
|
|
37
|
+
export interface ResolvedValidationEntry {
|
|
38
|
+
readonly workflowId: string;
|
|
39
|
+
readonly sourceRef: SourceRef;
|
|
40
|
+
readonly resolvedBy: ResolutionReason;
|
|
41
|
+
readonly outcome: ValidationOutcome | ValidationOutcomePhase1a;
|
|
42
|
+
}
|
|
43
|
+
export interface RawFileValidationEntry {
|
|
44
|
+
readonly filePath: string;
|
|
45
|
+
readonly relativeFilePath: string;
|
|
46
|
+
readonly sourceRef: SourceRef | undefined;
|
|
47
|
+
readonly workflowId: string | undefined;
|
|
48
|
+
readonly variantKind: VariantKind | undefined;
|
|
49
|
+
readonly isResolvedWinner: boolean;
|
|
50
|
+
readonly tier1Outcome: Tier1Outcome;
|
|
51
|
+
}
|
|
52
|
+
export interface DuplicateIdReport {
|
|
53
|
+
readonly workflowId: string;
|
|
54
|
+
readonly sourceRefs: readonly SourceRef[];
|
|
55
|
+
readonly isBundledProtection: boolean;
|
|
56
|
+
}
|
|
57
|
+
export interface RegistryValidationReport {
|
|
58
|
+
readonly totalRawFiles: number;
|
|
59
|
+
readonly totalResolvedWorkflows: number;
|
|
60
|
+
readonly validResolvedCount: number;
|
|
61
|
+
readonly invalidResolvedCount: number;
|
|
62
|
+
readonly tier1PassedRawFiles: number;
|
|
63
|
+
readonly tier1FailedRawFiles: number;
|
|
64
|
+
readonly duplicateIds: readonly DuplicateIdReport[];
|
|
65
|
+
readonly resolvedResults: readonly ResolvedValidationEntry[];
|
|
66
|
+
readonly rawFileResults: readonly RawFileValidationEntry[];
|
|
67
|
+
readonly isValid: boolean;
|
|
68
|
+
}
|
|
69
|
+
export interface RegistryValidatorDeps extends ValidationPipelineDepsPhase1a {
|
|
70
|
+
}
|
|
71
|
+
export declare function validateRegistry(snapshot: RegistrySnapshot, deps: RegistryValidatorDeps): RegistryValidationReport;
|
|
72
|
+
export declare function buildRegistrySnapshot(storageInstances: readonly IWorkflowStorage[]): Promise<RegistrySnapshot>;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateRegistry = validateRegistry;
|
|
4
|
+
exports.buildRegistrySnapshot = buildRegistrySnapshot;
|
|
5
|
+
const workflow_validation_pipeline_js_1 = require("../services/workflow-validation-pipeline.js");
|
|
6
|
+
const workflow_resolution_js_1 = require("../../infrastructure/storage/workflow-resolution.js");
|
|
7
|
+
const raw_workflow_file_scanner_js_1 = require("./raw-workflow-file-scanner.js");
|
|
8
|
+
const workflow_source_js_1 = require("../../types/workflow-source.js");
|
|
9
|
+
const workflow_js_1 = require("../../types/workflow.js");
|
|
10
|
+
function validateRegistry(snapshot, deps) {
|
|
11
|
+
const resolvedWinnerIds = new Set();
|
|
12
|
+
for (const { workflow } of snapshot.resolved) {
|
|
13
|
+
resolvedWinnerIds.add(workflow.definition.id);
|
|
14
|
+
}
|
|
15
|
+
const resolvedResults = [];
|
|
16
|
+
for (const { workflow, resolvedBy } of snapshot.resolved) {
|
|
17
|
+
const outcome = (0, workflow_validation_pipeline_js_1.validateWorkflowPhase1a)(workflow, deps);
|
|
18
|
+
resolvedResults.push({
|
|
19
|
+
workflowId: workflow.definition.id,
|
|
20
|
+
sourceRef: extractSourceRef(resolvedBy),
|
|
21
|
+
resolvedBy,
|
|
22
|
+
outcome,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const validResolvedCount = resolvedResults.filter(e => e.outcome.kind === 'phase1a_valid').length;
|
|
26
|
+
const rawFileResults = [];
|
|
27
|
+
for (const rawFile of snapshot.rawFiles) {
|
|
28
|
+
if (rawFile.kind === 'unparseable') {
|
|
29
|
+
rawFileResults.push({
|
|
30
|
+
filePath: rawFile.filePath,
|
|
31
|
+
relativeFilePath: rawFile.relativeFilePath,
|
|
32
|
+
sourceRef: findSourceRefForFile(rawFile.filePath, snapshot.sources),
|
|
33
|
+
workflowId: undefined,
|
|
34
|
+
variantKind: undefined,
|
|
35
|
+
isResolvedWinner: false,
|
|
36
|
+
tier1Outcome: { kind: 'tier1_unparseable', parseError: rawFile.error },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const tier1Outcome = validateRawFileTier1(rawFile, deps);
|
|
41
|
+
const isWinner = resolvedWinnerIds.has(rawFile.definition.id);
|
|
42
|
+
rawFileResults.push({
|
|
43
|
+
filePath: rawFile.filePath,
|
|
44
|
+
relativeFilePath: rawFile.relativeFilePath,
|
|
45
|
+
sourceRef: findSourceRefForFile(rawFile.filePath, snapshot.sources),
|
|
46
|
+
workflowId: rawFile.definition.id,
|
|
47
|
+
variantKind: rawFile.variantKind,
|
|
48
|
+
isResolvedWinner: isWinner,
|
|
49
|
+
tier1Outcome,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const tier1PassedRawFiles = rawFileResults.filter(e => e.tier1Outcome.kind === 'tier1_passed').length;
|
|
54
|
+
const tier1FailedRawFiles = rawFileResults.length - tier1PassedRawFiles;
|
|
55
|
+
const resolvedByKindMap = new Map();
|
|
56
|
+
for (const { workflow, resolvedBy } of snapshot.resolved) {
|
|
57
|
+
resolvedByKindMap.set(workflow.definition.id, resolvedBy);
|
|
58
|
+
}
|
|
59
|
+
const duplicateIdReports = snapshot.duplicates.map(dup => ({
|
|
60
|
+
workflowId: dup.workflowId,
|
|
61
|
+
sourceRefs: dup.sources,
|
|
62
|
+
isBundledProtection: resolvedByKindMap.get(dup.workflowId)?.kind === 'bundled_protected',
|
|
63
|
+
}));
|
|
64
|
+
const hardErrorDuplicates = duplicateIdReports.filter(d => !d.isBundledProtection);
|
|
65
|
+
const isValid = validResolvedCount === snapshot.resolved.length &&
|
|
66
|
+
tier1FailedRawFiles === 0 &&
|
|
67
|
+
hardErrorDuplicates.length === 0;
|
|
68
|
+
return {
|
|
69
|
+
totalRawFiles: snapshot.rawFiles.length,
|
|
70
|
+
totalResolvedWorkflows: snapshot.resolved.length,
|
|
71
|
+
validResolvedCount,
|
|
72
|
+
invalidResolvedCount: snapshot.resolved.length - validResolvedCount,
|
|
73
|
+
tier1PassedRawFiles,
|
|
74
|
+
tier1FailedRawFiles,
|
|
75
|
+
duplicateIds: duplicateIdReports,
|
|
76
|
+
resolvedResults,
|
|
77
|
+
rawFileResults,
|
|
78
|
+
isValid,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function validateRawFileTier1(rawFile, deps) {
|
|
82
|
+
const fakeWorkflow = (0, workflow_js_1.createWorkflow)(rawFile.definition, { kind: 'bundled' });
|
|
83
|
+
const schemaResult = deps.schemaValidate(fakeWorkflow);
|
|
84
|
+
if (schemaResult.isErr()) {
|
|
85
|
+
return { kind: 'schema_failed', errors: schemaResult.error };
|
|
86
|
+
}
|
|
87
|
+
const structuralResult = deps.structuralValidate(fakeWorkflow);
|
|
88
|
+
if (structuralResult.isErr()) {
|
|
89
|
+
return { kind: 'structural_failed', issues: structuralResult.error };
|
|
90
|
+
}
|
|
91
|
+
return { kind: 'tier1_passed' };
|
|
92
|
+
}
|
|
93
|
+
function extractSourceRef(resolvedBy) {
|
|
94
|
+
switch (resolvedBy.kind) {
|
|
95
|
+
case 'unique':
|
|
96
|
+
return resolvedBy.sourceRef;
|
|
97
|
+
case 'source_priority':
|
|
98
|
+
return resolvedBy.winnerRef;
|
|
99
|
+
case 'bundled_protected':
|
|
100
|
+
return resolvedBy.bundledSourceRef;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function findSourceRefForFile(filePath, sources) {
|
|
104
|
+
for (let i = 0; i < sources.length; i++) {
|
|
105
|
+
const sourcePath = (0, workflow_source_js_1.getSourcePath)(sources[i]);
|
|
106
|
+
if (sourcePath && filePath.startsWith(sourcePath)) {
|
|
107
|
+
return i;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
async function buildRegistrySnapshot(storageInstances) {
|
|
113
|
+
const sources = storageInstances.map(s => s.source);
|
|
114
|
+
const allRawFiles = [];
|
|
115
|
+
for (const source of sources) {
|
|
116
|
+
const sourcePath = (0, workflow_source_js_1.getSourcePath)(source);
|
|
117
|
+
if (!sourcePath)
|
|
118
|
+
continue;
|
|
119
|
+
try {
|
|
120
|
+
const rawFiles = await (0, raw_workflow_file_scanner_js_1.scanRawWorkflowFiles)(sourcePath);
|
|
121
|
+
allRawFiles.push(...rawFiles);
|
|
122
|
+
}
|
|
123
|
+
catch (_e) {
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const candidates = [];
|
|
127
|
+
for (let i = 0; i < storageInstances.length; i++) {
|
|
128
|
+
const storage = storageInstances[i];
|
|
129
|
+
try {
|
|
130
|
+
const workflows = await storage.loadAllWorkflows();
|
|
131
|
+
const variantResolutions = deriveVariantResolutions(workflows, allRawFiles, sources[i]);
|
|
132
|
+
candidates.push({
|
|
133
|
+
sourceRef: i,
|
|
134
|
+
workflows,
|
|
135
|
+
variantResolutions,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch (_e) {
|
|
139
|
+
candidates.push({
|
|
140
|
+
sourceRef: i,
|
|
141
|
+
workflows: [],
|
|
142
|
+
variantResolutions: new Map(),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const variantMap = new Map();
|
|
147
|
+
for (const { sourceRef, variantResolutions } of candidates) {
|
|
148
|
+
for (const [id, resolution] of variantResolutions.entries()) {
|
|
149
|
+
const existing = variantMap.get(id) ?? new Map();
|
|
150
|
+
const updated = new Map(existing);
|
|
151
|
+
updated.set(sourceRef, resolution);
|
|
152
|
+
variantMap.set(id, updated);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const candidatesForResolution = candidates.map(c => ({
|
|
156
|
+
sourceRef: c.sourceRef,
|
|
157
|
+
workflows: c.workflows,
|
|
158
|
+
}));
|
|
159
|
+
const resolved = (0, workflow_resolution_js_1.resolveWorkflowCandidates)(candidatesForResolution, variantMap);
|
|
160
|
+
const duplicates = (0, workflow_resolution_js_1.detectDuplicateIds)(candidatesForResolution);
|
|
161
|
+
return Object.freeze({
|
|
162
|
+
sources: Object.freeze(sources),
|
|
163
|
+
rawFiles: Object.freeze(allRawFiles),
|
|
164
|
+
candidates: Object.freeze(candidates),
|
|
165
|
+
resolved: Object.freeze(resolved),
|
|
166
|
+
duplicates: Object.freeze(duplicates),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function deriveVariantResolutions(loadedWorkflows, allRawFiles, source) {
|
|
170
|
+
const result = new Map();
|
|
171
|
+
const sourcePath = (0, workflow_source_js_1.getSourcePath)(source);
|
|
172
|
+
if (!sourcePath)
|
|
173
|
+
return result;
|
|
174
|
+
const rawFilesByWorkflowId = new Map();
|
|
175
|
+
for (const rawFile of allRawFiles) {
|
|
176
|
+
if (rawFile.kind !== 'parsed')
|
|
177
|
+
continue;
|
|
178
|
+
if (!rawFile.filePath.startsWith(sourcePath))
|
|
179
|
+
continue;
|
|
180
|
+
const id = rawFile.definition.id;
|
|
181
|
+
const existing = rawFilesByWorkflowId.get(id) ?? [];
|
|
182
|
+
rawFilesByWorkflowId.set(id, [...existing, rawFile]);
|
|
183
|
+
}
|
|
184
|
+
for (const workflow of loadedWorkflows) {
|
|
185
|
+
const id = workflow.definition.id;
|
|
186
|
+
const rawFilesForId = rawFilesByWorkflowId.get(id) ?? [];
|
|
187
|
+
if (rawFilesForId.length <= 1) {
|
|
188
|
+
result.set(id, { kind: 'only_variant' });
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const availableVariants = rawFilesForId.map(f => f.variantKind);
|
|
192
|
+
const selectedRaw = rawFilesForId.find(f => f.definition.id === id);
|
|
193
|
+
const selectedVariant = selectedRaw?.variantKind ?? 'standard';
|
|
194
|
+
if (selectedVariant === 'v2' || selectedVariant === 'agentic') {
|
|
195
|
+
result.set(id, {
|
|
196
|
+
kind: 'feature_flag_selected',
|
|
197
|
+
selectedVariant,
|
|
198
|
+
availableVariants: availableVariants,
|
|
199
|
+
enabledFlags: {
|
|
200
|
+
v2Tools: selectedVariant === 'v2',
|
|
201
|
+
agenticRoutines: selectedVariant === 'agentic',
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
result.set(id, {
|
|
207
|
+
kind: 'precedence_fallback',
|
|
208
|
+
selectedVariant,
|
|
209
|
+
availableVariants: availableVariants,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import type { Workflow } from '../types/workflow.js';
|
|
2
|
+
import type { SchemaError } from './services/workflow-validation-pipeline.js';
|
|
3
|
+
import { type Result } from 'neverthrow';
|
|
1
4
|
export interface WorkflowValidationResult {
|
|
2
5
|
valid: boolean;
|
|
3
6
|
errors: string[];
|
|
4
7
|
}
|
|
5
8
|
export declare function validateWorkflow(workflow: unknown): WorkflowValidationResult;
|
|
9
|
+
export declare function validateWorkflowSchema(workflow: Workflow): Result<Workflow, readonly SchemaError[]>;
|
|
@@ -4,10 +4,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.validateWorkflow = validateWorkflow;
|
|
7
|
+
exports.validateWorkflowSchema = validateWorkflowSchema;
|
|
7
8
|
const fs_1 = __importDefault(require("fs"));
|
|
8
9
|
const path_1 = __importDefault(require("path"));
|
|
9
10
|
const ajv_1 = __importDefault(require("ajv"));
|
|
10
11
|
const enhanced_error_service_1 = require("./services/enhanced-error-service");
|
|
12
|
+
const neverthrow_1 = require("neverthrow");
|
|
11
13
|
const schemaPath = path_1.default.resolve(__dirname, '../../spec/workflow.schema.json');
|
|
12
14
|
const schema = JSON.parse(fs_1.default.readFileSync(schemaPath, 'utf-8'));
|
|
13
15
|
const ajv = new ajv_1.default({ allErrors: true, strict: false });
|
|
@@ -19,3 +21,17 @@ function validateWorkflow(workflow) {
|
|
|
19
21
|
errors: ok ? [] : enhanced_error_service_1.EnhancedErrorService.enhanceErrors(validate.errors || [])
|
|
20
22
|
};
|
|
21
23
|
}
|
|
24
|
+
function validateWorkflowSchema(workflow) {
|
|
25
|
+
const result = validateWorkflow(workflow.definition);
|
|
26
|
+
if (result.valid) {
|
|
27
|
+
return (0, neverthrow_1.ok)(workflow);
|
|
28
|
+
}
|
|
29
|
+
const ajvErrors = validate.errors || [];
|
|
30
|
+
const errors = ajvErrors.map(e => ({
|
|
31
|
+
instancePath: e.instancePath ?? '',
|
|
32
|
+
message: e.message,
|
|
33
|
+
keyword: e.keyword,
|
|
34
|
+
params: e.params,
|
|
35
|
+
}));
|
|
36
|
+
return (0, neverthrow_1.err)(errors);
|
|
37
|
+
}
|
|
@@ -49,6 +49,21 @@ function executeValidateCommand(filePath, deps) {
|
|
|
49
49
|
],
|
|
50
50
|
suggestions: result.suggestions ? [...result.suggestions] : undefined,
|
|
51
51
|
});
|
|
52
|
+
case 'v1_compilation_failed':
|
|
53
|
+
return (0, cli_result_js_1.failure)(`V1 compilation failed: ${filePath}`, {
|
|
54
|
+
details: [result.message],
|
|
55
|
+
suggestions: ['Review the workflow definition and try again'],
|
|
56
|
+
});
|
|
57
|
+
case 'normalization_failed':
|
|
58
|
+
return (0, cli_result_js_1.failure)(`Normalization failed: ${filePath}`, {
|
|
59
|
+
details: [result.message],
|
|
60
|
+
suggestions: ['Review template/feature/ref definitions and try again'],
|
|
61
|
+
});
|
|
62
|
+
case 'executable_compilation_failed':
|
|
63
|
+
return (0, cli_result_js_1.failure)(`Executable compilation failed: ${filePath}`, {
|
|
64
|
+
details: [result.message],
|
|
65
|
+
suggestions: ['The normalized workflow has an internal conflict — ensure steps use exactly one prompt source'],
|
|
66
|
+
});
|
|
52
67
|
default:
|
|
53
68
|
return (0, assert_never_js_1.assertNever)(result);
|
|
54
69
|
}
|
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,8 @@ const server_js_1 = require("./mcp/server.js");
|
|
|
14
14
|
const workflow_js_1 = require("./types/workflow.js");
|
|
15
15
|
const validation_js_1 = require("./application/validation.js");
|
|
16
16
|
const validate_workflow_file_js_1 = require("./application/use-cases/validate-workflow-file.js");
|
|
17
|
+
const workflow_compiler_js_1 = require("./application/services/workflow-compiler.js");
|
|
18
|
+
const v1_to_v2_shim_js_1 = require("./v2/read-only/v1-to-v2-shim.js");
|
|
17
19
|
const interpret_result_js_1 = require("./cli/interpret-result.js");
|
|
18
20
|
const index_js_1 = require("./cli/commands/index.js");
|
|
19
21
|
const program = new commander_1.Command();
|
|
@@ -68,7 +70,8 @@ program
|
|
|
68
70
|
await (0, container_js_1.initializeContainer)({ runtimeMode: { kind: 'cli' } });
|
|
69
71
|
const terminator = container_js_1.container.resolve(tokens_js_1.DI.Runtime.ProcessTerminator);
|
|
70
72
|
const validationEngine = container_js_1.container.resolve(tokens_js_1.DI.Infra.ValidationEngine);
|
|
71
|
-
const
|
|
73
|
+
const compiler = new workflow_compiler_js_1.WorkflowCompiler();
|
|
74
|
+
const validateWorkflowFile = (0, validate_workflow_file_js_1.createValidateWorkflowFileUseCasePipeline)({
|
|
72
75
|
resolvePath: path_1.default.resolve,
|
|
73
76
|
existsSync: fs_1.default.existsSync,
|
|
74
77
|
readFileSyncUtf8: (resolvedPath) => fs_1.default.readFileSync(resolvedPath, 'utf-8'),
|
|
@@ -76,6 +79,12 @@ program
|
|
|
76
79
|
schemaValidate: (definition) => (0, validation_js_1.validateWorkflow)(definition),
|
|
77
80
|
makeRuntimeWorkflow: (definition, resolvedPath) => (0, workflow_js_1.createWorkflow)(definition, (0, workflow_js_1.createCustomDirectorySource)(path_1.default.dirname(resolvedPath), 'CLI Validate')),
|
|
78
81
|
validateRuntimeWorkflow: (workflow) => validationEngine.validateWorkflow(workflow),
|
|
82
|
+
validationPipelineDeps: {
|
|
83
|
+
schemaValidate: validation_js_1.validateWorkflowSchema,
|
|
84
|
+
structuralValidate: validationEngine.validateWorkflowStructureOnly.bind(validationEngine),
|
|
85
|
+
compiler,
|
|
86
|
+
normalizeToExecutable: v1_to_v2_shim_js_1.normalizeV1WorkflowToPinnedSnapshot,
|
|
87
|
+
},
|
|
79
88
|
});
|
|
80
89
|
const result = (0, index_js_1.executeValidateCommand)(filePath, { validateWorkflowFile });
|
|
81
90
|
(0, interpret_result_js_1.interpretCliResult)(result, terminator);
|
|
@@ -30,6 +30,7 @@ export declare class CachingCompositeWorkflowStorage implements ICompositeWorkfl
|
|
|
30
30
|
constructor(inner: ICompositeWorkflowStorage, ttlMs: number);
|
|
31
31
|
private isFresh;
|
|
32
32
|
getSources(): readonly WorkflowSource[];
|
|
33
|
+
getStorageInstances(): readonly IWorkflowStorage[];
|
|
33
34
|
loadAllWorkflows(): Promise<readonly Workflow[]>;
|
|
34
35
|
getWorkflowById(id: string): Promise<Workflow | null>;
|
|
35
36
|
listWorkflowSummaries(): Promise<readonly WorkflowSummary[]>;
|
|
@@ -85,6 +85,9 @@ class CachingCompositeWorkflowStorage {
|
|
|
85
85
|
getSources() {
|
|
86
86
|
return this.inner.getSources();
|
|
87
87
|
}
|
|
88
|
+
getStorageInstances() {
|
|
89
|
+
return this.inner.getStorageInstances();
|
|
90
|
+
}
|
|
88
91
|
async loadAllWorkflows() {
|
|
89
92
|
if (this.isFresh(this.workflowCache)) {
|
|
90
93
|
this.stats.hits += 1;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ICompositeWorkflowStorage } from '../../types/storage';
|
|
1
|
+
import { ICompositeWorkflowStorage, IWorkflowStorage } from '../../types/storage';
|
|
2
2
|
import { Workflow, WorkflowSummary, WorkflowDefinition, WorkflowSource } from '../../types/workflow';
|
|
3
3
|
import { GitWorkflowConfig } from './git-workflow-storage';
|
|
4
4
|
import { RemoteWorkflowRegistryConfig } from './remote-workflow-storage';
|
|
@@ -31,6 +31,7 @@ export declare class EnhancedMultiSourceWorkflowStorage implements ICompositeWor
|
|
|
31
31
|
private readonly config;
|
|
32
32
|
constructor(config?: EnhancedMultiSourceConfig, featureFlagProvider?: IFeatureFlagProvider | null);
|
|
33
33
|
getSources(): readonly WorkflowSource[];
|
|
34
|
+
getStorageInstances(): readonly IWorkflowStorage[];
|
|
34
35
|
private initializeStorageSources;
|
|
35
36
|
loadAllWorkflows(): Promise<readonly Workflow[]>;
|
|
36
37
|
getWorkflowById(id: string): Promise<Workflow | null>;
|
|
@@ -16,6 +16,7 @@ const remote_workflow_storage_1 = require("./remote-workflow-storage");
|
|
|
16
16
|
const plugin_workflow_storage_1 = require("./plugin-workflow-storage");
|
|
17
17
|
const feature_flags_1 = require("../../config/feature-flags");
|
|
18
18
|
const logger_1 = require("../../utils/logger");
|
|
19
|
+
const workflow_resolution_1 = require("./workflow-resolution");
|
|
19
20
|
const logger = (0, logger_1.createLogger)('EnhancedMultiSourceWorkflowStorage');
|
|
20
21
|
class EnhancedMultiSourceWorkflowStorage {
|
|
21
22
|
constructor(config = {}, featureFlagProvider = null) {
|
|
@@ -40,6 +41,9 @@ class EnhancedMultiSourceWorkflowStorage {
|
|
|
40
41
|
getSources() {
|
|
41
42
|
return this.storageInstances.map(storage => storage.source);
|
|
42
43
|
}
|
|
44
|
+
getStorageInstances() {
|
|
45
|
+
return this.storageInstances;
|
|
46
|
+
}
|
|
43
47
|
initializeStorageSources(config) {
|
|
44
48
|
const instances = [];
|
|
45
49
|
if (!this.featureFlagProvider) {
|
|
@@ -127,36 +131,19 @@ class EnhancedMultiSourceWorkflowStorage {
|
|
|
127
131
|
return instances;
|
|
128
132
|
}
|
|
129
133
|
async loadAllWorkflows() {
|
|
130
|
-
const
|
|
131
|
-
const seenIds = new Set();
|
|
134
|
+
const candidates = [];
|
|
132
135
|
for (let i = 0; i < this.storageInstances.length; i++) {
|
|
133
136
|
const storage = this.storageInstances[i];
|
|
134
137
|
try {
|
|
135
138
|
const workflows = await storage.loadAllWorkflows();
|
|
136
|
-
|
|
137
|
-
const id = workflow.definition.id;
|
|
138
|
-
if (seenIds.has(id)) {
|
|
139
|
-
const existingIndex = allWorkflows.findIndex((wf) => wf.definition.id === id);
|
|
140
|
-
if (existingIndex >= 0) {
|
|
141
|
-
const existing = allWorkflows[existingIndex];
|
|
142
|
-
const isWr = id.startsWith('wr.');
|
|
143
|
-
if (isWr && existing.source.kind === 'bundled') {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
allWorkflows[existingIndex] = workflow;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
allWorkflows.push(workflow);
|
|
151
|
-
seenIds.add(id);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
139
|
+
candidates.push({ sourceRef: i, workflows });
|
|
154
140
|
}
|
|
155
141
|
catch (error) {
|
|
156
142
|
this.handleSourceError(`source-${i}`, error);
|
|
157
143
|
}
|
|
158
144
|
}
|
|
159
|
-
|
|
145
|
+
const resolved = (0, workflow_resolution_1.resolveWorkflowCandidates)(candidates, new Map());
|
|
146
|
+
return resolved.map(r => r.workflow);
|
|
160
147
|
}
|
|
161
148
|
async getWorkflowById(id) {
|
|
162
149
|
if (id.startsWith('wr.')) {
|
|
@@ -21,7 +21,6 @@ export declare class FileWorkflowStorage implements IWorkflowStorage {
|
|
|
21
21
|
private workflowIndex;
|
|
22
22
|
private indexExpires;
|
|
23
23
|
constructor(directory: string, source: WorkflowSource, featureFlagProvider: IFeatureFlagProvider, options?: Omit<FileWorkflowStorageOptions, 'featureFlagProvider'>);
|
|
24
|
-
private findJsonFiles;
|
|
25
24
|
private buildWorkflowIndex;
|
|
26
25
|
private getWorkflowIndex;
|
|
27
26
|
private loadDefinitionFromFile;
|
|
@@ -12,6 +12,8 @@ const workflow_1 = require("../../types/workflow");
|
|
|
12
12
|
const error_handler_1 = require("../../core/error-handler");
|
|
13
13
|
const workflow_id_policy_1 = require("../../domain/workflow-id-policy");
|
|
14
14
|
const storage_security_1 = require("../../utils/storage-security");
|
|
15
|
+
const workflow_resolution_1 = require("./workflow-resolution");
|
|
16
|
+
const raw_workflow_file_scanner_1 = require("../../application/use-cases/raw-workflow-file-scanner");
|
|
15
17
|
function sanitizeId(id) {
|
|
16
18
|
if (id.includes('\u0000')) {
|
|
17
19
|
throw new error_handler_1.SecurityError('Null byte detected in identifier', 'sanitizeId');
|
|
@@ -40,28 +42,8 @@ class FileWorkflowStorage {
|
|
|
40
42
|
this.indexCacheTTL = options.indexCacheTTLms ?? 30000;
|
|
41
43
|
this.featureFlags = featureFlagProvider;
|
|
42
44
|
}
|
|
43
|
-
async findJsonFiles(dir) {
|
|
44
|
-
const files = [];
|
|
45
|
-
async function scan(currentDir) {
|
|
46
|
-
const entries = await promises_1.default.readdir(currentDir, { withFileTypes: true });
|
|
47
|
-
for (const entry of entries) {
|
|
48
|
-
const fullPath = path_1.default.join(currentDir, entry.name);
|
|
49
|
-
if (entry.isDirectory()) {
|
|
50
|
-
if (entry.name === 'examples') {
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
await scan(fullPath);
|
|
54
|
-
}
|
|
55
|
-
else if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
56
|
-
files.push(fullPath);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
await scan(dir);
|
|
61
|
-
return files;
|
|
62
|
-
}
|
|
63
45
|
async buildWorkflowIndex() {
|
|
64
|
-
const allJsonFiles = await
|
|
46
|
+
const allJsonFiles = await (0, raw_workflow_file_scanner_1.findWorkflowJsonFiles)(this.baseDirReal);
|
|
65
47
|
const relativeFiles = allJsonFiles.map(f => path_1.default.relative(this.baseDirReal, f));
|
|
66
48
|
const index = new Map();
|
|
67
49
|
const idToFiles = new Map();
|
|
@@ -93,22 +75,19 @@ class FileWorkflowStorage {
|
|
|
93
75
|
continue;
|
|
94
76
|
}
|
|
95
77
|
}
|
|
78
|
+
const flags = {
|
|
79
|
+
v2Tools: this.featureFlags.isEnabled('v2Tools'),
|
|
80
|
+
agenticRoutines: this.featureFlags.isEnabled('agenticRoutines'),
|
|
81
|
+
};
|
|
96
82
|
for (const [id, files] of idToFiles) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
else if (isAgenticEnabled && agenticEntry) {
|
|
107
|
-
selected = agenticEntry;
|
|
108
|
-
}
|
|
109
|
-
else if (standardEntry) {
|
|
110
|
-
selected = standardEntry;
|
|
111
|
-
}
|
|
83
|
+
const candidates = files.map(f => ({
|
|
84
|
+
variantKind: f.file.includes('.v2.') ? 'v2'
|
|
85
|
+
: f.file.includes('.agentic.') ? 'agentic'
|
|
86
|
+
: 'standard',
|
|
87
|
+
identifier: f.file,
|
|
88
|
+
}));
|
|
89
|
+
const selection = (0, workflow_resolution_1.selectVariant)(candidates, flags);
|
|
90
|
+
const selected = files.find(f => f.file === selection.selectedIdentifier) ?? files[0];
|
|
112
91
|
const filePath = path_1.default.resolve(this.baseDirReal, selected.file);
|
|
113
92
|
const stats = (0, fs_1.statSync)(filePath);
|
|
114
93
|
index.set(id, {
|
|
@@ -19,6 +19,7 @@ export declare class SchemaValidatingCompositeWorkflowStorage implements ICompos
|
|
|
19
19
|
constructor(inner: ICompositeWorkflowStorage);
|
|
20
20
|
private validateDefinition;
|
|
21
21
|
getSources(): readonly WorkflowSource[];
|
|
22
|
+
getStorageInstances(): readonly IWorkflowStorage[];
|
|
22
23
|
loadAllWorkflows(): Promise<readonly Workflow[]>;
|
|
23
24
|
getWorkflowById(id: string): Promise<Workflow | null>;
|
|
24
25
|
listWorkflowSummaries(): Promise<readonly WorkflowSummary[]>;
|
|
@@ -7,8 +7,13 @@ exports.SchemaValidatingCompositeWorkflowStorage = exports.SchemaValidatingWorkf
|
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const ajv_1 = __importDefault(require("ajv"));
|
|
10
|
+
const workflow_1 = require("../../types/workflow");
|
|
10
11
|
const error_handler_1 = require("../../core/error-handler");
|
|
11
12
|
const workflow_id_policy_1 = require("../../domain/workflow-id-policy");
|
|
13
|
+
const VALIDATION_ERROR_PREFIX = '[ValidationError]';
|
|
14
|
+
function reportValidationFailure(workflowId, sourceKind, error) {
|
|
15
|
+
console.error(`${VALIDATION_ERROR_PREFIX} ${sourceKind}/${workflowId}: ${error}`);
|
|
16
|
+
}
|
|
12
17
|
class SchemaValidatingWorkflowStorage {
|
|
13
18
|
constructor(inner) {
|
|
14
19
|
this.inner = inner;
|
|
@@ -40,7 +45,7 @@ class SchemaValidatingWorkflowStorage {
|
|
|
40
45
|
}
|
|
41
46
|
}
|
|
42
47
|
catch (err) {
|
|
43
|
-
|
|
48
|
+
reportValidationFailure(workflow.definition.id, workflow.source.kind, err instanceof Error ? err.message : String(err));
|
|
44
49
|
}
|
|
45
50
|
}
|
|
46
51
|
return validWorkflows;
|
|
@@ -55,12 +60,13 @@ class SchemaValidatingWorkflowStorage {
|
|
|
55
60
|
return workflow;
|
|
56
61
|
}
|
|
57
62
|
catch (err) {
|
|
58
|
-
|
|
63
|
+
reportValidationFailure(id, workflow.source.kind, err instanceof Error ? err.message : String(err));
|
|
59
64
|
return null;
|
|
60
65
|
}
|
|
61
66
|
}
|
|
62
67
|
async listWorkflowSummaries() {
|
|
63
|
-
|
|
68
|
+
const validated = await this.loadAllWorkflows();
|
|
69
|
+
return validated.map(workflow_1.toWorkflowSummary);
|
|
64
70
|
}
|
|
65
71
|
async save(definition) {
|
|
66
72
|
this.validateDefinition(definition, this.source.kind);
|
|
@@ -92,6 +98,9 @@ class SchemaValidatingCompositeWorkflowStorage {
|
|
|
92
98
|
getSources() {
|
|
93
99
|
return this.inner.getSources();
|
|
94
100
|
}
|
|
101
|
+
getStorageInstances() {
|
|
102
|
+
return this.inner.getStorageInstances();
|
|
103
|
+
}
|
|
95
104
|
async loadAllWorkflows() {
|
|
96
105
|
const workflows = await this.inner.loadAllWorkflows();
|
|
97
106
|
const validWorkflows = [];
|
|
@@ -102,7 +111,7 @@ class SchemaValidatingCompositeWorkflowStorage {
|
|
|
102
111
|
}
|
|
103
112
|
}
|
|
104
113
|
catch (err) {
|
|
105
|
-
|
|
114
|
+
reportValidationFailure(workflow.definition.id, workflow.source.kind, err instanceof Error ? err.message : String(err));
|
|
106
115
|
}
|
|
107
116
|
}
|
|
108
117
|
return validWorkflows;
|
|
@@ -116,12 +125,13 @@ class SchemaValidatingCompositeWorkflowStorage {
|
|
|
116
125
|
return workflow;
|
|
117
126
|
}
|
|
118
127
|
catch (err) {
|
|
119
|
-
|
|
128
|
+
reportValidationFailure(id, workflow.source.kind, err instanceof Error ? err.message : String(err));
|
|
120
129
|
return null;
|
|
121
130
|
}
|
|
122
131
|
}
|
|
123
132
|
async listWorkflowSummaries() {
|
|
124
|
-
|
|
133
|
+
const validated = await this.loadAllWorkflows();
|
|
134
|
+
return validated.map(workflow_1.toWorkflowSummary);
|
|
125
135
|
}
|
|
126
136
|
async save(definition) {
|
|
127
137
|
this.validateDefinition(definition, 'project');
|