@o-lang/olang 1.0.1 → 1.0.3

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
@@ -5,6 +5,21 @@ const { execute } = require('./src/runtime');
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
7
 
8
+ /**
9
+ * Enforce .ol extension ONLY
10
+ */
11
+ function ensureOlExtension(filename) {
12
+ if (!filename.endsWith('.ol')) {
13
+ throw new Error(
14
+ `Invalid file: "${filename}".\n` +
15
+ `O-Lang workflows must use the ".ol" extension.`
16
+ );
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Default mock resolver (for demo use)
22
+ */
8
23
  async function defaultMockResolver(action, context) {
9
24
  if (action.startsWith('Search for ')) {
10
25
  return {
@@ -13,20 +28,27 @@ async function defaultMockResolver(action, context) {
13
28
  url: "mock://hr-policy"
14
29
  };
15
30
  }
31
+
16
32
  if (action.startsWith('Ask ')) {
17
- return "✅ [Mock] Summarized for staff.";
33
+ return "✅ [Mock] Summarized for demonstration.";
18
34
  }
35
+
19
36
  if (action.startsWith('Notify ')) {
20
37
  const recipient = action.match(/Notify (\S+)/)?.[1] || 'user@example.com';
21
38
  return `📬 Notification sent to ${recipient}`;
22
39
  }
40
+
23
41
  if (action.startsWith('Debrief ') || action.startsWith('Evolve ')) {
24
42
  console.log(`[O-Lang] ${action}`);
25
43
  return 'Acknowledged';
26
44
  }
45
+
27
46
  return `[Unhandled: ${action}]`;
28
47
  }
29
48
 
49
+ /**
50
+ * Resolver chaining mechanism
51
+ */
30
52
  function createResolverChain(resolvers) {
31
53
  return async (action, context) => {
32
54
  for (const resolver of resolvers) {
@@ -61,7 +83,9 @@ function loadSingleResolver(specifier) {
61
83
  console.log(`📁 Loaded resolver: ${absolutePath}`);
62
84
  return resolver;
63
85
  } catch (e2) {
64
- throw new Error(`Failed to load resolver '${specifier}':\n npm: ${e1.message}\n file: ${e2.message}`);
86
+ throw new Error(
87
+ `Failed to load resolver '${specifier}':\n npm: ${e1.message}\n file: ${e2.message}`
88
+ );
65
89
  }
66
90
  }
67
91
  }
@@ -76,27 +100,41 @@ function loadResolverChain(specifiers) {
76
100
  return createResolverChain(resolvers);
77
101
  }
78
102
 
103
+ /**
104
+ * CLI Setup
105
+ */
79
106
  const program = new Command();
80
107
 
81
108
  program
82
109
  .name('olang')
83
- .description('O-Lang CLI: run .olang workflows')
110
+ .description('O-Lang CLI: run .ol workflows with rule-enforced agent governance')
84
111
  .command('run <file>')
85
- .option('-r, --resolver <specifier>', 'Resolver (npm package or local path). Can be used multiple times.\nExample:\n -r @o-lang/llm-groq\n -r @o-lang/notify-telegram', (val, acc) => {
86
- acc.push(val);
87
- return acc;
88
- }, [])
89
- .option('-i, --input <k=v>', 'Input parameters', (val, acc = {}) => {
90
- const [k, v] = val.split('=');
91
- acc[k] = v;
92
- return acc;
93
- }, {})
112
+ .option(
113
+ '-r, --resolver <specifier>',
114
+ 'Resolver (npm package or local path). Can be used multiple times.\nExample:\n -r @o-lang/llm-groq\n -r @o-lang/notify-telegram',
115
+ (val, acc) => { acc.push(val); return acc; },
116
+ []
117
+ )
118
+ .option(
119
+ '-i, --input <k=v>',
120
+ 'Input parameters',
121
+ (val, acc = {}) => {
122
+ const [k, v] = val.split('=');
123
+ acc[k] = v;
124
+ return acc;
125
+ },
126
+ {}
127
+ )
94
128
  .action(async (file, options) => {
95
129
  try {
130
+ ensureOlExtension(file);
131
+
96
132
  const content = fs.readFileSync(file, 'utf8');
97
133
  const workflow = parse(content);
134
+
98
135
  const resolver = loadResolverChain(options.resolver);
99
136
  const result = await execute(workflow, options.input, resolver);
137
+
100
138
  console.log('\n=== Workflow Result ===');
101
139
  console.log(JSON.stringify(result, null, 2));
102
140
  } catch (err) {
@@ -106,10 +144,3 @@ program
106
144
  });
107
145
 
108
146
  program.parse(process.argv);
109
-
110
-
111
-
112
-
113
-
114
-
115
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o-lang/olang",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
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",
@@ -18,11 +18,22 @@
18
18
  "commander": "^12.0.0",
19
19
  "dotenv": "^17.2.3"
20
20
  },
21
- "keywords": ["agent", "governance", "workflow", "llm", "automation", "olang", "ai"],
21
+ "keywords": [
22
+ "agent",
23
+ "governance",
24
+ "workflow",
25
+ "llm",
26
+ "automation",
27
+ "olang",
28
+ "ai"
29
+ ],
22
30
  "license": "MIT",
23
31
  "repository": {
24
32
  "type": "git",
25
- "url": "https://github.com/O-Lang-Central/olang-kernel.git"
33
+ "url": "git+https://github.com/O-Lang-Central/olang-kernel.git"
26
34
  },
27
- "homepage": "https://github.com/O-Lang-Central/olang-kernel"
28
- }
35
+ "homepage": "https://github.com/O-Lang-Central/olang-kernel",
36
+ "publishConfig": {
37
+ "access": "public"
38
+ }
39
+ }
package/src/parser.js CHANGED
@@ -1,5 +1,11 @@
1
1
  // src/parser.js
2
- function parse(code) {
2
+
3
+ function parse(code, fileName = null) {
4
+ // --- New: Enforce .ol extension if filename provided ---
5
+ if (fileName && !fileName.endsWith(".ol")) {
6
+ throw new Error(`Expected .ol workflow, got: ${fileName}`);
7
+ }
8
+
3
9
  const lines = code
4
10
  .split(/\r?\n/)
5
11
  .map(l => l.trim())
@@ -33,7 +39,7 @@ function parse(code) {
33
39
  stepNumber: parseInt(stepMatch[1], 10),
34
40
  actionRaw: stepMatch[2].trim(),
35
41
  saveAs: null,
36
- constraints: {} // Initialize constraints
42
+ constraints: {}
37
43
  });
38
44
  i++;
39
45
  continue;
@@ -47,7 +53,7 @@ function parse(code) {
47
53
  continue;
48
54
  }
49
55
 
50
- // Constraint (NEW: parse key = value lines and attach to last step)
56
+ // Constraint
51
57
  const constraintMatch = line.match(/^Constraint:\s*(.+)$/i);
52
58
  if (constraintMatch && workflow.steps.length > 0) {
53
59
  const lastStep = workflow.steps[workflow.steps.length - 1];
@@ -59,16 +65,11 @@ function parse(code) {
59
65
  let key = eq[1].trim();
60
66
  let value = eq[2].trim();
61
67
 
62
- // Parse list: [a, b, c]
63
68
  if (value.startsWith('[') && value.endsWith(']')) {
64
69
  value = value.slice(1, -1).split(',').map(v => v.trim().replace(/^"/, '').replace(/"$/, ''));
65
- }
66
- // Parse number
67
- else if (!isNaN(value)) {
70
+ } else if (!isNaN(value)) {
68
71
  value = Number(value);
69
- }
70
- // Keep string (remove quotes if present)
71
- else if (value.startsWith('"') && value.endsWith('"')) {
72
+ } else if (value.startsWith('"') && value.endsWith('"')) {
72
73
  value = value.slice(1, -1);
73
74
  }
74
75
 
@@ -88,7 +89,7 @@ function parse(code) {
88
89
  body.push(lines[i]);
89
90
  i++;
90
91
  }
91
- if (i < lines.length) i++; // skip 'End If'
92
+ if (i < lines.length) i++;
92
93
  workflow.steps.push({ type: 'if', condition, body: parseBlock(body) });
93
94
  continue;
94
95
  }
@@ -119,7 +120,7 @@ function parse(code) {
119
120
  continue;
120
121
  }
121
122
 
122
- // Agent ... uses ...
123
+ // Agent uses
123
124
  const agentUseMatch = line.match(/^Agent\s+"([^"]+)"\s+uses\s+"([^"]+)"$/i);
124
125
  if (agentUseMatch) {
125
126
  workflow.steps.push({
@@ -199,6 +200,32 @@ function parse(code) {
199
200
  continue;
200
201
  }
201
202
 
203
+ // Use <Tool>
204
+ const useMatch = line.match(/^Use\s+(.+)$/i);
205
+ if (useMatch) {
206
+ workflow.steps.push({
207
+ type: 'use',
208
+ tool: useMatch[1].trim(),
209
+ saveAs: null,
210
+ constraints: {}
211
+ });
212
+ i++;
213
+ continue;
214
+ }
215
+
216
+ // Ask <Target>
217
+ const askMatch = line.match(/^Ask\s+(.+)$/i);
218
+ if (askMatch) {
219
+ workflow.steps.push({
220
+ type: 'ask',
221
+ target: askMatch[1].trim(),
222
+ saveAs: null,
223
+ constraints: {}
224
+ });
225
+ i++;
226
+ continue;
227
+ }
228
+
202
229
  i++;
203
230
  }
204
231
 
@@ -221,25 +248,38 @@ function parseBlock(lines) {
221
248
  steps.push(current);
222
249
  continue;
223
250
  }
251
+
224
252
  const saveMatch = line.match(/^Save as\s+(.+)$/i);
225
253
  if (saveMatch && current) {
226
254
  current.saveAs = saveMatch[1].trim();
227
255
  }
256
+
228
257
  const debriefMatch = line.match(/^Debrief\s+(\w+)\s+with\s+"(.+)"$/i);
229
258
  if (debriefMatch) {
230
259
  steps.push({ type: 'debrief', agent: debriefMatch[1], message: debriefMatch[2] });
231
260
  }
261
+
232
262
  const evolveMatch = line.match(/^Evolve\s+(\w+)\s+using\s+feedback:\s+"(.+)"$/i);
233
263
  if (evolveMatch) {
234
264
  steps.push({ type: 'evolve', agent: evolveMatch[1], feedback: evolveMatch[2] });
235
265
  }
266
+
236
267
  const promptMatch = line.match(/^Prompt user to\s+"(.+)"$/i);
237
268
  if (promptMatch) {
238
269
  steps.push({ type: 'prompt', question: promptMatch[1], saveAs: null });
239
270
  }
271
+
272
+ const useMatch = line.match(/^Use\s+(.+)$/i);
273
+ if (useMatch) {
274
+ steps.push({ type: 'use', tool: useMatch[1].trim(), saveAs: null, constraints: {} });
275
+ }
276
+
277
+ const askMatch = line.match(/^Ask\s+(.+)$/i);
278
+ if (askMatch) {
279
+ steps.push({ type: 'ask', target: askMatch[1].trim(), saveAs: null, constraints: {} });
280
+ }
240
281
  }
241
282
  return steps;
242
283
  }
243
284
 
244
285
  module.exports = { parse };
245
-
package/src/runtime.js CHANGED
@@ -35,7 +35,6 @@ class RuntimeAPI {
35
35
  return path.split('.').reduce((o, k) => (o && o[k] !== undefined) ? o[k] : undefined, obj);
36
36
  }
37
37
 
38
- // 🔍 Find the last "Ask ... Save as ..." step that can be evolved
39
38
  findLastSummaryStep() {
40
39
  for (let i = this.workflowSteps.length - 1; i >= 0; i--) {
41
40
  const step = this.workflowSteps[i];
@@ -58,6 +57,20 @@ class RuntimeAPI {
58
57
  break;
59
58
  }
60
59
 
60
+ case 'use': {
61
+ // "Use <Tool>" step
62
+ const res = await agentResolver(`Use ${step.tool}`, this.context);
63
+ if (step.saveAs) this.context[step.saveAs] = res;
64
+ break;
65
+ }
66
+
67
+ case 'ask': {
68
+ // "Ask <Target>" step
69
+ const res = await agentResolver(`Ask ${step.target}`, this.context);
70
+ if (step.saveAs) this.context[step.saveAs] = res;
71
+ break;
72
+ }
73
+
61
74
  case 'if': {
62
75
  if (this.evaluateCondition(step.condition, this.context)) {
63
76
  for (const s of step.body) await this.executeStep(s, agentResolver);
@@ -86,7 +99,6 @@ class RuntimeAPI {
86
99
  }
87
100
 
88
101
  case 'evolve': {
89
- // ✅ Runtime-enforced bounded evolution
90
102
  const maxGen = step.constraints?.max_generations || 1;
91
103
  if (maxGen < 1) {
92
104
  this.context['improved_summary'] = this.context['summary'] || '';
@@ -100,34 +112,28 @@ class RuntimeAPI {
100
112
  return;
101
113
  }
102
114
 
103
- const varName = summaryStep.saveAs; // e.g., "summary"
115
+ const varName = summaryStep.saveAs;
104
116
  let currentOutput = this.context[varName] || '';
105
117
 
106
- // Run up to max_generations attempts
107
118
  for (let attempt = 0; attempt < maxGen; attempt++) {
108
- // Rebuild the original action with current context (re-interpolates {doc.text}, etc.)
109
119
  let revisedAction = summaryStep.actionRaw.replace(/\{([^\}]+)\}/g, (_, path) => {
110
120
  const val = this.getNested(this.context, path.trim());
111
121
  return val !== undefined ? String(val) : `{${path}}`;
112
122
  });
113
123
 
114
- // Append improvement feedback on every attempt (including first)
115
124
  if (step.feedback) {
116
- revisedAction = revisedAction.replace(/(")$/ , `\n\n[IMPROVEMENT FEEDBACK: ${step.feedback}]$1`);
125
+ revisedAction = revisedAction.replace(/(")$/, `\n\n[IMPROVEMENT FEEDBACK: ${step.feedback}]$1`);
117
126
  }
118
127
 
119
- // Delegate ONLY the "Ask ..." action to the resolver
120
128
  currentOutput = await agentResolver(revisedAction, this.context);
121
- this.context[varName] = currentOutput; // update original variable
129
+ this.context[varName] = currentOutput;
122
130
 
123
- // Optional: emit debrief for observability
124
131
  this.emit('debrief', {
125
132
  agent: step.agent || 'Evolver',
126
133
  message: `Evolve attempt ${attempt + 1}/${maxGen}: ${currentOutput.substring(0, 80)}...`
127
134
  });
128
135
  }
129
136
 
130
- // ✅ Always expose final result as 'improved_summary' for downstream use
131
137
  this.context['improved_summary'] = currentOutput;
132
138
  break;
133
139
  }
@@ -167,7 +173,7 @@ class RuntimeAPI {
167
173
 
168
174
  async executeWorkflow(workflow, inputs, agentResolver) {
169
175
  this.context = { ...inputs };
170
- this.workflowSteps = workflow.steps; // critical for evolve lookup
176
+ this.workflowSteps = workflow.steps;
171
177
 
172
178
  for (const step of workflow.steps) {
173
179
  await this.executeStep(step, agentResolver);