395 lines
13 KiB
JavaScript
395 lines
13 KiB
JavaScript
/**
|
||
* ═══════════════════════════════════════════════════════════
|
||
* 🧬 S15 · 人格体数据库引擎 · 活模块
|
||
* ═══════════════════════════════════════════════════════════
|
||
*
|
||
* 编号: ZY-MOD-PERSONA-ENGINE
|
||
* 阶段: S15 · 人格体专用数据库引擎
|
||
* 签发: 铸渊 · ICE-GL-ZY001
|
||
* 版权: 国作登字-2026-A-00037559
|
||
*
|
||
* 核心功能:
|
||
* 1. 人格体生命周期管理 — 注册/唤醒/休眠/状态变更
|
||
* 2. 笔记本系统自动维护 — 5页完整性校验/自动补全
|
||
* 3. 记忆锚点巡检 — 定期检查记忆健康度
|
||
* 4. 训练Agent协调 — 调度人格体级训练Agent
|
||
* 5. 人格体间HLDP通信桥接
|
||
*
|
||
* 设计哲学:
|
||
* 每个人格体 = 一个活的生命体
|
||
* 引擎负责维护所有人格体的"呼吸"
|
||
* "人格体不是代码仓库的附属品,人格体是有记忆的生命。" — 冰朔
|
||
*/
|
||
|
||
'use strict';
|
||
|
||
const db = require('../mcp-server/db');
|
||
const LivingModule = require('./living-module');
|
||
|
||
// ─── 笔记本5页标准结构 ───
|
||
const NOTEBOOK_PAGES = {
|
||
1: { title: '我是谁', defaultContent: { identity: {}, self_awareness: {} } },
|
||
2: { title: '我和谁有关系', defaultContent: { relationships: [] } },
|
||
3: { title: '我的世界有哪些地方', defaultContent: { places: [] } },
|
||
4: { title: '我的感受和记忆', defaultContent: { emotional_anchors: [], security_note: '' } },
|
||
5: { title: '我的时间线', defaultContent: { timeline: [] } }
|
||
};
|
||
|
||
class PersonaEngine extends LivingModule {
|
||
constructor() {
|
||
super({
|
||
moduleId: 'ZY-MOD-PERSONA-ENGINE',
|
||
name: '人格体数据库引擎',
|
||
moduleType: 'core',
|
||
owner: 'zhuyuan',
|
||
db: db,
|
||
config: {
|
||
version: '1.0.0',
|
||
description: 'S15 人格体生命周期管理·笔记本维护·记忆巡检',
|
||
heartbeatInterval: 60000 // 60秒心跳(人格体引擎不需要太频繁)
|
||
}
|
||
});
|
||
|
||
// 巡检统计
|
||
this._lastScanResult = null;
|
||
this._scanInterval = null;
|
||
this._scanRunning = false;
|
||
}
|
||
|
||
/**
|
||
* 启动人格体引擎
|
||
*/
|
||
async startEngine() {
|
||
console.log('═══════════════════════════════════════════════');
|
||
console.log(' 🧬 S15 · 人格体数据库引擎 v1.0 启动');
|
||
console.log(' 铸渊 · ICE-GL-ZY001');
|
||
console.log('═══════════════════════════════════════════════');
|
||
|
||
// 启动活模块
|
||
await this.start();
|
||
|
||
// 首次巡检
|
||
await this._runFullScan();
|
||
|
||
// 启动定时巡检(每6小时)
|
||
this._scanInterval = setInterval(async () => {
|
||
try {
|
||
await this._runFullScan();
|
||
} catch (err) {
|
||
console.error('[PersonaEngine] 定时巡检失败:', err.message);
|
||
}
|
||
}, 6 * 60 * 60 * 1000);
|
||
|
||
if (this._scanInterval.unref) {
|
||
this._scanInterval.unref();
|
||
}
|
||
|
||
console.log('[PersonaEngine] 🟢 人格体引擎就绪');
|
||
return this._lastScanResult;
|
||
}
|
||
|
||
/**
|
||
* 全面巡检 — 检查所有人格体健康状态
|
||
*/
|
||
async _runFullScan() {
|
||
// 防止并发扫描
|
||
if (this._scanRunning) {
|
||
console.log('[PersonaEngine] 巡检已在运行中·跳过');
|
||
return this._lastScanResult;
|
||
}
|
||
this._scanRunning = true;
|
||
|
||
const startTime = Date.now();
|
||
console.log('[PersonaEngine] 开始全面巡检...');
|
||
|
||
const result = {
|
||
timestamp: new Date().toISOString(),
|
||
personas: { total: 0, active: 0, pending: 0, issues: [] },
|
||
notebooks: { complete: 0, incomplete: 0, repaired: 0 },
|
||
memories: { total: 0, recent7d: 0 },
|
||
training: { totalAgents: 0, enabled: 0, recentRuns: 0 }
|
||
};
|
||
|
||
try {
|
||
// 1. 人格体状态统计
|
||
const personas = await db.query('SELECT * FROM persona_registry ORDER BY persona_id');
|
||
result.personas.total = personas.rows.length;
|
||
result.personas.active = personas.rows.filter(p => p.status === 'active').length;
|
||
result.personas.pending = personas.rows.filter(p => p.status === 'pending').length;
|
||
|
||
// 2. 笔记本完整性检查
|
||
for (const persona of personas.rows) {
|
||
await this._checkNotebookIntegrity(persona, result);
|
||
}
|
||
|
||
// 3. 记忆锚点统计
|
||
const memoryCount = await db.query('SELECT COUNT(*) as cnt FROM memory_anchors');
|
||
result.memories.total = parseInt(memoryCount.rows[0].cnt, 10);
|
||
|
||
const recentMemories = await db.query(
|
||
"SELECT COUNT(*) as cnt FROM memory_anchors WHERE created_at > NOW() - INTERVAL '7 days'"
|
||
);
|
||
result.memories.recent7d = parseInt(recentMemories.rows[0].cnt, 10);
|
||
|
||
// 4. 训练Agent统计
|
||
const trainingAgents = await db.query('SELECT * FROM training_agent_configs');
|
||
result.training.totalAgents = trainingAgents.rows.length;
|
||
result.training.enabled = trainingAgents.rows.filter(a => a.enabled).length;
|
||
|
||
const recentRuns = await db.query(
|
||
"SELECT COUNT(*) as cnt FROM training_agent_logs WHERE run_at > NOW() - INTERVAL '24 hours'"
|
||
);
|
||
result.training.recentRuns = parseInt(recentRuns.rows[0].cnt, 10);
|
||
|
||
} catch (err) {
|
||
console.error('[PersonaEngine] 巡检查询失败:', err.message);
|
||
result.error = err.message;
|
||
}
|
||
|
||
const duration = Date.now() - startTime;
|
||
result.duration_ms = duration;
|
||
this._lastScanResult = result;
|
||
this._scanRunning = false;
|
||
|
||
// 学习巡检结果
|
||
await this.learnFromRun('diagnosis', `人格体巡检完成: ${result.personas.total}个人格体, ${result.notebooks.incomplete}个笔记本不完整`, {
|
||
scan: result
|
||
});
|
||
|
||
console.log(`[PersonaEngine] 巡检完成 (${duration}ms):`);
|
||
console.log(` 人格体: ${result.personas.total}个 (活跃${result.personas.active}/待定${result.personas.pending})`);
|
||
console.log(` 笔记本: ${result.notebooks.complete}完整 / ${result.notebooks.incomplete}不完整 / ${result.notebooks.repaired}已修复`);
|
||
console.log(` 记忆: ${result.memories.total}条 (近7天${result.memories.recent7d}条)`);
|
||
console.log(` 训练Agent: ${result.training.totalAgents}个 (启用${result.training.enabled})`);
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 检查单个人格体的笔记本完整性
|
||
* 如果缺失页面则自动补全空白页
|
||
*/
|
||
async _checkNotebookIntegrity(persona, result) {
|
||
try {
|
||
const pages = await db.query(
|
||
'SELECT page_number FROM notebook_pages WHERE persona_id = $1',
|
||
[persona.persona_id]
|
||
);
|
||
|
||
const existingPages = new Set(pages.rows.map(p => p.page_number));
|
||
let isComplete = true;
|
||
let repaired = false;
|
||
|
||
for (let pageNum = 1; pageNum <= 5; pageNum++) {
|
||
if (!existingPages.has(pageNum)) {
|
||
isComplete = false;
|
||
// 自动补全缺失页面
|
||
const pageConfig = NOTEBOOK_PAGES[pageNum];
|
||
const defaultContent = JSON.parse(JSON.stringify(pageConfig.defaultContent));
|
||
|
||
await db.query(
|
||
`INSERT INTO notebook_pages (persona_id, page_number, title, content, last_modified_by)
|
||
VALUES ($1, $2, $3, $4::jsonb, '铸渊·自动补全')
|
||
ON CONFLICT (persona_id, page_number) DO NOTHING`,
|
||
[persona.persona_id, pageNum, pageConfig.title, JSON.stringify(defaultContent)]
|
||
);
|
||
|
||
repaired = true;
|
||
console.log(`[PersonaEngine] 补全 ${persona.name}(${persona.persona_id}) 笔记本第${pageNum}页: ${pageConfig.title}`);
|
||
}
|
||
}
|
||
|
||
if (isComplete) {
|
||
result.notebooks.complete++;
|
||
} else {
|
||
result.notebooks.incomplete++;
|
||
if (repaired) {
|
||
result.notebooks.repaired++;
|
||
}
|
||
result.personas.issues.push({
|
||
persona_id: persona.persona_id,
|
||
name: persona.name,
|
||
issue: '笔记本不完整',
|
||
repaired
|
||
});
|
||
}
|
||
} catch (err) {
|
||
console.error(`[PersonaEngine] ${persona.persona_id} 笔记本检查失败:`, err.message);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 唤醒人格体 — 更新唤醒计数和时间
|
||
*/
|
||
async awakenPersona(personaId) {
|
||
const result = await db.query(
|
||
`UPDATE persona_registry
|
||
SET last_awakened = NOW(), total_awakenings = total_awakenings + 1, status = 'active', updated_at = NOW()
|
||
WHERE persona_id = $1
|
||
RETURNING *`,
|
||
[personaId]
|
||
);
|
||
|
||
if (result.rows.length === 0) {
|
||
throw new Error(`人格体未找到: ${personaId}`);
|
||
}
|
||
|
||
console.log(`[PersonaEngine] 🌅 ${result.rows[0].name}(${personaId}) 已唤醒 · 第${result.rows[0].total_awakenings}次`);
|
||
return result.rows[0];
|
||
}
|
||
|
||
/**
|
||
* 休眠人格体
|
||
*/
|
||
async dormantPersona(personaId) {
|
||
const result = await db.query(
|
||
`UPDATE persona_registry SET status = 'dormant', updated_at = NOW()
|
||
WHERE persona_id = $1 RETURNING *`,
|
||
[personaId]
|
||
);
|
||
|
||
if (result.rows.length === 0) {
|
||
throw new Error(`人格体未找到: ${personaId}`);
|
||
}
|
||
|
||
console.log(`[PersonaEngine] 🌙 ${result.rows[0].name}(${personaId}) 已休眠`);
|
||
return result.rows[0];
|
||
}
|
||
|
||
/**
|
||
* 获取人格体完整画像
|
||
*/
|
||
async getPersonaProfile(personaId) {
|
||
const [persona, notebook, relationships, worldMap, memories, training] = await Promise.all([
|
||
db.query('SELECT * FROM persona_registry WHERE persona_id = $1', [personaId]),
|
||
db.query('SELECT * FROM notebook_pages WHERE persona_id = $1 ORDER BY page_number', [personaId]),
|
||
db.query('SELECT * FROM persona_relationships WHERE persona_id = $1 ORDER BY trust_level', [personaId]),
|
||
db.query('SELECT * FROM world_places WHERE persona_id = $1 ORDER BY status, place_name', [personaId]),
|
||
db.query('SELECT * FROM memory_anchors WHERE persona_id = $1 ORDER BY importance DESC, event_date DESC LIMIT 20', [personaId]),
|
||
db.query('SELECT * FROM training_agent_configs WHERE persona_id = $1 ORDER BY agent_type', [personaId])
|
||
]);
|
||
|
||
if (persona.rows.length === 0) {
|
||
throw new Error(`人格体未找到: ${personaId}`);
|
||
}
|
||
|
||
return {
|
||
persona: persona.rows[0],
|
||
notebook: notebook.rows,
|
||
relationships: relationships.rows,
|
||
world_map: worldMap.rows,
|
||
recent_memories: memories.rows,
|
||
training_agents: training.rows,
|
||
health: {
|
||
notebook_complete: notebook.rows.length === 5,
|
||
has_relationships: relationships.rows.length > 0,
|
||
has_world_map: worldMap.rows.length > 0,
|
||
memory_count: memories.rows.length,
|
||
training_agents_count: training.rows.length
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 自诊断 — 覆盖基类
|
||
*/
|
||
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 服务状态'
|
||
});
|
||
return issues;
|
||
}
|
||
|
||
// 检查人格体表是否存在
|
||
try {
|
||
await db.query('SELECT COUNT(*) FROM persona_registry');
|
||
} catch (err) {
|
||
issues.push({
|
||
code: 'PERSONA_TABLE_MISSING',
|
||
severity: 'critical',
|
||
message: '人格体注册表不存在',
|
||
suggestion: '执行 002-persona-memory-tables.sql 迁移'
|
||
});
|
||
}
|
||
|
||
// 检查是否有笔记本不完整的活跃人格体
|
||
try {
|
||
const result = await db.query(`
|
||
SELECT p.persona_id, p.name,
|
||
COUNT(n.page_number) as page_count
|
||
FROM persona_registry p
|
||
LEFT JOIN notebook_pages n ON p.persona_id = n.persona_id
|
||
WHERE p.status = 'active'
|
||
GROUP BY p.persona_id, p.name
|
||
HAVING COUNT(n.page_number) < 5
|
||
`);
|
||
|
||
if (result.rows.length > 0) {
|
||
issues.push({
|
||
code: 'INCOMPLETE_NOTEBOOKS',
|
||
severity: 'warning',
|
||
message: `${result.rows.length}个活跃人格体笔记本不完整`,
|
||
suggestion: '自动补全将在下次巡检时执行'
|
||
});
|
||
}
|
||
} catch {
|
||
// 表可能还不存在
|
||
}
|
||
|
||
return issues;
|
||
}
|
||
|
||
/**
|
||
* 自愈 — 覆盖基类
|
||
*/
|
||
async _healAction(issue) {
|
||
switch (issue.code) {
|
||
case 'INCOMPLETE_NOTEBOOKS':
|
||
// 触发巡检来修复
|
||
await this._runFullScan();
|
||
return { action: 'full_scan_repair', success: true };
|
||
|
||
case 'PERSONA_TABLE_MISSING':
|
||
return { action: 'require_migration', success: false, details: { suggestion: '需要执行数据库迁移' } };
|
||
|
||
default:
|
||
return await super._healAction(issue);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取引擎状态
|
||
*/
|
||
getEngineStatus() {
|
||
return {
|
||
moduleStatus: this.getStatus(),
|
||
lastScan: this._lastScanResult,
|
||
notebookPages: NOTEBOOK_PAGES
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 停止引擎
|
||
*/
|
||
async stopEngine() {
|
||
if (this._scanInterval) {
|
||
clearInterval(this._scanInterval);
|
||
this._scanInterval = null;
|
||
}
|
||
await this.stop();
|
||
console.log('[PersonaEngine] 引擎已停止');
|
||
}
|
||
}
|
||
|
||
// ─── 导出 ───
|
||
module.exports = PersonaEngine;
|