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 +1 -1
- package/src/claw/setup.js +43 -6
- package/wolverine-claw/bootstrap.js +201 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wolverine-ai",
|
|
3
|
-
"version": "6.4.
|
|
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
|
-
|
|
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
|
-
}
|
|
888
|
-
|
|
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
|
+
}
|