265 lines
9.7 KiB
JavaScript
265 lines
9.7 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* AOAC-05 · 首页主控Agent (README Master Agent)
|
|||
|
|
* ═══════════════════════════════════════════════
|
|||
|
|
* AGE OS Agent Chain · 全Agent(事件+时间双触发)
|
|||
|
|
*
|
|||
|
|
* 唤醒条件1:事件触发 — AOAC-04写入readme-update-payload.json
|
|||
|
|
* 唤醒条件2:时间触发 — 每天23:00 CST
|
|||
|
|
* 职责:回看仓库首页→分析进度→更新README.md AOAC区域
|
|||
|
|
* 输出:更新README.md + data/aoac/master-report.json
|
|||
|
|
*
|
|||
|
|
* 版权:国作登字-2026-A-00037559 · TCS-0002∞
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
const { execFileSync } = require('child_process');
|
|||
|
|
|
|||
|
|
const ROOT = path.resolve(__dirname, '..', '..');
|
|||
|
|
const README_PATH = path.join(ROOT, 'README.md');
|
|||
|
|
const PAYLOAD_PATH = path.join(ROOT, 'data', 'aoac', 'readme-update-payload.json');
|
|||
|
|
const CHAIN_REPORT_PATH = path.join(ROOT, 'data', 'aoac', 'chain-report.json');
|
|||
|
|
const CHAIN_STATUS_PATH = path.join(ROOT, 'data', 'aoac', 'chain-status.json');
|
|||
|
|
const MASTER_REPORT_PATH = path.join(ROOT, 'data', 'aoac', 'master-report.json');
|
|||
|
|
const DASHBOARD_PATH = path.join(ROOT, 'data', 'bulletin-board', 'dashboard.json');
|
|||
|
|
const MEMORY_PATH = path.join(ROOT, '.github', 'persona-brain', 'memory.json');
|
|||
|
|
const HISTORY_DIR = path.join(ROOT, 'data', 'aoac', 'history');
|
|||
|
|
const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
|
|||
|
|
|
|||
|
|
const AOAC_START_MARKER = '<!-- AOAC_STATUS_START -->';
|
|||
|
|
const AOAC_END_MARKER = '<!-- AOAC_STATUS_END -->';
|
|||
|
|
|
|||
|
|
function getBeijingTime() {
|
|||
|
|
const now = new Date();
|
|||
|
|
return new Date(now.getTime() + BEIJING_OFFSET_MS)
|
|||
|
|
.toISOString().replace('T', ' ').slice(0, 19) + '+08:00';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function getDateStr() {
|
|||
|
|
const now = new Date();
|
|||
|
|
return new Date(now.getTime() + BEIJING_OFFSET_MS).toISOString().slice(0, 10);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function readJSON(filePath) {
|
|||
|
|
try {
|
|||
|
|
if (!fs.existsSync(filePath)) return null;
|
|||
|
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|||
|
|
} catch { return null; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function writeJSON(filePath, data) {
|
|||
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|||
|
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function generateAOACSection(chainStatus, chainReport, payload) {
|
|||
|
|
const lines = [];
|
|||
|
|
lines.push('');
|
|||
|
|
lines.push('### 🔗 AOAC · Agent链路闭环系统');
|
|||
|
|
lines.push('');
|
|||
|
|
lines.push('| Agent | 名称 | 状态 | 最后运行 |');
|
|||
|
|
lines.push('|-------|------|------|----------|');
|
|||
|
|
|
|||
|
|
if (chainStatus && chainStatus.agents) {
|
|||
|
|
const agentOrder = ['AOAC-01', 'AOAC-02', 'AOAC-03', 'AOAC-04', 'AOAC-05', 'AOAC-06', 'AOAC-07', 'AOAC-08'];
|
|||
|
|
for (const id of agentOrder) {
|
|||
|
|
const agent = chainStatus.agents[id];
|
|||
|
|
if (!agent) continue;
|
|||
|
|
const statusIcon = agent.status === 'completed' ? '✅' :
|
|||
|
|
agent.status === 'half_ready' ? '🔶' :
|
|||
|
|
agent.status === 'running' ? '🔄' :
|
|||
|
|
agent.status === 'error' ? '❌' : '⬜';
|
|||
|
|
const lastRun = agent.last_run ?
|
|||
|
|
new Date(new Date(agent.last_run).getTime() + BEIJING_OFFSET_MS).toISOString().replace('T', ' ').slice(0, 16) :
|
|||
|
|
'N/A';
|
|||
|
|
lines.push(`| ${id} | ${agent.name_cn} | ${statusIcon} ${agent.status} | ${lastRun} |`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
lines.push('');
|
|||
|
|
|
|||
|
|
// Latest chain report
|
|||
|
|
if (chainReport && chainReport.development) {
|
|||
|
|
const dev = chainReport.development;
|
|||
|
|
const ci = chainReport.ci_result || {};
|
|||
|
|
const ciBadge = ci.overall === 'success' ? '✅' : ci.overall === 'failure' ? '❌' : '⚠️';
|
|||
|
|
lines.push(`**最新链路报告**: ${chainReport.report_id || 'N/A'}`);
|
|||
|
|
lines.push(`- PR #${dev.pr_number || '?'}: ${(dev.pr_title || '').slice(0, 80)}`);
|
|||
|
|
lines.push(`- 变更: ${dev.files_changed || 0}文件 (+${dev.additions || 0}/-${dev.deletions || 0})`);
|
|||
|
|
lines.push(`- CI: ${ciBadge} ${ci.overall || 'unknown'}`);
|
|||
|
|
lines.push(`- 时间: ${chainReport.timestamp || 'N/A'}`);
|
|||
|
|
lines.push('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Chain health
|
|||
|
|
if (chainStatus) {
|
|||
|
|
const health = chainStatus.chain_health || 'unknown';
|
|||
|
|
const healthIcon = health === 'healthy' ? '🟢' : health === 'warning' ? '🟡' : '⚪';
|
|||
|
|
lines.push(`**链路健康**: ${healthIcon} ${health} · 完成周期: ${chainStatus.total_cycles || 0} · 修复次数: ${chainStatus.total_repairs || 0}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
lines.push('');
|
|||
|
|
return lines.join('\n');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function updateReadmeAOACSection(aoacContent) {
|
|||
|
|
if (!fs.existsSync(README_PATH)) {
|
|||
|
|
console.log('⚠️ README.md不存在,跳过更新');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let readme = fs.readFileSync(README_PATH, 'utf8');
|
|||
|
|
const startIdx = readme.indexOf(AOAC_START_MARKER);
|
|||
|
|
const endIdx = readme.indexOf(AOAC_END_MARKER);
|
|||
|
|
|
|||
|
|
if (startIdx === -1 || endIdx === -1) {
|
|||
|
|
// Markers don't exist yet - add them before the footer if possible
|
|||
|
|
const footerMarker = '<!-- FOOTER_START -->';
|
|||
|
|
const footerIdx = readme.indexOf(footerMarker);
|
|||
|
|
if (footerIdx !== -1) {
|
|||
|
|
const insertion = `\n${AOAC_START_MARKER}\n${aoacContent}\n${AOAC_END_MARKER}\n\n`;
|
|||
|
|
readme = readme.slice(0, footerIdx) + insertion + readme.slice(footerIdx);
|
|||
|
|
} else {
|
|||
|
|
// Append to end
|
|||
|
|
readme += `\n${AOAC_START_MARKER}\n${aoacContent}\n${AOAC_END_MARKER}\n`;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// Replace existing content
|
|||
|
|
readme = readme.slice(0, startIdx + AOAC_START_MARKER.length) +
|
|||
|
|
'\n' + aoacContent + '\n' +
|
|||
|
|
readme.slice(endIdx);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fs.writeFileSync(README_PATH, readme, 'utf8');
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function collectRepoStatus() {
|
|||
|
|
// Collect basic repo statistics
|
|||
|
|
const status = {
|
|||
|
|
total_workflows: 0,
|
|||
|
|
total_scripts: 0,
|
|||
|
|
aoac_agents: 0
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const wfDir = path.join(ROOT, '.github', 'workflows');
|
|||
|
|
if (fs.existsSync(wfDir)) {
|
|||
|
|
status.total_workflows = fs.readdirSync(wfDir).filter(f => f.endsWith('.yml')).length;
|
|||
|
|
}
|
|||
|
|
} catch { /* ignore */ }
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const scriptDir = path.join(ROOT, 'scripts');
|
|||
|
|
if (fs.existsSync(scriptDir)) {
|
|||
|
|
status.total_scripts = fs.readdirSync(scriptDir).filter(f => f.endsWith('.js')).length;
|
|||
|
|
}
|
|||
|
|
} catch { /* ignore */ }
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const aoacDir = path.join(ROOT, 'scripts', 'aoac');
|
|||
|
|
if (fs.existsSync(aoacDir)) {
|
|||
|
|
status.aoac_agents = fs.readdirSync(aoacDir).filter(f => f.endsWith('.js')).length;
|
|||
|
|
}
|
|||
|
|
} catch { /* ignore */ }
|
|||
|
|
|
|||
|
|
return status;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function main() {
|
|||
|
|
const mode = process.argv[2] || 'auto';
|
|||
|
|
console.log(`🏠 AOAC-05 · 首页主控Agent · 唤醒中 (模式: ${mode})`);
|
|||
|
|
console.log(`📅 ${getBeijingTime()}`);
|
|||
|
|
|
|||
|
|
const now = new Date();
|
|||
|
|
const dateStr = getDateStr();
|
|||
|
|
|
|||
|
|
// Read all data sources
|
|||
|
|
const chainStatus = readJSON(CHAIN_STATUS_PATH);
|
|||
|
|
const chainReport = readJSON(CHAIN_REPORT_PATH);
|
|||
|
|
const payload = readJSON(PAYLOAD_PATH);
|
|||
|
|
const dashboard = readJSON(DASHBOARD_PATH);
|
|||
|
|
const memory = readJSON(MEMORY_PATH);
|
|||
|
|
const repoStatus = collectRepoStatus();
|
|||
|
|
|
|||
|
|
// Generate AOAC section for README
|
|||
|
|
const aoacContent = generateAOACSection(chainStatus, chainReport, payload);
|
|||
|
|
|
|||
|
|
// Update README
|
|||
|
|
const updated = updateReadmeAOACSection(aoacContent);
|
|||
|
|
if (updated) {
|
|||
|
|
console.log('✅ README.md AOAC区域已更新');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Also call generate-readme-dashboard.js if it exists (event-triggered mode)
|
|||
|
|
const dashGen = path.join(ROOT, 'scripts', 'generate-readme-dashboard.js');
|
|||
|
|
if (fs.existsSync(dashGen) && mode === 'event') {
|
|||
|
|
try {
|
|||
|
|
execFileSync('node', [dashGen], { cwd: ROOT, timeout: 60000, stdio: 'inherit' });
|
|||
|
|
console.log('✅ README仪表盘同步更新');
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('⚠️ 仪表盘更新失败:', err.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// In scheduled/auto mode, also generate dashboard for full daily review
|
|||
|
|
if (fs.existsSync(dashGen) && mode !== 'event') {
|
|||
|
|
try {
|
|||
|
|
execFileSync('node', [dashGen], { cwd: ROOT, timeout: 60000, stdio: 'inherit' });
|
|||
|
|
console.log('✅ 定时仪表盘同步更新');
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('⚠️ 定时仪表盘更新失败:', err.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Generate master report
|
|||
|
|
const seq = String(Math.floor(Math.random() * 1000)).padStart(3, '0');
|
|||
|
|
const masterReport = {
|
|||
|
|
aoac_agent: 'AOAC-05',
|
|||
|
|
aoac_agent_name: 'readme-master-agent',
|
|||
|
|
report_id: `AOAC-MASTER-${dateStr.replace(/-/g, '')}-${seq}`,
|
|||
|
|
timestamp: getBeijingTime(),
|
|||
|
|
timestamp_utc: now.toISOString(),
|
|||
|
|
mode,
|
|||
|
|
readme_updated: updated,
|
|||
|
|
chain_health: chainStatus ? chainStatus.chain_health : 'unknown',
|
|||
|
|
latest_chain_report: chainReport ? chainReport.report_id : null,
|
|||
|
|
repo_status: repoStatus,
|
|||
|
|
changes_summary: chainReport ? chainReport.changes_summary : '定时巡检·无新合并',
|
|||
|
|
triggers_next: 'AOAC-06 (notion-sync-signal)'
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
writeJSON(MASTER_REPORT_PATH, masterReport);
|
|||
|
|
console.log(`✅ 主控报告: ${masterReport.report_id}`);
|
|||
|
|
|
|||
|
|
// Update chain status
|
|||
|
|
if (chainStatus && chainStatus.agents && chainStatus.agents['AOAC-05']) {
|
|||
|
|
chainStatus.agents['AOAC-05'].status = 'completed';
|
|||
|
|
chainStatus.agents['AOAC-05'].last_run = now.toISOString();
|
|||
|
|
chainStatus.agents['AOAC-05'].last_success = now.toISOString();
|
|||
|
|
chainStatus.total_cycles = (chainStatus.total_cycles || 0) + 1;
|
|||
|
|
chainStatus.last_full_cycle = now.toISOString();
|
|||
|
|
writeJSON(CHAIN_STATUS_PATH, chainStatus);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Archive
|
|||
|
|
const historyDir = path.join(HISTORY_DIR, dateStr);
|
|||
|
|
fs.mkdirSync(historyDir, { recursive: true });
|
|||
|
|
writeJSON(path.join(historyDir, `${masterReport.report_id}.json`), masterReport);
|
|||
|
|
|
|||
|
|
// Output for workflow
|
|||
|
|
const outputFile = process.env.GITHUB_OUTPUT;
|
|||
|
|
if (outputFile) {
|
|||
|
|
fs.appendFileSync(outputFile, `master_report_id=${masterReport.report_id}\n`);
|
|||
|
|
fs.appendFileSync(outputFile, `readme_updated=${updated}\n`);
|
|||
|
|
fs.appendFileSync(outputFile, `changes_summary=${(masterReport.changes_summary || '').slice(0, 200)}\n`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('🏠 AOAC-05 · 主控Agent完成 · 触发AOAC-06 Notion同步');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
main();
|