@o-lang/olang 1.0.26 → 1.1.1
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/cli.js +125 -133
- package/package.json +1 -1
- package/src/parser.js +432 -94
- package/src/runtime.js +320 -42
package/src/runtime.js
CHANGED
|
@@ -92,6 +92,33 @@ class RuntimeAPI {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
// -----------------------------
|
|
96
|
+
// ✅ SEMANTIC ENFORCEMENT HELPER
|
|
97
|
+
// -----------------------------
|
|
98
|
+
_requireSemantic(symbol, stepType) {
|
|
99
|
+
const value = this.context[symbol];
|
|
100
|
+
if (value === undefined) {
|
|
101
|
+
const error = {
|
|
102
|
+
type: 'semantic_violation',
|
|
103
|
+
symbol: symbol,
|
|
104
|
+
expected: 'defined value',
|
|
105
|
+
used_by: stepType,
|
|
106
|
+
phase: 'execution'
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Emit semantic event (for observability)
|
|
110
|
+
this.emit('semantic_violation', error);
|
|
111
|
+
|
|
112
|
+
// Log as error (not warning)
|
|
113
|
+
if (this.verbose) {
|
|
114
|
+
console.error(`[O-Lang SEMANTIC] Missing required symbol "${symbol}" for ${stepType}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
95
122
|
// -----------------------------
|
|
96
123
|
// Parser/runtime warnings
|
|
97
124
|
// -----------------------------
|
|
@@ -145,6 +172,67 @@ class RuntimeAPI {
|
|
|
145
172
|
}
|
|
146
173
|
}
|
|
147
174
|
|
|
175
|
+
// -----------------------------
|
|
176
|
+
// ✅ ADDITION 1 — External Resolver Detection
|
|
177
|
+
// -----------------------------
|
|
178
|
+
_isExternalResolver(resolver) {
|
|
179
|
+
return Boolean(
|
|
180
|
+
resolver &&
|
|
181
|
+
resolver.manifest &&
|
|
182
|
+
typeof resolver.manifest === 'object' &&
|
|
183
|
+
typeof resolver.manifest.protocol === 'string' &&
|
|
184
|
+
resolver.manifest.protocol.startsWith('http')
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// -----------------------------
|
|
189
|
+
// ✅ ADDITION 2 — External Resolver Invocation (HTTP Enforcement)
|
|
190
|
+
// -----------------------------
|
|
191
|
+
async _callExternalResolver(resolver, action, context) {
|
|
192
|
+
const manifest = resolver.manifest;
|
|
193
|
+
const endpoint = manifest.endpoint;
|
|
194
|
+
const timeoutMs = manifest.timeout_ms || 30000;
|
|
195
|
+
|
|
196
|
+
const payload = {
|
|
197
|
+
action,
|
|
198
|
+
context,
|
|
199
|
+
resolver: resolver.resolverName,
|
|
200
|
+
workflow: context.workflow_name,
|
|
201
|
+
timestamp: new Date().toISOString()
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const controller = new AbortController();
|
|
205
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const res = await fetch(`${endpoint}/resolve`, {
|
|
209
|
+
method: 'POST',
|
|
210
|
+
headers: { 'Content-Type': 'application/json' },
|
|
211
|
+
body: JSON.stringify(payload),
|
|
212
|
+
signal: controller.signal
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (!res.ok) {
|
|
216
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const json = await res.json();
|
|
220
|
+
|
|
221
|
+
if (json?.error) {
|
|
222
|
+
throw new Error(json.error.message || 'External resolver error');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return json.result;
|
|
226
|
+
} catch (err) {
|
|
227
|
+
if (err.name === 'AbortError') {
|
|
228
|
+
throw new Error(`External resolver timeout after ${timeoutMs}ms`);
|
|
229
|
+
}
|
|
230
|
+
throw err;
|
|
231
|
+
} finally {
|
|
232
|
+
clearTimeout(timer);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
148
236
|
// -----------------------------
|
|
149
237
|
// Utilities
|
|
150
238
|
// -----------------------------
|
|
@@ -218,15 +306,40 @@ class RuntimeAPI {
|
|
|
218
306
|
async executeStep(step, agentResolver) {
|
|
219
307
|
const stepType = step.type;
|
|
220
308
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
309
|
+
// ✅ Enforce per-step constraints (basic validation)
|
|
310
|
+
if (step.constraints && Object.keys(step.constraints).length > 0) {
|
|
311
|
+
for (const [key, value] of Object.entries(step.constraints)) {
|
|
312
|
+
// Log unsupported constraints (future extensibility)
|
|
313
|
+
if (['max_time_sec', 'cost_limit', 'allowed_resolvers'].includes(key)) {
|
|
314
|
+
this.addWarning(`Per-step constraint "${key}=${value}" is parsed but not yet enforced`);
|
|
315
|
+
} else {
|
|
316
|
+
this.addWarning(`Unknown per-step constraint: ${key}=${value}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ✅ ADDITION 3 — Resolver Policy Enforcement (External + Local)
|
|
322
|
+
const enforceResolverPolicy = (resolver, step) => {
|
|
323
|
+
const resolverName = resolver?.resolverName || resolver?.name;
|
|
224
324
|
|
|
225
|
-
|
|
325
|
+
if (!resolverName) {
|
|
326
|
+
throw new Error('[O-Lang] Resolver missing resolverName');
|
|
327
|
+
}
|
|
226
328
|
|
|
227
|
-
if (!
|
|
228
|
-
this.logDisallowedResolver(resolverName, step.actionRaw || step.
|
|
229
|
-
throw new Error(
|
|
329
|
+
if (!this.allowedResolvers.has(resolverName)) {
|
|
330
|
+
this.logDisallowedResolver(resolverName, step.actionRaw || step.type);
|
|
331
|
+
throw new Error(
|
|
332
|
+
`[O-Lang] Resolver "${resolverName}" blocked by workflow policy`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// External resolvers MUST be HTTP-only
|
|
337
|
+
if (this._isExternalResolver(resolver)) {
|
|
338
|
+
if (!resolver.manifest.endpoint) {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`[O-Lang] External resolver "${resolverName}" missing endpoint`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
230
343
|
}
|
|
231
344
|
};
|
|
232
345
|
|
|
@@ -261,17 +374,28 @@ class RuntimeAPI {
|
|
|
261
374
|
// ✅ Return the FIRST resolver that returns a non-undefined result
|
|
262
375
|
for (let idx = 0; idx < resolversToRun.length; idx++) {
|
|
263
376
|
const resolver = resolversToRun[idx];
|
|
264
|
-
|
|
377
|
+
enforceResolverPolicy(resolver, step); // ✅ Use new policy enforcement
|
|
265
378
|
|
|
266
379
|
try {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
this.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
380
|
+
let result; // ✅ ADDITION 4 — External Resolver Execution Path
|
|
381
|
+
|
|
382
|
+
if (this._isExternalResolver(resolver)) {
|
|
383
|
+
result = await this._callExternalResolver(
|
|
384
|
+
resolver,
|
|
385
|
+
action,
|
|
386
|
+
this.context
|
|
387
|
+
);
|
|
388
|
+
} else {
|
|
389
|
+
result = await resolver(action, this.context);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (result !== undefined) {
|
|
393
|
+
this.context[`__resolver_${idx}`] = result;
|
|
394
|
+
return result;
|
|
274
395
|
}
|
|
396
|
+
|
|
397
|
+
outputs.push(result);
|
|
398
|
+
this.context[`__resolver_${idx}`] = result;
|
|
275
399
|
} catch (e) {
|
|
276
400
|
this.addWarning(`Resolver ${resolver?.resolverName || resolver?.name || idx} failed for action "${action}": ${e.message}`);
|
|
277
401
|
outputs.push(null);
|
|
@@ -329,14 +453,12 @@ class RuntimeAPI {
|
|
|
329
453
|
}
|
|
330
454
|
|
|
331
455
|
case 'evolve': {
|
|
332
|
-
// ✅ Handle in-workflow Evolve steps
|
|
333
456
|
const { targetResolver, feedback } = step;
|
|
334
457
|
|
|
335
458
|
if (this.verbose) {
|
|
336
459
|
console.log(`🔄 Evolve step: ${targetResolver} with feedback: "${feedback}"`);
|
|
337
460
|
}
|
|
338
461
|
|
|
339
|
-
// Basic evolution: record the request (free tier)
|
|
340
462
|
const evolutionResult = {
|
|
341
463
|
resolver: targetResolver,
|
|
342
464
|
feedback: feedback,
|
|
@@ -345,12 +467,10 @@ class RuntimeAPI {
|
|
|
345
467
|
workflow: this.context.workflow_name
|
|
346
468
|
};
|
|
347
469
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
evolutionResult.message = 'Advanced evolution service would process this request';
|
|
353
|
-
}
|
|
470
|
+
if (process.env.OLANG_EVOLUTION_API_KEY) {
|
|
471
|
+
evolutionResult.status = 'advanced_evolution_enabled';
|
|
472
|
+
evolutionResult.message = 'Advanced evolution service would process this request';
|
|
473
|
+
}
|
|
354
474
|
|
|
355
475
|
if (step.saveAs) {
|
|
356
476
|
this.context[step.saveAs] = evolutionResult;
|
|
@@ -366,7 +486,99 @@ class RuntimeAPI {
|
|
|
366
486
|
}
|
|
367
487
|
|
|
368
488
|
case 'parallel': {
|
|
369
|
-
|
|
489
|
+
const { steps, timeout } = step;
|
|
490
|
+
|
|
491
|
+
if (timeout !== undefined && timeout > 0) {
|
|
492
|
+
// Timed parallel execution
|
|
493
|
+
const timeoutPromise = new Promise(resolve => {
|
|
494
|
+
setTimeout(() => resolve({ timedOut: true }), timeout);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
const parallelPromise = Promise.all(
|
|
498
|
+
steps.map(s => this.executeStep(s, agentResolver))
|
|
499
|
+
).then(() => ({ timedOut: false }));
|
|
500
|
+
|
|
501
|
+
const result = await Promise.race([timeoutPromise, parallelPromise]);
|
|
502
|
+
this.context.timed_out = result.timedOut;
|
|
503
|
+
|
|
504
|
+
if (result.timedOut) {
|
|
505
|
+
this.emit('parallel_timeout', { duration: timeout, steps: steps.length });
|
|
506
|
+
if (this.verbose) {
|
|
507
|
+
console.log(`⏰ Parallel execution timed out after ${timeout}ms`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
} else {
|
|
511
|
+
// Normal parallel execution (no timeout)
|
|
512
|
+
await Promise.all(steps.map(s => this.executeStep(s, agentResolver)));
|
|
513
|
+
this.context.timed_out = false;
|
|
514
|
+
}
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
case 'escalation': {
|
|
519
|
+
const { levels } = step;
|
|
520
|
+
let finalResult = null;
|
|
521
|
+
let currentTimeout = 0;
|
|
522
|
+
let completedLevel = null;
|
|
523
|
+
|
|
524
|
+
for (const level of levels) {
|
|
525
|
+
if (level.timeout === 0) {
|
|
526
|
+
// Immediate execution (no timeout)
|
|
527
|
+
const levelSteps = require('./parser').parseBlock(level.steps);
|
|
528
|
+
for (const levelStep of levelSteps) {
|
|
529
|
+
await this.executeStep(levelStep, agentResolver);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Check if the target variable was set in this level
|
|
533
|
+
// For now, we'll assume the last saveAs in the level is the result
|
|
534
|
+
if (levelSteps.length > 0) {
|
|
535
|
+
const lastStep = levelSteps[levelSteps.length - 1];
|
|
536
|
+
if (lastStep.saveAs && this.context[lastStep.saveAs] !== undefined) {
|
|
537
|
+
finalResult = this.context[lastStep.saveAs];
|
|
538
|
+
completedLevel = level.levelNumber;
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
} else {
|
|
543
|
+
// Timed execution for this level
|
|
544
|
+
currentTimeout += level.timeout;
|
|
545
|
+
|
|
546
|
+
const timeoutPromise = new Promise(resolve => {
|
|
547
|
+
setTimeout(() => resolve({ timedOut: true }), level.timeout);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
const levelPromise = (async () => {
|
|
551
|
+
const levelSteps = require('./parser').parseBlock(level.steps);
|
|
552
|
+
for (const levelStep of levelSteps) {
|
|
553
|
+
await this.executeStep(levelStep, agentResolver);
|
|
554
|
+
}
|
|
555
|
+
return { timedOut: false };
|
|
556
|
+
})();
|
|
557
|
+
|
|
558
|
+
const result = await Promise.race([timeoutPromise, levelPromise]);
|
|
559
|
+
|
|
560
|
+
if (!result.timedOut) {
|
|
561
|
+
// Level completed successfully
|
|
562
|
+
if (levelSteps && levelSteps.length > 0) {
|
|
563
|
+
const lastStep = levelSteps[levelSteps.length - 1];
|
|
564
|
+
if (lastStep.saveAs && this.context[lastStep.saveAs] !== undefined) {
|
|
565
|
+
finalResult = this.context[lastStep.saveAs];
|
|
566
|
+
completedLevel = level.levelNumber;
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// If timed out, continue to next level
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Set escalation status in context
|
|
576
|
+
this.context.escalation_completed = finalResult !== null;
|
|
577
|
+
this.context.timed_out = finalResult === null;
|
|
578
|
+
if (completedLevel !== null) {
|
|
579
|
+
this.context.escalation_level = completedLevel;
|
|
580
|
+
}
|
|
581
|
+
|
|
370
582
|
break;
|
|
371
583
|
}
|
|
372
584
|
|
|
@@ -381,26 +593,87 @@ class RuntimeAPI {
|
|
|
381
593
|
}
|
|
382
594
|
|
|
383
595
|
case 'debrief': {
|
|
596
|
+
// ✅ SEMANTIC VALIDATION: Check symbols in message
|
|
597
|
+
if (step.message.includes('{')) {
|
|
598
|
+
const symbols = step.message.match(/\{([^\}]+)\}/g) || [];
|
|
599
|
+
for (const symbolMatch of symbols) {
|
|
600
|
+
const symbol = symbolMatch.replace(/[{}]/g, '');
|
|
601
|
+
this._requireSemantic(symbol, 'debrief');
|
|
602
|
+
}
|
|
603
|
+
}
|
|
384
604
|
this.emit('debrief', { agent: step.agent, message: step.message });
|
|
385
605
|
break;
|
|
386
606
|
}
|
|
387
607
|
|
|
388
|
-
// ✅
|
|
389
|
-
case '
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
608
|
+
// ✅ NEW: Prompt step handler
|
|
609
|
+
case 'prompt': {
|
|
610
|
+
if (this.verbose) {
|
|
611
|
+
console.log(`❓ Prompt: ${step.question}`);
|
|
612
|
+
}
|
|
613
|
+
// In non-interactive mode, leave as no-op
|
|
614
|
+
// (Could integrate with stdin or API in future)
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// ✅ NEW: Emit step handler with semantic validation
|
|
619
|
+
case 'emit': {
|
|
620
|
+
// ✅ SEMANTIC VALIDATION: Check all symbols in payload
|
|
621
|
+
const payloadTemplate = step.payload;
|
|
622
|
+
const symbols = [...new Set(payloadTemplate.match(/\{([^\}]+)\}/g) || [])];
|
|
623
|
+
|
|
624
|
+
let shouldEmit = true;
|
|
625
|
+
for (const symbolMatch of symbols) {
|
|
626
|
+
const symbol = symbolMatch.replace(/[{}]/g, '');
|
|
627
|
+
if (!this._requireSemantic(symbol, 'emit')) {
|
|
628
|
+
shouldEmit = false;
|
|
629
|
+
// Continue to validate all symbols (for complete error reporting)
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (!shouldEmit) {
|
|
634
|
+
if (this.verbose) {
|
|
635
|
+
console.log(`⏭️ Skipped emit due to missing semantic symbols`);
|
|
636
|
+
}
|
|
393
637
|
break;
|
|
394
638
|
}
|
|
639
|
+
|
|
640
|
+
const payload = step.payload.replace(/\{([^\}]+)\}/g, (_, path) => {
|
|
641
|
+
const value = this.getNested(this.context, path.trim());
|
|
642
|
+
return value !== undefined ? String(value) : `{${path}}`;
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
this.emit(step.event, {
|
|
646
|
+
payload: payload,
|
|
647
|
+
workflow: this.context.workflow_name,
|
|
648
|
+
timestamp: new Date().toISOString()
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
if (this.verbose) {
|
|
652
|
+
console.log(`📤 Emit event "${step.event}" with payload: ${payload}`);
|
|
653
|
+
}
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
395
656
|
|
|
396
|
-
|
|
657
|
+
// ✅ File Persist step handler with semantic validation
|
|
658
|
+
case 'persist': {
|
|
659
|
+
// ✅ SEMANTIC VALIDATION: Require symbol exists
|
|
660
|
+
if (!this._requireSemantic(step.variable, 'persist')) {
|
|
661
|
+
// Default policy: Skip persist (safe)
|
|
662
|
+
if (this.verbose) {
|
|
663
|
+
console.log(`⏭️ Skipped persist for undefined "${step.variable}"`);
|
|
664
|
+
}
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const sourceValue = this.context[step.variable]; // Now guaranteed defined
|
|
669
|
+
const outputPath = path.resolve(process.cwd(), step.target);
|
|
397
670
|
const outputDir = path.dirname(outputPath);
|
|
398
671
|
if (!fs.existsSync(outputDir)) {
|
|
399
672
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
400
673
|
}
|
|
401
674
|
|
|
402
675
|
let content;
|
|
403
|
-
if (step.
|
|
676
|
+
if (step.target.endsWith('.json')) {
|
|
404
677
|
content = JSON.stringify(sourceValue, null, 2);
|
|
405
678
|
} else {
|
|
406
679
|
content = String(sourceValue);
|
|
@@ -409,23 +682,27 @@ class RuntimeAPI {
|
|
|
409
682
|
fs.writeFileSync(outputPath, content, 'utf8');
|
|
410
683
|
|
|
411
684
|
if (this.verbose) {
|
|
412
|
-
console.log(`💾 Persisted "${step.
|
|
685
|
+
console.log(`💾 Persisted "${step.variable}" to ${step.target}`);
|
|
413
686
|
}
|
|
414
687
|
break;
|
|
415
688
|
}
|
|
416
689
|
|
|
417
|
-
// ✅ NEW: Database persist handler
|
|
690
|
+
// ✅ NEW: Database persist handler with semantic validation
|
|
418
691
|
case 'persist-db': {
|
|
419
692
|
if (!this.dbClient) {
|
|
420
693
|
this.addWarning(`DB persistence skipped (no DB configured). Set OLANG_DB_TYPE env var.`);
|
|
421
694
|
break;
|
|
422
695
|
}
|
|
423
696
|
|
|
424
|
-
|
|
425
|
-
if (
|
|
426
|
-
this.
|
|
697
|
+
// ✅ SEMANTIC VALIDATION: Require symbol exists
|
|
698
|
+
if (!this._requireSemantic(step.variable, 'persist-db')) {
|
|
699
|
+
if (this.verbose) {
|
|
700
|
+
console.log(`⏭️ Skipped DB persist for undefined "${step.variable}"`);
|
|
701
|
+
}
|
|
427
702
|
break;
|
|
428
703
|
}
|
|
704
|
+
|
|
705
|
+
const sourceValue = this.context[step.variable]; // Now guaranteed defined
|
|
429
706
|
|
|
430
707
|
try {
|
|
431
708
|
switch (this.dbClient.type) {
|
|
@@ -466,10 +743,10 @@ class RuntimeAPI {
|
|
|
466
743
|
}
|
|
467
744
|
|
|
468
745
|
if (this.verbose) {
|
|
469
|
-
console.log(`🗄️ Persisted "${step.
|
|
746
|
+
console.log(`🗄️ Persisted "${step.variable}" to DB collection ${step.collection}`);
|
|
470
747
|
}
|
|
471
748
|
} catch (e) {
|
|
472
|
-
this.addWarning(`DB persist failed for "${step.
|
|
749
|
+
this.addWarning(`DB persist failed for "${step.variable}": ${e.message}`);
|
|
473
750
|
}
|
|
474
751
|
break;
|
|
475
752
|
}
|
|
@@ -482,18 +759,15 @@ class RuntimeAPI {
|
|
|
482
759
|
}
|
|
483
760
|
|
|
484
761
|
async executeWorkflow(workflow, inputs, agentResolver) {
|
|
485
|
-
// Handle regular workflows only (Evolve is a step type now)
|
|
486
762
|
if (workflow.type !== 'workflow') {
|
|
487
763
|
throw new Error(`Unknown workflow type: ${workflow.type}`);
|
|
488
764
|
}
|
|
489
765
|
|
|
490
|
-
// ✅ Inject workflow name into context
|
|
491
766
|
this.context = {
|
|
492
767
|
...inputs,
|
|
493
768
|
workflow_name: workflow.name
|
|
494
769
|
};
|
|
495
770
|
|
|
496
|
-
// ✅ Check generation constraint from Constraint: max_generations = X
|
|
497
771
|
const currentGeneration = inputs.__generation || 1;
|
|
498
772
|
if (workflow.maxGenerations !== null && currentGeneration > workflow.maxGenerations) {
|
|
499
773
|
throw new Error(`Workflow generation ${currentGeneration} exceeds Constraint: max_generations = ${workflow.maxGenerations}`);
|
|
@@ -524,9 +798,13 @@ class RuntimeAPI {
|
|
|
524
798
|
});
|
|
525
799
|
}
|
|
526
800
|
|
|
801
|
+
// ✅ SEMANTIC VALIDATION: For return values
|
|
527
802
|
const result = {};
|
|
528
803
|
for (const key of workflow.returnValues) {
|
|
529
|
-
|
|
804
|
+
if (this._requireSemantic(key, 'return')) {
|
|
805
|
+
result[key] = this.context[key];
|
|
806
|
+
}
|
|
807
|
+
// Skip undefined return values (safe default)
|
|
530
808
|
}
|
|
531
809
|
return result;
|
|
532
810
|
}
|