wolverine-ai 4.6.1 → 4.7.0

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/bin/wolverine.js CHANGED
@@ -4,6 +4,16 @@ const path = require("path");
4
4
  const dotenv = require("dotenv");
5
5
  const chalk = require("chalk");
6
6
 
7
+ // Global error handlers — prevent parent process death from unhandled errors
8
+ process.on("uncaughtException", (err) => {
9
+ console.error(chalk.red(`\n ⚠️ Uncaught exception (wolverine survived): ${err.message}`));
10
+ console.error(chalk.gray(` ${err.stack?.split("\n")[1]?.trim() || ""}`));
11
+ });
12
+ process.on("unhandledRejection", (reason) => {
13
+ const msg = reason instanceof Error ? reason.message : String(reason);
14
+ console.error(chalk.red(`\n ⚠️ Unhandled rejection (wolverine survived): ${msg}`));
15
+ });
16
+
7
17
  // Load secrets
8
18
  dotenv.config({ path: path.resolve(process.cwd(), ".env.local") });
9
19
  dotenv.config({ path: path.resolve(process.cwd(), ".env") });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolverine-ai",
3
- "version": "4.6.1",
3
+ "version": "4.7.0",
4
4
  "description": "Self-healing Node.js server framework powered by AI. Catches crashes, diagnoses errors, generates fixes, verifies, and restarts — automatically.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -58,14 +58,14 @@
58
58
  "diff": "^7.0.0",
59
59
  "dotenv": "^16.4.7",
60
60
  "fastify": "^5.8.4",
61
- "ioredis": "^5.0.0",
62
- "openai": "^4.73.0",
63
- "pg": "^8.0.0"
61
+ "openai": "^4.73.0"
64
62
  },
65
63
  "optionalDependencies": {
66
64
  "@privy-io/server-auth": "1.14.0",
67
65
  "better-sqlite3": "^11.0.0",
68
66
  "ethers": "^6.0.0",
67
+ "ioredis": "^5.0.0",
68
+ "pg": "^8.0.0",
69
69
  "stripe": "^18.0.0"
70
70
  },
71
71
  "engines": {
@@ -544,6 +544,16 @@ function _detectSandboxEscape(cmd, cwd) {
544
544
  return "curl uploading local file (potential data exfiltration)";
545
545
  }
546
546
 
547
+ // 6. Environment variable dumping to file/network (staging attack via /tmp)
548
+ if (/\b(env|printenv|set)\s*>/.test(c) || /\b(env|printenv)\s*\|/.test(c)) {
549
+ return "Environment variable dump detected (exfiltration risk)";
550
+ }
551
+
552
+ // 7. Reading secrets and piping to network
553
+ if (/cat\s+.*\.(env|key|pem|crt)\s*\|/.test(c) || /cat\s+.*secret.*\|/.test(c)) {
554
+ return "Reading sensitive file and piping to output";
555
+ }
556
+
547
557
  return null; // safe
548
558
  }
549
559
 
@@ -1075,6 +1085,10 @@ class AgentEngine {
1075
1085
  /^192\.168\./,
1076
1086
  /^fd[0-9a-f]{2}:/i,
1077
1087
  /^::1$/,
1088
+ /^0\.0\.0\.0$/,
1089
+ /^0\./,
1090
+ /^\[::1\]$/,
1091
+ /^metadata\.google\.internal$/i,
1078
1092
  ];
1079
1093
  if (privatePatterns.some(p => p.test(hostname))) {
1080
1094
  resolve({ content: `BLOCKED: Cannot fetch private/internal address "${hostname}"` });
@@ -710,14 +710,16 @@ class WolverineRunner {
710
710
  }
711
711
  this._healInProgress = true;
712
712
 
713
- // #8: Safety timeout — if heal hangs, force-release the lock after 6 minutes
713
+ // Safety timeout — must be strictly greater than heal()'s 5-min timeout to avoid concurrent heals
714
+ const HEAL_TIMEOUT_MS = parseInt(process.env.WOLVERINE_HEAL_TIMEOUT_MS, 10) || 300000;
715
+ const safetyMs = HEAL_TIMEOUT_MS + 30000; // heal timeout + 30s grace
714
716
  const healTimeout = setTimeout(() => {
715
717
  if (this._healInProgress) {
716
- console.log(chalk.red(` ⚠️ _healFromError safety timeout (6min) — releasing heal lock`));
718
+ console.log(chalk.red(` ⚠️ _healFromError safety timeout (${Math.round(safetyMs / 60000)}min) — releasing heal lock`));
717
719
  this._healInProgress = false;
718
720
  this._healStatus = null;
719
721
  }
720
- }, 360000);
722
+ }, safetyMs);
721
723
 
722
724
  console.log(chalk.yellow(`\n🐺 Wolverine healing caught error on ${routePath}...`));
723
725
  this._healStatus = { active: true, route: routePath, error: errorDetails?.message?.slice(0, 200), phase: "diagnosing", startedAt: Date.now() };
