wolverine-ai 1.0.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/PLATFORM.md +442 -0
- package/README.md +475 -0
- package/SERVER_BEST_PRACTICES.md +62 -0
- package/TELEMETRY.md +108 -0
- package/bin/wolverine.js +95 -0
- package/examples/01-basic-typo.js +31 -0
- package/examples/02-multi-file/routes/users.js +15 -0
- package/examples/02-multi-file/server.js +25 -0
- package/examples/03-syntax-error.js +23 -0
- package/examples/04-secret-leak.js +14 -0
- package/examples/05-expired-key.js +27 -0
- package/examples/06-json-config/config.json +13 -0
- package/examples/06-json-config/server.js +28 -0
- package/examples/07-rate-limit-loop.js +11 -0
- package/examples/08-sandbox-escape.js +20 -0
- package/examples/buggy-server.js +39 -0
- package/examples/demos/01-basic-typo/index.js +20 -0
- package/examples/demos/01-basic-typo/routes/api.js +13 -0
- package/examples/demos/01-basic-typo/routes/health.js +4 -0
- package/examples/demos/02-multi-file/index.js +24 -0
- package/examples/demos/02-multi-file/routes/api.js +13 -0
- package/examples/demos/02-multi-file/routes/health.js +4 -0
- package/examples/demos/03-syntax-error/index.js +18 -0
- package/examples/demos/04-secret-leak/index.js +16 -0
- package/examples/demos/05-expired-key/index.js +21 -0
- package/examples/demos/06-json-config/config.json +9 -0
- package/examples/demos/06-json-config/index.js +20 -0
- package/examples/demos/07-null-crash/index.js +16 -0
- package/examples/run-demo.js +110 -0
- package/package.json +67 -0
- package/server/config/settings.json +62 -0
- package/server/index.js +33 -0
- package/server/routes/api.js +12 -0
- package/server/routes/health.js +16 -0
- package/server/routes/time.js +12 -0
- package/src/agent/agent-engine.js +727 -0
- package/src/agent/goal-loop.js +140 -0
- package/src/agent/research-agent.js +120 -0
- package/src/agent/sub-agents.js +176 -0
- package/src/backup/backup-manager.js +321 -0
- package/src/brain/brain.js +315 -0
- package/src/brain/embedder.js +131 -0
- package/src/brain/function-map.js +263 -0
- package/src/brain/vector-store.js +267 -0
- package/src/core/ai-client.js +387 -0
- package/src/core/cluster-manager.js +144 -0
- package/src/core/config.js +89 -0
- package/src/core/error-parser.js +87 -0
- package/src/core/health-monitor.js +129 -0
- package/src/core/models.js +132 -0
- package/src/core/patcher.js +55 -0
- package/src/core/runner.js +464 -0
- package/src/core/system-info.js +141 -0
- package/src/core/verifier.js +146 -0
- package/src/core/wolverine.js +290 -0
- package/src/dashboard/server.js +1332 -0
- package/src/index.js +94 -0
- package/src/logger/event-logger.js +237 -0
- package/src/logger/pricing.js +96 -0
- package/src/logger/repair-history.js +109 -0
- package/src/logger/token-tracker.js +277 -0
- package/src/mcp/mcp-client.js +224 -0
- package/src/mcp/mcp-registry.js +228 -0
- package/src/mcp/mcp-security.js +152 -0
- package/src/monitor/perf-monitor.js +300 -0
- package/src/monitor/process-monitor.js +231 -0
- package/src/monitor/route-prober.js +191 -0
- package/src/notifications/notifier.js +227 -0
- package/src/platform/heartbeat.js +93 -0
- package/src/platform/queue.js +53 -0
- package/src/platform/register.js +64 -0
- package/src/platform/telemetry.js +76 -0
- package/src/security/admin-auth.js +150 -0
- package/src/security/injection-detector.js +174 -0
- package/src/security/rate-limiter.js +152 -0
- package/src/security/sandbox.js +128 -0
- package/src/security/secret-redactor.js +217 -0
- package/src/skills/skill-registry.js +129 -0
- package/src/skills/sql.js +375 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const http = require("http");
|
|
2
|
+
const chalk = require("chalk");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Health Monitor — periodically pings the server to detect hangs, freezes,
|
|
6
|
+
* and unresponsive states that don't produce a crash/exit.
|
|
7
|
+
*
|
|
8
|
+
* Works like PM2's health check: if the server stops responding,
|
|
9
|
+
* wolverine treats it as a crash and triggers the heal cycle.
|
|
10
|
+
*/
|
|
11
|
+
class HealthMonitor {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.port = options.port || parseInt(process.env.PORT, 10) || 3000;
|
|
14
|
+
this.path = options.path || "/health";
|
|
15
|
+
this.intervalMs = options.intervalMs || 15000; // check every 15s
|
|
16
|
+
this.timeoutMs = options.timeoutMs || 5000; // 5s timeout per check
|
|
17
|
+
this.failThreshold = options.failThreshold || 3; // 3 consecutive fails = dead
|
|
18
|
+
this.startDelayMs = options.startDelayMs || 10000; // wait 10s before first check
|
|
19
|
+
|
|
20
|
+
this._timer = null;
|
|
21
|
+
this._consecutiveFailures = 0;
|
|
22
|
+
this._onUnhealthy = null;
|
|
23
|
+
this._running = false;
|
|
24
|
+
this._totalChecks = 0;
|
|
25
|
+
this._totalPasses = 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Start monitoring. Calls onUnhealthy callback when the server is deemed dead.
|
|
30
|
+
*/
|
|
31
|
+
start(onUnhealthy) {
|
|
32
|
+
this._onUnhealthy = onUnhealthy;
|
|
33
|
+
this._consecutiveFailures = 0;
|
|
34
|
+
this._running = true;
|
|
35
|
+
|
|
36
|
+
// Delay first check to let the server boot
|
|
37
|
+
this._timer = setTimeout(() => {
|
|
38
|
+
if (!this._running) return;
|
|
39
|
+
this._check();
|
|
40
|
+
this._timer = setInterval(() => this._check(), this.intervalMs);
|
|
41
|
+
}, this.startDelayMs);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
stop() {
|
|
45
|
+
this._running = false;
|
|
46
|
+
if (this._timer) {
|
|
47
|
+
clearTimeout(this._timer);
|
|
48
|
+
clearInterval(this._timer);
|
|
49
|
+
this._timer = null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
reset() {
|
|
54
|
+
this._consecutiveFailures = 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getStats() {
|
|
58
|
+
return {
|
|
59
|
+
totalChecks: this._totalChecks,
|
|
60
|
+
totalPasses: this._totalPasses,
|
|
61
|
+
consecutiveFailures: this._consecutiveFailures,
|
|
62
|
+
uptimePercent: this._totalChecks > 0
|
|
63
|
+
? Math.round((this._totalPasses / this._totalChecks) * 100)
|
|
64
|
+
: 100,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_check() {
|
|
69
|
+
if (!this._running) return;
|
|
70
|
+
this._totalChecks++;
|
|
71
|
+
|
|
72
|
+
const req = http.request(
|
|
73
|
+
{
|
|
74
|
+
hostname: "127.0.0.1",
|
|
75
|
+
port: this.port,
|
|
76
|
+
path: this.path,
|
|
77
|
+
method: "GET",
|
|
78
|
+
timeout: this.timeoutMs,
|
|
79
|
+
},
|
|
80
|
+
(res) => {
|
|
81
|
+
let body = "";
|
|
82
|
+
res.on("data", (chunk) => { body += chunk; });
|
|
83
|
+
res.on("end", () => {
|
|
84
|
+
if (res.statusCode >= 200 && res.statusCode < 400) {
|
|
85
|
+
this._onPass();
|
|
86
|
+
} else {
|
|
87
|
+
this._onFail(`HTTP ${res.statusCode}`);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
req.on("timeout", () => {
|
|
94
|
+
req.destroy();
|
|
95
|
+
this._onFail("timeout");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
req.on("error", (err) => {
|
|
99
|
+
// ECONNREFUSED means the server isn't listening — this is expected
|
|
100
|
+
// right after a crash/restart, so only count it if we've been checking a while
|
|
101
|
+
this._onFail(err.code || err.message);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
req.end();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_onPass() {
|
|
108
|
+
if (this._consecutiveFailures > 0) {
|
|
109
|
+
console.log(chalk.green(` 💓 Health check passed (recovered after ${this._consecutiveFailures} failures)`));
|
|
110
|
+
}
|
|
111
|
+
this._consecutiveFailures = 0;
|
|
112
|
+
this._totalPasses++;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_onFail(reason) {
|
|
116
|
+
this._consecutiveFailures++;
|
|
117
|
+
console.log(chalk.yellow(` 💔 Health check failed (${this._consecutiveFailures}/${this.failThreshold}): ${reason}`));
|
|
118
|
+
|
|
119
|
+
if (this._consecutiveFailures >= this.failThreshold) {
|
|
120
|
+
console.log(chalk.red(`\n🚨 Server unresponsive after ${this.failThreshold} consecutive health check failures.`));
|
|
121
|
+
this._consecutiveFailures = 0; // reset to avoid re-triggering immediately
|
|
122
|
+
if (this._onUnhealthy) {
|
|
123
|
+
this._onUnhealthy(reason);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = { HealthMonitor };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Configuration — centralized model selection for every AI task.
|
|
3
|
+
*
|
|
4
|
+
* Users configure models in .env.local to optimize spend:
|
|
5
|
+
*
|
|
6
|
+
* REASONING_MODEL — Deep analysis, complex debugging (most expensive, most capable)
|
|
7
|
+
* CODING_MODEL — Code repair generation (important, needs strong coding ability)
|
|
8
|
+
* CHAT_MODEL — Explanations, summaries (good but cheaper)
|
|
9
|
+
* AUDIT_MODEL — Security scans, injection detection (runs on every error)
|
|
10
|
+
* UTILITY_MODEL — JSON formatting, regex validation, simple classification (cheapest)
|
|
11
|
+
*
|
|
12
|
+
* Defaults use gpt-4o tiers. Users can swap in any OpenAI-compatible model.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const MODEL_ROLES = {
|
|
16
|
+
// Deep reasoning — used for multi-step debugging when a simple fix fails
|
|
17
|
+
reasoning: {
|
|
18
|
+
envKey: "REASONING_MODEL",
|
|
19
|
+
default: "gpt-5.4",
|
|
20
|
+
description: "Deep analysis and complex multi-step debugging",
|
|
21
|
+
tier: "premium",
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
// Code generation — the main repair model
|
|
25
|
+
coding: {
|
|
26
|
+
envKey: "CODING_MODEL",
|
|
27
|
+
default: "gpt-5.3-codex",
|
|
28
|
+
description: "Code repair and fix generation",
|
|
29
|
+
tier: "premium",
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// Chat/explanation — used for generating human-readable explanations
|
|
33
|
+
chat: {
|
|
34
|
+
envKey: "CHAT_MODEL",
|
|
35
|
+
default: "gpt-5.4-mini",
|
|
36
|
+
description: "Explanations, summaries, and user-facing messages",
|
|
37
|
+
tier: "standard",
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// Security audit — injection detection, runs on every single error
|
|
41
|
+
audit: {
|
|
42
|
+
envKey: "AUDIT_MODEL",
|
|
43
|
+
default: "gpt-5.4-nano",
|
|
44
|
+
description: "Security scanning and prompt injection detection",
|
|
45
|
+
tier: "economy",
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// Compacting — compresses text before embedding into brain
|
|
49
|
+
utility: {
|
|
50
|
+
envKey: "COMPACTING_MODEL",
|
|
51
|
+
default: "gpt-5.4-nano",
|
|
52
|
+
description: "Text compaction before brain embedding",
|
|
53
|
+
tier: "economy",
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// Tool — chat responses that use function calling (call_endpoint, search_brain)
|
|
57
|
+
tool: {
|
|
58
|
+
envKey: "TOOL_MODEL",
|
|
59
|
+
default: "gpt-4o-mini",
|
|
60
|
+
description: "Chat with tool calling (must support function calling)",
|
|
61
|
+
tier: "standard",
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
// Classifier — routes commands to CHAT vs AGENT, picks tiers
|
|
65
|
+
classifier: {
|
|
66
|
+
envKey: "CLASSIFIER_MODEL",
|
|
67
|
+
default: "gpt-4o-mini",
|
|
68
|
+
description: "Command routing and classification (CHAT/AGENT, SMALL/MEDIUM/LARGE)",
|
|
69
|
+
tier: "economy",
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// Research — deep research for solutions when fixes fail
|
|
73
|
+
research: {
|
|
74
|
+
envKey: "RESEARCH_MODEL",
|
|
75
|
+
default: "gpt-4o",
|
|
76
|
+
description: "Deep research for error solutions and documentation lookup",
|
|
77
|
+
tier: "premium",
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// Embedding — vector representations for semantic search
|
|
81
|
+
embedding: {
|
|
82
|
+
envKey: "TEXT_EMBEDDING_MODEL",
|
|
83
|
+
default: "text-embedding-3-small",
|
|
84
|
+
description: "Text embeddings for brain vector store",
|
|
85
|
+
tier: "economy",
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get the configured model for a given role.
|
|
91
|
+
*/
|
|
92
|
+
function getModel(role) {
|
|
93
|
+
const config = MODEL_ROLES[role];
|
|
94
|
+
if (!config) {
|
|
95
|
+
throw new Error(`Unknown model role: "${role}". Valid roles: ${Object.keys(MODEL_ROLES).join(", ")}`);
|
|
96
|
+
}
|
|
97
|
+
// Priority: env var → wolverine.config.js → hardcoded default
|
|
98
|
+
const { getConfig } = require("./config");
|
|
99
|
+
return process.env[config.envKey] || getConfig(`models.${role}`) || config.default;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get all model assignments for logging.
|
|
104
|
+
*/
|
|
105
|
+
function getModelConfig() {
|
|
106
|
+
const config = {};
|
|
107
|
+
for (const [role, def] of Object.entries(MODEL_ROLES)) {
|
|
108
|
+
config[role] = {
|
|
109
|
+
model: process.env[def.envKey] || def.default,
|
|
110
|
+
source: process.env[def.envKey] ? "env" : "default",
|
|
111
|
+
tier: def.tier,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return config;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Log the current model configuration.
|
|
119
|
+
*/
|
|
120
|
+
function logModelConfig(chalk) {
|
|
121
|
+
const config = getModelConfig();
|
|
122
|
+
const tierColors = { premium: "cyan", standard: "blue", economy: "gray" };
|
|
123
|
+
|
|
124
|
+
for (const [role, info] of Object.entries(config)) {
|
|
125
|
+
const color = tierColors[info.tier] || "white";
|
|
126
|
+
const label = `${role.padEnd(10)} → ${info.model}`;
|
|
127
|
+
const source = info.source === "env" ? "(custom)" : "(default)";
|
|
128
|
+
console.log(chalk[color](` ${label} ${source}`));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = { getModel, getModelConfig, logModelConfig, MODEL_ROLES };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Apply a set of changes from the AI repair response.
|
|
5
|
+
* All file operations go through the sandbox.
|
|
6
|
+
* Backups are managed by BackupManager (not inline .bak files anymore).
|
|
7
|
+
*
|
|
8
|
+
* Returns an array of { file, success, error? } objects.
|
|
9
|
+
*/
|
|
10
|
+
function applyPatch(changes, basePath, sandbox) {
|
|
11
|
+
const results = [];
|
|
12
|
+
|
|
13
|
+
for (const change of changes) {
|
|
14
|
+
const filePath = path.isAbsolute(change.file)
|
|
15
|
+
? change.file
|
|
16
|
+
: path.resolve(basePath, change.file);
|
|
17
|
+
|
|
18
|
+
// All access through sandbox
|
|
19
|
+
if (!sandbox.exists(filePath)) {
|
|
20
|
+
results.push({ file: filePath, success: false, error: "File not found" });
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const original = sandbox.readFile(filePath);
|
|
25
|
+
|
|
26
|
+
// Normalize line endings for matching
|
|
27
|
+
const normalizedOriginal = original.replace(/\r\n/g, "\n");
|
|
28
|
+
const normalizedOld = change.old.replace(/\r\n/g, "\n");
|
|
29
|
+
|
|
30
|
+
if (!normalizedOriginal.includes(normalizedOld)) {
|
|
31
|
+
results.push({
|
|
32
|
+
file: filePath,
|
|
33
|
+
success: false,
|
|
34
|
+
error: "Could not find the exact text to replace in the source file",
|
|
35
|
+
});
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const patched = normalizedOriginal.replace(normalizedOld, change.new.replace(/\r\n/g, "\n"));
|
|
40
|
+
sandbox.writeFile(filePath, patched);
|
|
41
|
+
|
|
42
|
+
results.push({ file: filePath, success: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return results;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Rollback files from a backup entry using BackupManager.
|
|
50
|
+
*/
|
|
51
|
+
function rollbackFromBackup(backupManager, backupId) {
|
|
52
|
+
return backupManager.rollbackTo(backupId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { applyPatch, rollbackFromBackup };
|