@o-lang/olang 1.0.22 → 1.0.24
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.js +206 -270
- package/src/runtime.js +74 -18
package/package.json
CHANGED
package/src/parser.js
CHANGED
|
@@ -1,319 +1,255 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
function parse(content, filename = '<unknown>') {
|
|
4
|
+
if (typeof content === 'string') {
|
|
5
|
+
const lines = content.split('\n').map(line => line.replace(/\r$/, ''));
|
|
6
|
+
return parseLines(lines, filename);
|
|
7
|
+
} else if (typeof content === 'object' && content !== null) {
|
|
8
|
+
// Already parsed
|
|
9
|
+
return content;
|
|
10
|
+
} else {
|
|
11
|
+
throw new Error('parse() expects string content or pre-parsed object');
|
|
5
12
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseFromFile(filepath) {
|
|
16
|
+
const content = fs.readFileSync(filepath, 'utf8');
|
|
17
|
+
return parse(content, filepath);
|
|
18
|
+
}
|
|
10
19
|
|
|
20
|
+
function parseLines(lines, filename) {
|
|
21
|
+
// Remove evolution file parsing - evolution is now in-workflow
|
|
22
|
+
return parseWorkflowLines(lines, filename);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseWorkflowLines(lines, filename) {
|
|
11
26
|
const workflow = {
|
|
12
|
-
|
|
27
|
+
type: 'workflow',
|
|
28
|
+
name: null,
|
|
13
29
|
parameters: [],
|
|
14
30
|
steps: [],
|
|
15
31
|
returnValues: [],
|
|
16
32
|
allowedResolvers: [],
|
|
17
|
-
|
|
18
|
-
declared: [],
|
|
19
|
-
autoInjected: [],
|
|
20
|
-
used: [],
|
|
21
|
-
warnings: []
|
|
22
|
-
},
|
|
33
|
+
maxGenerations: null, // ✅ Updated field name for Constraint: max_generations = X
|
|
23
34
|
__warnings: [],
|
|
24
|
-
|
|
35
|
+
filename: filename
|
|
25
36
|
};
|
|
26
|
-
|
|
37
|
+
|
|
27
38
|
let i = 0;
|
|
39
|
+
let currentStep = null;
|
|
40
|
+
let inAllowResolvers = false;
|
|
41
|
+
let inIfBlock = false;
|
|
42
|
+
let ifCondition = null;
|
|
43
|
+
let ifBody = [];
|
|
44
|
+
|
|
28
45
|
while (i < lines.length) {
|
|
29
|
-
let line = lines[i];
|
|
30
|
-
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
if (allowMatch) {
|
|
34
|
-
i++;
|
|
35
|
-
while (i < lines.length) {
|
|
36
|
-
const nextLine = lines[i].trim();
|
|
37
|
-
// Stop if line is empty or looks like a new top-level section
|
|
38
|
-
if (nextLine === '' || /^[A-Z][a-z]/.test(nextLine)) {
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
// Strip optional YAML list marker: "- name" → "name"
|
|
42
|
-
const cleanVal = nextLine.replace(/^\-\s*/, '').trim();
|
|
43
|
-
if (cleanVal) {
|
|
44
|
-
workflow.allowedResolvers.push(cleanVal);
|
|
45
|
-
workflow.resolverPolicy.declared.push(cleanVal);
|
|
46
|
-
}
|
|
47
|
-
i++;
|
|
48
|
-
}
|
|
46
|
+
let line = lines[i++].trim();
|
|
47
|
+
|
|
48
|
+
// Skip empty lines and comments
|
|
49
|
+
if (line === '' || line.startsWith('#')) {
|
|
49
50
|
continue;
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
|
|
53
|
+
// Parse Workflow declaration
|
|
54
|
+
if (line.startsWith('Workflow ')) {
|
|
55
|
+
const match = line.match(/^Workflow\s+"([^"]+)"(?:\s+with\s+(.+))?$/i);
|
|
56
|
+
if (match) {
|
|
57
|
+
workflow.name = match[1];
|
|
58
|
+
if (match[2]) {
|
|
59
|
+
workflow.parameters = match[2].split(',').map(p => p.trim()).filter(p => p !== '');
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
workflow.__warnings.push(`Invalid Workflow syntax: ${line}`);
|
|
63
|
+
}
|
|
61
64
|
continue;
|
|
62
65
|
}
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
i++;
|
|
66
|
+
|
|
67
|
+
// Parse Constraint: max_generations = X (✅ Updated syntax)
|
|
68
|
+
if (line.startsWith('Constraint: max_generations = ')) {
|
|
69
|
+
const match = line.match(/^Constraint:\s+max_generations\s*=\s*(\d+)$/i);
|
|
70
|
+
if (match) {
|
|
71
|
+
workflow.maxGenerations = parseInt(match[1], 10);
|
|
72
|
+
} else {
|
|
73
|
+
workflow.__warnings.push(`Invalid Constraint syntax: ${line}`);
|
|
74
|
+
}
|
|
73
75
|
continue;
|
|
74
76
|
}
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
workflow.__requiresMath = true;
|
|
80
|
-
workflow.steps.push({
|
|
81
|
-
type: 'calculate',
|
|
82
|
-
expression: `add({${mathAdd[1]}}, {${mathAdd[2]}})`,
|
|
83
|
-
saveAs: mathAdd[3].trim()
|
|
84
|
-
});
|
|
85
|
-
i++;
|
|
77
|
+
|
|
78
|
+
// Parse Allow resolvers section
|
|
79
|
+
if (line === 'Allow resolvers:') {
|
|
80
|
+
inAllowResolvers = true;
|
|
86
81
|
continue;
|
|
87
82
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
83
|
+
|
|
84
|
+
if (inAllowResolvers) {
|
|
85
|
+
if (line.startsWith('- ')) {
|
|
86
|
+
const resolverName = line.substring(2).trim();
|
|
87
|
+
if (resolverName) {
|
|
88
|
+
workflow.allowedResolvers.push(resolverName);
|
|
89
|
+
}
|
|
90
|
+
} else if (line === '' || line.startsWith('#')) {
|
|
91
|
+
// Continue
|
|
92
|
+
} else {
|
|
93
|
+
// End of Allow resolvers section
|
|
94
|
+
inAllowResolvers = false;
|
|
95
|
+
i--; // Re-process this line
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
98
|
continue;
|
|
99
99
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
100
|
+
|
|
101
|
+
// Parse Step declarations
|
|
102
|
+
const stepMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
|
|
103
|
+
if (stepMatch) {
|
|
104
|
+
// Save previous step if it exists
|
|
105
|
+
if (currentStep) {
|
|
106
|
+
workflow.steps.push(currentStep);
|
|
107
|
+
currentStep = null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const stepNumber = parseInt(stepMatch[1], 10);
|
|
111
|
+
const stepContent = stepMatch[2];
|
|
112
|
+
|
|
113
|
+
currentStep = {
|
|
114
|
+
type: 'action',
|
|
115
|
+
stepNumber: stepNumber,
|
|
116
|
+
actionRaw: stepContent,
|
|
117
|
+
saveAs: null,
|
|
118
|
+
constraints: {}
|
|
119
|
+
};
|
|
110
120
|
continue;
|
|
111
121
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
|
|
123
|
+
// Parse Evolve steps (✅ NEW IN-WORKFLOW EVOLUTION)
|
|
124
|
+
const evolveMatch = line.match(/^Evolve\s+([^\s]+)\s+using\s+feedback:\s*"([^"]*)"$/i);
|
|
125
|
+
if (evolveMatch) {
|
|
126
|
+
if (currentStep) {
|
|
127
|
+
workflow.steps.push(currentStep);
|
|
128
|
+
currentStep = null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
currentStep = {
|
|
132
|
+
type: 'evolve',
|
|
133
|
+
stepNumber: workflow.steps.length + 1,
|
|
134
|
+
targetResolver: evolveMatch[1].trim(),
|
|
135
|
+
feedback: evolveMatch[2],
|
|
136
|
+
saveAs: null,
|
|
137
|
+
constraints: {}
|
|
138
|
+
};
|
|
122
139
|
continue;
|
|
123
140
|
}
|
|
124
|
-
|
|
125
|
-
//
|
|
126
|
-
const
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
workflow.parameters = wfMatch[2]
|
|
130
|
-
? wfMatch[2].split(',').map(p => p.trim())
|
|
131
|
-
: [];
|
|
132
|
-
i++;
|
|
141
|
+
|
|
142
|
+
// Parse Save as
|
|
143
|
+
const saveMatch = line.match(/^Save as\s+(.+)$/i);
|
|
144
|
+
if (saveMatch && currentStep) {
|
|
145
|
+
currentStep.saveAs = saveMatch[1].trim();
|
|
133
146
|
continue;
|
|
134
147
|
}
|
|
135
|
-
|
|
136
|
-
//
|
|
137
|
-
const
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
for (const retVar of returns) {
|
|
143
|
-
const producedByMath = workflow.steps.some(
|
|
144
|
-
s => s.saveAs === retVar && s.type === 'calculate'
|
|
145
|
-
);
|
|
146
|
-
if (producedByMath) workflow.__requiresMath = true;
|
|
147
|
-
}
|
|
148
|
-
i++;
|
|
148
|
+
|
|
149
|
+
// Parse If/When conditions
|
|
150
|
+
const ifMatch = line.match(/^(?:If|When)\s+(.+)$/i);
|
|
151
|
+
if (ifMatch) {
|
|
152
|
+
ifCondition = ifMatch[1].trim();
|
|
153
|
+
inIfBlock = true;
|
|
154
|
+
ifBody = [];
|
|
149
155
|
continue;
|
|
150
156
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
let mathDetected = null;
|
|
159
|
-
let expr = '';
|
|
160
|
-
let saveVar = null;
|
|
161
|
-
const mathOps = [
|
|
162
|
-
{ re: /^Add\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'add' },
|
|
163
|
-
{ re: /^Subtract\s+\{(.+?)\}\s+from\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'subtract' },
|
|
164
|
-
{ re: /^Multiply\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'multiply' },
|
|
165
|
-
{ re: /^Divide\s+\{(.+?)\}\s+by\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'divide' }
|
|
166
|
-
];
|
|
167
|
-
for (const op of mathOps) {
|
|
168
|
-
const m = raw.match(op.re);
|
|
169
|
-
if (m) {
|
|
170
|
-
mathDetected = op.fn;
|
|
171
|
-
saveVar = m[3].trim();
|
|
172
|
-
if (op.fn === 'subtract') {
|
|
173
|
-
expr = `subtract({${m[2]}}, {${m[1]}})`;
|
|
174
|
-
} else {
|
|
175
|
-
expr = `${op.fn}({${m[1]}}, {${m[2]}})`;
|
|
176
|
-
}
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
157
|
+
|
|
158
|
+
const endIfMatch = line.match(/^End(?:If)?$/i);
|
|
159
|
+
if (endIfMatch && inIfBlock) {
|
|
160
|
+
if (currentStep) {
|
|
161
|
+
workflow.steps.push(currentStep);
|
|
162
|
+
currentStep = null;
|
|
179
163
|
}
|
|
180
|
-
|
|
164
|
+
|
|
181
165
|
workflow.steps.push({
|
|
182
|
-
type:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
saveAs: saveVar,
|
|
187
|
-
constraints: {}
|
|
166
|
+
type: 'if',
|
|
167
|
+
condition: ifCondition,
|
|
168
|
+
body: ifBody,
|
|
169
|
+
stepNumber: workflow.steps.length + 1
|
|
188
170
|
});
|
|
189
|
-
|
|
171
|
+
|
|
172
|
+
inIfBlock = false;
|
|
173
|
+
ifCondition = null;
|
|
174
|
+
ifBody = [];
|
|
190
175
|
continue;
|
|
191
176
|
}
|
|
192
|
-
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const lastStep = workflow.steps[workflow.steps.length - 1];
|
|
197
|
-
lastStep.saveAs = saveMatch[1].trim();
|
|
198
|
-
if (lastStep.saveAs.match(/[A-Z][A-Za-z0-9_]*/)) {
|
|
199
|
-
workflow.__requiresMath = true;
|
|
200
|
-
}
|
|
201
|
-
i++;
|
|
177
|
+
|
|
178
|
+
// Handle lines inside If block
|
|
179
|
+
if (inIfBlock) {
|
|
180
|
+
ifBody.push(line);
|
|
202
181
|
continue;
|
|
203
182
|
}
|
|
204
|
-
|
|
205
|
-
//
|
|
206
|
-
const
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (eq) {
|
|
212
|
-
let key = eq[1].trim();
|
|
213
|
-
let value = eq[2].trim();
|
|
214
|
-
if (value.startsWith('[') && value.endsWith(']')) {
|
|
215
|
-
value = value.slice(1, -1).split(',').map(v =>
|
|
216
|
-
v.trim().replace(/^"/, '').replace(/"$/, '')
|
|
217
|
-
);
|
|
218
|
-
} else if (!isNaN(value)) {
|
|
219
|
-
value = Number(value);
|
|
220
|
-
} else if (value.startsWith('"') && value.endsWith('"')) {
|
|
221
|
-
value = value.slice(1, -1);
|
|
222
|
-
}
|
|
223
|
-
lastStep.constraints[key] = value;
|
|
183
|
+
|
|
184
|
+
// Parse Return statement
|
|
185
|
+
const returnMatch = line.match(/^Return\s+(.+)$/i);
|
|
186
|
+
if (returnMatch) {
|
|
187
|
+
if (currentStep) {
|
|
188
|
+
workflow.steps.push(currentStep);
|
|
189
|
+
currentStep = null;
|
|
224
190
|
}
|
|
225
|
-
|
|
191
|
+
workflow.returnValues = returnMatch[1].split(',').map(r => r.trim()).filter(r => r !== '');
|
|
226
192
|
continue;
|
|
227
193
|
}
|
|
228
|
-
|
|
229
|
-
|
|
194
|
+
|
|
195
|
+
// If we reach here and have unprocessed content, it's likely a workflow line without "Step X:"
|
|
196
|
+
// Try to handle it as a step
|
|
197
|
+
if (line.trim() !== '') {
|
|
198
|
+
if (!currentStep) {
|
|
199
|
+
currentStep = {
|
|
200
|
+
type: 'action',
|
|
201
|
+
stepNumber: workflow.steps.length + 1,
|
|
202
|
+
actionRaw: line,
|
|
203
|
+
saveAs: null,
|
|
204
|
+
constraints: {}
|
|
205
|
+
};
|
|
206
|
+
} else {
|
|
207
|
+
// Append to current step action (multi-line)
|
|
208
|
+
currentStep.actionRaw += ' ' + line;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
230
211
|
}
|
|
231
|
-
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
212
|
+
|
|
213
|
+
// Don't forget the last step
|
|
214
|
+
if (currentStep) {
|
|
215
|
+
workflow.steps.push(currentStep);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Post-process steps to extract Save as from actionRaw
|
|
219
|
+
workflow.steps.forEach(step => {
|
|
220
|
+
if (step.actionRaw && step.saveAs === null) {
|
|
221
|
+
const saveInAction = step.actionRaw.match(/(.+?)\s+Save as\s+(.+)$/i);
|
|
222
|
+
if (saveInAction) {
|
|
223
|
+
step.actionRaw = saveInAction[1].trim();
|
|
224
|
+
step.saveAs = saveInAction[2].trim();
|
|
225
|
+
}
|
|
243
226
|
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Check for common issues
|
|
230
|
+
if (!workflow.name) {
|
|
231
|
+
workflow.__warnings.push('Workflow name not found');
|
|
244
232
|
}
|
|
245
|
-
|
|
246
|
-
if (workflow.
|
|
247
|
-
workflow.__warnings.push(
|
|
248
|
-
'No "Allow resolvers" section declared. Workflow will run in restricted mode.'
|
|
249
|
-
);
|
|
233
|
+
|
|
234
|
+
if (workflow.steps.length === 0) {
|
|
235
|
+
workflow.__warnings.push('No steps found in workflow');
|
|
250
236
|
}
|
|
251
|
-
|
|
252
|
-
workflow.
|
|
237
|
+
|
|
238
|
+
if (workflow.returnValues.length === 0 && workflow.steps.length > 0) {
|
|
239
|
+
workflow.__warnings.push('No Return statement found');
|
|
240
|
+
}
|
|
241
|
+
|
|
253
242
|
return workflow;
|
|
254
243
|
}
|
|
255
244
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
let current = null;
|
|
262
|
-
for (const line of lines) {
|
|
263
|
-
const stepMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
|
|
264
|
-
if (stepMatch) {
|
|
265
|
-
current = {
|
|
266
|
-
type: 'action',
|
|
267
|
-
stepNumber: parseInt(stepMatch[1], 10),
|
|
268
|
-
actionRaw: stepMatch[2].trim(),
|
|
269
|
-
saveAs: null,
|
|
270
|
-
constraints: {}
|
|
271
|
-
};
|
|
272
|
-
steps.push(current);
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
const saveMatch = line.match(/^Save as\s+(.+)$/i);
|
|
276
|
-
if (saveMatch && current) current.saveAs = saveMatch[1].trim();
|
|
277
|
-
const debriefMatch = line.match(/^Debrief\s+(\w+)\s+with\s+"(.+)"$/i);
|
|
278
|
-
if (debriefMatch) {
|
|
279
|
-
steps.push({ type: 'debrief', agent: debriefMatch[1], message: debriefMatch[2] });
|
|
280
|
-
}
|
|
281
|
-
const evolveMatch = line.match(/^Evolve\s+(\w+)\s+using\s+feedback:\s+"(.+)"$/i);
|
|
282
|
-
if (evolveMatch) {
|
|
283
|
-
steps.push({ type: 'evolve', agent: evolveMatch[1], feedback: evolveMatch[2] });
|
|
284
|
-
}
|
|
285
|
-
const promptMatch = line.match(/^Prompt user to\s+"(.+)"$/i);
|
|
286
|
-
if (promptMatch) {
|
|
287
|
-
steps.push({ type: 'prompt', question: promptMatch[1], saveAs: null });
|
|
288
|
-
}
|
|
289
|
-
const useMatch = line.match(/^Use\s+(.+)$/i);
|
|
290
|
-
if (useMatch) {
|
|
291
|
-
steps.push({ type: 'use', tool: useMatch[1].trim(), saveAs: null, constraints: {} });
|
|
292
|
-
}
|
|
293
|
-
const askMatch = line.match(/^Ask\s+(.+)$/i);
|
|
294
|
-
if (askMatch) {
|
|
295
|
-
steps.push({ type: 'ask', target: askMatch[1].trim(), saveAs: null, constraints: {} });
|
|
296
|
-
}
|
|
297
|
-
// ✅ Parse file Persist in blocks
|
|
298
|
-
const persistMatch = line.match(/^Persist\s+([^\s]+)\s+to\s+"([^"]+)"$/i);
|
|
299
|
-
if (persistMatch) {
|
|
300
|
-
steps.push({
|
|
301
|
-
type: 'persist',
|
|
302
|
-
source: persistMatch[1].trim(),
|
|
303
|
-
destination: persistMatch[2].trim()
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
// ✅ NEW: Parse database Persist in blocks
|
|
307
|
-
const dbPersistMatch = line.match(/^Persist\s+([^\s]+)\s+to\s+db:([^\s]+)$/i);
|
|
308
|
-
if (dbPersistMatch) {
|
|
309
|
-
steps.push({
|
|
310
|
-
type: 'persist-db',
|
|
311
|
-
source: dbPersistMatch[1].trim(),
|
|
312
|
-
collection: dbPersistMatch[2].trim()
|
|
313
|
-
});
|
|
314
|
-
}
|
|
245
|
+
function validate(workflow) {
|
|
246
|
+
const errors = [];
|
|
247
|
+
|
|
248
|
+
if (workflow.maxGenerations !== null && workflow.maxGenerations <= 0) {
|
|
249
|
+
errors.push('max_generations must be positive');
|
|
315
250
|
}
|
|
316
|
-
|
|
251
|
+
|
|
252
|
+
return errors;
|
|
317
253
|
}
|
|
318
254
|
|
|
319
|
-
module.exports = { parse };
|
|
255
|
+
module.exports = { parse, parseFromFile, parseLines, validate };
|
package/src/runtime.js
CHANGED
|
@@ -244,28 +244,43 @@ class RuntimeAPI {
|
|
|
244
244
|
this.allowedResolvers.add('builtInMathResolver');
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
// Handle different resolver input formats
|
|
248
|
+
let resolversToRun = [];
|
|
249
|
+
|
|
247
250
|
if (agentResolver && Array.isArray(agentResolver._chain)) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
251
|
+
// Resolver chain mode
|
|
252
|
+
resolversToRun = agentResolver._chain;
|
|
253
|
+
} else if (Array.isArray(agentResolver)) {
|
|
254
|
+
// Array of resolvers mode (what npx olang passes with -r flags)
|
|
255
|
+
resolversToRun = agentResolver;
|
|
256
|
+
} else if (agentResolver) {
|
|
257
|
+
// Single resolver mode
|
|
258
|
+
resolversToRun = [agentResolver];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ✅ Return the FIRST resolver that returns a non-undefined result
|
|
262
|
+
for (let idx = 0; idx < resolversToRun.length; idx++) {
|
|
263
|
+
const resolver = resolversToRun[idx];
|
|
264
|
+
validateResolver(resolver);
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const out = await resolver(action, this.context);
|
|
268
|
+
outputs.push(out);
|
|
269
|
+
this.context[`__resolver_${idx}`] = out;
|
|
270
|
+
|
|
271
|
+
// ✅ If resolver handled the action (returned non-undefined), use it immediately
|
|
272
|
+
if (out !== undefined) {
|
|
273
|
+
return out;
|
|
259
274
|
}
|
|
275
|
+
} catch (e) {
|
|
276
|
+
this.addWarning(`Resolver ${resolver?.resolverName || resolver?.name || idx} failed for action "${action}": ${e.message}`);
|
|
277
|
+
outputs.push(null);
|
|
278
|
+
this.context[`__resolver_${idx}`] = null;
|
|
260
279
|
}
|
|
261
|
-
} else {
|
|
262
|
-
validateResolver(agentResolver);
|
|
263
|
-
const out = await agentResolver(action, this.context);
|
|
264
|
-
outputs.push(out);
|
|
265
|
-
this.context['__resolver_0'] = out;
|
|
266
280
|
}
|
|
267
281
|
|
|
268
|
-
|
|
282
|
+
// If no resolver handled the action, return undefined
|
|
283
|
+
return undefined;
|
|
269
284
|
};
|
|
270
285
|
|
|
271
286
|
switch (stepType) {
|
|
@@ -313,6 +328,35 @@ class RuntimeAPI {
|
|
|
313
328
|
break;
|
|
314
329
|
}
|
|
315
330
|
|
|
331
|
+
case 'evolve': {
|
|
332
|
+
// ✅ Handle in-workflow Evolve steps
|
|
333
|
+
const { targetResolver, feedback } = step;
|
|
334
|
+
|
|
335
|
+
if (this.verbose) {
|
|
336
|
+
console.log(`🔄 Evolve step: ${targetResolver} with feedback: "${feedback}"`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Basic evolution: record the request (free tier)
|
|
340
|
+
const evolutionResult = {
|
|
341
|
+
resolver: targetResolver,
|
|
342
|
+
feedback: feedback,
|
|
343
|
+
status: 'evolution_requested',
|
|
344
|
+
timestamp: new Date().toISOString(),
|
|
345
|
+
workflow: this.context.workflow_name
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// ✅ Check for Advanced Evolution Service (paid tier)
|
|
349
|
+
if (process.env.OLANG_EVOLUTION_API_KEY) {
|
|
350
|
+
evolutionResult.status = 'advanced_evolution_enabled';
|
|
351
|
+
evolution resultList.message = 'Advanced evolution service would process this request';
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (step.saveAs) {
|
|
355
|
+
this.context[step.saveAs] = evolutionResult;
|
|
356
|
+
}
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
|
|
316
360
|
case 'if': {
|
|
317
361
|
if (this.evaluateCondition(step.condition, this.context)) {
|
|
318
362
|
for (const s of step.body) await this.executeStep(s, agentResolver);
|
|
@@ -403,7 +447,7 @@ class RuntimeAPI {
|
|
|
403
447
|
const db = this.dbClient.client.db(process.env.DB_NAME || 'olang');
|
|
404
448
|
await db.collection(step.collection).insertOne({
|
|
405
449
|
workflow_name: this.context.workflow_name || 'unknown',
|
|
406
|
-
|
|
450
|
+
data: sourceValue,
|
|
407
451
|
created_at: new Date()
|
|
408
452
|
});
|
|
409
453
|
break;
|
|
@@ -437,11 +481,23 @@ class RuntimeAPI {
|
|
|
437
481
|
}
|
|
438
482
|
|
|
439
483
|
async executeWorkflow(workflow, inputs, agentResolver) {
|
|
484
|
+
// Handle regular workflows only (Evolve is a step type now)
|
|
485
|
+
if (workflow.type !== 'workflow') {
|
|
486
|
+
throw new Error(`Unknown workflow type: ${workflow.type}`);
|
|
487
|
+
}
|
|
488
|
+
|
|
440
489
|
// ✅ Inject workflow name into context
|
|
441
490
|
this.context = {
|
|
442
491
|
...inputs,
|
|
443
492
|
workflow_name: workflow.name
|
|
444
493
|
};
|
|
494
|
+
|
|
495
|
+
// ✅ Check generation constraint from Constraint: max_generations = X
|
|
496
|
+
const currentGeneration = inputs.__generation || 1;
|
|
497
|
+
if (workflow.maxGenerations !== null && currentGeneration > workflow.maxGenerations) {
|
|
498
|
+
throw new Error(`Workflow generation ${currentGeneration} exceeds Constraint: max_generations = ${workflow.maxGenerations}`);
|
|
499
|
+
}
|
|
500
|
+
|
|
445
501
|
this.workflowSteps = workflow.steps;
|
|
446
502
|
this.allowedResolvers = new Set(workflow.allowedResolvers || []);
|
|
447
503
|
|