@o-lang/olang 1.0.13 → 1.0.15

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.
Files changed (3) hide show
  1. package/cli.js +1 -1
  2. package/package.json +1 -1
  3. package/src/runtime.js +45 -76
package/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  const { Command } = require('commander');
3
3
  const { parse } = require('./src/parser');
4
4
  const { execute } = require('./src/runtime');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o-lang/olang",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
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/runtime.js CHANGED
@@ -86,7 +86,7 @@ class RuntimeAPI {
86
86
  const gt = cond.match(/^\{(.+)\}\s+greater than\s+(\d+\.?\d*)$/);
87
87
  if (gt) return parseFloat(this.getNested(ctx, gt[1])) > parseFloat(gt[2]);
88
88
  const lt = cond.match(/^\{(.+)\}\s+less than\s+(\d+\.?\d*)$/);
89
- if (lt) return parseFloat(this.getNested(ctx, lt[1])) < parseFloat(lt[2]);
89
+ if (lt) return parseFloat(this.getNested(ctx, lt[1])) < parseFloat(gt[2]);
90
90
  return Boolean(this.getNested(ctx, cond.replace(/\{|\}/g, '')));
91
91
  }
92
92
 
@@ -145,33 +145,36 @@ class RuntimeAPI {
145
145
  const stepType = step.type;
146
146
 
147
147
  const validateResolver = (resolver) => {
148
- // Get resolver name from metadata, trim whitespace
149
- const resolverName = (resolver?.resolverName || resolver?.name || '').trim();
148
+ const resolverName = (resolver?.resolverName || resolver?.name || '').trim();
149
+ if (!resolverName) throw new Error('[O-Lang] Resolver missing name metadata');
150
150
 
151
- if (!resolverName) throw new Error('[O-Lang] Resolver missing name metadata');
151
+ const allowed = Array.from(this.allowedResolvers || []).map(r => r.trim());
152
152
 
153
- // Normalize allowed resolver names for comparison
154
- const allowed = Array.from(this.allowedResolvers || []).map(r => r.trim());
153
+ if (!allowed.includes(resolverName)) {
154
+ this.logDisallowedResolver(resolverName, step.actionRaw || step.tool || step.target);
155
+ throw new Error(`[O-Lang] Resolver "${resolverName}" is not allowed by workflow policy`);
156
+ }
157
+ };
155
158
 
156
- // Auto-inject builtInMathResolver if math is required
157
- if (resolverName === 'builtInMathResolver' && workflow.__requiresMath && !allowed.includes('builtInMathResolver')) {
158
- this.allowedResolvers.add('builtInMathResolver');
159
- allowed.push('builtInMathResolver');
160
- }
159
+ const runResolvers = async (action) => {
160
+ const outputs = [];
161
161
 
162
- if (!allowed.includes(resolverName)) {
163
- this.logDisallowedResolver(resolverName, step.actionRaw || step.tool || step.target);
164
- throw new Error(`[O-Lang] Resolver "${resolverName}" is not allowed by workflow policy`);
165
- }
166
- };
162
+ const mathPattern =
163
+ /^(Add|Subtract|Multiply|Divide|Sum|Avg|Min|Max|Round|Floor|Ceil|Abs)\b/i;
167
164
 
165
+ if (
166
+ step.actionRaw &&
167
+ mathPattern.test(step.actionRaw) &&
168
+ !this.allowedResolvers.has('builtInMathResolver')
169
+ ) {
170
+ this.allowedResolvers.add('builtInMathResolver');
171
+ }
168
172
 
169
- const runResolvers = async (action) => {
170
- const outputs = [];
171
173
  if (agentResolver && Array.isArray(agentResolver._chain)) {
172
174
  for (let idx = 0; idx < agentResolver._chain.length; idx++) {
173
175
  const resolver = agentResolver._chain[idx];
174
176
  validateResolver(resolver);
177
+
175
178
  try {
176
179
  const out = await resolver(action, this.context);
177
180
  outputs.push(out);
@@ -187,16 +190,17 @@ class RuntimeAPI {
187
190
  outputs.push(out);
188
191
  this.context['__resolver_0'] = out;
189
192
  }
193
+
190
194
  return outputs[outputs.length - 1];
191
195
  };
192
196
 
193
- // --- execute based on step.type ---
194
197
  switch (stepType) {
195
198
  case 'calculate': {
196
199
  const result = this.evaluateMath(step.expression || step.actionRaw);
197
200
  if (step.saveAs) this.context[step.saveAs] = result;
198
201
  break;
199
202
  }
203
+
200
204
  case 'action': {
201
205
  const action = step.actionRaw.replace(/\{([^\}]+)\}/g, (_, path) => {
202
206
  const value = this.getNested(this.context, path.trim());
@@ -206,11 +210,10 @@ class RuntimeAPI {
206
210
  const mathCall = action.match(/^(add|subtract|multiply|divide|sum|avg|min|max|round|floor|ceil|abs)\((.*)\)$/i);
207
211
  if (mathCall) {
208
212
  const fn = mathCall[1].toLowerCase();
209
- const argsRaw = mathCall[2];
210
- const args = argsRaw.split(',').map(s => s.trim()).map(s => {
211
- if (/^".*"$/.test(s) || /^'.*'$/.test(s)) return s.slice(1, -1);
213
+ const args = mathCall[2].split(',').map(s => {
214
+ s = s.trim();
212
215
  if (!isNaN(s)) return parseFloat(s);
213
- return this.getNested(this.context, s.replace(/^\{|\}$/g, '').trim());
216
+ return this.getNested(this.context, s.replace(/^\{|\}$/g, ''));
214
217
  });
215
218
  if (this.mathFunctions[fn]) {
216
219
  const value = this.mathFunctions[fn](...args);
@@ -223,79 +226,45 @@ class RuntimeAPI {
223
226
  if (step.saveAs) this.context[step.saveAs] = res;
224
227
  break;
225
228
  }
229
+
226
230
  case 'use': {
227
231
  const res = await runResolvers(`Use ${step.tool}`);
228
232
  if (step.saveAs) this.context[step.saveAs] = res;
229
233
  break;
230
234
  }
235
+
231
236
  case 'ask': {
232
237
  const res = await runResolvers(`Ask ${step.target}`);
233
238
  if (step.saveAs) this.context[step.saveAs] = res;
234
239
  break;
235
240
  }
241
+
236
242
  case 'if': {
237
243
  if (this.evaluateCondition(step.condition, this.context)) {
238
244
  for (const s of step.body) await this.executeStep(s, agentResolver);
239
245
  }
240
246
  break;
241
247
  }
248
+
242
249
  case 'parallel': {
243
250
  await Promise.all(step.steps.map(s => this.executeStep(s, agentResolver)));
244
251
  break;
245
252
  }
253
+
246
254
  case 'connect': {
247
255
  this.resources[step.resource] = step.endpoint;
248
256
  break;
249
257
  }
258
+
250
259
  case 'agent_use': {
251
260
  this.agentMap[step.logicalName] = step.resource;
252
261
  break;
253
262
  }
263
+
254
264
  case 'debrief': {
255
265
  this.emit('debrief', { agent: step.agent, message: step.message });
256
266
  break;
257
267
  }
258
- case 'evolve': {
259
- const maxGen = step.constraints?.max_generations || 1;
260
- if (maxGen < 1) return;
261
- const summaryStep = this.findLastSummaryStep();
262
- if (!summaryStep) {
263
- this.addWarning('Evolve step has no prior "Ask ... Save as" step to evolve');
264
- return;
265
- }
266
- const varName = summaryStep.saveAs;
267
- let currentOutput = this.context[varName] || '';
268
- for (let attempt = 0; attempt < maxGen; attempt++) {
269
- let revisedAction = summaryStep.actionRaw.replace(/\{([^\}]+)\}/g, (_, path) => {
270
- const val = this.getNested(this.context, path.trim());
271
- return val !== undefined ? String(val) : `{${path}}`;
272
- });
273
- if (step.feedback) revisedAction += `\n[IMPROVEMENT FEEDBACK: ${step.feedback}]`;
274
- currentOutput = await runResolvers(revisedAction);
275
- this.context[varName] = currentOutput;
276
- this.emit('debrief', {
277
- agent: step.agent || 'Evolver',
278
- message: `Evolve attempt ${attempt + 1}/${maxGen}: ${currentOutput.substring(0, 80)}...`
279
- });
280
- }
281
- this.context['improved_summary'] = currentOutput;
282
- break;
283
- }
284
- case 'prompt': {
285
- const input = await this.getUserInput(step.question);
286
- if (step.saveAs) this.context[step.saveAs] = input;
287
- break;
288
- }
289
- case 'persist': {
290
- const val = this.context[step.variable];
291
- if (val !== undefined) fs.appendFileSync(step.target, JSON.stringify(val) + '\n', 'utf8');
292
- break;
293
- }
294
- case 'emit': {
295
- const payload = step.payload ? this.getNested(this.context, step.payload) : undefined;
296
- this.emit(step.event, payload || step.payload);
297
- break;
298
- }
299
268
  }
300
269
 
301
270
  if (this.verbose) {
@@ -304,23 +273,23 @@ class RuntimeAPI {
304
273
  }
305
274
  }
306
275
 
307
- async getUserInput(question) {
308
- return new Promise(resolve => {
309
- process.stdout.write(`${question}: `);
310
- process.stdin.resume();
311
- process.stdin.once('data', data => {
312
- process.stdin.pause();
313
- resolve(data.toString().trim());
314
- });
315
- });
316
- }
317
-
318
276
  async executeWorkflow(workflow, inputs, agentResolver) {
319
277
  this.context = { ...inputs };
320
278
  this.workflowSteps = workflow.steps;
321
279
  this.allowedResolvers = new Set(workflow.allowedResolvers || []);
322
280
 
323
- for (const step of workflow.steps) await this.executeStep(step, agentResolver);
281
+ const mathPattern =
282
+ /^(Add|Subtract|Multiply|Divide|Sum|Avg|Min|Max|Round|Floor|Ceil|Abs)\b/i;
283
+
284
+ for (const step of workflow.steps) {
285
+ if (step.type === 'calculate' || (step.actionRaw && mathPattern.test(step.actionRaw))) {
286
+ this.allowedResolvers.add('builtInMathResolver');
287
+ }
288
+ }
289
+
290
+ for (const step of workflow.steps) {
291
+ await this.executeStep(step, agentResolver);
292
+ }
324
293
 
325
294
  this.printDisallowedSummary();
326
295