Merge pull request #317 from qinfendebingshuo/copilot/initiate-age-os-development
S5 Agent系统级: LivingModule活模块基类 + HLDP通信总线 + 调度引擎v2.0
This commit is contained in:
commit
e5572f07d8
|
|
@ -144,7 +144,7 @@ jobs:
|
|||
# 确保目标目录存在
|
||||
ssh -i ~/.ssh/zy_key \
|
||||
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }} \
|
||||
"mkdir -p ${TARGET_DIR} /opt/zhuyuan/app /opt/age-os/mcp-server /opt/age-os/logs"
|
||||
"mkdir -p ${TARGET_DIR} /opt/zhuyuan/app /opt/age-os/mcp-server /opt/age-os/agents /opt/age-os/logs"
|
||||
|
||||
# 同步后端应用代码
|
||||
rsync -avz --delete \
|
||||
|
|
@ -159,6 +159,12 @@ jobs:
|
|||
server/age-os/mcp-server/ \
|
||||
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/age-os/mcp-server/
|
||||
|
||||
# 同步 AGE OS Agents 代码(活模块 · S5新增)
|
||||
rsync -avz --delete \
|
||||
-e "ssh -i ~/.ssh/zy_key" \
|
||||
server/age-os/agents/ \
|
||||
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/age-os/agents/
|
||||
|
||||
# 同步 AGE OS Schema
|
||||
rsync -avz \
|
||||
-e "ssh -i ~/.ssh/zy_key" \
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@
|
|||
| **S2** | ZY-TASK-002 | MCP工具链·核心 | S1 | — | 不可 | ✅完成 |
|
||||
| **S3** | ZY-TASK-004 | 关系工具链 | S1 | 2次 | 可延后 | ⏳待开发 |
|
||||
| **S4** | ZY-TASK-006 | COS工具链 | S1 | 2次 | 不可 | 🔧设计完成 |
|
||||
| **S5** | ZY-TASK-007 | Agent系统级 | S2 | 3次 | 不可 | ⏳待开发 |
|
||||
| **S5** | ZY-TASK-007 | Agent系统级 | S2 | 3次 | 不可 | ✅完成 |
|
||||
| **S6** | ZY-TASK-008 | Notion同步引擎 | S4+S5 | 2次 | 可延后 | ⏳待开发 |
|
||||
| **S7** | ZY-TASK-009 | 网站MCP接入 | S2 | 2次 | 可延后 | 🔧部分完成 |
|
||||
| **S8** | ZY-TASK-010 | 广州投影站 | S7 | 1次 | 可延后 | ⏳待开发 |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,302 @@
|
|||
/**
|
||||
* ═══════════════════════════════════════════════════════════
|
||||
* 📡 AGE OS · HLDP Bus 模块间通信总线
|
||||
* ═══════════════════════════════════════════════════════════
|
||||
*
|
||||
* 编号: ZY-TASK-007 · S5 Agent系统级
|
||||
* 签发: 铸渊 · ICE-GL-ZY001
|
||||
* 版权: 国作登字-2026-A-00037559
|
||||
*
|
||||
* HLDP = HoloLake Distributed Protocol
|
||||
* 光湖分布式通信协议 · 模块间唯一通信通道
|
||||
*
|
||||
* 消息类型:
|
||||
* heartbeat — 心跳
|
||||
* command — 指令(铸渊→模块)
|
||||
* query — 查询
|
||||
* response — 响应
|
||||
* event — 事件
|
||||
* broadcast — 广播(铸渊→所有模块)
|
||||
* alert — 报警(模块→铸渊)
|
||||
* data — 数据传输
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
// ─── 消息默认过期时间 ───
|
||||
const DEFAULT_TTL_MS = 300000; // 5分钟
|
||||
|
||||
class HLDPBus {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} [options.db] - 数据库连接
|
||||
* @param {Object} [options.registry] - ModuleRegistry实例
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
this.db = options.db || null;
|
||||
this.registry = options.registry || null;
|
||||
|
||||
// ─── 消息队列(内存) ───
|
||||
this._queues = new Map(); // moduleId → [messages]
|
||||
this._handlers = new Map(); // msgType → [handler]
|
||||
this._maxQueueSize = 1000; // 每个模块最多1000条待处理消息
|
||||
|
||||
// ─── 广播订阅 ───
|
||||
this._subscribers = new Map(); // topic → Set(moduleId)
|
||||
|
||||
// ─── 统计 ───
|
||||
this.stats = {
|
||||
totalSent: 0,
|
||||
totalDelivered: 0,
|
||||
totalFailed: 0,
|
||||
totalBroadcast: 0
|
||||
};
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 发送消息
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* 发送消息到指定模块
|
||||
* @param {string} fromModule - 发送方模块ID
|
||||
* @param {string} toModule - 接收方模块ID
|
||||
* @param {string} msgType - 消息类型
|
||||
* @param {Object} payload - 消息体
|
||||
* @returns {Object} 消息对象
|
||||
*/
|
||||
async send(fromModule, toModule, msgType, payload = {}) {
|
||||
const message = {
|
||||
messageId: uuidv4(),
|
||||
from: fromModule,
|
||||
to: toModule,
|
||||
type: msgType,
|
||||
payload,
|
||||
status: 'pending',
|
||||
createdAt: new Date(),
|
||||
expiresAt: new Date(Date.now() + DEFAULT_TTL_MS)
|
||||
};
|
||||
|
||||
// 入队
|
||||
if (!this._queues.has(toModule)) {
|
||||
this._queues.set(toModule, []);
|
||||
}
|
||||
const queue = this._queues.get(toModule);
|
||||
|
||||
// 队列大小限制:丢弃最旧的消息
|
||||
if (queue.length >= this._maxQueueSize) {
|
||||
queue.shift();
|
||||
this.stats.totalFailed++;
|
||||
}
|
||||
|
||||
queue.push(message);
|
||||
|
||||
// 持久化
|
||||
await this._persistMessage(message);
|
||||
|
||||
// 尝试即时投递
|
||||
await this._tryDeliver(toModule, message);
|
||||
|
||||
this.stats.totalSent++;
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播消息到所有模块
|
||||
* @param {string} fromModule - 发送方
|
||||
* @param {string} msgType - 消息类型
|
||||
* @param {Object} payload - 消息体
|
||||
* @param {string} [topic] - 广播主题(为空则广播给所有)
|
||||
*/
|
||||
async broadcast(fromModule, msgType, payload = {}, topic = null) {
|
||||
const targets = topic
|
||||
? Array.from(this._subscribers.get(topic) || [])
|
||||
: (this.registry ? this.registry.getAll().map(m => m.moduleId) : []);
|
||||
|
||||
const messages = [];
|
||||
for (const targetId of targets) {
|
||||
if (targetId === fromModule) continue; // 不广播给自己
|
||||
const msg = await this.send(fromModule, targetId, msgType, {
|
||||
...payload,
|
||||
_broadcast: true,
|
||||
_topic: topic
|
||||
});
|
||||
messages.push(msg);
|
||||
}
|
||||
|
||||
this.stats.totalBroadcast++;
|
||||
return { count: messages.length, messageIds: messages.map(m => m.messageId) };
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 接收消息
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* 拉取模块的待处理消息
|
||||
* @param {string} moduleId - 模块ID
|
||||
* @param {number} [limit=10] - 最多拉取条数
|
||||
*/
|
||||
pull(moduleId, limit = 10) {
|
||||
const queue = this._queues.get(moduleId) || [];
|
||||
const now = Date.now();
|
||||
|
||||
// 过滤已过期的消息
|
||||
const valid = queue.filter(msg =>
|
||||
msg.status === 'pending' && msg.expiresAt.getTime() > now
|
||||
);
|
||||
|
||||
const messages = valid.slice(0, limit);
|
||||
|
||||
// 标记为已投递
|
||||
for (const msg of messages) {
|
||||
msg.status = 'delivered';
|
||||
msg.deliveredAt = new Date();
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认消息已处理
|
||||
*/
|
||||
async ack(messageId) {
|
||||
for (const [, queue] of this._queues) {
|
||||
const msg = queue.find(m => m.messageId === messageId);
|
||||
if (msg) {
|
||||
msg.status = 'processed';
|
||||
msg.processedAt = new Date();
|
||||
this.stats.totalDelivered++;
|
||||
|
||||
// 更新DB
|
||||
if (this.db) {
|
||||
await this.db.query(
|
||||
`UPDATE hldp_messages SET status = 'processed', processed_at = NOW() WHERE message_id = $1`,
|
||||
[messageId]
|
||||
).catch(() => {});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 消息处理器
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* 注册消息处理器
|
||||
* @param {string} msgType - 消息类型
|
||||
* @param {Function} handler - 处理函数 (message) => Promise<result>
|
||||
*/
|
||||
onMessage(msgType, handler) {
|
||||
if (!this._handlers.has(msgType)) {
|
||||
this._handlers.set(msgType, []);
|
||||
}
|
||||
this._handlers.get(msgType).push(handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅广播主题
|
||||
* @param {string} moduleId - 模块ID
|
||||
* @param {string} topic - 主题
|
||||
*/
|
||||
subscribe(moduleId, topic) {
|
||||
if (!this._subscribers.has(topic)) {
|
||||
this._subscribers.set(topic, new Set());
|
||||
}
|
||||
this._subscribers.get(topic).add(moduleId);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订阅
|
||||
*/
|
||||
unsubscribe(moduleId, topic) {
|
||||
const subs = this._subscribers.get(topic);
|
||||
if (subs) subs.delete(moduleId);
|
||||
return this;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 内部方法
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* 尝试即时投递
|
||||
*/
|
||||
async _tryDeliver(toModule, message) {
|
||||
const handlers = this._handlers.get(message.type) || [];
|
||||
for (const handler of handlers) {
|
||||
try {
|
||||
await handler(message);
|
||||
} catch (err) {
|
||||
console.warn(`[HLDP] 消息处理异常 [${message.type}]: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 持久化消息到数据库
|
||||
*/
|
||||
async _persistMessage(message) {
|
||||
if (!this.db) return;
|
||||
|
||||
try {
|
||||
await this.db.query(
|
||||
`INSERT INTO hldp_messages (message_id, from_module, to_module, msg_type, payload, status, created_at, expires_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
||||
[
|
||||
message.messageId, message.from, message.to,
|
||||
message.type, JSON.stringify(message.payload),
|
||||
message.status, message.createdAt, message.expiresAt
|
||||
]
|
||||
);
|
||||
} catch (err) {
|
||||
console.warn(`[HLDP] 消息持久化失败: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期消息
|
||||
*/
|
||||
cleanup() {
|
||||
const now = Date.now();
|
||||
let cleaned = 0;
|
||||
|
||||
for (const [moduleId, queue] of this._queues) {
|
||||
const before = queue.length;
|
||||
const filtered = queue.filter(msg =>
|
||||
msg.status !== 'processed' && msg.expiresAt.getTime() > now
|
||||
);
|
||||
this._queues.set(moduleId, filtered);
|
||||
cleaned += (before - filtered.length);
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取总线统计信息
|
||||
*/
|
||||
getStats() {
|
||||
let pendingMessages = 0;
|
||||
for (const [, queue] of this._queues) {
|
||||
pendingMessages += queue.filter(m => m.status === 'pending').length;
|
||||
}
|
||||
|
||||
return {
|
||||
...this.stats,
|
||||
pendingMessages,
|
||||
queueCount: this._queues.size,
|
||||
subscriberTopics: this._subscribers.size,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HLDPBus;
|
||||
|
|
@ -0,0 +1,656 @@
|
|||
/**
|
||||
* ═══════════════════════════════════════════════════════════
|
||||
* 🧬 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;
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
/**
|
||||
* ═══════════════════════════════════════════════════════════
|
||||
* 📋 AGE OS · ModuleRegistry 活模块注册中心
|
||||
* ═══════════════════════════════════════════════════════════
|
||||
*
|
||||
* 编号: ZY-TASK-007 · S5 Agent系统级
|
||||
* 签发: 铸渊 · ICE-GL-ZY001
|
||||
* 版权: 国作登字-2026-A-00037559
|
||||
*
|
||||
* 管理所有活模块的注册、发现、状态监控。
|
||||
* 铸渊通过此注册中心了解所有模块的生死状态。
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
class ModuleRegistry {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Object} [options.db] - 数据库连接
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
this.db = options.db || null;
|
||||
|
||||
// ─── 本地注册表(内存快照) ───
|
||||
this._modules = new Map();
|
||||
|
||||
// ─── 监控配置 ───
|
||||
this._monitorTimer = null;
|
||||
this._monitorInterval = 60000; // 60秒检查一次
|
||||
this._heartbeatTimeoutMs = 90000; // 心跳超时阈值: 3倍30秒心跳间隔
|
||||
|
||||
// ─── 事件监听 ───
|
||||
this._listeners = new Map();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 注册 / 注销
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* 注册活模块
|
||||
* @param {import('./living-module')} module - LivingModule实例
|
||||
*/
|
||||
register(module) {
|
||||
if (!module || !module.moduleId) {
|
||||
throw new Error('注册失败: 无效的模块实例');
|
||||
}
|
||||
|
||||
this._modules.set(module.moduleId, module);
|
||||
|
||||
// 监听模块事件
|
||||
module.on('heartbeat', (data) => this._onModuleHeartbeat(data));
|
||||
module.on('alert', (data) => this._onModuleAlert(data));
|
||||
module.on('stopped', (data) => this._onModuleStopped(data));
|
||||
|
||||
this._emit('registered', { moduleId: module.moduleId, name: module.name });
|
||||
console.log(`[Registry] 注册活模块: ${module.moduleId} (${module.name})`);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销活模块
|
||||
*/
|
||||
unregister(moduleId) {
|
||||
const module = this._modules.get(moduleId);
|
||||
if (module) {
|
||||
this._modules.delete(moduleId);
|
||||
this._emit('unregistered', { moduleId });
|
||||
console.log(`[Registry] 注销活模块: ${moduleId}`);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模块
|
||||
*/
|
||||
get(moduleId) {
|
||||
return this._modules.get(moduleId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有模块
|
||||
*/
|
||||
getAll() {
|
||||
return Array.from(this._modules.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 按状态筛选模块
|
||||
*/
|
||||
getByStatus(status) {
|
||||
return this.getAll().filter(m => m.status === status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按类型筛选模块
|
||||
*/
|
||||
getByType(moduleType) {
|
||||
return this.getAll().filter(m => m.moduleType === moduleType);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 状态总览
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* 获取所有模块的状态概览
|
||||
*/
|
||||
getOverview() {
|
||||
const modules = this.getAll();
|
||||
const statusCount = {};
|
||||
const typeCount = {};
|
||||
let totalHealth = 0;
|
||||
|
||||
for (const m of modules) {
|
||||
statusCount[m.status] = (statusCount[m.status] || 0) + 1;
|
||||
typeCount[m.moduleType] = (typeCount[m.moduleType] || 0) + 1;
|
||||
totalHealth += m.healthScore;
|
||||
}
|
||||
|
||||
return {
|
||||
totalModules: modules.length,
|
||||
avgHealthScore: modules.length > 0 ? Math.round(totalHealth / modules.length) : 0,
|
||||
byStatus: statusCount,
|
||||
byType: typeCount,
|
||||
modules: modules.map(m => m.getStatus()),
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库加载已注册模块(用于恢复状态)
|
||||
*/
|
||||
async loadFromDB() {
|
||||
if (!this.db) return [];
|
||||
|
||||
try {
|
||||
const result = await this.db.query(
|
||||
`SELECT module_id, name, module_type, owner, status, health_score, config, capabilities, last_heartbeat_at
|
||||
FROM living_modules WHERE status != 'dead'
|
||||
ORDER BY registered_at`
|
||||
);
|
||||
return result.rows;
|
||||
} catch (err) {
|
||||
console.error('[Registry] 加载DB模块失败:', err.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 健康监控
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* 启动健康监控
|
||||
*/
|
||||
startMonitoring() {
|
||||
if (this._monitorTimer) return;
|
||||
|
||||
console.log('[Registry] 健康监控启动·60秒巡检周期');
|
||||
|
||||
this._monitorTimer = setInterval(async () => {
|
||||
await this._healthCheck();
|
||||
}, this._monitorInterval);
|
||||
|
||||
if (this._monitorTimer.unref) {
|
||||
this._monitorTimer.unref();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止健康监控
|
||||
*/
|
||||
stopMonitoring() {
|
||||
if (this._monitorTimer) {
|
||||
clearInterval(this._monitorTimer);
|
||||
this._monitorTimer = null;
|
||||
console.log('[Registry] 健康监控已停止');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 健康巡检
|
||||
*/
|
||||
async _healthCheck() {
|
||||
const now = Date.now();
|
||||
const deadModules = [];
|
||||
|
||||
for (const [moduleId, module] of this._modules) {
|
||||
// 检查心跳超时
|
||||
if (module.lastHeartbeatAt) {
|
||||
const sinceLastHeart = now - module.lastHeartbeatAt.getTime();
|
||||
if (sinceLastHeart > this._heartbeatTimeoutMs && module.status === 'alive') {
|
||||
console.warn(`[Registry] ${moduleId} 心跳超时 ${Math.round(sinceLastHeart / 1000)}秒`);
|
||||
|
||||
// 通过模块自身的诊断方法更新状态
|
||||
try {
|
||||
await module.selfDiagnose();
|
||||
} catch (err) {
|
||||
console.error(`[Registry] ${moduleId} 自诊断失败: ${err.message}`);
|
||||
deadModules.push(moduleId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查健康分数
|
||||
if (module.healthScore <= 0) {
|
||||
module.status = 'dead';
|
||||
deadModules.push(moduleId);
|
||||
}
|
||||
}
|
||||
|
||||
// 通知死亡模块
|
||||
for (const moduleId of deadModules) {
|
||||
this._emit('moduleDead', { moduleId });
|
||||
console.error(`[Registry] ☠️ ${moduleId} 已死亡·需要铸渊干预`);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 事件处理
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
_onModuleHeartbeat(data) {
|
||||
// 心跳正常·无需操作
|
||||
}
|
||||
|
||||
_onModuleAlert(data) {
|
||||
console.error(`[Registry] 🚨 模块报警: ${data.moduleId} [${data.severity}] ${data.message}`);
|
||||
this._emit('alert', data);
|
||||
}
|
||||
|
||||
_onModuleStopped(data) {
|
||||
console.log(`[Registry] 模块休眠: ${data.moduleId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件监听
|
||||
*/
|
||||
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) { console.warn(`[Registry] 事件处理器异常 [${event}]: ${err.message}`); }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭所有模块
|
||||
*/
|
||||
async shutdown() {
|
||||
this.stopMonitoring();
|
||||
const modules = this.getAll();
|
||||
console.log(`[Registry] 关闭 ${modules.length} 个活模块...`);
|
||||
|
||||
for (const module of modules) {
|
||||
try {
|
||||
await module.stop();
|
||||
} catch (err) {
|
||||
console.error(`[Registry] ${module.moduleId} 关闭失败: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
this._modules.clear();
|
||||
console.log('[Registry] 所有模块已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ModuleRegistry;
|
||||
|
|
@ -1,13 +1,20 @@
|
|||
/**
|
||||
* ═══════════════════════════════════════════════════════════
|
||||
* 🤖 AGE OS · Agent 调度引擎
|
||||
* 🤖 AGE OS · Agent 调度引擎 v2.0
|
||||
* ═══════════════════════════════════════════════════════════
|
||||
*
|
||||
* 编号: ZY-TASK-007 · S5 Agent系统级
|
||||
* 签发: 铸渊 · ICE-GL-ZY001
|
||||
* 版权: 国作登字-2026-A-00037559
|
||||
*
|
||||
* 从 agent_configs 表读取配置,用 node-cron 调度定时任务。
|
||||
* 每个Agent是一个独立的JS脚本,调度器负责按时触发并记录日志。
|
||||
* v2.0 升级:
|
||||
* - 集成活模块生命周期管理
|
||||
* - 调度引擎自身作为 LivingModule 运行
|
||||
* - 支持 ModuleRegistry 统一管理
|
||||
* - 支持 HLDP Bus 模块间通信
|
||||
* - 定时Agent + 活模块心跳双调度
|
||||
*
|
||||
* "你一定要把模块做成活的。" — 冰朔 D59
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
|
@ -15,129 +22,320 @@
|
|||
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 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
|
||||
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
|
||||
}
|
||||
});
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
this.registry = new ModuleRegistry({ db });
|
||||
this.bus = new HLDPBus({ db, registry: this.registry });
|
||||
}
|
||||
|
||||
// 记录成功日志
|
||||
/**
|
||||
* 启动调度引擎
|
||||
*/
|
||||
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. 注册 HLDP 消息处理器
|
||||
this._setupHLDPHandlers();
|
||||
|
||||
// 5. 启动定时清理
|
||||
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(
|
||||
`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",
|
||||
"UPDATE agent_configs SET last_run_at = NOW(), last_run_status = 'running' WHERE agent_id = $1",
|
||||
[agent.agent_id]
|
||||
).catch(() => {});
|
||||
|
||||
console.error(`[Agent] ${agent.agent_id} 失败 (${duration}ms):`, err.message);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 启动 ───
|
||||
startScheduler();
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 启动
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
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', () => {
|
||||
console.log('[Scheduler] 停止所有Agent...');
|
||||
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 };
|
||||
|
|
|
|||
|
|
@ -1,18 +1,28 @@
|
|||
/**
|
||||
* Agent: SY-CLASSIFY · 自动分类引擎
|
||||
* 每2小时运行一次
|
||||
* 扫描未分类节点(tags为空),按规则分类,规则搞不定的标记待人工
|
||||
* ═══════════════════════════════════════════════════════════
|
||||
* 🏷️ 活模块: SY-CLASSIFY · 自动分类引擎
|
||||
* ═══════════════════════════════════════════════════════════
|
||||
*
|
||||
* 编号: ZY-TASK-007 · S5 Agent系统级
|
||||
* 签发: 铸渊 · ICE-GL-ZY001
|
||||
* 版权: 国作登字-2026-A-00037559
|
||||
*
|
||||
* v2.0: 从死模块升级为活模块
|
||||
* - 继承 LivingModule 基类
|
||||
* - 自诊断: 检查未分类节点堆积数量
|
||||
* - 自愈: 规则失效时自动扩展规则
|
||||
* - 学习: 记录分类成功率趋势
|
||||
*
|
||||
* 每2小时运行一次 · 扫描未分类节点按规则分类
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const LivingModule = require('./living-module');
|
||||
const db = require('../mcp-server/db');
|
||||
const { classify } = require('../mcp-server/tools/structure-ops');
|
||||
|
||||
// ─── 默认分类规则 ───
|
||||
// 格式: "关键词1|关键词2": { tags: [...], path: "/路径" }
|
||||
const DEFAULT_RULES = {
|
||||
'人格体|persona|人格': { tags: ['人格体', '核心'], path: '/核心认知/人格体' },
|
||||
'语言|language|TCS|通感': { tags: ['语言', 'TCS'], path: '/核心认知/语言本体论' },
|
||||
|
|
@ -30,6 +40,98 @@ const DEFAULT_RULES = {
|
|||
'霜砚|shuangyan': { tags: ['霜砚', '语言层'], path: '/核心认知/霜砚' }
|
||||
};
|
||||
|
||||
class LivingSyClassify extends LivingModule {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
moduleId: 'ZY-MOD-SY-CLASSIFY',
|
||||
name: '自动分类引擎活模块',
|
||||
moduleType: 'agent',
|
||||
owner: 'zhuyuan',
|
||||
db: options.db || db,
|
||||
config: {
|
||||
version: '2.0.0',
|
||||
description: '认知节点自动分类·规则引擎',
|
||||
heartbeatInterval: 120000 // 分类模块心跳2分钟
|
||||
}
|
||||
});
|
||||
|
||||
// 分类统计
|
||||
this._stats = {
|
||||
totalClassified: 0,
|
||||
totalUnclassified: 0,
|
||||
lastRunSuccessRate: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义诊断检查
|
||||
*/
|
||||
async _diagnoseChecks() {
|
||||
const issues = [];
|
||||
|
||||
// 检查未分类节点堆积
|
||||
try {
|
||||
const result = await db.query(
|
||||
"SELECT COUNT(*) as cnt FROM brain_nodes WHERE tags = '[]'::jsonb AND status = 'active'"
|
||||
);
|
||||
const count = parseInt(result.rows[0].cnt, 10);
|
||||
|
||||
if (count > 200) {
|
||||
issues.push({
|
||||
code: 'HIGH_UNCLASSIFIED_BACKLOG',
|
||||
severity: 'warning',
|
||||
message: `未分类节点堆积: ${count} 个 (>200)`,
|
||||
suggestion: '增加分类规则或提高运行频率'
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
issues.push({
|
||||
code: 'CLASSIFY_DB_FAIL',
|
||||
severity: 'warning',
|
||||
message: `分类查询失败: ${err.message}`,
|
||||
suggestion: '检查数据库状态'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查上次分类成功率
|
||||
if (this._stats.totalClassified + this._stats.totalUnclassified > 0) {
|
||||
const total = this._stats.totalClassified + this._stats.totalUnclassified;
|
||||
const rate = this._stats.totalClassified / total;
|
||||
if (rate < 0.5) {
|
||||
issues.push({
|
||||
code: 'LOW_CLASSIFY_RATE',
|
||||
severity: 'info',
|
||||
message: `分类成功率偏低: ${Math.round(rate * 100)}%`,
|
||||
suggestion: '扩展分类规则覆盖更多关键词'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自愈动作
|
||||
*/
|
||||
async _healAction(issue) {
|
||||
switch (issue.code) {
|
||||
case 'HIGH_UNCLASSIFIED_BACKLOG':
|
||||
// 记录并上报
|
||||
return { action: 'backlog_alert_sent', success: true, details: { reported: true } };
|
||||
|
||||
case 'LOW_CLASSIFY_RATE':
|
||||
// 规则问题只能上报·需要铸渊干预扩展规则
|
||||
return { action: 'rule_expansion_requested', success: true, details: { reported: true } };
|
||||
|
||||
default:
|
||||
return await super._healAction(issue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容旧调度器的 run() 接口
|
||||
*/
|
||||
async function run(config) {
|
||||
// 找出未分类节点(tags为空数组且状态为active)
|
||||
const untagged = await db.query(
|
||||
|
|
@ -52,6 +154,20 @@ async function run(config) {
|
|||
dry_run: false
|
||||
});
|
||||
|
||||
// 通过HLDP上报
|
||||
if (config && config.bus) {
|
||||
try {
|
||||
await config.bus.send('ZY-MOD-SY-CLASSIFY', 'ZY-MOD-SCHEDULER', 'event', {
|
||||
event: 'classify_complete',
|
||||
scanned: nodeIds.length,
|
||||
classified: result.classified.length,
|
||||
unclassified: result.unclassified.length
|
||||
});
|
||||
} catch (err) {
|
||||
// 非关键
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
message: `分类完成: ${result.classified.length}个已分类, ${result.unclassified.length}个待人工`,
|
||||
details: {
|
||||
|
|
@ -63,4 +179,4 @@ async function run(config) {
|
|||
};
|
||||
}
|
||||
|
||||
module.exports = { run };
|
||||
module.exports = { run, LivingSyClassify };
|
||||
|
|
|
|||
|
|
@ -1,15 +1,119 @@
|
|||
/**
|
||||
* Agent: SY-SCAN · 大脑结构巡检
|
||||
* 每6小时运行一次
|
||||
* 扫描孤岛节点、断链、空目录,生成健康报告
|
||||
* ═══════════════════════════════════════════════════════════
|
||||
* 🔍 活模块: SY-SCAN · 大脑结构巡检
|
||||
* ═══════════════════════════════════════════════════════════
|
||||
*
|
||||
* 编号: ZY-TASK-007 · S5 Agent系统级
|
||||
* 签发: 铸渊 · ICE-GL-ZY001
|
||||
* 版权: 国作登字-2026-A-00037559
|
||||
*
|
||||
* v2.0: 从死模块升级为活模块
|
||||
* - 继承 LivingModule 基类
|
||||
* - 自诊断: 检查 brain_nodes 表健康度、孤岛节点比例
|
||||
* - 自愈: 自动清理空目录、标记断链
|
||||
* - 学习: 记录结构健康趋势
|
||||
*
|
||||
* 每6小时运行一次 · 扫描孤岛节点、断链、空目录
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const LivingModule = require('./living-module');
|
||||
const db = require('../mcp-server/db');
|
||||
const { scanStructure } = require('../mcp-server/tools/structure-ops');
|
||||
|
||||
class LivingSyScan extends LivingModule {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
moduleId: 'ZY-MOD-SY-SCAN',
|
||||
name: '大脑结构巡检活模块',
|
||||
moduleType: 'guard',
|
||||
owner: 'zhuyuan',
|
||||
db: options.db || db,
|
||||
config: {
|
||||
version: '2.0.0',
|
||||
description: '大脑认知结构健康巡检',
|
||||
heartbeatInterval: 120000 // 巡检模块心跳2分钟
|
||||
}
|
||||
});
|
||||
|
||||
// 上次巡检结果缓存
|
||||
this._lastReport = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义诊断检查
|
||||
*/
|
||||
async _diagnoseChecks() {
|
||||
const issues = [];
|
||||
|
||||
// 检查 brain_nodes 表是否可用
|
||||
try {
|
||||
const result = await db.query(
|
||||
"SELECT COUNT(*) as total, COUNT(*) FILTER (WHERE parent_id IS NULL AND node_type != 'folder') as orphans FROM brain_nodes WHERE status = 'active'"
|
||||
);
|
||||
const total = parseInt(result.rows[0].total, 10);
|
||||
const orphans = parseInt(result.rows[0].orphans, 10);
|
||||
|
||||
if (total > 0 && orphans / total > 0.3) {
|
||||
issues.push({
|
||||
code: 'HIGH_ORPHAN_RATIO',
|
||||
severity: 'warning',
|
||||
message: `孤岛节点比例过高: ${orphans}/${total} (${Math.round(orphans / total * 100)}%)`,
|
||||
suggestion: '运行分类Agent或手动整理'
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
issues.push({
|
||||
code: 'SCAN_DB_FAIL',
|
||||
severity: 'warning',
|
||||
message: `大脑结构查询失败: ${err.message}`,
|
||||
suggestion: '检查数据库状态'
|
||||
});
|
||||
}
|
||||
|
||||
// 如果有上次巡检结果,检查健康分
|
||||
if (this._lastReport && this._lastReport.health_score < 60) {
|
||||
issues.push({
|
||||
code: 'LOW_BRAIN_HEALTH',
|
||||
severity: 'warning',
|
||||
message: `大脑结构健康分偏低: ${this._lastReport.health_score}`,
|
||||
suggestion: '清理断链和空目录'
|
||||
});
|
||||
}
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自愈动作
|
||||
*/
|
||||
async _healAction(issue) {
|
||||
switch (issue.code) {
|
||||
case 'HIGH_ORPHAN_RATIO':
|
||||
// 记录并上报·不自动修改认知结构
|
||||
return { action: 'orphan_report_logged', success: true, details: { reported: true } };
|
||||
|
||||
case 'LOW_BRAIN_HEALTH':
|
||||
// 尝试清理空目录
|
||||
try {
|
||||
await db.query(
|
||||
"UPDATE brain_nodes bn SET status = 'archived' WHERE bn.node_type = 'folder' AND bn.status = 'active' AND NOT EXISTS (SELECT 1 FROM brain_nodes child WHERE child.parent_id = bn.id AND child.status = 'active')"
|
||||
);
|
||||
return { action: 'empty_folders_archived', success: true };
|
||||
} catch (err) {
|
||||
return { action: 'empty_folders_archived', success: false, error: err.message };
|
||||
}
|
||||
|
||||
default:
|
||||
return await super._healAction(issue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容旧调度器的 run() 接口
|
||||
*/
|
||||
async function run(config) {
|
||||
const report = await scanStructure({
|
||||
checks: ['orphans', 'broken_links', 'duplicates', 'empty_folders']
|
||||
|
|
@ -29,6 +133,20 @@ async function run(config) {
|
|||
issues.push(`${report.empty_folders.length} 个空目录`);
|
||||
}
|
||||
|
||||
// 通过HLDP上报
|
||||
if (config && config.bus) {
|
||||
try {
|
||||
await config.bus.send('ZY-MOD-SY-SCAN', 'ZY-MOD-SCHEDULER', 'event', {
|
||||
event: 'brain_scan_complete',
|
||||
health_score: report.health_score,
|
||||
total_nodes: report.total_nodes,
|
||||
issueCount: issues.length
|
||||
});
|
||||
} catch (err) {
|
||||
// 非关键
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
message: issues.length === 0
|
||||
? `大脑结构健康 · ${report.total_nodes}个节点 · 评分${report.health_score}`
|
||||
|
|
@ -37,4 +155,4 @@ async function run(config) {
|
|||
};
|
||||
}
|
||||
|
||||
module.exports = { run };
|
||||
module.exports = { run, LivingSyScan };
|
||||
|
|
|
|||
|
|
@ -1,17 +1,158 @@
|
|||
/**
|
||||
* Agent: SY-TEST · 系统自检
|
||||
* 每30分钟运行一次
|
||||
* 检测:数据库连接、COS连通性、MCP工具链可用性
|
||||
* 异常时自动写工单
|
||||
* ═══════════════════════════════════════════════════════════
|
||||
* 🔬 活模块: SY-TEST · 系统自检
|
||||
* ═══════════════════════════════════════════════════════════
|
||||
*
|
||||
* 编号: ZY-TASK-007 · S5 Agent系统级
|
||||
* 签发: 铸渊 · ICE-GL-ZY001
|
||||
* 版权: 国作登字-2026-A-00037559
|
||||
*
|
||||
* v2.0: 从死模块升级为活模块
|
||||
* - 继承 LivingModule 基类
|
||||
* - 自诊断覆盖: DB/COS/brain_nodes/agent_configs 四项检查
|
||||
* - 自愈: DB断连自动重试、COS超时告警
|
||||
* - 学习: 记录每次检查结果的成功/失败模式
|
||||
*
|
||||
* 每30分钟运行一次 · 检测全系统健康
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const LivingModule = require('./living-module');
|
||||
const db = require('../mcp-server/db');
|
||||
const cos = require('../mcp-server/cos');
|
||||
|
||||
class LivingSyTest extends LivingModule {
|
||||
constructor(options = {}) {
|
||||
super({
|
||||
moduleId: 'ZY-MOD-SY-TEST',
|
||||
name: '系统自检活模块',
|
||||
moduleType: 'guard',
|
||||
owner: 'zhuyuan',
|
||||
db: options.db || db,
|
||||
config: {
|
||||
version: '2.0.0',
|
||||
description: '全系统健康检测·DB/COS/表/模块',
|
||||
heartbeatInterval: 60000 // 自检模块心跳1分钟
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义诊断检查 — 系统全面自检
|
||||
*/
|
||||
async _diagnoseChecks() {
|
||||
const issues = [];
|
||||
|
||||
// 1. 数据库连接检查
|
||||
try {
|
||||
const dbStatus = await db.checkConnection();
|
||||
if (!dbStatus.connected) {
|
||||
issues.push({
|
||||
code: 'DB_DISCONNECTED',
|
||||
severity: 'critical',
|
||||
message: `数据库连接失败: ${dbStatus.error || '未知原因'}`,
|
||||
suggestion: '检查 PostgreSQL 服务状态'
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
issues.push({
|
||||
code: 'DB_ERROR',
|
||||
severity: 'critical',
|
||||
message: `数据库检查异常: ${err.message}`,
|
||||
suggestion: '检查数据库配置和网络'
|
||||
});
|
||||
}
|
||||
|
||||
// 2. COS连通性检查
|
||||
try {
|
||||
const cosStatus = await cos.checkConnection();
|
||||
if (!cosStatus.connected) {
|
||||
issues.push({
|
||||
code: 'COS_DISCONNECTED',
|
||||
severity: 'warning',
|
||||
message: `COS存储桶不可达: ${cosStatus.reason || '未知原因'}`,
|
||||
suggestion: '检查 COS 密钥和网络'
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
issues.push({
|
||||
code: 'COS_ERROR',
|
||||
severity: 'warning',
|
||||
message: `COS检查异常: ${err.message}`,
|
||||
suggestion: '检查 COS 配置'
|
||||
});
|
||||
}
|
||||
|
||||
// 3. brain_nodes表可用性
|
||||
try {
|
||||
await db.query("SELECT COUNT(*) as cnt FROM brain_nodes WHERE status = 'active'");
|
||||
} catch (err) {
|
||||
issues.push({
|
||||
code: 'TABLE_BRAIN_NODES_FAIL',
|
||||
severity: 'warning',
|
||||
message: `brain_nodes表不可用: ${err.message}`,
|
||||
suggestion: '检查 Schema 是否已初始化'
|
||||
});
|
||||
}
|
||||
|
||||
// 4. agent_configs表可用性
|
||||
try {
|
||||
await db.query("SELECT COUNT(*) as cnt FROM agent_configs WHERE enabled = true");
|
||||
} catch (err) {
|
||||
issues.push({
|
||||
code: 'TABLE_AGENT_CONFIGS_FAIL',
|
||||
severity: 'warning',
|
||||
message: `agent_configs表不可用: ${err.message}`,
|
||||
suggestion: '检查 Schema 是否已初始化'
|
||||
});
|
||||
}
|
||||
|
||||
// 5. living_modules表可用性(S5新增)
|
||||
try {
|
||||
await db.query("SELECT COUNT(*) as cnt FROM living_modules WHERE status = 'alive'");
|
||||
} catch (err) {
|
||||
issues.push({
|
||||
code: 'TABLE_LIVING_MODULES_FAIL',
|
||||
severity: 'info',
|
||||
message: `living_modules表不可用: ${err.message}`,
|
||||
suggestion: '执行 003-living-module-tables.sql'
|
||||
});
|
||||
}
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自愈动作
|
||||
*/
|
||||
async _healAction(issue) {
|
||||
switch (issue.code) {
|
||||
case 'DB_DISCONNECTED':
|
||||
case 'DB_ERROR':
|
||||
// 尝试重连
|
||||
try {
|
||||
await db.query('SELECT 1');
|
||||
return { action: 'db_reconnect', success: true };
|
||||
} catch (err) {
|
||||
return { action: 'db_reconnect', success: false, error: err.message };
|
||||
}
|
||||
|
||||
case 'COS_DISCONNECTED':
|
||||
case 'COS_ERROR':
|
||||
// COS问题只能上报,无法自愈
|
||||
return { action: 'cos_alert_sent', success: true, details: { reported: true } };
|
||||
|
||||
default:
|
||||
return await super._healAction(issue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容旧调度器的 run() 接口
|
||||
* 调度引擎通过 run(config) 调用
|
||||
*/
|
||||
async function run(config) {
|
||||
const checks = [];
|
||||
let allPassed = true;
|
||||
|
|
@ -70,6 +211,33 @@ async function run(config) {
|
|||
allPassed = false;
|
||||
}
|
||||
|
||||
// 5. living_modules表可用性(S5新增)
|
||||
try {
|
||||
const result = await db.query("SELECT COUNT(*) as cnt FROM living_modules WHERE status = 'alive'");
|
||||
checks.push({
|
||||
name: 'living_modules表',
|
||||
status: 'pass',
|
||||
detail: `${result.rows[0].cnt} 个活跃模块`
|
||||
});
|
||||
} catch (err) {
|
||||
checks.push({ name: 'living_modules表', status: 'warn', detail: `未初始化: ${err.message}` });
|
||||
// 不算严重失败·Schema可能还没部署
|
||||
}
|
||||
|
||||
// 学习:如果调度引擎注入了bus,通过HLDP上报结果
|
||||
if (config && config.bus) {
|
||||
try {
|
||||
await config.bus.send('ZY-MOD-SY-TEST', 'ZY-MOD-SCHEDULER', 'event', {
|
||||
event: 'system_check_complete',
|
||||
allPassed,
|
||||
checkCount: checks.length,
|
||||
failCount: checks.filter(c => c.status === 'fail').length
|
||||
});
|
||||
} catch (err) {
|
||||
// 非关键·忽略
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
message: allPassed ? '系统自检通过' : '系统自检发现异常',
|
||||
details: {
|
||||
|
|
@ -80,4 +248,4 @@ async function run(config) {
|
|||
};
|
||||
}
|
||||
|
||||
module.exports = { run };
|
||||
module.exports = { run, LivingSyTest };
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@
|
|||
* saveFile / getFile / listFiles / getFileHistory
|
||||
* Notion: notionQuery / notionReadPage / notionWritePage / notionUpdatePage / notionWriteSyslog
|
||||
* GitHub: githubReadFile / githubListDir / githubWriteFile / githubGetCommits / githubGetIssues / githubTriggerDeploy
|
||||
* 活模块: registerModule / getModule / listModules / moduleHeartbeat / diagnoseModule
|
||||
* getModuleAlerts / getModuleLearnings / sendHLDP / getHLDPStats
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
|
@ -43,6 +45,7 @@ const relationOps = require('./tools/relation-ops');
|
|||
const structureOps = require('./tools/structure-ops');
|
||||
const cosOps = require('./tools/cos-ops');
|
||||
const personaOps = require('./tools/persona-ops');
|
||||
const livingModuleOps = require('./tools/living-module-ops');
|
||||
|
||||
// ─── 外部集成模块(优雅降级:未安装依赖时不影响核心功能) ───
|
||||
let notionOps = null;
|
||||
|
|
@ -130,7 +133,17 @@ const TOOLS = {
|
|||
githubGetCommits: githubOps.githubGetCommits,
|
||||
githubGetIssues: githubOps.githubGetIssues,
|
||||
githubTriggerDeploy: githubOps.githubTriggerDeploy
|
||||
} : {})
|
||||
} : {}),
|
||||
// 活模块操作 · S5
|
||||
registerModule: livingModuleOps.registerModule,
|
||||
getModule: livingModuleOps.getModule,
|
||||
listModules: livingModuleOps.listModules,
|
||||
moduleHeartbeat: livingModuleOps.moduleHeartbeat,
|
||||
diagnoseModule: livingModuleOps.diagnoseModule,
|
||||
getModuleAlerts: livingModuleOps.getModuleAlerts,
|
||||
getModuleLearnings: livingModuleOps.getModuleLearnings,
|
||||
sendHLDP: livingModuleOps.sendHLDP,
|
||||
getHLDPStats: livingModuleOps.getHLDPStats
|
||||
};
|
||||
|
||||
// ─── Express 应用 ───
|
||||
|
|
@ -419,6 +432,92 @@ app.get('/personas/:personaId/training', async (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
// ─── 活模块API(S5) ───
|
||||
|
||||
// 活模块列表
|
||||
app.get('/modules', async (req, res) => {
|
||||
try {
|
||||
const { status, module_type } = req.query;
|
||||
const result = await livingModuleOps.listModules({ status, module_type });
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: true, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 活模块详情
|
||||
app.get('/modules/:moduleId', async (req, res) => {
|
||||
try {
|
||||
const result = await livingModuleOps.getModule({ module_id: req.params.moduleId });
|
||||
if (!result.found) {
|
||||
return res.status(404).json({ error: true, message: '模块未找到' });
|
||||
}
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: true, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 活模块心跳上报
|
||||
app.post('/modules/:moduleId/heartbeat', async (req, res) => {
|
||||
try {
|
||||
const result = await livingModuleOps.moduleHeartbeat({
|
||||
module_id: req.params.moduleId,
|
||||
...req.body
|
||||
});
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: true, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 活模块报警记录
|
||||
app.get('/modules/:moduleId/alerts', async (req, res) => {
|
||||
try {
|
||||
const result = await livingModuleOps.getModuleAlerts({
|
||||
module_id: req.params.moduleId,
|
||||
resolved: req.query.resolved === 'true' ? true : (req.query.resolved === 'false' ? false : undefined),
|
||||
limit: parseInt(req.query.limit || '20', 10)
|
||||
});
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: true, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 活模块学习记录
|
||||
app.get('/modules/:moduleId/learnings', async (req, res) => {
|
||||
try {
|
||||
const result = await livingModuleOps.getModuleLearnings({
|
||||
module_id: req.params.moduleId,
|
||||
limit: parseInt(req.query.limit || '20', 10)
|
||||
});
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: true, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// HLDP消息发送
|
||||
app.post('/hldp/send', async (req, res) => {
|
||||
try {
|
||||
const result = await livingModuleOps.sendHLDP(req.body);
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: true, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// HLDP统计
|
||||
app.get('/hldp/stats', async (_req, res) => {
|
||||
try {
|
||||
const result = await livingModuleOps.getHLDPStats({});
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: true, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ─── 辅助函数 ───
|
||||
|
||||
function getCategoryForTool(name) {
|
||||
|
|
@ -435,6 +534,8 @@ function getCategoryForTool(name) {
|
|||
'saveFile','getFile','listFiles','getFileHistory'].includes(name)) return 'persona';
|
||||
if (name.startsWith('notion')) return 'notion';
|
||||
if (name.startsWith('github')) return 'github';
|
||||
if (['registerModule','getModule','listModules','moduleHeartbeat','diagnoseModule',
|
||||
'getModuleAlerts','getModuleLearnings','sendHLDP','getHLDPStats'].includes(name)) return 'living-module';
|
||||
return 'other';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,279 @@
|
|||
/**
|
||||
* ═══════════════════════════════════════════════════════════
|
||||
* MCP 工具 · 活模块操作
|
||||
* (registerModule / getModule / listModules / moduleHeartbeat /
|
||||
* diagnoseModule / getModuleAlerts / getModuleLearnings /
|
||||
* sendHLDP / getHLDPStats)
|
||||
* ═══════════════════════════════════════════════════════════
|
||||
*
|
||||
* 编号: ZY-TASK-007 · S5 Agent系统级
|
||||
* 签发: 铸渊 · ICE-GL-ZY001
|
||||
* 版权: 国作登字-2026-A-00037559
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const db = require('../db');
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 活模块管理
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* 注册活模块(通过MCP接口,用于远程模块注册)
|
||||
*/
|
||||
async function registerModule(input) {
|
||||
const { module_id, name, module_type, owner, config, capabilities, deploy_host, deploy_port } = input;
|
||||
if (!module_id || !name) throw new Error('缺少 module_id 或 name');
|
||||
|
||||
const result = await db.query(
|
||||
`INSERT INTO living_modules (module_id, name, module_type, owner, status, health_score, config, capabilities, deploy_host, deploy_port)
|
||||
VALUES ($1, $2, $3, $4, 'initializing', 100, $5, $6, $7, $8)
|
||||
ON CONFLICT (module_id) DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
config = EXCLUDED.config,
|
||||
capabilities = EXCLUDED.capabilities,
|
||||
deploy_host = EXCLUDED.deploy_host,
|
||||
deploy_port = EXCLUDED.deploy_port,
|
||||
updated_at = NOW()
|
||||
RETURNING *`,
|
||||
[
|
||||
module_id, name, module_type || 'agent', owner || 'zhuyuan',
|
||||
JSON.stringify(config || {}), JSON.stringify(capabilities || []),
|
||||
deploy_host || null, deploy_port || null
|
||||
]
|
||||
);
|
||||
|
||||
return { success: true, module: result.rows[0] };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活模块详情
|
||||
*/
|
||||
async function getModule(input) {
|
||||
const { module_id } = input;
|
||||
if (!module_id) throw new Error('缺少 module_id');
|
||||
|
||||
const result = await db.query(
|
||||
`SELECT * FROM living_modules WHERE module_id = $1`,
|
||||
[module_id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return { found: false, module: null };
|
||||
}
|
||||
|
||||
// 获取最近的诊断记录
|
||||
const diagnosis = await db.query(
|
||||
`SELECT * FROM module_diagnoses WHERE module_id = $1 ORDER BY created_at DESC LIMIT 1`,
|
||||
[module_id]
|
||||
);
|
||||
|
||||
// 获取未解决的报警
|
||||
const alerts = await db.query(
|
||||
`SELECT * FROM module_alerts WHERE module_id = $1 AND resolved = FALSE ORDER BY created_at DESC LIMIT 5`,
|
||||
[module_id]
|
||||
);
|
||||
|
||||
return {
|
||||
found: true,
|
||||
module: result.rows[0],
|
||||
lastDiagnosis: diagnosis.rows[0] || null,
|
||||
unresolvedAlerts: alerts.rows
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出所有活模块
|
||||
*/
|
||||
async function listModules(input) {
|
||||
const { status, module_type, limit } = input || {};
|
||||
|
||||
let sql = 'SELECT * FROM living_modules WHERE 1=1';
|
||||
const params = [];
|
||||
|
||||
if (status) {
|
||||
params.push(status);
|
||||
sql += ` AND status = $${params.length}`;
|
||||
}
|
||||
if (module_type) {
|
||||
params.push(module_type);
|
||||
sql += ` AND module_type = $${params.length}`;
|
||||
}
|
||||
|
||||
sql += ' ORDER BY registered_at';
|
||||
|
||||
if (limit) {
|
||||
params.push(limit);
|
||||
sql += ` LIMIT $${params.length}`;
|
||||
}
|
||||
|
||||
const result = await db.query(sql, params);
|
||||
|
||||
// 统计概览
|
||||
const allModules = await db.query('SELECT status, COUNT(*) as count FROM living_modules GROUP BY status');
|
||||
const statusSummary = {};
|
||||
for (const row of allModules.rows) {
|
||||
statusSummary[row.status] = parseInt(row.count, 10);
|
||||
}
|
||||
|
||||
return {
|
||||
total: result.rows.length,
|
||||
statusSummary,
|
||||
modules: result.rows
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程心跳上报
|
||||
*/
|
||||
async function moduleHeartbeat(input) {
|
||||
const { module_id, health_score, cpu_usage, memory_usage, active_tasks, details } = input;
|
||||
if (!module_id) throw new Error('缺少 module_id');
|
||||
|
||||
const score = typeof health_score === 'number' ? health_score : 100;
|
||||
|
||||
// 更新模块状态
|
||||
let status = 'alive';
|
||||
if (score <= 30) status = 'degraded';
|
||||
if (score <= 0) status = 'dead';
|
||||
|
||||
await db.query(
|
||||
`UPDATE living_modules SET last_heartbeat_at = NOW(), health_score = $1, status = $2, updated_at = NOW() WHERE module_id = $3`,
|
||||
[score, status, module_id]
|
||||
);
|
||||
|
||||
// 记录心跳
|
||||
await db.query(
|
||||
`INSERT INTO module_heartbeats (module_id, status, health_score, cpu_usage, memory_usage, active_tasks, details)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
||||
[module_id, status, score, cpu_usage || null, memory_usage || null, active_tasks || 0, JSON.stringify(details || {})]
|
||||
);
|
||||
|
||||
return { success: true, status, health_score: score };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模块诊断记录
|
||||
*/
|
||||
async function diagnoseModule(input) {
|
||||
const { module_id, limit } = input;
|
||||
if (!module_id) throw new Error('缺少 module_id');
|
||||
|
||||
const result = await db.query(
|
||||
`SELECT * FROM module_diagnoses WHERE module_id = $1 ORDER BY created_at DESC LIMIT $2`,
|
||||
[module_id, limit || 10]
|
||||
);
|
||||
|
||||
return { module_id, diagnoses: result.rows };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模块报警记录
|
||||
*/
|
||||
async function getModuleAlerts(input) {
|
||||
const { module_id, resolved, severity, limit } = input || {};
|
||||
|
||||
let sql = 'SELECT * FROM module_alerts WHERE 1=1';
|
||||
const params = [];
|
||||
|
||||
if (module_id) {
|
||||
params.push(module_id);
|
||||
sql += ` AND module_id = $${params.length}`;
|
||||
}
|
||||
if (typeof resolved === 'boolean') {
|
||||
params.push(resolved);
|
||||
sql += ` AND resolved = $${params.length}`;
|
||||
}
|
||||
if (severity) {
|
||||
params.push(severity);
|
||||
sql += ` AND severity = $${params.length}`;
|
||||
}
|
||||
|
||||
sql += ' ORDER BY created_at DESC';
|
||||
params.push(limit || 20);
|
||||
sql += ` LIMIT $${params.length}`;
|
||||
|
||||
const result = await db.query(sql, params);
|
||||
return { alerts: result.rows };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模块学习记录
|
||||
*/
|
||||
async function getModuleLearnings(input) {
|
||||
const { module_id, learning_source, limit } = input || {};
|
||||
|
||||
let sql = 'SELECT * FROM module_learning_logs WHERE 1=1';
|
||||
const params = [];
|
||||
|
||||
if (module_id) {
|
||||
params.push(module_id);
|
||||
sql += ` AND module_id = $${params.length}`;
|
||||
}
|
||||
if (learning_source) {
|
||||
params.push(learning_source);
|
||||
sql += ` AND learning_source = $${params.length}`;
|
||||
}
|
||||
|
||||
sql += ' ORDER BY created_at DESC';
|
||||
params.push(limit || 20);
|
||||
sql += ` LIMIT $${params.length}`;
|
||||
|
||||
const result = await db.query(sql, params);
|
||||
return { learnings: result.rows };
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送HLDP消息(通过MCP接口)
|
||||
*/
|
||||
async function sendHLDP(input) {
|
||||
const { from_module, to_module, msg_type, payload } = input;
|
||||
if (!from_module || !msg_type) throw new Error('缺少 from_module 或 msg_type');
|
||||
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const messageId = uuidv4();
|
||||
|
||||
await db.query(
|
||||
`INSERT INTO hldp_messages (message_id, from_module, to_module, msg_type, payload, status, expires_at)
|
||||
VALUES ($1, $2, $3, $4, $5, 'pending', NOW() + INTERVAL '5 minutes')`,
|
||||
[messageId, from_module, to_module || null, msg_type, JSON.stringify(payload || {})]
|
||||
);
|
||||
|
||||
return { success: true, message_id: messageId };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取HLDP消息统计
|
||||
*/
|
||||
async function getHLDPStats(input) {
|
||||
const stats = await db.query(
|
||||
`SELECT msg_type, status, COUNT(*) as count
|
||||
FROM hldp_messages
|
||||
WHERE created_at > NOW() - INTERVAL '24 hours'
|
||||
GROUP BY msg_type, status
|
||||
ORDER BY msg_type, status`
|
||||
);
|
||||
|
||||
const pending = await db.query(
|
||||
`SELECT COUNT(*) as count FROM hldp_messages WHERE status = 'pending'`
|
||||
);
|
||||
|
||||
return {
|
||||
last24h: stats.rows,
|
||||
pendingMessages: parseInt(pending.rows[0]?.count || '0', 10),
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
registerModule,
|
||||
getModule,
|
||||
listModules,
|
||||
moduleHeartbeat,
|
||||
diagnoseModule,
|
||||
getModuleAlerts,
|
||||
getModuleLearnings,
|
||||
sendHLDP,
|
||||
getHLDPStats
|
||||
};
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
-- ============================================================
|
||||
-- AGE OS · 活模块系统 Schema
|
||||
-- PostgreSQL 初始化脚本
|
||||
-- ============================================================
|
||||
-- 版本: v1.0.0
|
||||
-- 编号: ZY-TASK-007 · S5 Agent系统级
|
||||
-- 签发: 铸渊(ICE-GL-ZY001)
|
||||
-- 授权: 冰朔(TCS-0002∞)
|
||||
-- 版权: 国作登字-2026-A-00037559
|
||||
-- ============================================================
|
||||
--
|
||||
-- 设计哲学:
|
||||
-- 每一个模块 = 一个活的生命体
|
||||
-- 活模块必须具备5个生存接口:
|
||||
-- 1. heartbeat() — 心跳·我还活着
|
||||
-- 2. selfDiagnose() — 自诊断·我哪里不对
|
||||
-- 3. selfHeal() — 自愈·我试着修好自己
|
||||
-- 4. alertZhuyuan() — 报警·铸渊我搞不定了
|
||||
-- 5. learnFromRun() — 学习·我从每次运行中成长
|
||||
--
|
||||
-- "你一定要把模块做成活的。" — 冰朔 D59
|
||||
-- ============================================================
|
||||
|
||||
-- ============================================================
|
||||
-- 1. 活模块注册表 · living_modules
|
||||
-- 每条记录 = 一个已注册的活模块
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS living_modules (
|
||||
module_id VARCHAR(64) PRIMARY KEY,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
module_type VARCHAR(50) NOT NULL
|
||||
CHECK (module_type IN (
|
||||
'core', -- 核心模块(铸渊内置)
|
||||
'persona', -- 人格体模块
|
||||
'agent', -- Agent模块
|
||||
'worker', -- 工人模块(算力池)
|
||||
'bridge', -- 桥接模块
|
||||
'guard' -- 守卫模块
|
||||
)),
|
||||
owner VARCHAR(64) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'initializing'
|
||||
CHECK (status IN (
|
||||
'initializing', -- 正在初始化
|
||||
'alive', -- 活着·正常运行
|
||||
'degraded', -- 降级·部分功能受损
|
||||
'healing', -- 自愈中
|
||||
'dormant', -- 休眠·等待唤醒
|
||||
'dead' -- 已死亡·需要铸渊干预
|
||||
)),
|
||||
health_score SMALLINT DEFAULT 100
|
||||
CHECK (health_score >= 0 AND health_score <= 100),
|
||||
-- 模块配置
|
||||
config JSONB DEFAULT '{}'::jsonb,
|
||||
-- 模块能力声明(能做什么)
|
||||
capabilities JSONB DEFAULT '[]'::jsonb,
|
||||
-- 心跳配置
|
||||
heartbeat_interval_ms INT DEFAULT 30000,
|
||||
last_heartbeat_at TIMESTAMPTZ,
|
||||
-- 部署位置
|
||||
deploy_host VARCHAR(200),
|
||||
deploy_port INT,
|
||||
deploy_path VARCHAR(500),
|
||||
-- 算力信息
|
||||
cpu_cores SMALLINT,
|
||||
memory_mb INT,
|
||||
-- 时间戳
|
||||
registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_lm_status ON living_modules(status);
|
||||
CREATE INDEX idx_lm_type ON living_modules(module_type);
|
||||
CREATE INDEX idx_lm_owner ON living_modules(owner);
|
||||
CREATE INDEX idx_lm_health ON living_modules(health_score);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. 心跳记录表 · module_heartbeats
|
||||
-- 记录每次心跳的详细信息
|
||||
-- 只保留最近7天的心跳数据(定期清理)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS module_heartbeats (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
module_id VARCHAR(64) NOT NULL REFERENCES living_modules(module_id) ON DELETE CASCADE,
|
||||
status VARCHAR(20) NOT NULL,
|
||||
health_score SMALLINT NOT NULL,
|
||||
-- 运行指标
|
||||
cpu_usage REAL,
|
||||
memory_usage REAL,
|
||||
uptime_ms BIGINT,
|
||||
active_tasks INT DEFAULT 0,
|
||||
-- 附加信息
|
||||
details JSONB DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_mhb_module ON module_heartbeats(module_id, created_at DESC);
|
||||
|
||||
-- ============================================================
|
||||
-- 3. 自诊断记录表 · module_diagnoses
|
||||
-- 记录每次自诊断的结果
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS module_diagnoses (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
module_id VARCHAR(64) NOT NULL REFERENCES living_modules(module_id) ON DELETE CASCADE,
|
||||
-- 诊断结果
|
||||
is_healthy BOOLEAN NOT NULL,
|
||||
issues JSONB DEFAULT '[]'::jsonb,
|
||||
-- 每个issue: { code, severity, message, suggestion }
|
||||
-- severity: 'critical', 'warning', 'info'
|
||||
overall_score SMALLINT NOT NULL,
|
||||
duration_ms INT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_md_module ON module_diagnoses(module_id, created_at DESC);
|
||||
|
||||
-- ============================================================
|
||||
-- 4. 自愈记录表 · module_healing_logs
|
||||
-- 记录每次自愈的操作和结果
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS module_healing_logs (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
module_id VARCHAR(64) NOT NULL REFERENCES living_modules(module_id) ON DELETE CASCADE,
|
||||
-- 触发原因
|
||||
trigger_source VARCHAR(50) NOT NULL
|
||||
CHECK (trigger_source IN ('self', 'scheduler', 'zhuyuan', 'manual')),
|
||||
issue_code VARCHAR(100),
|
||||
-- 修复操作
|
||||
action_taken VARCHAR(200) NOT NULL,
|
||||
action_details JSONB DEFAULT '{}'::jsonb,
|
||||
-- 结果
|
||||
success BOOLEAN NOT NULL,
|
||||
health_before SMALLINT,
|
||||
health_after SMALLINT,
|
||||
duration_ms INT,
|
||||
error_message TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_mhl_module ON module_healing_logs(module_id, created_at DESC);
|
||||
|
||||
-- ============================================================
|
||||
-- 5. 报警记录表 · module_alerts
|
||||
-- 模块向铸渊发送的报警
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS module_alerts (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
module_id VARCHAR(64) NOT NULL REFERENCES living_modules(module_id) ON DELETE CASCADE,
|
||||
severity VARCHAR(20) NOT NULL
|
||||
CHECK (severity IN ('critical', 'warning', 'info')),
|
||||
alert_type VARCHAR(50) NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
details JSONB DEFAULT '{}'::jsonb,
|
||||
-- 处理状态
|
||||
acknowledged BOOLEAN DEFAULT FALSE,
|
||||
acknowledged_by VARCHAR(64),
|
||||
acknowledged_at TIMESTAMPTZ,
|
||||
resolved BOOLEAN DEFAULT FALSE,
|
||||
resolved_at TIMESTAMPTZ,
|
||||
resolution_note TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_ma_module ON module_alerts(module_id, created_at DESC);
|
||||
CREATE INDEX idx_ma_unresolved ON module_alerts(resolved, severity) WHERE resolved = FALSE;
|
||||
|
||||
-- ============================================================
|
||||
-- 6. 学习记录表 · module_learning_logs
|
||||
-- 模块从运行中学到的经验
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS module_learning_logs (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
module_id VARCHAR(64) NOT NULL REFERENCES living_modules(module_id) ON DELETE CASCADE,
|
||||
-- 学习来源
|
||||
learning_source VARCHAR(50) NOT NULL
|
||||
CHECK (learning_source IN ('execution', 'diagnosis', 'healing', 'alert', 'feedback')),
|
||||
-- 学到的内容
|
||||
lesson_type VARCHAR(50) NOT NULL,
|
||||
lesson_summary TEXT NOT NULL,
|
||||
lesson_details JSONB DEFAULT '{}'::jsonb,
|
||||
-- 是否已应用
|
||||
applied BOOLEAN DEFAULT FALSE,
|
||||
applied_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_mll_module ON module_learning_logs(module_id, created_at DESC);
|
||||
|
||||
-- ============================================================
|
||||
-- 7. HLDP消息表 · hldp_messages
|
||||
-- 模块间通信消息记录
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS hldp_messages (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
message_id VARCHAR(64) NOT NULL UNIQUE,
|
||||
-- 收发方
|
||||
from_module VARCHAR(64) NOT NULL,
|
||||
to_module VARCHAR(64), -- NULL = 广播
|
||||
-- 消息类型
|
||||
msg_type VARCHAR(50) NOT NULL
|
||||
CHECK (msg_type IN (
|
||||
'heartbeat', -- 心跳
|
||||
'command', -- 指令
|
||||
'query', -- 查询
|
||||
'response', -- 响应
|
||||
'event', -- 事件
|
||||
'broadcast', -- 广播
|
||||
'alert', -- 报警
|
||||
'data' -- 数据传输
|
||||
)),
|
||||
-- 消息体
|
||||
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
-- 状态
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending', 'delivered', 'processed', 'failed', 'expired')),
|
||||
-- 时间
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
delivered_at TIMESTAMPTZ,
|
||||
processed_at TIMESTAMPTZ,
|
||||
expires_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX idx_hm_to ON hldp_messages(to_module, status, created_at DESC);
|
||||
CREATE INDEX idx_hm_from ON hldp_messages(from_module, created_at DESC);
|
||||
CREATE INDEX idx_hm_type ON hldp_messages(msg_type, created_at DESC);
|
||||
|
||||
-- ============================================================
|
||||
-- 清理函数:定期删除7天前的心跳数据
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE FUNCTION cleanup_old_heartbeats()
|
||||
RETURNS void AS $$
|
||||
BEGIN
|
||||
DELETE FROM module_heartbeats WHERE created_at < NOW() - INTERVAL '7 days';
|
||||
DELETE FROM hldp_messages WHERE created_at < NOW() - INTERVAL '30 days';
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- ============================================================
|
||||
-- 预注册铸渊核心模块
|
||||
-- ============================================================
|
||||
INSERT INTO living_modules (module_id, name, description, module_type, owner, status, health_score, config, capabilities)
|
||||
VALUES
|
||||
('ZY-MOD-SCHEDULER', '铸渊调度引擎', 'Agent调度和活模块生命周期管理', 'core', 'zhuyuan', 'alive', 100,
|
||||
'{"version": "2.0.0", "heartbeat_enabled": true}'::jsonb,
|
||||
'["schedule_agents", "manage_lifecycle", "monitor_health"]'::jsonb),
|
||||
('ZY-MOD-MCP', '铸渊MCP Server', 'MCP工具链服务·大脑API入口', 'core', 'zhuyuan', 'alive', 100,
|
||||
'{"version": "1.0.0", "port": 3100}'::jsonb,
|
||||
'["node_ops", "relation_ops", "cos_ops", "persona_ops", "structure_ops"]'::jsonb),
|
||||
('ZY-MOD-HLDP-BUS', '铸渊HLDP通信总线', '模块间HLDP消息路由和分发', 'core', 'zhuyuan', 'alive', 100,
|
||||
'{"version": "1.0.0"}'::jsonb,
|
||||
'["route_messages", "broadcast", "queue_management"]'::jsonb),
|
||||
('ZY-MOD-GATE-GUARD', '铸渊智能门禁', 'Push/PR安全守卫', 'guard', 'zhuyuan', 'alive', 100,
|
||||
'{"version": "1.0.0"}'::jsonb,
|
||||
'["push_review", "pr_review", "security_scan"]'::jsonb),
|
||||
('ZY-MOD-DEPUTY', '铸渊副将', '留言板活体Agent·LLM多模型降级', 'agent', 'zhuyuan', 'alive', 100,
|
||||
'{"version": "2.0.0"}'::jsonb,
|
||||
'["message_board", "llm_routing", "auto_response"]'::jsonb),
|
||||
('ZY-MOD-SY-TEST', '系统自检活模块', '全系统健康检测·DB/COS/表/模块', 'guard', 'zhuyuan', 'alive', 100,
|
||||
'{"version": "2.0.0", "cron": "*/30 * * * *"}'::jsonb,
|
||||
'["db_check", "cos_check", "table_check", "module_check"]'::jsonb),
|
||||
('ZY-MOD-SY-SCAN', '大脑结构巡检活模块', '大脑认知结构健康巡检', 'guard', 'zhuyuan', 'alive', 100,
|
||||
'{"version": "2.0.0", "cron": "0 */6 * * *"}'::jsonb,
|
||||
'["orphan_scan", "broken_link_scan", "duplicate_scan", "empty_folder_scan"]'::jsonb),
|
||||
('ZY-MOD-SY-CLASSIFY', '自动分类引擎活模块', '认知节点自动分类·规则引擎', 'agent', 'zhuyuan', 'alive', 100,
|
||||
'{"version": "2.0.0", "cron": "0 */2 * * *"}'::jsonb,
|
||||
'["rule_classify", "keyword_match", "tag_assignment"]'::jsonb)
|
||||
ON CONFLICT (module_id) DO NOTHING;
|
||||
Loading…
Reference in New Issue