wolverine-ai 6.3.1 → 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/src/claw/wolverine-api.js +443 -0
- 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.
|
|
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,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 };
|
|
@@ -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
|
+
}
|