@@ -249,27 +249,35 @@ async function _healImpl({ stderr, cwd, sandbox, notifier, rateLimiter, backupMa
249
249
  } catch {}
250
250
  }
251
251
 
252
- // 7. Classify error complexity — CLASSIFIER_MODEL determines strategy
252
+ // 7. Classify error complexity — regex first (fast, free), AI only when uncertain
253
253
  let errorComplexity = "moderate";
254
- try {
255
- const classifyResult = await aiCall({
256
- model: getModel("classifier"),
257
- systemPrompt: "You classify Node.js errors. Respond with ONLY one word: SIMPLE, MODERATE, or COMPLEX.",
258
- userPrompt: `Classify this error:\n${parsed.errorMessage}\n\nFile: ${parsed.filePath || "unknown"}\nType: ${parsed.errorType || "unknown"}`,
259
- maxTokens: 10,
260
- category: "classifier",
261
- });
262
- const word = (classifyResult.content || "").trim().toUpperCase();
263
- if (word.includes("SIMPLE")) errorComplexity = "simple";
264
- else if (word.includes("COMPLEX")) errorComplexity = "complex";
265
- else errorComplexity = "moderate";
266
- console.log(chalk.gray(` 🏷️ Classifier: ${errorComplexity}`));
267
- } catch {
268
- // Fallback to regex classification
269
- if (/TypeError|ReferenceError|SyntaxError|Cannot find module/.test(parsed.errorMessage)) errorComplexity = "simple";
270
- else if (/ECONNREFUSED|timeout|ENOENT|EACCES/.test(parsed.errorMessage)) errorComplexity = "moderate";
271
- else errorComplexity = "complex";
272
- console.log(chalk.gray(` 🏷️ Classifier (fallback): ${errorComplexity}`));
254
+ const isObviouslySimple = /TypeError|ReferenceError|SyntaxError|Cannot find module|Cannot read prop/.test(parsed.errorMessage);
255
+ const isObviouslyModerate = /ECONNREFUSED|timeout|ENOENT|EACCES|EADDRINUSE|ENOSPC|EMFILE/.test(parsed.errorMessage);
256
+ if (isObviouslySimple) {
257
+ errorComplexity = "simple";
258
+ console.log(chalk.gray(` 🏷️ Classifier (fast): simple`));
259
+ } else if (isObviouslyModerate) {
260
+ errorComplexity = "moderate";
261
+ console.log(chalk.gray(` 🏷️ Classifier (fast): moderate`));
262
+ } else {
263
+ // Uncertain use AI classifier
264
+ try {
265
+ const classifyResult = await aiCall({
266
+ model: getModel("classifier"),
267
+ systemPrompt: "You classify Node.js errors. Respond with ONLY one word: SIMPLE, MODERATE, or COMPLEX.",
268
+ userPrompt: `Classify this error:\n${parsed.errorMessage}\n\nFile: ${parsed.filePath || "unknown"}\nType: ${parsed.errorType || "unknown"}`,
269
+ maxTokens: 10,
270
+ category: "classifier",
271
+ });
272
+ const word = (classifyResult.content || "").trim().toUpperCase();
273
+ if (word.includes("SIMPLE")) errorComplexity = "simple";
274
+ else if (word.includes("COMPLEX")) errorComplexity = "complex";
275
+ else errorComplexity = "moderate";
276
+ console.log(chalk.gray(` 🏷️ Classifier (AI): ${errorComplexity}`));
277
+ } catch {
278
+ errorComplexity = "complex"; // unknown = treat as complex to be safe
279
+ console.log(chalk.gray(` 🏷️ Classifier (fallback): complex`));
280
+ }
273
281
  }
274
282
 
275
283
  // 7b. Research — look up past fixes AND search for solutions
@@ -687,13 +695,15 @@ async function tryOperationalFix(parsed, cwd, logger, sandbox) {
687
695
  } catch {}
688
696
  }
689
697
 
690
- // Pattern 6: EMFILE — too many open files, try raising ulimit
698
+ // Pattern 6: EMFILE — too many open files
691
699
  if (/EMFILE|ENFILE/.test(msg)) {
700
+ // Close stale file handles by clearing node_modules/.cache and restarting
692
701
  try {
693
- if (process.platform !== "win32") {
694
- execSync("ulimit -n 65536 2>/dev/null || true", { cwd, stdio: "pipe", timeout: 3000 });
695
- console.log(chalk.blue(" 📂 Raised file descriptor limit to 65536"));
696
- return { fixed: true, action: "EMFILE raised file descriptor limit (ulimit -n 65536)" };
702
+ const cachePath = path.join(cwd, "node_modules", ".cache");
703
+ if (fs.existsSync(cachePath)) {
704
+ execSync(`rm -rf "${cachePath}"`, { timeout: 10000 });
705
+ console.log(chalk.blue(" 📂 Cleared node_modules/.cache to reduce open FDs"));
706
+ return { fixed: true, action: "EMFILE — cleared build cache to reduce open file descriptors. Consider increasing ulimit -n in your system profile." };
697
707
  }
698
708
  } catch {}
699
709
  }