wolverine-ai 6.1.2 → 6.2.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.
@@ -0,0 +1,115 @@
1
+ {
2
+ "_": "Wolverine Claw Configuration — OpenClaw agentic agent powered by Wolverine self-healing. Docs: https://github.com/bobbyswhip/Wolverine",
3
+
4
+ "_gateway": "OpenClaw gateway settings. port: WebSocket control plane port. host: bind address (127.0.0.1 = local only, 0.0.0.0 = all interfaces).",
5
+ "gateway": {
6
+ "port": 18789,
7
+ "host": "127.0.0.1"
8
+ },
9
+
10
+ "_agent": "Pi agent runtime settings. model: default LLM for agent tasks. maxTurns: max tool-use iterations per request. timeoutMs: max time per agent call.",
11
+ "agent": {
12
+ "model": "claude-sonnet-4-6",
13
+ "maxTurns": 25,
14
+ "timeoutMs": 120000
15
+ },
16
+
17
+ "_models": "AI models for different claw tasks. Provider auto-detected from name (claude-* = Anthropic, gpt-* = OpenAI).",
18
+ "models": {
19
+ "reasoning": "claude-sonnet-4-6",
20
+ "coding": "claude-sonnet-4-6",
21
+ "chat": "claude-sonnet-4-6",
22
+ "embedding": "text-embedding-3-small"
23
+ },
24
+
25
+ "_channels": "Messaging channel integrations. Enable channels by adding their config. Each channel needs its own API keys in .env.local.",
26
+ "channels": {
27
+ "terminal": {
28
+ "enabled": true
29
+ },
30
+ "discord": {
31
+ "enabled": false,
32
+ "token": "",
33
+ "allowedChannels": []
34
+ },
35
+ "slack": {
36
+ "enabled": false,
37
+ "botToken": "",
38
+ "appToken": "",
39
+ "allowedChannels": []
40
+ },
41
+ "telegram": {
42
+ "enabled": false,
43
+ "botToken": "",
44
+ "allowedUsers": []
45
+ },
46
+ "whatsapp": {
47
+ "enabled": false
48
+ }
49
+ },
50
+
51
+ "_healing": "Wolverine self-healing settings for the claw process. Same pipeline as server healing.",
52
+ "healing": {
53
+ "enabled": true,
54
+ "healTimeoutMs": 300000,
55
+ "maxHealsPerWindow": 5,
56
+ "windowMs": 300000,
57
+ "loopMaxAttempts": 3,
58
+ "loopWindowMs": 600000
59
+ },
60
+
61
+ "_skills": "OpenClaw skills configuration. Enable/disable built-in and custom skills.",
62
+ "skills": {
63
+ "codingAgent": {
64
+ "enabled": true,
65
+ "defaultAgent": "pi",
66
+ "sandbox": true,
67
+ "allowedPaths": ["wolverine-claw/workspace/"]
68
+ },
69
+ "browserControl": {
70
+ "enabled": false
71
+ },
72
+ "cron": {
73
+ "enabled": true,
74
+ "maxJobs": 10
75
+ },
76
+ "canvas": {
77
+ "enabled": false
78
+ }
79
+ },
80
+
81
+ "_workspace": "Working directory for the claw agent. Files created/modified by the agent go here. Protected from wolverine auto-update.",
82
+ "workspace": {
83
+ "path": "wolverine-claw/workspace",
84
+ "maxFileSizeMB": 50,
85
+ "allowedExtensions": ["*"]
86
+ },
87
+
88
+ "_security": "Security settings for the claw agent. dmPairing: require pairing for unknown senders. sandbox: restrict agent file access.",
89
+ "security": {
90
+ "dmPairing": true,
91
+ "sandbox": true,
92
+ "blockedCommands": ["rm -rf /", "format", "shutdown", "reboot"],
93
+ "maxConcurrentSessions": 5
94
+ },
95
+
96
+ "_logging": "Logging configuration.",
97
+ "logging": {
98
+ "level": "info",
99
+ "logFile": ".wolverine/claw.log",
100
+ "maxLogSizeMB": 50
101
+ },
102
+
103
+ "_remoteAccess": "Remote access via Tailscale or SSH tunnel. Disabled by default for security.",
104
+ "remoteAccess": {
105
+ "enabled": false,
106
+ "method": "tailscale"
107
+ },
108
+
109
+ "_backup": "Backup settings for claw workspace and config.",
110
+ "backup": {
111
+ "enabled": true,
112
+ "stabilityMs": 1800000,
113
+ "retentionDays": 7
114
+ }
115
+ }
@@ -0,0 +1,297 @@
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, falling back to CLI mode...");
99
+ await startClawCLI(gatewayPort, gatewayHost);
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: spawn openclaw gateway via CLI (npx openclaw gateway).
111
+ */
112
+ async function startClawCLI(port, host) {
113
+ const { spawn } = require("child_process");
114
+
115
+ // Build the config file for openclaw CLI
116
+ const clawConfigPath = path.join(__dirname, ".claw-runtime-config.yml");
117
+ const yamlConfig = buildClawYaml(config, port, host);
118
+ fs.writeFileSync(clawConfigPath, yamlConfig);
119
+
120
+ console.log(`[CLAW] Spawning: npx openclaw gateway --port ${port}`);
121
+
122
+ const child = spawn("npx", ["openclaw", "gateway", "--port", String(port)], {
123
+ cwd: path.resolve(__dirname, ".."),
124
+ stdio: ["inherit", "inherit", "pipe"],
125
+ env: {
126
+ ...process.env,
127
+ OPENCLAW_CONFIG: clawConfigPath,
128
+ OPENCLAW_GATEWAY_PORT: String(port),
129
+ OPENCLAW_GATEWAY_HOST: host,
130
+ },
131
+ shell: true,
132
+ });
133
+
134
+ let stderrBuffer = "";
135
+ child.stderr.on("data", (chunk) => {
136
+ const text = chunk.toString();
137
+ stderrBuffer += text;
138
+ process.stderr.write(chunk);
139
+
140
+ // Report errors to wolverine parent
141
+ if (text.includes("Error") || text.includes("FATAL")) {
142
+ reportError(text, stderrBuffer);
143
+ }
144
+ });
145
+
146
+ child.on("exit", (code, signal) => {
147
+ if (!shutdownCalled) {
148
+ console.error(`[CLAW] Gateway exited unexpectedly (code=${code}, signal=${signal})`);
149
+ reportHealth("crashed", `exit code ${code}`);
150
+ process.exit(code || 1);
151
+ }
152
+ });
153
+
154
+ gateway = child;
155
+ reportHealth("running");
156
+ }
157
+
158
+ /**
159
+ * Build channel config from settings, filtering to enabled channels only.
160
+ */
161
+ function buildChannelConfig(channels) {
162
+ if (!channels) return {};
163
+ const result = {};
164
+ for (const [name, cfg] of Object.entries(channels)) {
165
+ if (name.startsWith("_")) continue;
166
+ if (cfg.enabled) {
167
+ result[name] = { ...cfg };
168
+ delete result[name].enabled;
169
+ }
170
+ }
171
+ return result;
172
+ }
173
+
174
+ /**
175
+ * Build a YAML config string for openclaw CLI fallback.
176
+ */
177
+ function buildClawYaml(cfg, port, host) {
178
+ const lines = [
179
+ `# Auto-generated by Wolverine Claw — do not edit manually`,
180
+ `gateway:`,
181
+ ` port: ${port}`,
182
+ ` host: "${host}"`,
183
+ ``,
184
+ ];
185
+
186
+ if (cfg.agent?.model) {
187
+ lines.push(`agent:`);
188
+ lines.push(` model: "${cfg.agent.model}"`);
189
+ lines.push(` maxTurns: ${cfg.agent.maxTurns || 25}`);
190
+ lines.push(``);
191
+ }
192
+
193
+ // Add enabled channels
194
+ const channels = cfg.channels || {};
195
+ const enabledChannels = Object.entries(channels).filter(([k, v]) => !k.startsWith("_") && v.enabled);
196
+ if (enabledChannels.length > 0) {
197
+ lines.push(`channels:`);
198
+ for (const [name, chanCfg] of enabledChannels) {
199
+ lines.push(` ${name}:`);
200
+ for (const [key, val] of Object.entries(chanCfg)) {
201
+ if (key === "enabled") continue;
202
+ if (typeof val === "string") lines.push(` ${key}: "${val}"`);
203
+ else if (Array.isArray(val)) lines.push(` ${key}: [${val.map(v => `"${v}"`).join(", ")}]`);
204
+ else lines.push(` ${key}: ${val}`);
205
+ }
206
+ }
207
+ lines.push(``);
208
+ }
209
+
210
+ return lines.join("\n");
211
+ }
212
+
213
+ /**
214
+ * Register wolverine self-healing integration as an OpenClaw plugin.
215
+ */
216
+ async function registerWolverinePlugin(gw) {
217
+ try {
218
+ const pluginPath = path.join(__dirname, "plugins", "wolverine-integration.js");
219
+ if (fs.existsSync(pluginPath)) {
220
+ const plugin = require(pluginPath);
221
+ if (typeof plugin.register === "function") {
222
+ await plugin.register(gw, config);
223
+ console.log("[CLAW] Wolverine integration plugin loaded");
224
+ }
225
+ }
226
+ } catch (err) {
227
+ console.warn(`[CLAW] Plugin load warning: ${err.message}`);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Report health status to wolverine parent process via IPC.
233
+ */
234
+ function reportHealth(status, detail) {
235
+ if (typeof process.send === "function") {
236
+ try {
237
+ process.send({
238
+ type: "claw_health",
239
+ status,
240
+ detail: detail || null,
241
+ timestamp: Date.now(),
242
+ gateway: {
243
+ port: config.gateway?.port || 18789,
244
+ host: config.gateway?.host || "127.0.0.1",
245
+ },
246
+ });
247
+ } catch {}
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Report errors to wolverine parent process via IPC for healing.
253
+ */
254
+ function reportError(message, stderr) {
255
+ if (typeof process.send === "function") {
256
+ try {
257
+ process.send({
258
+ type: "route_error",
259
+ path: "claw://gateway",
260
+ method: "INTERNAL",
261
+ statusCode: 500,
262
+ message: message.trim().slice(0, 500),
263
+ stack: stderr.slice(-2000),
264
+ file: null,
265
+ line: null,
266
+ timestamp: Date.now(),
267
+ });
268
+ } catch {}
269
+ }
270
+ }
271
+
272
+ // Graceful shutdown
273
+ function shutdown(signal) {
274
+ if (shutdownCalled) return;
275
+ shutdownCalled = true;
276
+ console.log(`\n[CLAW] Shutting down (${signal})...`);
277
+
278
+ if (gateway) {
279
+ if (typeof gateway.stop === "function") {
280
+ gateway.stop();
281
+ } else if (typeof gateway.kill === "function") {
282
+ gateway.kill("SIGTERM");
283
+ }
284
+ }
285
+
286
+ reportHealth("stopped");
287
+ setTimeout(() => process.exit(0), 2000);
288
+ }
289
+
290
+ process.on("SIGINT", () => shutdown("SIGINT"));
291
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
292
+
293
+ // Start
294
+ start().catch((err) => {
295
+ console.error(`[CLAW] Fatal: ${err.message}`);
296
+ process.exit(1);
297
+ });
@@ -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