// scripts/tianyen/bulletin-dispatcher.js // Bulletin Dispatcher · 公告分发器 // ZY-SKD-006 · Phase 1 · TianYen Scheduling // 版权:国作登字-2026-A-00037559 'use strict'; const fs = require('fs'); const path = require('path'); const ROOT = path.resolve(__dirname, '../..'); const TIANYEN_DIR = path.join(ROOT, '.github/tianyen'); const DISPATCH_PATH = path.join(TIANYEN_DIR, 'bulletin-dispatch.json'); /** * 安全读取 JSON 文件 * @param {string} filePath * @returns {object|null} */ function safeReadJSON(filePath) { if (!fs.existsSync(filePath)) return null; try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { // JSON 损坏,跳过该数据源 return null; } } /** * 采集所有 Agent 状态 * @returns {object[]} */ function collectAllAgentStatus() { const statuses = []; // 签到记录 const log = safeReadJSON(path.join(TIANYEN_DIR, 'checkin-log.json')); if (log) { for (const [agentId, record] of Object.entries(log.checkins || {})) { statuses.push({ agentId, status: record.status || 'unknown', lastSeen: record.timestamp || null }); } } // 调度配置 const schedule = safeReadJSON(path.join(TIANYEN_DIR, 'agent-schedule.json')); if (schedule) { for (const [agentId, config] of Object.entries(schedule.agents || {})) { const existing = statuses.find(s => s.agentId === agentId); if (existing) { existing.schedule = config; } else { statuses.push({ agentId, status: 'configured', lastSeen: null, schedule: config }); } } } return statuses; } /** * 评估需要唤醒的 Agent * @param {object} globalState - 包含 statuses 数组 * @returns {string[]} 需要唤醒的 Agent ID 列表 */ function evaluateWakeTargets(globalState) { const targets = []; const now = Date.now(); for (const agent of (globalState.statuses || [])) { // 事件驱动型不主动唤醒 if (agent.schedule && agent.schedule.mode === 'event_driven') { continue; } // 超过最大间隔未活动 → 唤醒 if (agent.lastSeen) { const lastSeen = new Date(agent.lastSeen).getTime(); const maxInterval = 24 * 60 * 60 * 1000; // 默认 24h if (now - lastSeen > maxInterval) { targets.push(agent.agentId); } } } return targets; } /** * 生成公告条目 * @param {string} agentId * @param {number} priority - 1(紧急) ~ 5(常规) * @param {string} context * @returns {object} */ function generateBulletinEntry(agentId, priority, context) { return { id: `DISP-${Date.now()}-${agentId}`, timestamp: new Date().toISOString(), agentId, priority: priority || 3, context: context || '天眼调度唤醒', status: 'pending' }; } /** * 完整分发流程 * @returns {object} */ function dispatch() { const statuses = collectAllAgentStatus(); const wakeTargets = evaluateWakeTargets({ statuses }); const entries = []; for (const agentId of wakeTargets) { entries.push(generateBulletinEntry(agentId, 2, '超时唤醒')); } // 加载现有分发数据 let dispatchData = { version: '1.0.0', bulletin: [] }; if (fs.existsSync(DISPATCH_PATH)) { try { dispatchData = JSON.parse(fs.readFileSync(DISPATCH_PATH, 'utf8')); if (!Array.isArray(dispatchData.bulletin)) { dispatchData.bulletin = []; } } catch (_) { dispatchData = { version: '1.0.0', bulletin: [] }; } } // 追加新条目 dispatchData.bulletin.push(...entries); // 保留最近 100 条 if (dispatchData.bulletin.length > 100) { dispatchData.bulletin = dispatchData.bulletin.slice(-100); } // 保存 if (!fs.existsSync(TIANYEN_DIR)) { fs.mkdirSync(TIANYEN_DIR, { recursive: true }); } fs.writeFileSync(DISPATCH_PATH, JSON.stringify(dispatchData, null, 2), 'utf8'); return { timestamp: new Date().toISOString(), agentCount: statuses.length, wakeTargets, dispatched: entries.length }; } // ── CLI 入口 ───────────────────────────────────────────────────────────── if (require.main === module) { console.log('👁️ TianYen Bulletin Dispatcher · 公告分发器\n'); const result = dispatch(); console.log(` Agent 总数: ${result.agentCount}`); console.log(` 唤醒目标: ${result.wakeTargets.length}`); console.log(` 已分发: ${result.dispatched}`); console.log('\n✅ 分发完成'); } module.exports = { collectAllAgentStatus, evaluateWakeTargets, generateBulletinEntry, dispatch };