wolverine-ai 6.3.1 → 6.4.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/claw/wolverine-api.js +443 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wolverine-ai",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.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": {
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wolverine API — unified entry point for any OpenClaw process.
|
|
3
|
+
*
|
|
4
|
+
* Exposes every wolverine subsystem through a single, lazy-loaded API.
|
|
5
|
+
* Nothing is imported until first use, so requiring this module is free.
|
|
6
|
+
*
|
|
7
|
+
* Usage from any OpenClaw skill, plugin, or agent:
|
|
8
|
+
* const wolverine = require("wolverine-ai/src/claw/wolverine-api");
|
|
9
|
+
* const api = wolverine.init("/path/to/project");
|
|
10
|
+
*
|
|
11
|
+
* // Security
|
|
12
|
+
* api.security.detectInjection("some user input");
|
|
13
|
+
* api.security.redact("text with sk-abc123 key");
|
|
14
|
+
* api.security.sandbox.resolve("server/index.js");
|
|
15
|
+
*
|
|
16
|
+
* // Brain
|
|
17
|
+
* await api.brain.search("how to fix ECONNREFUSED");
|
|
18
|
+
* await api.brain.learn("Redis needs REDIS_URL env var", "fix");
|
|
19
|
+
*
|
|
20
|
+
* // AI
|
|
21
|
+
* const result = await api.ai.call({ model: "claude-sonnet-4-6", ... });
|
|
22
|
+
*
|
|
23
|
+
* // Errors
|
|
24
|
+
* api.errors.parse(stderrText);
|
|
25
|
+
* api.errors.classify(errorMessage);
|
|
26
|
+
* api.errors.routeTools("ECONNREFUSED");
|
|
27
|
+
*
|
|
28
|
+
* // Backup
|
|
29
|
+
* api.backup.create("before risky change");
|
|
30
|
+
* api.backup.rollbackLatest();
|
|
31
|
+
*
|
|
32
|
+
* // ... etc
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const path = require("path");
|
|
36
|
+
|
|
37
|
+
let _projectRoot = null;
|
|
38
|
+
let _initialized = false;
|
|
39
|
+
|
|
40
|
+
// Lazy singletons — created on first access
|
|
41
|
+
const _cache = {};
|
|
42
|
+
|
|
43
|
+
function _require(relativePath) {
|
|
44
|
+
return require(path.join(_projectRoot, relativePath));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function _lazy(key, factory) {
|
|
48
|
+
if (!_cache[key]) _cache[key] = factory();
|
|
49
|
+
return _cache[key];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── Public API ──────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Initialize the Wolverine API for a project.
|
|
56
|
+
* Call once — returns the API object. Subsequent calls return the same instance.
|
|
57
|
+
*/
|
|
58
|
+
function init(projectRoot) {
|
|
59
|
+
if (_initialized && _projectRoot === path.resolve(projectRoot)) return api;
|
|
60
|
+
_projectRoot = path.resolve(projectRoot);
|
|
61
|
+
_initialized = true;
|
|
62
|
+
|
|
63
|
+
// Clear cache if re-initializing with different root
|
|
64
|
+
for (const key of Object.keys(_cache)) delete _cache[key];
|
|
65
|
+
|
|
66
|
+
return api;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const api = {
|
|
70
|
+
// ── Security ────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
get security() {
|
|
73
|
+
return _lazy("security", () => {
|
|
74
|
+
const { detectInjection, localScan, INJECTION_PATTERNS } = _require("src/security/injection-detector");
|
|
75
|
+
const { initRedactor, redact, redactObj, hasSecrets } = _require("src/security/secret-redactor");
|
|
76
|
+
const { Sandbox } = _require("src/security/sandbox");
|
|
77
|
+
const { RateLimiter } = _require("src/security/rate-limiter");
|
|
78
|
+
|
|
79
|
+
// Initialize redactor for this project
|
|
80
|
+
const redactor = initRedactor(_projectRoot);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
// Prompt injection detection (~50 patterns)
|
|
84
|
+
detectInjection,
|
|
85
|
+
localScan,
|
|
86
|
+
INJECTION_PATTERNS,
|
|
87
|
+
|
|
88
|
+
// Secret redaction
|
|
89
|
+
redact,
|
|
90
|
+
redactObj,
|
|
91
|
+
hasSecrets,
|
|
92
|
+
redactor,
|
|
93
|
+
|
|
94
|
+
// File sandbox
|
|
95
|
+
sandbox: new Sandbox(_projectRoot),
|
|
96
|
+
Sandbox,
|
|
97
|
+
|
|
98
|
+
// Rate limiting
|
|
99
|
+
createRateLimiter: (opts) => new RateLimiter(opts),
|
|
100
|
+
RateLimiter,
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// ── Brain (semantic memory) ─────────────────────────────────
|
|
106
|
+
|
|
107
|
+
get brain() {
|
|
108
|
+
return _lazy("brain", () => {
|
|
109
|
+
const { Brain } = _require("src/brain/brain");
|
|
110
|
+
const { VectorStore } = _require("src/brain/vector-store");
|
|
111
|
+
const { embed, embedBatch, compact, compactAndEmbed } = _require("src/brain/embedder");
|
|
112
|
+
const { scanProject, mapToChunks } = _require("src/brain/function-map");
|
|
113
|
+
const { route, getRoutePrompt, TOOL_ROUTES } = _require("src/brain/tool-router");
|
|
114
|
+
|
|
115
|
+
const brain = new Brain(_projectRoot);
|
|
116
|
+
let _initPromise = null;
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
// High-level brain operations
|
|
120
|
+
async search(query, opts) {
|
|
121
|
+
if (!_initPromise) _initPromise = brain.init();
|
|
122
|
+
await _initPromise;
|
|
123
|
+
return brain.search ? brain.search(query, opts)
|
|
124
|
+
: brain.recall ? brain.recall(query, opts)
|
|
125
|
+
: [];
|
|
126
|
+
},
|
|
127
|
+
async learn(content, category) {
|
|
128
|
+
if (!_initPromise) _initPromise = brain.init();
|
|
129
|
+
await _initPromise;
|
|
130
|
+
if (brain.addDocument) {
|
|
131
|
+
return brain.addDocument({ content, namespace: category || "learnings", source: "wolverine-claw", timestamp: Date.now() });
|
|
132
|
+
}
|
|
133
|
+
if (brain.remember) return brain.remember(content, { namespace: category });
|
|
134
|
+
},
|
|
135
|
+
async getContext(query) {
|
|
136
|
+
if (!_initPromise) _initPromise = brain.init();
|
|
137
|
+
await _initPromise;
|
|
138
|
+
return brain.getContext ? brain.getContext(query) : "";
|
|
139
|
+
},
|
|
140
|
+
async init() {
|
|
141
|
+
if (!_initPromise) _initPromise = brain.init();
|
|
142
|
+
return _initPromise;
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
// Low-level access
|
|
146
|
+
instance: brain,
|
|
147
|
+
VectorStore,
|
|
148
|
+
embed,
|
|
149
|
+
embedBatch,
|
|
150
|
+
compact,
|
|
151
|
+
compactAndEmbed,
|
|
152
|
+
scanProject,
|
|
153
|
+
mapToChunks,
|
|
154
|
+
|
|
155
|
+
// Tool router — maps error types to tool chains
|
|
156
|
+
routeTools: route,
|
|
157
|
+
getRoutePrompt,
|
|
158
|
+
TOOL_ROUTES,
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// ── AI Client ───────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
get ai() {
|
|
166
|
+
return _lazy("ai", () => {
|
|
167
|
+
const client = _require("src/core/ai-client");
|
|
168
|
+
const { getModel, getEmbeddingModel, getModelConfig, detectProvider, MODEL_ROLES } = _require("src/core/models");
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
// Make AI calls (auto-detects provider from model name)
|
|
172
|
+
call: client.aiCall,
|
|
173
|
+
callWithHistory: client.aiCallWithHistory,
|
|
174
|
+
|
|
175
|
+
// Embeddings
|
|
176
|
+
embed: async (text) => {
|
|
177
|
+
const { embed } = _require("src/brain/embedder");
|
|
178
|
+
return embed(text);
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
// Model utilities
|
|
182
|
+
getModel,
|
|
183
|
+
getEmbeddingModel,
|
|
184
|
+
getModelConfig,
|
|
185
|
+
detectProvider,
|
|
186
|
+
MODEL_ROLES,
|
|
187
|
+
|
|
188
|
+
// Token tracking
|
|
189
|
+
setTokenTracker: client.setTokenTracker,
|
|
190
|
+
getTrackerSnapshot: client.getTrackerSnapshot,
|
|
191
|
+
};
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
// ── Error Handling ──────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
get errors() {
|
|
198
|
+
return _lazy("errors", () => {
|
|
199
|
+
const { parseError, classifyError } = _require("src/core/error-parser");
|
|
200
|
+
const { route, getRoutePrompt } = _require("src/brain/tool-router");
|
|
201
|
+
const { Notifier, HUMAN_REQUIRED_PATTERNS } = _require("src/notifications/notifier");
|
|
202
|
+
const { LoopGuard } = _require("src/skills/loop-guard");
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
// Parse error text → structured {file, line, message, errorType}
|
|
206
|
+
parse: parseError,
|
|
207
|
+
// Classify error → type (missing_module, syntax, runtime, etc.)
|
|
208
|
+
classify: classifyError,
|
|
209
|
+
// Map error type → recommended tool chain
|
|
210
|
+
routeTools: route,
|
|
211
|
+
getRoutePrompt,
|
|
212
|
+
|
|
213
|
+
// Detect if error needs human vs can be auto-fixed
|
|
214
|
+
HUMAN_REQUIRED_PATTERNS,
|
|
215
|
+
createNotifier: (opts) => new Notifier(opts),
|
|
216
|
+
|
|
217
|
+
// Loop detection — prevent infinite fix attempts
|
|
218
|
+
createLoopGuard: (opts) => new LoopGuard(_projectRoot, opts),
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
// ── Backup & Recovery ───────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
get backup() {
|
|
226
|
+
return _lazy("backup", () => {
|
|
227
|
+
const { BackupManager } = _require("src/backup/backup-manager");
|
|
228
|
+
const manager = new BackupManager(_projectRoot);
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
create: (reason) => manager.createBackup(reason),
|
|
232
|
+
rollback: (id) => manager.rollbackTo(id),
|
|
233
|
+
rollbackLatest: () => manager.rollbackLatest(),
|
|
234
|
+
undoRollback: () => manager.undoRollback(),
|
|
235
|
+
list: () => manager.list(),
|
|
236
|
+
getStats: () => manager.getStats(),
|
|
237
|
+
promote: (id) => manager.promote(id),
|
|
238
|
+
prune: () => manager.prune(),
|
|
239
|
+
instance: manager,
|
|
240
|
+
};
|
|
241
|
+
});
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
// ── Skills ──────────────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
get skills() {
|
|
247
|
+
return _lazy("skills", () => {
|
|
248
|
+
const { SkillRegistry } = _require("src/skills/skill-registry");
|
|
249
|
+
const registry = new SkillRegistry();
|
|
250
|
+
registry.load();
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
// Skill registry
|
|
254
|
+
match: (query) => registry.match(query),
|
|
255
|
+
list: () => registry.getAll(),
|
|
256
|
+
registry,
|
|
257
|
+
|
|
258
|
+
// SQL injection protection
|
|
259
|
+
get sql() {
|
|
260
|
+
const { scanForInjection, deepScan, sqlGuard } = _require("src/skills/sql");
|
|
261
|
+
return { scanForInjection, deepScan, sqlGuard };
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
// Dependency analysis
|
|
265
|
+
get deps() {
|
|
266
|
+
const deps = _require("src/skills/deps");
|
|
267
|
+
return {
|
|
268
|
+
diagnose: deps.diagnose,
|
|
269
|
+
healthReport: deps.healthReport,
|
|
270
|
+
getMigration: deps.getMigration,
|
|
271
|
+
};
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
// Backup (alias)
|
|
275
|
+
get backup() {
|
|
276
|
+
const b = _require("src/skills/backup");
|
|
277
|
+
return { backup: b.backup, rollback: b.rollback, rollbackLatest: b.rollbackLatest, undoRollback: b.undoRollback, listBackups: b.listBackups };
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
// ── Monitoring ──────────────────────────────────────────────
|
|
284
|
+
|
|
285
|
+
get monitor() {
|
|
286
|
+
return _lazy("monitor", () => {
|
|
287
|
+
const { ErrorMonitor } = _require("src/monitor/error-monitor");
|
|
288
|
+
const { PerfMonitor } = _require("src/monitor/perf-monitor");
|
|
289
|
+
const { ProcessMonitor } = _require("src/monitor/process-monitor");
|
|
290
|
+
const { AdaptiveLimiter } = _require("src/monitor/adaptive-limiter");
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
ErrorMonitor,
|
|
294
|
+
PerfMonitor,
|
|
295
|
+
ProcessMonitor,
|
|
296
|
+
AdaptiveLimiter,
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
// ── Logging & Metrics ───────────────────────────────────────
|
|
302
|
+
|
|
303
|
+
get logger() {
|
|
304
|
+
return _lazy("logger", () => {
|
|
305
|
+
const { EventLogger, EVENT_TYPES, SEVERITY } = _require("src/logger/event-logger");
|
|
306
|
+
const { TokenTracker } = _require("src/logger/token-tracker");
|
|
307
|
+
const { RepairHistory } = _require("src/logger/repair-history");
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
createEventLogger: () => new EventLogger(_projectRoot),
|
|
311
|
+
createTokenTracker: () => new TokenTracker(_projectRoot),
|
|
312
|
+
createRepairHistory: () => new RepairHistory(_projectRoot),
|
|
313
|
+
EVENT_TYPES,
|
|
314
|
+
SEVERITY,
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
// ── Config ──────────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
get config() {
|
|
322
|
+
return _lazy("config", () => {
|
|
323
|
+
const { loadConfig, getConfig } = _require("src/core/config");
|
|
324
|
+
return { load: loadConfig, get: getConfig };
|
|
325
|
+
});
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
// ── Agent Engine ────────────────────────────────────────────
|
|
329
|
+
|
|
330
|
+
get agent() {
|
|
331
|
+
return _lazy("agent", () => {
|
|
332
|
+
const { AgentEngine, TOOL_DEFINITIONS, BLOCKED_COMMANDS } = _require("src/agent/agent-engine");
|
|
333
|
+
const { Sandbox } = _require("src/security/sandbox");
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
// Create a sandboxed agent engine
|
|
337
|
+
create: (opts = {}) => new AgentEngine({
|
|
338
|
+
cwd: opts.cwd || _projectRoot,
|
|
339
|
+
sandbox: opts.sandbox || new Sandbox(opts.cwd || _projectRoot),
|
|
340
|
+
maxTurns: opts.maxTurns || 25,
|
|
341
|
+
maxTokens: opts.maxTokens || 100000,
|
|
342
|
+
logger: opts.logger,
|
|
343
|
+
mcp: opts.mcp,
|
|
344
|
+
}),
|
|
345
|
+
TOOL_DEFINITIONS,
|
|
346
|
+
BLOCKED_COMMANDS,
|
|
347
|
+
AgentEngine,
|
|
348
|
+
};
|
|
349
|
+
});
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
// ── Server Context ──────────────────────────────────────────
|
|
353
|
+
|
|
354
|
+
get context() {
|
|
355
|
+
return _lazy("context", () => {
|
|
356
|
+
const { scan, load, getSummary } = _require("src/core/server-context");
|
|
357
|
+
return {
|
|
358
|
+
scan: () => scan(_projectRoot),
|
|
359
|
+
load: () => load(_projectRoot),
|
|
360
|
+
getSummary: () => getSummary(_projectRoot),
|
|
361
|
+
};
|
|
362
|
+
});
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
// ── Verification ────────────────────────────────────────────
|
|
366
|
+
|
|
367
|
+
get verify() {
|
|
368
|
+
return _lazy("verify", () => {
|
|
369
|
+
const { verifyFix, syntaxCheck, bootProbe } = _require("src/core/verifier");
|
|
370
|
+
return { verifyFix, syntaxCheck, bootProbe };
|
|
371
|
+
});
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
// ── MCP (Model Context Protocol) ───────────────────────────
|
|
375
|
+
|
|
376
|
+
get mcp() {
|
|
377
|
+
return _lazy("mcp", () => {
|
|
378
|
+
const { McpRegistry } = _require("src/mcp/mcp-registry");
|
|
379
|
+
return {
|
|
380
|
+
create: (opts = {}) => new McpRegistry({
|
|
381
|
+
projectRoot: _projectRoot,
|
|
382
|
+
...opts,
|
|
383
|
+
}),
|
|
384
|
+
McpRegistry,
|
|
385
|
+
};
|
|
386
|
+
});
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
// ── Convenience: all-in-one scan ───────────────────────────
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Run a fast security scan on text (regex injection + secrets). Sync, no AI call.
|
|
393
|
+
* For deep AI-powered injection scan, use api.security.detectInjection() (async).
|
|
394
|
+
* Returns { safe, injection, secrets, redacted }.
|
|
395
|
+
*/
|
|
396
|
+
scanText(text) {
|
|
397
|
+
const sec = this.security;
|
|
398
|
+
const injectionResult = sec.localScan(text);
|
|
399
|
+
const hasSecretContent = sec.hasSecrets(text);
|
|
400
|
+
const redacted = sec.redact(text);
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
safe: injectionResult.safe !== false && !hasSecretContent,
|
|
404
|
+
injection: injectionResult,
|
|
405
|
+
secrets: hasSecretContent,
|
|
406
|
+
redacted,
|
|
407
|
+
};
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Diagnose an error: parse, classify, route tools, check if human-needed.
|
|
412
|
+
* Returns { parsed, type, tools, humanRequired }.
|
|
413
|
+
*/
|
|
414
|
+
diagnoseError(errorText) {
|
|
415
|
+
const err = this.errors;
|
|
416
|
+
const parsed = err.parse(errorText);
|
|
417
|
+
const toolRoute = err.routeTools(parsed.errorType || "unknown");
|
|
418
|
+
|
|
419
|
+
// Check if human required
|
|
420
|
+
let humanRequired = false;
|
|
421
|
+
for (const pattern of err.HUMAN_REQUIRED_PATTERNS) {
|
|
422
|
+
if (pattern.test ? pattern.test(errorText) : errorText.includes(pattern)) {
|
|
423
|
+
humanRequired = true;
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
parsed,
|
|
430
|
+
type: parsed.errorType,
|
|
431
|
+
tools: toolRoute,
|
|
432
|
+
humanRequired,
|
|
433
|
+
};
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
/** Get the project root. */
|
|
437
|
+
get projectRoot() { return _projectRoot; },
|
|
438
|
+
|
|
439
|
+
/** Check if initialized. */
|
|
440
|
+
get initialized() { return _initialized; },
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
module.exports = { init, api };
|