wolverine-ai 6.4.0 → 6.4.1

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": "6.4.0",
3
+ "version": "6.4.1",
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": {
package/src/claw/setup.js CHANGED
@@ -565,12 +565,13 @@ function ensureWolverineDep(cwd) {
565
565
  */
566
566
  function addClawScripts(cwd) {
567
567
  const pkgPath = path.join(cwd, "package.json");
568
- if (!fs.existsSync(pkgPath)) return { added: 0 };
568
+ if (!fs.existsSync(pkgPath)) return { added: 0, patched: 0 };
569
569
 
570
570
  try {
571
571
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
572
572
  if (!pkg.scripts) pkg.scripts = {};
573
573
 
574
+ // 1. Add wolverine-claw management scripts
574
575
  const toAdd = {
575
576
  "claw": "wolverine-claw",
576
577
  "claw:direct": "wolverine-claw --direct",
@@ -585,13 +586,44 @@ function addClawScripts(cwd) {
585
586
  }
586
587
  }
587
588
 
588
- if (added > 0) {
589
+ // 2. Patch existing openclaw start scripts to inject --require bootstrap
590
+ // This is the key integration: the bootstrap preload auto-initializes
591
+ // wolverine for the entire process — no code changes in skills/plugins.
592
+ const BOOTSTRAP = "--require ./wolverine-claw/bootstrap.js";
593
+ let patched = 0;
594
+
595
+ for (const [name, cmd] of Object.entries(pkg.scripts)) {
596
+ if (typeof cmd !== "string") continue;
597
+ // Skip scripts we just added
598
+ if (name.startsWith("claw")) continue;
599
+
600
+ // Find scripts that run openclaw (start, dev, gateway, etc.)
601
+ const isOpenClawScript = cmd.includes("openclaw") || cmd.includes("open-claw");
602
+ // Also patch plain node scripts that start a gateway/agent
603
+ const isNodeScript = cmd.startsWith("node ") && !cmd.includes("wolverine");
604
+
605
+ if ((isOpenClawScript || isNodeScript) && !cmd.includes("bootstrap.js")) {
606
+ // Inject --require before the main script/command
607
+ if (cmd.startsWith("node ")) {
608
+ // node index.js → node --require ./wolverine-claw/bootstrap.js index.js
609
+ pkg.scripts[name] = cmd.replace("node ", `node ${BOOTSTRAP} `);
610
+ patched++;
611
+ } else if (cmd.startsWith("openclaw ") || cmd.startsWith("npx openclaw")) {
612
+ // For CLI commands, use NODE_OPTIONS to inject --require
613
+ // npm scripts inherit env vars, so this works cross-platform
614
+ pkg.scripts[name] = `NODE_OPTIONS="${BOOTSTRAP}" ${cmd}`;
615
+ patched++;
616
+ }
617
+ }
618
+ }
619
+
620
+ if (added > 0 || patched > 0) {
589
621
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
590
622
  }
591
623
 
592
- return { added, skipped: added === 0 };
624
+ return { added, patched, skipped: added === 0 && patched === 0 };
593
625
  } catch {
594
- return { added: 0 };
626
+ return { added: 0, patched: 0 };
595
627
  }
596
628
  }
597
629
 
@@ -884,8 +916,13 @@ async function setup(cwd, options = {}) {
884
916
  const scriptsResult = addClawScripts(cwd);
885
917
  if (scriptsResult.added > 0) {
886
918
  log(chalk.green(` ✅ Added ${scriptsResult.added} npm scripts (claw, claw:info, claw:direct)`));
887
- } else if (scriptsResult.skipped) {
888
- log(chalk.gray(" ○ npm scripts already present"));
919
+ }
920
+ if (scriptsResult.patched > 0) {
921
+ log(chalk.green(` ✅ Patched ${scriptsResult.patched} start script(s) with wolverine bootstrap`));
922
+ log(chalk.gray(" Your existing commands now auto-load wolverine (global.wolverine)"));
923
+ }
924
+ if (scriptsResult.added === 0 && (scriptsResult.patched || 0) === 0) {
925
+ log(chalk.gray(" ○ npm scripts already configured"));
889
926
  }
890
927
 
891
928
  log("");
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Wolverine Bootstrap — auto-preload for OpenClaw processes.
3
+ *
4
+ * Loaded via: node --require ./wolverine-claw/bootstrap.js <openclaw-cmd>
5
+ *
6
+ * This file is injected into the OpenClaw process BEFORE any user code runs.
7
+ * It makes the full wolverine API available globally without any imports:
8
+ *
9
+ * // In any OpenClaw skill, plugin, or gateway code:
10
+ * const scan = wolverine.scanText(userInput);
11
+ * if (!scan.safe) throw new Error("Injection detected");
12
+ *
13
+ * const results = await wolverine.brain.search("how to fix ECONNREFUSED");
14
+ * wolverine.backup.create("before risky change");
15
+ *
16
+ * Also patches:
17
+ * - process.on("uncaughtException") → reports to wolverine heal pipeline
18
+ * - process.on("unhandledRejection") → same
19
+ * - Intercepts require("openclaw") → auto-registers wolverine plugin
20
+ */
21
+
22
+ const path = require("path");
23
+ const Module = require("module");
24
+
25
+ // ── Initialize wolverine API ────────────────────────────────────
26
+
27
+ const projectRoot = process.cwd();
28
+ let api;
29
+
30
+ try {
31
+ // Try loading from node_modules first (installed as dep)
32
+ let initFn;
33
+ try {
34
+ initFn = require("wolverine-ai/src/claw/wolverine-api").init;
35
+ } catch {
36
+ // Fallback: relative path (running from wolverine repo)
37
+ initFn = require(path.join(projectRoot, "src", "claw", "wolverine-api")).init;
38
+ }
39
+
40
+ api = initFn(projectRoot);
41
+
42
+ // Make wolverine globally accessible — no imports needed anywhere
43
+ global.wolverine = api;
44
+
45
+ // Also set on process for structured access
46
+ process.wolverine = api;
47
+
48
+ console.log("[wolverine] Bootstrap loaded — global.wolverine available");
49
+ } catch (err) {
50
+ console.warn(`[wolverine] Bootstrap warning: ${err.message}`);
51
+ // Non-fatal — OpenClaw can still run without wolverine features
52
+ }
53
+
54
+ // ── Error handlers → wolverine heal pipeline ────────────────────
55
+
56
+ if (api) {
57
+ // Catch uncaught exceptions and report to wolverine
58
+ process.on("uncaughtException", (err) => {
59
+ console.error(`[wolverine] Uncaught: ${err.message}`);
60
+ _reportError(err, "uncaughtException");
61
+ // Don't exit — let OpenClaw's own handler decide
62
+ });
63
+
64
+ process.on("unhandledRejection", (reason) => {
65
+ const err = reason instanceof Error ? reason : new Error(String(reason));
66
+ console.error(`[wolverine] Unhandled rejection: ${err.message}`);
67
+ _reportError(err, "unhandledRejection");
68
+ });
69
+ }
70
+
71
+ // ── Auto-register wolverine plugin into OpenClaw ────────────────
72
+
73
+ const _originalLoad = Module._load;
74
+ let _openclawPatched = false;
75
+
76
+ Module._load = function (request, parent, isMain) {
77
+ const result = _originalLoad.apply(this, arguments);
78
+
79
+ // Hook into openclaw when it's first loaded
80
+ if (request === "openclaw" && !_openclawPatched && api) {
81
+ _openclawPatched = true;
82
+ _patchOpenClaw(result);
83
+ }
84
+
85
+ return result;
86
+ };
87
+
88
+ function _patchOpenClaw(openclaw) {
89
+ // Wrap gateway creation to auto-register wolverine plugin
90
+ const factories = ["createGateway", "Gateway", "start"];
91
+
92
+ for (const method of factories) {
93
+ if (typeof openclaw[method] !== "function") continue;
94
+
95
+ const original = openclaw[method];
96
+
97
+ if (method === "Gateway") {
98
+ // Constructor — wrap with class
99
+ openclaw[method] = class extends original {
100
+ constructor(...args) {
101
+ super(...args);
102
+ _autoRegisterPlugin(this);
103
+ }
104
+ };
105
+ } else {
106
+ // Factory function — wrap return value
107
+ openclaw[method] = async function (...args) {
108
+ const gateway = await original.apply(this, args);
109
+ if (gateway) _autoRegisterPlugin(gateway);
110
+ return gateway;
111
+ };
112
+ }
113
+ }
114
+
115
+ console.log("[wolverine] OpenClaw patched — plugin auto-registers on gateway start");
116
+ }
117
+
118
+ function _autoRegisterPlugin(gateway) {
119
+ try {
120
+ // Load and register the wolverine integration plugin
121
+ let pluginPath;
122
+ try {
123
+ pluginPath = require.resolve("wolverine-ai/wolverine-claw/plugins/wolverine-integration.js");
124
+ } catch {
125
+ pluginPath = path.join(projectRoot, "wolverine-claw", "plugins", "wolverine-integration.js");
126
+ }
127
+
128
+ const plugin = require(pluginPath);
129
+ const fs = require("fs");
130
+ const configPath = path.join(projectRoot, "wolverine-claw", "config", "settings.json");
131
+ let config = {};
132
+ try { config = JSON.parse(fs.readFileSync(configPath, "utf-8")); } catch {}
133
+
134
+ plugin.register(gateway, config);
135
+ console.log("[wolverine] Plugin auto-registered into OpenClaw gateway");
136
+ } catch (err) {
137
+ console.warn(`[wolverine] Plugin auto-register warning: ${err.message}`);
138
+ }
139
+ }
140
+
141
+ // ── Scan incoming text for injection (available as middleware) ───
142
+
143
+ /**
144
+ * Express/Fastify middleware that scans all request bodies for injection.
145
+ * Usage: app.use(wolverine.middleware.injectionGuard);
146
+ */
147
+ if (api) {
148
+ api.middleware = {
149
+ injectionGuard: (req, res, next) => {
150
+ // Fastify-style (req.body is already parsed)
151
+ const body = typeof req.body === "string" ? req.body : JSON.stringify(req.body || "");
152
+ if (body.length > 10) {
153
+ const scan = api.scanText(body);
154
+ if (!scan.safe) {
155
+ const code = scan.injection?.safe === false ? 403 : 400;
156
+ const msg = scan.injection?.safe === false ? "Blocked: injection detected" : "Blocked: contains secrets";
157
+ if (res.code) {
158
+ // Fastify
159
+ res.code(code).send({ error: msg });
160
+ } else {
161
+ // Express
162
+ res.status(code).json({ error: msg });
163
+ }
164
+ return;
165
+ }
166
+ }
167
+ if (next) next();
168
+ },
169
+ };
170
+ }
171
+
172
+ // ── IPC error reporting ─────────────────────────────────────────
173
+
174
+ function _reportError(err, source) {
175
+ if (typeof process.send !== "function") return;
176
+
177
+ try {
178
+ let file = null, line = null;
179
+ if (err.stack) {
180
+ const frames = err.stack.split("\n");
181
+ for (const frame of frames) {
182
+ const m = frame.match(/\(([^)]+):(\d+):(\d+)\)/) || frame.match(/at\s+([^\s(]+):(\d+):(\d+)/);
183
+ if (m && !m[1].includes("node_modules") && !m[1].includes("node:")) {
184
+ file = m[1]; line = parseInt(m[2], 10); break;
185
+ }
186
+ }
187
+ }
188
+
189
+ process.send({
190
+ type: "route_error",
191
+ path: `claw://${source}`,
192
+ method: "INTERNAL",
193
+ statusCode: 500,
194
+ message: err.message?.slice(0, 500),
195
+ stack: err.stack?.slice(0, 2000),
196
+ file,
197
+ line,
198
+ timestamp: Date.now(),
199
+ });
200
+ } catch {}
201
+ }