192 lines
5.8 KiB
JavaScript
192 lines
5.8 KiB
JavaScript
// scripts/commander-dashboard.js
|
||
// 铸渊·将军全局仪表盘生成器
|
||
//
|
||
// 功能:
|
||
// ① 拉取所有 Workflow 运行状态
|
||
// ② 读取天眼最新报告
|
||
// ③ 读取公告板状态
|
||
// ④ 生成将军全局仪表盘 JSON
|
||
//
|
||
// 输出:data/bulletin-board/dashboard.json + stdout
|
||
|
||
'use strict';
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const ROOT = path.resolve(__dirname, '..');
|
||
const BRAIN_DIR = path.join(ROOT, '.github/persona-brain');
|
||
const SKYEYE_REPORTS_DIR = path.join(ROOT, 'data/skyeye-reports');
|
||
const BULLETIN_DIR = path.join(ROOT, 'data/bulletin-board');
|
||
const DASHBOARD_PATH = path.join(BULLETIN_DIR, 'dashboard.json');
|
||
|
||
const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
|
||
|
||
// ━━━ 安全读取 JSON ━━━
|
||
function readJSON(filePath) {
|
||
try {
|
||
if (!fs.existsSync(filePath)) return null;
|
||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// ━━━ 获取最新天眼报告 ━━━
|
||
function getLatestSkyeyeReport() {
|
||
try {
|
||
if (!fs.existsSync(SKYEYE_REPORTS_DIR)) return null;
|
||
const files = fs.readdirSync(SKYEYE_REPORTS_DIR)
|
||
.filter(f => f.startsWith('skyeye-') && f.endsWith('.json'))
|
||
.sort()
|
||
.reverse();
|
||
if (files.length === 0) return null;
|
||
return readJSON(path.join(SKYEYE_REPORTS_DIR, files[0]));
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// ━━━ 获取公告板工单状态 ━━━
|
||
function getBulletinBoardStatus() {
|
||
const workOrdersDir = path.join(BULLETIN_DIR, 'work-orders');
|
||
const receiptsDir = path.join(BULLETIN_DIR, 'receipts');
|
||
|
||
let unreadWorkOrders = 0;
|
||
const pendingReceipts = [];
|
||
|
||
try {
|
||
if (fs.existsSync(workOrdersDir)) {
|
||
const orders = fs.readdirSync(workOrdersDir)
|
||
.filter(f => f.endsWith('.json'));
|
||
for (const orderFile of orders) {
|
||
const order = readJSON(path.join(workOrdersDir, orderFile));
|
||
if (order && order.status !== 'completed') {
|
||
unreadWorkOrders++;
|
||
pendingReceipts.push(order.order_id || orderFile.replace('.json', ''));
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
|
||
return {
|
||
unread_work_orders: unreadWorkOrders,
|
||
pending_receipts: pendingReceipts
|
||
};
|
||
}
|
||
|
||
// ━━━ 从天眼报告提取小兵状态 ━━━
|
||
function extractSoldiersFromReport(report) {
|
||
const soldiers = [];
|
||
|
||
if (!report) return { soldiers, total: 0, healthy: 0, failed: 0, needs_optimization: 0 };
|
||
|
||
// 从 workflow_health 提取小兵
|
||
if (report.workflow_health && report.workflow_health.details) {
|
||
for (const wf of report.workflow_health.details) {
|
||
const status = wf.status === 'healthy' ? '✅' :
|
||
wf.status === 'failed' ? '❌' : '⚠️';
|
||
const soldier = {
|
||
name: wf.name || wf.file,
|
||
status,
|
||
last_run: wf.last_run || 'unknown'
|
||
};
|
||
if (status === '❌') {
|
||
soldier.error = wf.error || 'unknown error';
|
||
soldier.fix_plan = 'retry + check logs';
|
||
}
|
||
soldiers.push(soldier);
|
||
}
|
||
}
|
||
|
||
const total = soldiers.length;
|
||
const healthy = soldiers.filter(s => s.status === '✅').length;
|
||
const failed = soldiers.filter(s => s.status === '❌').length;
|
||
const needsOpt = soldiers.filter(s => s.status === '⚠️').length;
|
||
|
||
return { soldiers, total, healthy, failed, needs_optimization: needsOpt };
|
||
}
|
||
|
||
// ━━━ 生成决策摘要 ━━━
|
||
function generateDecision(soldierStats, bulletinStatus, skyeyeReport) {
|
||
const actions = [];
|
||
|
||
if (soldierStats.failed > 0) {
|
||
actions.push(`修复 ${soldierStats.failed} 个故障小兵`);
|
||
}
|
||
if (soldierStats.needs_optimization > 0) {
|
||
actions.push(`优化 ${soldierStats.needs_optimization} 个小兵`);
|
||
}
|
||
if (bulletinStatus.unread_work_orders > 0) {
|
||
actions.push(`执行 ${bulletinStatus.unread_work_orders} 个未完成工单`);
|
||
}
|
||
if (skyeyeReport && skyeyeReport.diagnosis && skyeyeReport.diagnosis.needs_human > 0) {
|
||
actions.push(`${skyeyeReport.diagnosis.needs_human} 个问题需人工处理`);
|
||
}
|
||
|
||
if (actions.length === 0) {
|
||
return '全局健康 · 无需干预 · 继续巡航';
|
||
}
|
||
return actions.join(' → ');
|
||
}
|
||
|
||
// ━━━ 主函数 ━━━
|
||
function generateDashboard() {
|
||
const now = new Date();
|
||
const bjTime = new Date(now.getTime() + BEIJING_OFFSET_MS).toISOString()
|
||
.replace('T', ' ').slice(0, 19) + '+08:00';
|
||
|
||
// 读取天眼最新报告
|
||
const skyeyeReport = getLatestSkyeyeReport();
|
||
|
||
// 提取小兵状态
|
||
const soldierStats = extractSoldiersFromReport(skyeyeReport);
|
||
|
||
// 读取公告板状态
|
||
const bulletinStatus = getBulletinBoardStatus();
|
||
|
||
// 生成决策
|
||
const decision = generateDecision(soldierStats, bulletinStatus, skyeyeReport);
|
||
|
||
const dashboard = {
|
||
commander: '铸渊',
|
||
timestamp: bjTime,
|
||
global_view: {
|
||
total_soldiers: soldierStats.total,
|
||
healthy: soldierStats.healthy,
|
||
failed: soldierStats.failed,
|
||
needs_optimization: soldierStats.needs_optimization,
|
||
soldiers: soldierStats.soldiers
|
||
},
|
||
bulletin_board_status: bulletinStatus,
|
||
skyeye_latest: skyeyeReport ? {
|
||
overall_health: skyeyeReport.overall_health || '❓',
|
||
report_id: skyeyeReport.report_id || null,
|
||
issues: skyeyeReport.diagnosis ? skyeyeReport.diagnosis.total_issues : 0,
|
||
auto_fixed: skyeyeReport.diagnosis ? skyeyeReport.diagnosis.auto_fixed : 0
|
||
} : {
|
||
overall_health: '❓',
|
||
report_id: null,
|
||
issues: 0,
|
||
auto_fixed: 0
|
||
},
|
||
decision
|
||
};
|
||
|
||
// 保存仪表盘
|
||
fs.mkdirSync(BULLETIN_DIR, { recursive: true });
|
||
fs.writeFileSync(DASHBOARD_PATH, JSON.stringify(dashboard, null, 2));
|
||
|
||
console.log(JSON.stringify(dashboard, null, 2));
|
||
console.log(`\n📊 将军全局仪表盘已生成 · ${bjTime}`);
|
||
}
|
||
|
||
// 允许作为模块导入或直接运行
|
||
if (require.main === module) {
|
||
generateDashboard();
|
||
}
|
||
|
||
module.exports = { generateDashboard };
|