wolverine-ai 4.0.5 → 4.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolverine-ai",
3
- "version": "4.0.5",
3
+ "version": "4.1.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": {
@@ -459,6 +459,13 @@ class WolverineRunner {
459
459
  return;
460
460
  }
461
461
 
462
+ // #28: SIGKILL = likely OOM — synthesize useful stderr for the heal pipeline
463
+ if (signal === "SIGKILL" && (!this._stderrBuffer.trim() || this._stderrBuffer.trim().length < 10)) {
464
+ this._stderrBuffer = `Process killed by SIGKILL (possible OOM). Memory limit may have been exceeded. Check memory usage patterns and reduce memory consumption.\nExit code: ${code}, Signal: ${signal}`;
465
+ console.log(chalk.red(`\n💀 Process killed by SIGKILL (possible OOM)`));
466
+ this.logger.error(EVENT_TYPES.PROCESS_CRASH, "SIGKILL — possible OOM", { exitCode: code, signal });
467
+ }
468
+
462
469
  // Killed by signal with no stderr — just restart, don't waste tokens healing
463
470
  if (!this._stderrBuffer.trim() || this._stderrBuffer.trim().length < 10) {
464
471
  console.log(chalk.yellow(`\n⚠️ Process killed (code: ${code}, signal: ${signal}) — no error to heal, restarting`));
@@ -483,13 +490,28 @@ class WolverineRunner {
483
490
  }
484
491
 
485
492
  this.retryCount++;
486
- await this._healAndRestart();
493
+ // #3: Guard against unhandled rejections — don't let heal errors crash the parent
494
+ try {
495
+ await this._healAndRestart();
496
+ } catch (healErr) {
497
+ console.log(chalk.red(` ⚠️ Heal error (recovering): ${healErr.message}`));
498
+ this._healInProgress = false;
499
+ this._healStatus = null;
500
+ if (this.running) this._spawn(); // restart without healing
501
+ }
487
502
  });
488
503
 
489
504
  this.child.on("error", (err) => {
490
505
  console.log(chalk.red(`Failed to start process: ${err.message}`));
491
506
  this.logger.error(EVENT_TYPES.PROCESS_CRASH, `Failed to start: ${err.message}`);
492
- this.running = false;
507
+ // #10: Retry spawn after delay instead of permanently dying
508
+ if (this.running && this.retryCount < this.maxRetries) {
509
+ this.retryCount++;
510
+ console.log(chalk.yellow(` Retrying spawn in 5s (attempt ${this.retryCount}/${this.maxRetries})...`));
511
+ setTimeout(() => { if (this.running) this._spawn(); }, 5000);
512
+ } else {
513
+ this.running = false;
514
+ }
493
515
  });
494
516
 
495
517
  // IPC channel: child reports caught 500 errors (Fastify/Express)
@@ -626,9 +648,14 @@ class WolverineRunner {
626
648
  }
627
649
  }
628
650
  } catch (err) {
629
- console.log(chalk.red(`\n🐺 Wolverine encountered an error: ${err.message}`));
651
+ // #4: Don't permanently die on transient errors — restart without healing
652
+ console.log(chalk.red(`\n🐺 Wolverine heal error (recovering): ${err.message}`));
630
653
  this._healInProgress = false;
631
- this.running = false;
654
+ this._healStatus = null;
655
+ if (this.running) {
656
+ console.log(chalk.yellow(" Restarting without healing..."));
657
+ this._spawn();
658
+ }
632
659
  }
633
660
  }
634
661
 
