guanghulab/scripts/aoac/chain-repair-agent.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

318 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
'use strict';
/**
* AOAC-07 · 链路修复Agent (Chain Repair Agent)
* ═══════════════════════════════════════════════
* AGE OS Agent Chain · 修复Agent3次自动修复
*
* 唤醒条件Notion侧RECEIPT_DB打红色标签 / 定时检查
* 职责定位断链位置→自动修复→最多3次
* 输出data/aoac/repair-report.json
*
* 版权:国作登字-2026-A-00037559 · TCS-0002∞
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
const ROOT = path.resolve(__dirname, '..', '..');
const CHAIN_STATUS_PATH = path.join(ROOT, 'data', 'aoac', 'chain-status.json');
const SYNC_LOG_PATH = path.join(ROOT, 'data', 'aoac', 'notion-sync-log.json');
const REPAIR_REPORT_PATH = path.join(ROOT, 'data', 'aoac', 'repair-report.json');
const HISTORY_DIR = path.join(ROOT, 'data', 'aoac', 'history');
const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
const MAX_REPAIR_ATTEMPTS = 3;
const NOTION_VERSION = '2022-06-28';
const NOTION_API_HOSTNAME = 'api.notion.com';
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 notionRequest(method, endpoint, body, token) {
return new Promise((resolve, reject) => {
const bodyStr = body ? JSON.stringify(body) : '';
const options = {
hostname: NOTION_API_HOSTNAME,
path: endpoint,
method,
headers: {
'Authorization': `Bearer ${token}`,
'Notion-Version': NOTION_VERSION,
'Content-Type': 'application/json',
...(bodyStr ? { 'Content-Length': Buffer.byteLength(bodyStr) } : {})
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => {
try {
const parsed = JSON.parse(data);
if (res.statusCode >= 400) reject(new Error(`${res.statusCode}: ${parsed.message || data}`));
else resolve(parsed);
} catch { resolve(data); }
});
});
req.on('error', reject);
req.setTimeout(30000, () => { req.destroy(); reject(new Error('timeout')); });
if (bodyStr) req.write(bodyStr);
req.end();
});
}
function diagnoseChain(chainStatus) {
const issues = [];
if (!chainStatus || !chainStatus.agents) {
issues.push({ agent: 'ALL', issue: 'chain-status.json missing or corrupt', severity: 'critical' });
return issues;
}
const agentOrder = ['AOAC-01', 'AOAC-02', 'AOAC-03', 'AOAC-04', 'AOAC-05', 'AOAC-06'];
for (const id of agentOrder) {
const agent = chainStatus.agents[id];
if (!agent) {
issues.push({ agent: id, issue: 'agent config missing', severity: 'warning' });
continue;
}
if (agent.status === 'error') {
issues.push({ agent: id, issue: `agent in error state`, severity: 'critical' });
}
if (!agent.last_run) {
issues.push({ agent: id, issue: 'never executed', severity: 'info' });
}
}
// Check sync log
const syncLog = readJSON(SYNC_LOG_PATH);
if (syncLog && syncLog.notion_sync && !syncLog.notion_sync.sent) {
issues.push({
agent: 'AOAC-06',
issue: `Notion sync failed: ${syncLog.notion_sync.reason}`,
severity: 'critical'
});
}
return issues;
}
function attemptRepair(issues, attempt) {
const results = [];
for (const issue of issues) {
if (issue.severity === 'info') continue;
const result = {
agent: issue.agent,
issue: issue.issue,
attempt,
action: 'none',
success: false
};
// Repair strategy: reset agent status
if (issue.issue.includes('error state')) {
const chainStatus = readJSON(CHAIN_STATUS_PATH);
if (chainStatus && chainStatus.agents && chainStatus.agents[issue.agent]) {
chainStatus.agents[issue.agent].status = 'idle';
writeJSON(CHAIN_STATUS_PATH, chainStatus);
result.action = 'reset_status_to_idle';
result.success = true;
}
}
// Repair strategy: re-trigger Notion sync on next AOAC-05 cycle
if (issue.issue.includes('Notion sync failed') && attempt <= 2) {
// Reset AOAC-06 status to idle so next AOAC-05 cycle will re-trigger it
const cs = readJSON(CHAIN_STATUS_PATH);
if (cs && cs.agents && cs.agents['AOAC-06']) {
cs.agents['AOAC-06'].status = 'idle';
writeJSON(CHAIN_STATUS_PATH, cs);
result.action = 'reset_aoac06_for_retry';
result.success = true;
} else {
result.action = 'cannot_reset_aoac06';
result.success = false;
}
}
// Repair strategy: recreate missing chain-status from default template
if (issue.issue.includes('missing or corrupt')) {
const templatePath = path.join(ROOT, 'data', 'aoac', 'chain-status.json');
if (!fs.existsSync(templatePath)) {
// Recreate from scratch
const defaultStatus = {
_meta: { system: 'AGE OS Agent Chain (AOAC) v1.0', recreated: new Date().toISOString() },
agents: {},
chain_health: 'unknown',
last_full_cycle: null,
total_cycles: 0,
total_repairs: 0
};
writeJSON(templatePath, defaultStatus);
result.action = 'recreated_chain_status';
result.success = true;
} else {
result.action = 'config_exists_no_action';
result.success = true;
}
}
results.push(result);
}
return results;
}
async function checkNotionReceipt() {
const notionToken = process.env.ZY_NOTION_TOKEN || process.env.NOTION_TOKEN;
const receiptDb = process.env.ZY_NOTION_RECEIPT_DB;
if (!notionToken || !receiptDb) {
return { checked: false, reason: 'missing_credentials' };
}
try {
// Query latest AOAC sync receipts
const body = {
filter: {
property: '类型',
select: { equals: 'AOAC链路同步' }
},
sorts: [{ property: '时间戳', direction: 'descending' }],
page_size: 5
};
const result = await notionRequest('POST', `/v1/databases/${receiptDb}/query`, body, notionToken);
if (result && result.results && result.results.length > 0) {
const latest = result.results[0];
const status = latest.properties && latest.properties['状态'] &&
latest.properties['状态'].select ? latest.properties['状态'].select.name : 'unknown';
return {
checked: true,
latest_status: status,
needs_repair: status === 'failed' || status === 'red' || status === '红色',
page_id: latest.id
};
}
return { checked: true, latest_status: 'no_records', needs_repair: false };
} catch (err) {
return { checked: false, reason: err.message };
}
}
async function main() {
console.log('🔧 AOAC-07 · 链路修复Agent · 唤醒中...');
console.log(`📅 ${getBeijingTime()}`);
const now = new Date();
const dateStr = getDateStr();
const seq = String(Math.floor(Math.random() * 1000)).padStart(3, '0');
const repairId = `AOAC-REPAIR-${dateStr.replace(/-/g, '')}-${seq}`;
// Step 1: Check Notion receipt status
const receiptCheck = await checkNotionReceipt();
console.log(`📋 Notion回执检查: ${JSON.stringify(receiptCheck)}`);
// Step 2: Diagnose chain
const chainStatus = readJSON(CHAIN_STATUS_PATH);
const issues = diagnoseChain(chainStatus);
console.log(`🔍 诊断发现 ${issues.length} 个问题`);
const needsRepair = receiptCheck.needs_repair || issues.some(i => i.severity === 'critical');
// Step 3: Attempt repairs (up to 3 times)
const allRepairResults = [];
let repairSuccess = false;
if (needsRepair) {
console.log('🔧 开始修复流程...');
for (let attempt = 1; attempt <= MAX_REPAIR_ATTEMPTS; attempt++) {
console.log(`${attempt}次修复尝试...`);
const results = attemptRepair(issues, attempt);
allRepairResults.push({ attempt, results });
const allFixed = results.every(r => r.success);
if (allFixed) {
repairSuccess = true;
console.log(` ✅ 第${attempt}次修复成功`);
break;
}
console.log(` ⚠️ 第${attempt}次修复部分失败`);
}
} else {
console.log('✅ 链路健康,无需修复');
repairSuccess = true;
}
// Step 4: Generate repair report
const repairReport = {
aoac_agent: 'AOAC-07',
aoac_agent_name: 'chain-repair-agent',
repair_id: repairId,
timestamp: getBeijingTime(),
timestamp_utc: now.toISOString(),
receipt_check: receiptCheck,
diagnosis: {
total_issues: issues.length,
critical: issues.filter(i => i.severity === 'critical').length,
issues
},
needed_repair: needsRepair,
repair_attempts: allRepairResults,
final_result: repairSuccess ? 'success' : 'failure',
triggers_next: 'AOAC-08 (repair-supervisor)'
};
writeJSON(REPAIR_REPORT_PATH, repairReport);
console.log(`📝 修复报告: ${repairId}${repairReport.final_result}`);
// Archive
const historyDir = path.join(HISTORY_DIR, dateStr);
fs.mkdirSync(historyDir, { recursive: true });
writeJSON(path.join(historyDir, `${repairId}.json`), repairReport);
// Update chain status
if (chainStatus && chainStatus.agents && chainStatus.agents['AOAC-07']) {
chainStatus.agents['AOAC-07'].status = repairSuccess ? 'completed' : 'error';
chainStatus.agents['AOAC-07'].last_run = now.toISOString();
if (repairSuccess) chainStatus.agents['AOAC-07'].last_success = now.toISOString();
chainStatus.total_repairs = (chainStatus.total_repairs || 0) + (needsRepair ? 1 : 0);
writeJSON(CHAIN_STATUS_PATH, chainStatus);
}
// Output for workflow
const outputFile = process.env.GITHUB_OUTPUT;
if (outputFile) {
fs.appendFileSync(outputFile, `repair_result=${repairReport.final_result}\n`);
fs.appendFileSync(outputFile, `needed_repair=${needsRepair}\n`);
fs.appendFileSync(outputFile, `repair_id=${repairId}\n`);
}
console.log(`🔧 AOAC-07 · 修复Agent完成 → ${repairReport.final_result}`);
}
main();