wolverine-ai 6.1.2 → 6.2.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/README.md +171 -0
- package/bin/wolverine-claw.js +184 -0
- package/bin/wolverine.js +27 -0
- package/package.json +10 -4
- package/src/brain/brain.js +20 -0
- package/src/claw/claw-runner.js +422 -0
- package/src/claw/setup.js +876 -0
- package/src/claw/standalone-agent.js +488 -0
- package/wolverine-claw/config/settings.json +115 -0
- package/wolverine-claw/index.js +322 -0
- package/wolverine-claw/plugins/wolverine-integration.js +283 -0
- package/wolverine-claw/skills/.gitkeep +0 -0
- package/wolverine-claw/workspace/.gitignore +5 -0
- package/wolverine-claw/workspace/.gitkeep +0 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wolverine Claw — OpenClaw agentic agent with Wolverine self-healing.
|
|
3
|
+
*
|
|
4
|
+
* This is the entry point for the claw environment. It bootstraps the OpenClaw
|
|
5
|
+
* gateway and Pi agent runtime, wrapped by Wolverine's process manager for
|
|
6
|
+
* automatic crash recovery, healing, and workspace backup.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node bin/wolverine-claw.js (under wolverine healing)
|
|
10
|
+
* node wolverine-claw/index.js (direct, no healing)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const path = require("path");
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
|
|
16
|
+
// Load claw-specific config
|
|
17
|
+
function loadClawConfig() {
|
|
18
|
+
const configPath = path.join(__dirname, "config", "settings.json");
|
|
19
|
+
try {
|
|
20
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
21
|
+
// Strip comment keys (keys starting with _)
|
|
22
|
+
const parsed = JSON.parse(raw);
|
|
23
|
+
return parsed;
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error(`[CLAW] Failed to load config: ${err.message}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const config = loadClawConfig();
|
|
31
|
+
|
|
32
|
+
// Gateway state
|
|
33
|
+
let gateway = null;
|
|
34
|
+
let shutdownCalled = false;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Start the OpenClaw gateway with wolverine integration.
|
|
38
|
+
*/
|
|
39
|
+
async function start() {
|
|
40
|
+
const gatewayPort = config.gateway?.port || 18789;
|
|
41
|
+
const gatewayHost = config.gateway?.host || "127.0.0.1";
|
|
42
|
+
|
|
43
|
+
console.log(`[CLAW] Starting Wolverine Claw...`);
|
|
44
|
+
console.log(`[CLAW] Gateway: ws://${gatewayHost}:${gatewayPort}`);
|
|
45
|
+
console.log(`[CLAW] Workspace: ${path.resolve(config.workspace?.path || "wolverine-claw/workspace")}`);
|
|
46
|
+
|
|
47
|
+
// Ensure workspace directory exists
|
|
48
|
+
const workspacePath = path.resolve(config.workspace?.path || "wolverine-claw/workspace");
|
|
49
|
+
if (!fs.existsSync(workspacePath)) {
|
|
50
|
+
fs.mkdirSync(workspacePath, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// Try to use openclaw as npm dependency
|
|
55
|
+
const openclaw = require("openclaw");
|
|
56
|
+
|
|
57
|
+
// Configure OpenClaw with our settings
|
|
58
|
+
const clawConfig = {
|
|
59
|
+
gateway: {
|
|
60
|
+
port: gatewayPort,
|
|
61
|
+
host: gatewayHost,
|
|
62
|
+
},
|
|
63
|
+
agent: {
|
|
64
|
+
model: config.agent?.model || config.models?.coding || "claude-sonnet-4-6",
|
|
65
|
+
maxTurns: config.agent?.maxTurns || 25,
|
|
66
|
+
},
|
|
67
|
+
channels: buildChannelConfig(config.channels),
|
|
68
|
+
skills: config.skills || {},
|
|
69
|
+
security: config.security || {},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Start gateway via openclaw SDK
|
|
73
|
+
if (openclaw.createGateway) {
|
|
74
|
+
gateway = await openclaw.createGateway(clawConfig);
|
|
75
|
+
await gateway.start();
|
|
76
|
+
} else if (openclaw.Gateway) {
|
|
77
|
+
gateway = new openclaw.Gateway(clawConfig);
|
|
78
|
+
await gateway.start();
|
|
79
|
+
} else if (openclaw.start) {
|
|
80
|
+
gateway = await openclaw.start(clawConfig);
|
|
81
|
+
} else {
|
|
82
|
+
// Fallback: spawn openclaw CLI as child process
|
|
83
|
+
console.log("[CLAW] OpenClaw SDK API not found, falling back to CLI mode...");
|
|
84
|
+
await startClawCLI(gatewayPort, gatewayHost);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(`[CLAW] Gateway running on ws://${gatewayHost}:${gatewayPort}`);
|
|
89
|
+
|
|
90
|
+
// Register wolverine integration plugin
|
|
91
|
+
await registerWolverinePlugin(gateway);
|
|
92
|
+
|
|
93
|
+
// Report health to wolverine parent via IPC
|
|
94
|
+
reportHealth("running");
|
|
95
|
+
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (err.code === "MODULE_NOT_FOUND" && err.message.includes("openclaw")) {
|
|
98
|
+
console.log("[CLAW] OpenClaw not installed, starting standalone agent...");
|
|
99
|
+
await startStandaloneAgent();
|
|
100
|
+
} else {
|
|
101
|
+
console.error(`[CLAW] Startup failed: ${err.message}`);
|
|
102
|
+
console.error(err.stack);
|
|
103
|
+
reportHealth("error", err.message);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Fallback: start wolverine's built-in standalone agent.
|
|
111
|
+
* No external deps needed — uses wolverine's own AI client + tools.
|
|
112
|
+
*/
|
|
113
|
+
async function startStandaloneAgent() {
|
|
114
|
+
try {
|
|
115
|
+
// Load dotenv for AI keys
|
|
116
|
+
const dotenv = require("dotenv");
|
|
117
|
+
dotenv.config({ path: path.resolve(__dirname, "..", ".env.local") });
|
|
118
|
+
dotenv.config({ path: path.resolve(__dirname, "..", ".env") });
|
|
119
|
+
|
|
120
|
+
const { startRepl } = require("../src/claw/standalone-agent");
|
|
121
|
+
reportHealth("running");
|
|
122
|
+
await startRepl(config, { cwd: path.resolve(__dirname, "..") });
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.error(`[CLAW] Standalone agent failed: ${err.message}`);
|
|
125
|
+
console.error(err.stack);
|
|
126
|
+
|
|
127
|
+
// Last resort: try CLI mode
|
|
128
|
+
const gatewayPort = config.gateway?.port || 18789;
|
|
129
|
+
const gatewayHost = config.gateway?.host || "127.0.0.1";
|
|
130
|
+
await startClawCLI(gatewayPort, gatewayHost);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Fallback: spawn openclaw gateway via CLI (npx openclaw gateway).
|
|
136
|
+
*/
|
|
137
|
+
async function startClawCLI(port, host) {
|
|
138
|
+
const { spawn } = require("child_process");
|
|
139
|
+
|
|
140
|
+
// Build the config file for openclaw CLI
|
|
141
|
+
const clawConfigPath = path.join(__dirname, ".claw-runtime-config.yml");
|
|
142
|
+
const yamlConfig = buildClawYaml(config, port, host);
|
|
143
|
+
fs.writeFileSync(clawConfigPath, yamlConfig);
|
|
144
|
+
|
|
145
|
+
console.log(`[CLAW] Spawning: npx openclaw gateway --port ${port}`);
|
|
146
|
+
|
|
147
|
+
const child = spawn("npx", ["openclaw", "gateway", "--port", String(port)], {
|
|
148
|
+
cwd: path.resolve(__dirname, ".."),
|
|
149
|
+
stdio: ["inherit", "inherit", "pipe"],
|
|
150
|
+
env: {
|
|
151
|
+
...process.env,
|
|
152
|
+
OPENCLAW_CONFIG: clawConfigPath,
|
|
153
|
+
OPENCLAW_GATEWAY_PORT: String(port),
|
|
154
|
+
OPENCLAW_GATEWAY_HOST: host,
|
|
155
|
+
},
|
|
156
|
+
shell: true,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
let stderrBuffer = "";
|
|
160
|
+
child.stderr.on("data", (chunk) => {
|
|
161
|
+
const text = chunk.toString();
|
|
162
|
+
stderrBuffer += text;
|
|
163
|
+
process.stderr.write(chunk);
|
|
164
|
+
|
|
165
|
+
// Report errors to wolverine parent
|
|
166
|
+
if (text.includes("Error") || text.includes("FATAL")) {
|
|
167
|
+
reportError(text, stderrBuffer);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
child.on("exit", (code, signal) => {
|
|
172
|
+
if (!shutdownCalled) {
|
|
173
|
+
console.error(`[CLAW] Gateway exited unexpectedly (code=${code}, signal=${signal})`);
|
|
174
|
+
reportHealth("crashed", `exit code ${code}`);
|
|
175
|
+
process.exit(code || 1);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
gateway = child;
|
|
180
|
+
reportHealth("running");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Build channel config from settings, filtering to enabled channels only.
|
|
185
|
+
*/
|
|
186
|
+
function buildChannelConfig(channels) {
|
|
187
|
+
if (!channels) return {};
|
|
188
|
+
const result = {};
|
|
189
|
+
for (const [name, cfg] of Object.entries(channels)) {
|
|
190
|
+
if (name.startsWith("_")) continue;
|
|
191
|
+
if (cfg.enabled) {
|
|
192
|
+
result[name] = { ...cfg };
|
|
193
|
+
delete result[name].enabled;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Build a YAML config string for openclaw CLI fallback.
|
|
201
|
+
*/
|
|
202
|
+
function buildClawYaml(cfg, port, host) {
|
|
203
|
+
const lines = [
|
|
204
|
+
`# Auto-generated by Wolverine Claw — do not edit manually`,
|
|
205
|
+
`gateway:`,
|
|
206
|
+
` port: ${port}`,
|
|
207
|
+
` host: "${host}"`,
|
|
208
|
+
``,
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
if (cfg.agent?.model) {
|
|
212
|
+
lines.push(`agent:`);
|
|
213
|
+
lines.push(` model: "${cfg.agent.model}"`);
|
|
214
|
+
lines.push(` maxTurns: ${cfg.agent.maxTurns || 25}`);
|
|
215
|
+
lines.push(``);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Add enabled channels
|
|
219
|
+
const channels = cfg.channels || {};
|
|
220
|
+
const enabledChannels = Object.entries(channels).filter(([k, v]) => !k.startsWith("_") && v.enabled);
|
|
221
|
+
if (enabledChannels.length > 0) {
|
|
222
|
+
lines.push(`channels:`);
|
|
223
|
+
for (const [name, chanCfg] of enabledChannels) {
|
|
224
|
+
lines.push(` ${name}:`);
|
|
225
|
+
for (const [key, val] of Object.entries(chanCfg)) {
|
|
226
|
+
if (key === "enabled") continue;
|
|
227
|
+
if (typeof val === "string") lines.push(` ${key}: "${val}"`);
|
|
228
|
+
else if (Array.isArray(val)) lines.push(` ${key}: [${val.map(v => `"${v}"`).join(", ")}]`);
|
|
229
|
+
else lines.push(` ${key}: ${val}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
lines.push(``);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return lines.join("\n");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Register wolverine self-healing integration as an OpenClaw plugin.
|
|
240
|
+
*/
|
|
241
|
+
async function registerWolverinePlugin(gw) {
|
|
242
|
+
try {
|
|
243
|
+
const pluginPath = path.join(__dirname, "plugins", "wolverine-integration.js");
|
|
244
|
+
if (fs.existsSync(pluginPath)) {
|
|
245
|
+
const plugin = require(pluginPath);
|
|
246
|
+
if (typeof plugin.register === "function") {
|
|
247
|
+
await plugin.register(gw, config);
|
|
248
|
+
console.log("[CLAW] Wolverine integration plugin loaded");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} catch (err) {
|
|
252
|
+
console.warn(`[CLAW] Plugin load warning: ${err.message}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Report health status to wolverine parent process via IPC.
|
|
258
|
+
*/
|
|
259
|
+
function reportHealth(status, detail) {
|
|
260
|
+
if (typeof process.send === "function") {
|
|
261
|
+
try {
|
|
262
|
+
process.send({
|
|
263
|
+
type: "claw_health",
|
|
264
|
+
status,
|
|
265
|
+
detail: detail || null,
|
|
266
|
+
timestamp: Date.now(),
|
|
267
|
+
gateway: {
|
|
268
|
+
port: config.gateway?.port || 18789,
|
|
269
|
+
host: config.gateway?.host || "127.0.0.1",
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
} catch {}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Report errors to wolverine parent process via IPC for healing.
|
|
278
|
+
*/
|
|
279
|
+
function reportError(message, stderr) {
|
|
280
|
+
if (typeof process.send === "function") {
|
|
281
|
+
try {
|
|
282
|
+
process.send({
|
|
283
|
+
type: "route_error",
|
|
284
|
+
path: "claw://gateway",
|
|
285
|
+
method: "INTERNAL",
|
|
286
|
+
statusCode: 500,
|
|
287
|
+
message: message.trim().slice(0, 500),
|
|
288
|
+
stack: stderr.slice(-2000),
|
|
289
|
+
file: null,
|
|
290
|
+
line: null,
|
|
291
|
+
timestamp: Date.now(),
|
|
292
|
+
});
|
|
293
|
+
} catch {}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Graceful shutdown
|
|
298
|
+
function shutdown(signal) {
|
|
299
|
+
if (shutdownCalled) return;
|
|
300
|
+
shutdownCalled = true;
|
|
301
|
+
console.log(`\n[CLAW] Shutting down (${signal})...`);
|
|
302
|
+
|
|
303
|
+
if (gateway) {
|
|
304
|
+
if (typeof gateway.stop === "function") {
|
|
305
|
+
gateway.stop();
|
|
306
|
+
} else if (typeof gateway.kill === "function") {
|
|
307
|
+
gateway.kill("SIGTERM");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
reportHealth("stopped");
|
|
312
|
+
setTimeout(() => process.exit(0), 2000);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
316
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
317
|
+
|
|
318
|
+
// Start
|
|
319
|
+
start().catch((err) => {
|
|
320
|
+
console.error(`[CLAW] Fatal: ${err.message}`);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
});
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wolverine Integration Plugin for OpenClaw
|
|
3
|
+
*
|
|
4
|
+
* Gives the claw agent access to wolverine's self-healing capabilities:
|
|
5
|
+
* - Brain: semantic memory, project context, and learning
|
|
6
|
+
* - Backup: workspace snapshots and rollback
|
|
7
|
+
* - Healing: error diagnosis and auto-fix pipeline
|
|
8
|
+
* - Health: process monitoring and status reporting
|
|
9
|
+
*
|
|
10
|
+
* This plugin registers as an OpenClaw skill/extension, adding wolverine-specific
|
|
11
|
+
* tools to the agent's toolkit.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const path = require("path");
|
|
15
|
+
const fs = require("fs");
|
|
16
|
+
|
|
17
|
+
const PLUGIN_NAME = "wolverine-integration";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Register the wolverine integration with the OpenClaw gateway.
|
|
21
|
+
*/
|
|
22
|
+
async function register(gateway, config) {
|
|
23
|
+
const projectRoot = path.resolve(__dirname, "../..");
|
|
24
|
+
|
|
25
|
+
// Register wolverine tools as OpenClaw tools/skills
|
|
26
|
+
const tools = buildWolverineTools(projectRoot, config);
|
|
27
|
+
|
|
28
|
+
if (gateway.registerTools) {
|
|
29
|
+
gateway.registerTools(PLUGIN_NAME, tools);
|
|
30
|
+
} else if (gateway.addTools) {
|
|
31
|
+
gateway.addTools(tools);
|
|
32
|
+
} else if (gateway.skills?.register) {
|
|
33
|
+
gateway.skills.register(PLUGIN_NAME, {
|
|
34
|
+
description: "Wolverine self-healing integration — backup, brain, and health tools",
|
|
35
|
+
tools,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Hook into gateway events for self-healing
|
|
40
|
+
if (gateway.on) {
|
|
41
|
+
gateway.on("error", (err) => handleGatewayError(err, projectRoot));
|
|
42
|
+
gateway.on("agent:error", (err) => handleAgentError(err, projectRoot));
|
|
43
|
+
gateway.on("skill:error", (err) => handleSkillError(err, projectRoot));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Periodic health reporting
|
|
47
|
+
const healthInterval = setInterval(() => {
|
|
48
|
+
reportToWolverine("claw_heartbeat", {
|
|
49
|
+
uptime: process.uptime(),
|
|
50
|
+
memory: process.memoryUsage(),
|
|
51
|
+
timestamp: Date.now(),
|
|
52
|
+
});
|
|
53
|
+
}, 30000);
|
|
54
|
+
|
|
55
|
+
// Cleanup on shutdown
|
|
56
|
+
if (gateway.on) {
|
|
57
|
+
gateway.on("shutdown", () => clearInterval(healthInterval));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Build wolverine-specific tools for the OpenClaw agent.
|
|
63
|
+
*/
|
|
64
|
+
function buildWolverineTools(projectRoot, config) {
|
|
65
|
+
return [
|
|
66
|
+
{
|
|
67
|
+
name: "wolverine_backup",
|
|
68
|
+
description: "Create a backup snapshot of the claw workspace",
|
|
69
|
+
parameters: {
|
|
70
|
+
type: "object",
|
|
71
|
+
properties: {
|
|
72
|
+
reason: { type: "string", description: "Reason for backup" },
|
|
73
|
+
},
|
|
74
|
+
required: ["reason"],
|
|
75
|
+
},
|
|
76
|
+
execute: async ({ reason }) => {
|
|
77
|
+
try {
|
|
78
|
+
const { BackupManager } = require(path.join(projectRoot, "src/backup/backup-manager"));
|
|
79
|
+
const manager = new BackupManager(projectRoot);
|
|
80
|
+
const id = manager.createBackup(`claw: ${reason}`);
|
|
81
|
+
return `Backup created: ${id}`;
|
|
82
|
+
} catch (err) {
|
|
83
|
+
return `[ERROR] Backup failed: ${err.message}`;
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "wolverine_rollback",
|
|
89
|
+
description: "Rollback workspace to a previous backup",
|
|
90
|
+
parameters: {
|
|
91
|
+
type: "object",
|
|
92
|
+
properties: {
|
|
93
|
+
backupId: { type: "string", description: "Backup ID to rollback to (or 'latest')" },
|
|
94
|
+
},
|
|
95
|
+
required: ["backupId"],
|
|
96
|
+
},
|
|
97
|
+
execute: async ({ backupId }) => {
|
|
98
|
+
try {
|
|
99
|
+
const { BackupManager } = require(path.join(projectRoot, "src/backup/backup-manager"));
|
|
100
|
+
const manager = new BackupManager(projectRoot);
|
|
101
|
+
if (backupId === "latest") {
|
|
102
|
+
const result = manager.rollbackLatest();
|
|
103
|
+
return result.success ? `Rolled back to latest backup` : `Rollback failed: ${result.error}`;
|
|
104
|
+
}
|
|
105
|
+
const result = manager.rollback(backupId);
|
|
106
|
+
return result.success ? `Rolled back to ${backupId}` : `Rollback failed: ${result.error}`;
|
|
107
|
+
} catch (err) {
|
|
108
|
+
return `[ERROR] Rollback failed: ${err.message}`;
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "wolverine_brain_search",
|
|
114
|
+
description: "Search wolverine's semantic memory for relevant context (past fixes, patterns, learnings)",
|
|
115
|
+
parameters: {
|
|
116
|
+
type: "object",
|
|
117
|
+
properties: {
|
|
118
|
+
query: { type: "string", description: "Search query" },
|
|
119
|
+
limit: { type: "number", description: "Max results (default 5)" },
|
|
120
|
+
},
|
|
121
|
+
required: ["query"],
|
|
122
|
+
},
|
|
123
|
+
execute: async ({ query, limit }) => {
|
|
124
|
+
try {
|
|
125
|
+
const { Brain } = require(path.join(projectRoot, "src/brain/brain"));
|
|
126
|
+
const brain = new Brain(projectRoot);
|
|
127
|
+
await brain.init();
|
|
128
|
+
const results = await brain.search(query, { limit: limit || 5 });
|
|
129
|
+
if (!results || results.length === 0) return "No relevant memories found.";
|
|
130
|
+
return results.map((r, i) => `${i + 1}. [${r.score?.toFixed(2) || "?"}] ${r.content?.slice(0, 200) || r.text?.slice(0, 200)}`).join("\n");
|
|
131
|
+
} catch (err) {
|
|
132
|
+
return `[ERROR] Brain search failed: ${err.message}`;
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "wolverine_brain_learn",
|
|
138
|
+
description: "Store a new learning in wolverine's brain memory",
|
|
139
|
+
parameters: {
|
|
140
|
+
type: "object",
|
|
141
|
+
properties: {
|
|
142
|
+
content: { type: "string", description: "What was learned" },
|
|
143
|
+
category: { type: "string", description: "Category: fix, pattern, warning, optimization" },
|
|
144
|
+
},
|
|
145
|
+
required: ["content"],
|
|
146
|
+
},
|
|
147
|
+
execute: async ({ content, category }) => {
|
|
148
|
+
try {
|
|
149
|
+
const { Brain } = require(path.join(projectRoot, "src/brain/brain"));
|
|
150
|
+
const brain = new Brain(projectRoot);
|
|
151
|
+
await brain.init();
|
|
152
|
+
await brain.addDocument({
|
|
153
|
+
content,
|
|
154
|
+
namespace: category || "learnings",
|
|
155
|
+
source: "wolverine-claw",
|
|
156
|
+
timestamp: Date.now(),
|
|
157
|
+
});
|
|
158
|
+
return "Learning stored in brain.";
|
|
159
|
+
} catch (err) {
|
|
160
|
+
return `[ERROR] Brain learn failed: ${err.message}`;
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: "wolverine_health",
|
|
166
|
+
description: "Get wolverine system health status (process, memory, backups, heal history)",
|
|
167
|
+
parameters: { type: "object", properties: {} },
|
|
168
|
+
execute: async () => {
|
|
169
|
+
const status = {
|
|
170
|
+
uptime: `${Math.floor(process.uptime())}s`,
|
|
171
|
+
memory: `${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)}MB`,
|
|
172
|
+
pid: process.pid,
|
|
173
|
+
nodeVersion: process.version,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const { BackupManager } = require(path.join(projectRoot, "src/backup/backup-manager"));
|
|
178
|
+
const manager = new BackupManager(projectRoot);
|
|
179
|
+
const stats = manager.getStats();
|
|
180
|
+
status.backups = `${stats.total} total (${stats.stable} stable)`;
|
|
181
|
+
} catch {}
|
|
182
|
+
|
|
183
|
+
return JSON.stringify(status, null, 2);
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "wolverine_list_backups",
|
|
188
|
+
description: "List all available workspace backups",
|
|
189
|
+
parameters: { type: "object", properties: {} },
|
|
190
|
+
execute: async () => {
|
|
191
|
+
try {
|
|
192
|
+
const { BackupManager } = require(path.join(projectRoot, "src/backup/backup-manager"));
|
|
193
|
+
const manager = new BackupManager(projectRoot);
|
|
194
|
+
const backups = manager.list();
|
|
195
|
+
if (!backups || backups.length === 0) return "No backups found.";
|
|
196
|
+
return backups.map(b => `${b.id} — ${b.reason || "manual"} (${b.status || "unknown"}) ${b.timestamp || ""}`).join("\n");
|
|
197
|
+
} catch (err) {
|
|
198
|
+
return `[ERROR] List backups failed: ${err.message}`;
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: "wolverine_self_heal",
|
|
204
|
+
description: "Trigger wolverine's self-healing pipeline on a specific error",
|
|
205
|
+
parameters: {
|
|
206
|
+
type: "object",
|
|
207
|
+
properties: {
|
|
208
|
+
error: { type: "string", description: "Error message or stack trace" },
|
|
209
|
+
file: { type: "string", description: "File where error occurred (optional)" },
|
|
210
|
+
},
|
|
211
|
+
required: ["error"],
|
|
212
|
+
},
|
|
213
|
+
execute: async ({ error, file }) => {
|
|
214
|
+
// Report to parent wolverine process for healing
|
|
215
|
+
reportToWolverine("route_error", {
|
|
216
|
+
path: "claw://agent",
|
|
217
|
+
method: "INTERNAL",
|
|
218
|
+
statusCode: 500,
|
|
219
|
+
message: error.slice(0, 500),
|
|
220
|
+
stack: error,
|
|
221
|
+
file: file || null,
|
|
222
|
+
line: null,
|
|
223
|
+
timestamp: Date.now(),
|
|
224
|
+
});
|
|
225
|
+
return "Error reported to wolverine healing pipeline.";
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Handle gateway errors — report to wolverine for healing.
|
|
233
|
+
*/
|
|
234
|
+
function handleGatewayError(err, projectRoot) {
|
|
235
|
+
console.error(`[CLAW] Gateway error: ${err.message}`);
|
|
236
|
+
reportToWolverine("route_error", {
|
|
237
|
+
path: "claw://gateway",
|
|
238
|
+
method: "INTERNAL",
|
|
239
|
+
statusCode: 500,
|
|
240
|
+
message: err.message,
|
|
241
|
+
stack: err.stack,
|
|
242
|
+
file: null,
|
|
243
|
+
line: null,
|
|
244
|
+
timestamp: Date.now(),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Handle agent errors.
|
|
250
|
+
*/
|
|
251
|
+
function handleAgentError(err, projectRoot) {
|
|
252
|
+
console.error(`[CLAW] Agent error: ${err.message}`);
|
|
253
|
+
reportToWolverine("route_error", {
|
|
254
|
+
path: "claw://agent",
|
|
255
|
+
method: "INTERNAL",
|
|
256
|
+
statusCode: 500,
|
|
257
|
+
message: err.message,
|
|
258
|
+
stack: err.stack,
|
|
259
|
+
file: null,
|
|
260
|
+
line: null,
|
|
261
|
+
timestamp: Date.now(),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Handle skill errors.
|
|
267
|
+
*/
|
|
268
|
+
function handleSkillError(err, projectRoot) {
|
|
269
|
+
console.warn(`[CLAW] Skill error (non-fatal): ${err.message}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Report to wolverine parent process via IPC.
|
|
274
|
+
*/
|
|
275
|
+
function reportToWolverine(type, data) {
|
|
276
|
+
if (typeof process.send === "function") {
|
|
277
|
+
try {
|
|
278
|
+
process.send({ type, ...data });
|
|
279
|
+
} catch {}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
module.exports = { register, PLUGIN_NAME };
|
|
File without changes
|
|
File without changes
|