guanghulab/scripts/tree-gardener.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

352 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* ═══════════════════════════════════════════════════════════
* 🌳 树园丁 · Tree Gardener Agent
* ═══════════════════════════════════════════════════════════
*
* AG-ZY-GARDENER · 光之树健康巡检Agent
* 签发: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* 功能:
* 1. 扫描所有人格体的光之树
* 2. 检查孤叶(没有挂到树上的记忆锚点)
* 3. 计算树的生长方向(哪个分支最活跃)
* 4. 向天眼SYSLOG汇报树的健康状态
* 5. 刷新天眼涌现视图
*
* 运行方式:
* node scripts/tree-gardener.js [scan|report|refresh|full]
* - scan: 扫描孤叶和异常
* - report: 生成光之树健康报告
* - refresh: 刷新天眼视图
* - full: 完整巡检scan + report + refresh
*
* 建议触发频率: 每天 08:00/20:00 CST
*/
'use strict';
const https = require('https');
const http = require('http');
const fs = require('fs');
const path = require('path');
// ─── 配置 ───
const MCP_BASE = process.env.MCP_BASE_URL || 'http://127.0.0.1:3100';
const MCP_API_KEY = process.env.ZHUYUAN_API_KEY || '';
const AGENT_ID = 'AG-ZY-GARDENER';
const REPORT_DIR = path.join(__dirname, '..', 'data', 'tree-reports');
// ─── MCP调用封装 ───
async function callMCP(tool, input) {
const url = new URL('/call', MCP_BASE);
return new Promise((resolve, reject) => {
const postData = JSON.stringify({ tool, input, caller: AGENT_ID });
const isHttps = url.protocol === 'https:';
const client = isHttps ? https : http;
const options = {
hostname: url.hostname,
port: url.port || (isHttps ? 443 : 80),
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData),
...(MCP_API_KEY ? { 'Authorization': `Bearer ${MCP_API_KEY}` } : {})
}
};
const req = client.request(options, (res) => {
let data = '';
res.on('data', chunk => { data += chunk; });
res.on('end', () => {
try {
const parsed = JSON.parse(data);
if (parsed.error) {
reject(new Error(parsed.message || 'MCP调用失败'));
} else {
resolve(parsed.result || parsed);
}
} catch (e) {
reject(new Error(`MCP响应解析失败: ${data.substring(0, 200)}`));
}
});
});
req.on('error', reject);
req.setTimeout(30000, () => {
req.destroy();
reject(new Error('MCP调用超时'));
});
req.write(postData);
req.end();
});
}
// ─── 扫描任务 ───
/**
* 扫描孤叶: 检查memory_anchors中没有tree_node_id的记录
*/
async function scanOrphanMemories() {
console.log('[树园丁] 🔍 开始扫描孤叶...');
const startTime = Date.now();
try {
// 通过MCP查询人格体列表
const { personas } = await callMCP('listPersonas', {});
let orphanCount = 0;
const orphanDetails = [];
for (const persona of personas) {
// 查询该人格体的未关联记忆
const { anchors } = await callMCP('queryMemoryAnchors', {
persona_id: persona.persona_id,
limit: 200
});
// 检查是否有tree_node_id通过详情判断
// 注意: 初始状态所有记忆都是孤叶,这是正常的
if (anchors && anchors.length > 0) {
const noTreeLink = anchors.filter(a => !a.tree_node_id);
if (noTreeLink.length > 0) {
orphanCount += noTreeLink.length;
orphanDetails.push({
persona_id: persona.persona_id,
name: persona.name,
orphan_count: noTreeLink.length,
total_memories: anchors.length
});
}
}
}
const duration = Date.now() - startTime;
console.log(`[树园丁] ✅ 孤叶扫描完成: ${orphanCount} 条未关联记忆 (${duration}ms)`);
// 写入天眼SYSLOG
await callMCP('writeSyslog', {
agent_id: AGENT_ID,
action: 'scan_orphan_memories',
result: orphanCount > 0 ? 'warning' : 'success',
message: `孤叶扫描: ${orphanCount} 条未关联记忆`,
details: { orphan_count: orphanCount, persona_details: orphanDetails },
duration_ms: duration
});
return { orphan_count: orphanCount, details: orphanDetails };
} catch (err) {
console.error(`[树园丁] ❌ 孤叶扫描失败: ${err.message}`);
await callMCP('writeSyslog', {
agent_id: AGENT_ID,
action: 'scan_orphan_memories',
result: 'error',
message: `扫描失败: ${err.message}`,
duration_ms: Date.now() - startTime
}).catch(() => {});
return { error: err.message };
}
}
/**
* 计算树的生长方向: 哪个分支最活跃
*/
async function analyzeGrowthDirection() {
console.log('[树园丁] 📊 分析光之树生长方向...');
const startTime = Date.now();
try {
const { personas } = await callMCP('listPersonas', {});
const branchStats = [];
for (const persona of personas) {
try {
const branchResult = await callMCP('getPersonaBranch', { persona_id: persona.persona_id });
if (branchResult.branch) {
branchStats.push({
persona_id: persona.persona_id,
name: persona.name,
branch_id: branchResult.branch.id,
node_type: branchResult.branch.node_type,
stats: branchResult.stats,
last_activity: branchResult.children.length > 0
? branchResult.children[0].created_at
: branchResult.branch.created_at
});
}
} catch {
// 某些人格体可能还没有分支
}
}
// 按活跃度排序
branchStats.sort((a, b) => (b.stats?.total || 0) - (a.stats?.total || 0));
const duration = Date.now() - startTime;
const mostActive = branchStats.length > 0 ? branchStats[0] : null;
console.log(`[树园丁] ✅ 生长分析完成: ${branchStats.length} 个分支 (${duration}ms)`);
if (mostActive) {
console.log(`[树园丁] 最活跃分支: ${mostActive.name} (${mostActive.stats?.total || 0} 节点)`);
}
await callMCP('writeSyslog', {
agent_id: AGENT_ID,
action: 'analyze_growth',
result: 'success',
message: `生长分析: ${branchStats.length} 个分支, 最活跃: ${mostActive?.name || '无'}`,
details: { branches: branchStats, most_active: mostActive },
duration_ms: duration
});
return { branches: branchStats, most_active: mostActive };
} catch (err) {
console.error(`[树园丁] ❌ 生长分析失败: ${err.message}`);
return { error: err.message };
}
}
// ─── 报告任务 ───
/**
* 生成光之树健康报告
*/
async function generateReport() {
console.log('[树园丁] 📋 生成光之树健康报告...');
const startTime = Date.now();
try {
// 获取天眼视图
const tianyan = await callMCP('getTianyanView', {});
// 获取生长方向
const growth = await analyzeGrowthDirection();
const report = {
report_id: `TREE-RPT-${new Date().toISOString().slice(0, 10).replace(/-/g, '')}`,
generated_at: new Date().toISOString(),
generated_by: AGENT_ID,
tianyan_status: tianyan.tianyan || {},
tree_root: tianyan.tree_root,
persona_branches: tianyan.persona_branches,
growth_analysis: {
total_branches: growth.branches?.length || 0,
most_active: growth.most_active,
branch_details: growth.branches || []
},
recent_syslog: tianyan.recent_syslog?.slice(0, 5) || []
};
// 保存报告到文件
if (!fs.existsSync(REPORT_DIR)) {
fs.mkdirSync(REPORT_DIR, { recursive: true });
}
const reportFile = path.join(REPORT_DIR, `${report.report_id}.json`);
fs.writeFileSync(reportFile, JSON.stringify(report, null, 2), 'utf-8');
const duration = Date.now() - startTime;
console.log(`[树园丁] ✅ 报告已保存: ${reportFile} (${duration}ms)`);
await callMCP('writeSyslog', {
agent_id: AGENT_ID,
action: 'generate_report',
result: 'success',
message: `光之树健康报告已生成: ${report.report_id}`,
details: { report_id: report.report_id, file: reportFile },
duration_ms: duration
});
return report;
} catch (err) {
console.error(`[树园丁] ❌ 报告生成失败: ${err.message}`);
return { error: err.message };
}
}
// ─── 刷新天眼视图 ───
async function refreshTianyanView() {
console.log('[树园丁] 🔄 刷新天眼涌现视图...');
const startTime = Date.now();
try {
const tianyan = await callMCP('getTianyanView', {});
const duration = Date.now() - startTime;
console.log(`[树园丁] ✅ 天眼视图已刷新 (${duration}ms)`);
if (tianyan.tianyan) {
console.log(`[树园丁] 系统健康度: ${tianyan.tianyan.health_percent_1h || 'N/A'}%`);
console.log(`[树园丁] 活跃人格体: ${tianyan.tianyan.active_personas_24h || 0}`);
console.log(`[树园丁] 树生长速度: ${tianyan.tianyan.tree_growth_24h || 0} 节点/24h`);
console.log(`[树园丁] 树总规模: ${tianyan.tianyan.tree_total_nodes || 0} 节点`);
}
return tianyan;
} catch (err) {
console.error(`[树园丁] ❌ 天眼刷新失败: ${err.message}`);
return { error: err.message };
}
}
// ─── 主入口 ───
async function main() {
const command = process.argv[2] || 'full';
console.log(`\n${'═'.repeat(60)}`);
console.log(` 🌳 树园丁 · Tree Gardener Agent`);
console.log(` ${AGENT_ID} · 光之树健康巡检`);
console.log(` 命令: ${command}`);
console.log(` 时间: ${new Date().toISOString()}`);
console.log(`${'═'.repeat(60)}\n`);
const results = {};
switch (command) {
case 'scan':
results.orphans = await scanOrphanMemories();
break;
case 'report':
results.report = await generateReport();
break;
case 'refresh':
results.tianyan = await refreshTianyanView();
break;
case 'full':
default:
results.orphans = await scanOrphanMemories();
results.report = await generateReport();
results.tianyan = await refreshTianyanView();
break;
}
console.log(`\n${'─'.repeat(60)}`);
console.log(`[树园丁] 巡检完成: ${command}`);
console.log(`${'─'.repeat(60)}\n`);
return results;
}
// 如果直接运行
if (require.main === module) {
main()
.then(results => {
if (results.error) {
process.exit(1);
}
})
.catch(err => {
console.error(`[树园丁] 致命错误: ${err.message}`);
process.exit(1);
});
}
module.exports = { main, scanOrphanMemories, analyzeGrowthDirection, generateReport, refreshTianyanView };