@o-lang/olang 1.0.5 → 1.0.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  const { Command } = require('commander');
3
- const { parse } = require('../src/parser'); // adjusted path if bin vs root
4
- const { execute } = require('../src/runtime');
3
+ const { parse } = require('./src/parser');
4
+ const { execute } = require('./src/runtime');
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
7
 
@@ -49,61 +49,47 @@ async function defaultMockResolver(action, context) {
49
49
  }
50
50
 
51
51
  /**
52
- * Built-in Math Resolver (so action style math strings are handled too)
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]) + parseFloat(m[2]);
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 mechanism
73
+ * Resolver chaining with verbose + context logging
91
74
  */
92
- function createResolverChain(resolvers) {
93
- return async (action, context) => {
94
- for (const resolver of resolvers) {
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 result = await resolver(action, context);
97
- if (result !== null && result !== undefined) {
98
- return result;
99
- }
100
- } catch (err) {
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
- return `[Unhandled: ${action}]`;
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.\nExample:\n -r @o-lang/llm-groq\n -r @o-lang/notify-telegram',
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o-lang/olang",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "author": "Olalekan Ogundipe <info@workfily.com>",
5
5
  "description": "O-Lang: A governance language for user-directed, rule-enforced agent workflows",
6
6
  "main": "./src/index.js",
package/src/parser.js CHANGED
@@ -23,7 +23,7 @@ function parse(code, fileName = null) {
23
23
  let line = lines[i];
24
24
 
25
25
  // ============================
26
- // NEW: Detect math operations
26
+ // NEW: Detect math operations..
27
27
  // ============================
28
28
 
29
29
  // Add X and Y
package/src/runtime.js CHANGED
@@ -1,13 +1,13 @@
1
- // src/runtime.js
2
1
  const fs = require('fs');
3
2
 
4
3
  class RuntimeAPI {
5
- constructor() {
4
+ constructor({ verbose = false } = {}) {
6
5
  this.context = {};
7
6
  this.resources = {};
8
7
  this.agentMap = {};
9
8
  this.events = {};
10
9
  this.workflowSteps = []; // store for evolve lookup
10
+ this.verbose = verbose; // verbose logging flag
11
11
  }
12
12
 
13
13
  on(eventName, cb) {
@@ -47,9 +47,6 @@ class RuntimeAPI {
47
47
  return null;
48
48
  }
49
49
 
50
- // --------------------------
51
- // Math helper functions
52
- // --------------------------
53
50
  mathFunctions = {
54
51
  add: (a, b) => a + b,
55
52
  subtract: (a, b) => a - b,
@@ -71,15 +68,12 @@ class RuntimeAPI {
71
68
  };
72
69
 
73
70
  evaluateMath(expr) {
74
- // Replace context variables in curly braces
75
71
  expr = expr.replace(/\{([^\}]+)\}/g, (_, path) => {
76
72
  const value = this.getNested(this.context, path.trim());
77
- // if value is string, wrap in quotes for functions that accept strings
78
73
  if (typeof value === 'string') return `"${value.replace(/"/g, '\\"')}"`;
79
74
  return value !== undefined ? value : 0;
80
75
  });
81
76
 
82
- // expose safe functions only
83
77
  const funcNames = Object.keys(this.mathFunctions);
84
78
  const safeFunc = {};
85
79
  funcNames.forEach(fn => {
@@ -87,7 +81,6 @@ class RuntimeAPI {
87
81
  });
88
82
 
89
83
  try {
90
- // eslint-disable-next-line no-new-func
91
84
  const f = new Function(...funcNames, `return ${expr};`);
92
85
  return f(...funcNames.map(fn => safeFunc[fn]));
93
86
  } catch (e) {
@@ -100,9 +93,33 @@ class RuntimeAPI {
100
93
  // Execute workflow step
101
94
  // --------------------------
102
95
  async executeStep(step, agentResolver) {
103
- switch (step.type) {
96
+ const stepType = step.type;
97
+
98
+ // Helper: execute all resolvers for this step action
99
+ const runAllResolvers = async (action) => {
100
+ const outputs = [];
101
+ if (agentResolver && Array.isArray(agentResolver._chain)) {
102
+ for (let idx = 0; idx < agentResolver._chain.length; idx++) {
103
+ const resolver = agentResolver._chain[idx];
104
+ try {
105
+ const out = await resolver(action, this.context);
106
+ outputs.push(out);
107
+ this.context[`__resolver_${idx}`] = out;
108
+ } catch (e) {
109
+ console.error(`❌ Resolver ${idx} error for action "${action}":`, e.message);
110
+ outputs.push(null);
111
+ }
112
+ }
113
+ } else {
114
+ const out = await agentResolver(action, this.context);
115
+ outputs.push(out);
116
+ this.context['__resolver_0'] = out;
117
+ }
118
+ return outputs[outputs.length - 1]; // last result as primary
119
+ };
120
+
121
+ switch (stepType) {
104
122
  case 'calculate': {
105
- // step.expression or actionRaw can contain math expression strings
106
123
  const expr = step.expression || step.actionRaw;
107
124
  const result = this.evaluateMath(expr);
108
125
  if (step.saveAs) this.context[step.saveAs] = result;
@@ -115,23 +132,16 @@ class RuntimeAPI {
115
132
  return value !== undefined ? String(value) : `{${path}}`;
116
133
  });
117
134
 
118
- // Provide fallback math recognition for action lines too (e.g., add(1,2))
119
- // Try simple math function calls in action form
120
135
  const mathCall = action.match(/^(add|subtract|multiply|divide|sum|avg|min|max|round|floor|ceil|abs)\((.*)\)$/i);
121
136
  if (mathCall) {
122
137
  const fn = mathCall[1].toLowerCase();
123
138
  const argsRaw = mathCall[2];
124
- // simple split by comma (doesn't handle nested arrays/funcs) — fine for workflow math
125
139
  const args = argsRaw.split(',').map(s => s.trim()).map(s => {
126
- // if it's a quoted string
127
140
  if (/^".*"$/.test(s) || /^'.*'$/.test(s)) return s.slice(1, -1);
128
- // try number
129
141
  if (!isNaN(s)) return parseFloat(s);
130
- // variable lookup
131
142
  const lookup = s.replace(/^\{|\}$/g, '').trim();
132
143
  return this.getNested(this.context, lookup);
133
144
  });
134
-
135
145
  if (this.mathFunctions[fn]) {
136
146
  const value = this.mathFunctions[fn](...args);
137
147
  if (step.saveAs) this.context[step.saveAs] = value;
@@ -139,20 +149,19 @@ class RuntimeAPI {
139
149
  }
140
150
  }
141
151
 
142
- // fallback to agent resolver
143
- const res = await agentResolver(action, this.context);
152
+ const res = await runAllResolvers(action);
144
153
  if (step.saveAs) this.context[step.saveAs] = res;
145
154
  break;
146
155
  }
147
156
 
148
157
  case 'use': {
149
- const res = await agentResolver(`Use ${step.tool}`, this.context);
158
+ const res = await runAllResolvers(`Use ${step.tool}`);
150
159
  if (step.saveAs) this.context[step.saveAs] = res;
151
160
  break;
152
161
  }
153
162
 
154
163
  case 'ask': {
155
- const res = await agentResolver(`Ask ${step.target}`, this.context);
164
+ const res = await runAllResolvers(`Ask ${step.target}`);
156
165
  if (step.saveAs) this.context[step.saveAs] = res;
157
166
  break;
158
167
  }
@@ -211,7 +220,7 @@ class RuntimeAPI {
211
220
  revisedAction = revisedAction.replace(/(")$/, `\n\n[IMPROVEMENT FEEDBACK: ${step.feedback}]$1`);
212
221
  }
213
222
 
214
- currentOutput = await agentResolver(revisedAction, this.context);
223
+ currentOutput = await runAllResolvers(revisedAction);
215
224
  this.context[varName] = currentOutput;
216
225
 
217
226
  this.emit('debrief', {
@@ -244,6 +253,12 @@ class RuntimeAPI {
244
253
  break;
245
254
  }
246
255
  }
256
+
257
+ // Verbose logging of context after each step
258
+ if (this.verbose) {
259
+ console.log(`\n[Step: ${step.type} | saveAs: ${step.saveAs || 'N/A'}]`);
260
+ console.log(JSON.stringify(this.context, null, 2));
261
+ }
247
262
  }
248
263
 
249
264
  async getUserInput(question) {
@@ -273,8 +288,8 @@ class RuntimeAPI {
273
288
  }
274
289
  }
275
290
 
276
- async function execute(workflow, inputs, agentResolver) {
277
- const rt = new RuntimeAPI();
291
+ async function execute(workflow, inputs, agentResolver, verbose = false) {
292
+ const rt = new RuntimeAPI({ verbose });
278
293
  return rt.executeWorkflow(workflow, inputs, agentResolver);
279
294
  }
280
295