@o-lang/olang 1.2.38 → 1.4.0-alpha.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/package.json +26 -11
- package/src/parser/index.js +187 -258
- package/src/runtime/RuntimeAPI.js +1324 -473
- package/src/runtime/index.js +11 -1
package/src/parser/index.js
CHANGED
|
@@ -8,8 +8,6 @@ function normalizeSymbol(raw) {
|
|
|
8
8
|
return raw.split(/\s+/)[0].replace(/[^\w$]/g, '');
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
// ❌ REMOVED: normalizeAction() function (was stripping "Action" prefix → broke resolver matching)
|
|
12
|
-
|
|
13
11
|
function parse(content, filename = '<unknown>') {
|
|
14
12
|
if (typeof content === 'string') {
|
|
15
13
|
// ✅ Strip UTF-8 BOM if present (0xFEFF = Unicode BOM)
|
|
@@ -39,6 +37,10 @@ function parseLines(lines, filename) {
|
|
|
39
37
|
return parseWorkflowLines(lines, filename);
|
|
40
38
|
}
|
|
41
39
|
|
|
40
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
// CORE WORKFLOW PARSER
|
|
42
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
42
44
|
function parseWorkflowLines(lines, filename) {
|
|
43
45
|
const workflow = {
|
|
44
46
|
type: 'workflow',
|
|
@@ -55,17 +57,26 @@ function parseWorkflowLines(lines, filename) {
|
|
|
55
57
|
let i = 0;
|
|
56
58
|
let currentStep = null;
|
|
57
59
|
let inAllowResolvers = false;
|
|
60
|
+
|
|
61
|
+
// If/Else State Machine Variables (FIXED: Separate accumulators)
|
|
58
62
|
let inIfBlock = false;
|
|
59
63
|
let ifCondition = null;
|
|
60
|
-
let
|
|
64
|
+
let mainIfBody = []; // Accumulates main if branch lines
|
|
65
|
+
let currentBranchBody = []; // Accumulates else-if or else branches
|
|
66
|
+
let elseIfChain = [];
|
|
67
|
+
let currentElseIf = null;
|
|
68
|
+
let inElseBlock = false;
|
|
69
|
+
|
|
70
|
+
// Parallel State
|
|
61
71
|
let inParallelBlock = false;
|
|
62
72
|
let parallelSteps = [];
|
|
63
73
|
let parallelTimeout = null;
|
|
74
|
+
|
|
75
|
+
// Escalation State
|
|
64
76
|
let inEscalationBlock = false;
|
|
65
77
|
let escalationLevels = [];
|
|
66
78
|
let currentLevel = null;
|
|
67
79
|
|
|
68
|
-
// ✅ Helper to flush currentStep
|
|
69
80
|
const flushCurrentStep = () => {
|
|
70
81
|
if (currentStep) {
|
|
71
82
|
workflow.steps.push(currentStep);
|
|
@@ -75,12 +86,9 @@ function parseWorkflowLines(lines, filename) {
|
|
|
75
86
|
|
|
76
87
|
while (i < lines.length) {
|
|
77
88
|
let line = lines[i++].trim();
|
|
89
|
+
if (line === '' || line.startsWith('#')) continue;
|
|
78
90
|
|
|
79
|
-
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Workflow declaration
|
|
91
|
+
// --- 1. Workflow Declaration ---
|
|
84
92
|
if (line.startsWith('Workflow ')) {
|
|
85
93
|
const match = line.match(/^Workflow\s+"([^"]+)"(?:\s+with\s+(.+))?$/i);
|
|
86
94
|
if (match) {
|
|
@@ -94,18 +102,16 @@ function parseWorkflowLines(lines, filename) {
|
|
|
94
102
|
continue;
|
|
95
103
|
}
|
|
96
104
|
|
|
97
|
-
// Global
|
|
105
|
+
// --- 2. Global Constraints ---
|
|
98
106
|
if (line.startsWith('Constraint: max_generations = ')) {
|
|
99
107
|
const match = line.match(/^Constraint:\s+max_generations\s*=\s*(\d+)$/i);
|
|
100
108
|
if (match) {
|
|
101
109
|
workflow.maxGenerations = parseInt(match[1], 10);
|
|
102
|
-
} else {
|
|
103
|
-
workflow.__warnings.push(`Invalid Constraint syntax: ${line}`);
|
|
104
110
|
}
|
|
105
111
|
continue;
|
|
106
112
|
}
|
|
107
113
|
|
|
108
|
-
// Allow
|
|
114
|
+
// --- 3. Allow Resolvers Section ---
|
|
109
115
|
if (line === 'Allow resolvers:') {
|
|
110
116
|
inAllowResolvers = true;
|
|
111
117
|
continue;
|
|
@@ -121,13 +127,13 @@ function parseWorkflowLines(lines, filename) {
|
|
|
121
127
|
continue;
|
|
122
128
|
} else {
|
|
123
129
|
inAllowResolvers = false;
|
|
124
|
-
i--;
|
|
130
|
+
i--; // Re-process this line as normal step
|
|
125
131
|
continue;
|
|
126
132
|
}
|
|
127
133
|
continue;
|
|
128
134
|
}
|
|
129
135
|
|
|
130
|
-
//
|
|
136
|
+
// --- 4. Block: Escalation ---
|
|
131
137
|
if (line.match(/^Run in parallel with escalation:$/i)) {
|
|
132
138
|
flushCurrentStep();
|
|
133
139
|
inEscalationBlock = true;
|
|
@@ -138,9 +144,7 @@ function parseWorkflowLines(lines, filename) {
|
|
|
138
144
|
|
|
139
145
|
if (inEscalationBlock) {
|
|
140
146
|
if (line.match(/^End$/i)) {
|
|
141
|
-
if (currentLevel)
|
|
142
|
-
escalationLevels.push(currentLevel);
|
|
143
|
-
}
|
|
147
|
+
if (currentLevel) escalationLevels.push(currentLevel);
|
|
144
148
|
workflow.steps.push({
|
|
145
149
|
type: 'escalation',
|
|
146
150
|
levels: escalationLevels,
|
|
@@ -149,14 +153,10 @@ function parseWorkflowLines(lines, filename) {
|
|
|
149
153
|
inEscalationBlock = false;
|
|
150
154
|
continue;
|
|
151
155
|
} else if (line.match(/^Level \d+:/i)) {
|
|
152
|
-
// Parse level declaration
|
|
153
156
|
const levelMatch = line.match(/^Level (\d+):\s+(.+)$/i);
|
|
154
157
|
if (levelMatch) {
|
|
155
|
-
if (currentLevel)
|
|
156
|
-
escalationLevels.push(currentLevel);
|
|
157
|
-
}
|
|
158
|
+
if (currentLevel) escalationLevels.push(currentLevel);
|
|
158
159
|
|
|
159
|
-
// Parse timeout from level description
|
|
160
160
|
let timeoutMs = null;
|
|
161
161
|
const desc = levelMatch[2].trim().toLowerCase();
|
|
162
162
|
if (desc.includes('immediately')) {
|
|
@@ -174,34 +174,30 @@ function parseWorkflowLines(lines, filename) {
|
|
|
174
174
|
currentLevel = {
|
|
175
175
|
levelNumber: parseInt(levelMatch[1]),
|
|
176
176
|
timeout: timeoutMs,
|
|
177
|
-
steps: []
|
|
177
|
+
steps: []
|
|
178
178
|
};
|
|
179
179
|
continue;
|
|
180
180
|
}
|
|
181
181
|
} else if (currentLevel) {
|
|
182
|
-
// Parse steps within level
|
|
183
182
|
currentLevel.steps.push(line);
|
|
184
183
|
continue;
|
|
185
184
|
}
|
|
186
185
|
}
|
|
187
186
|
|
|
188
|
-
//
|
|
187
|
+
// --- 5. Block: Parallel ---
|
|
189
188
|
const timedParMatch = line.match(/^Run in parallel for (\d+)\s*([smhd])$/i);
|
|
190
189
|
if (timedParMatch) {
|
|
191
190
|
flushCurrentStep();
|
|
192
|
-
|
|
193
191
|
const value = parseInt(timedParMatch[1]);
|
|
194
192
|
const unit = timedParMatch[2].toLowerCase();
|
|
195
193
|
const multipliers = { s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
196
|
-
|
|
194
|
+
parallelTimeout = value * (multipliers[unit] || 1000);
|
|
197
195
|
|
|
198
196
|
inParallelBlock = true;
|
|
199
197
|
parallelSteps = [];
|
|
200
|
-
parallelTimeout = timeoutMs;
|
|
201
198
|
continue;
|
|
202
199
|
}
|
|
203
200
|
|
|
204
|
-
// ✅ Parse Normal Parallel block (backward compatible)
|
|
205
201
|
if (line.match(/^Run in parallel$/i)) {
|
|
206
202
|
flushCurrentStep();
|
|
207
203
|
inParallelBlock = true;
|
|
@@ -212,7 +208,7 @@ function parseWorkflowLines(lines, filename) {
|
|
|
212
208
|
|
|
213
209
|
if (inParallelBlock) {
|
|
214
210
|
if (line.match(/^End$/i)) {
|
|
215
|
-
flushCurrentStep();
|
|
211
|
+
flushCurrentStep();
|
|
216
212
|
const parsedParallel = parseBlock(parallelSteps);
|
|
217
213
|
workflow.steps.push({
|
|
218
214
|
type: 'parallel',
|
|
@@ -229,96 +225,144 @@ function parseWorkflowLines(lines, filename) {
|
|
|
229
225
|
}
|
|
230
226
|
}
|
|
231
227
|
|
|
232
|
-
//
|
|
228
|
+
// --- 6. Block: If / Else If / Else (FIXED STATE MACHINE) ---
|
|
229
|
+
|
|
230
|
+
// Start If
|
|
233
231
|
if (line.match(/^(?:If|When)\s+(.+)$/i)) {
|
|
234
232
|
flushCurrentStep();
|
|
235
233
|
const ifMatch = line.match(/^(?:If|When)\s+(.+)$/i);
|
|
236
234
|
ifCondition = ifMatch[1].trim();
|
|
237
235
|
inIfBlock = true;
|
|
238
|
-
|
|
236
|
+
|
|
237
|
+
// Reset accumulators
|
|
238
|
+
mainIfBody = [];
|
|
239
|
+
currentBranchBody = [];
|
|
240
|
+
elseIfChain = [];
|
|
241
|
+
currentElseIf = null;
|
|
242
|
+
inElseBlock = false;
|
|
239
243
|
continue;
|
|
240
244
|
}
|
|
241
245
|
|
|
242
246
|
if (inIfBlock) {
|
|
247
|
+
// Handle Else If
|
|
248
|
+
if (line.match(/^Else If\s+(.+)$/i) || line.match(/^Elseif\s+(.+)$/i)) {
|
|
249
|
+
const eiMatch = line.match(/^(?:Else If|Elseif)\s+(.+)$/i);
|
|
250
|
+
|
|
251
|
+
// Save previous branch (either main if or previous else-if)
|
|
252
|
+
if (currentElseIf) {
|
|
253
|
+
currentElseIf.body = parseBlock(currentBranchBody);
|
|
254
|
+
elseIfChain.push(currentElseIf);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Reset for new else-if branch
|
|
258
|
+
currentBranchBody = [];
|
|
259
|
+
currentElseIf = { condition: eiMatch[1].trim(), body: [] };
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Handle Else
|
|
264
|
+
if (line.match(/^Else$/i)) {
|
|
265
|
+
// Save previous else-if if it exists
|
|
266
|
+
if (currentElseIf) {
|
|
267
|
+
currentElseIf.body = parseBlock(currentBranchBody);
|
|
268
|
+
elseIfChain.push(currentElseIf);
|
|
269
|
+
currentElseIf = null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
inElseBlock = true;
|
|
273
|
+
currentBranchBody = [];
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Handle End
|
|
243
278
|
if (line.match(/^End(?:If)?$/i)) {
|
|
244
|
-
flushCurrentStep();
|
|
245
|
-
|
|
279
|
+
flushCurrentStep();
|
|
280
|
+
|
|
281
|
+
// Close any open else-if
|
|
282
|
+
if (currentElseIf) {
|
|
283
|
+
currentElseIf.body = parseBlock(currentBranchBody);
|
|
284
|
+
elseIfChain.push(currentElseIf);
|
|
285
|
+
currentElseIf = null;
|
|
286
|
+
}
|
|
287
|
+
|
|
246
288
|
workflow.steps.push({
|
|
247
289
|
type: 'if',
|
|
248
290
|
condition: ifCondition,
|
|
249
|
-
body:
|
|
291
|
+
body: parseBlock(mainIfBody), // Main body correctly isolated
|
|
292
|
+
elseIf: elseIfChain, // Else-if chain
|
|
293
|
+
elseBranch: inElseBlock ? parseBlock(currentBranchBody) : [], // Else body
|
|
250
294
|
stepNumber: workflow.steps.length + 1
|
|
251
295
|
});
|
|
296
|
+
|
|
297
|
+
// Reset State
|
|
252
298
|
inIfBlock = false;
|
|
253
299
|
ifCondition = null;
|
|
254
|
-
|
|
300
|
+
mainIfBody = [];
|
|
301
|
+
currentBranchBody = [];
|
|
302
|
+
elseIfChain = [];
|
|
303
|
+
inElseBlock = false;
|
|
255
304
|
continue;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Accumulate lines into correct bucket
|
|
308
|
+
if (inElseBlock || currentElseIf) {
|
|
309
|
+
currentBranchBody.push(line);
|
|
256
310
|
} else {
|
|
257
|
-
|
|
258
|
-
continue;
|
|
311
|
+
mainIfBody.push(line); // Main if body accumulates here
|
|
259
312
|
}
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// --- 7. Standard Steps & Keywords ---
|
|
317
|
+
|
|
318
|
+
// Connect: Connect "name" to url "..." OR Connect "name" to resolver "..."
|
|
319
|
+
const connectMatch = line.match(/^Connect\s+"([^"]+)"\s+to\s+(url|resolver)\s+"([^"]+)"$/i);
|
|
320
|
+
if (connectMatch) {
|
|
321
|
+
flushCurrentStep();
|
|
322
|
+
workflow.steps.push({
|
|
323
|
+
type: 'connect',
|
|
324
|
+
resource: connectMatch[1],
|
|
325
|
+
endpoint: connectMatch[3],
|
|
326
|
+
targetType: connectMatch[2].toLowerCase(),
|
|
327
|
+
stepNumber: workflow.steps.length + 1
|
|
328
|
+
});
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Use: Use "logicalName" as "resource"
|
|
333
|
+
const useMatch = line.match(/^Use\s+"([^"]+)"\s+as\s+"([^"]+)"$/i);
|
|
334
|
+
if (useMatch) {
|
|
335
|
+
flushCurrentStep();
|
|
336
|
+
workflow.steps.push({
|
|
337
|
+
type: 'agent_use',
|
|
338
|
+
logicalName: useMatch[1],
|
|
339
|
+
resource: useMatch[2],
|
|
340
|
+
stepNumber: workflow.steps.length + 1
|
|
341
|
+
});
|
|
342
|
+
continue;
|
|
260
343
|
}
|
|
261
344
|
|
|
262
|
-
// Step
|
|
345
|
+
// Step Declaration
|
|
263
346
|
const stepMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
|
|
264
347
|
if (stepMatch) {
|
|
265
|
-
flushCurrentStep();
|
|
266
|
-
const stepNumber = parseInt(stepMatch[1], 10);
|
|
267
|
-
const stepContent = stepMatch[2].trim(); // ← PRESERVED EXACTLY (no normalizeAction)
|
|
268
|
-
|
|
348
|
+
flushCurrentStep();
|
|
269
349
|
currentStep = {
|
|
270
350
|
type: 'action',
|
|
271
|
-
stepNumber:
|
|
272
|
-
actionRaw:
|
|
351
|
+
stepNumber: parseInt(stepMatch[1], 10),
|
|
352
|
+
actionRaw: stepMatch[2].trim(),
|
|
273
353
|
saveAs: null,
|
|
274
354
|
constraints: {}
|
|
275
355
|
};
|
|
276
356
|
continue;
|
|
277
357
|
}
|
|
278
358
|
|
|
279
|
-
// Save
|
|
359
|
+
// Save As
|
|
280
360
|
const saveMatch = line.match(/^Save as\s+(.+)$/i);
|
|
281
361
|
if (saveMatch && currentStep) {
|
|
282
362
|
currentStep.saveAs = normalizeSymbol(saveMatch[1].trim());
|
|
283
363
|
continue;
|
|
284
364
|
}
|
|
285
365
|
|
|
286
|
-
// Constraint (per-step)
|
|
287
|
-
const constraintMatch = line.match(/^Constraint:\s*(.+)$/i);
|
|
288
|
-
if (constraintMatch && currentStep) {
|
|
289
|
-
const constraintLine = constraintMatch[1].trim();
|
|
290
|
-
const eq = constraintLine.match(/^([^=]+)=\s*(.+)$/);
|
|
291
|
-
if (eq) {
|
|
292
|
-
let key = eq[1].trim();
|
|
293
|
-
let value = eq[2].trim();
|
|
294
|
-
|
|
295
|
-
if (value.startsWith('[') && value.endsWith(']')) {
|
|
296
|
-
value = value.slice(1, -1).split(',').map(v => v.trim().replace(/^"/, '').replace(/"$/, ''));
|
|
297
|
-
} else if (!isNaN(value)) {
|
|
298
|
-
value = Number(value);
|
|
299
|
-
} else if (value.startsWith('"') && value.endsWith('"')) {
|
|
300
|
-
value = value.slice(1, -1);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
currentStep.constraints[key] = value;
|
|
304
|
-
}
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// ✅ ADD: Set keyword (e.g., Set analysis_result = "")
|
|
309
|
-
const setMatch = line.match(/^Set\s+(\w+)\s*=\s*(.+)$/i);
|
|
310
|
-
if (setMatch) {
|
|
311
|
-
flushCurrentStep();
|
|
312
|
-
workflow.steps.push({
|
|
313
|
-
type: 'calculate',
|
|
314
|
-
stepNumber: workflow.steps.length + 1,
|
|
315
|
-
actionRaw: setMatch[2].trim(), // e.g., '""' or '"N/A"'
|
|
316
|
-
saveAs: setMatch[1].trim(), // e.g., 'analysis_result'
|
|
317
|
-
constraints: {}
|
|
318
|
-
});
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
366
|
// Debrief
|
|
323
367
|
const debriefMatch = line.match(/^Debrief\s+([^\s]+)\s+with\s+"([^"]*)"$/i);
|
|
324
368
|
if (debriefMatch) {
|
|
@@ -332,19 +376,6 @@ function parseWorkflowLines(lines, filename) {
|
|
|
332
376
|
continue;
|
|
333
377
|
}
|
|
334
378
|
|
|
335
|
-
// Evolve
|
|
336
|
-
const evolveMatch = line.match(/^Evolve\s+([^\s]+)\s+using\s+feedback:\s*"([^"]*)"$/i);
|
|
337
|
-
if (evolveMatch) {
|
|
338
|
-
flushCurrentStep();
|
|
339
|
-
workflow.steps.push({
|
|
340
|
-
type: 'evolve',
|
|
341
|
-
targetResolver: evolveMatch[1].trim(),
|
|
342
|
-
feedback: evolveMatch[2],
|
|
343
|
-
stepNumber: workflow.steps.length + 1
|
|
344
|
-
});
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
379
|
// Prompt
|
|
349
380
|
const promptMatch = line.match(/^Prompt user to\s+"([^"]*)"$/i);
|
|
350
381
|
if (promptMatch) {
|
|
@@ -384,29 +415,12 @@ function parseWorkflowLines(lines, filename) {
|
|
|
384
415
|
continue;
|
|
385
416
|
}
|
|
386
417
|
|
|
387
|
-
//
|
|
388
|
-
const useMatch = line.match(/^Use\s+(.+)$/i);
|
|
389
|
-
if (useMatch) {
|
|
390
|
-
flushCurrentStep();
|
|
391
|
-
workflow.steps.push({
|
|
392
|
-
type: 'use',
|
|
393
|
-
tool: useMatch[1].trim(), // ← PRESERVED EXACTLY (no normalizeAction)
|
|
394
|
-
stepNumber: workflow.steps.length + 1,
|
|
395
|
-
saveAs: null,
|
|
396
|
-
constraints: {}
|
|
397
|
-
});
|
|
398
|
-
continue;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Ask (for Notify/resolver calls) - ✅ PRESERVE TARGET EXACTLY (NO NORMALIZATION)
|
|
418
|
+
// Ask (Multiline support)
|
|
402
419
|
const askMatch = line.match(/^Ask\s+(.+)$/i);
|
|
403
420
|
if (askMatch) {
|
|
404
421
|
flushCurrentStep();
|
|
405
422
|
let actionContent = askMatch[1].trim();
|
|
406
|
-
|
|
407
|
-
// ✅ Multiline heredoc support: Ask llm-groq """
|
|
408
423
|
if (actionContent.endsWith('"""')) {
|
|
409
|
-
// Consume lines until closing """
|
|
410
424
|
let multiline = actionContent.slice(0, -3).trim() + ' ';
|
|
411
425
|
while (i < lines.length) {
|
|
412
426
|
const nextLine = lines[i++].trim();
|
|
@@ -415,7 +429,6 @@ function parseWorkflowLines(lines, filename) {
|
|
|
415
429
|
}
|
|
416
430
|
actionContent = multiline.trim();
|
|
417
431
|
}
|
|
418
|
-
|
|
419
432
|
workflow.steps.push({
|
|
420
433
|
type: 'action',
|
|
421
434
|
actionRaw: `Action ${actionContent}`,
|
|
@@ -426,59 +439,36 @@ function parseWorkflowLines(lines, filename) {
|
|
|
426
439
|
continue;
|
|
427
440
|
}
|
|
428
441
|
|
|
429
|
-
//
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
.map(r => r.trim())
|
|
442
|
-
.filter(r => r !== '');
|
|
443
|
-
|
|
444
|
-
console.log(`[PARSER DEBUG] Parsed returnValues: ${JSON.stringify(workflow.returnValues)}`);
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
// Fallback: treat as action
|
|
442
|
+
// Return
|
|
443
|
+
const returnMatch = line.match(/^Return\s+(.+)$/i);
|
|
444
|
+
if (returnMatch) {
|
|
445
|
+
flushCurrentStep();
|
|
446
|
+
workflow.returnValues = returnMatch[1]
|
|
447
|
+
.split(',')
|
|
448
|
+
.map(r => r.trim())
|
|
449
|
+
.filter(r => r !== '');
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Fallback: Treat as action
|
|
448
454
|
if (line.trim() !== '') {
|
|
449
455
|
if (!currentStep) {
|
|
450
456
|
currentStep = {
|
|
451
457
|
type: 'action',
|
|
452
458
|
stepNumber: workflow.steps.length + 1,
|
|
453
|
-
actionRaw: line,
|
|
459
|
+
actionRaw: line,
|
|
454
460
|
saveAs: null,
|
|
455
461
|
constraints: {}
|
|
456
462
|
};
|
|
457
463
|
} else {
|
|
458
|
-
currentStep.actionRaw += ' ' + line;
|
|
464
|
+
currentStep.actionRaw += ' ' + line;
|
|
459
465
|
}
|
|
460
466
|
}
|
|
461
467
|
}
|
|
462
468
|
|
|
463
|
-
flushCurrentStep();
|
|
464
|
-
|
|
465
|
-
// ✅ FALLBACK: Scan raw lines for Return if regex missed it (Windows line endings, hidden chars, etc.)
|
|
466
|
-
if (workflow.returnValues.length === 0) {
|
|
467
|
-
for (let j = 0; j < lines.length; j++) {
|
|
468
|
-
const clean = lines[j].replace(/\r/g, '').trim();
|
|
469
|
-
const match = clean.match(/^Return\s+(.+)$/i);
|
|
470
|
-
if (match) {
|
|
471
|
-
console.log(`[PARSER] Recovered Return at line ${j+1}: "${clean}"`);
|
|
472
|
-
workflow.returnValues = match[1]
|
|
473
|
-
.split(',')
|
|
474
|
-
.map(r => r.trim())
|
|
475
|
-
.filter(r => r !== '');
|
|
476
|
-
break;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
469
|
+
flushCurrentStep();
|
|
480
470
|
|
|
481
|
-
// Post-process Save as in actionRaw
|
|
471
|
+
// Post-process Save as in actionRaw
|
|
482
472
|
workflow.steps.forEach(step => {
|
|
483
473
|
if (step.actionRaw && step.saveAs === null) {
|
|
484
474
|
const saveInAction = step.actionRaw.match(/(.+?)\s+Save as\s+(.+)$/i);
|
|
@@ -487,27 +477,18 @@ if (returnMatch) {
|
|
|
487
477
|
step.saveAs = normalizeSymbol(saveInAction[2].trim());
|
|
488
478
|
}
|
|
489
479
|
}
|
|
490
|
-
// ✅ Also normalize any existing saveAs values
|
|
491
480
|
if (step.saveAs) {
|
|
492
481
|
step.saveAs = normalizeSymbol(step.saveAs);
|
|
493
482
|
}
|
|
494
483
|
});
|
|
495
484
|
|
|
496
|
-
// Validation warnings
|
|
497
|
-
if (!workflow.name) {
|
|
498
|
-
workflow.__warnings.push('Workflow name not found');
|
|
499
|
-
}
|
|
500
|
-
if (workflow.steps.length === 0) {
|
|
501
|
-
workflow.__warnings.push('No steps found in workflow');
|
|
502
|
-
}
|
|
503
|
-
if (workflow.returnValues.length === 0 && workflow.steps.length > 0) {
|
|
504
|
-
workflow.__warnings.push('No Return statement found');
|
|
505
|
-
}
|
|
506
|
-
|
|
507
485
|
return workflow;
|
|
508
486
|
}
|
|
509
487
|
|
|
510
|
-
//
|
|
488
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
489
|
+
// BLOCK PARSER (Exported for RuntimeAPI Escalation)
|
|
490
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
491
|
+
|
|
511
492
|
function parseBlock(lines) {
|
|
512
493
|
const steps = [];
|
|
513
494
|
let current = null;
|
|
@@ -523,31 +504,53 @@ function parseBlock(lines) {
|
|
|
523
504
|
line = line.trim();
|
|
524
505
|
if (!line || line.startsWith('#')) continue;
|
|
525
506
|
|
|
526
|
-
//
|
|
507
|
+
// Connect in Block
|
|
508
|
+
const connectMatch = line.match(/^Connect\s+"([^"]+)"\s+to\s+(url|resolver)\s+"([^"]+)"$/i);
|
|
509
|
+
if (connectMatch) {
|
|
510
|
+
flush();
|
|
511
|
+
steps.push({
|
|
512
|
+
type: 'connect',
|
|
513
|
+
resource: connectMatch[1],
|
|
514
|
+
endpoint: connectMatch[3],
|
|
515
|
+
targetType: connectMatch[2].toLowerCase()
|
|
516
|
+
});
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Use in Block
|
|
521
|
+
const useMatch = line.match(/^Use\s+"([^"]+)"\s+as\s+"([^"]+)"$/i);
|
|
522
|
+
if (useMatch) {
|
|
523
|
+
flush();
|
|
524
|
+
steps.push({
|
|
525
|
+
type: 'agent_use',
|
|
526
|
+
logicalName: useMatch[1],
|
|
527
|
+
resource: useMatch[2]
|
|
528
|
+
});
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Step in Block
|
|
527
533
|
const stepMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
|
|
528
534
|
if (stepMatch) {
|
|
529
535
|
flush();
|
|
530
|
-
const stepNumber = parseInt(stepMatch[1], 10);
|
|
531
|
-
const stepContent = stepMatch[2].trim(); // ← PRESERVED EXACTLY
|
|
532
|
-
|
|
533
536
|
current = {
|
|
534
537
|
type: 'action',
|
|
535
|
-
stepNumber:
|
|
536
|
-
actionRaw:
|
|
538
|
+
stepNumber: parseInt(stepMatch[1], 10),
|
|
539
|
+
actionRaw: stepMatch[2].trim(),
|
|
537
540
|
saveAs: null,
|
|
538
541
|
constraints: {}
|
|
539
542
|
};
|
|
540
543
|
continue;
|
|
541
544
|
}
|
|
542
545
|
|
|
543
|
-
// Save
|
|
546
|
+
// Save As in Block
|
|
544
547
|
const saveMatch = line.match(/^Save as\s+(.+)$/i);
|
|
545
548
|
if (saveMatch && current) {
|
|
546
549
|
current.saveAs = normalizeSymbol(saveMatch[1].trim());
|
|
547
550
|
continue;
|
|
548
551
|
}
|
|
549
552
|
|
|
550
|
-
//
|
|
553
|
+
// Debrief in Block
|
|
551
554
|
const debriefMatch = line.match(/^Debrief\s+([^\s]+)\s+with\s+"([^"]*)"$/i);
|
|
552
555
|
if (debriefMatch) {
|
|
553
556
|
flush();
|
|
@@ -555,20 +558,7 @@ function parseBlock(lines) {
|
|
|
555
558
|
continue;
|
|
556
559
|
}
|
|
557
560
|
|
|
558
|
-
|
|
559
|
-
if (evolveMatch) {
|
|
560
|
-
flush();
|
|
561
|
-
steps.push({ type: 'evolve', targetResolver: evolveMatch[1].trim(), feedback: evolveMatch[2] });
|
|
562
|
-
continue;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
const promptMatch = line.match(/^Prompt user to\s+"([^"]*)"$/i);
|
|
566
|
-
if (promptMatch) {
|
|
567
|
-
flush();
|
|
568
|
-
steps.push({ type: 'prompt', question: promptMatch[1], saveAs: null });
|
|
569
|
-
continue;
|
|
570
|
-
}
|
|
571
|
-
|
|
561
|
+
// Persist in Block
|
|
572
562
|
const persistMatch = line.match(/^Persist\s+([^\s]+)\s+to\s+"([^"]*)"$/i);
|
|
573
563
|
if (persistMatch) {
|
|
574
564
|
flush();
|
|
@@ -576,6 +566,7 @@ function parseBlock(lines) {
|
|
|
576
566
|
continue;
|
|
577
567
|
}
|
|
578
568
|
|
|
569
|
+
// Emit in Block
|
|
579
570
|
const emitMatch = line.match(/^Emit\s+"([^"]+)"\s+with\s+(.+)$/i);
|
|
580
571
|
if (emitMatch) {
|
|
581
572
|
flush();
|
|
@@ -583,20 +574,7 @@ function parseBlock(lines) {
|
|
|
583
574
|
continue;
|
|
584
575
|
}
|
|
585
576
|
|
|
586
|
-
//
|
|
587
|
-
const useMatch = line.match(/^Use\s+(.+)$/i);
|
|
588
|
-
if (useMatch) {
|
|
589
|
-
flush();
|
|
590
|
-
steps.push({
|
|
591
|
-
type: 'use',
|
|
592
|
-
tool: useMatch[1].trim(),
|
|
593
|
-
saveAs: null,
|
|
594
|
-
constraints: {}
|
|
595
|
-
});
|
|
596
|
-
continue;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// Ask in block — CANONICALIZE AT PARSE TIME
|
|
577
|
+
// Ask in Block
|
|
600
578
|
const askMatch = line.match(/^Ask\s+(.+)$/i);
|
|
601
579
|
if (askMatch) {
|
|
602
580
|
flush();
|
|
@@ -609,56 +587,15 @@ function parseBlock(lines) {
|
|
|
609
587
|
continue;
|
|
610
588
|
}
|
|
611
589
|
|
|
612
|
-
//
|
|
613
|
-
const constraintMatch = line.match(/^Constraint:\s*(.+)$/i);
|
|
614
|
-
if (constraintMatch && current) {
|
|
615
|
-
const constraintLine = constraintMatch[1].trim();
|
|
616
|
-
const eq = constraintLine.match(/^([^=]+)=\s*(.+)$/);
|
|
617
|
-
if (eq) {
|
|
618
|
-
let key = eq[1].trim();
|
|
619
|
-
let value = eq[2].trim();
|
|
620
|
-
if (value.startsWith('[') && value.endsWith(']')) {
|
|
621
|
-
value = value.slice(1, -1).split(',').map(v => v.trim().replace(/^"/, '').replace(/"$/, ''));
|
|
622
|
-
} else if (!isNaN(value)) {
|
|
623
|
-
value = Number(value);
|
|
624
|
-
} else if (value.startsWith('"') && value.endsWith('"')) {
|
|
625
|
-
value = value.slice(1, -1);
|
|
626
|
-
}
|
|
627
|
-
current.constraints[key] = value;
|
|
628
|
-
}
|
|
629
|
-
continue;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
// ✅ ADD: Set keyword inside blocks (e.g., Set analysis_result = "")
|
|
633
|
-
const setMatch = line.match(/^Set\s+(\w+)\s*=\s*(.+)$/i);
|
|
634
|
-
if (setMatch) {
|
|
635
|
-
flush(); // Flush any pending step
|
|
636
|
-
steps.push({
|
|
637
|
-
type: 'calculate',
|
|
638
|
-
actionRaw: setMatch[2].trim(), // e.g., '""' or '"N/A"'
|
|
639
|
-
saveAs: setMatch[1].trim(), // e.g., 'analysis_result'
|
|
640
|
-
constraints: {}
|
|
641
|
-
});
|
|
642
|
-
continue;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// ✅ ADD: Return keyword inside blocks (rare but supported)
|
|
646
|
-
const returnMatch = line.match(/^Return\s+(.+)$/i);
|
|
647
|
-
if (returnMatch) {
|
|
648
|
-
flush();
|
|
649
|
-
// Note: Return inside blocks is unusual; this just parses it
|
|
650
|
-
continue;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// Fallback
|
|
590
|
+
// Fallback for block actions
|
|
654
591
|
if (current) {
|
|
655
|
-
current.actionRaw += ' ' + line;
|
|
592
|
+
current.actionRaw += ' ' + line;
|
|
656
593
|
}
|
|
657
594
|
}
|
|
658
595
|
|
|
659
|
-
flush();
|
|
596
|
+
flush();
|
|
660
597
|
|
|
661
|
-
//
|
|
598
|
+
// Post-process Save as in block steps
|
|
662
599
|
steps.forEach(step => {
|
|
663
600
|
if (step.actionRaw && step.saveAs === null) {
|
|
664
601
|
const saveInAction = step.actionRaw.match(/(.+?)\s+Save as\s+(.+)$/i);
|
|
@@ -667,7 +604,6 @@ function parseBlock(lines) {
|
|
|
667
604
|
step.saveAs = normalizeSymbol(saveInAction[2].trim());
|
|
668
605
|
}
|
|
669
606
|
}
|
|
670
|
-
// ✅ Also normalize any existing saveAs values
|
|
671
607
|
if (step.saveAs) {
|
|
672
608
|
step.saveAs = normalizeSymbol(step.saveAs);
|
|
673
609
|
}
|
|
@@ -676,12 +612,5 @@ function parseBlock(lines) {
|
|
|
676
612
|
return steps;
|
|
677
613
|
}
|
|
678
614
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
if (workflow.maxGenerations !== null && workflow.maxGenerations <= 0) {
|
|
682
|
-
errors.push('max_generations must be positive');
|
|
683
|
-
}
|
|
684
|
-
return errors;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
module.exports = { parse, parseFromFile, parseLines, validate };
|
|
615
|
+
// ✅ EXPORTS: Added parseBlock so RuntimeAPI can use it for Escalation
|
|
616
|
+
module.exports = { parse, parseFromFile, parseLines, parseBlock };
|