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
|
1 month ago
|
#!/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;
|