@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.
Files changed (57) hide show
  1. package/dist/application/services/compiler/feature-registry.js +18 -0
  2. package/dist/application/services/compiler/prompt-blocks.js +2 -1
  3. package/dist/application/services/compiler/ref-registry.js +36 -0
  4. package/dist/application/services/validation-engine.d.ts +1 -0
  5. package/dist/application/services/validation-engine.js +14 -0
  6. package/dist/application/services/workflow-validation-pipeline.d.ts +96 -0
  7. package/dist/application/services/workflow-validation-pipeline.js +94 -0
  8. package/dist/application/use-cases/raw-workflow-file-scanner.d.ts +18 -0
  9. package/dist/application/use-cases/raw-workflow-file-scanner.js +91 -0
  10. package/dist/application/use-cases/validate-workflow-file.d.ts +17 -0
  11. package/dist/application/use-cases/validate-workflow-file.js +96 -0
  12. package/dist/application/use-cases/validate-workflow-json.d.ts +2 -1
  13. package/dist/application/use-cases/validate-workflow-json.js +67 -13
  14. package/dist/application/use-cases/validate-workflow-registry.d.ts +72 -0
  15. package/dist/application/use-cases/validate-workflow-registry.js +215 -0
  16. package/dist/application/validation.d.ts +4 -0
  17. package/dist/application/validation.js +16 -0
  18. package/dist/cli/commands/validate.js +15 -0
  19. package/dist/cli.js +10 -1
  20. package/dist/infrastructure/storage/caching-workflow-storage.d.ts +1 -0
  21. package/dist/infrastructure/storage/caching-workflow-storage.js +3 -0
  22. package/dist/infrastructure/storage/enhanced-multi-source-workflow-storage.d.ts +2 -1
  23. package/dist/infrastructure/storage/enhanced-multi-source-workflow-storage.js +8 -21
  24. package/dist/infrastructure/storage/file-workflow-storage.d.ts +0 -1
  25. package/dist/infrastructure/storage/file-workflow-storage.js +15 -36
  26. package/dist/infrastructure/storage/schema-validating-workflow-storage.d.ts +1 -0
  27. package/dist/infrastructure/storage/schema-validating-workflow-storage.js +16 -6
  28. package/dist/infrastructure/storage/workflow-resolution.d.ts +62 -0
  29. package/dist/infrastructure/storage/workflow-resolution.js +150 -0
  30. package/dist/manifest.json +140 -68
  31. package/dist/mcp/handlers/v2-execution/replay.d.ts +1 -1
  32. package/dist/mcp/handlers/v2-execution/replay.js +37 -21
  33. package/dist/mcp/handlers/v2-execution/start.js +35 -13
  34. package/dist/mcp/handlers/v2-execution-helpers.d.ts +9 -11
  35. package/dist/mcp/handlers/v2-execution-helpers.js +6 -18
  36. package/dist/mcp/output-schemas.d.ts +20 -20
  37. package/dist/mcp/server.d.ts +18 -0
  38. package/dist/mcp/server.js +8 -1
  39. package/dist/mcp/transports/http-entry.d.ts +1 -0
  40. package/dist/mcp/transports/http-entry.js +87 -0
  41. package/dist/mcp/transports/http-listener.d.ts +9 -0
  42. package/dist/mcp/transports/http-listener.js +64 -0
  43. package/dist/mcp/transports/stdio-entry.d.ts +1 -0
  44. package/dist/mcp/transports/stdio-entry.js +92 -0
  45. package/dist/mcp/transports/transport-mode.d.ts +7 -0
  46. package/dist/mcp/transports/transport-mode.js +18 -0
  47. package/dist/mcp-server.js +21 -5
  48. package/dist/types/storage.d.ts +1 -0
  49. package/dist/v2/durable-core/domain/prompt-renderer.js +13 -7
  50. package/dist/v2/durable-core/domain/start-construction.d.ts +22 -0
  51. package/dist/v2/durable-core/domain/start-construction.js +31 -0
  52. package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +8 -8
  53. package/dist/v2/read-only/v1-to-v2-shim.d.ts +5 -0
  54. package/dist/v2/read-only/v1-to-v2-shim.js +18 -0
  55. package/package.json +3 -2
  56. package/workflows/bug-investigation.agentic.v2.json +134 -0
  57. 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 validateWorkflowFile = (0, validate_workflow_file_js_1.createValidateWorkflowFileUseCase)({
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 allWorkflows = [];
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
- for (const workflow of workflows) {
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
- return allWorkflows;
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 this.findJsonFiles(this.baseDirReal);
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
- let selected = files[0];
98
- const isV2Enabled = this.featureFlags.isEnabled('v2Tools');
99
- const isAgenticEnabled = this.featureFlags.isEnabled('agenticRoutines');
100
- const v2Entry = files.find(f => f.file.includes('.v2.'));
101
- const agenticEntry = files.find(f => f.file.includes('.agentic.'));
102
- const standardEntry = files.find(f => !f.file.includes('.agentic.') && !f.file.includes('.v2.'));
103
- if (isV2Enabled && v2Entry) {
104
- selected = v2Entry;
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
- console.error(`[SchemaValidation] Workflow '${workflow.definition.id}' failed validation:`, err instanceof Error ? err.message : err);
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
- console.error(`[SchemaValidation] Workflow '${id}' failed validation:`, err instanceof Error ? err.message : err);
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
- return this.inner.listWorkflowSummaries();
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
- console.error(`[SchemaValidation] Workflow '${workflow.definition.id}' failed validation:`, err instanceof Error ? err.message : err);
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
- console.error(`[SchemaValidation] Workflow '${id}' failed validation:`, err instanceof Error ? err.message : err);
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
- return this.inner.listWorkflowSummaries();
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');