@exaudeus/workrail 0.12.0 → 0.14.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 (98) hide show
  1. package/dist/application/services/enhanced-loop-validator.js +3 -3
  2. package/dist/application/services/step-output-decoder.d.ts +6 -0
  3. package/dist/application/services/step-output-decoder.js +49 -0
  4. package/dist/application/services/validation-engine.d.ts +9 -0
  5. package/dist/application/services/validation-engine.js +142 -18
  6. package/dist/application/services/workflow-interpreter.d.ts +1 -1
  7. package/dist/application/services/workflow-interpreter.js +147 -81
  8. package/dist/application/services/workflow-service.d.ts +2 -0
  9. package/dist/application/services/workflow-service.js +9 -4
  10. package/dist/application/use-cases/validate-step-output.d.ts +2 -0
  11. package/dist/di/container.js +19 -3
  12. package/dist/di/tokens.d.ts +3 -0
  13. package/dist/di/tokens.js +3 -0
  14. package/dist/domain/execution/state.d.ts +6 -6
  15. package/dist/domain/workflow-id-policy.d.ts +17 -0
  16. package/dist/domain/workflow-id-policy.js +57 -0
  17. package/dist/infrastructure/storage/enhanced-multi-source-workflow-storage.js +33 -6
  18. package/dist/infrastructure/storage/file-workflow-storage.js +3 -1
  19. package/dist/infrastructure/storage/schema-validating-workflow-storage.js +13 -8
  20. package/dist/manifest.json +261 -109
  21. package/dist/mcp/handlers/v2-execution.js +62 -79
  22. package/dist/mcp/handlers/v2-workflow.js +14 -9
  23. package/dist/mcp/server.js +53 -48
  24. package/dist/mcp/tool-descriptions.js +15 -15
  25. package/dist/mcp/tools.js +14 -14
  26. package/dist/mcp/types/tool-description-types.d.ts +1 -1
  27. package/dist/mcp/types/tool-description-types.js +5 -5
  28. package/dist/mcp/types.d.ts +2 -0
  29. package/dist/v2/durable-core/constants.d.ts +15 -0
  30. package/dist/v2/durable-core/constants.js +18 -0
  31. package/dist/v2/durable-core/domain/ack-advance-append-plan.d.ts +32 -0
  32. package/dist/v2/durable-core/domain/ack-advance-append-plan.js +95 -0
  33. package/dist/v2/durable-core/domain/loop-runtime.d.ts +50 -0
  34. package/dist/v2/durable-core/domain/loop-runtime.js +95 -0
  35. package/dist/v2/durable-core/domain/notes-markdown.d.ts +4 -0
  36. package/dist/v2/durable-core/domain/notes-markdown.js +46 -0
  37. package/dist/v2/durable-core/domain/outputs.d.ts +12 -0
  38. package/dist/v2/durable-core/domain/outputs.js +18 -0
  39. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +113 -113
  40. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.js +11 -10
  41. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +7129 -0
  42. package/dist/v2/durable-core/schemas/export-bundle/index.js +82 -0
  43. package/dist/v2/durable-core/schemas/lib/decision-trace-ref.d.ts +80 -0
  44. package/dist/v2/durable-core/schemas/lib/decision-trace-ref.js +38 -0
  45. package/dist/v2/durable-core/schemas/lib/dedupe-key.d.ts +8 -0
  46. package/dist/v2/durable-core/schemas/lib/dedupe-key.js +28 -0
  47. package/dist/v2/durable-core/schemas/lib/utf8-bounded-string.d.ts +6 -0
  48. package/dist/v2/durable-core/schemas/lib/utf8-bounded-string.js +12 -0
  49. package/dist/v2/durable-core/schemas/session/events.d.ts +158 -12
  50. package/dist/v2/durable-core/schemas/session/events.js +47 -20
  51. package/dist/v2/durable-core/schemas/session/manifest.d.ts +1 -1
  52. package/dist/v2/durable-core/schemas/session/manifest.js +6 -1
  53. package/dist/v2/durable-core/schemas/session/preferences.d.ts +5 -0
  54. package/dist/v2/durable-core/schemas/session/preferences.js +6 -0
  55. package/dist/v2/durable-core/schemas/session/session-health.d.ts +3 -0
  56. package/dist/v2/durable-core/tokens/index.d.ts +0 -1
  57. package/dist/v2/durable-core/tokens/index.js +1 -4
  58. package/dist/v2/durable-core/tokens/token-codec.d.ts +3 -2
  59. package/dist/v2/durable-core/tokens/token-codec.js +12 -6
  60. package/dist/v2/durable-core/tokens/token-signer.d.ts +3 -2
  61. package/dist/v2/durable-core/tokens/token-signer.js +8 -9
  62. package/dist/v2/infra/local/base64url/index.d.ts +5 -0
  63. package/dist/v2/infra/local/base64url/index.js +48 -0
  64. package/dist/v2/infra/local/fs/index.js +8 -4
  65. package/dist/v2/infra/local/keyring/index.d.ts +5 -1
  66. package/dist/v2/infra/local/keyring/index.js +41 -32
  67. package/dist/v2/infra/local/pinned-workflow-store/index.d.ts +3 -1
  68. package/dist/v2/infra/local/pinned-workflow-store/index.js +50 -62
  69. package/dist/v2/infra/local/random-entropy/index.d.ts +4 -0
  70. package/dist/v2/infra/local/random-entropy/index.js +10 -0
  71. package/dist/v2/infra/local/session-lock/index.d.ts +3 -1
  72. package/dist/v2/infra/local/session-lock/index.js +4 -3
  73. package/dist/v2/infra/local/session-store/index.js +26 -4
  74. package/dist/v2/infra/local/snapshot-store/index.js +20 -25
  75. package/dist/v2/infra/local/time-clock/index.d.ts +5 -0
  76. package/dist/v2/infra/local/time-clock/index.js +12 -0
  77. package/dist/v2/infra/local/utf8/index.d.ts +5 -0
  78. package/dist/v2/infra/local/utf8/index.js +12 -0
  79. package/dist/v2/ports/base64url.port.d.ts +12 -0
  80. package/dist/v2/ports/base64url.port.js +2 -0
  81. package/dist/v2/ports/random-entropy.port.d.ts +3 -0
  82. package/dist/v2/ports/random-entropy.port.js +2 -0
  83. package/dist/v2/ports/time-clock.port.d.ts +4 -0
  84. package/dist/v2/ports/time-clock.port.js +2 -0
  85. package/dist/v2/ports/utf8.port.d.ts +3 -0
  86. package/dist/v2/ports/utf8.port.js +2 -0
  87. package/dist/v2/projections/node-outputs.js +28 -11
  88. package/dist/v2/projections/preferences.d.ts +1 -2
  89. package/dist/v2/projections/preferences.js +11 -4
  90. package/dist/v2/projections/run-dag.js +40 -28
  91. package/dist/v2/projections/run-status-signals.d.ts +1 -2
  92. package/dist/v2/usecases/execution-session-gate.js +33 -34
  93. package/package.json +3 -1
  94. package/spec/workflow.schema.json +2 -2
  95. package/workflows/coding-task-workflow-agentic.json +213 -50
  96. package/workflows/relocation-workflow-us.json +430 -0
  97. package/dist/v2/durable-core/tokens/base64url.d.ts +0 -7
  98. package/dist/v2/durable-core/tokens/base64url.js +0 -16
