@o-lang/olang 1.4.1 → 1.4.3
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/package.json +1 -1
- package/src/parser/index.js +103 -45
- package/src/runtime/RuntimeAPI.js +45 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@o-lang/olang",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"author": "Olalekan Ogundipe <info@olang.cloud>",
|
|
5
5
|
"description": "O-Lang: A governance language for user-directed, rule-enforced agent workflows with native African language PII protection.",
|
|
6
6
|
"main": "./src/runtime/index.js",
|
package/src/parser/index.js
CHANGED
|
@@ -77,6 +77,11 @@ function parseWorkflowLines(lines, filename) {
|
|
|
77
77
|
let escalationLevels = [];
|
|
78
78
|
let currentLevel = null;
|
|
79
79
|
|
|
80
|
+
// ✅ ENHANCED: Track labels for blocks
|
|
81
|
+
let currentIfLabel = null;
|
|
82
|
+
let currentParallelLabel = null;
|
|
83
|
+
let currentEscalationLabel = null;
|
|
84
|
+
|
|
80
85
|
const flushCurrentStep = () => {
|
|
81
86
|
if (currentStep) {
|
|
82
87
|
workflow.steps.push(currentStep);
|
|
@@ -88,6 +93,15 @@ function parseWorkflowLines(lines, filename) {
|
|
|
88
93
|
let line = lines[i++].trim();
|
|
89
94
|
if (line === '' || line.startsWith('#')) continue;
|
|
90
95
|
|
|
96
|
+
// ✅ ENHANCED: Extract "Step N:" label if present
|
|
97
|
+
let stepLabel = null;
|
|
98
|
+
let strippedLine = line;
|
|
99
|
+
const labelMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
|
|
100
|
+
if (labelMatch) {
|
|
101
|
+
stepLabel = `Step ${labelMatch[1]}`;
|
|
102
|
+
strippedLine = labelMatch[2].trim();
|
|
103
|
+
}
|
|
104
|
+
|
|
91
105
|
// --- 1. Workflow Declaration ---
|
|
92
106
|
if (line.startsWith('Workflow ')) {
|
|
93
107
|
const match = line.match(/^Workflow\s+"([^"]+)"(?:\s+with\s+(.+))?$/i);
|
|
@@ -134,11 +148,13 @@ function parseWorkflowLines(lines, filename) {
|
|
|
134
148
|
}
|
|
135
149
|
|
|
136
150
|
// --- 4. Block: Escalation ---
|
|
137
|
-
|
|
151
|
+
// ✅ ENHANCED: Use strippedLine to match "Step N: Run in parallel with escalation:"
|
|
152
|
+
if (strippedLine.match(/^Run in parallel with escalation:$/i)) {
|
|
138
153
|
flushCurrentStep();
|
|
139
154
|
inEscalationBlock = true;
|
|
140
155
|
escalationLevels = [];
|
|
141
156
|
currentLevel = null;
|
|
157
|
+
currentEscalationLabel = stepLabel; // ✅ Save label
|
|
142
158
|
continue;
|
|
143
159
|
}
|
|
144
160
|
|
|
@@ -148,8 +164,10 @@ function parseWorkflowLines(lines, filename) {
|
|
|
148
164
|
workflow.steps.push({
|
|
149
165
|
type: 'escalation',
|
|
150
166
|
levels: escalationLevels,
|
|
151
|
-
stepNumber: workflow.steps.length + 1
|
|
167
|
+
stepNumber: workflow.steps.length + 1,
|
|
168
|
+
label: currentEscalationLabel // ✅ ENHANCED: Add label
|
|
152
169
|
});
|
|
170
|
+
currentEscalationLabel = null; // ✅ Reset
|
|
153
171
|
inEscalationBlock = false;
|
|
154
172
|
continue;
|
|
155
173
|
} else if (line.match(/^Level \d+:/i)) {
|
|
@@ -185,7 +203,8 @@ function parseWorkflowLines(lines, filename) {
|
|
|
185
203
|
}
|
|
186
204
|
|
|
187
205
|
// --- 5. Block: Parallel ---
|
|
188
|
-
|
|
206
|
+
// ✅ ENHANCED: Use strippedLine to match "Step N: Run in parallel for Xs"
|
|
207
|
+
const timedParMatch = strippedLine.match(/^Run in parallel for (\d+)\s*([smhd])$/i);
|
|
189
208
|
if (timedParMatch) {
|
|
190
209
|
flushCurrentStep();
|
|
191
210
|
const value = parseInt(timedParMatch[1]);
|
|
@@ -195,14 +214,17 @@ function parseWorkflowLines(lines, filename) {
|
|
|
195
214
|
|
|
196
215
|
inParallelBlock = true;
|
|
197
216
|
parallelSteps = [];
|
|
217
|
+
currentParallelLabel = stepLabel; // ✅ Save label
|
|
198
218
|
continue;
|
|
199
219
|
}
|
|
200
220
|
|
|
201
|
-
|
|
221
|
+
// ✅ ENHANCED: Use strippedLine to match "Step N: Run in parallel"
|
|
222
|
+
if (strippedLine.match(/^Run in parallel$/i)) {
|
|
202
223
|
flushCurrentStep();
|
|
203
224
|
inParallelBlock = true;
|
|
204
225
|
parallelSteps = [];
|
|
205
226
|
parallelTimeout = null;
|
|
227
|
+
currentParallelLabel = stepLabel; // ✅ Save label
|
|
206
228
|
continue;
|
|
207
229
|
}
|
|
208
230
|
|
|
@@ -214,8 +236,10 @@ function parseWorkflowLines(lines, filename) {
|
|
|
214
236
|
type: 'parallel',
|
|
215
237
|
steps: parsedParallel,
|
|
216
238
|
timeout: parallelTimeout,
|
|
217
|
-
stepNumber: workflow.steps.length + 1
|
|
239
|
+
stepNumber: workflow.steps.length + 1,
|
|
240
|
+
label: currentParallelLabel // ✅ ENHANCED: Add label
|
|
218
241
|
});
|
|
242
|
+
currentParallelLabel = null; // ✅ Reset
|
|
219
243
|
inParallelBlock = false;
|
|
220
244
|
parallelTimeout = null;
|
|
221
245
|
continue;
|
|
@@ -227,12 +251,13 @@ function parseWorkflowLines(lines, filename) {
|
|
|
227
251
|
|
|
228
252
|
// --- 6. Block: If / Else If / Else (FIXED STATE MACHINE) ---
|
|
229
253
|
|
|
230
|
-
//
|
|
231
|
-
if (
|
|
254
|
+
// ✅ ENHANCED: Use strippedLine to match "Step N: If ..."
|
|
255
|
+
if (strippedLine.match(/^(?:If|When)\s+(.+)$/i)) {
|
|
232
256
|
flushCurrentStep();
|
|
233
|
-
const ifMatch =
|
|
257
|
+
const ifMatch = strippedLine.match(/^(?:If|When)\s+(.+)$/i);
|
|
234
258
|
ifCondition = ifMatch[1].trim();
|
|
235
259
|
inIfBlock = true;
|
|
260
|
+
currentIfLabel = stepLabel; // ✅ Save label
|
|
236
261
|
|
|
237
262
|
// Reset accumulators
|
|
238
263
|
mainIfBody = [];
|
|
@@ -291,8 +316,10 @@ function parseWorkflowLines(lines, filename) {
|
|
|
291
316
|
body: parseBlock(mainIfBody), // Main body correctly isolated
|
|
292
317
|
elseIf: elseIfChain, // Else-if chain
|
|
293
318
|
elseBranch: inElseBlock ? parseBlock(currentBranchBody) : [], // Else body
|
|
294
|
-
stepNumber: workflow.steps.length + 1
|
|
319
|
+
stepNumber: workflow.steps.length + 1,
|
|
320
|
+
label: currentIfLabel // ✅ ENHANCED: Add label
|
|
295
321
|
});
|
|
322
|
+
currentIfLabel = null; // ✅ Reset
|
|
296
323
|
|
|
297
324
|
// Reset State
|
|
298
325
|
inIfBlock = false;
|
|
@@ -316,7 +343,8 @@ function parseWorkflowLines(lines, filename) {
|
|
|
316
343
|
// --- 7. Standard Steps & Keywords ---
|
|
317
344
|
|
|
318
345
|
// Calculate (NEW v1.4.0 — math expression evaluation)
|
|
319
|
-
|
|
346
|
+
// ✅ ENHANCED: Use strippedLine
|
|
347
|
+
const calcMatch = strippedLine.match(/^Calculate\s+(.+)$/i);
|
|
320
348
|
if (calcMatch) {
|
|
321
349
|
flushCurrentStep();
|
|
322
350
|
workflow.steps.push({
|
|
@@ -324,13 +352,15 @@ function parseWorkflowLines(lines, filename) {
|
|
|
324
352
|
expression: calcMatch[1].trim(),
|
|
325
353
|
stepNumber: workflow.steps.length + 1,
|
|
326
354
|
saveAs: null,
|
|
327
|
-
constraints: {}
|
|
355
|
+
constraints: {},
|
|
356
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
328
357
|
});
|
|
329
358
|
continue;
|
|
330
359
|
}
|
|
331
360
|
|
|
332
361
|
// Connect: Connect "name" to url "..." OR Connect "name" to resolver "..."
|
|
333
|
-
|
|
362
|
+
// ✅ ENHANCED: Use strippedLine
|
|
363
|
+
const connectMatch = strippedLine.match(/^Connect\s+"([^"]+)"\s+to\s+(url|resolver)\s+"([^"]+)"$/i);
|
|
334
364
|
if (connectMatch) {
|
|
335
365
|
flushCurrentStep();
|
|
336
366
|
workflow.steps.push({
|
|
@@ -338,20 +368,23 @@ function parseWorkflowLines(lines, filename) {
|
|
|
338
368
|
resource: connectMatch[1],
|
|
339
369
|
endpoint: connectMatch[3],
|
|
340
370
|
targetType: connectMatch[2].toLowerCase(),
|
|
341
|
-
stepNumber: workflow.steps.length + 1
|
|
371
|
+
stepNumber: workflow.steps.length + 1,
|
|
372
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
342
373
|
});
|
|
343
374
|
continue;
|
|
344
375
|
}
|
|
345
376
|
|
|
346
377
|
// Use: Use "logicalName" as "resource"
|
|
347
|
-
|
|
378
|
+
// ✅ ENHANCED: Use strippedLine
|
|
379
|
+
const useMatch = strippedLine.match(/^Use\s+"([^"]+)"\s+as\s+"([^"]+)"$/i);
|
|
348
380
|
if (useMatch) {
|
|
349
381
|
flushCurrentStep();
|
|
350
382
|
workflow.steps.push({
|
|
351
383
|
type: 'agent_use',
|
|
352
384
|
logicalName: useMatch[1],
|
|
353
385
|
resource: useMatch[2],
|
|
354
|
-
stepNumber: workflow.steps.length + 1
|
|
386
|
+
stepNumber: workflow.steps.length + 1,
|
|
387
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
355
388
|
});
|
|
356
389
|
continue;
|
|
357
390
|
}
|
|
@@ -365,7 +398,8 @@ function parseWorkflowLines(lines, filename) {
|
|
|
365
398
|
stepNumber: parseInt(stepMatch[1], 10),
|
|
366
399
|
actionRaw: stepMatch[2].trim(),
|
|
367
400
|
saveAs: null,
|
|
368
|
-
constraints: {}
|
|
401
|
+
constraints: {},
|
|
402
|
+
label: `Step ${stepMatch[1]}` // ✅ ENHANCED: Add label
|
|
369
403
|
};
|
|
370
404
|
continue;
|
|
371
405
|
}
|
|
@@ -378,59 +412,68 @@ function parseWorkflowLines(lines, filename) {
|
|
|
378
412
|
}
|
|
379
413
|
|
|
380
414
|
// Debrief
|
|
381
|
-
|
|
415
|
+
// ✅ ENHANCED: Use strippedLine
|
|
416
|
+
const debriefMatch = strippedLine.match(/^Debrief\s+([^\s]+)\s+with\s+"([^"]*)"$/i);
|
|
382
417
|
if (debriefMatch) {
|
|
383
418
|
flushCurrentStep();
|
|
384
419
|
workflow.steps.push({
|
|
385
420
|
type: 'debrief',
|
|
386
421
|
agent: debriefMatch[1].trim(),
|
|
387
422
|
message: debriefMatch[2],
|
|
388
|
-
stepNumber: workflow.steps.length + 1
|
|
423
|
+
stepNumber: workflow.steps.length + 1,
|
|
424
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
389
425
|
});
|
|
390
426
|
continue;
|
|
391
427
|
}
|
|
392
428
|
|
|
393
429
|
// Prompt
|
|
394
|
-
|
|
430
|
+
// ✅ ENHANCED: Use strippedLine
|
|
431
|
+
const promptMatch = strippedLine.match(/^Prompt user to\s+"([^"]*)"$/i);
|
|
395
432
|
if (promptMatch) {
|
|
396
433
|
flushCurrentStep();
|
|
397
434
|
workflow.steps.push({
|
|
398
435
|
type: 'prompt',
|
|
399
436
|
question: promptMatch[1],
|
|
400
437
|
stepNumber: workflow.steps.length + 1,
|
|
401
|
-
saveAs: null
|
|
438
|
+
saveAs: null,
|
|
439
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
402
440
|
});
|
|
403
441
|
continue;
|
|
404
442
|
}
|
|
405
443
|
|
|
406
444
|
// Persist
|
|
407
|
-
|
|
445
|
+
// ✅ ENHANCED: Use strippedLine
|
|
446
|
+
const persistMatch = strippedLine.match(/^Persist\s+([^\s]+)\s+to\s+"([^"]*)"$/i);
|
|
408
447
|
if (persistMatch) {
|
|
409
448
|
flushCurrentStep();
|
|
410
449
|
workflow.steps.push({
|
|
411
450
|
type: 'persist',
|
|
412
451
|
variable: persistMatch[1].trim(),
|
|
413
452
|
target: persistMatch[2],
|
|
414
|
-
stepNumber: workflow.steps.length + 1
|
|
453
|
+
stepNumber: workflow.steps.length + 1,
|
|
454
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
415
455
|
});
|
|
416
456
|
continue;
|
|
417
457
|
}
|
|
418
458
|
|
|
419
459
|
// Emit
|
|
420
|
-
|
|
460
|
+
// ✅ ENHANCED: Use strippedLine
|
|
461
|
+
const emitMatch = strippedLine.match(/^Emit\s+"([^"]+)"\s+with\s+(.+)$/i);
|
|
421
462
|
if (emitMatch) {
|
|
422
463
|
flushCurrentStep();
|
|
423
464
|
workflow.steps.push({
|
|
424
465
|
type: 'emit',
|
|
425
466
|
event: emitMatch[1],
|
|
426
467
|
payload: emitMatch[2].trim(),
|
|
427
|
-
stepNumber: workflow.steps.length + 1
|
|
468
|
+
stepNumber: workflow.steps.length + 1,
|
|
469
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
428
470
|
});
|
|
429
471
|
continue;
|
|
430
472
|
}
|
|
431
473
|
|
|
432
474
|
// Ask (Multiline support)
|
|
433
|
-
|
|
475
|
+
// ✅ ENHANCED: Use strippedLine
|
|
476
|
+
const askMatch = strippedLine.match(/^Ask\s+(.+)$/i);
|
|
434
477
|
if (askMatch) {
|
|
435
478
|
flushCurrentStep();
|
|
436
479
|
let actionContent = askMatch[1].trim();
|
|
@@ -448,7 +491,8 @@ function parseWorkflowLines(lines, filename) {
|
|
|
448
491
|
actionRaw: `Action ${actionContent}`,
|
|
449
492
|
stepNumber: workflow.steps.length + 1,
|
|
450
493
|
saveAs: null,
|
|
451
|
-
constraints: {}
|
|
494
|
+
constraints: {},
|
|
495
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
452
496
|
});
|
|
453
497
|
continue;
|
|
454
498
|
}
|
|
@@ -465,17 +509,19 @@ function parseWorkflowLines(lines, filename) {
|
|
|
465
509
|
}
|
|
466
510
|
|
|
467
511
|
// Fallback: Treat as action
|
|
468
|
-
|
|
512
|
+
// ✅ ENHANCED: Use strippedLine for actionRaw
|
|
513
|
+
if (strippedLine.trim() !== '') {
|
|
469
514
|
if (!currentStep) {
|
|
470
515
|
currentStep = {
|
|
471
516
|
type: 'action',
|
|
472
517
|
stepNumber: workflow.steps.length + 1,
|
|
473
|
-
actionRaw:
|
|
518
|
+
actionRaw: strippedLine,
|
|
474
519
|
saveAs: null,
|
|
475
|
-
constraints: {}
|
|
520
|
+
constraints: {},
|
|
521
|
+
label: stepLabel // ✅ ENHANCED: Add label
|
|
476
522
|
};
|
|
477
523
|
} else {
|
|
478
|
-
currentStep.actionRaw += ' ' +
|
|
524
|
+
currentStep.actionRaw += ' ' + strippedLine;
|
|
479
525
|
}
|
|
480
526
|
}
|
|
481
527
|
}
|
|
@@ -526,16 +572,16 @@ function parseBlock(lines) {
|
|
|
526
572
|
line = line.trim();
|
|
527
573
|
if (!line || line.startsWith('#')) continue;
|
|
528
574
|
|
|
529
|
-
// Calculate in Block
|
|
575
|
+
// Calculate in Block
|
|
530
576
|
const calcMatch = line.match(/^Calculate\s+(.+)$/i);
|
|
531
577
|
if (calcMatch) {
|
|
532
578
|
flush();
|
|
533
|
-
|
|
579
|
+
current = {
|
|
534
580
|
type: 'calculate',
|
|
535
581
|
expression: calcMatch[1].trim(),
|
|
536
582
|
saveAs: null,
|
|
537
583
|
constraints: {}
|
|
538
|
-
}
|
|
584
|
+
};
|
|
539
585
|
continue;
|
|
540
586
|
}
|
|
541
587
|
|
|
@@ -543,12 +589,12 @@ function parseBlock(lines) {
|
|
|
543
589
|
const connectMatch = line.match(/^Connect\s+"([^"]+)"\s+to\s+(url|resolver)\s+"([^"]+)"$/i);
|
|
544
590
|
if (connectMatch) {
|
|
545
591
|
flush();
|
|
546
|
-
|
|
592
|
+
current = {
|
|
547
593
|
type: 'connect',
|
|
548
594
|
resource: connectMatch[1],
|
|
549
595
|
endpoint: connectMatch[3],
|
|
550
596
|
targetType: connectMatch[2].toLowerCase()
|
|
551
|
-
}
|
|
597
|
+
};
|
|
552
598
|
continue;
|
|
553
599
|
}
|
|
554
600
|
|
|
@@ -556,11 +602,11 @@ function parseBlock(lines) {
|
|
|
556
602
|
const useMatch = line.match(/^Use\s+"([^"]+)"\s+as\s+"([^"]+)"$/i);
|
|
557
603
|
if (useMatch) {
|
|
558
604
|
flush();
|
|
559
|
-
|
|
605
|
+
current = {
|
|
560
606
|
type: 'agent_use',
|
|
561
607
|
logicalName: useMatch[1],
|
|
562
608
|
resource: useMatch[2]
|
|
563
|
-
}
|
|
609
|
+
};
|
|
564
610
|
continue;
|
|
565
611
|
}
|
|
566
612
|
|
|
@@ -573,7 +619,8 @@ function parseBlock(lines) {
|
|
|
573
619
|
stepNumber: parseInt(stepMatch[1], 10),
|
|
574
620
|
actionRaw: stepMatch[2].trim(),
|
|
575
621
|
saveAs: null,
|
|
576
|
-
constraints: {}
|
|
622
|
+
constraints: {},
|
|
623
|
+
label: `Step ${stepMatch[1]}`
|
|
577
624
|
};
|
|
578
625
|
continue;
|
|
579
626
|
}
|
|
@@ -589,7 +636,11 @@ function parseBlock(lines) {
|
|
|
589
636
|
const debriefMatch = line.match(/^Debrief\s+([^\s]+)\s+with\s+"([^"]*)"$/i);
|
|
590
637
|
if (debriefMatch) {
|
|
591
638
|
flush();
|
|
592
|
-
|
|
639
|
+
current = {
|
|
640
|
+
type: 'debrief',
|
|
641
|
+
agent: debriefMatch[1].trim(),
|
|
642
|
+
message: debriefMatch[2]
|
|
643
|
+
};
|
|
593
644
|
continue;
|
|
594
645
|
}
|
|
595
646
|
|
|
@@ -597,7 +648,11 @@ function parseBlock(lines) {
|
|
|
597
648
|
const persistMatch = line.match(/^Persist\s+([^\s]+)\s+to\s+"([^"]*)"$/i);
|
|
598
649
|
if (persistMatch) {
|
|
599
650
|
flush();
|
|
600
|
-
|
|
651
|
+
current = {
|
|
652
|
+
type: 'persist',
|
|
653
|
+
variable: persistMatch[1].trim(),
|
|
654
|
+
target: persistMatch[2]
|
|
655
|
+
};
|
|
601
656
|
continue;
|
|
602
657
|
}
|
|
603
658
|
|
|
@@ -605,20 +660,24 @@ function parseBlock(lines) {
|
|
|
605
660
|
const emitMatch = line.match(/^Emit\s+"([^"]+)"\s+with\s+(.+)$/i);
|
|
606
661
|
if (emitMatch) {
|
|
607
662
|
flush();
|
|
608
|
-
|
|
663
|
+
current = {
|
|
664
|
+
type: 'emit',
|
|
665
|
+
event: emitMatch[1],
|
|
666
|
+
payload: emitMatch[2].trim()
|
|
667
|
+
};
|
|
609
668
|
continue;
|
|
610
669
|
}
|
|
611
670
|
|
|
612
|
-
// Ask in Block
|
|
671
|
+
// Ask in Block — FIXED: Set as current instead of pushing
|
|
613
672
|
const askMatch = line.match(/^Ask\s+(.+)$/i);
|
|
614
673
|
if (askMatch) {
|
|
615
674
|
flush();
|
|
616
|
-
|
|
675
|
+
current = {
|
|
617
676
|
type: 'action',
|
|
618
677
|
actionRaw: `Action ${askMatch[1].trim()}`,
|
|
619
678
|
saveAs: null,
|
|
620
679
|
constraints: {}
|
|
621
|
-
}
|
|
680
|
+
};
|
|
622
681
|
continue;
|
|
623
682
|
}
|
|
624
683
|
|
|
@@ -639,7 +698,6 @@ function parseBlock(lines) {
|
|
|
639
698
|
step.saveAs = normalizeSymbol(saveInAction[2].trim());
|
|
640
699
|
}
|
|
641
700
|
}
|
|
642
|
-
// ✅ NEW: Handle Calculate steps with inline Save as in blocks
|
|
643
701
|
if (step.type === 'calculate' && step.expression && step.saveAs === null) {
|
|
644
702
|
const saveInExpr = step.expression.match(/(.+?)\s+Save as\s+(.+)$/i);
|
|
645
703
|
if (saveInExpr) {
|
|
@@ -1624,8 +1624,15 @@ class RuntimeAPI {
|
|
|
1624
1624
|
}
|
|
1625
1625
|
}
|
|
1626
1626
|
|
|
1627
|
-
|
|
1627
|
+
// ✅ ENHANCED: Extract step location context for debugging
|
|
1628
|
+
const stepLabel = step.label || `unnamed_${step.type}_step`;
|
|
1629
|
+
const branchContext = step._parentLabel ? ` [Inside ${step._parentLabel} → ${step._branch} branch]` : '';
|
|
1630
|
+
|
|
1631
|
+
let errorMessage = `[O-Lang SAFETY] No resolver handled action: "${action}"\n`;
|
|
1632
|
+
errorMessage += ` → Location: ${stepLabel}${branchContext}\n`;
|
|
1633
|
+
errorMessage += ` → Workflow: ${this.context.workflow_name || 'unknown'}\n`;
|
|
1628
1634
|
errorMessage += `Attempted resolvers:\n`;
|
|
1635
|
+
|
|
1629
1636
|
resolverAttempts.forEach((attempt, i) => {
|
|
1630
1637
|
const namePad = attempt.name.padEnd(30);
|
|
1631
1638
|
if (attempt.status === 'skipped') {
|
|
@@ -1682,6 +1689,16 @@ class RuntimeAPI {
|
|
|
1682
1689
|
errorMessage += ` → Visit https://www.npmjs.com/search?q=%40o-lang for resolver packages\n`;
|
|
1683
1690
|
}
|
|
1684
1691
|
errorMessage += `\n🛑 Workflow halted to prevent unsafe data propagation to LLMs.`;
|
|
1692
|
+
|
|
1693
|
+
// ✅ ENHANCED: Log the failure with full context before throwing
|
|
1694
|
+
this._createAuditEntry('resolver_execution_failed', {
|
|
1695
|
+
step_label: stepLabel,
|
|
1696
|
+
branch_context: step._parentLabel ? `${step._parentLabel} (${step._branch})` : null,
|
|
1697
|
+
action: action,
|
|
1698
|
+
attempted_resolvers: resolverAttempts.map(a => a.name),
|
|
1699
|
+
severity: 'high'
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1685
1702
|
throw new Error(errorMessage);
|
|
1686
1703
|
};
|
|
1687
1704
|
|
|
@@ -1859,7 +1876,9 @@ class RuntimeAPI {
|
|
|
1859
1876
|
// 3. Auditability: Logs which condition was evaluated and which branch fired.
|
|
1860
1877
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1861
1878
|
|
|
1862
|
-
|
|
1879
|
+
case 'if': {
|
|
1880
|
+
const stepLabel = step.label || 'unnamed_if_block';
|
|
1881
|
+
|
|
1863
1882
|
// 1. Validate all symbols referenced in the main condition
|
|
1864
1883
|
const condSymbols = step.condition ? step.condition.match(/\{([^\}]+)\}/g) || [] : [];
|
|
1865
1884
|
let symbolsValid = true;
|
|
@@ -1873,6 +1892,7 @@ class RuntimeAPI {
|
|
|
1873
1892
|
|
|
1874
1893
|
if (!symbolsValid) {
|
|
1875
1894
|
this._createAuditEntry('condition_skipped', {
|
|
1895
|
+
step_label: stepLabel,
|
|
1876
1896
|
condition: step.condition,
|
|
1877
1897
|
reason: 'One or more symbols missing in context',
|
|
1878
1898
|
severity: 'warn'
|
|
@@ -1884,6 +1904,7 @@ class RuntimeAPI {
|
|
|
1884
1904
|
const mainPassed = this.evaluateCondition(step.condition, this.context);
|
|
1885
1905
|
|
|
1886
1906
|
this._createAuditEntry('condition_evaluated', {
|
|
1907
|
+
step_label: stepLabel,
|
|
1887
1908
|
condition: step.condition,
|
|
1888
1909
|
passed: mainPassed,
|
|
1889
1910
|
branch: 'if',
|
|
@@ -1892,7 +1913,12 @@ class RuntimeAPI {
|
|
|
1892
1913
|
|
|
1893
1914
|
if (mainPassed) {
|
|
1894
1915
|
if (step.body && Array.isArray(step.body)) {
|
|
1895
|
-
|
|
1916
|
+
// ✅ ENHANCED: Tag child steps with parent context for debugging
|
|
1917
|
+
for (const s of step.body) {
|
|
1918
|
+
s._parentLabel = stepLabel;
|
|
1919
|
+
s._branch = 'if';
|
|
1920
|
+
await this.executeStep(s, agentResolver);
|
|
1921
|
+
}
|
|
1896
1922
|
}
|
|
1897
1923
|
break; // Exit after successful if
|
|
1898
1924
|
}
|
|
@@ -1913,6 +1939,7 @@ class RuntimeAPI {
|
|
|
1913
1939
|
|
|
1914
1940
|
if (!branchSymbolsValid) {
|
|
1915
1941
|
this._createAuditEntry('condition_skipped', {
|
|
1942
|
+
step_label: stepLabel,
|
|
1916
1943
|
condition: branch.condition,
|
|
1917
1944
|
reason: 'One or more symbols missing in context',
|
|
1918
1945
|
severity: 'warn'
|
|
@@ -1923,6 +1950,7 @@ class RuntimeAPI {
|
|
|
1923
1950
|
const branchPassed = this.evaluateCondition(branch.condition, this.context);
|
|
1924
1951
|
|
|
1925
1952
|
this._createAuditEntry('condition_evaluated', {
|
|
1953
|
+
step_label: stepLabel,
|
|
1926
1954
|
condition: branch.condition,
|
|
1927
1955
|
passed: branchPassed,
|
|
1928
1956
|
branch: 'else-if',
|
|
@@ -1931,7 +1959,12 @@ class RuntimeAPI {
|
|
|
1931
1959
|
|
|
1932
1960
|
if (branchPassed) {
|
|
1933
1961
|
if (branch.body && Array.isArray(branch.body)) {
|
|
1934
|
-
|
|
1962
|
+
// ✅ ENHANCED: Tag child steps with parent context
|
|
1963
|
+
for (const s of branch.body) {
|
|
1964
|
+
s._parentLabel = stepLabel;
|
|
1965
|
+
s._branch = 'else-if';
|
|
1966
|
+
await this.executeStep(s, agentResolver);
|
|
1967
|
+
}
|
|
1935
1968
|
}
|
|
1936
1969
|
elseIfFired = true;
|
|
1937
1970
|
break;
|
|
@@ -1943,17 +1976,23 @@ class RuntimeAPI {
|
|
|
1943
1976
|
// 4. else fallback
|
|
1944
1977
|
if (step.elseBranch && Array.isArray(step.elseBranch)) {
|
|
1945
1978
|
this._createAuditEntry('condition_evaluated', {
|
|
1979
|
+
step_label: stepLabel,
|
|
1946
1980
|
condition: 'else',
|
|
1947
1981
|
passed: true,
|
|
1948
1982
|
branch: 'else',
|
|
1949
1983
|
severity: 'info'
|
|
1950
1984
|
});
|
|
1951
|
-
|
|
1985
|
+
// ✅ ENHANCED: Tag child steps with parent context
|
|
1986
|
+
for (const s of step.elseBranch) {
|
|
1987
|
+
s._parentLabel = stepLabel;
|
|
1988
|
+
s._branch = 'else';
|
|
1989
|
+
await this.executeStep(s, agentResolver);
|
|
1990
|
+
}
|
|
1952
1991
|
}
|
|
1953
1992
|
|
|
1954
1993
|
break;
|
|
1955
1994
|
}
|
|
1956
|
-
|
|
1995
|
+
|
|
1957
1996
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1958
1997
|
// PARALLEL
|
|
1959
1998
|
//
|