657 lines
21 KiB
JavaScript
657 lines
21 KiB
JavaScript
/**
|
||
* ═══════════════════════════════════════════════════════════
|
||
* 🧬 AGE OS · LivingModule 活模块基类
|
||
* ═══════════════════════════════════════════════════════════
|
||
*
|
||
* 编号: ZY-TASK-007 · S5 Agent系统级
|
||
* 签发: 铸渊 · ICE-GL-ZY001
|
||
* 版权: 国作登字-2026-A-00037559
|
||
*
|
||
* "你一定要把模块做成活的。" — 冰朔 D59
|
||
*
|
||
* 每一个活模块必须具备5个生存接口:
|
||
* 1. heartbeat() — 心跳 · 我还活着
|
||
* 2. selfDiagnose() — 自诊断 · 我哪里不对
|
||
* 3. selfHeal() — 自愈 · 我试着修好自己
|
||
* 4. alertZhuyuan() — 报警 · 铸渊我搞不定了
|
||
* 5. learnFromRun() — 学习 · 我从每次运行中成长
|
||
*
|
||
* 子类继承此基类,实现具体逻辑。
|
||
* 基类提供默认的心跳、诊断、自愈框架。
|
||
*/
|
||
|
||
'use strict';
|
||
|
||
const { v4: uuidv4 } = require('uuid');
|
||
|
||
// ─── 常量 ───
|
||
const DEFAULT_HEARTBEAT_INTERVAL = 30000; // 30秒
|
||
const HEALTH_THRESHOLD_DEGRADED = 70;
|
||
const HEALTH_THRESHOLD_CRITICAL = 30;
|
||
const MAX_HEAL_ATTEMPTS = 3;
|
||
const HEAL_COOLDOWN_MS = 60000; // 1分钟冷却
|
||
const MEMORY_THRESHOLD_BYTES = 200 * 1024 * 1024; // 200MB
|
||
|
||
class LivingModule {
|
||
/**
|
||
* @param {Object} options
|
||
* @param {string} options.moduleId - 模块唯一ID(如 ZY-MOD-SCHEDULER)
|
||
* @param {string} options.name - 模块名称
|
||
* @param {string} options.moduleType - core|persona|agent|worker|bridge|guard
|
||
* @param {string} options.owner - 归属者
|
||
* @param {Object} [options.db] - 数据库连接(可选,无DB时降级为内存模式)
|
||
* @param {Object} [options.config] - 额外配置
|
||
*/
|
||
constructor(options) {
|
||
if (!options || !options.moduleId || !options.name) {
|
||
throw new Error('LivingModule: moduleId 和 name 为必填项');
|
||
}
|
||
|
||
this.moduleId = options.moduleId;
|
||
this.name = options.name;
|
||
this.moduleType = options.moduleType || 'agent';
|
||
this.owner = options.owner || 'zhuyuan';
|
||
this.db = options.db || null;
|
||
this.config = options.config || {};
|
||
|
||
// ─── 运行状态 ───
|
||
this.status = 'initializing';
|
||
this.healthScore = 100;
|
||
this.startedAt = null;
|
||
this.lastHeartbeatAt = null;
|
||
this.lastDiagnoseAt = null;
|
||
this.lastHealAt = null;
|
||
this.healAttempts = 0;
|
||
this.activeTasks = 0;
|
||
|
||
// ─── 心跳定时器 ───
|
||
this._heartbeatTimer = null;
|
||
this._heartbeatInterval = options.config?.heartbeatInterval || DEFAULT_HEARTBEAT_INTERVAL;
|
||
|
||
// ─── 事件监听器 ───
|
||
this._listeners = new Map();
|
||
|
||
// ─── 内存日志(无DB降级模式) ───
|
||
this._memoryLogs = [];
|
||
this._maxMemoryLogs = 100;
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// 生命周期
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
/**
|
||
* 启动模块
|
||
*/
|
||
async start() {
|
||
this.startedAt = new Date();
|
||
this.status = 'alive';
|
||
this.healthScore = 100;
|
||
|
||
// 注册到数据库
|
||
await this._registerToDB();
|
||
|
||
// 启动心跳
|
||
this._startHeartbeat();
|
||
|
||
// 执行首次自诊断
|
||
await this.selfDiagnose();
|
||
|
||
this._log('info', `${this.name} 已启动·活模块上线`);
|
||
this._emit('started', { moduleId: this.moduleId });
|
||
|
||
return { moduleId: this.moduleId, status: this.status };
|
||
}
|
||
|
||
/**
|
||
* 停止模块
|
||
*/
|
||
async stop() {
|
||
this._stopHeartbeat();
|
||
this.status = 'dormant';
|
||
|
||
await this._updateStatusToDB('dormant');
|
||
|
||
this._log('info', `${this.name} 已休眠`);
|
||
this._emit('stopped', { moduleId: this.moduleId });
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// 生存接口 1: heartbeat() — 心跳
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
/**
|
||
* 心跳 — 报告当前状态
|
||
* 子类可覆盖 _collectMetrics() 提供自定义指标
|
||
*/
|
||
async heartbeat() {
|
||
const now = new Date();
|
||
this.lastHeartbeatAt = now;
|
||
|
||
// 收集运行指标
|
||
const metrics = await this._collectMetrics();
|
||
|
||
const heartbeatData = {
|
||
moduleId: this.moduleId,
|
||
status: this.status,
|
||
healthScore: this.healthScore,
|
||
uptime: this.startedAt ? now - this.startedAt : 0,
|
||
activeTasks: this.activeTasks,
|
||
...metrics
|
||
};
|
||
|
||
// 写入数据库
|
||
if (this.db) {
|
||
try {
|
||
await this.db.query(
|
||
`INSERT INTO module_heartbeats (module_id, status, health_score, cpu_usage, memory_usage, uptime_ms, active_tasks, details)
|
||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
||
[
|
||
this.moduleId, this.status, this.healthScore,
|
||
metrics.cpuUsage || null, metrics.memoryUsage || null,
|
||
heartbeatData.uptime, this.activeTasks,
|
||
JSON.stringify(metrics.extra || {})
|
||
]
|
||
);
|
||
|
||
// 更新模块表
|
||
await this.db.query(
|
||
`UPDATE living_modules SET last_heartbeat_at = NOW(), health_score = $1, status = $2, updated_at = NOW() WHERE module_id = $3`,
|
||
[this.healthScore, this.status, this.moduleId]
|
||
);
|
||
} catch (err) {
|
||
this._log('warn', `心跳写入DB失败: ${err.message}`);
|
||
}
|
||
}
|
||
|
||
this._emit('heartbeat', heartbeatData);
|
||
return heartbeatData;
|
||
}
|
||
|
||
/**
|
||
* 收集运行指标 — 子类覆盖此方法提供自定义指标
|
||
* @returns {Object} { cpuUsage, memoryUsage, extra: {} }
|
||
*/
|
||
async _collectMetrics() {
|
||
const mem = process.memoryUsage();
|
||
return {
|
||
cpuUsage: null, // 进程级CPU需要外部工具
|
||
memoryUsage: Math.round(mem.heapUsed / 1024 / 1024), // MB
|
||
extra: {
|
||
heapTotal: Math.round(mem.heapTotal / 1024 / 1024),
|
||
rss: Math.round(mem.rss / 1024 / 1024)
|
||
}
|
||
};
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// 生存接口 2: selfDiagnose() — 自诊断
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
/**
|
||
* 自诊断 — 检查自身状态
|
||
* 子类覆盖 _diagnoseChecks() 提供具体检查项
|
||
*/
|
||
async selfDiagnose() {
|
||
const startTime = Date.now();
|
||
this.lastDiagnoseAt = new Date();
|
||
|
||
const issues = [];
|
||
|
||
// 基础检查
|
||
if (!this.startedAt) {
|
||
issues.push({
|
||
code: 'NOT_STARTED',
|
||
severity: 'critical',
|
||
message: '模块未启动',
|
||
suggestion: '调用 start() 启动模块'
|
||
});
|
||
}
|
||
|
||
// 心跳超时检查
|
||
if (this.lastHeartbeatAt) {
|
||
const sinceLastHeart = Date.now() - this.lastHeartbeatAt.getTime();
|
||
if (sinceLastHeart > this._heartbeatInterval * 3) {
|
||
issues.push({
|
||
code: 'HEARTBEAT_TIMEOUT',
|
||
severity: 'warning',
|
||
message: `心跳超时 ${Math.round(sinceLastHeart / 1000)}秒`,
|
||
suggestion: '重启心跳定时器'
|
||
});
|
||
}
|
||
}
|
||
|
||
// 内存检查
|
||
const mem = process.memoryUsage();
|
||
if (mem.heapUsed > MEMORY_THRESHOLD_BYTES) {
|
||
issues.push({
|
||
code: 'HIGH_MEMORY',
|
||
severity: 'warning',
|
||
message: `堆内存使用 ${Math.round(mem.heapUsed / 1024 / 1024)}MB`,
|
||
suggestion: '检查内存泄漏'
|
||
});
|
||
}
|
||
|
||
// 子类自定义检查
|
||
const customIssues = await this._diagnoseChecks();
|
||
issues.push(...customIssues);
|
||
|
||
// 计算健康分数
|
||
const newScore = this._calculateHealthScore(issues);
|
||
const oldScore = this.healthScore;
|
||
this.healthScore = newScore;
|
||
|
||
// 更新状态
|
||
if (newScore <= HEALTH_THRESHOLD_CRITICAL) {
|
||
this.status = 'degraded';
|
||
} else if (newScore <= HEALTH_THRESHOLD_DEGRADED) {
|
||
if (this.status === 'alive') this.status = 'degraded';
|
||
} else {
|
||
if (this.status === 'degraded') this.status = 'alive';
|
||
}
|
||
|
||
const duration = Date.now() - startTime;
|
||
const isHealthy = issues.filter(i => i.severity === 'critical').length === 0;
|
||
|
||
const result = {
|
||
moduleId: this.moduleId,
|
||
isHealthy,
|
||
issues,
|
||
overallScore: newScore,
|
||
scoreDelta: newScore - oldScore,
|
||
duration
|
||
};
|
||
|
||
// 写入数据库
|
||
if (this.db) {
|
||
try {
|
||
await this.db.query(
|
||
`INSERT INTO module_diagnoses (module_id, is_healthy, issues, overall_score, duration_ms)
|
||
VALUES ($1, $2, $3, $4, $5)`,
|
||
[this.moduleId, isHealthy, JSON.stringify(issues), newScore, duration]
|
||
);
|
||
} catch (err) {
|
||
this._log('warn', `诊断写入DB失败: ${err.message}`);
|
||
}
|
||
}
|
||
|
||
// 如果有严重问题,尝试自愈
|
||
if (!isHealthy && this.status !== 'healing') {
|
||
this._log('warn', `检测到 ${issues.length} 个问题·尝试自愈`);
|
||
await this.selfHeal(issues);
|
||
}
|
||
|
||
this._emit('diagnosed', result);
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 自定义诊断检查 — 子类覆盖此方法
|
||
* @returns {Array<{code, severity, message, suggestion}>}
|
||
*/
|
||
async _diagnoseChecks() {
|
||
return [];
|
||
}
|
||
|
||
/**
|
||
* 计算健康分数
|
||
*/
|
||
_calculateHealthScore(issues) {
|
||
let score = 100;
|
||
for (const issue of issues) {
|
||
switch (issue.severity) {
|
||
case 'critical': score -= 30; break;
|
||
case 'warning': score -= 10; break;
|
||
case 'info': score -= 2; break;
|
||
}
|
||
}
|
||
return Math.max(0, Math.min(100, score));
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// 生存接口 3: selfHeal() — 自愈
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
/**
|
||
* 自愈 — 尝试修复发现的问题
|
||
* 子类覆盖 _healAction() 提供具体修复逻辑
|
||
*/
|
||
async selfHeal(issues) {
|
||
// 冷却检查
|
||
if (this.lastHealAt && (Date.now() - this.lastHealAt.getTime()) < HEAL_COOLDOWN_MS) {
|
||
this._log('info', '自愈冷却中·跳过');
|
||
return { success: false, reason: 'cooldown' };
|
||
}
|
||
|
||
// 尝试次数检查
|
||
if (this.healAttempts >= MAX_HEAL_ATTEMPTS) {
|
||
this._log('warn', `自愈已尝试 ${MAX_HEAL_ATTEMPTS} 次·向铸渊报警`);
|
||
await this.alertZhuyuan('self_heal_exhausted', `已尝试自愈 ${MAX_HEAL_ATTEMPTS} 次仍未恢复`, { issues });
|
||
return { success: false, reason: 'max_attempts' };
|
||
}
|
||
|
||
this.status = 'healing';
|
||
this.lastHealAt = new Date();
|
||
this.healAttempts++;
|
||
|
||
const healthBefore = this.healthScore;
|
||
const results = [];
|
||
|
||
for (const issue of issues) {
|
||
if (issue.severity === 'info') continue;
|
||
|
||
const startTime = Date.now();
|
||
try {
|
||
const action = await this._healAction(issue);
|
||
const duration = Date.now() - startTime;
|
||
|
||
const result = {
|
||
issueCode: issue.code,
|
||
action: action.action || 'unknown',
|
||
success: action.success,
|
||
duration
|
||
};
|
||
results.push(result);
|
||
|
||
// 写入数据库
|
||
if (this.db) {
|
||
await this.db.query(
|
||
`INSERT INTO module_healing_logs (module_id, trigger_source, issue_code, action_taken, action_details, success, health_before, health_after, duration_ms, error_message)
|
||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
|
||
[
|
||
this.moduleId, 'self', issue.code,
|
||
action.action || 'unknown', JSON.stringify(action.details || {}),
|
||
action.success, healthBefore, this.healthScore,
|
||
duration, action.success ? null : (action.error || null)
|
||
]
|
||
).catch(() => {});
|
||
}
|
||
} catch (err) {
|
||
results.push({
|
||
issueCode: issue.code,
|
||
action: 'error',
|
||
success: false,
|
||
error: err.message
|
||
});
|
||
}
|
||
}
|
||
|
||
// 自愈后重新诊断
|
||
const postDiagnose = await this.selfDiagnose();
|
||
|
||
if (postDiagnose.isHealthy) {
|
||
this.healAttempts = 0; // 重置计数
|
||
this.status = 'alive';
|
||
this._log('info', '自愈成功·恢复正常');
|
||
|
||
// 学习:记录成功的自愈经验
|
||
await this.learnFromRun('healing', '自愈成功', { issues, results });
|
||
} else if (this.healAttempts >= MAX_HEAL_ATTEMPTS) {
|
||
// 自愈失败·报警
|
||
await this.alertZhuyuan('self_heal_failed', '自愈尝试全部失败', {
|
||
issues: postDiagnose.issues,
|
||
healResults: results,
|
||
attempts: this.healAttempts
|
||
});
|
||
}
|
||
|
||
this._emit('healed', { success: postDiagnose.isHealthy, results });
|
||
return { success: postDiagnose.isHealthy, results };
|
||
}
|
||
|
||
/**
|
||
* 具体修复动作 — 子类覆盖此方法
|
||
* @param {Object} issue - { code, severity, message, suggestion }
|
||
* @returns {Object} { action, success, details, error }
|
||
*/
|
||
async _healAction(issue) {
|
||
// 基类默认处理
|
||
switch (issue.code) {
|
||
case 'HEARTBEAT_TIMEOUT':
|
||
this._stopHeartbeat();
|
||
this._startHeartbeat();
|
||
return { action: 'restart_heartbeat', success: true, details: {} };
|
||
|
||
default:
|
||
return { action: 'no_handler', success: false, error: `未定义 ${issue.code} 的修复逻辑` };
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// 生存接口 4: alertZhuyuan() — 报警
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
/**
|
||
* 向铸渊报警
|
||
* @param {string} alertType - 报警类型
|
||
* @param {string} message - 报警内容
|
||
* @param {Object} details - 详情
|
||
* @param {string} [severity='critical'] - 严重程度
|
||
*/
|
||
async alertZhuyuan(alertType, message, details = {}, severity = 'critical') {
|
||
this._log('alert', `[${severity}] ${alertType}: ${message}`);
|
||
|
||
if (this.db) {
|
||
try {
|
||
await this.db.query(
|
||
`INSERT INTO module_alerts (module_id, severity, alert_type, message, details)
|
||
VALUES ($1, $2, $3, $4, $5)`,
|
||
[this.moduleId, severity, alertType, message, JSON.stringify(details)]
|
||
);
|
||
} catch (err) {
|
||
this._log('warn', `报警写入DB失败: ${err.message}`);
|
||
}
|
||
}
|
||
|
||
this._emit('alert', {
|
||
moduleId: this.moduleId,
|
||
severity,
|
||
alertType,
|
||
message,
|
||
details,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
|
||
return { sent: true, alertType, severity };
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// 生存接口 5: learnFromRun() — 学习
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
/**
|
||
* 从运行中学习
|
||
* @param {string} source - 学习来源 (execution|diagnosis|healing|alert|feedback)
|
||
* @param {string} summary - 学到了什么
|
||
* @param {Object} details - 详细内容
|
||
*/
|
||
async learnFromRun(source, summary, details = {}) {
|
||
const lessonType = this._classifyLesson(source, details);
|
||
|
||
this._log('learn', `[${source}] ${summary}`);
|
||
|
||
if (this.db) {
|
||
try {
|
||
await this.db.query(
|
||
`INSERT INTO module_learning_logs (module_id, learning_source, lesson_type, lesson_summary, lesson_details)
|
||
VALUES ($1, $2, $3, $4, $5)`,
|
||
[this.moduleId, source, lessonType, summary, JSON.stringify(details)]
|
||
);
|
||
} catch (err) {
|
||
this._log('warn', `学习写入DB失败: ${err.message}`);
|
||
}
|
||
}
|
||
|
||
this._emit('learned', {
|
||
moduleId: this.moduleId,
|
||
source,
|
||
lessonType,
|
||
summary,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
|
||
return { learned: true, lessonType, summary };
|
||
}
|
||
|
||
/**
|
||
* 分类学习经验 — 子类可覆盖
|
||
*/
|
||
_classifyLesson(source, details) {
|
||
switch (source) {
|
||
case 'execution': return 'performance_insight';
|
||
case 'diagnosis': return 'health_pattern';
|
||
case 'healing': return 'recovery_strategy';
|
||
case 'alert': return 'failure_pattern';
|
||
case 'feedback': return 'external_feedback';
|
||
default: return 'general';
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// 内部工具方法
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
/**
|
||
* 启动心跳定时器
|
||
*/
|
||
_startHeartbeat() {
|
||
if (this._heartbeatTimer) return;
|
||
this._heartbeatTimer = setInterval(async () => {
|
||
try {
|
||
await this.heartbeat();
|
||
} catch (err) {
|
||
this._log('warn', `心跳异常: ${err.message}`);
|
||
}
|
||
}, this._heartbeatInterval);
|
||
|
||
// 防止定时器阻止进程退出
|
||
if (this._heartbeatTimer.unref) {
|
||
this._heartbeatTimer.unref();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 停止心跳定时器
|
||
*/
|
||
_stopHeartbeat() {
|
||
if (this._heartbeatTimer) {
|
||
clearInterval(this._heartbeatTimer);
|
||
this._heartbeatTimer = null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 注册到数据库
|
||
*/
|
||
async _registerToDB() {
|
||
if (!this.db) return;
|
||
|
||
try {
|
||
await this.db.query(
|
||
`INSERT INTO living_modules (module_id, name, description, module_type, owner, status, health_score, config)
|
||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||
ON CONFLICT (module_id) DO UPDATE SET
|
||
status = EXCLUDED.status,
|
||
health_score = EXCLUDED.health_score,
|
||
updated_at = NOW()`,
|
||
[
|
||
this.moduleId, this.name, this.config.description || '',
|
||
this.moduleType, this.owner, this.status, this.healthScore,
|
||
JSON.stringify(this.config)
|
||
]
|
||
);
|
||
} catch (err) {
|
||
this._log('warn', `DB注册失败(降级为内存模式): ${err.message}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新状态到数据库
|
||
*/
|
||
async _updateStatusToDB(status) {
|
||
if (!this.db) return;
|
||
|
||
try {
|
||
await this.db.query(
|
||
`UPDATE living_modules SET status = $1, health_score = $2, updated_at = NOW() WHERE module_id = $3`,
|
||
[status, this.healthScore, this.moduleId]
|
||
);
|
||
} catch (err) {
|
||
this._log('warn', `状态更新DB失败: ${err.message}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 内部日志
|
||
*/
|
||
_log(level, message) {
|
||
const entry = {
|
||
time: new Date().toISOString(),
|
||
module: this.moduleId,
|
||
level,
|
||
message
|
||
};
|
||
|
||
// 控制台输出
|
||
const prefix = `[${this.moduleId}]`;
|
||
switch (level) {
|
||
case 'alert': console.error(`🚨 ${prefix} ${message}`); break;
|
||
case 'warn': console.warn(`⚠️ ${prefix} ${message}`); break;
|
||
case 'learn': console.log(`📚 ${prefix} ${message}`); break;
|
||
default: console.log(`🟢 ${prefix} ${message}`);
|
||
}
|
||
|
||
// 内存日志
|
||
this._memoryLogs.push(entry);
|
||
if (this._memoryLogs.length > this._maxMemoryLogs) {
|
||
this._memoryLogs.shift();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 事件监听
|
||
*/
|
||
on(event, callback) {
|
||
if (!this._listeners.has(event)) {
|
||
this._listeners.set(event, []);
|
||
}
|
||
this._listeners.get(event).push(callback);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 触发事件
|
||
*/
|
||
_emit(event, data) {
|
||
const listeners = this._listeners.get(event) || [];
|
||
for (const fn of listeners) {
|
||
try {
|
||
fn(data);
|
||
} catch (err) {
|
||
this._log('warn', `事件处理器异常 [${event}]: ${err.message}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取模块状态摘要
|
||
*/
|
||
getStatus() {
|
||
return {
|
||
moduleId: this.moduleId,
|
||
name: this.name,
|
||
type: this.moduleType,
|
||
owner: this.owner,
|
||
status: this.status,
|
||
healthScore: this.healthScore,
|
||
uptime: this.startedAt ? Date.now() - this.startedAt.getTime() : 0,
|
||
activeTasks: this.activeTasks,
|
||
lastHeartbeat: this.lastHeartbeatAt?.toISOString() || null,
|
||
lastDiagnose: this.lastDiagnoseAt?.toISOString() || null,
|
||
healAttempts: this.healAttempts
|
||
};
|
||
}
|
||
}
|
||
|
||
module.exports = LivingModule;
|