@@ -102,10 +102,10 @@ let EnhancedLoopValidator = class EnhancedLoopValidator {
102
102
  }
103
103
  getKnownLoopVariables(loopStep) {
104
104
  const vars = new Set();
105
- vars.add(loopStep.loop.iterationVar || 'iteration');
105
+ vars.add(loopStep.loop.iterationVar || 'currentIteration');
106
106
  if (loopStep.loop.type === 'forEach') {
107
- vars.add(loopStep.loop.itemVar || 'item');
108
- vars.add(loopStep.loop.indexVar || 'index');
107
+ vars.add(loopStep.loop.itemVar || 'currentItem');
108
+ vars.add(loopStep.loop.indexVar || 'currentIndex');
109
109
  }
110
110
  vars.add('context');
111
111
  vars.add('workflowId');
@@ -0,0 +1,6 @@
1
+ export interface SchemaDecodeResult {
2
+ readonly value: unknown;
3
+ readonly warnings: readonly string[];
4
+ }
5
+ export declare function decodeForSchemaValidation(output: string, schema: unknown): SchemaDecodeResult | null;
6
+ export declare function schemaExpectsObjectOrArray(schema: unknown): boolean;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decodeForSchemaValidation = decodeForSchemaValidation;
4
+ exports.schemaExpectsObjectOrArray = schemaExpectsObjectOrArray;
5
+ const COERCION_WARNING = 'Coerced double-encoded JSON (JSON string containing JSON) into an object/array for schema validation.';
6
+ function decodeForSchemaValidation(output, schema) {
7
+ let parsed;
8
+ try {
9
+ parsed = JSON.parse(output);
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ if (typeof parsed === 'string' && schemaExpectsObjectOrArray(schema)) {
15
+ const inner = parsed.trim();
16
+ if (inner.startsWith('{') || inner.startsWith('[')) {
17
+ try {
18
+ const innerParsed = JSON.parse(inner);
19
+ if (innerParsed !== null && typeof innerParsed === 'object') {
20
+ return { value: innerParsed, warnings: [COERCION_WARNING] };
21
+ }
22
+ }
23
+ catch {
24
+ }
25
+ }
26
+ }
27
+ return { value: parsed, warnings: [] };
28
+ }
29
+ function schemaExpectsObjectOrArray(schema) {
30
+ if (schema == null || typeof schema !== 'object')
31
+ return false;
32
+ const s = schema;
33
+ const t = s.type;
34
+ if (t === 'object' || t === 'array')
35
+ return true;
36
+ if (Array.isArray(t) && (t.includes('object') || t.includes('array')))
37
+ return true;
38
+ if (s.properties && typeof s.properties === 'object')
39
+ return true;
40
+ if (Array.isArray(s.required) && s.required.length > 0)
41
+ return true;
42
+ const combos = ['oneOf', 'anyOf', 'allOf'];
43
+ for (const k of combos) {
44
+ if (Array.isArray(s[k]) && s[k].some((child) => schemaExpectsObjectOrArray(child))) {
45
+ return true;
46
+ }
47
+ }
48
+ return false;
49
+ }
@@ -7,6 +7,9 @@ export declare class ValidationEngine {
7
7
  private ajv;
8
8
  private schemaCache;
9
9
  private enhancedLoopValidator;
10
+ private static readonly DEFAULT_FAILURE_SUGGESTION;
11
+ private static readonly JSON_OBJECT_NOT_STRING_SUGGESTION;
12
+ private static readonly JSON_STRING_CONTAINS_JSON_SUGGESTION;
10
13
  constructor(enhancedLoopValidator: EnhancedLoopValidator);
11
14
  private compileSchema;
12
15
  private evaluateCriteria;
@@ -17,8 +20,14 @@ export declare class ValidationEngine {
17
20
  private isValidationComposition;
18
21
  validate(output: string, criteria: ValidationCriteria, context?: ConditionContext): Promise<ValidationResult>;
19
22
  private evaluateRule;
23
+ private looksLikeQuotedJsonSnippet;
24
+ private maybePushJsonObjectNotStringSuggestion;
25
+ private uniqueSuggestions;
26
+ private formatDefaultContainsMessage;
20
27
  validateLoopStep(step: LoopStepDefinition, workflow: Workflow): ValidationResult;
21
28
  validateWorkflow(workflow: Workflow): ValidationResult;
29
+ private collectQuotedJsonValidationMessageWarnings;
30
+ private collectValidationRuleMessages;
22
31
  private isValidVariableName;
23
32
  private validateStepFunctionCalls;
24
33
  private isArgTypeValid;
@@ -14,6 +14,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
14
14
  var __importDefault = (this && this.__importDefault) || function (mod) {
15
15
  return (mod && mod.__esModule) ? mod : { "default": mod };
16
16
  };
17
+ var ValidationEngine_1;
17
18
  Object.defineProperty(exports, "__esModule", { value: true });
18
19
  exports.ValidationEngine = void 0;
19
20
  const tsyringe_1 = require("tsyringe");
@@ -22,7 +23,8 @@ const condition_evaluator_1 = require("../../utils/condition-evaluator");
22
23
  const ajv_1 = __importDefault(require("ajv"));
23
24
  const workflow_definition_1 = require("../../types/workflow-definition");
24
25
  const enhanced_loop_validator_1 = require("./enhanced-loop-validator");
25
- let ValidationEngine = class ValidationEngine {
26
+ const step_output_decoder_1 = require("./step-output-decoder");
27
+ let ValidationEngine = ValidationEngine_1 = class ValidationEngine {
26
28
  constructor(enhancedLoopValidator) {
27
29
  this.schemaCache = new Map();
28
30
  this.ajv = new ajv_1.default({ allErrors: true });
@@ -52,7 +54,8 @@ let ValidationEngine = class ValidationEngine {
52
54
  return {
53
55
  valid: compositionResult,
54
56
  issues: compositionResult ? [] : ['Validation composition failed'],
55
- suggestions: compositionResult ? [] : ['Review validation criteria and adjust output accordingly.']
57
+ suggestions: compositionResult ? [] : ['Review validation criteria and adjust output accordingly.'],
58
+ warnings: undefined,
56
59
  };
57
60
  }
58
61
  throw new error_handler_1.ValidationError('Invalid validation criteria format');
@@ -66,12 +69,14 @@ let ValidationEngine = class ValidationEngine {
66
69
  }
67
70
  evaluateRuleArray(output, rules, context) {
68
71
  const issues = [];
72
+ const suggestions = [];
73
+ const warnings = [];
69
74
  for (const rule of rules) {
70
75
  try {
71
76
  if (rule.condition && !(0, condition_evaluator_1.evaluateCondition)(rule.condition, context)) {
72
77
  continue;
73
78
  }
74
- this.evaluateRule(output, rule, issues);
79
+ this.evaluateRule(output, rule, issues, suggestions, warnings);
75
80
  }
76
81
  catch (error) {
77
82
  if (error instanceof error_handler_1.ValidationError) {
@@ -83,7 +88,8 @@ let ValidationEngine = class ValidationEngine {
83
88
  return {
84
89
  valid: issues.length === 0,
85
90
  issues,
86
- suggestions: issues.length > 0 ? ['Review validation criteria and adjust output accordingly.'] : []
91
+ suggestions: issues.length > 0 ? this.uniqueSuggestions([...suggestions, ValidationEngine_1.DEFAULT_FAILURE_SUGGESTION]) : [],
92
+ warnings: warnings.length > 0 ? this.uniqueSuggestions(warnings) : undefined,
87
93
  };
88
94
  }
89
95
  evaluateComposition(output, composition, context) {
@@ -104,7 +110,9 @@ let ValidationEngine = class ValidationEngine {
104
110
  return true;
105
111
  }
106
112
  const issues = [];
107
- this.evaluateRule(output, criteria, issues);
113
+ const suggestions = [];
114
+ const warnings = [];
115
+ this.evaluateRule(output, criteria, issues, suggestions, warnings);
108
116
  return issues.length === 0;
109
117
  }
110
118
  if (this.isValidationComposition(criteria)) {
@@ -133,18 +141,19 @@ let ValidationEngine = class ValidationEngine {
133
141
  suggestions: issues.length > 0 ? ['Provide valid output content.'] : []
134
142
  };
135
143
  }
136
- const isValid = this.evaluateCriteria(output, criteria, context || {});
137
- if (!isValid.valid) {
138
- issues.push(...isValid.issues);
144
+ const evaluation = this.evaluateCriteria(output, criteria, context || {});
145
+ if (!evaluation.valid) {
146
+ issues.push(...evaluation.issues);
139
147
  }
140
148
  const valid = issues.length === 0;
141
149
  return {
142
150
  valid,
143
151
  issues,
144
- suggestions: valid ? [] : ['Review validation criteria and adjust output accordingly.']
152
+ suggestions: valid ? [] : this.uniqueSuggestions([...evaluation.suggestions, ValidationEngine_1.DEFAULT_FAILURE_SUGGESTION]),
153
+ warnings: evaluation.warnings,
145
154
  };
146
155
  }
147
- evaluateRule(output, rule, issues) {
156
+ evaluateRule(output, rule, issues, suggestions, warnings) {
148
157
  if (typeof rule === 'string') {
149
158
  const re = new RegExp(rule);
150
159
  if (!re.test(output)) {
@@ -157,7 +166,10 @@ let ValidationEngine = class ValidationEngine {
157
166
  case 'contains': {
158
167
  const value = rule.value;
159
168
  if (typeof value !== 'string' || !output.includes(value)) {
160
- issues.push(rule.message || `Output must include "${value}"`);
169
+ issues.push(rule.message || this.formatDefaultContainsMessage(value));
170
+ if (this.looksLikeQuotedJsonSnippet(rule.message)) {
171
+ this.maybePushJsonObjectNotStringSuggestion(suggestions);
172
+ }
161
173
  }
162
174
  break;
163
175
  }
@@ -166,6 +178,9 @@ let ValidationEngine = class ValidationEngine {
166
178
  const re = new RegExp(rule.pattern, rule.flags || undefined);
167
179
  if (!re.test(output)) {
168
180
  issues.push(rule.message || `Pattern mismatch: ${rule.pattern}`);
181
+ if (this.looksLikeQuotedJsonSnippet(rule.message)) {
182
+ this.maybePushJsonObjectNotStringSuggestion(suggestions);
183
+ }
169
184
  }
170
185
  }
171
186
  catch {
@@ -177,31 +192,50 @@ let ValidationEngine = class ValidationEngine {
177
192
  const { min, max } = rule;
178
193
  if (typeof min === 'number' && output.length < min) {
179
194
  issues.push(rule.message || `Output shorter than minimum length ${min}`);
195
+ if (this.looksLikeQuotedJsonSnippet(rule.message)) {
196
+ this.maybePushJsonObjectNotStringSuggestion(suggestions);
197
+ }
180
198
  }
181
199
  if (typeof max === 'number' && output.length > max) {
182
200
  issues.push(rule.message || `Output exceeds maximum length ${max}`);
201
+ if (this.looksLikeQuotedJsonSnippet(rule.message)) {
202
+ this.maybePushJsonObjectNotStringSuggestion(suggestions);
203
+ }
183
204
  }
184
205
  break;
185
206
  }
186
207
  case 'schema': {
187
208
  if (!rule.schema) {
188
209
  issues.push(rule.message || 'Schema validation rule requires a schema property');
210
+ if (this.looksLikeQuotedJsonSnippet(rule.message)) {
211
+ this.maybePushJsonObjectNotStringSuggestion(suggestions);
212
+ }
189
213
  break;
190
214
  }
191
215
  try {
192
- let parsedOutput;
193
- try {
194
- parsedOutput = JSON.parse(output);
195
- }
196
- catch (parseError) {
216
+ const decoded = (0, step_output_decoder_1.decodeForSchemaValidation)(output, rule.schema);
217
+ if (!decoded) {
197
218
  issues.push(rule.message || 'Output is not valid JSON for schema validation');
219
+ if (this.looksLikeQuotedJsonSnippet(rule.message)) {
220
+ this.maybePushJsonObjectNotStringSuggestion(suggestions);
221
+ }
198
222
  break;
199
223
  }
224
+ if (decoded.warnings.length > 0) {
225
+ warnings.push(...decoded.warnings);
226
+ this.maybePushJsonObjectNotStringSuggestion(suggestions);
227
+ if (!suggestions.includes(ValidationEngine_1.JSON_STRING_CONTAINS_JSON_SUGGESTION)) {
228
+ suggestions.push(ValidationEngine_1.JSON_STRING_CONTAINS_JSON_SUGGESTION);
229
+ }
230
+ }
200
231
  const validate = this.compileSchema(rule.schema);
201
- const isValid = validate(parsedOutput);
232
+ const isValid = validate(decoded.value);
202
233
  if (!isValid) {
203
234
  const errorMessages = validate.errors?.map((error) => `Validation Error at '${error.instancePath}': ${error.message}`) || ['Schema validation failed'];
204
235
  issues.push(rule.message || errorMessages.join('; '));
236
+ if (this.looksLikeQuotedJsonSnippet(rule.message)) {
237
+ this.maybePushJsonObjectNotStringSuggestion(suggestions);
238
+ }
205
239
  }
206
240
  }
207
241
  catch (error) {
@@ -219,6 +253,50 @@ let ValidationEngine = class ValidationEngine {
219
253
  }
220
254
  throw new error_handler_1.ValidationError('Invalid validationCriteria format.');
221
255
  }
256
+ looksLikeQuotedJsonSnippet(message) {
257
+ if (typeof message !== 'string')
258
+ return false;
259
+ const m = message.trim();
260
+ if (m.length === 0)
261
+ return false;
262
+ if (m.includes('\\"{') || m.includes('\\"['))
263
+ return true;
264
+ if (m.includes('"{') || m.includes("'{") || m.includes('`{'))
265
+ return true;
266
+ if (m.includes('"[') || m.includes("'[") || m.includes('`['))
267
+ return true;
268
+ return /["'`]\s*[\[{]/.test(m);
269
+ }
270
+ maybePushJsonObjectNotStringSuggestion(suggestions) {
271
+ if (suggestions.includes(ValidationEngine_1.JSON_OBJECT_NOT_STRING_SUGGESTION))
272
+ return;
273
+ suggestions.push(ValidationEngine_1.JSON_OBJECT_NOT_STRING_SUGGESTION);
274
+ }
275
+ uniqueSuggestions(values) {
276
+ const out = [];
277
+ for (const v of values) {
278
+ if (!v)
279
+ continue;
280
+ if (out.includes(v))
281
+ continue;
282
+ out.push(v);
283
+ }
284
+ return out;
285
+ }
286
+ formatDefaultContainsMessage(value) {
287
+ if (typeof value !== 'string') {
288
+ return 'Output must include the required substring.';
289
+ }
290
+ const snippet = value.trim();
291
+ const looksJsonLike = snippet.startsWith('{') ||
292
+ snippet.startsWith('[') ||
293
+ snippet.includes('"') ||
294
+ snippet.includes(':');
295
+ if (looksJsonLike) {
296
+ return `Output must include this literal JSON snippet (not a JSON-encoded string): ${snippet}`;
297
+ }
298
+ return `Output must include: ${snippet}`;
299
+ }
222
300
  validateLoopStep(step, workflow) {
223
301
  const enhancedResult = this.enhancedLoopValidator.validateLoopStep(step);
224
302
  const issues = [];
@@ -366,6 +444,7 @@ let ValidationEngine = class ValidationEngine {
366
444
  if (loopResult.info) {
367
445
  info.push(...loopResult.info.map(i => `Step '${step.id}': ${i}`));
368
446
  }
447
+ this.collectQuotedJsonValidationMessageWarnings(step, `Step '${step.id}'`, warnings);
369
448
  }
370
449
  else {
371
450
  if (!step.id) {
@@ -377,6 +456,7 @@ let ValidationEngine = class ValidationEngine {
377
456
  if (!step.prompt) {
378
457
  issues.push(`Step '${step.id}' missing required prompt`);
379
458
  }
459
+ this.collectQuotedJsonValidationMessageWarnings(step, `Step '${step.id}'`, warnings);
380
460
  const callValidation = this.validateStepFunctionCalls(step, workflow.definition.functionDefinitions || []);
381
461
  if (!callValidation.valid) {
382
462
  issues.push(...callValidation.issues.map(i => `Step '${step.id}': ${i}`));
@@ -413,6 +493,47 @@ let ValidationEngine = class ValidationEngine {
413
493
  info: info.length > 0 ? info : undefined
414
494
  };
415
495
  }
496
+ collectQuotedJsonValidationMessageWarnings(stepLike, prefix, warnings) {
497
+ const criteria = stepLike?.validationCriteria;
498
+ if (!criteria)
499
+ return;
500
+ const msgs = this.collectValidationRuleMessages(criteria);
501
+ for (const m of msgs) {
502
+ if (!this.looksLikeQuotedJsonSnippet(m))
503
+ continue;
504
+ warnings.push(`${prefix}: validationCriteria.message appears to contain a quoted JSON snippet. ` +
505
+ `Agents often respond with a JSON-encoded string (escaping quotes) instead of a JSON object. ` +
506
+ `Prefer describing the required object/keys without quoting the JSON (or use schema validation).`);
507
+ return;
508
+ }
509
+ }
510
+ collectValidationRuleMessages(criteria) {
511
+ if (Array.isArray(criteria)) {
512
+ const msgs = [];
513
+ for (const r of criteria) {
514
+ if (r && typeof r === 'object' && typeof r.message === 'string')
515
+ msgs.push(r.message);
516
+ }
517
+ return msgs;
518
+ }
519
+ if (this.isValidationRule(criteria)) {
520
+ return typeof criteria.message === 'string' ? [String(criteria.message)] : [];
521
+ }
522
+ if (this.isValidationComposition(criteria)) {
523
+ const msgs = [];
524
+ const c = criteria;
525
+ if (Array.isArray(c.and))
526
+ for (const child of c.and)
527
+ msgs.push(...this.collectValidationRuleMessages(child));
528
+ if (Array.isArray(c.or))
529
+ for (const child of c.or)
530
+ msgs.push(...this.collectValidationRuleMessages(child));
531
+ if (c.not)
532
+ msgs.push(...this.collectValidationRuleMessages(c.not));
533
+ return msgs;
534
+ }
535
+ return [];
536
+ }
416
537
  isValidVariableName(name) {
417
538
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
418
539
  }
@@ -472,7 +593,10 @@ let ValidationEngine = class ValidationEngine {
472
593
  }
473
594
  };
474
595
  exports.ValidationEngine = ValidationEngine;
475
- exports.ValidationEngine = ValidationEngine = __decorate([
596
+ ValidationEngine.DEFAULT_FAILURE_SUGGESTION = 'Review validation criteria and adjust output accordingly.';
597
+ ValidationEngine.JSON_OBJECT_NOT_STRING_SUGGESTION = 'If you are returning JSON, return an object/array directly (not a JSON-encoded string). Do not wrap it in quotes or escape quotes.';
598
+ ValidationEngine.JSON_STRING_CONTAINS_JSON_SUGGESTION = 'It looks like you returned a JSON string that itself contains JSON. Remove the outer quotes and return the JSON object/array directly.';
599
+ exports.ValidationEngine = ValidationEngine = ValidationEngine_1 = __decorate([
476
600
  (0, tsyringe_1.singleton)(),
477
601
  __param(0, (0, tsyringe_1.inject)(enhanced_loop_validator_1.EnhancedLoopValidator)),
478
602
  __metadata("design:paramtypes", [enhanced_loop_validator_1.EnhancedLoopValidator])
@@ -24,7 +24,7 @@ export declare class WorkflowInterpreter {
24
24
  private ensureRunning;
25
25
  private nextTopLevel;
26
26
  private nextInCurrentLoop;
27
- private shouldContinueLoop;
27
+ private projectLoopContextAtIteration;
28
28
  private projectLoopContext;
29
29
  private lookupStepInstance;
30
30
  private materializeStep;