AI Newsletter Digest improvements: fixed QP soft line break decoding, URL extraction, and content cleaning
This commit is contained in:
297
skills/openclaw-self-healing/scripts/level2-weekly-analysis.js
Normal file
297
skills/openclaw-self-healing/scripts/level2-weekly-analysis.js
Normal file
@@ -0,0 +1,297 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Level 2: Weekly Analysis (Semi-Automatic)
|
||||
*
|
||||
* 주간 로그 분석 + 파라미터 조정 제안 생성
|
||||
* - 자동 실행 (매주 일요일 23:00)
|
||||
* - Discord로 제안 전송
|
||||
* - 수동 승인 대기 (자동 적용 안 함)
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { LogAnalyzer } = require('../lib/log-analyzer');
|
||||
const { ParameterOptimizer } = require('../lib/parameter-optimizer');
|
||||
|
||||
// ============================================================================
|
||||
// Configuration
|
||||
// ============================================================================
|
||||
|
||||
const CONFIG = {
|
||||
logPath: path.join(process.env.HOME, 'openclaw/logs/auto-retry.jsonl'),
|
||||
outputDir: path.join(process.env.HOME, 'openclaw/logs/level2'),
|
||||
discordWebhook: process.env.DISCORD_WEBHOOK_URL || process.env.OPENCLAW_DISCORD_WEBHOOK,
|
||||
timeWindow: 7 * 24 * 3600 * 1000 // 7 days
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Discord Notification
|
||||
// ============================================================================
|
||||
|
||||
async function sendDiscordReport(analysis, recommendations) {
|
||||
if (!CONFIG.discordWebhook) {
|
||||
console.log('⚠️ No Discord webhook configured, skipping notification');
|
||||
return;
|
||||
}
|
||||
|
||||
const https = require('https');
|
||||
const url = new URL(CONFIG.discordWebhook);
|
||||
|
||||
// Build Discord embed
|
||||
const embed = {
|
||||
title: '📊 Level 2: Weekly Auto-Retry Analysis',
|
||||
description: `Analysis period: ${Math.round(CONFIG.timeWindow / (24 * 3600 * 1000))} days`,
|
||||
color: recommendations.length > 0 ? 0xFFA500 : 0x00FF00, // Orange if recs, green if none
|
||||
fields: [],
|
||||
footer: { text: 'Level 2 Parameter Tuning (Semi-Automatic)' },
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Overall summary
|
||||
embed.fields.push({
|
||||
name: '📈 Overall Summary',
|
||||
value: [
|
||||
`Total Executions: **${analysis.summary.overall.totalExecutions}**`,
|
||||
`Success Rate: **${analysis.summary.overall.successRate}**`,
|
||||
`Retry Rate: **${analysis.summary.overall.retryRate}**`,
|
||||
`Failure Rate: **${analysis.summary.overall.failureRate}**`,
|
||||
`Avg Duration: **${analysis.summary.overall.avgDuration}**`
|
||||
].join('\n'),
|
||||
inline: false
|
||||
});
|
||||
|
||||
// Recommendations
|
||||
if (recommendations.length > 0) {
|
||||
const recText = recommendations.slice(0, 3).map((rec, i) => {
|
||||
const icon = rec.severity === 'high' ? '🔴' : rec.severity === 'medium' ? '🟡' : '🟢';
|
||||
const safeIcon = rec.safe ? '✅' : '⚠️';
|
||||
return [
|
||||
`${i + 1}. ${icon} **${rec.cron}**`,
|
||||
` ${safeIcon} \`${rec.param}\`: ${rec.current} → **${rec.proposed}**`,
|
||||
` 📝 ${rec.reason}`,
|
||||
` 💡 ${rec.expectedImprovement}`,
|
||||
` 🎯 Confidence: ${rec.confidence}`
|
||||
].join('\n');
|
||||
}).join('\n\n');
|
||||
|
||||
embed.fields.push({
|
||||
name: `💡 Recommendations (${recommendations.length} total)`,
|
||||
value: recText + (recommendations.length > 3 ? `\n\n_...and ${recommendations.length - 3} more_` : ''),
|
||||
inline: false
|
||||
});
|
||||
|
||||
// How to apply
|
||||
embed.fields.push({
|
||||
name: '🔧 How to Apply',
|
||||
value: [
|
||||
'```bash',
|
||||
'# Review recommendations',
|
||||
'cat ~/openclaw/logs/level2/recommendations-latest.json',
|
||||
'',
|
||||
'# Apply a specific recommendation',
|
||||
'node ~/openclaw/scripts/apply-recommendation.js --id=0',
|
||||
'```'
|
||||
].join('\n'),
|
||||
inline: false
|
||||
});
|
||||
} else {
|
||||
embed.fields.push({
|
||||
name: '✅ Status',
|
||||
value: 'No optimization needed - all metrics within normal range',
|
||||
inline: false
|
||||
});
|
||||
}
|
||||
|
||||
// Top errors (if any)
|
||||
if (analysis.summary.topErrors.length > 0) {
|
||||
const errorsText = analysis.summary.topErrors.slice(0, 3).map(e =>
|
||||
`- **${e.category}** (${e.count}x): ${e.topType}`
|
||||
).join('\n');
|
||||
|
||||
embed.fields.push({
|
||||
name: '🚨 Top Errors',
|
||||
value: errorsText,
|
||||
inline: false
|
||||
});
|
||||
}
|
||||
|
||||
// Send to Discord
|
||||
const message = { embeds: [embed] };
|
||||
const data = JSON.stringify(message);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
hostname: url.hostname,
|
||||
path: url.pathname,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': data.length
|
||||
}
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
if (res.statusCode === 204) {
|
||||
console.log('✅ Discord notification sent');
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Discord returned ${res.statusCode}`));
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
req.write(data);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Main Analysis
|
||||
// ============================================================================
|
||||
|
||||
async function main() {
|
||||
console.log('╔═══════════════════════════════════════════════════════════╗');
|
||||
console.log('║ 📊 Level 2: Weekly Auto-Retry Analysis ║');
|
||||
console.log('╚═══════════════════════════════════════════════════════════╝\n');
|
||||
|
||||
// 1. Ensure output directory exists
|
||||
if (!fs.existsSync(CONFIG.outputDir)) {
|
||||
fs.mkdirSync(CONFIG.outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 2. Analyze logs
|
||||
console.log('📖 Reading logs...');
|
||||
const analyzer = new LogAnalyzer({
|
||||
timeWindow: CONFIG.timeWindow,
|
||||
minSampleSize: 5 // Low threshold for pattern detection, actual tuning has higher threshold
|
||||
});
|
||||
|
||||
const analysis = await analyzer.analyze(CONFIG.logPath);
|
||||
console.log(` ✅ Analyzed ${analysis.metadata.analyzedEntries} entries\n`);
|
||||
|
||||
// 3. Print summary
|
||||
analyzer.printResults(analysis);
|
||||
|
||||
// 4. Generate recommendations
|
||||
console.log('\n💡 Generating recommendations...');
|
||||
const optimizer = new ParameterOptimizer({
|
||||
aggressiveness: 'conservative',
|
||||
requireStatisticalSignificance: true
|
||||
});
|
||||
|
||||
const recommendations = optimizer.generateRecommendations(
|
||||
analysis.patterns,
|
||||
analysis.stats,
|
||||
analysis.trends
|
||||
);
|
||||
|
||||
console.log(` ✅ Generated ${recommendations.length} recommendation(s)\n`);
|
||||
|
||||
// 5. Print recommendations
|
||||
if (recommendations.length > 0) {
|
||||
console.log('═'.repeat(60));
|
||||
console.log('💡 RECOMMENDATIONS');
|
||||
console.log('═'.repeat(60) + '\n');
|
||||
|
||||
recommendations.forEach((rec, i) => {
|
||||
const icon = rec.severity === 'high' ? '🔴' : rec.severity === 'medium' ? '🟡' : '🟢';
|
||||
const safeIcon = rec.safe ? '✅ SAFE' : '⚠️ REVIEW REQUIRED';
|
||||
|
||||
console.log(`${i}. ${icon} ${rec.cron}`);
|
||||
console.log(` Parameter: ${rec.param}`);
|
||||
console.log(` Current: ${rec.current}`);
|
||||
console.log(` Proposed: ${rec.proposed}`);
|
||||
console.log(` Reason: ${rec.reason}`);
|
||||
console.log(` Expected: ${rec.expectedImprovement}`);
|
||||
console.log(` Severity: ${rec.severity}`);
|
||||
console.log(` Confidence: ${rec.confidence}`);
|
||||
console.log(` Safety: ${safeIcon}`);
|
||||
if (rec.warning) {
|
||||
console.log(` ⚠️ Warning: ${rec.warning}`);
|
||||
}
|
||||
if (rec.recommendation) {
|
||||
console.log(` 📝 Note: ${rec.recommendation}`);
|
||||
}
|
||||
console.log('');
|
||||
});
|
||||
|
||||
console.log('═'.repeat(60));
|
||||
console.log('🔧 TO APPLY:');
|
||||
console.log('═'.repeat(60));
|
||||
console.log('');
|
||||
console.log('# Review the recommendations');
|
||||
console.log('cat ~/openclaw/logs/level2/recommendations-latest.json');
|
||||
console.log('');
|
||||
console.log('# Apply a specific recommendation (by index)');
|
||||
console.log('node ~/openclaw/scripts/apply-recommendation.js --id=0');
|
||||
console.log('');
|
||||
console.log('# Apply all safe recommendations');
|
||||
console.log('node ~/openclaw/scripts/apply-recommendation.js --all-safe');
|
||||
console.log('');
|
||||
} else {
|
||||
console.log('✅ No recommendations - all metrics within normal range\n');
|
||||
}
|
||||
|
||||
// 6. Save results
|
||||
const timestamp = new Date().toISOString().replace(/:/g, '-').split('.')[0];
|
||||
const outputPath = path.join(CONFIG.outputDir, `recommendations-${timestamp}.json`);
|
||||
const latestPath = path.join(CONFIG.outputDir, 'recommendations-latest.json');
|
||||
|
||||
const output = {
|
||||
timestamp: new Date().toISOString(),
|
||||
analysis: {
|
||||
summary: analysis.summary,
|
||||
patterns: analysis.patterns,
|
||||
trends: analysis.trends,
|
||||
metadata: analysis.metadata
|
||||
},
|
||||
recommendations,
|
||||
config: {
|
||||
timeWindow: CONFIG.timeWindow,
|
||||
aggressiveness: 'conservative'
|
||||
}
|
||||
};
|
||||
|
||||
fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
|
||||
fs.writeFileSync(latestPath, JSON.stringify(output, null, 2));
|
||||
|
||||
console.log(`📁 Saved to: ${outputPath}`);
|
||||
console.log(`📁 Latest: ${latestPath}\n`);
|
||||
|
||||
// 7. Send Discord notification
|
||||
try {
|
||||
await sendDiscordReport(analysis, recommendations);
|
||||
} catch (error) {
|
||||
console.error('❌ Discord notification failed:', error.message);
|
||||
}
|
||||
|
||||
// 8. Summary
|
||||
console.log('╔═══════════════════════════════════════════════════════════╗');
|
||||
console.log('║ ✅ Weekly Analysis Complete ║');
|
||||
console.log('╚═══════════════════════════════════════════════════════════╝');
|
||||
console.log('');
|
||||
console.log('Next steps:');
|
||||
if (recommendations.length > 0) {
|
||||
console.log(' 1. Review recommendations in Discord or log file');
|
||||
console.log(' 2. Apply selected recommendations manually');
|
||||
console.log(' 3. Monitor results for 24-48 hours');
|
||||
console.log(' 4. Rollback if needed');
|
||||
} else {
|
||||
console.log(' - Continue monitoring (no action needed)');
|
||||
}
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Run
|
||||
// ============================================================================
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch(err => {
|
||||
console.error('❌ Error:', err.message);
|
||||
console.error(err.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { main };
|
||||
Reference in New Issue
Block a user