wolverine-ai 1.5.0 → 1.5.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/server/index.js +30 -2
- package/src/core/error-hook.js +49 -56
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wolverine-ai",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.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/server/index.js
CHANGED
|
@@ -19,10 +19,38 @@ fastify.setNotFoundHandler((req, reply) => {
|
|
|
19
19
|
reply.code(404).send({ error: "Not found", path: req.url });
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
// Error handler
|
|
22
|
+
// Error handler — reports to Wolverine parent via IPC for auto-healing
|
|
23
23
|
fastify.setErrorHandler((err, req, reply) => {
|
|
24
24
|
console.error(`[ERROR] ${err.message}`);
|
|
25
|
-
reply.code(500).send({ error:
|
|
25
|
+
reply.code(500).send({ error: err.message });
|
|
26
|
+
|
|
27
|
+
// Report to Wolverine via IPC (if running under wolverine)
|
|
28
|
+
if (typeof process.send === "function") {
|
|
29
|
+
try {
|
|
30
|
+
// Extract file/line from stack trace
|
|
31
|
+
let file = null, line = null;
|
|
32
|
+
if (err.stack) {
|
|
33
|
+
const frames = err.stack.split("\n");
|
|
34
|
+
for (const frame of frames) {
|
|
35
|
+
const m = frame.match(/\(([^)]+):(\d+):(\d+)\)/) || frame.match(/at\s+([^\s(]+):(\d+):(\d+)/);
|
|
36
|
+
if (m && !m[1].includes("node_modules") && !m[1].includes("node:")) {
|
|
37
|
+
file = m[1]; line = parseInt(m[2], 10); break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
process.send({
|
|
42
|
+
type: "route_error",
|
|
43
|
+
path: req.url,
|
|
44
|
+
method: req.method,
|
|
45
|
+
statusCode: 500,
|
|
46
|
+
message: err.message,
|
|
47
|
+
stack: err.stack,
|
|
48
|
+
file,
|
|
49
|
+
line,
|
|
50
|
+
timestamp: Date.now(),
|
|
51
|
+
});
|
|
52
|
+
} catch (_) { /* IPC send failed — non-fatal */ }
|
|
53
|
+
}
|
|
26
54
|
});
|
|
27
55
|
|
|
28
56
|
fastify.listen({ port: PORT, host: "0.0.0.0" }, (err) => {
|
package/src/core/error-hook.js
CHANGED
|
@@ -1,55 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Error Hook — preloaded into the child server process via --require.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Safety net: patches Fastify and Express to report caught errors
|
|
5
|
+
* via IPC to the Wolverine parent process. Works even if the user's
|
|
6
|
+
* server code doesn't call process.send() in its error handler.
|
|
7
7
|
*
|
|
8
8
|
* How it works:
|
|
9
|
-
* 1. Runner spawns
|
|
10
|
-
* 2. This file
|
|
11
|
-
* 3.
|
|
12
|
-
*
|
|
9
|
+
* 1. Runner spawns: node --require error-hook.js server/index.js
|
|
10
|
+
* 2. This file intercepts require("fastify") and require("express")
|
|
11
|
+
* 3. Wraps the constructor to add an onError hook (Fastify) or
|
|
12
|
+
* error middleware (Express) that sends IPC messages
|
|
13
|
+
* 4. Parent's ErrorMonitor receives messages and triggers heal
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
+
* If the server already reports errors via process.send(), the hook
|
|
16
|
+
* deduplicates by checking a timestamp flag on the error object.
|
|
15
17
|
*/
|
|
16
18
|
|
|
17
19
|
const Module = require("module");
|
|
18
20
|
const originalLoad = Module._load;
|
|
19
21
|
|
|
20
|
-
let
|
|
22
|
+
let _fastifyHooked = false;
|
|
23
|
+
let _expressHooked = false;
|
|
21
24
|
|
|
22
25
|
Module._load = function (request, parent, isMain) {
|
|
23
26
|
const result = originalLoad.apply(this, arguments);
|
|
24
27
|
|
|
25
28
|
// Hook Fastify
|
|
26
|
-
if (request === "fastify" && typeof result === "function" && !
|
|
29
|
+
if (request === "fastify" && typeof result === "function" && !_fastifyHooked) {
|
|
30
|
+
_fastifyHooked = true;
|
|
27
31
|
const originalFastify = result;
|
|
28
32
|
const wrapped = function (...args) {
|
|
29
33
|
const instance = originalFastify(...args);
|
|
30
34
|
_hookFastify(instance);
|
|
31
35
|
return instance;
|
|
32
36
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
wrapped[key] = originalFastify[key];
|
|
36
|
-
});
|
|
37
|
-
_hooked = true;
|
|
37
|
+
Object.keys(originalFastify).forEach((key) => { wrapped[key] = originalFastify[key]; });
|
|
38
|
+
wrapped.default = wrapped; // ESM compat
|
|
38
39
|
return wrapped;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
// Hook Express
|
|
42
|
-
if (request === "express" && typeof result === "function" && !
|
|
43
|
+
if (request === "express" && typeof result === "function" && !_expressHooked) {
|
|
44
|
+
_expressHooked = true;
|
|
43
45
|
const originalExpress = result;
|
|
44
46
|
const wrapped = function (...args) {
|
|
45
47
|
const app = originalExpress(...args);
|
|
46
48
|
_hookExpress(app);
|
|
47
49
|
return app;
|
|
48
50
|
};
|
|
49
|
-
Object.keys(originalExpress).forEach((key) => {
|
|
50
|
-
wrapped[key] = originalExpress[key];
|
|
51
|
-
});
|
|
52
|
-
_hooked = true;
|
|
51
|
+
Object.keys(originalExpress).forEach((key) => { wrapped[key] = originalExpress[key]; });
|
|
53
52
|
return wrapped;
|
|
54
53
|
}
|
|
55
54
|
|
|
@@ -57,55 +56,51 @@ Module._load = function (request, parent, isMain) {
|
|
|
57
56
|
};
|
|
58
57
|
|
|
59
58
|
function _hookFastify(fastify) {
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
// Wrap setErrorHandler so our IPC reporting runs BEFORE the user's handler
|
|
60
|
+
const origSetError = fastify.setErrorHandler;
|
|
61
|
+
fastify.setErrorHandler = function (userHandler) {
|
|
62
|
+
return origSetError.call(this, function (error, request, reply) {
|
|
64
63
|
_reportError(request.url, request.method, error);
|
|
65
|
-
|
|
64
|
+
return userHandler.call(this, error, request, reply);
|
|
66
65
|
});
|
|
67
|
-
|
|
68
|
-
});
|
|
66
|
+
};
|
|
69
67
|
|
|
70
|
-
// Also
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return originalSetError(function (error, request, reply) {
|
|
68
|
+
// Also add onError hook as a fallback (fires even if no custom error handler)
|
|
69
|
+
try {
|
|
70
|
+
fastify.addHook("onError", function (request, reply, error, done) {
|
|
74
71
|
_reportError(request.url, request.method, error);
|
|
75
|
-
|
|
72
|
+
done();
|
|
76
73
|
});
|
|
77
|
-
}
|
|
74
|
+
} catch { /* addHook may fail if server is already started */ }
|
|
78
75
|
}
|
|
79
76
|
|
|
80
77
|
function _hookExpress(app) {
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
const originalListen = app.listen.bind(app);
|
|
78
|
+
// Wrap app.listen to inject error middleware AFTER all user middleware
|
|
79
|
+
const originalListen = app.listen;
|
|
84
80
|
app.listen = function (...args) {
|
|
85
|
-
|
|
86
|
-
app.use(function wolverineErrorHandler(err, req, res, next) {
|
|
81
|
+
app.use(function _wolverineErrorHook(err, req, res, next) {
|
|
87
82
|
_reportError(req.originalUrl || req.url, req.method, err);
|
|
88
83
|
next(err);
|
|
89
84
|
});
|
|
90
|
-
return originalListen(
|
|
85
|
+
return originalListen.apply(this, args);
|
|
91
86
|
};
|
|
92
87
|
}
|
|
93
88
|
|
|
89
|
+
// Dedup: skip if error was already reported in the same tick
|
|
90
|
+
const _reported = new WeakSet();
|
|
91
|
+
|
|
94
92
|
function _reportError(url, method, error) {
|
|
95
|
-
if (
|
|
93
|
+
if (typeof process.send !== "function") return;
|
|
94
|
+
if (!error || _reported.has(error)) return;
|
|
95
|
+
_reported.add(error);
|
|
96
96
|
|
|
97
97
|
try {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const match = sl.match(/\(([^)]+):(\d+):(\d+)\)/) || sl.match(/at\s+([^\s(]+):(\d+):(\d+)/);
|
|
105
|
-
if (match && !match[1].includes("node_modules") && !match[1].includes("node:")) {
|
|
106
|
-
file = match[1];
|
|
107
|
-
line = parseInt(match[2], 10);
|
|
108
|
-
break;
|
|
98
|
+
let file = null, line = null;
|
|
99
|
+
if (error.stack) {
|
|
100
|
+
for (const frame of error.stack.split("\n")) {
|
|
101
|
+
const m = frame.match(/\(([^)]+):(\d+):(\d+)\)/) || frame.match(/at\s+([^\s(]+):(\d+):(\d+)/);
|
|
102
|
+
if (m && !m[1].includes("node_modules") && !m[1].includes("node:")) {
|
|
103
|
+
file = m[1]; line = parseInt(m[2], 10); break;
|
|
109
104
|
}
|
|
110
105
|
}
|
|
111
106
|
}
|
|
@@ -115,13 +110,11 @@ function _reportError(url, method, error) {
|
|
|
115
110
|
path: url,
|
|
116
111
|
method: method || "GET",
|
|
117
112
|
statusCode: 500,
|
|
118
|
-
message: error
|
|
119
|
-
stack: error
|
|
113
|
+
message: error.message || "Unknown error",
|
|
114
|
+
stack: (error.stack || "").slice(0, 2000),
|
|
120
115
|
file,
|
|
121
116
|
line,
|
|
122
117
|
timestamp: Date.now(),
|
|
123
118
|
});
|
|
124
|
-
} catch {
|
|
125
|
-
// Silently fail — don't break the server for IPC issues
|
|
126
|
-
}
|
|
119
|
+
} catch { /* IPC send failed — non-fatal */ }
|
|
127
120
|
}
|