wolverine-ai 4.2.0 → 4.3.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 +1 -1
- package/src/agent/agent-engine.js +182 -0
- package/src/brain/brain.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wolverine-ai",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.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": {
|
|
@@ -289,6 +289,76 @@ const TOOL_DEFINITIONS = [
|
|
|
289
289
|
},
|
|
290
290
|
},
|
|
291
291
|
// ── COMPLETION ──
|
|
292
|
+
{
|
|
293
|
+
type: "function",
|
|
294
|
+
function: {
|
|
295
|
+
name: "check_memory",
|
|
296
|
+
description: "Check system and process memory usage. Returns RSS, heap, free system memory, and whether memory pressure is detected.",
|
|
297
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
type: "function",
|
|
302
|
+
function: {
|
|
303
|
+
name: "list_processes",
|
|
304
|
+
description: "List running Node.js processes. Useful for finding zombie/orphan processes or port conflicts.",
|
|
305
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
type: "function",
|
|
310
|
+
function: {
|
|
311
|
+
name: "check_logs",
|
|
312
|
+
description: "Read recent log output. Returns the last N lines from the server's log source.",
|
|
313
|
+
parameters: {
|
|
314
|
+
type: "object",
|
|
315
|
+
properties: {
|
|
316
|
+
lines: { type: "number", description: "Number of lines to return (default: 50)" },
|
|
317
|
+
filter: { type: "string", description: "Optional grep filter pattern" },
|
|
318
|
+
},
|
|
319
|
+
required: [],
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
type: "function",
|
|
325
|
+
function: {
|
|
326
|
+
name: "restart_service",
|
|
327
|
+
description: "Trigger a graceful server restart. Use after applying fixes that require a process restart to take effect.",
|
|
328
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
type: "function",
|
|
333
|
+
function: {
|
|
334
|
+
name: "check_network",
|
|
335
|
+
description: "Check network connectivity: DNS resolution, port availability, and external service reachability.",
|
|
336
|
+
parameters: {
|
|
337
|
+
type: "object",
|
|
338
|
+
properties: {
|
|
339
|
+
host: { type: "string", description: "Hostname to check DNS for (optional)" },
|
|
340
|
+
port: { type: "number", description: "Port to check availability (optional)" },
|
|
341
|
+
url: { type: "string", description: "URL to check reachability (optional)" },
|
|
342
|
+
},
|
|
343
|
+
required: [],
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
type: "function",
|
|
349
|
+
function: {
|
|
350
|
+
name: "inspect_env",
|
|
351
|
+
description: "List environment variable names (NOT values) that are set. Checks if required vars exist without exposing secrets.",
|
|
352
|
+
parameters: {
|
|
353
|
+
type: "object",
|
|
354
|
+
properties: {
|
|
355
|
+
check: { type: "array", items: { type: "string" }, description: "Specific var names to check if set (optional)" },
|
|
356
|
+
},
|
|
357
|
+
required: [],
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
// ── TASK MANAGEMENT ──
|
|
292
362
|
{
|
|
293
363
|
type: "function",
|
|
294
364
|
function: {
|
|
@@ -569,6 +639,12 @@ class AgentEngine {
|
|
|
569
639
|
case "run_db_fix": return this._runDbFix(args);
|
|
570
640
|
case "audit_deps": return this._auditDeps(args);
|
|
571
641
|
case "check_migration": return this._checkMigration(args);
|
|
642
|
+
case "check_memory": return this._checkMemory(args);
|
|
643
|
+
case "list_processes": return this._listProcesses(args);
|
|
644
|
+
case "check_logs": return this._checkLogs(args);
|
|
645
|
+
case "restart_service": return this._restartService(args);
|
|
646
|
+
case "check_network": return this._checkNetwork(args);
|
|
647
|
+
case "inspect_env": return this._inspectEnv(args);
|
|
572
648
|
case "done": return this._done(args);
|
|
573
649
|
// Legacy aliases
|
|
574
650
|
case "list_files": return this._globFiles({ pattern: (args.dir || ".") + "/*" + (args.pattern || "") });
|
|
@@ -1062,6 +1138,112 @@ class AgentEngine {
|
|
|
1062
1138
|
} catch (e) { return { content: `Migration check error: ${e.message}` }; }
|
|
1063
1139
|
}
|
|
1064
1140
|
|
|
1141
|
+
// ── SERVER DIAGNOSTICS ──
|
|
1142
|
+
|
|
1143
|
+
_checkMemory() {
|
|
1144
|
+
const os = require("os");
|
|
1145
|
+
const mem = process.memoryUsage();
|
|
1146
|
+
const totalMB = Math.round(os.totalmem() / 1048576);
|
|
1147
|
+
const freeMB = Math.round(os.freemem() / 1048576);
|
|
1148
|
+
const usedPct = Math.round((1 - os.freemem() / os.totalmem()) * 100);
|
|
1149
|
+
const lines = [
|
|
1150
|
+
`Process RSS: ${Math.round(mem.rss / 1048576)}MB`,
|
|
1151
|
+
`Process Heap: ${Math.round(mem.heapUsed / 1048576)}MB / ${Math.round(mem.heapTotal / 1048576)}MB`,
|
|
1152
|
+
`Process External: ${Math.round(mem.external / 1048576)}MB`,
|
|
1153
|
+
`System: ${freeMB}MB free / ${totalMB}MB total (${usedPct}% used)`,
|
|
1154
|
+
usedPct > 90 ? "⚠️ MEMORY PRESSURE DETECTED — system above 90% usage" : "✅ Memory OK",
|
|
1155
|
+
];
|
|
1156
|
+
return { content: lines.join("\n") };
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
_listProcesses() {
|
|
1160
|
+
try {
|
|
1161
|
+
const cmd = process.platform === "win32"
|
|
1162
|
+
? 'tasklist /FI "IMAGENAME eq node.exe" /FO CSV /NH'
|
|
1163
|
+
: "ps aux | grep -E 'node|PID' | grep -v grep";
|
|
1164
|
+
const output = execSync(cmd, { encoding: "utf-8", timeout: 5000 }).trim();
|
|
1165
|
+
return { content: output || "(no node processes found)" };
|
|
1166
|
+
} catch (e) { return { content: `Error listing processes: ${e.message}` }; }
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
_checkLogs(args) {
|
|
1170
|
+
const lines = args.lines || 50;
|
|
1171
|
+
const filter = args.filter || "";
|
|
1172
|
+
try {
|
|
1173
|
+
let cmd;
|
|
1174
|
+
if (process.platform === "win32") {
|
|
1175
|
+
cmd = `powershell -c "Get-Content -Tail ${lines} .wolverine\\events.log"`;
|
|
1176
|
+
} else {
|
|
1177
|
+
// Try journalctl first (systemd), fall back to log file
|
|
1178
|
+
cmd = `journalctl -u wolverine --no-pager -n ${lines} 2>/dev/null || tail -n ${lines} .wolverine/events.log 2>/dev/null || echo 'No logs found'`;
|
|
1179
|
+
}
|
|
1180
|
+
if (filter) cmd += ` | grep -i '${filter.replace(/'/g, "'\\''")}'`;
|
|
1181
|
+
const output = execSync(cmd, { encoding: "utf-8", timeout: 10000, cwd: this.cwd }).trim();
|
|
1182
|
+
return { content: output.slice(0, 4000) || "(empty)" };
|
|
1183
|
+
} catch (e) { return { content: `Error reading logs: ${e.message}` }; }
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
_restartService() {
|
|
1187
|
+
// Signal the parent wolverine process to restart the child server
|
|
1188
|
+
// We can't directly restart — but we can signal via a file that the runner checks
|
|
1189
|
+
try {
|
|
1190
|
+
const restartFlag = path.join(this.cwd, ".wolverine", "restart-requested");
|
|
1191
|
+
fs.writeFileSync(restartFlag, Date.now().toString(), "utf-8");
|
|
1192
|
+
return { content: "Restart requested. The server will restart after this heal completes." };
|
|
1193
|
+
} catch (e) { return { content: `Error requesting restart: ${e.message}` }; }
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
_checkNetwork(args) {
|
|
1197
|
+
const results = [];
|
|
1198
|
+
try {
|
|
1199
|
+
// DNS check
|
|
1200
|
+
if (args.host) {
|
|
1201
|
+
try {
|
|
1202
|
+
const dns = require("dns");
|
|
1203
|
+
const addresses = execSync(`node -e "require('dns').resolve('${args.host.replace(/'/g, "")}', (e,a) => console.log(e ? 'FAIL:'+e.code : a.join(',')))"`, { encoding: "utf-8", timeout: 5000 }).trim();
|
|
1204
|
+
results.push(`DNS ${args.host}: ${addresses}`);
|
|
1205
|
+
} catch (e) { results.push(`DNS ${args.host}: FAILED — ${e.message}`); }
|
|
1206
|
+
}
|
|
1207
|
+
// Port check
|
|
1208
|
+
if (args.port) {
|
|
1209
|
+
try {
|
|
1210
|
+
const portCmd = process.platform === "win32"
|
|
1211
|
+
? `netstat -ano | findstr ":${args.port}"`
|
|
1212
|
+
: `ss -tlnp | grep ":${args.port}" || echo "port ${args.port} is free"`;
|
|
1213
|
+
const portResult = execSync(portCmd, { encoding: "utf-8", timeout: 3000 }).trim();
|
|
1214
|
+
results.push(`Port ${args.port}: ${portResult || "free"}`);
|
|
1215
|
+
} catch { results.push(`Port ${args.port}: free (nothing listening)`); }
|
|
1216
|
+
}
|
|
1217
|
+
// URL reachability
|
|
1218
|
+
if (args.url) {
|
|
1219
|
+
try {
|
|
1220
|
+
const urlResult = execSync(`node -e "require('${args.url.startsWith('https') ? 'https' : 'http'}').get('${args.url.replace(/'/g, "")}', r => { console.log(r.statusCode); r.resume(); }).on('error', e => console.log('FAIL:'+e.code))"`, { encoding: "utf-8", timeout: 10000 }).trim();
|
|
1221
|
+
results.push(`URL ${args.url}: ${urlResult}`);
|
|
1222
|
+
} catch (e) { results.push(`URL ${args.url}: FAILED — ${e.message}`); }
|
|
1223
|
+
}
|
|
1224
|
+
if (results.length === 0) results.push("Provide host, port, or url to check.");
|
|
1225
|
+
return { content: results.join("\n") };
|
|
1226
|
+
} catch (e) { return { content: `Network check error: ${e.message}` }; }
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
_inspectEnv(args) {
|
|
1230
|
+
if (args.check && Array.isArray(args.check)) {
|
|
1231
|
+
const results = args.check.map(v => `${v}: ${process.env[v] ? "SET (" + process.env[v].length + " chars)" : "NOT SET"}`);
|
|
1232
|
+
return { content: results.join("\n") };
|
|
1233
|
+
}
|
|
1234
|
+
// List all env var names (not values) grouped by category
|
|
1235
|
+
const keys = Object.keys(process.env).sort();
|
|
1236
|
+
const wolverine = keys.filter(k => /^WOLVERINE|^PORT$|^NODE_ENV$/i.test(k));
|
|
1237
|
+
const apiKeys = keys.filter(k => /KEY|SECRET|TOKEN|PASSWORD|AUTH/i.test(k));
|
|
1238
|
+
const other = keys.filter(k => !wolverine.includes(k) && !apiKeys.includes(k));
|
|
1239
|
+
const lines = [
|
|
1240
|
+
`Wolverine vars (${wolverine.length}): ${wolverine.join(", ") || "none"}`,
|
|
1241
|
+
`Secret vars (${apiKeys.length}): ${apiKeys.map(k => k + "=" + (process.env[k] ? "SET" : "MISSING")).join(", ") || "none"}`,
|
|
1242
|
+
`Total env vars: ${keys.length}`,
|
|
1243
|
+
];
|
|
1244
|
+
return { content: lines.join("\n") };
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1065
1247
|
_done(args) {
|
|
1066
1248
|
console.log(chalk.green(` ✅ Agent done: ${args.summary}`));
|
|
1067
1249
|
if (this.logger) {
|
package/src/brain/brain.js
CHANGED
|
@@ -66,7 +66,7 @@ const SEED_DOCS = [
|
|
|
66
66
|
metadata: { topic: "verification" },
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
|
-
text: "Wolverine multi-file agent: turn-limited agent loop with
|
|
69
|
+
text: "Wolverine multi-file agent: turn-limited agent loop with 24 tools across 8 categories. Turn budget adapts to error type: simple (TypeError)=4, config/ENOENT=5, complex=8. Each AI call has 90s timeout via Promise.race. FILE: read_file, write_file, edit_file, glob_files, grep_code, list_dir, move_file. SHELL: bash_exec (30s default, 60s cap), git_log, git_diff. DATABASE: inspect_db (tables/schema/SELECT on SQLite), run_db_fix (UPDATE/DELETE/ALTER with auto-backup). DIAGNOSTICS: check_port, check_env, check_memory (RSS/heap/system with OOM detection), list_processes (find zombie/orphan node processes), check_logs (read recent journalctl/log file), check_network (DNS/port/URL reachability), inspect_env (list env var names without values, check if required vars exist). SERVER: restart_service (request graceful restart after fix). DEPS: audit_deps, check_migration. RESEARCH: web_fetch. CONTROL: done.",
|
|
70
70
|
metadata: { topic: "agent" },
|
|
71
71
|
},
|
|
72
72
|
{
|
|
@@ -110,7 +110,7 @@ const SEED_DOCS = [
|
|
|
110
110
|
metadata: { topic: "sub-agent-workflow" },
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
|
-
text: "Sub-agent tool restrictions: explore gets read_file/glob/grep/git_log/git_diff/list_dir/check_env/check_port/inspect_db/audit_deps. plan gets read_file/glob/grep/list_dir/inspect_db/check_env/audit_deps/check_migration
|
|
113
|
+
text: "Sub-agent tool restrictions: explore gets read_file/glob/grep/git_log/git_diff/list_dir/check_env/check_port/check_memory/check_network/list_processes/inspect_db/audit_deps. plan gets read_file/glob/grep/list_dir/inspect_db/check_env/inspect_env/audit_deps/check_migration. fix gets read_file/write_file/edit_file/glob/grep/bash_exec/move_file/run_db_fix/audit_deps/restart_service. verify gets read_file/glob/grep/bash_exec/inspect_db/check_port/check_memory/check_logs. research gets read_file/grep/web_fetch/check_logs. security gets read_file/glob/grep/inspect_db/inspect_env. database gets read_file/write_file/edit_file/glob/grep/bash_exec/inspect_db/run_db_fix. 24 tools total, each sub-agent type gets tools relevant to its role.",
|
|
114
114
|
metadata: { topic: "sub-agent-tools" },
|
|
115
115
|
},
|
|
116
116
|
{
|