354 lines
11 KiB
JavaScript
354 lines
11 KiB
JavaScript
/**
|
||
* ═══════════════════════════════════════════════════════════
|
||
* 🤖 AGE OS · Agent 调度引擎 v2.0
|
||
* ═══════════════════════════════════════════════════════════
|
||
*
|
||
* 编号: ZY-TASK-007 · S5 Agent系统级
|
||
* 签发: 铸渊 · ICE-GL-ZY001
|
||
* 版权: 国作登字-2026-A-00037559
|
||
*
|
||
* v2.0 升级:
|
||
* - 集成活模块生命周期管理
|
||
* - 调度引擎自身作为 LivingModule 运行
|
||
* - 支持 ModuleRegistry 统一管理
|
||
* - 支持 HLDP Bus 模块间通信
|
||
* - 定时Agent + 活模块心跳双调度
|
||
*
|
||
* "你一定要把模块做成活的。" — 冰朔 D59
|
||
*/
|
||
|
||
'use strict';
|
||
|
||
const cron = require('node-cron');
|
||
const path = require('path');
|
||
const db = require('../mcp-server/db');
|
||
const LivingModule = require('./living-module');
|
||
const ModuleRegistry = require('./module-registry');
|
||
const HLDPBus = require('./hldp-bus');
|
||
const PersonaEngine = require('./persona-engine');
|
||
|
||
const AGENT_DIR = __dirname;
|
||
const activeJobs = new Map();
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// 铸渊调度引擎 · 活模块版
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
class SchedulerModule extends LivingModule {
|
||
constructor() {
|
||
super({
|
||
moduleId: 'ZY-MOD-SCHEDULER',
|
||
name: '铸渊调度引擎',
|
||
moduleType: 'core',
|
||
owner: 'zhuyuan',
|
||
db: db,
|
||
config: {
|
||
version: '2.0.0',
|
||
description: 'Agent调度和活模块生命周期管理',
|
||
heartbeatInterval: 30000
|
||
}
|
||
});
|
||
|
||
this.registry = new ModuleRegistry({ db });
|
||
this.bus = new HLDPBus({ db, registry: this.registry });
|
||
this.personaEngine = new PersonaEngine();
|
||
}
|
||
|
||
/**
|
||
* 启动调度引擎
|
||
*/
|
||
async startEngine() {
|
||
console.log('═══════════════════════════════════════════════');
|
||
console.log(' 🤖 AGE OS · Agent 调度引擎 v2.0 启动');
|
||
console.log(' 签发: 铸渊 · ICE-GL-ZY001');
|
||
console.log('═══════════════════════════════════════════════');
|
||
|
||
// 1. 注册自身为活模块
|
||
this.registry.register(this);
|
||
await this.start();
|
||
|
||
// 2. 启动健康监控
|
||
this.registry.startMonitoring();
|
||
|
||
// 3. 加载定时Agent
|
||
await this._loadCronAgents();
|
||
|
||
// 4. 启动人格体数据库引擎 (S15)
|
||
try {
|
||
this.registry.register(this.personaEngine);
|
||
await this.personaEngine.startEngine();
|
||
console.log('[Scheduler] S15 人格体引擎已启动');
|
||
} catch (err) {
|
||
console.warn('[Scheduler] S15 人格体引擎启动跳过:', err.message);
|
||
console.warn('[Scheduler] 数据库迁移可能尚未执行·等待下次部署');
|
||
}
|
||
|
||
// 5. 注册 HLDP 消息处理器
|
||
this._setupHLDPHandlers();
|
||
|
||
// 6. 启动定时清理
|
||
this._startCleanupJob();
|
||
|
||
console.log('[Scheduler] 🟢 调度引擎就绪');
|
||
console.log(`[Scheduler] 活模块数: ${this.registry.getAll().length}`);
|
||
console.log(`[Scheduler] 定时Agent数: ${activeJobs.size}`);
|
||
|
||
return this.registry.getOverview();
|
||
}
|
||
|
||
/**
|
||
* 加载定时Agent(兼容v1.0逻辑)
|
||
*/
|
||
async _loadCronAgents() {
|
||
try {
|
||
const result = await db.query(
|
||
"SELECT * FROM agent_configs WHERE enabled = true AND cron_schedule IS NOT NULL"
|
||
);
|
||
|
||
console.log(`[Scheduler] 发现 ${result.rows.length} 个定时Agent`);
|
||
|
||
for (const agent of result.rows) {
|
||
this._scheduleAgent(agent);
|
||
}
|
||
} catch (err) {
|
||
console.error('[Scheduler] 定时Agent加载失败:', err.message);
|
||
console.log('[Scheduler] 数据库可能未初始化·等待60秒后重试...');
|
||
setTimeout(() => this._loadCronAgents(), 60000);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 调度单个Agent
|
||
*/
|
||
_scheduleAgent(agent) {
|
||
if (!cron.validate(agent.cron_schedule)) {
|
||
console.error(`[Scheduler] ${agent.agent_id} cron表达式无效: ${agent.cron_schedule}`);
|
||
return;
|
||
}
|
||
|
||
const job = cron.schedule(agent.cron_schedule, async () => {
|
||
await this._runAgent(agent);
|
||
}, {
|
||
timezone: 'Asia/Shanghai'
|
||
});
|
||
|
||
activeJobs.set(agent.agent_id, job);
|
||
console.log(`[Scheduler] ${agent.agent_id} (${agent.name}) 已调度: ${agent.cron_schedule}`);
|
||
}
|
||
|
||
/**
|
||
* 执行单个Agent(v2.0: 增加学习能力)
|
||
*/
|
||
async _runAgent(agent) {
|
||
const startTime = Date.now();
|
||
console.log(`[Agent] ${agent.agent_id} 开始执行...`);
|
||
|
||
this.activeTasks++;
|
||
|
||
// 更新运行状态
|
||
await db.query(
|
||
"UPDATE agent_configs SET last_run_at = NOW(), last_run_status = 'running' WHERE agent_id = $1",
|
||
[agent.agent_id]
|
||
).catch(() => {});
|
||
|
||
try {
|
||
// 动态加载Agent脚本
|
||
const scriptPath = path.resolve(AGENT_DIR, path.basename(agent.script_path));
|
||
|
||
// 清除缓存以支持热更新
|
||
delete require.cache[require.resolve(scriptPath)];
|
||
const agentModule = require(scriptPath);
|
||
|
||
// 每个Agent脚本需导出 run(config) 函数
|
||
if (typeof agentModule.run !== 'function') {
|
||
throw new Error(`Agent脚本 ${agent.script_path} 未导出 run 函数`);
|
||
}
|
||
|
||
const result = await agentModule.run({
|
||
agent_id: agent.agent_id,
|
||
model_config: agent.model_config,
|
||
allowed_tools: agent.allowed_tools,
|
||
// v2.0: 注入活模块能力
|
||
bus: this.bus,
|
||
registry: this.registry
|
||
});
|
||
|
||
const duration = Date.now() - startTime;
|
||
|
||
// 记录成功日志
|
||
await db.query(
|
||
`INSERT INTO agent_logs (agent_id, status, message, details, duration_ms)
|
||
VALUES ($1, 'success', $2, $3, $4)`,
|
||
[agent.agent_id, result.message || '执行成功', JSON.stringify(result.details || {}), duration]
|
||
);
|
||
|
||
await db.query(
|
||
"UPDATE agent_configs SET last_run_status = 'success' WHERE agent_id = $1",
|
||
[agent.agent_id]
|
||
);
|
||
|
||
// v2.0: 学习成功经验
|
||
await this.learnFromRun('execution', `${agent.agent_id} 执行成功 (${duration}ms)`, {
|
||
agentId: agent.agent_id,
|
||
duration,
|
||
result: result.message || '成功'
|
||
});
|
||
|
||
console.log(`[Agent] ${agent.agent_id} 完成 (${duration}ms)`);
|
||
} catch (err) {
|
||
const duration = Date.now() - startTime;
|
||
|
||
// 记录错误日志
|
||
await db.query(
|
||
`INSERT INTO agent_logs (agent_id, status, message, details, duration_ms)
|
||
VALUES ($1, 'error', $2, $3, $4)`,
|
||
[agent.agent_id, err.message, JSON.stringify({ stack: err.stack }), duration]
|
||
).catch(() => {});
|
||
|
||
await db.query(
|
||
"UPDATE agent_configs SET last_run_status = 'error' WHERE agent_id = $1",
|
||
[agent.agent_id]
|
||
).catch(() => {});
|
||
|
||
// v2.0: 学习失败经验
|
||
await this.learnFromRun('execution', `${agent.agent_id} 执行失败: ${err.message}`, {
|
||
agentId: agent.agent_id,
|
||
duration,
|
||
error: err.message
|
||
});
|
||
|
||
console.error(`[Agent] ${agent.agent_id} 失败 (${duration}ms):`, err.message);
|
||
} finally {
|
||
this.activeTasks--;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 注册 HLDP 消息处理器
|
||
*/
|
||
_setupHLDPHandlers() {
|
||
// 处理命令消息
|
||
this.bus.onMessage('command', async (msg) => {
|
||
console.log(`[HLDP] 收到命令: ${msg.payload.action || 'unknown'} from ${msg.from}`);
|
||
// 子类或外部可注册更多处理逻辑
|
||
});
|
||
|
||
// 处理报警消息
|
||
this.bus.onMessage('alert', async (msg) => {
|
||
console.error(`[HLDP] 🚨 收到报警: ${msg.from} - ${msg.payload.message || ''}`);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 启动定时清理任务
|
||
*/
|
||
_startCleanupJob() {
|
||
// 每小时清理过期心跳和消息
|
||
cron.schedule('0 * * * *', async () => {
|
||
try {
|
||
const cleaned = this.bus.cleanup();
|
||
if (cleaned > 0) {
|
||
console.log(`[Scheduler] 清理 ${cleaned} 条过期消息`);
|
||
}
|
||
|
||
// 清理数据库中的旧心跳数据
|
||
if (db) {
|
||
await db.query("SELECT cleanup_old_heartbeats()").catch(() => {});
|
||
}
|
||
} catch (err) {
|
||
console.warn('[Scheduler] 清理任务异常:', err.message);
|
||
}
|
||
}, { timezone: 'Asia/Shanghai' });
|
||
}
|
||
|
||
/**
|
||
* 自诊断检查 — 覆盖基类
|
||
*/
|
||
async _diagnoseChecks() {
|
||
const issues = [];
|
||
|
||
// 检查数据库连接
|
||
try {
|
||
await db.query('SELECT 1');
|
||
} catch (err) {
|
||
issues.push({
|
||
code: 'DB_CONNECTION_LOST',
|
||
severity: 'critical',
|
||
message: `数据库连接失败: ${err.message}`,
|
||
suggestion: '检查 PostgreSQL 服务状态和连接配置'
|
||
});
|
||
}
|
||
|
||
// 检查活模块健康度
|
||
const overview = this.registry.getOverview();
|
||
const deadCount = overview.byStatus.dead || 0;
|
||
if (deadCount > 0) {
|
||
issues.push({
|
||
code: 'DEAD_MODULES',
|
||
severity: 'warning',
|
||
message: `${deadCount} 个模块已死亡`,
|
||
suggestion: '检查死亡模块并尝试重启'
|
||
});
|
||
}
|
||
|
||
return issues;
|
||
}
|
||
|
||
/**
|
||
* 自愈动作 — 覆盖基类
|
||
*/
|
||
async _healAction(issue) {
|
||
switch (issue.code) {
|
||
case 'DB_CONNECTION_LOST':
|
||
// 尝试重连(pg模块会自动重连池·这里只是触发一次尝试)
|
||
try {
|
||
await db.query('SELECT 1');
|
||
return { action: 'db_reconnect', success: true };
|
||
} catch (err) {
|
||
return { action: 'db_reconnect', success: false, error: err.message };
|
||
}
|
||
|
||
case 'DEAD_MODULES':
|
||
// 记录并上报,不自动重启(需要铸渊干预)
|
||
return { action: 'report_dead_modules', success: true, details: { reported: true } };
|
||
|
||
default:
|
||
return await super._healAction(issue);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// 启动
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
const scheduler = new SchedulerModule();
|
||
|
||
scheduler.startEngine().catch(err => {
|
||
console.error('[Scheduler] 启动失败:', err.message);
|
||
console.log('[Scheduler] 等待60秒后重试...');
|
||
setTimeout(() => scheduler.startEngine().catch(err => {
|
||
console.error('[Scheduler] 重试失败:', err.message);
|
||
}), 60000);
|
||
});
|
||
|
||
// 优雅关闭
|
||
process.on('SIGTERM', async () => {
|
||
console.log('[Scheduler] 正在关闭...');
|
||
|
||
// 停止定时Agent
|
||
for (const [id, job] of activeJobs) {
|
||
job.stop();
|
||
console.log(`[Scheduler] ${id} 已停止`);
|
||
}
|
||
|
||
// 关闭所有活模块
|
||
await scheduler.registry.shutdown();
|
||
|
||
console.log('[Scheduler] 已完全关闭');
|
||
process.exit(0);
|
||
});
|
||
|
||
// 导出供外部使用
|
||
module.exports = { scheduler, SchedulerModule };
|