guanghulab/scripts/zhuyuan-inspection.js

247 lines
8.4 KiB
JavaScript
Raw Normal View History

/**
* scripts/zhuyuan-inspection.js
* 铸渊每日巡检脚本
*
* 逻辑
* 1. 读取当日 checkin-board.json
* 2. 找出所有 status: 未签到 的小兵
* 3. 对每个未签到小兵
* - 查看 workflow 最近 3 run 的失败原因
* - 判断 self_repair: true/false
* - 能自修复 尝试修复重新触发 workflow 写入修复日志
* - 不能自修复 写入需要之之干预清单
* 4. 生成巡检报告
*/
'use strict';
const fs = require('fs');
const path = require('path');
const https = require('https');
const ROOT = path.resolve(__dirname, '..');
const BRAIN_DIR = path.join(ROOT, '.github/persona-brain');
const BOARD_PATH = path.join(BRAIN_DIR, 'checkin-board.json');
const REGISTRY_PATH = path.join(BRAIN_DIR, 'agent-registry.json');
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
const REPO = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab';
const today = new Date().toISOString().split('T')[0];
console.log(`🔍 铸渊每日巡检开始 · ${today}`);
// ── GitHub API helper ─────────────────────────────────────────────────────
function githubApi(urlPath, method = 'GET') {
return new Promise((resolve, reject) => {
const options = {
hostname: 'api.github.com',
path: urlPath,
method: method,
headers: {
'User-Agent': 'zhuyuan-inspection',
'Accept': 'application/vnd.github.v3+json',
},
};
if (GITHUB_TOKEN) {
options.headers['Authorization'] = `Bearer ${GITHUB_TOKEN}`;
}
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
resolve({ statusCode: res.statusCode, data: JSON.parse(data) });
} catch {
resolve({ statusCode: res.statusCode, data: null });
}
});
});
req.on('error', (err) => {
console.error(` ⚠️ API 请求失败: ${err.message}`);
resolve({ statusCode: 0, data: null });
});
req.end();
});
}
// ── 获取最近 N 次 run ────────────────────────────────────────────────────
async function getRecentRuns(workflowFile, count = 3) {
const urlPath = `/repos/${REPO}/actions/workflows/${encodeURIComponent(workflowFile)}/runs?per_page=${count}`;
const result = await githubApi(urlPath);
if (!result.data || !result.data.workflow_runs) return [];
return result.data.workflow_runs.map(run => ({
id: run.id,
status: run.status,
conclusion: run.conclusion,
created_at: run.created_at,
html_url: run.html_url,
}));
}
// ── 重新触发 workflow ─────────────────────────────────────────────────────
async function retriggerWorkflow(workflowFile) {
const urlPath = `/repos/${REPO}/actions/workflows/${encodeURIComponent(workflowFile)}/dispatches`;
const postData = JSON.stringify({ ref: 'main' });
return new Promise((resolve) => {
const options = {
hostname: 'api.github.com',
path: urlPath,
method: 'POST',
headers: {
'User-Agent': 'zhuyuan-inspection',
'Accept': 'application/vnd.github.v3+json',
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData),
},
};
if (GITHUB_TOKEN) {
options.headers['Authorization'] = `Bearer ${GITHUB_TOKEN}`;
}
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
resolve(res.statusCode === 204);
});
});
req.on('error', () => resolve(false));
req.write(postData);
req.end();
});
}
// ── 主巡检流程 ────────────────────────────────────────────────────────────
async function main() {
// 1. 读取签到板和注册表
if (!fs.existsSync(BOARD_PATH)) {
console.error('❌ checkin-board.json 不存在');
process.exit(1);
}
if (!fs.existsSync(REGISTRY_PATH)) {
console.error('❌ agent-registry.json 不存在');
process.exit(1);
}
const board = JSON.parse(fs.readFileSync(BOARD_PATH, 'utf8'));
const registry = JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf8'));
// 建立 agent_id → registry entry 的映射
const registryMap = {};
for (const agent of registry.agents) {
registryMap[agent.id] = agent;
}
// 2. 找出未签到小兵
const missingRecords = board.records.filter(r => r.status === '❌ 未签到');
const checkedInRecords = board.records.filter(r => r.status === '✅ 已签到');
console.log(`\n📊 签到情况: 已签到 ${checkedInRecords.length} / 未签到 ${missingRecords.length} / 总计 ${board.records.length}`);
const autoRepaired = [];
const interventionRequired = [];
// 3. 对每个未签到小兵进行诊断
for (const record of missingRecords) {
const agent = registryMap[record.agent_id];
if (!agent) {
console.log(` ⚠️ ${record.agent_id} 未在注册表中找到`);
continue;
}
console.log(`\n 🔍 诊断 ${agent.id} (${agent.workflow})...`);
// 获取最近 3 次运行
const recentRuns = await getRecentRuns(agent.workflow, 3);
const failureReasons = recentRuns
.filter(r => r.conclusion && r.conclusion !== 'success')
.map(r => `${r.conclusion} (${r.created_at})`);
if (failureReasons.length > 0) {
console.log(` 最近失败记录: ${failureReasons.join(', ')}`);
} else if (recentRuns.length === 0) {
console.log(` 无运行记录`);
}
// 判断是否能自修复
if (agent.self_repair) {
console.log(` 🔧 尝试自修复 (重新触发 workflow)...`);
const success = await retriggerWorkflow(agent.workflow);
if (success) {
console.log(` ✅ 已重新触发 workflow`);
autoRepaired.push({
agent_id: agent.id,
agent_name: agent.name,
workflow: agent.workflow,
action: '重新触发 workflow_dispatch',
result: '已触发',
});
} else {
console.log(` ⚠️ 重新触发失败(可能不支持 workflow_dispatch`);
interventionRequired.push({
agent_id: agent.id,
agent_name: agent.name,
workflow: agent.workflow,
reason: '自修复失败workflow_dispatch 触发失败',
recent_failures: failureReasons,
});
}
} else {
console.log(` 📌 self_repair=false, 需要之之干预`);
interventionRequired.push({
agent_id: agent.id,
agent_name: agent.name,
workflow: agent.workflow,
reason: '不支持自修复',
recent_failures: failureReasons,
});
}
}
// 4. 生成巡检报告
const report = {
title: `[铸渊巡检] ${today} 每日巡检报告`,
type: '巡检报告',
date: today,
checked_in: checkedInRecords.map(r => ({
agent_id: r.agent_id,
agent_name: r.agent_name,
})),
missing_agents: missingRecords.map(r => ({
agent_id: r.agent_id,
agent_name: r.agent_name,
})),
auto_repaired: autoRepaired,
intervention_required: interventionRequired,
summary: {
total: board.records.length,
checked_in: checkedInRecords.length,
missing: missingRecords.length,
auto_repaired: autoRepaired.length,
needs_intervention: interventionRequired.length,
},
push_targets: ['零点原核', '明天见频道'],
};
// 保存巡检报告
const reportPath = path.join(BRAIN_DIR, 'inspection-report.json');
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
console.log(`\n📋 巡检报告已生成:`);
console.log(` 已签到: ${report.summary.checked_in}`);
console.log(` 未签到: ${report.summary.missing}`);
console.log(` 已自修复: ${report.summary.auto_repaired}`);
console.log(` 需要干预: ${report.summary.needs_intervention}`);
console.log(`\n✅ 巡检报告已保存到 inspection-report.json`);
}
main().catch((err) => {
console.error('❌ 巡检脚本异常:', err.message);
process.exit(1);
});