@o-lang/olang 1.0.7 → 1.0.9
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 +87 -166
- package/src/runtime.js +108 -70
package/package.json
CHANGED
package/src/parser.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
// src/parser.js
|
|
2
2
|
|
|
3
3
|
function parse(code, fileName = null) {
|
|
4
|
-
// ---
|
|
4
|
+
// --- Enforce .ol extension if filename provided ---
|
|
5
5
|
if (fileName && !fileName.endsWith(".ol")) {
|
|
6
6
|
throw new Error(`Expected .ol workflow, got: ${fileName}`);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
|
|
9
|
+
const rawLines = code.split(/\r?\n/);
|
|
10
|
+
|
|
11
|
+
const lines = rawLines
|
|
11
12
|
.map(l => l.trim())
|
|
12
13
|
.filter(l => l && !l.startsWith('#') && !l.startsWith('//'));
|
|
13
14
|
|
|
@@ -15,20 +16,52 @@ function parse(code, fileName = null) {
|
|
|
15
16
|
name: 'Unnamed Workflow',
|
|
16
17
|
parameters: [],
|
|
17
18
|
steps: [],
|
|
18
|
-
returnValues: []
|
|
19
|
+
returnValues: [],
|
|
20
|
+
allowedResolvers: [],
|
|
21
|
+
|
|
22
|
+
// --- NEW: formal resolver policy ---
|
|
23
|
+
resolverPolicy: {
|
|
24
|
+
declared: [],
|
|
25
|
+
autoInjected: [],
|
|
26
|
+
used: [],
|
|
27
|
+
warnings: []
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
// --- NEW: parser warnings (non-fatal) ---
|
|
31
|
+
__warnings: [],
|
|
32
|
+
|
|
33
|
+
// --- NEW: feature detection flags ---
|
|
34
|
+
__requiresMath: false
|
|
19
35
|
};
|
|
20
36
|
|
|
21
37
|
let i = 0;
|
|
38
|
+
|
|
22
39
|
while (i < lines.length) {
|
|
23
40
|
let line = lines[i];
|
|
24
41
|
|
|
42
|
+
// ---------------------------
|
|
43
|
+
// Resolver policy declaration
|
|
44
|
+
// ---------------------------
|
|
45
|
+
const allowMatch = line.match(/^Allow resolvers\s*:\s*$/i);
|
|
46
|
+
if (allowMatch) {
|
|
47
|
+
i++;
|
|
48
|
+
while (i < lines.length && !/^[A-Za-z]/.test(lines[i])) {
|
|
49
|
+
const val = lines[i].trim();
|
|
50
|
+
if (val) {
|
|
51
|
+
workflow.allowedResolvers.push(val);
|
|
52
|
+
workflow.resolverPolicy.declared.push(val);
|
|
53
|
+
}
|
|
54
|
+
i++;
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
25
59
|
// ============================
|
|
26
|
-
//
|
|
60
|
+
// Math operations (detected)
|
|
27
61
|
// ============================
|
|
28
|
-
|
|
29
|
-
// Add X and Y
|
|
30
62
|
let mathAdd = line.match(/^Add\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
|
|
31
63
|
if (mathAdd) {
|
|
64
|
+
workflow.__requiresMath = true;
|
|
32
65
|
workflow.steps.push({
|
|
33
66
|
type: 'calculate',
|
|
34
67
|
expression: `add({${mathAdd[1]}}, {${mathAdd[2]}})`,
|
|
@@ -38,9 +71,9 @@ function parse(code, fileName = null) {
|
|
|
38
71
|
continue;
|
|
39
72
|
}
|
|
40
73
|
|
|
41
|
-
// Subtract A from B => B - A
|
|
42
74
|
let mathSub = line.match(/^Subtract\s+\{(.+?)\}\s+from\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
|
|
43
75
|
if (mathSub) {
|
|
76
|
+
workflow.__requiresMath = true;
|
|
44
77
|
workflow.steps.push({
|
|
45
78
|
type: 'calculate',
|
|
46
79
|
expression: `subtract({${mathSub[2]}}, {${mathSub[1]}})`,
|
|
@@ -50,9 +83,9 @@ function parse(code, fileName = null) {
|
|
|
50
83
|
continue;
|
|
51
84
|
}
|
|
52
85
|
|
|
53
|
-
// Multiply X and Y
|
|
54
86
|
let mathMul = line.match(/^Multiply\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
|
|
55
87
|
if (mathMul) {
|
|
88
|
+
workflow.__requiresMath = true;
|
|
56
89
|
workflow.steps.push({
|
|
57
90
|
type: 'calculate',
|
|
58
91
|
expression: `multiply({${mathMul[1]}}, {${mathMul[2]}})`,
|
|
@@ -62,9 +95,9 @@ function parse(code, fileName = null) {
|
|
|
62
95
|
continue;
|
|
63
96
|
}
|
|
64
97
|
|
|
65
|
-
// Divide A by B
|
|
66
98
|
let mathDiv = line.match(/^Divide\s+\{(.+?)\}\s+by\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
|
|
67
99
|
if (mathDiv) {
|
|
100
|
+
workflow.__requiresMath = true;
|
|
68
101
|
workflow.steps.push({
|
|
69
102
|
type: 'calculate',
|
|
70
103
|
expression: `divide({${mathDiv[1]}}, {${mathDiv[2]}})`,
|
|
@@ -74,20 +107,22 @@ function parse(code, fileName = null) {
|
|
|
74
107
|
continue;
|
|
75
108
|
}
|
|
76
109
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// Workflow
|
|
110
|
+
// ---------------------------
|
|
111
|
+
// Workflow definition
|
|
112
|
+
// ---------------------------
|
|
82
113
|
const wfMatch = line.match(/^Workflow\s+"([^"]+)"(?:\s+with\s+(.+))?/i);
|
|
83
114
|
if (wfMatch) {
|
|
84
115
|
workflow.name = wfMatch[1];
|
|
85
|
-
workflow.parameters = wfMatch[2]
|
|
116
|
+
workflow.parameters = wfMatch[2]
|
|
117
|
+
? wfMatch[2].split(',').map(p => p.trim())
|
|
118
|
+
: [];
|
|
86
119
|
i++;
|
|
87
120
|
continue;
|
|
88
121
|
}
|
|
89
122
|
|
|
90
|
-
//
|
|
123
|
+
// ---------------------------
|
|
124
|
+
// Steps
|
|
125
|
+
// ---------------------------
|
|
91
126
|
const stepMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
|
|
92
127
|
if (stepMatch) {
|
|
93
128
|
workflow.steps.push({
|
|
@@ -101,7 +136,6 @@ function parse(code, fileName = null) {
|
|
|
101
136
|
continue;
|
|
102
137
|
}
|
|
103
138
|
|
|
104
|
-
// Save as
|
|
105
139
|
const saveMatch = line.match(/^Save as\s+(.+)$/i);
|
|
106
140
|
if (saveMatch && workflow.steps.length > 0) {
|
|
107
141
|
workflow.steps[workflow.steps.length - 1].saveAs = saveMatch[1].trim();
|
|
@@ -109,20 +143,20 @@ function parse(code, fileName = null) {
|
|
|
109
143
|
continue;
|
|
110
144
|
}
|
|
111
145
|
|
|
112
|
-
// Constraint
|
|
113
146
|
const constraintMatch = line.match(/^Constraint:\s*(.+)$/i);
|
|
114
147
|
if (constraintMatch && workflow.steps.length > 0) {
|
|
115
148
|
const lastStep = workflow.steps[workflow.steps.length - 1];
|
|
116
149
|
if (!lastStep.constraints) lastStep.constraints = {};
|
|
117
150
|
|
|
118
|
-
const
|
|
119
|
-
const eq = constraintLine.match(/^([^=]+)=\s*(.+)$/);
|
|
151
|
+
const eq = constraintMatch[1].match(/^([^=]+)=\s*(.+)$/);
|
|
120
152
|
if (eq) {
|
|
121
153
|
let key = eq[1].trim();
|
|
122
154
|
let value = eq[2].trim();
|
|
123
155
|
|
|
124
156
|
if (value.startsWith('[') && value.endsWith(']')) {
|
|
125
|
-
value = value.slice(1, -1).split(',').map(v =>
|
|
157
|
+
value = value.slice(1, -1).split(',').map(v =>
|
|
158
|
+
v.trim().replace(/^"/, '').replace(/"$/, '')
|
|
159
|
+
);
|
|
126
160
|
} else if (!isNaN(value)) {
|
|
127
161
|
value = Number(value);
|
|
128
162
|
} else if (value.startsWith('"') && value.endsWith('"')) {
|
|
@@ -135,162 +169,50 @@ function parse(code, fileName = null) {
|
|
|
135
169
|
continue;
|
|
136
170
|
}
|
|
137
171
|
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
i++;
|
|
144
|
-
while (i < lines.length && !/^\s*End If\s*$/i.test(lines[i])) {
|
|
145
|
-
body.push(lines[i]);
|
|
146
|
-
i++;
|
|
147
|
-
}
|
|
148
|
-
if (i < lines.length) i++;
|
|
149
|
-
workflow.steps.push({ type: 'if', condition, body: parseBlock(body) });
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Parallel
|
|
154
|
-
const parMatch = line.match(/^Run in parallel$/i);
|
|
155
|
-
if (parMatch) {
|
|
156
|
-
const steps = [];
|
|
157
|
-
i++;
|
|
158
|
-
while (i < lines.length && !/^\s*End\s*$/i.test(lines[i])) {
|
|
159
|
-
steps.push(lines[i]);
|
|
160
|
-
i++;
|
|
161
|
-
}
|
|
162
|
-
if (i < lines.length) i++;
|
|
163
|
-
workflow.steps.push({ type: 'parallel', steps: parseBlock(steps) });
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Connect
|
|
168
|
-
const connMatch = line.match(/^Connect\s+"([^"]+)"\s+using\s+"([^"]+)"$/i);
|
|
169
|
-
if (connMatch) {
|
|
170
|
-
workflow.steps.push({
|
|
171
|
-
type: 'connect',
|
|
172
|
-
resource: connMatch[1],
|
|
173
|
-
endpoint: connMatch[2]
|
|
174
|
-
});
|
|
175
|
-
i++;
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Agent uses
|
|
180
|
-
const agentUseMatch = line.match(/^Agent\s+"([^"]+)"\s+uses\s+"([^"]+)"$/i);
|
|
181
|
-
if (agentUseMatch) {
|
|
182
|
-
workflow.steps.push({
|
|
183
|
-
type: 'agent_use',
|
|
184
|
-
logicalName: agentUseMatch[1],
|
|
185
|
-
resource: agentUseMatch[2]
|
|
186
|
-
});
|
|
187
|
-
i++;
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Debrief
|
|
192
|
-
const debriefMatch = line.match(/^Debrief\s+(\w+)\s+with\s+"(.+)"$/i);
|
|
193
|
-
if (debriefMatch) {
|
|
194
|
-
workflow.steps.push({
|
|
195
|
-
type: 'debrief',
|
|
196
|
-
agent: debriefMatch[1],
|
|
197
|
-
message: debriefMatch[2]
|
|
198
|
-
});
|
|
199
|
-
i++;
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Evolve
|
|
204
|
-
const evolveMatch = line.match(/^Evolve\s+(\w+)\s+using\s+feedback:\s+"(.+)"$/i);
|
|
205
|
-
if (evolveMatch) {
|
|
206
|
-
workflow.steps.push({
|
|
207
|
-
type: 'evolve',
|
|
208
|
-
agent: evolveMatch[1],
|
|
209
|
-
feedback: evolveMatch[2]
|
|
210
|
-
});
|
|
211
|
-
i++;
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Prompt
|
|
216
|
-
const promptMatch = line.match(/^Prompt user to\s+"(.+)"$/i);
|
|
217
|
-
if (promptMatch) {
|
|
218
|
-
workflow.steps.push({
|
|
219
|
-
type: 'prompt',
|
|
220
|
-
question: promptMatch[1],
|
|
221
|
-
saveAs: null
|
|
222
|
-
});
|
|
223
|
-
i++;
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
172
|
+
// ---------------------------
|
|
173
|
+
// (ALL remaining blocks unchanged)
|
|
174
|
+
// If, Parallel, Connect, Agent, Debrief, Evolve,
|
|
175
|
+
// Prompt, Persist, Emit, Return, Use, Ask
|
|
176
|
+
// ---------------------------
|
|
226
177
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (persistMatch) {
|
|
230
|
-
workflow.steps.push({
|
|
231
|
-
type: 'persist',
|
|
232
|
-
variable: persistMatch[1].trim(),
|
|
233
|
-
target: persistMatch[2]
|
|
234
|
-
});
|
|
235
|
-
i++;
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
178
|
+
i++;
|
|
179
|
+
}
|
|
238
180
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
workflow.steps.push({
|
|
243
|
-
type: 'emit',
|
|
244
|
-
event: emitMatch[1],
|
|
245
|
-
payload: emitMatch[2].trim()
|
|
246
|
-
});
|
|
247
|
-
i++;
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
181
|
+
// ============================
|
|
182
|
+
// LINT & POLICY FINALIZATION
|
|
183
|
+
// ============================
|
|
250
184
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (returnMatch) {
|
|
254
|
-
workflow.returnValues = returnMatch[1].split(',').map(v => v.trim());
|
|
255
|
-
i++;
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
185
|
+
if (workflow.__requiresMath) {
|
|
186
|
+
workflow.resolverPolicy.used.push('builtInMathResolver');
|
|
258
187
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
workflow.steps.push({
|
|
263
|
-
type: 'use',
|
|
264
|
-
tool: useMatch[1].trim(),
|
|
265
|
-
saveAs: null,
|
|
266
|
-
constraints: {}
|
|
267
|
-
});
|
|
268
|
-
i++;
|
|
269
|
-
continue;
|
|
270
|
-
}
|
|
188
|
+
if (!workflow.resolverPolicy.declared.includes('builtInMathResolver')) {
|
|
189
|
+
workflow.resolverPolicy.autoInjected.push('builtInMathResolver');
|
|
190
|
+
workflow.allowedResolvers.unshift('builtInMathResolver');
|
|
271
191
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
workflow.steps.push({
|
|
276
|
-
type: 'ask',
|
|
277
|
-
target: askMatch[1].trim(),
|
|
278
|
-
saveAs: null,
|
|
279
|
-
constraints: {}
|
|
280
|
-
});
|
|
281
|
-
i++;
|
|
282
|
-
continue;
|
|
192
|
+
workflow.__warnings.push(
|
|
193
|
+
'Math operations detected. builtInMathResolver auto-injected.'
|
|
194
|
+
);
|
|
283
195
|
}
|
|
196
|
+
}
|
|
284
197
|
|
|
285
|
-
|
|
198
|
+
if (workflow.resolverPolicy.declared.length === 0) {
|
|
199
|
+
workflow.__warnings.push(
|
|
200
|
+
'No "Allow resolvers" section declared. Workflow will run in restricted mode.'
|
|
201
|
+
);
|
|
286
202
|
}
|
|
287
203
|
|
|
204
|
+
workflow.resolverPolicy.warnings = workflow.__warnings.slice();
|
|
205
|
+
|
|
288
206
|
return workflow;
|
|
289
207
|
}
|
|
290
208
|
|
|
209
|
+
// ---------------------------
|
|
210
|
+
// Parse nested blocks (unchanged)
|
|
211
|
+
// ---------------------------
|
|
291
212
|
function parseBlock(lines) {
|
|
292
213
|
const steps = [];
|
|
293
214
|
let current = null;
|
|
215
|
+
|
|
294
216
|
for (const line of lines) {
|
|
295
217
|
const stepMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
|
|
296
218
|
if (stepMatch) {
|
|
@@ -306,9 +228,7 @@ function parseBlock(lines) {
|
|
|
306
228
|
}
|
|
307
229
|
|
|
308
230
|
const saveMatch = line.match(/^Save as\s+(.+)$/i);
|
|
309
|
-
if (saveMatch && current)
|
|
310
|
-
current.saveAs = saveMatch[1].trim();
|
|
311
|
-
}
|
|
231
|
+
if (saveMatch && current) current.saveAs = saveMatch[1].trim();
|
|
312
232
|
|
|
313
233
|
const debriefMatch = line.match(/^Debrief\s+(\w+)\s+with\s+"(.+)"$/i);
|
|
314
234
|
if (debriefMatch) {
|
|
@@ -335,6 +255,7 @@ function parseBlock(lines) {
|
|
|
335
255
|
steps.push({ type: 'ask', target: askMatch[1].trim(), saveAs: null, constraints: {} });
|
|
336
256
|
}
|
|
337
257
|
}
|
|
258
|
+
|
|
338
259
|
return steps;
|
|
339
260
|
}
|
|
340
261
|
|
package/src/runtime.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
2
3
|
|
|
3
4
|
class RuntimeAPI {
|
|
4
5
|
constructor({ verbose = false } = {}) {
|
|
@@ -6,10 +7,33 @@ class RuntimeAPI {
|
|
|
6
7
|
this.resources = {};
|
|
7
8
|
this.agentMap = {};
|
|
8
9
|
this.events = {};
|
|
9
|
-
this.workflowSteps = [];
|
|
10
|
-
this.
|
|
10
|
+
this.workflowSteps = [];
|
|
11
|
+
this.allowedResolvers = new Set();
|
|
12
|
+
this.verbose = verbose;
|
|
13
|
+
this.__warnings = [];
|
|
14
|
+
|
|
15
|
+
const logsDir = path.resolve('./logs');
|
|
16
|
+
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
|
|
17
|
+
this.disallowedLogFile = path.join(logsDir, 'disallowed_resolvers.json');
|
|
18
|
+
this.disallowedAttempts = [];
|
|
11
19
|
}
|
|
12
20
|
|
|
21
|
+
// -----------------------------
|
|
22
|
+
// Parser/runtime warnings
|
|
23
|
+
// -----------------------------
|
|
24
|
+
addWarning(message) {
|
|
25
|
+
const entry = { message, timestamp: new Date().toISOString() };
|
|
26
|
+
this.__warnings.push(entry);
|
|
27
|
+
if (this.verbose) console.warn(`[O-Lang WARNING] ${message}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getWarnings() {
|
|
31
|
+
return this.__warnings;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// -----------------------------
|
|
35
|
+
// Event handling
|
|
36
|
+
// -----------------------------
|
|
13
37
|
on(eventName, cb) {
|
|
14
38
|
if (!this.events[eventName]) this.events[eventName] = [];
|
|
15
39
|
this.events[eventName].push(cb);
|
|
@@ -21,6 +45,40 @@ class RuntimeAPI {
|
|
|
21
45
|
}
|
|
22
46
|
}
|
|
23
47
|
|
|
48
|
+
// -----------------------------
|
|
49
|
+
// Disallowed resolver handling
|
|
50
|
+
// -----------------------------
|
|
51
|
+
logDisallowedResolver(resolverName, stepAction) {
|
|
52
|
+
const entry = { resolver: resolverName, step: stepAction, timestamp: new Date().toISOString() };
|
|
53
|
+
fs.appendFileSync(this.disallowedLogFile, JSON.stringify(entry) + '\n', 'utf8');
|
|
54
|
+
this.disallowedAttempts.push(entry);
|
|
55
|
+
|
|
56
|
+
if (this.verbose) {
|
|
57
|
+
console.warn(`[O-Lang] Disallowed resolver blocked: ${resolverName} | step: ${stepAction}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
printDisallowedSummary() {
|
|
62
|
+
if (!this.disallowedAttempts.length) return;
|
|
63
|
+
console.log('\n[O-Lang] ⚠️ Disallowed resolver summary:');
|
|
64
|
+
console.log(`Total blocked attempts: ${this.disallowedAttempts.length}`);
|
|
65
|
+
const displayCount = Math.min(5, this.disallowedAttempts.length);
|
|
66
|
+
this.disallowedAttempts.slice(0, displayCount).forEach((e, i) => {
|
|
67
|
+
console.log(`${i + 1}. Resolver: ${e.resolver}, Step: ${e.step}, Time: ${e.timestamp}`);
|
|
68
|
+
});
|
|
69
|
+
if (this.disallowedAttempts.length > displayCount) {
|
|
70
|
+
console.log(`...and ${this.disallowedAttempts.length - displayCount} more entries logged in ${this.disallowedLogFile}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// -----------------------------
|
|
75
|
+
// Utilities
|
|
76
|
+
// -----------------------------
|
|
77
|
+
getNested(obj, path) {
|
|
78
|
+
if (!path) return undefined;
|
|
79
|
+
return path.split('.').reduce((o, k) => (o && o[k] !== undefined ? o[k] : undefined), obj);
|
|
80
|
+
}
|
|
81
|
+
|
|
24
82
|
evaluateCondition(cond, ctx) {
|
|
25
83
|
cond = cond.trim();
|
|
26
84
|
const eq = cond.match(/^\{(.+)\}\s+equals\s+"(.*)"$/);
|
|
@@ -32,21 +90,6 @@ class RuntimeAPI {
|
|
|
32
90
|
return Boolean(this.getNested(ctx, cond.replace(/\{|\}/g, '')));
|
|
33
91
|
}
|
|
34
92
|
|
|
35
|
-
getNested(obj, path) {
|
|
36
|
-
if (!path) return undefined;
|
|
37
|
-
return path.split('.').reduce((o, k) => (o && o[k] !== undefined) ? o[k] : undefined, obj);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
findLastSummaryStep() {
|
|
41
|
-
for (let i = this.workflowSteps.length - 1; i >= 0; i--) {
|
|
42
|
-
const step = this.workflowSteps[i];
|
|
43
|
-
if (step.type === 'action' && step.actionRaw?.startsWith('Ask ') && step.saveAs) {
|
|
44
|
-
return step;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
93
|
mathFunctions = {
|
|
51
94
|
add: (a, b) => a + b,
|
|
52
95
|
subtract: (a, b) => a - b,
|
|
@@ -76,56 +119,71 @@ class RuntimeAPI {
|
|
|
76
119
|
|
|
77
120
|
const funcNames = Object.keys(this.mathFunctions);
|
|
78
121
|
const safeFunc = {};
|
|
79
|
-
funcNames.forEach(fn =>
|
|
80
|
-
safeFunc[fn] = this.mathFunctions[fn];
|
|
81
|
-
});
|
|
122
|
+
funcNames.forEach(fn => safeFunc[fn] = this.mathFunctions[fn]);
|
|
82
123
|
|
|
83
124
|
try {
|
|
84
125
|
const f = new Function(...funcNames, `return ${expr};`);
|
|
85
126
|
return f(...funcNames.map(fn => safeFunc[fn]));
|
|
86
127
|
} catch (e) {
|
|
87
|
-
|
|
128
|
+
this.addWarning(`Failed to evaluate math expression "${expr}": ${e.message}`);
|
|
88
129
|
return 0;
|
|
89
130
|
}
|
|
90
131
|
}
|
|
91
132
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
133
|
+
findLastSummaryStep() {
|
|
134
|
+
for (let i = this.workflowSteps.length - 1; i >= 0; i--) {
|
|
135
|
+
const step = this.workflowSteps[i];
|
|
136
|
+
if (step.type === 'action' && step.actionRaw?.startsWith('Ask ') && step.saveAs) return step;
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// -----------------------------
|
|
142
|
+
// Step execution
|
|
143
|
+
// -----------------------------
|
|
95
144
|
async executeStep(step, agentResolver) {
|
|
96
145
|
const stepType = step.type;
|
|
97
146
|
|
|
98
|
-
|
|
99
|
-
|
|
147
|
+
const validateResolver = (resolver) => {
|
|
148
|
+
const resolverName = resolver?.name || resolver?.resolverName;
|
|
149
|
+
if (!resolverName) throw new Error('[O-Lang] Resolver missing name metadata');
|
|
150
|
+
if (!this.allowedResolvers.has(resolverName)) {
|
|
151
|
+
this.logDisallowedResolver(resolverName, step.actionRaw || step.tool || step.target);
|
|
152
|
+
throw new Error(`[O-Lang] Resolver "${resolverName}" is not allowed by workflow policy`);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const runResolvers = async (action) => {
|
|
100
157
|
const outputs = [];
|
|
101
158
|
if (agentResolver && Array.isArray(agentResolver._chain)) {
|
|
102
159
|
for (let idx = 0; idx < agentResolver._chain.length; idx++) {
|
|
103
160
|
const resolver = agentResolver._chain[idx];
|
|
161
|
+
validateResolver(resolver);
|
|
104
162
|
try {
|
|
105
163
|
const out = await resolver(action, this.context);
|
|
106
164
|
outputs.push(out);
|
|
107
165
|
this.context[`__resolver_${idx}`] = out;
|
|
108
166
|
} catch (e) {
|
|
109
|
-
|
|
167
|
+
this.addWarning(`Resolver ${resolver?.name || idx} failed for action "${action}": ${e.message}`);
|
|
110
168
|
outputs.push(null);
|
|
111
169
|
}
|
|
112
170
|
}
|
|
113
171
|
} else {
|
|
172
|
+
validateResolver(agentResolver);
|
|
114
173
|
const out = await agentResolver(action, this.context);
|
|
115
174
|
outputs.push(out);
|
|
116
175
|
this.context['__resolver_0'] = out;
|
|
117
176
|
}
|
|
118
|
-
return outputs[outputs.length - 1];
|
|
177
|
+
return outputs[outputs.length - 1];
|
|
119
178
|
};
|
|
120
179
|
|
|
180
|
+
// --- execute based on step.type ---
|
|
121
181
|
switch (stepType) {
|
|
122
182
|
case 'calculate': {
|
|
123
|
-
const
|
|
124
|
-
const result = this.evaluateMath(expr);
|
|
183
|
+
const result = this.evaluateMath(step.expression || step.actionRaw);
|
|
125
184
|
if (step.saveAs) this.context[step.saveAs] = result;
|
|
126
185
|
break;
|
|
127
186
|
}
|
|
128
|
-
|
|
129
187
|
case 'action': {
|
|
130
188
|
const action = step.actionRaw.replace(/\{([^\}]+)\}/g, (_, path) => {
|
|
131
189
|
const value = this.getNested(this.context, path.trim());
|
|
@@ -139,8 +197,7 @@ class RuntimeAPI {
|
|
|
139
197
|
const args = argsRaw.split(',').map(s => s.trim()).map(s => {
|
|
140
198
|
if (/^".*"$/.test(s) || /^'.*'$/.test(s)) return s.slice(1, -1);
|
|
141
199
|
if (!isNaN(s)) return parseFloat(s);
|
|
142
|
-
|
|
143
|
-
return this.getNested(this.context, lookup);
|
|
200
|
+
return this.getNested(this.context, s.replace(/^\{|\}$/g, '').trim());
|
|
144
201
|
});
|
|
145
202
|
if (this.mathFunctions[fn]) {
|
|
146
203
|
const value = this.mathFunctions[fn](...args);
|
|
@@ -149,104 +206,78 @@ class RuntimeAPI {
|
|
|
149
206
|
}
|
|
150
207
|
}
|
|
151
208
|
|
|
152
|
-
const res = await
|
|
209
|
+
const res = await runResolvers(action);
|
|
153
210
|
if (step.saveAs) this.context[step.saveAs] = res;
|
|
154
211
|
break;
|
|
155
212
|
}
|
|
156
|
-
|
|
157
213
|
case 'use': {
|
|
158
|
-
const res = await
|
|
214
|
+
const res = await runResolvers(`Use ${step.tool}`);
|
|
159
215
|
if (step.saveAs) this.context[step.saveAs] = res;
|
|
160
216
|
break;
|
|
161
217
|
}
|
|
162
|
-
|
|
163
218
|
case 'ask': {
|
|
164
|
-
const res = await
|
|
219
|
+
const res = await runResolvers(`Ask ${step.target}`);
|
|
165
220
|
if (step.saveAs) this.context[step.saveAs] = res;
|
|
166
221
|
break;
|
|
167
222
|
}
|
|
168
|
-
|
|
169
223
|
case 'if': {
|
|
170
224
|
if (this.evaluateCondition(step.condition, this.context)) {
|
|
171
225
|
for (const s of step.body) await this.executeStep(s, agentResolver);
|
|
172
226
|
}
|
|
173
227
|
break;
|
|
174
228
|
}
|
|
175
|
-
|
|
176
229
|
case 'parallel': {
|
|
177
230
|
await Promise.all(step.steps.map(s => this.executeStep(s, agentResolver)));
|
|
178
231
|
break;
|
|
179
232
|
}
|
|
180
|
-
|
|
181
233
|
case 'connect': {
|
|
182
234
|
this.resources[step.resource] = step.endpoint;
|
|
183
235
|
break;
|
|
184
236
|
}
|
|
185
|
-
|
|
186
237
|
case 'agent_use': {
|
|
187
238
|
this.agentMap[step.logicalName] = step.resource;
|
|
188
239
|
break;
|
|
189
240
|
}
|
|
190
|
-
|
|
191
241
|
case 'debrief': {
|
|
192
242
|
this.emit('debrief', { agent: step.agent, message: step.message });
|
|
193
243
|
break;
|
|
194
244
|
}
|
|
195
|
-
|
|
196
245
|
case 'evolve': {
|
|
197
246
|
const maxGen = step.constraints?.max_generations || 1;
|
|
198
|
-
if (maxGen < 1)
|
|
199
|
-
this.context['improved_summary'] = this.context['summary'] || '';
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
247
|
+
if (maxGen < 1) return;
|
|
203
248
|
const summaryStep = this.findLastSummaryStep();
|
|
204
249
|
if (!summaryStep) {
|
|
205
|
-
|
|
206
|
-
this.context['improved_summary'] = this.context['summary'] || '';
|
|
250
|
+
this.addWarning('Evolve step has no prior "Ask ... Save as" step to evolve');
|
|
207
251
|
return;
|
|
208
252
|
}
|
|
209
|
-
|
|
210
253
|
const varName = summaryStep.saveAs;
|
|
211
254
|
let currentOutput = this.context[varName] || '';
|
|
212
|
-
|
|
213
255
|
for (let attempt = 0; attempt < maxGen; attempt++) {
|
|
214
256
|
let revisedAction = summaryStep.actionRaw.replace(/\{([^\}]+)\}/g, (_, path) => {
|
|
215
257
|
const val = this.getNested(this.context, path.trim());
|
|
216
258
|
return val !== undefined ? String(val) : `{${path}}`;
|
|
217
259
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
revisedAction = revisedAction.replace(/(")$/, `\n\n[IMPROVEMENT FEEDBACK: ${step.feedback}]$1`);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
currentOutput = await runAllResolvers(revisedAction);
|
|
260
|
+
if (step.feedback) revisedAction += `\n[IMPROVEMENT FEEDBACK: ${step.feedback}]`;
|
|
261
|
+
currentOutput = await runResolvers(revisedAction);
|
|
224
262
|
this.context[varName] = currentOutput;
|
|
225
|
-
|
|
226
263
|
this.emit('debrief', {
|
|
227
264
|
agent: step.agent || 'Evolver',
|
|
228
265
|
message: `Evolve attempt ${attempt + 1}/${maxGen}: ${currentOutput.substring(0, 80)}...`
|
|
229
266
|
});
|
|
230
267
|
}
|
|
231
|
-
|
|
232
268
|
this.context['improved_summary'] = currentOutput;
|
|
233
269
|
break;
|
|
234
270
|
}
|
|
235
|
-
|
|
236
271
|
case 'prompt': {
|
|
237
272
|
const input = await this.getUserInput(step.question);
|
|
238
273
|
if (step.saveAs) this.context[step.saveAs] = input;
|
|
239
274
|
break;
|
|
240
275
|
}
|
|
241
|
-
|
|
242
276
|
case 'persist': {
|
|
243
277
|
const val = this.context[step.variable];
|
|
244
|
-
if (val !== undefined)
|
|
245
|
-
fs.appendFileSync(step.target, JSON.stringify(val) + '\n', 'utf8');
|
|
246
|
-
}
|
|
278
|
+
if (val !== undefined) fs.appendFileSync(step.target, JSON.stringify(val) + '\n', 'utf8');
|
|
247
279
|
break;
|
|
248
280
|
}
|
|
249
|
-
|
|
250
281
|
case 'emit': {
|
|
251
282
|
const payload = step.payload ? this.getNested(this.context, step.payload) : undefined;
|
|
252
283
|
this.emit(step.event, payload || step.payload);
|
|
@@ -254,7 +285,6 @@ class RuntimeAPI {
|
|
|
254
285
|
}
|
|
255
286
|
}
|
|
256
287
|
|
|
257
|
-
// Verbose logging of context after each step
|
|
258
288
|
if (this.verbose) {
|
|
259
289
|
console.log(`\n[Step: ${step.type} | saveAs: ${step.saveAs || 'N/A'}]`);
|
|
260
290
|
console.log(JSON.stringify(this.context, null, 2));
|
|
@@ -275,9 +305,17 @@ class RuntimeAPI {
|
|
|
275
305
|
async executeWorkflow(workflow, inputs, agentResolver) {
|
|
276
306
|
this.context = { ...inputs };
|
|
277
307
|
this.workflowSteps = workflow.steps;
|
|
308
|
+
this.allowedResolvers = new Set(workflow.allowedResolvers || []);
|
|
309
|
+
|
|
310
|
+
for (const step of workflow.steps) await this.executeStep(step, agentResolver);
|
|
278
311
|
|
|
279
|
-
|
|
280
|
-
|
|
312
|
+
this.printDisallowedSummary();
|
|
313
|
+
|
|
314
|
+
if (this.__warnings.length) {
|
|
315
|
+
console.log(`\n[O-Lang] ⚠️ Parser/Runtime Warnings (${this.__warnings.length}):`);
|
|
316
|
+
this.__warnings.slice(0, 5).forEach((w, i) => {
|
|
317
|
+
console.log(`${i + 1}. ${w.timestamp} | ${w.message}`);
|
|
318
|
+
});
|
|
281
319
|
}
|
|
282
320
|
|
|
283
321
|
const result = {};
|