@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.
- package/package.json +1 -1
- package/src/runtime.js +107 -24
package/package.json
CHANGED
package/src/runtime.js
CHANGED
|
@@ -343,7 +343,7 @@ class RuntimeAPI {
|
|
|
343
343
|
}
|
|
344
344
|
};
|
|
345
345
|
|
|
346
|
-
// ✅
|
|
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
|
-
// ✅
|
|
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
|
-
// ✅
|
|
392
|
-
if (result
|
|
393
|
-
|
|
394
|
-
|
|
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
|
|
400
|
-
|
|
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
|
-
//
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
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) {
|