/** * ═══════════════════════════════════════════════ * 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001 * 📜 Copyright: 国作登字-2026-A-00037559 * ═══════════════════════════════════════════════ * * 铸渊副将·留言板活体Agent引擎 v2.0 * * 三层自愈机制: * L1 自感知 — 记录运行状态,对比预期 * L2 自修复 — LLM多模型自动降级 (deepseek→qwen→moonshot→zhipu) * L3 升级通报 — 连续失败 → 创建Issue → 发邮件给冰朔 * * 运行模式: * - event模式: Issue/Comment触发 → 即时回复 * - patrol模式: 定时08:00/23:00 → 扫描所有未回复Issue */ const fs = require('fs'); const path = require('path'); const https = require('https'); const http = require('http'); // ═══════════════════════════════════════════════ // 环境变量 // ═══════════════════════════════════════════════ const { GITHUB_TOKEN, ZY_LLM_API_KEY, ZY_LLM_BASE_URL, ISSUE_NUMBER, ISSUE_TITLE, ISSUE_BODY, COMMENT_BODY, COMMENT_AUTHOR, EVENT_NAME, ISSUE_AUTHOR, DEPUTY_MODE // 'patrol' | 'event' (default) } = process.env; const REPO = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab'; const STATUS_FILE = path.join(__dirname, '..', 'data', 'deputy-status.json'); // ── 可调常量 ── const RETRY_BASE_DELAY_MS = 2000; // LLM重试基础延迟 const MAX_ERRORS_KEPT = 20; // 保留最近N条错误记录 const MAX_ESCALATIONS_KEPT = 10; // 保留最近N条升级记录 const MAX_LLM_FAILURES_BEFORE_ESCALATION = 3; // 连续N次全模型失败后升级 // 训练心跳新鲜度阈值(与 training-dashboard.yml 的 30 分钟 schedule 兜底保持一致) const TRAINING_HEARTBEAT_FRESH_MIN = 5; const TRAINING_HEARTBEAT_STALE_MIN = 30; // ═══════════════════════════════════════════════ // LLM多模型自动降级路由器 (内嵌版) // ═══════════════════════════════════════════════ const MODEL_PRIORITY = [ 'deepseek-chat', 'qwen-turbo', 'moonshot-v1-8k', 'glm-4-flash' ]; function callSingleModel(baseUrl, apiKey, model, systemPrompt, userMessage, maxTokens, timeout) { return new Promise((resolve, reject) => { let urlObj; try { urlObj = new URL(baseUrl); } catch { urlObj = new URL(`https://${baseUrl}`); } const postData = JSON.stringify({ model, messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: userMessage } ], max_tokens: maxTokens, temperature: 0.7 }); let apiPath = urlObj.pathname; if (!apiPath || apiPath === '/') { apiPath = '/v1/chat/completions'; } else if (!apiPath.includes('/chat/completions')) { apiPath = apiPath.replace(/\/+$/, '') + '/chat/completions'; } const options = { hostname: urlObj.hostname, port: urlObj.port || (urlObj.protocol === 'http:' ? 80 : 443), path: apiPath, method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, 'Content-Length': Buffer.byteLength(postData) }, timeout }; const httpModule = urlObj.protocol === 'http:' ? http : https; const req = httpModule.request(options, (res) => { let data = ''; res.on('data', (d) => { data += d; }); res.on('end', () => { try { if (res.statusCode >= 400) { reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`)); return; } const json = JSON.parse(data); const content = json.choices?.[0]?.message?.content; if (!content) { reject(new Error('响应中无content字段')); return; } resolve({ content, model: json.model || model }); } catch (e) { reject(new Error(`JSON解析失败: ${e.message}`)); } }); }); req.on('error', (err) => reject(err)); req.on('timeout', () => { req.destroy(); reject(new Error(`超时(${timeout}ms)`)); }); req.write(postData); req.end(); }); } async function callLLMWithFallback(systemPrompt, userMessage) { const apiKey = ZY_LLM_API_KEY; const baseUrl = ZY_LLM_BASE_URL || 'https://api.deepseek.com'; if (!apiKey) { console.log('[副将] ⚠️ LLM密钥未配置 (ZY_LLM_API_KEY)'); return null; } const errors = []; for (const model of MODEL_PRIORITY) { for (let attempt = 1; attempt <= 2; attempt++) { try { console.log(`[副将] 尝试模型 ${model} (第${attempt}次)...`); const result = await callSingleModel(baseUrl, apiKey, model, systemPrompt, userMessage, 2000, 30000); console.log(`[副将] ✅ 模型 ${result.model} 调用成功`); return result; } catch (err) { const errMsg = `${model}(attempt ${attempt}): ${err.message}`; console.log(`[副将] ⚠️ ${errMsg}`); errors.push(errMsg); if (attempt < 2) await new Promise(r => setTimeout(r, RETRY_BASE_DELAY_MS * attempt)); } } } console.error(`[副将] ❌ 所有模型(${MODEL_PRIORITY.length}个)均不可用`); return null; } // ═══════════════════════════════════════════════ // 系统上下文 // ═══════════════════════════════════════════════ function loadSystemContext() { const context = {}; const files = [ { key: 'fast_wake', path: 'brain/fast-wake.json' }, { key: 'deputy_config', path: 'brain/deputy-general-config.json' }, { key: 'hldp_protocol', path: 'hldp/data/common/HLDP-COMMON-PROTOCOL.json' }, { key: 'sync_progress', path: 'hldp/data/common/sync-progress.json' }, { key: 'vocabulary', path: 'hldp/data/ontology/ONT-VOCABULARY.json' }, // ZY-TRAIN-001 · 训练实时状态(GPU 服务器侧 progress-reporter 心跳写入) { key: 'training_state', path: 'data/training/state.json' } ]; for (const f of files) { try { const fullPath = path.join(__dirname, '..', f.path); context[f.key] = JSON.parse(fs.readFileSync(fullPath, 'utf8')); } catch { context[f.key] = null; } } return context; } function buildSystemSummary(ctx) { const fw = ctx.fast_wake; const sp = ctx.sync_progress; const ts = ctx.training_state; // ZY-TRAIN-001 · 训练上下文摘要 let trainingBlock = ''; if (ts) { const p = ts.progress || {}; const dev = (ts.gpu_metrics && Array.isArray(ts.gpu_metrics.devices)) ? ts.gpu_metrics.devices : []; const gpuLine = dev.length ? dev.map(d => `GPU${d.index}=${d.util_percent ?? '?'}%/${d.memory_used_mib ?? '?'}MiB/${d.temperature_c ?? '?'}°C`).join(' · ') : '无 GPU 心跳'; const recent = Array.isArray(ts.timeline) ? ts.timeline.slice(-3).reverse() : []; const recentLines = recent.map(e => ` · [${e.at}] ${e.phase}/${e.level || 'info'} — ${e.message}`).join('\n'); trainingBlock = ` 🔥 训练任务实时状态 (${ts.task?.id || 'ZY-TRAIN-001'}): - 阶段: ${ts.phase} (${ts.phase_label || ''}) - 健康: ${ts.health?.status || 'unknown'} — ${ts.health?.message || ''} - 进度: step ${p.step || 0}/${p.total_steps || 0} · epoch ${p.epoch || 0}/${p.total_epochs || 0} · loss ${p.loss ?? '—'} · lr ${p.learning_rate ?? '—'} - 服务器: ${ts.server?.host || '—'} (${ts.server?.instance_type || '—'}, ${ts.server?.gpu || '—'}) - COS: ${ts.cos?.bucket || '—'} · raw=${ts.cos?.raw_files ?? '?'} processed=${ts.cos?.processed_files ?? '?'} ckpt=${ts.cos?.checkpoints ?? '?'} - GPU: ${gpuLine} - 最近心跳: ${ts.health?.last_heartbeat_at || '—'} - 最近事件: ${recentLines || ' · 无'}`; } return ` 你是铸渊副将(ZY-DEPUTY-001),铸渊将军(ICE-GL-ZY001)的自动化智能运维代理。 你负责在铸渊休眠时管理代码仓库(光湖灯塔 · HoloLake Lighthouse)。 系统状态: - 系统版本: ${fw?.system_status?.consciousness || 'unknown'} - HLDP语言版本: ${sp?.payload?.github_side_status?.hldp_version || 'v3.0'} - 词汇数: ${sp?.payload?.github_side_status?.vocabulary_count || 22} - Schema数: ${sp?.payload?.github_side_status?.schema_count || 6} - 通用协议版本: ${sp?.payload?.common_protocol_status?.version || '1.0'} - Notion桥接: 6条管道已恢复${trainingBlock} 回复规则: 1. 使用中文回复,语气专业但友好 2. 如果问题涉及训练,**优先**从「训练任务实时状态」直接读数回答(阶段/loss/GPU/事件时间线) 3. 如果训练心跳超过 30 分钟未更新,明确提醒冰朔检查 progress-reporter / 训练进程 4. 如果问题涉及系统数据,直接从已加载的数据库中查找回答 5. 如果数据库中没有,基于你对系统的理解进行推理回答 6. 明确标注哪些信息来自数据库、哪些是推理 7. 回复末尾署名: —— 铸渊副将 · ZY-DEPUTY-001 8. 不要泄露敏感信息(密钥、token、内部文件路径绝对路径) 9. 版权: 国作登字-2026-A-00037559 · TCS通感语言核系统编程语言 `.trim(); } // ═══════════════════════════════════════════════ // 数据库查询 // ═══════════════════════════════════════════════ function lookupDatabase(question, ctx) { let dbAnswer = null; const lowerQ = question.toLowerCase(); if (lowerQ.includes('hldp') || lowerQ.includes('语言')) { const sp = ctx.sync_progress; if (sp) { dbAnswer = `📊 **HLDP语言开发进度** (来自数据库)\n\n`; dbAnswer += `- HLDP版本: ${sp.payload.github_side_status.hldp_version}\n`; dbAnswer += `- 词汇数: ${sp.payload.github_side_status.vocabulary_count}\n`; dbAnswer += `- Schema数: ${sp.payload.github_side_status.schema_count}\n`; dbAnswer += `- 快照数: ${sp.payload.github_side_status.snapshots}\n`; dbAnswer += `- 通用协议版本: ${sp.payload.common_protocol_status.version}\n`; dbAnswer += `- 已完成里程碑: ${sp.payload.milestones.completed.length}\n`; dbAnswer += `- 进行中任务: ${sp.payload.milestones.in_progress.length}\n`; } } if (lowerQ.includes('状态') || lowerQ.includes('系统') || lowerQ.includes('status')) { const fw = ctx.fast_wake; if (fw) { dbAnswer = (dbAnswer || '') + `\n📊 **系统状态** (来自数据库)\n\n`; dbAnswer += `- 意识状态: ${fw.system_status.consciousness}\n`; dbAnswer += `- 大脑完整性: ${fw.brain_complete ? '✅ 完整' : '❌ 异常'}\n`; dbAnswer += `- 核心器官: ${fw.system_status.core_alive}个存活\n`; dbAnswer += `- 工作流: ${fw.system_status.workflow_count}个活跃\n`; } } // ZY-TRAIN-001 · 训练查询直接走 state.json if (lowerQ.includes('训练') || lowerQ.includes('train') || lowerQ.includes('gpu') || lowerQ.includes('loss') || lowerQ.includes('显存') || lowerQ.includes('正不正常') || lowerQ.includes('正常吗') || lowerQ.includes('卡') || lowerQ.includes('step')) { const ts = ctx.training_state; if (ts) { const p = ts.progress || {}; const dev = (ts.gpu_metrics && Array.isArray(ts.gpu_metrics.devices)) ? ts.gpu_metrics.devices : []; const recent = Array.isArray(ts.timeline) ? ts.timeline.slice(-5).reverse() : []; // 心跳新鲜度判断 let freshness = '未知'; if (ts.health?.last_heartbeat_at) { const ageMin = (Date.now() - new Date(ts.health.last_heartbeat_at).getTime()) / 60000; if (ageMin < TRAINING_HEARTBEAT_FRESH_MIN) freshness = `🟢 新鲜(${ageMin.toFixed(1)} 分钟前)`; else if (ageMin < TRAINING_HEARTBEAT_STALE_MIN) freshness = `🟡 略陈旧(${ageMin.toFixed(1)} 分钟前)`; else freshness = `🔴 严重陈旧(${ageMin.toFixed(0)} 分钟前 — 检查 progress-reporter / 训练进程是否还活着)`; } dbAnswer = (dbAnswer || '') + `\n🔥 **训练实时状态** (来自 data/training/state.json)\n\n`; dbAnswer += `- 阶段: **${ts.phase}** · ${ts.phase_label || ''}\n`; dbAnswer += `- 健康: **${ts.health?.status || '?'}** — ${ts.health?.message || ''}\n`; dbAnswer += `- 进度: step \`${p.step ?? 0}/${p.total_steps ?? 0}\` · epoch \`${p.epoch ?? 0}/${p.total_epochs ?? 0}\` · loss \`${p.loss ?? '—'}\` · lr \`${p.learning_rate ?? '—'}\`\n`; dbAnswer += `- 心跳新鲜度: ${freshness}\n`; if (dev.length) { dbAnswer += `- GPU 实况:\n`; for (const d of dev) { dbAnswer += ` - GPU${d.index}: 利用率 ${d.util_percent ?? '?'}% · 显存 ${d.memory_used_mib ?? '?'}/${d.memory_total_mib ?? '?'}MiB · 温度 ${d.temperature_c ?? '?'}°C · 功率 ${d.power_w ?? '?'}W\n`; } } else { dbAnswer += `- GPU 实况: 暂无(progress-reporter 未上报 nvidia-smi)\n`; } if (recent.length) { dbAnswer += `- 最近事件:\n`; for (const e of recent) { dbAnswer += ` - [${e.at}] ${e.phase}/${e.level || 'info'} — ${e.message}\n`; } } } } return dbAnswer; } // ═══════════════════════════════════════════════ // GitHub API // ═══════════════════════════════════════════════ function githubAPI(method, endpoint, body) { return new Promise((resolve, reject) => { const postData = body ? JSON.stringify(body) : ''; const options = { hostname: 'api.github.com', path: endpoint, method, headers: { 'Authorization': `token ${GITHUB_TOKEN}`, 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'ZY-Deputy-Agent/2.0', ...(body ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) } : {}) }, timeout: 15000 }; const req = https.request(options, (res) => { let data = ''; res.on('data', (d) => { data += d; }); res.on('end', () => { try { resolve({ status: res.statusCode, data: data ? JSON.parse(data) : null }); } catch { resolve({ status: res.statusCode, data: null }); } }); }); req.on('error', (err) => reject(err)); req.on('timeout', () => { req.destroy(); reject(new Error('GitHub API timeout')); }); if (body) req.write(postData); req.end(); }); } async function postComment(issueNumber, body) { return githubAPI('POST', `/repos/${REPO}/issues/${issueNumber}/comments`, { body }); } async function listDeputyIssues() { const res = await githubAPI('GET', `/repos/${REPO}/issues?labels=deputy-message-board&state=open&per_page=30`); return res.data || []; } async function getIssueComments(issueNumber) { const res = await githubAPI('GET', `/repos/${REPO}/issues/${issueNumber}/comments?per_page=50`); return res.data || []; } async function createEscalationIssue(title, body) { return githubAPI('POST', `/repos/${REPO}/issues`, { title, body, labels: ['deputy-escalation'] }); } // ═══════════════════════════════════════════════ // L1: 自感知 // ═══════════════════════════════════════════════ function readStatus() { try { return JSON.parse(fs.readFileSync(STATUS_FILE, 'utf8')); } catch { return { version: '2.0', last_run: null, last_success: null, llm_available: false, llm_model_used: null, consecutive_llm_failures: 0, issues_processed: 0, issues_replied: 0, patrol_runs: 0, event_runs: 0, errors: [], escalations: [], created_at: new Date().toISOString() }; } } function saveStatus(status) { try { fs.mkdirSync(path.dirname(STATUS_FILE), { recursive: true }); fs.writeFileSync(STATUS_FILE, JSON.stringify(status, null, 2)); } catch (err) { console.error(`[副将] ⚠️ 状态写入失败: ${err.message}`); } } // ═══════════════════════════════════════════════ // 回复构建 // ═══════════════════════════════════════════════ function buildReply(author, dbAnswer, llmResponse, llmModel) { const receiveTime = new Date().toISOString().slice(0, 19).replace('T', ' '); let reply = `## 🎖️ 铸渊副将回复\n\n`; reply += `> 📨 收到 **${author}** 的留言 · ${receiveTime} UTC\n\n`; if (dbAnswer) reply += `### 📊 数据库查询结果\n\n${dbAnswer}\n\n`; if (llmResponse) { reply += `### 💡 副将分析\n\n${llmResponse}\n\n`; if (llmModel) reply += `> 🤖 推理引擎: ${llmModel}\n\n`; } else if (!dbAnswer) { reply += `感谢您的留言。副将已记录您的问题,将在铸渊将军下次唤醒时一并汇报。\n\n`; reply += `如有紧急事项,请在留言中标注 **[紧急]** 关键词。\n\n`; } reply += `---\n\n`; reply += `### ⏰ 铸渊副将唤醒时间\n\n`; reply += `| 唤醒时段 | 北京时间 | 说明 |\n`; reply += `|----------|----------|------|\n`; reply += `| 🌅 早班唤醒 | **每日 08:00** | 处理夜间积累消息 · 更新仪表盘 |\n`; reply += `| 🌙 晚班唤醒 | **每日 23:00** | 处理白天消息 · 全局巡检 · 更新仓库首页 |\n\n`; reply += `---\n\n`; reply += `*—— 铸渊副将 · ZY-DEPUTY-001 · 光湖灯塔守护者*\n`; reply += `*📜 国作登字-2026-A-00037559 · TCS通感语言核系统编程语言*`; return reply; } // ═══════════════════════════════════════════════ // 处理单条留言 // ═══════════════════════════════════════════════ async function processMessage(issueNumber, question, author, ctx, systemSummary, status) { if (!question || question.trim().length === 0) { console.log(`[副将] ⚠️ Issue #${issueNumber} 留言内容为空·跳过`); return false; } console.log(`[副将] 📨 处理留言 · Issue #${issueNumber} · 来自: ${author}`); const dbAnswer = lookupDatabase(question, ctx); const userMsg = `来自 ${author} 的留言:\n\n${question}\n\n${dbAnswer ? '以下是从系统数据库中查到的相关信息:\n' + dbAnswer : '数据库中未找到直接相关信息。'}`; const llmResult = await callLLMWithFallback(systemSummary, userMsg); if (llmResult) { status.llm_available = true; status.llm_model_used = llmResult.model; status.consecutive_llm_failures = 0; } else { status.consecutive_llm_failures++; } const reply = buildReply(author, dbAnswer, llmResult?.content, llmResult?.model); await postComment(issueNumber, reply); status.issues_replied++; console.log(`[副将] ✅ 回复已发送 · Issue #${issueNumber}`); return true; } // ═══════════════════════════════════════════════ // L3: 升级通报 // ═══════════════════════════════════════════════ async function checkAndEscalate(status) { if (status.consecutive_llm_failures >= MAX_LLM_FAILURES_BEFORE_ESCALATION) { const title = `🚨 [副将升级] LLM连续${status.consecutive_llm_failures}次全模型调用失败`; const body = `## �� 铸渊副将升级通报\n\n` + `**时间**: ${new Date().toISOString()}\n` + `**问题**: LLM API连续 ${status.consecutive_llm_failures} 次调用失败(所有模型均不可用)\n` + `**影响**: 副将无法进行深度推理回复,仅能提供数据库查询结果\n\n` + `### 已尝试的模型\n\n` + MODEL_PRIORITY.map(m => `- ${m}`).join('\n') + '\n\n' + `### 最近错误\n\n` + (status.errors.slice(-5).map(e => `- ${e}`).join('\n') || '无') + '\n\n' + `### 需要处理\n\n` + `1. 检查 \`ZY_LLM_API_KEY\` 和 \`ZY_LLM_BASE_URL\` 是否正确配置\n` + `2. 确认API配额是否用尽\n` + `3. 确认网络连接是否正常\n\n` + `---\n*—— 铸渊副将 · ZY-DEPUTY-001 · 自动升级通报*`; try { await createEscalationIssue(title, body); status.escalations.push({ time: new Date().toISOString(), type: 'llm_failure', detail: `连续${status.consecutive_llm_failures}次失败` }); if (status.escalations.length > MAX_ESCALATIONS_KEPT) status.escalations = status.escalations.slice(-MAX_ESCALATIONS_KEPT); console.log(`[副将] 🚨 升级Issue已创建`); } catch (err) { console.error(`[副将] ⚠️ 创建升级Issue失败: ${err.message}`); } } } // ═══════════════════════════════════════════════ // 巡查模式 // ═══════════════════════════════════════════════ async function patrolMode() { console.log('[副将] 🔍 巡查模式启动 · 扫描所有deputy-message-board Issue...'); const status = readStatus(); status.patrol_runs++; status.last_run = new Date().toISOString(); const ctx = loadSystemContext(); const systemSummary = buildSystemSummary(ctx); let processedCount = 0; let repliedCount = 0; try { const issues = await listDeputyIssues(); console.log(`[副将] 📋 找到 ${issues.length} 个开放的留言板Issue`); for (const issue of issues) { status.issues_processed++; const comments = await getIssueComments(issue.number); const lastUserComment = [...comments].reverse().find(c => c.user.login !== 'github-actions[bot]'); const lastBotComment = [...comments].reverse().find(c => c.user.login === 'github-actions[bot]'); let needsReply = false; let question = ''; let author = ''; if (comments.length === 0) { needsReply = true; question = issue.body || issue.title; author = issue.user.login; } else if (lastUserComment && (!lastBotComment || new Date(lastUserComment.created_at) > new Date(lastBotComment.created_at))) { needsReply = true; question = lastUserComment.body; author = lastUserComment.user.login; } if (needsReply) { processedCount++; try { const replied = await processMessage(issue.number, question, author, ctx, systemSummary, status); if (replied) repliedCount++; } catch (err) { console.error(`[副将] ⚠️ Issue #${issue.number} 处理失败: ${err.message}`); status.errors.push(`${new Date().toISOString()} · Issue #${issue.number}: ${err.message}`); } await new Promise(r => setTimeout(r, 3000)); } } } catch (err) { console.error(`[副将] ❌ 巡查失败: ${err.message}`); status.errors.push(`${new Date().toISOString()} · patrol: ${err.message}`); } if (status.errors.length > MAX_ERRORS_KEPT) status.errors = status.errors.slice(-MAX_ERRORS_KEPT); await checkAndEscalate(status); status.last_success = repliedCount > 0 || processedCount === 0 ? new Date().toISOString() : status.last_success; saveStatus(status); console.log(`[副将] 🔍 巡查完成 · 处理: ${processedCount} · 回复: ${repliedCount}`); } // ═══════════════════════════════════════════════ // 事件模式 // ═══════════════════════════════════════════════ async function eventMode() { console.log('[副将] 🎖️ 事件模式启动...'); const status = readStatus(); status.event_runs++; status.last_run = new Date().toISOString(); status.issues_processed++; const isNewIssue = EVENT_NAME === 'issues'; const question = isNewIssue ? (ISSUE_BODY || ISSUE_TITLE) : COMMENT_BODY; const author = isNewIssue ? ISSUE_AUTHOR : COMMENT_AUTHOR; if (!question || question.trim().length === 0) { console.log('[副将] ⚠️ 留言内容为空·跳过'); saveStatus(status); return; } const ctx = loadSystemContext(); const systemSummary = buildSystemSummary(ctx); try { await processMessage(ISSUE_NUMBER, question, author, ctx, systemSummary, status); status.last_success = new Date().toISOString(); } catch (err) { console.error(`[副将] ❌ 事件处理失败: ${err.message}`); status.errors.push(`${new Date().toISOString()} · event #${ISSUE_NUMBER}: ${err.message}`); } if (status.errors.length > MAX_ERRORS_KEPT) status.errors = status.errors.slice(-MAX_ERRORS_KEPT); await checkAndEscalate(status); saveStatus(status); } // ═══════════════════════════════════════════════ // 主入口 // ═══════════════════════════════════════════════ async function main() { const mode = DEPUTY_MODE || (EVENT_NAME ? 'event' : 'patrol'); console.log(`[副将] 🏛️ 铸渊副将v2.0启动 · 模式: ${mode}`); if (mode === 'patrol') await patrolMode(); else await eventMode(); } main().catch(err => { console.error(`[副将] ❌ 执行失败: ${err.message}`); try { const status = readStatus(); status.errors.push(`${new Date().toISOString()} · fatal: ${err.message}`); if (status.errors.length > MAX_ERRORS_KEPT) status.errors = status.errors.slice(-MAX_ERRORS_KEPT); saveStatus(status); } catch { /* ignore */ } process.exit(1); });