zhizhi/server/age-os/agents/scheduler.js

144 lines
4.3 KiB
JavaScript
Raw 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.

/**
* ═══════════════════════════════════════════════════════════
* 🤖 AGE OS · Agent 调度引擎
* ═══════════════════════════════════════════════════════════
*
* 签发: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* 从 agent_configs 表读取配置,用 node-cron 调度定时任务。
* 每个Agent是一个独立的JS脚本调度器负责按时触发并记录日志。
*/
'use strict';
const cron = require('node-cron');
const path = require('path');
const db = require('../mcp-server/db');
const AGENT_DIR = __dirname;
const activeJobs = new Map();
/**
* 加载并启动所有启用的定时Agent
*/
async function startScheduler() {
console.log('[Scheduler] AGE OS Agent 调度引擎启动...');
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) {
scheduleAgent(agent);
}
console.log('[Scheduler] 所有Agent已调度');
} catch (err) {
console.error('[Scheduler] 启动失败:', err.message);
console.log('[Scheduler] 数据库可能未初始化等待60秒后重试...');
setTimeout(startScheduler, 60000);
}
}
/**
* 调度单个Agent
*/
function 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 runAgent(agent);
}, {
timezone: 'Asia/Shanghai'
});
activeJobs.set(agent.agent_id, job);
console.log(`[Scheduler] ${agent.agent_id} (${agent.name}) 已调度: ${agent.cron_schedule}`);
}
/**
* 执行单个Agent
*/
async function runAgent(agent) {
const startTime = Date.now();
console.log(`[Agent] ${agent.agent_id} 开始执行...`);
// 更新运行状态
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
});
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]
);
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(() => {});
console.error(`[Agent] ${agent.agent_id} 失败 (${duration}ms):`, err.message);
}
}
// ─── 启动 ───
startScheduler();
// 优雅关闭
process.on('SIGTERM', () => {
console.log('[Scheduler] 停止所有Agent...');
for (const [id, job] of activeJobs) {
job.stop();
console.log(`[Scheduler] ${id} 已停止`);
}
process.exit(0);
});