@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/parser.js
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
|
|
3
|
+
// ✅ Symbol normalization helper (backward compatible)
|
|
4
|
+
function normalizeSymbol(raw) {
|
|
5
|
+
if (!raw) return raw;
|
|
6
|
+
// Take only the first word (stop at first whitespace)
|
|
7
|
+
// Keep letters, numbers, underscores, and $ (for JS compatibility)
|
|
8
|
+
return raw.split(/\s+/)[0].replace(/[^\w$]/g, '');
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
function parse(content, filename = '<unknown>') {
|
|
4
12
|
if (typeof content === 'string') {
|
|
13
|
+
// ✅ Strip UTF-8 BOM if present (0xFEFF = Unicode BOM)
|
|
14
|
+
if (content.charCodeAt(0) === 0xFEFF) {
|
|
15
|
+
content = content.slice(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
5
18
|
const lines = content.split('\n').map(line => line.replace(/\r$/, ''));
|
|
6
19
|
return parseLines(lines, filename);
|
|
7
20
|
} else if (typeof content === 'object' && content !== null) {
|
|
8
|
-
// Already parsed
|
|
9
21
|
return content;
|
|
10
22
|
} else {
|
|
11
23
|
throw new Error('parse() expects string content or pre-parsed object');
|
|
@@ -13,12 +25,15 @@ function parse(content, filename = '<unknown>') {
|
|
|
13
25
|
}
|
|
14
26
|
|
|
15
27
|
function parseFromFile(filepath) {
|
|
28
|
+
// Enforce .ol extension
|
|
29
|
+
if (!filepath.endsWith(".ol")) {
|
|
30
|
+
throw new Error(`Expected .ol workflow, got: ${filepath}`);
|
|
31
|
+
}
|
|
16
32
|
const content = fs.readFileSync(filepath, 'utf8');
|
|
17
33
|
return parse(content, filepath);
|
|
18
34
|
}
|
|
19
35
|
|
|
20
36
|
function parseLines(lines, filename) {
|
|
21
|
-
// Remove evolution file parsing - evolution is now in-workflow
|
|
22
37
|
return parseWorkflowLines(lines, filename);
|
|
23
38
|
}
|
|
24
39
|
|
|
@@ -30,27 +45,40 @@ function parseWorkflowLines(lines, filename) {
|
|
|
30
45
|
steps: [],
|
|
31
46
|
returnValues: [],
|
|
32
47
|
allowedResolvers: [],
|
|
33
|
-
maxGenerations: null,
|
|
48
|
+
maxGenerations: null,
|
|
34
49
|
__warnings: [],
|
|
35
50
|
filename: filename
|
|
36
51
|
};
|
|
37
|
-
|
|
52
|
+
|
|
38
53
|
let i = 0;
|
|
39
54
|
let currentStep = null;
|
|
40
55
|
let inAllowResolvers = false;
|
|
41
56
|
let inIfBlock = false;
|
|
42
57
|
let ifCondition = null;
|
|
43
58
|
let ifBody = [];
|
|
44
|
-
|
|
59
|
+
let inParallelBlock = false;
|
|
60
|
+
let parallelSteps = [];
|
|
61
|
+
let parallelTimeout = null;
|
|
62
|
+
let inEscalationBlock = false;
|
|
63
|
+
let escalationLevels = [];
|
|
64
|
+
let currentLevel = null;
|
|
65
|
+
|
|
66
|
+
// ✅ Helper to flush currentStep
|
|
67
|
+
const flushCurrentStep = () => {
|
|
68
|
+
if (currentStep) {
|
|
69
|
+
workflow.steps.push(currentStep);
|
|
70
|
+
currentStep = null;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
45
74
|
while (i < lines.length) {
|
|
46
75
|
let line = lines[i++].trim();
|
|
47
|
-
|
|
48
|
-
// Skip empty lines and comments
|
|
76
|
+
|
|
49
77
|
if (line === '' || line.startsWith('#')) {
|
|
50
78
|
continue;
|
|
51
79
|
}
|
|
52
|
-
|
|
53
|
-
//
|
|
80
|
+
|
|
81
|
+
// Workflow declaration
|
|
54
82
|
if (line.startsWith('Workflow ')) {
|
|
55
83
|
const match = line.match(/^Workflow\s+"([^"]+)"(?:\s+with\s+(.+))?$/i);
|
|
56
84
|
if (match) {
|
|
@@ -63,8 +91,8 @@ function parseWorkflowLines(lines, filename) {
|
|
|
63
91
|
}
|
|
64
92
|
continue;
|
|
65
93
|
}
|
|
66
|
-
|
|
67
|
-
//
|
|
94
|
+
|
|
95
|
+
// Global Constraint: max_generations
|
|
68
96
|
if (line.startsWith('Constraint: max_generations = ')) {
|
|
69
97
|
const match = line.match(/^Constraint:\s+max_generations\s*=\s*(\d+)$/i);
|
|
70
98
|
if (match) {
|
|
@@ -74,13 +102,13 @@ function parseWorkflowLines(lines, filename) {
|
|
|
74
102
|
}
|
|
75
103
|
continue;
|
|
76
104
|
}
|
|
77
|
-
|
|
78
|
-
//
|
|
105
|
+
|
|
106
|
+
// Allow resolvers section
|
|
79
107
|
if (line === 'Allow resolvers:') {
|
|
80
108
|
inAllowResolvers = true;
|
|
81
109
|
continue;
|
|
82
110
|
}
|
|
83
|
-
|
|
111
|
+
|
|
84
112
|
if (inAllowResolvers) {
|
|
85
113
|
if (line.startsWith('- ')) {
|
|
86
114
|
const resolverName = line.substring(2).trim();
|
|
@@ -88,28 +116,153 @@ function parseWorkflowLines(lines, filename) {
|
|
|
88
116
|
workflow.allowedResolvers.push(resolverName);
|
|
89
117
|
}
|
|
90
118
|
} else if (line === '' || line.startsWith('#')) {
|
|
91
|
-
|
|
119
|
+
continue;
|
|
92
120
|
} else {
|
|
93
|
-
// End of Allow resolvers section
|
|
94
121
|
inAllowResolvers = false;
|
|
95
|
-
i--;
|
|
122
|
+
i--;
|
|
96
123
|
continue;
|
|
97
124
|
}
|
|
98
125
|
continue;
|
|
99
126
|
}
|
|
100
|
-
|
|
101
|
-
// Parse
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
127
|
+
|
|
128
|
+
// ✅ Parse Escalation block
|
|
129
|
+
if (line.match(/^Run in parallel with escalation:$/i)) {
|
|
130
|
+
flushCurrentStep();
|
|
131
|
+
inEscalationBlock = true;
|
|
132
|
+
escalationLevels = [];
|
|
133
|
+
currentLevel = null;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (inEscalationBlock) {
|
|
138
|
+
if (line.match(/^End$/i)) {
|
|
139
|
+
if (currentLevel) {
|
|
140
|
+
escalationLevels.push(currentLevel);
|
|
141
|
+
}
|
|
142
|
+
workflow.steps.push({
|
|
143
|
+
type: 'escalation',
|
|
144
|
+
levels: escalationLevels,
|
|
145
|
+
stepNumber: workflow.steps.length + 1
|
|
146
|
+
});
|
|
147
|
+
inEscalationBlock = false;
|
|
148
|
+
continue;
|
|
149
|
+
} else if (line.match(/^Level \d+:/i)) {
|
|
150
|
+
// Parse level declaration
|
|
151
|
+
const levelMatch = line.match(/^Level (\d+):\s+(.+)$/i);
|
|
152
|
+
if (levelMatch) {
|
|
153
|
+
if (currentLevel) {
|
|
154
|
+
escalationLevels.push(currentLevel);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Parse timeout from level description
|
|
158
|
+
let timeoutMs = null;
|
|
159
|
+
const desc = levelMatch[2].trim().toLowerCase();
|
|
160
|
+
if (desc.includes('immediately')) {
|
|
161
|
+
timeoutMs = 0;
|
|
162
|
+
} else {
|
|
163
|
+
const timeMatch = desc.match(/within\s+(\d+)\s*([smhd])/i);
|
|
164
|
+
if (timeMatch) {
|
|
165
|
+
const value = parseInt(timeMatch[1]);
|
|
166
|
+
const unit = timeMatch[2].toLowerCase();
|
|
167
|
+
const multipliers = { s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
168
|
+
timeoutMs = value * (multipliers[unit] || 1000);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
currentLevel = {
|
|
173
|
+
levelNumber: parseInt(levelMatch[1]),
|
|
174
|
+
timeout: timeoutMs,
|
|
175
|
+
steps: []
|
|
176
|
+
};
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
} else if (currentLevel) {
|
|
180
|
+
// Parse steps within level
|
|
181
|
+
currentLevel.steps.push(line);
|
|
182
|
+
continue;
|
|
108
183
|
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ✅ Parse Timed Parallel block (EXACT FORMAT - NO DUPLICATION)
|
|
187
|
+
const timedParMatch = line.match(/^Run in parallel for (\d+)\s*([smhd])$/i);
|
|
188
|
+
if (timedParMatch) {
|
|
189
|
+
flushCurrentStep();
|
|
190
|
+
|
|
191
|
+
const value = parseInt(timedParMatch[1]);
|
|
192
|
+
const unit = timedParMatch[2].toLowerCase();
|
|
193
|
+
const multipliers = { s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
194
|
+
const timeoutMs = value * (multipliers[unit] || 1000);
|
|
109
195
|
|
|
196
|
+
inParallelBlock = true;
|
|
197
|
+
parallelSteps = [];
|
|
198
|
+
parallelTimeout = timeoutMs;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ✅ Parse Normal Parallel block (backward compatible)
|
|
203
|
+
if (line.match(/^Run in parallel$/i)) {
|
|
204
|
+
flushCurrentStep();
|
|
205
|
+
inParallelBlock = true;
|
|
206
|
+
parallelSteps = [];
|
|
207
|
+
parallelTimeout = null;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (inParallelBlock) {
|
|
212
|
+
if (line.match(/^End$/i)) {
|
|
213
|
+
flushCurrentStep(); // ✅ Flush last parallel step
|
|
214
|
+
const parsedParallel = parseBlock(parallelSteps);
|
|
215
|
+
workflow.steps.push({
|
|
216
|
+
type: 'parallel',
|
|
217
|
+
steps: parsedParallel,
|
|
218
|
+
timeout: parallelTimeout,
|
|
219
|
+
stepNumber: workflow.steps.length + 1
|
|
220
|
+
});
|
|
221
|
+
inParallelBlock = false;
|
|
222
|
+
parallelTimeout = null;
|
|
223
|
+
continue;
|
|
224
|
+
} else {
|
|
225
|
+
parallelSteps.push(line);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ✅ FLUSH before If/When block
|
|
231
|
+
if (line.match(/^(?:If|When)\s+(.+)$/i)) {
|
|
232
|
+
flushCurrentStep();
|
|
233
|
+
const ifMatch = line.match(/^(?:If|When)\s+(.+)$/i);
|
|
234
|
+
ifCondition = ifMatch[1].trim();
|
|
235
|
+
inIfBlock = true;
|
|
236
|
+
ifBody = [];
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (inIfBlock) {
|
|
241
|
+
if (line.match(/^End(?:If)?$/i)) {
|
|
242
|
+
flushCurrentStep(); // ✅ Flush last if step
|
|
243
|
+
const parsedIfBody = parseBlock(ifBody);
|
|
244
|
+
workflow.steps.push({
|
|
245
|
+
type: 'if',
|
|
246
|
+
condition: ifCondition,
|
|
247
|
+
body: parsedIfBody,
|
|
248
|
+
stepNumber: workflow.steps.length + 1
|
|
249
|
+
});
|
|
250
|
+
inIfBlock = false;
|
|
251
|
+
ifCondition = null;
|
|
252
|
+
ifBody = [];
|
|
253
|
+
continue;
|
|
254
|
+
} else {
|
|
255
|
+
ifBody.push(line);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Step declaration
|
|
261
|
+
const stepMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
|
|
262
|
+
if (stepMatch) {
|
|
263
|
+
flushCurrentStep(); // ✅ Flush previous step
|
|
110
264
|
const stepNumber = parseInt(stepMatch[1], 10);
|
|
111
265
|
const stepContent = stepMatch[2];
|
|
112
|
-
|
|
113
266
|
currentStep = {
|
|
114
267
|
type: 'action',
|
|
115
268
|
stepNumber: stepNumber,
|
|
@@ -119,81 +272,138 @@ function parseWorkflowLines(lines, filename) {
|
|
|
119
272
|
};
|
|
120
273
|
continue;
|
|
121
274
|
}
|
|
122
|
-
|
|
123
|
-
//
|
|
275
|
+
|
|
276
|
+
// Save as - ✅ Apply normalization
|
|
277
|
+
const saveMatch = line.match(/^Save as\s+(.+)$/i);
|
|
278
|
+
if (saveMatch && currentStep) {
|
|
279
|
+
currentStep.saveAs = normalizeSymbol(saveMatch[1].trim());
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Constraint (per-step)
|
|
284
|
+
const constraintMatch = line.match(/^Constraint:\s*(.+)$/i);
|
|
285
|
+
if (constraintMatch && currentStep) {
|
|
286
|
+
const constraintLine = constraintMatch[1].trim();
|
|
287
|
+
const eq = constraintLine.match(/^([^=]+)=\s*(.+)$/);
|
|
288
|
+
if (eq) {
|
|
289
|
+
let key = eq[1].trim();
|
|
290
|
+
let value = eq[2].trim();
|
|
291
|
+
|
|
292
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
293
|
+
value = value.slice(1, -1).split(',').map(v => v.trim().replace(/^"/, '').replace(/"$/, ''));
|
|
294
|
+
} else if (!isNaN(value)) {
|
|
295
|
+
value = Number(value);
|
|
296
|
+
} else if (value.startsWith('"') && value.endsWith('"')) {
|
|
297
|
+
value = value.slice(1, -1);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
currentStep.constraints[key] = value;
|
|
301
|
+
}
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Debrief
|
|
306
|
+
const debriefMatch = line.match(/^Debrief\s+([^\s]+)\s+with\s+"([^"]*)"$/i);
|
|
307
|
+
if (debriefMatch) {
|
|
308
|
+
flushCurrentStep();
|
|
309
|
+
workflow.steps.push({
|
|
310
|
+
type: 'debrief',
|
|
311
|
+
agent: debriefMatch[1].trim(),
|
|
312
|
+
message: debriefMatch[2],
|
|
313
|
+
stepNumber: workflow.steps.length + 1
|
|
314
|
+
});
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Evolve
|
|
124
319
|
const evolveMatch = line.match(/^Evolve\s+([^\s]+)\s+using\s+feedback:\s*"([^"]*)"$/i);
|
|
125
320
|
if (evolveMatch) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
currentStep = null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
currentStep = {
|
|
321
|
+
flushCurrentStep();
|
|
322
|
+
workflow.steps.push({
|
|
132
323
|
type: 'evolve',
|
|
133
|
-
stepNumber: workflow.steps.length + 1,
|
|
134
324
|
targetResolver: evolveMatch[1].trim(),
|
|
135
325
|
feedback: evolveMatch[2],
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
};
|
|
326
|
+
stepNumber: workflow.steps.length + 1
|
|
327
|
+
});
|
|
139
328
|
continue;
|
|
140
329
|
}
|
|
141
|
-
|
|
142
|
-
//
|
|
143
|
-
const
|
|
144
|
-
if (
|
|
145
|
-
|
|
330
|
+
|
|
331
|
+
// Prompt
|
|
332
|
+
const promptMatch = line.match(/^Prompt user to\s+"([^"]*)"$/i);
|
|
333
|
+
if (promptMatch) {
|
|
334
|
+
flushCurrentStep();
|
|
335
|
+
workflow.steps.push({
|
|
336
|
+
type: 'prompt',
|
|
337
|
+
question: promptMatch[1],
|
|
338
|
+
stepNumber: workflow.steps.length + 1,
|
|
339
|
+
saveAs: null
|
|
340
|
+
});
|
|
146
341
|
continue;
|
|
147
342
|
}
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
343
|
+
|
|
344
|
+
// Persist
|
|
345
|
+
const persistMatch = line.match(/^Persist\s+([^\s]+)\s+to\s+"([^"]*)"$/i);
|
|
346
|
+
if (persistMatch) {
|
|
347
|
+
flushCurrentStep();
|
|
348
|
+
workflow.steps.push({
|
|
349
|
+
type: 'persist',
|
|
350
|
+
variable: persistMatch[1].trim(),
|
|
351
|
+
target: persistMatch[2],
|
|
352
|
+
stepNumber: workflow.steps.length + 1
|
|
353
|
+
});
|
|
155
354
|
continue;
|
|
156
355
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
currentStep = null;
|
|
163
|
-
}
|
|
164
|
-
|
|
356
|
+
|
|
357
|
+
// Emit
|
|
358
|
+
const emitMatch = line.match(/^Emit\s+"([^"]+)"\s+with\s+(.+)$/i);
|
|
359
|
+
if (emitMatch) {
|
|
360
|
+
flushCurrentStep();
|
|
165
361
|
workflow.steps.push({
|
|
166
|
-
type: '
|
|
167
|
-
|
|
168
|
-
|
|
362
|
+
type: 'emit',
|
|
363
|
+
event: emitMatch[1],
|
|
364
|
+
payload: emitMatch[2].trim(),
|
|
169
365
|
stepNumber: workflow.steps.length + 1
|
|
170
366
|
});
|
|
171
|
-
|
|
172
|
-
inIfBlock = false;
|
|
173
|
-
ifCondition = null;
|
|
174
|
-
ifBody = [];
|
|
175
367
|
continue;
|
|
176
368
|
}
|
|
177
|
-
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
369
|
+
|
|
370
|
+
// Use (for Notify-like actions)
|
|
371
|
+
const useMatch = line.match(/^Use\s+(.+)$/i);
|
|
372
|
+
if (useMatch) {
|
|
373
|
+
flushCurrentStep();
|
|
374
|
+
workflow.steps.push({
|
|
375
|
+
type: 'use',
|
|
376
|
+
tool: useMatch[1].trim(),
|
|
377
|
+
stepNumber: workflow.steps.length + 1,
|
|
378
|
+
saveAs: null,
|
|
379
|
+
constraints: {}
|
|
380
|
+
});
|
|
181
381
|
continue;
|
|
182
382
|
}
|
|
183
|
-
|
|
184
|
-
//
|
|
383
|
+
|
|
384
|
+
// Ask (for Notify/resolver calls)
|
|
385
|
+
const askMatch = line.match(/^Ask\s+(.+)$/i);
|
|
386
|
+
if (askMatch) {
|
|
387
|
+
flushCurrentStep();
|
|
388
|
+
workflow.steps.push({
|
|
389
|
+
type: 'ask',
|
|
390
|
+
target: askMatch[1].trim(),
|
|
391
|
+
stepNumber: workflow.steps.length + 1,
|
|
392
|
+
saveAs: null,
|
|
393
|
+
constraints: {}
|
|
394
|
+
});
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Return
|
|
185
399
|
const returnMatch = line.match(/^Return\s+(.+)$/i);
|
|
186
400
|
if (returnMatch) {
|
|
187
|
-
|
|
188
|
-
workflow.steps.push(currentStep);
|
|
189
|
-
currentStep = null;
|
|
190
|
-
}
|
|
401
|
+
flushCurrentStep();
|
|
191
402
|
workflow.returnValues = returnMatch[1].split(',').map(r => r.trim()).filter(r => r !== '');
|
|
192
403
|
continue;
|
|
193
404
|
}
|
|
194
|
-
|
|
195
|
-
//
|
|
196
|
-
// Try to handle it as a step
|
|
405
|
+
|
|
406
|
+
// Fallback: treat as action
|
|
197
407
|
if (line.trim() !== '') {
|
|
198
408
|
if (!currentStep) {
|
|
199
409
|
currentStep = {
|
|
@@ -204,51 +414,179 @@ function parseWorkflowLines(lines, filename) {
|
|
|
204
414
|
constraints: {}
|
|
205
415
|
};
|
|
206
416
|
} else {
|
|
207
|
-
// Append to current step action (multi-line)
|
|
208
417
|
currentStep.actionRaw += ' ' + line;
|
|
209
418
|
}
|
|
210
419
|
}
|
|
211
420
|
}
|
|
212
|
-
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Post-process steps to extract Save as from actionRaw
|
|
421
|
+
|
|
422
|
+
flushCurrentStep(); // ✅ Final flush
|
|
423
|
+
|
|
424
|
+
// Post-process Save as in actionRaw - ✅ Apply normalization
|
|
219
425
|
workflow.steps.forEach(step => {
|
|
220
426
|
if (step.actionRaw && step.saveAs === null) {
|
|
221
427
|
const saveInAction = step.actionRaw.match(/(.+?)\s+Save as\s+(.+)$/i);
|
|
222
428
|
if (saveInAction) {
|
|
223
429
|
step.actionRaw = saveInAction[1].trim();
|
|
224
|
-
step.saveAs = saveInAction[2].trim();
|
|
430
|
+
step.saveAs = normalizeSymbol(saveInAction[2].trim());
|
|
225
431
|
}
|
|
226
432
|
}
|
|
433
|
+
// ✅ Also normalize any existing saveAs values
|
|
434
|
+
if (step.saveAs) {
|
|
435
|
+
step.saveAs = normalizeSymbol(step.saveAs);
|
|
436
|
+
}
|
|
227
437
|
});
|
|
228
|
-
|
|
229
|
-
//
|
|
438
|
+
|
|
439
|
+
// Validation warnings
|
|
230
440
|
if (!workflow.name) {
|
|
231
441
|
workflow.__warnings.push('Workflow name not found');
|
|
232
442
|
}
|
|
233
|
-
|
|
234
443
|
if (workflow.steps.length === 0) {
|
|
235
444
|
workflow.__warnings.push('No steps found in workflow');
|
|
236
445
|
}
|
|
237
|
-
|
|
238
446
|
if (workflow.returnValues.length === 0 && workflow.steps.length > 0) {
|
|
239
447
|
workflow.__warnings.push('No Return statement found');
|
|
240
448
|
}
|
|
241
|
-
|
|
449
|
+
|
|
242
450
|
return workflow;
|
|
243
451
|
}
|
|
244
452
|
|
|
453
|
+
// Parses blocks (for parallel, if, escalation levels)
|
|
454
|
+
function parseBlock(lines) {
|
|
455
|
+
const steps = [];
|
|
456
|
+
let current = null;
|
|
457
|
+
|
|
458
|
+
const flush = () => {
|
|
459
|
+
if (current) {
|
|
460
|
+
steps.push(current);
|
|
461
|
+
current = null;
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
for (let line of lines) {
|
|
466
|
+
line = line.trim();
|
|
467
|
+
if (!line || line.startsWith('#')) continue;
|
|
468
|
+
|
|
469
|
+
const stepMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
|
|
470
|
+
if (stepMatch) {
|
|
471
|
+
flush();
|
|
472
|
+
current = {
|
|
473
|
+
type: 'action',
|
|
474
|
+
stepNumber: parseInt(stepMatch[1], 10),
|
|
475
|
+
actionRaw: stepMatch[2].trim(),
|
|
476
|
+
saveAs: null,
|
|
477
|
+
constraints: {}
|
|
478
|
+
};
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Save as - ✅ Apply normalization
|
|
483
|
+
const saveMatch = line.match(/^Save as\s+(.+)$/i);
|
|
484
|
+
if (saveMatch && current) {
|
|
485
|
+
current.saveAs = normalizeSymbol(saveMatch[1].trim());
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Handle all special steps inside blocks
|
|
490
|
+
const debriefMatch = line.match(/^Debrief\s+([^\s]+)\s+with\s+"([^"]*)"$/i);
|
|
491
|
+
if (debriefMatch) {
|
|
492
|
+
flush();
|
|
493
|
+
steps.push({ type: 'debrief', agent: debriefMatch[1].trim(), message: debriefMatch[2] });
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const evolveMatch = line.match(/^Evolve\s+([^\s]+)\s+using\s+feedback:\s*"([^"]*)"$/i);
|
|
498
|
+
if (evolveMatch) {
|
|
499
|
+
flush();
|
|
500
|
+
steps.push({ type: 'evolve', targetResolver: evolveMatch[1].trim(), feedback: evolveMatch[2] });
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const promptMatch = line.match(/^Prompt user to\s+"([^"]*)"$/i);
|
|
505
|
+
if (promptMatch) {
|
|
506
|
+
flush();
|
|
507
|
+
steps.push({ type: 'prompt', question: promptMatch[1], saveAs: null });
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const persistMatch = line.match(/^Persist\s+([^\s]+)\s+to\s+"([^"]*)"$/i);
|
|
512
|
+
if (persistMatch) {
|
|
513
|
+
flush();
|
|
514
|
+
steps.push({ type: 'persist', variable: persistMatch[1].trim(), target: persistMatch[2] });
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const emitMatch = line.match(/^Emit\s+"([^"]+)"\s+with\s+(.+)$/i);
|
|
519
|
+
if (emitMatch) {
|
|
520
|
+
flush();
|
|
521
|
+
steps.push({ type: 'emit', event: emitMatch[1], payload: emitMatch[2].trim() });
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const useMatch = line.match(/^Use\s+(.+)$/i);
|
|
526
|
+
if (useMatch) {
|
|
527
|
+
flush();
|
|
528
|
+
steps.push({ type: 'use', tool: useMatch[1].trim(), saveAs: null, constraints: {} });
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const askMatch = line.match(/^Ask\s+(.+)$/i);
|
|
533
|
+
if (askMatch) {
|
|
534
|
+
flush();
|
|
535
|
+
steps.push({ type: 'ask', target: askMatch[1].trim(), saveAs: null, constraints: {} });
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Constraint inside block
|
|
540
|
+
const constraintMatch = line.match(/^Constraint:\s*(.+)$/i);
|
|
541
|
+
if (constraintMatch && current) {
|
|
542
|
+
const constraintLine = constraintMatch[1].trim();
|
|
543
|
+
const eq = constraintLine.match(/^([^=]+)=\s*(.+)$/);
|
|
544
|
+
if (eq) {
|
|
545
|
+
let key = eq[1].trim();
|
|
546
|
+
let value = eq[2].trim();
|
|
547
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
548
|
+
value = value.slice(1, -1).split(',').map(v => v.trim().replace(/^"/, '').replace(/"$/, ''));
|
|
549
|
+
} else if (!isNaN(value)) {
|
|
550
|
+
value = Number(value);
|
|
551
|
+
} else if (value.startsWith('"') && value.endsWith('"')) {
|
|
552
|
+
value = value.slice(1, -1);
|
|
553
|
+
}
|
|
554
|
+
current.constraints[key] = value;
|
|
555
|
+
}
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Fallback
|
|
560
|
+
if (current) {
|
|
561
|
+
current.actionRaw += ' ' + line;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
flush(); // ✅ Final flush in block
|
|
566
|
+
|
|
567
|
+
// ✅ Post-process Save as for steps inside blocks - Apply normalization
|
|
568
|
+
steps.forEach(step => {
|
|
569
|
+
if (step.actionRaw && step.saveAs === null) {
|
|
570
|
+
const saveInAction = step.actionRaw.match(/(.+?)\s+Save as\s+(.+)$/i);
|
|
571
|
+
if (saveInAction) {
|
|
572
|
+
step.actionRaw = saveInAction[1].trim();
|
|
573
|
+
step.saveAs = normalizeSymbol(saveInAction[2].trim());
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
// ✅ Also normalize any existing saveAs values
|
|
577
|
+
if (step.saveAs) {
|
|
578
|
+
step.saveAs = normalizeSymbol(step.saveAs);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
return steps;
|
|
583
|
+
}
|
|
584
|
+
|
|
245
585
|
function validate(workflow) {
|
|
246
586
|
const errors = [];
|
|
247
|
-
|
|
248
587
|
if (workflow.maxGenerations !== null && workflow.maxGenerations <= 0) {
|
|
249
588
|
errors.push('max_generations must be positive');
|
|
250
589
|
}
|
|
251
|
-
|
|
252
590
|
return errors;
|
|
253
591
|
}
|
|
254
592
|
|