@o-lang/olang 1.1.2 → 1.1.4

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/runtime.js +107 -24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o-lang/olang",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
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
@@ -343,7 +343,7 @@ class RuntimeAPI {
343
343
  }
344
344
  };
345
345
 
346
- // ✅ STRICT SAFETY: Updated runResolvers with failure halting
346
+ // ✅ CORRECTED: Strict safety WITH dynamic diagnostics
347
347
  const runResolvers = async (action) => {
348
348
  const mathPattern =
349
349
  /^(Add|Subtract|Multiply|Divide|Sum|Avg|Min|Max|Round|Floor|Ceil|Abs)\b/i;
@@ -360,19 +360,19 @@ class RuntimeAPI {
360
360
  let resolversToRun = [];
361
361
 
362
362
  if (agentResolver && Array.isArray(agentResolver._chain)) {
363
- // Resolver chain mode
364
363
  resolversToRun = agentResolver._chain;
365
364
  } else if (Array.isArray(agentResolver)) {
366
- // Array of resolvers mode (what npx olang passes with -r flags)
367
365
  resolversToRun = agentResolver;
368
366
  } else if (agentResolver) {
369
- // Single resolver mode
370
367
  resolversToRun = [agentResolver];
371
368
  }
372
369
 
373
- // ✅ STRICT SAFETY: Fail fast on resolver errors or empty results
370
+ // ✅ Track detailed resolver outcomes for diagnostics
371
+ const resolverAttempts = [];
372
+
374
373
  for (let idx = 0; idx < resolversToRun.length; idx++) {
375
374
  const resolver = resolversToRun[idx];
375
+ const resolverName = resolver?.resolverName || resolver?.name || `resolver-${idx}`;
376
376
  enforceResolverPolicy(resolver, step);
377
377
 
378
378
  try {
@@ -388,32 +388,115 @@ class RuntimeAPI {
388
388
  result = await resolver(action, this.context);
389
389
  }
390
390
 
391
- // ✅ SAFETY GUARD 1: Reject undefined/null results
392
- if (result === undefined || result === null) {
393
- throw new Error(
394
- `[O-Lang SAFETY] Resolver "${resolver.resolverName || resolver.name || 'anonymous'}" returned empty result for action: "${action}". ` +
395
- `Workflow halted to prevent unsafe data propagation.`
396
- );
391
+ // ✅ ACCEPT valid result immediately (non-null/non-undefined)
392
+ if (result !== undefined && result !== null) {
393
+ this.context[`__resolver_${idx}`] = result;
394
+ return result;
397
395
  }
398
396
 
399
- this.context[`__resolver_${idx}`] = result;
400
- return result;
397
+ // ⚪ Resolver skipped this action (normal behavior)
398
+ resolverAttempts.push({
399
+ name: resolverName,
400
+ status: 'skipped',
401
+ reason: 'Action not recognized'
402
+ });
401
403
 
402
404
  } catch (e) {
403
- // SAFETY GUARD 2: HALT workflow immediately on resolver failure
404
- throw new Error(
405
- `[O-Lang SAFETY] Resolver "${resolver?.resolverName || resolver?.name || idx}" failed for action "${action}": ${e.message}\n` +
406
- `Workflow execution halted.`
407
- );
405
+ // Resolver attempted but failed capture structured diagnostics
406
+ const diagnostics = {
407
+ error: e.message || String(e),
408
+ requiredEnvVars: e.requiredEnvVars || [], // Resolver can attach this
409
+ missingInputs: e.missingInputs || [], // Resolver can attach this
410
+ documentationUrl: resolver?.documentationUrl ||
411
+ (resolver?.manifest?.documentationUrl) || null
412
+ };
413
+
414
+ resolverAttempts.push({
415
+ name: resolverName,
416
+ status: 'failed',
417
+ diagnostics
418
+ });
419
+
420
+ // Log for verbose mode but continue chaining
421
+ this.addWarning(`Resolver "${resolverName}" failed for action "${action}": ${diagnostics.error}`);
422
+ }
423
+ }
424
+
425
+ // ✅ BUILD DYNAMIC, ACTIONABLE ERROR MESSAGE
426
+ let errorMessage = `[O-Lang SAFETY] No resolver handled action: "${action}"\n\n`;
427
+ errorMessage += `Attempted resolvers:\n`;
428
+
429
+ resolverAttempts.forEach((attempt, i) => {
430
+ const namePad = attempt.name.padEnd(30);
431
+ if (attempt.status === 'skipped') {
432
+ errorMessage += ` ${i + 1}. ${namePad} → SKIPPED (not applicable to this action)\n`;
433
+ } else {
434
+ errorMessage += ` ${i + 1}. ${namePad} → FAILED\n`;
435
+ errorMessage += ` Error: ${attempt.diagnostics.error}\n`;
436
+
437
+ // ✅ DYNAMIC HINT: Resolver-provided env vars
438
+ if (attempt.diagnostics.requiredEnvVars?.length) {
439
+ errorMessage += ` Required env vars: ${attempt.diagnostics.requiredEnvVars.join(', ')}\n`;
440
+ }
441
+
442
+ // ✅ DYNAMIC HINT: Resolver-provided docs link
443
+ if (attempt.diagnostics.documentationUrl) {
444
+ errorMessage += ` Docs: ${attempt.diagnostics.documentationUrl}\n`;
445
+ }
446
+ }
447
+ });
448
+
449
+ // ✅ DYNAMIC REMEDIATION (no hardcoded resolver names)
450
+ const failed = resolverAttempts.filter(a => a.status === 'failed');
451
+ const allSkipped = failed.length === 0;
452
+
453
+ errorMessage += `\n💡 How to fix:\n`;
454
+
455
+ if (allSkipped) {
456
+ // Likely action syntax mismatch
457
+ errorMessage += ` • Action syntax may not match resolver expectations\n`;
458
+ errorMessage += ` → Try removing "Action" keyword: bank-account-lookup "{id}"\n`;
459
+ errorMessage += ` → NOT: Action bank-account-lookup "{id}"\n`;
460
+ errorMessage += ` • Verify correct resolver is installed for this action type\n`;
461
+ } else {
462
+ // At least one resolver attempted but failed
463
+ errorMessage += ` • Check resolver requirements:\n`;
464
+ errorMessage += ` → Run workflow with --verbose flag for detailed logs\n`;
465
+ errorMessage += ` → Ensure required environment variables are set\n`;
466
+
467
+ // Pattern-based hints (generic, not hardcoded)
468
+ const envVarPattern = /environment variable|env\.|process\.env|missing.*path/i;
469
+ if (failed.some(f => envVarPattern.test(f.diagnostics.error))) {
470
+ errorMessage += ` → Example (PowerShell): $env:VARIABLE="value"\n`;
471
+ errorMessage += ` → Example (Linux/macOS): export VARIABLE="value"\n`;
408
472
  }
473
+
474
+ const dbPattern = /database|db\.|sqlite|postgres|mysql|mongodb/i;
475
+ if (failed.some(f => dbPattern.test(f.diagnostics.error))) {
476
+ errorMessage += ` → Ensure database file/connection exists and path is correct\n`;
477
+ }
478
+
479
+ const authPattern = /auth|api key|token|credential/i;
480
+ if (failed.some(f => authPattern.test(f.diagnostics.error))) {
481
+ errorMessage += ` → Verify API keys/tokens are set in environment variables\n`;
482
+ }
483
+ }
484
+
485
+ // ✅ Always show generic troubleshooting path
486
+ errorMessage += `\n • Resolver documentation:\n`;
487
+ let hasDocs = false;
488
+ resolverAttempts.forEach(attempt => {
489
+ if (attempt.diagnostics?.documentationUrl) {
490
+ errorMessage += ` → ${attempt.name}: ${attempt.diagnostics.documentationUrl}\n`;
491
+ hasDocs = true;
492
+ }
493
+ });
494
+ if (!hasDocs) {
495
+ errorMessage += ` → Search "@o-lang/<resolver-name>" on npmjs.com\n`;
409
496
  }
410
497
 
411
- // SAFETY GUARD 3: No resolver handled the action HALT
412
- throw new Error(
413
- `[O-Lang SAFETY] No resolver handled action: "${action}". ` +
414
- `Available resolvers: ${resolversToRun.map(r => r.resolverName || r.name || 'anonymous').join(', ')}. ` +
415
- `Workflow execution halted.`
416
- );
498
+ errorMessage += `\n🛑 Workflow halted to prevent unsafe data propagation to LLMs.`;
499
+ throw new Error(errorMessage);
417
500
  };
418
501
 
419
502
  switch (stepType) {