@@ -35,6 +35,17 @@ async function heal(opts) {
35
35
  if (err.message === "timeout") {
36
36
  console.log(chalk.red(`\n🐺 Heal timed out after ${HEAL_TIMEOUT_MS / 1000}s`));
37
37
  if (opts.logger) opts.logger.error(EVENT_TYPES.HEAL_FAILED, `Heal timed out after ${HEAL_TIMEOUT_MS / 1000}s`);
38
+ // #11: Rollback on timeout — the background _healImpl may have partially applied patches
39
+ if (opts.backupManager) {
40
+ try {
41
+ const all = opts.backupManager.getAll();
42
+ const latest = all.find(b => b.status === "unstable");
43
+ if (latest) {
44
+ opts.backupManager.rollbackTo(latest.id);
45
+ console.log(chalk.yellow(` ↩️ Rolled back to ${latest.id} (timeout cleanup)`));
46
+ }
47
+ } catch {}
48
+ }
38
49
  return { healed: false, explanation: `Heal timed out after ${HEAL_TIMEOUT_MS / 1000}s` };
39
50
  }
40
51
  throw err;
@@ -312,7 +323,7 @@ async function _healImpl({ stderr, cwd, sandbox, notifier, rateLimiter, backupMa
312
323
  errorMessage: parsed.errorMessage, stackTrace: parsed.stackTrace,
313
324
  extraContext: envContext,
314
325
  });
315
- rateLimiter.record(errorSignature);
326
+ // #15: Don't record rate limit until AFTER verification — failed attempts shouldn't exhaust the limit
316
327
 
317
328
  // Execute shell commands first (npm install, mkdir, etc.)
318
329
  if (repair.commands && Array.isArray(repair.commands)) {
@@ -351,6 +362,7 @@ async function _healImpl({ stderr, cwd, sandbox, notifier, rateLimiter, backupMa
351
362
  const verification = await verifyFix(parsed.filePath, cwd, errorSignature, routeContext);
352
363
  if (verification.verified) {
353
364
  backupManager.markVerified(bid);
365
+ rateLimiter.record(errorSignature);
354
366
  rateLimiter.clearSignature(errorSignature);
355
367
  // Track tool operations: file read + patch + verify + any commands
356
368
  // These are the same operations an agent would do with read_file/write_file/bash_exec
@@ -360,10 +372,11 @@ async function _healImpl({ stderr, cwd, sandbox, notifier, rateLimiter, backupMa
360
372
  return { healed: true, explanation: repair.explanation, backupId: bid, mode: "fast" };
361
373
  }
362
374
 
363
- backupManager.rollbackTo(bid);
375
+ // #13: Safe rollback — wrap in try/catch to prevent rollback-of-rollback loop
376
+ try { backupManager.rollbackTo(bid); } catch (rbErr) { console.log(chalk.red(` ⚠️ Rollback failed: ${rbErr.message}`)); }
364
377
  return { healed: false, explanation: `Fast path: ${verification.status}` };
365
378
  } catch (err) {
366
- backupManager.rollbackTo(bid);
379
+ try { backupManager.rollbackTo(bid); } catch (rbErr) { console.log(chalk.red(` ⚠️ Rollback failed: ${rbErr.message}`)); }
367
380
  return { healed: false, explanation: `Fast path error: ${err.message}` };
368
381
  }
369
382
  } else if (iteration <= 2) {
@@ -6,14 +6,14 @@
6
6
  },
7
7
 
8
8
  "models": {
9
- "reasoning": "gpt-4o",
10
- "coding": "gpt-4o",
11
- "chat": "gpt-4o-mini",
9
+ "reasoning": "claude-sonnet-4-6",
10
+ "coding": "claude-sonnet-4-6",
11
+ "chat": "gpt-5.4-mini",
12
12
  "tool": "gpt-4o-mini",
13
13
  "classifier": "gpt-4o-mini",
14
- "audit": "gpt-4o-mini",
15
- "compacting": "gpt-4o-mini",
16
- "research": "gpt-4o"
14
+ "audit": "wolverine-test-1",
15
+ "compacting": "wolverine-test-1",
16
+ "research": "claude-sonnet-4-6"
17
17
  },
18
18
 
19
19
  "embedding": "wolverine-embedding-1",