You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
117 lines
3.3 KiB
117 lines
3.3 KiB
#!/usr/bin/env node |
|
|
|
// Agent Health Monitor for OpenClaw |
|
// Monitors agent crashes, errors, and service health |
|
// Sends notifications via configured channels (Telegram, etc.) |
|
|
|
const fs = require('fs'); |
|
const path = require('path'); |
|
|
|
class AgentHealthMonitor { |
|
constructor() { |
|
this.config = this.loadConfig(); |
|
this.logDir = '/root/.openclaw/workspace/logs/agents'; |
|
this.ensureLogDir(); |
|
} |
|
|
|
loadConfig() { |
|
try { |
|
const configPath = '/root/.openclaw/openclaw.json'; |
|
return JSON.parse(fs.readFileSync(configPath, 'utf8')); |
|
} catch (error) { |
|
console.error('Failed to load OpenClaw config:', error); |
|
return {}; |
|
} |
|
} |
|
|
|
ensureLogDir() { |
|
if (!fs.existsSync(this.logDir)) { |
|
fs.mkdirSync(this.logDir, { recursive: true }); |
|
} |
|
} |
|
|
|
async sendNotification(message, severity = 'error') { |
|
// Log to file first |
|
const timestamp = new Date().toISOString(); |
|
const logEntry = `[${timestamp}] [${severity.toUpperCase()}] ${message}\n`; |
|
|
|
const logFile = path.join(this.logDir, `health-${new Date().toISOString().split('T')[0]}.log`); |
|
fs.appendFileSync(logFile, logEntry); |
|
|
|
// Send via Telegram if configured |
|
if (this.config.channels?.telegram?.enabled) { |
|
await this.sendTelegramNotification(message, severity); |
|
} |
|
} |
|
|
|
async sendTelegramNotification(message, severity) { |
|
const botToken = this.config.channels.telegram.botToken; |
|
const chatId = '5237946060'; // Your Telegram ID |
|
|
|
if (!botToken) { |
|
console.error('Telegram bot token not configured'); |
|
return; |
|
} |
|
|
|
try { |
|
const url = `https://api.telegram.org/bot${botToken}/sendMessage`; |
|
const payload = { |
|
chat_id: chatId, |
|
text: `🚨 OpenClaw Agent Alert (${severity})\n\n${message}`, |
|
parse_mode: 'Markdown' |
|
}; |
|
|
|
const response = await fetch(url, { |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json' }, |
|
body: JSON.stringify(payload) |
|
}); |
|
|
|
if (!response.ok) { |
|
console.error('Failed to send Telegram notification:', await response.text()); |
|
} |
|
} catch (error) { |
|
console.error('Telegram notification error:', error); |
|
} |
|
} |
|
|
|
monitorProcess(processName, checkFunction) { |
|
// Set up process monitoring |
|
process.on('uncaughtException', async (error) => { |
|
await this.sendNotification( |
|
`Uncaught exception in ${processName}:\n${error.stack || error.message}`, |
|
'critical' |
|
); |
|
process.exit(1); |
|
}); |
|
|
|
process.on('unhandledRejection', async (reason, promise) => { |
|
await this.sendNotification( |
|
`Unhandled rejection in ${processName}:\nReason: ${reason}\nPromise: ${promise}`, |
|
'error' |
|
); |
|
}); |
|
|
|
// Custom health check |
|
if (checkFunction) { |
|
setInterval(async () => { |
|
try { |
|
const isHealthy = await checkFunction(); |
|
if (!isHealthy) { |
|
await this.sendNotification( |
|
`${processName} health check failed`, |
|
'warning' |
|
); |
|
} |
|
} catch (error) { |
|
await this.sendNotification( |
|
`${processName} health check error: ${error.message}`, |
|
'error' |
|
); |
|
} |
|
}, 30000); // Check every 30 seconds |
|
} |
|
} |
|
} |
|
|
|
module.exports = AgentHealthMonitor; |