wolverine-ai 2.8.1 → 2.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolverine-ai",
3
- "version": "2.8.1",
3
+ "version": "2.8.3",
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": {
@@ -34,11 +34,11 @@
34
34
  "hybrid_settings": {
35
35
  "reasoning": "claude-haiku-4-5",
36
36
  "coding": "claude-sonnet-4-6",
37
- "chat": "claude-haiku-4-5",
37
+ "chat": "gpt-5-nano",
38
38
  "tool": "claude-sonnet-4-6",
39
39
  "classifier": "gpt-4o-mini",
40
40
  "audit": "gpt-4o-mini",
41
- "compacting": "claude-haiku-4-5",
41
+ "compacting": "gpt-4o-mini",
42
42
  "research": "o4-mini-deep-research",
43
43
  "embedding": "text-embedding-3-small"
44
44
  },
@@ -405,12 +405,21 @@ class WolverineRunner {
405
405
 
406
406
  if (!this.running) return;
407
407
 
408
- if (code === 0 || signal === "SIGTERM") {
408
+ // Clean exit or graceful shutdown don't heal
409
+ if (code === 0 || signal === "SIGTERM" || signal === "SIGINT") {
409
410
  console.log(chalk.green("\n✅ Process exited cleanly."));
410
411
  this.logger.info(EVENT_TYPES.PROCESS_HEALTHY, "Process exited cleanly");
411
412
  return;
412
413
  }
413
414
 
415
+ // Killed by signal with no stderr — just restart, don't waste tokens healing
416
+ if (!this._stderrBuffer.trim() || this._stderrBuffer.trim().length < 10) {
417
+ console.log(chalk.yellow(`\n⚠️ Process killed (code: ${code}, signal: ${signal}) — no error to heal, restarting`));
418
+ this.logger.warn(EVENT_TYPES.PROCESS_CRASH, `Killed with no stderr (code: ${code}, signal: ${signal})`, { exitCode: code, signal });
419
+ this._spawn();
420
+ return;
421
+ }
422
+
414
423
  const uptime = Date.now() - this._lastStartTime;
415
424
  console.log(chalk.red(`\n💥 Process crashed with exit code ${code} (uptime: ${Math.round(uptime / 1000)}s)`));
416
425
  this.logger.error(EVENT_TYPES.PROCESS_CRASH, `Crashed with code ${code}`, {
@@ -45,6 +45,13 @@ async function _healImpl({ stderr, cwd, sandbox, notifier, rateLimiter, backupMa
45
45
  const healStartTime = Date.now();
46
46
  const { redact, hasSecrets } = require("../security/secret-redactor");
47
47
 
48
+ // Guard: don't burn tokens on empty stderr (signal kills, clean shutdowns, etc.)
49
+ if (!stderr || stderr.trim().length < 10) {
50
+ console.log(chalk.yellow("\n🐺 Empty stderr — nothing to heal (likely signal kill)"));
51
+ if (logger) logger.warn(EVENT_TYPES.HEAL_FAILED, "Empty stderr — skipping heal");
52
+ return { healed: false, explanation: "Empty stderr — nothing to diagnose" };
53
+ }
54
+
48
55
  // Redact secrets BEFORE any processing, logging, or AI calls
49
56
  const safeStderr = redact(stderr);
50
57
 
@@ -1,8 +1,20 @@
1
1
  const crypto = require("crypto");
2
2
  const path = require("path");
3
3
 
4
- const INSTANCE_ID = process.env.WOLVERINE_INSTANCE_ID ||
5
- "wlv_" + crypto.createHash("sha256").update(process.cwd() + (process.env.PORT || "3000")).digest("hex").slice(0, 12);
4
+ // Stable instance ID — based on cwd ONLY (not port, which can change during restarts).
5
+ // Persisted to .wolverine/instance-id so it survives even if cwd hash changes.
6
+ const INSTANCE_ID = (() => {
7
+ if (process.env.WOLVERINE_INSTANCE_ID) return process.env.WOLVERINE_INSTANCE_ID;
8
+ const fs = require("fs");
9
+ const idPath = require("path").join(process.cwd(), ".wolverine", "instance-id");
10
+ try {
11
+ const saved = fs.readFileSync(idPath, "utf-8").trim();
12
+ if (saved) return saved;
13
+ } catch {}
14
+ const id = "wlv_" + crypto.createHash("sha256").update(process.cwd()).digest("hex").slice(0, 12);
15
+ try { fs.mkdirSync(require("path").dirname(idPath), { recursive: true }); fs.writeFileSync(idPath, id, "utf-8"); } catch {}
16
+ return id;
17
+ })();
6
18
 
7
19
  let _v = null;
8
20
 
@@ -47,10 +59,11 @@ function collectHeartbeat(subsystems) {
47
59
  totalCost: repairs?.totalCost || 0,
48
60
  },
49
61
 
62
+ // Usage: cumulative totals from disk (not session-only) so restarts don't reset
50
63
  usage: {
51
- totalTokens: usage?.session?.totalTokens || 0,
52
- totalCost: usage?.session?.totalCostUsd || 0,
53
- totalCalls: usage?.session?.totalCalls || 0,
64
+ totalTokens: tokenTracker?._totalTokens || usage?.session?.totalTokens || 0,
65
+ totalCost: tokenTracker?._totalCostUsd || usage?.session?.totalCostUsd || 0,
66
+ totalCalls: tokenTracker?._totalCalls || usage?.session?.totalCalls || 0,
54
67
  byCategory: usage?.byCategory || {},
55
68
  byModel: usage?.byModel || {},
56
69
  byTool: usage?.byTool || {},