@o-lang/olang 1.0.6 → 1.0.8
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 +28 -44
- package/package.json +1 -1
- package/src/parser.js +55 -21
- package/src/runtime.js +115 -63
package/cli.js
CHANGED
|
@@ -49,61 +49,47 @@ async function defaultMockResolver(action, context) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* Built-in Math Resolver
|
|
53
|
-
* Supports action strings like: add(1,2), subtract(5,2), multiply(2,3), divide(6,3), sum([1,2,3])
|
|
54
|
-
* Note: runtime handles calculate steps; this resolver helps when parser emits actions with math strings.
|
|
52
|
+
* Built-in Math Resolver
|
|
55
53
|
*/
|
|
56
54
|
async function builtInMathResolver(action, context) {
|
|
57
55
|
if (!action || typeof action !== 'string') return null;
|
|
58
56
|
|
|
59
|
-
// Replace contextual placeholders {var}
|
|
60
57
|
const a = action.replace(/\{([^\}]+)\}/g, (_, k) => {
|
|
61
58
|
const v = context[k.trim()];
|
|
62
59
|
return v !== undefined ? v : `{${k}}`;
|
|
63
60
|
});
|
|
64
61
|
|
|
65
|
-
// simple function matches
|
|
66
62
|
let m;
|
|
67
|
-
m = a.match(/^add\(([^,]+),\s*([^)]+)\)$/i);
|
|
68
|
-
if (m) return parseFloat(m[1])
|
|
63
|
+
m = a.match(/^add\(([^,]+),\s*([^)]+)\)$/i); if (m) return parseFloat(m[1]) + parseFloat(m[2]);
|
|
64
|
+
m = a.match(/^subtract\(([^,]+),\s*([^)]+)\)$/i); if (m) return parseFloat(m[1]) - parseFloat(m[2]);
|
|
65
|
+
m = a.match(/^multiply\(([^,]+),\s*([^)]+)\)$/i); if (m) return parseFloat(m[1]) * parseFloat(m[2]);
|
|
66
|
+
m = a.match(/^divide\(([^,]+),\s*([^)]+)\)$/i); if (m) return parseFloat(m[1]) / parseFloat(m[2]);
|
|
67
|
+
m = a.match(/^sum\(\s*\[([^\]]+)\]\s*\)$/i); if (m) return m[1].split(',').map(s => parseFloat(s.trim())).reduce((s, v) => s + v, 0);
|
|
69
68
|
|
|
70
|
-
m = a.match(/^subtract\(([^,]+),\s*([^)]+)\)$/i);
|
|
71
|
-
if (m) return parseFloat(m[1]) - parseFloat(m[2]);
|
|
72
|
-
|
|
73
|
-
m = a.match(/^multiply\(([^,]+),\s*([^)]+)\)$/i);
|
|
74
|
-
if (m) return parseFloat(m[1]) * parseFloat(m[2]);
|
|
75
|
-
|
|
76
|
-
m = a.match(/^divide\(([^,]+),\s*([^)]+)\)$/i);
|
|
77
|
-
if (m) return parseFloat(m[1]) / parseFloat(m[2]);
|
|
78
|
-
|
|
79
|
-
m = a.match(/^sum\(\s*\[([^\]]+)\]\s*\)$/i);
|
|
80
|
-
if (m) {
|
|
81
|
-
const arr = m[1].split(',').map(s => parseFloat(s.trim()));
|
|
82
|
-
return arr.reduce((s, v) => s + v, 0);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// not a math action
|
|
86
69
|
return null;
|
|
87
70
|
}
|
|
88
71
|
|
|
89
72
|
/**
|
|
90
|
-
* Resolver chaining
|
|
73
|
+
* Resolver chaining with verbose + context logging
|
|
91
74
|
*/
|
|
92
|
-
function createResolverChain(resolvers) {
|
|
93
|
-
|
|
94
|
-
|
|
75
|
+
function createResolverChain(resolvers, verbose = false) {
|
|
76
|
+
const chain = resolvers.slice();
|
|
77
|
+
const wrapped = async (action, context) => {
|
|
78
|
+
let lastResult;
|
|
79
|
+
for (let i = 0; i < chain.length; i++) {
|
|
95
80
|
try {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
console.error(`❌ Resolver error for action "${action}":`, err.message);
|
|
102
|
-
throw err;
|
|
81
|
+
const res = await chain[i](action, context);
|
|
82
|
+
context[`__resolver_${i}`] = res;
|
|
83
|
+
lastResult = res;
|
|
84
|
+
} catch (e) {
|
|
85
|
+
console.error(`❌ Resolver ${i} failed for action "${action}":`, e.message);
|
|
103
86
|
}
|
|
104
87
|
}
|
|
105
|
-
|
|
88
|
+
if (verbose) console.log(`[Resolver Chain] action="${action}" lastResult=`, lastResult);
|
|
89
|
+
return lastResult;
|
|
106
90
|
};
|
|
91
|
+
wrapped._chain = chain;
|
|
92
|
+
return wrapped;
|
|
107
93
|
}
|
|
108
94
|
|
|
109
95
|
function loadSingleResolver(specifier) {
|
|
@@ -111,9 +97,7 @@ function loadSingleResolver(specifier) {
|
|
|
111
97
|
|
|
112
98
|
try {
|
|
113
99
|
const resolver = require(specifier);
|
|
114
|
-
if (typeof resolver !== 'function')
|
|
115
|
-
throw new Error(`Resolver must export a function`);
|
|
116
|
-
}
|
|
100
|
+
if (typeof resolver !== 'function') throw new Error(`Resolver must export a function`);
|
|
117
101
|
console.log(`📦 Loaded resolver: ${specifier}`);
|
|
118
102
|
return resolver;
|
|
119
103
|
} catch (e1) {
|
|
@@ -133,7 +117,7 @@ function loadSingleResolver(specifier) {
|
|
|
133
117
|
/**
|
|
134
118
|
* loadResolverChain: include built-in math resolver first, then user resolvers, then default mock resolver
|
|
135
119
|
*/
|
|
136
|
-
function loadResolverChain(specifiers) {
|
|
120
|
+
function loadResolverChain(specifiers, verbose = false) {
|
|
137
121
|
const userResolvers = specifiers?.map(loadSingleResolver) || [];
|
|
138
122
|
const resolvers = [builtInMathResolver, ...userResolvers, defaultMockResolver];
|
|
139
123
|
|
|
@@ -143,7 +127,7 @@ function loadResolverChain(specifiers) {
|
|
|
143
127
|
console.log(`📦 Loaded user resolvers: ${specifiers.join(', ')}`);
|
|
144
128
|
}
|
|
145
129
|
|
|
146
|
-
return createResolverChain(resolvers);
|
|
130
|
+
return createResolverChain(resolvers, verbose);
|
|
147
131
|
}
|
|
148
132
|
|
|
149
133
|
/**
|
|
@@ -157,7 +141,7 @@ program
|
|
|
157
141
|
.command('run <file>')
|
|
158
142
|
.option(
|
|
159
143
|
'-r, --resolver <specifier>',
|
|
160
|
-
'Resolver (npm package or local path). Can be used multiple times
|
|
144
|
+
'Resolver (npm package or local path). Can be used multiple times.',
|
|
161
145
|
(val, acc) => { acc.push(val); return acc; },
|
|
162
146
|
[]
|
|
163
147
|
)
|
|
@@ -166,13 +150,13 @@ program
|
|
|
166
150
|
'Input parameters',
|
|
167
151
|
(val, acc = {}) => {
|
|
168
152
|
const [k, v] = val.split('=');
|
|
169
|
-
// try to parse numbers, preserve strings otherwise
|
|
170
153
|
const parsed = v === undefined ? '' : (v === 'true' ? true : (v === 'false' ? false : (isNaN(v) ? v : parseFloat(v))));
|
|
171
154
|
acc[k] = parsed;
|
|
172
155
|
return acc;
|
|
173
156
|
},
|
|
174
157
|
{}
|
|
175
158
|
)
|
|
159
|
+
.option('-v, --verbose', 'Verbose mode: logs resolver outputs and context after each step')
|
|
176
160
|
.action(async (file, options) => {
|
|
177
161
|
try {
|
|
178
162
|
ensureOlExtension(file);
|
|
@@ -180,8 +164,8 @@ program
|
|
|
180
164
|
const content = fs.readFileSync(file, 'utf8');
|
|
181
165
|
const workflow = parse(content);
|
|
182
166
|
|
|
183
|
-
const resolver = loadResolverChain(options.resolver);
|
|
184
|
-
const result = await execute(workflow, options.input, resolver);
|
|
167
|
+
const resolver = loadResolverChain(options.resolver, options.verbose);
|
|
168
|
+
const result = await execute(workflow, options.input, resolver, options.verbose);
|
|
185
169
|
|
|
186
170
|
console.log('\n=== Workflow Result ===');
|
|
187
171
|
console.log(JSON.stringify(result, null, 2));
|
package/package.json
CHANGED
package/src/parser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
|
}
|
|
@@ -15,18 +15,30 @@ function parse(code, fileName = null) {
|
|
|
15
15
|
name: 'Unnamed Workflow',
|
|
16
16
|
parameters: [],
|
|
17
17
|
steps: [],
|
|
18
|
-
returnValues: []
|
|
18
|
+
returnValues: [],
|
|
19
|
+
allowedResolvers: [] // NEW: store allowed resolvers
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
let i = 0;
|
|
22
23
|
while (i < lines.length) {
|
|
23
24
|
let line = lines[i];
|
|
24
25
|
|
|
26
|
+
// ---------------------------
|
|
27
|
+
// NEW: Detect Allow resolvers
|
|
28
|
+
// ---------------------------
|
|
29
|
+
const allowMatch = line.match(/^Allow resolvers\s*:\s*$/i);
|
|
30
|
+
if (allowMatch) {
|
|
31
|
+
i++;
|
|
32
|
+
while (i < lines.length && lines[i].startsWith(' ')) {
|
|
33
|
+
workflow.allowedResolvers.push(lines[i].trim());
|
|
34
|
+
i++;
|
|
35
|
+
}
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
// ============================
|
|
26
|
-
//
|
|
40
|
+
// Math operations
|
|
27
41
|
// ============================
|
|
28
|
-
|
|
29
|
-
// Add X and Y
|
|
30
42
|
let mathAdd = line.match(/^Add\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
|
|
31
43
|
if (mathAdd) {
|
|
32
44
|
workflow.steps.push({
|
|
@@ -38,7 +50,6 @@ function parse(code, fileName = null) {
|
|
|
38
50
|
continue;
|
|
39
51
|
}
|
|
40
52
|
|
|
41
|
-
// Subtract A from B => B - A
|
|
42
53
|
let mathSub = line.match(/^Subtract\s+\{(.+?)\}\s+from\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
|
|
43
54
|
if (mathSub) {
|
|
44
55
|
workflow.steps.push({
|
|
@@ -50,7 +61,6 @@ function parse(code, fileName = null) {
|
|
|
50
61
|
continue;
|
|
51
62
|
}
|
|
52
63
|
|
|
53
|
-
// Multiply X and Y
|
|
54
64
|
let mathMul = line.match(/^Multiply\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
|
|
55
65
|
if (mathMul) {
|
|
56
66
|
workflow.steps.push({
|
|
@@ -62,7 +72,6 @@ function parse(code, fileName = null) {
|
|
|
62
72
|
continue;
|
|
63
73
|
}
|
|
64
74
|
|
|
65
|
-
// Divide A by B
|
|
66
75
|
let mathDiv = line.match(/^Divide\s+\{(.+?)\}\s+by\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
|
|
67
76
|
if (mathDiv) {
|
|
68
77
|
workflow.steps.push({
|
|
@@ -74,11 +83,9 @@ function parse(code, fileName = null) {
|
|
|
74
83
|
continue;
|
|
75
84
|
}
|
|
76
85
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// Workflow
|
|
86
|
+
// ---------------------------
|
|
87
|
+
// Workflow definition
|
|
88
|
+
// ---------------------------
|
|
82
89
|
const wfMatch = line.match(/^Workflow\s+"([^"]+)"(?:\s+with\s+(.+))?/i);
|
|
83
90
|
if (wfMatch) {
|
|
84
91
|
workflow.name = wfMatch[1];
|
|
@@ -87,7 +94,9 @@ function parse(code, fileName = null) {
|
|
|
87
94
|
continue;
|
|
88
95
|
}
|
|
89
96
|
|
|
90
|
-
//
|
|
97
|
+
// ---------------------------
|
|
98
|
+
// Steps
|
|
99
|
+
// ---------------------------
|
|
91
100
|
const stepMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
|
|
92
101
|
if (stepMatch) {
|
|
93
102
|
workflow.steps.push({
|
|
@@ -101,7 +110,6 @@ function parse(code, fileName = null) {
|
|
|
101
110
|
continue;
|
|
102
111
|
}
|
|
103
112
|
|
|
104
|
-
// Save as
|
|
105
113
|
const saveMatch = line.match(/^Save as\s+(.+)$/i);
|
|
106
114
|
if (saveMatch && workflow.steps.length > 0) {
|
|
107
115
|
workflow.steps[workflow.steps.length - 1].saveAs = saveMatch[1].trim();
|
|
@@ -109,7 +117,6 @@ function parse(code, fileName = null) {
|
|
|
109
117
|
continue;
|
|
110
118
|
}
|
|
111
119
|
|
|
112
|
-
// Constraint
|
|
113
120
|
const constraintMatch = line.match(/^Constraint:\s*(.+)$/i);
|
|
114
121
|
if (constraintMatch && workflow.steps.length > 0) {
|
|
115
122
|
const lastStep = workflow.steps[workflow.steps.length - 1];
|
|
@@ -135,7 +142,9 @@ function parse(code, fileName = null) {
|
|
|
135
142
|
continue;
|
|
136
143
|
}
|
|
137
144
|
|
|
138
|
-
//
|
|
145
|
+
// ---------------------------
|
|
146
|
+
// If blocks
|
|
147
|
+
// ---------------------------
|
|
139
148
|
const ifMatch = line.match(/^If\s+(.+)\s+then$/i);
|
|
140
149
|
if (ifMatch) {
|
|
141
150
|
const condition = ifMatch[1].trim();
|
|
@@ -150,7 +159,9 @@ function parse(code, fileName = null) {
|
|
|
150
159
|
continue;
|
|
151
160
|
}
|
|
152
161
|
|
|
153
|
-
//
|
|
162
|
+
// ---------------------------
|
|
163
|
+
// Parallel blocks
|
|
164
|
+
// ---------------------------
|
|
154
165
|
const parMatch = line.match(/^Run in parallel$/i);
|
|
155
166
|
if (parMatch) {
|
|
156
167
|
const steps = [];
|
|
@@ -164,7 +175,9 @@ function parse(code, fileName = null) {
|
|
|
164
175
|
continue;
|
|
165
176
|
}
|
|
166
177
|
|
|
178
|
+
// ---------------------------
|
|
167
179
|
// Connect
|
|
180
|
+
// ---------------------------
|
|
168
181
|
const connMatch = line.match(/^Connect\s+"([^"]+)"\s+using\s+"([^"]+)"$/i);
|
|
169
182
|
if (connMatch) {
|
|
170
183
|
workflow.steps.push({
|
|
@@ -176,7 +189,9 @@ function parse(code, fileName = null) {
|
|
|
176
189
|
continue;
|
|
177
190
|
}
|
|
178
191
|
|
|
192
|
+
// ---------------------------
|
|
179
193
|
// Agent uses
|
|
194
|
+
// ---------------------------
|
|
180
195
|
const agentUseMatch = line.match(/^Agent\s+"([^"]+)"\s+uses\s+"([^"]+)"$/i);
|
|
181
196
|
if (agentUseMatch) {
|
|
182
197
|
workflow.steps.push({
|
|
@@ -188,7 +203,9 @@ function parse(code, fileName = null) {
|
|
|
188
203
|
continue;
|
|
189
204
|
}
|
|
190
205
|
|
|
206
|
+
// ---------------------------
|
|
191
207
|
// Debrief
|
|
208
|
+
// ---------------------------
|
|
192
209
|
const debriefMatch = line.match(/^Debrief\s+(\w+)\s+with\s+"(.+)"$/i);
|
|
193
210
|
if (debriefMatch) {
|
|
194
211
|
workflow.steps.push({
|
|
@@ -200,7 +217,9 @@ function parse(code, fileName = null) {
|
|
|
200
217
|
continue;
|
|
201
218
|
}
|
|
202
219
|
|
|
220
|
+
// ---------------------------
|
|
203
221
|
// Evolve
|
|
222
|
+
// ---------------------------
|
|
204
223
|
const evolveMatch = line.match(/^Evolve\s+(\w+)\s+using\s+feedback:\s+"(.+)"$/i);
|
|
205
224
|
if (evolveMatch) {
|
|
206
225
|
workflow.steps.push({
|
|
@@ -212,7 +231,9 @@ function parse(code, fileName = null) {
|
|
|
212
231
|
continue;
|
|
213
232
|
}
|
|
214
233
|
|
|
215
|
-
//
|
|
234
|
+
// ---------------------------
|
|
235
|
+
// Prompt user
|
|
236
|
+
// ---------------------------
|
|
216
237
|
const promptMatch = line.match(/^Prompt user to\s+"(.+)"$/i);
|
|
217
238
|
if (promptMatch) {
|
|
218
239
|
workflow.steps.push({
|
|
@@ -224,7 +245,9 @@ function parse(code, fileName = null) {
|
|
|
224
245
|
continue;
|
|
225
246
|
}
|
|
226
247
|
|
|
248
|
+
// ---------------------------
|
|
227
249
|
// Persist
|
|
250
|
+
// ---------------------------
|
|
228
251
|
const persistMatch = line.match(/^Persist\s+(.+)\s+to\s+"(.+)"$/i);
|
|
229
252
|
if (persistMatch) {
|
|
230
253
|
workflow.steps.push({
|
|
@@ -236,7 +259,9 @@ function parse(code, fileName = null) {
|
|
|
236
259
|
continue;
|
|
237
260
|
}
|
|
238
261
|
|
|
262
|
+
// ---------------------------
|
|
239
263
|
// Emit
|
|
264
|
+
// ---------------------------
|
|
240
265
|
const emitMatch = line.match(/^Emit\s+"(.+)"\s+with\s+(.+)$/i);
|
|
241
266
|
if (emitMatch) {
|
|
242
267
|
workflow.steps.push({
|
|
@@ -248,7 +273,9 @@ function parse(code, fileName = null) {
|
|
|
248
273
|
continue;
|
|
249
274
|
}
|
|
250
275
|
|
|
276
|
+
// ---------------------------
|
|
251
277
|
// Return
|
|
278
|
+
// ---------------------------
|
|
252
279
|
const returnMatch = line.match(/^Return\s+(.+)$/i);
|
|
253
280
|
if (returnMatch) {
|
|
254
281
|
workflow.returnValues = returnMatch[1].split(',').map(v => v.trim());
|
|
@@ -256,7 +283,9 @@ function parse(code, fileName = null) {
|
|
|
256
283
|
continue;
|
|
257
284
|
}
|
|
258
285
|
|
|
259
|
-
//
|
|
286
|
+
// ---------------------------
|
|
287
|
+
// Use tool
|
|
288
|
+
// ---------------------------
|
|
260
289
|
const useMatch = line.match(/^Use\s+(.+)$/i);
|
|
261
290
|
if (useMatch) {
|
|
262
291
|
workflow.steps.push({
|
|
@@ -269,7 +298,9 @@ function parse(code, fileName = null) {
|
|
|
269
298
|
continue;
|
|
270
299
|
}
|
|
271
300
|
|
|
272
|
-
//
|
|
301
|
+
// ---------------------------
|
|
302
|
+
// Ask target
|
|
303
|
+
// ---------------------------
|
|
273
304
|
const askMatch = line.match(/^Ask\s+(.+)$/i);
|
|
274
305
|
if (askMatch) {
|
|
275
306
|
workflow.steps.push({
|
|
@@ -288,6 +319,9 @@ function parse(code, fileName = null) {
|
|
|
288
319
|
return workflow;
|
|
289
320
|
}
|
|
290
321
|
|
|
322
|
+
// ---------------------------
|
|
323
|
+
// Parse nested blocks (If / Parallel)
|
|
324
|
+
// ---------------------------
|
|
291
325
|
function parseBlock(lines) {
|
|
292
326
|
const steps = [];
|
|
293
327
|
let current = null;
|
package/src/runtime.js
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
|
-
// src/runtime.js
|
|
2
1
|
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
3
|
|
|
4
4
|
class RuntimeAPI {
|
|
5
|
-
constructor() {
|
|
5
|
+
constructor({ verbose = false } = {}) {
|
|
6
6
|
this.context = {};
|
|
7
7
|
this.resources = {};
|
|
8
8
|
this.agentMap = {};
|
|
9
9
|
this.events = {};
|
|
10
|
-
this.workflowSteps = [];
|
|
10
|
+
this.workflowSteps = [];
|
|
11
|
+
this.allowedResolvers = null;
|
|
12
|
+
this.verbose = verbose;
|
|
13
|
+
|
|
14
|
+
// Ensure logs folder exists
|
|
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
|
+
|
|
19
|
+
// In-memory store for summary
|
|
20
|
+
this.disallowedAttempts = [];
|
|
11
21
|
}
|
|
12
22
|
|
|
23
|
+
// -----------------------------
|
|
24
|
+
// Event handling
|
|
25
|
+
// -----------------------------
|
|
13
26
|
on(eventName, cb) {
|
|
14
27
|
if (!this.events[eventName]) this.events[eventName] = [];
|
|
15
28
|
this.events[eventName].push(cb);
|
|
@@ -21,6 +34,40 @@ class RuntimeAPI {
|
|
|
21
34
|
}
|
|
22
35
|
}
|
|
23
36
|
|
|
37
|
+
// -----------------------------
|
|
38
|
+
// Disallowed resolver handling
|
|
39
|
+
// -----------------------------
|
|
40
|
+
logDisallowedResolver(resolverName, stepAction) {
|
|
41
|
+
const entry = {
|
|
42
|
+
resolver: resolverName,
|
|
43
|
+
step: stepAction,
|
|
44
|
+
timestamp: new Date().toISOString()
|
|
45
|
+
};
|
|
46
|
+
fs.appendFileSync(this.disallowedLogFile, JSON.stringify(entry) + '\n', 'utf8');
|
|
47
|
+
this.disallowedAttempts.push(entry);
|
|
48
|
+
|
|
49
|
+
if (this.verbose) {
|
|
50
|
+
console.warn(`[O-Lang] Disallowed resolver blocked: ${resolverName} | step: ${stepAction}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
printDisallowedSummary() {
|
|
55
|
+
if (!this.disallowedAttempts.length) return;
|
|
56
|
+
console.log('\n[O-Lang] ⚠️ Disallowed resolver summary:');
|
|
57
|
+
console.log(`Total blocked attempts: ${this.disallowedAttempts.length}`);
|
|
58
|
+
const displayCount = Math.min(5, this.disallowedAttempts.length);
|
|
59
|
+
console.log(`First ${displayCount} entries:`);
|
|
60
|
+
this.disallowedAttempts.slice(0, displayCount).forEach((e, i) => {
|
|
61
|
+
console.log(`${i + 1}. Resolver: ${e.resolver}, Step: ${e.step}, Time: ${e.timestamp}`);
|
|
62
|
+
});
|
|
63
|
+
if (this.disallowedAttempts.length > displayCount) {
|
|
64
|
+
console.log(`...and ${this.disallowedAttempts.length - displayCount} more entries logged in ${this.disallowedLogFile}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// -----------------------------
|
|
69
|
+
// Condition & math utilities
|
|
70
|
+
// -----------------------------
|
|
24
71
|
evaluateCondition(cond, ctx) {
|
|
25
72
|
cond = cond.trim();
|
|
26
73
|
const eq = cond.match(/^\{(.+)\}\s+equals\s+"(.*)"$/);
|
|
@@ -37,19 +84,6 @@ class RuntimeAPI {
|
|
|
37
84
|
return path.split('.').reduce((o, k) => (o && o[k] !== undefined) ? o[k] : undefined, obj);
|
|
38
85
|
}
|
|
39
86
|
|
|
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
|
-
// --------------------------
|
|
51
|
-
// Math helper functions
|
|
52
|
-
// --------------------------
|
|
53
87
|
mathFunctions = {
|
|
54
88
|
add: (a, b) => a + b,
|
|
55
89
|
subtract: (a, b) => a - b,
|
|
@@ -71,23 +105,17 @@ class RuntimeAPI {
|
|
|
71
105
|
};
|
|
72
106
|
|
|
73
107
|
evaluateMath(expr) {
|
|
74
|
-
// Replace context variables in curly braces
|
|
75
108
|
expr = expr.replace(/\{([^\}]+)\}/g, (_, path) => {
|
|
76
109
|
const value = this.getNested(this.context, path.trim());
|
|
77
|
-
// if value is string, wrap in quotes for functions that accept strings
|
|
78
110
|
if (typeof value === 'string') return `"${value.replace(/"/g, '\\"')}"`;
|
|
79
111
|
return value !== undefined ? value : 0;
|
|
80
112
|
});
|
|
81
113
|
|
|
82
|
-
// expose safe functions only
|
|
83
114
|
const funcNames = Object.keys(this.mathFunctions);
|
|
84
115
|
const safeFunc = {};
|
|
85
|
-
funcNames.forEach(fn =>
|
|
86
|
-
safeFunc[fn] = this.mathFunctions[fn];
|
|
87
|
-
});
|
|
116
|
+
funcNames.forEach(fn => safeFunc[fn] = this.mathFunctions[fn]);
|
|
88
117
|
|
|
89
118
|
try {
|
|
90
|
-
// eslint-disable-next-line no-new-func
|
|
91
119
|
const f = new Function(...funcNames, `return ${expr};`);
|
|
92
120
|
return f(...funcNames.map(fn => safeFunc[fn]));
|
|
93
121
|
} catch (e) {
|
|
@@ -96,140 +124,156 @@ class RuntimeAPI {
|
|
|
96
124
|
}
|
|
97
125
|
}
|
|
98
126
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
127
|
+
findLastSummaryStep() {
|
|
128
|
+
for (let i = this.workflowSteps.length - 1; i >= 0; i--) {
|
|
129
|
+
const step = this.workflowSteps[i];
|
|
130
|
+
if (step.type === 'action' && step.actionRaw?.startsWith('Ask ') && step.saveAs) {
|
|
131
|
+
return step;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// -----------------------------
|
|
138
|
+
// Step execution
|
|
139
|
+
// -----------------------------
|
|
102
140
|
async executeStep(step, agentResolver) {
|
|
103
|
-
|
|
141
|
+
const stepType = step.type;
|
|
142
|
+
|
|
143
|
+
const validateResolver = (resolver) => {
|
|
144
|
+
const resolverName = resolver?.name || resolver?.resolverName;
|
|
145
|
+
if (!resolverName) throw new Error('[O-Lang] Resolver missing name metadata');
|
|
146
|
+
if (!this.allowedResolvers.has(resolverName)) {
|
|
147
|
+
this.logDisallowedResolver(resolverName, step.actionRaw || step.tool || step.target);
|
|
148
|
+
throw new Error(`[O-Lang] Resolver "${resolverName}" is not allowed by workflow policy`);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const runAllResolvers = async (action) => {
|
|
153
|
+
const outputs = [];
|
|
154
|
+
if (agentResolver && Array.isArray(agentResolver._chain)) {
|
|
155
|
+
for (let idx = 0; idx < agentResolver._chain.length; idx++) {
|
|
156
|
+
const resolver = agentResolver._chain[idx];
|
|
157
|
+
validateResolver(resolver);
|
|
158
|
+
try {
|
|
159
|
+
const out = await resolver(action, this.context);
|
|
160
|
+
outputs.push(out);
|
|
161
|
+
this.context[`__resolver_${idx}`] = out;
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.error(`❌ Resolver ${idx} error for action "${action}":`, e.message);
|
|
164
|
+
outputs.push(null);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
validateResolver(agentResolver);
|
|
169
|
+
const out = await agentResolver(action, this.context);
|
|
170
|
+
outputs.push(out);
|
|
171
|
+
this.context['__resolver_0'] = out;
|
|
172
|
+
}
|
|
173
|
+
return outputs[outputs.length - 1];
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
switch (stepType) {
|
|
104
177
|
case 'calculate': {
|
|
105
|
-
// step.expression or actionRaw can contain math expression strings
|
|
106
178
|
const expr = step.expression || step.actionRaw;
|
|
107
179
|
const result = this.evaluateMath(expr);
|
|
108
180
|
if (step.saveAs) this.context[step.saveAs] = result;
|
|
109
181
|
break;
|
|
110
182
|
}
|
|
111
|
-
|
|
112
183
|
case 'action': {
|
|
113
184
|
const action = step.actionRaw.replace(/\{([^\}]+)\}/g, (_, path) => {
|
|
114
185
|
const value = this.getNested(this.context, path.trim());
|
|
115
186
|
return value !== undefined ? String(value) : `{${path}}`;
|
|
116
187
|
});
|
|
117
|
-
|
|
118
|
-
// Provide fallback math recognition for action lines too (e.g., add(1,2))
|
|
119
|
-
// Try simple math function calls in action form
|
|
120
188
|
const mathCall = action.match(/^(add|subtract|multiply|divide|sum|avg|min|max|round|floor|ceil|abs)\((.*)\)$/i);
|
|
121
189
|
if (mathCall) {
|
|
122
190
|
const fn = mathCall[1].toLowerCase();
|
|
123
191
|
const argsRaw = mathCall[2];
|
|
124
|
-
// simple split by comma (doesn't handle nested arrays/funcs) — fine for workflow math
|
|
125
192
|
const args = argsRaw.split(',').map(s => s.trim()).map(s => {
|
|
126
|
-
// if it's a quoted string
|
|
127
193
|
if (/^".*"$/.test(s) || /^'.*'$/.test(s)) return s.slice(1, -1);
|
|
128
|
-
// try number
|
|
129
194
|
if (!isNaN(s)) return parseFloat(s);
|
|
130
|
-
// variable lookup
|
|
131
195
|
const lookup = s.replace(/^\{|\}$/g, '').trim();
|
|
132
196
|
return this.getNested(this.context, lookup);
|
|
133
197
|
});
|
|
134
|
-
|
|
135
198
|
if (this.mathFunctions[fn]) {
|
|
136
199
|
const value = this.mathFunctions[fn](...args);
|
|
137
200
|
if (step.saveAs) this.context[step.saveAs] = value;
|
|
138
201
|
break;
|
|
139
202
|
}
|
|
140
203
|
}
|
|
141
|
-
|
|
142
|
-
// fallback to agent resolver
|
|
143
|
-
const res = await agentResolver(action, this.context);
|
|
204
|
+
const res = await runAllResolvers(action);
|
|
144
205
|
if (step.saveAs) this.context[step.saveAs] = res;
|
|
145
206
|
break;
|
|
146
207
|
}
|
|
147
|
-
|
|
148
208
|
case 'use': {
|
|
149
|
-
const res = await
|
|
209
|
+
const res = await runAllResolvers(`Use ${step.tool}`);
|
|
150
210
|
if (step.saveAs) this.context[step.saveAs] = res;
|
|
151
211
|
break;
|
|
152
212
|
}
|
|
153
|
-
|
|
154
213
|
case 'ask': {
|
|
155
|
-
const res = await
|
|
214
|
+
const res = await runAllResolvers(`Ask ${step.target}`);
|
|
156
215
|
if (step.saveAs) this.context[step.saveAs] = res;
|
|
157
216
|
break;
|
|
158
217
|
}
|
|
159
|
-
|
|
160
218
|
case 'if': {
|
|
161
219
|
if (this.evaluateCondition(step.condition, this.context)) {
|
|
162
220
|
for (const s of step.body) await this.executeStep(s, agentResolver);
|
|
163
221
|
}
|
|
164
222
|
break;
|
|
165
223
|
}
|
|
166
|
-
|
|
167
224
|
case 'parallel': {
|
|
168
225
|
await Promise.all(step.steps.map(s => this.executeStep(s, agentResolver)));
|
|
169
226
|
break;
|
|
170
227
|
}
|
|
171
|
-
|
|
172
228
|
case 'connect': {
|
|
173
229
|
this.resources[step.resource] = step.endpoint;
|
|
174
230
|
break;
|
|
175
231
|
}
|
|
176
|
-
|
|
177
232
|
case 'agent_use': {
|
|
178
233
|
this.agentMap[step.logicalName] = step.resource;
|
|
179
234
|
break;
|
|
180
235
|
}
|
|
181
|
-
|
|
182
236
|
case 'debrief': {
|
|
183
237
|
this.emit('debrief', { agent: step.agent, message: step.message });
|
|
184
238
|
break;
|
|
185
239
|
}
|
|
186
|
-
|
|
187
240
|
case 'evolve': {
|
|
188
241
|
const maxGen = step.constraints?.max_generations || 1;
|
|
189
242
|
if (maxGen < 1) {
|
|
190
243
|
this.context['improved_summary'] = this.context['summary'] || '';
|
|
191
244
|
return;
|
|
192
245
|
}
|
|
193
|
-
|
|
194
246
|
const summaryStep = this.findLastSummaryStep();
|
|
195
247
|
if (!summaryStep) {
|
|
196
248
|
console.warn('[O-Lang] Evolve: No prior "Ask ... Save as" step found to evolve');
|
|
197
249
|
this.context['improved_summary'] = this.context['summary'] || '';
|
|
198
250
|
return;
|
|
199
251
|
}
|
|
200
|
-
|
|
201
252
|
const varName = summaryStep.saveAs;
|
|
202
253
|
let currentOutput = this.context[varName] || '';
|
|
203
|
-
|
|
204
254
|
for (let attempt = 0; attempt < maxGen; attempt++) {
|
|
205
255
|
let revisedAction = summaryStep.actionRaw.replace(/\{([^\}]+)\}/g, (_, path) => {
|
|
206
256
|
const val = this.getNested(this.context, path.trim());
|
|
207
257
|
return val !== undefined ? String(val) : `{${path}}`;
|
|
208
258
|
});
|
|
209
|
-
|
|
210
259
|
if (step.feedback) {
|
|
211
260
|
revisedAction = revisedAction.replace(/(")$/, `\n\n[IMPROVEMENT FEEDBACK: ${step.feedback}]$1`);
|
|
212
261
|
}
|
|
213
|
-
|
|
214
|
-
currentOutput = await agentResolver(revisedAction, this.context);
|
|
262
|
+
currentOutput = await runAllResolvers(revisedAction);
|
|
215
263
|
this.context[varName] = currentOutput;
|
|
216
|
-
|
|
217
264
|
this.emit('debrief', {
|
|
218
265
|
agent: step.agent || 'Evolver',
|
|
219
266
|
message: `Evolve attempt ${attempt + 1}/${maxGen}: ${currentOutput.substring(0, 80)}...`
|
|
220
267
|
});
|
|
221
268
|
}
|
|
222
|
-
|
|
223
269
|
this.context['improved_summary'] = currentOutput;
|
|
224
270
|
break;
|
|
225
271
|
}
|
|
226
|
-
|
|
227
272
|
case 'prompt': {
|
|
228
273
|
const input = await this.getUserInput(step.question);
|
|
229
274
|
if (step.saveAs) this.context[step.saveAs] = input;
|
|
230
275
|
break;
|
|
231
276
|
}
|
|
232
|
-
|
|
233
277
|
case 'persist': {
|
|
234
278
|
const val = this.context[step.variable];
|
|
235
279
|
if (val !== undefined) {
|
|
@@ -237,13 +281,17 @@ class RuntimeAPI {
|
|
|
237
281
|
}
|
|
238
282
|
break;
|
|
239
283
|
}
|
|
240
|
-
|
|
241
284
|
case 'emit': {
|
|
242
285
|
const payload = step.payload ? this.getNested(this.context, step.payload) : undefined;
|
|
243
286
|
this.emit(step.event, payload || step.payload);
|
|
244
287
|
break;
|
|
245
288
|
}
|
|
246
289
|
}
|
|
290
|
+
|
|
291
|
+
if (this.verbose) {
|
|
292
|
+
console.log(`\n[Step: ${step.type} | saveAs: ${step.saveAs || 'N/A'}]`);
|
|
293
|
+
console.log(JSON.stringify(this.context, null, 2));
|
|
294
|
+
}
|
|
247
295
|
}
|
|
248
296
|
|
|
249
297
|
async getUserInput(question) {
|
|
@@ -260,11 +308,15 @@ class RuntimeAPI {
|
|
|
260
308
|
async executeWorkflow(workflow, inputs, agentResolver) {
|
|
261
309
|
this.context = { ...inputs };
|
|
262
310
|
this.workflowSteps = workflow.steps;
|
|
311
|
+
this.allowedResolvers = new Set(workflow.allowedResolvers || []);
|
|
263
312
|
|
|
264
313
|
for (const step of workflow.steps) {
|
|
265
314
|
await this.executeStep(step, agentResolver);
|
|
266
315
|
}
|
|
267
316
|
|
|
317
|
+
// Print disallowed resolver summary at the end
|
|
318
|
+
this.printDisallowedSummary();
|
|
319
|
+
|
|
268
320
|
const result = {};
|
|
269
321
|
for (const key of workflow.returnValues) {
|
|
270
322
|
result[key] = this.getNested(this.context, key);
|
|
@@ -273,8 +325,8 @@ class RuntimeAPI {
|
|
|
273
325
|
}
|
|
274
326
|
}
|
|
275
327
|
|
|
276
|
-
async function execute(workflow, inputs, agentResolver) {
|
|
277
|
-
const rt = new RuntimeAPI();
|
|
328
|
+
async function execute(workflow, inputs, agentResolver, verbose = false) {
|
|
329
|
+
const rt = new RuntimeAPI({ verbose });
|
|
278
330
|
return rt.executeWorkflow(workflow, inputs, agentResolver);
|
|
279
331
|
}
|
|
280
332
|
|