Files
BaiLongma/orchestrator-v2/curator.js
chengjiaxi 664ffb3834 小白龙 Bailongma - 初始提交
自主操作员与思考搭档系统。
包含 orchestrator-v2 多Agent编排层、后台意识引擎、记忆系统、ACUI 组件。
2026-05-22 19:22:06 +08:00

141 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const fs = require('fs');
const path = require('path');
// Curator — background skill maintenance orchestrator
// Periodically reviews agent-created skills and memories for
// consolidation, staleness, and merge opportunities.
class Curator {
constructor(config) {
this.config = config || {};
this.skillsDir = config.skillsDir || path.join(process.env.HOME || process.env.USERPROFILE || '.', '.hermes', 'skills');
this.reportDir = config.reportDir || path.join(__dirname, 'reports');
this.staleDays = config.staleDays || 30;
this.archiveDays = config.archiveDays || 90;
this.enabled = config.enabled !== false;
}
async run(memoryProvider) {
if (!this.enabled) return { status: 'disabled' };
var report = {
timestamp: new Date().toISOString(),
staleSkills: [],
archivedSkills: [],
mergedSuggestions: [],
memoryAnalysis: null
};
// 1. Check skill staleness
try {
var skills = this._scanSkills();
report.staleSkills = this._checkStaleness(skills);
report.archivedSkills = this._checkArchive(skills);
} catch (e) {
report.skillError = e.message;
}
// 2. Analyze memories for consolidation opportunities
if (memoryProvider) {
try {
report.memoryAnalysis = this._analyzeMemories(memoryProvider);
} catch (e) {
report.memoryError = e.message;
}
}
// 3. Generate report
this._saveReport(report);
return report;
}
_scanSkills() {
if (!fs.existsSync(this.skillsDir)) return [];
var entries = [];
try {
var items = fs.readdirSync(this.skillsDir, { withFileTypes: true });
for (var i = 0; i < items.length; i++) {
if (items[i].isDirectory() || items[i].name.endsWith('.md')) {
var fullPath = path.join(this.skillsDir, items[i].name);
var stat = fs.statSync(fullPath);
entries.push({
name: items[i].name,
path: fullPath,
isDir: items[i].isDirectory(),
created: stat.birthtime,
modified: stat.mtime,
ageDays: (Date.now() - stat.mtime.getTime()) / 86400000
});
}
}
} catch (e) {}
return entries;
}
_checkStaleness(skills) {
return skills.filter(function(s) {
return s.ageDays > this.staleDays && s.ageDays < this.archiveDays;
}.bind(this)).map(function(s) {
return { name: s.name, ageDays: Math.round(s.ageDays), action: 'mark-stale' };
});
}
_checkArchive(skills) {
return skills.filter(function(s) {
return s.ageDays > this.archiveDays;
}.bind(this)).map(function(s) {
return { name: s.name, ageDays: Math.round(s.ageDays), action: 'archive' };
});
}
_analyzeMemories(memoryProvider) {
var stats = memoryProvider.getStats();
var suggestions = [];
// Check for memory type balance
if (stats.byType) {
var total = stats.total || 0;
if (total > 50) {
suggestions.push('记忆总数超过50条建议整理合并相似条目');
}
var taskResults = stats.byType.task_result || 0;
if (taskResults > 20) {
suggestions.push('task_result 类型记忆过多(' + taskResults + '条),建议按项目归类');
}
}
return {
totalMemories: stats.total || 0,
byType: stats.byType || {},
suggestions: suggestions
};
}
_saveReport(report) {
if (!fs.existsSync(this.reportDir)) fs.mkdirSync(this.reportDir, { recursive: true });
var ts = new Date().toISOString().replace(/[:.]/g, '-');
var reportPath = path.join(this.reportDir, 'curator-' + ts + '.json');
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf8');
}
getReportHistory(limit) {
if (limit === undefined) limit = 5;
if (!fs.existsSync(this.reportDir)) return [];
try {
var files = fs.readdirSync(this.reportDir)
.filter(function(f) { return f.startsWith('curator-') && f.endsWith('.json'); })
.sort()
.reverse()
.slice(0, limit);
var reports = [];
for (var i = 0; i < files.length; i++) {
try {
reports.push(JSON.parse(fs.readFileSync(path.join(this.reportDir, files[i]), 'utf8')));
} catch (e) {}
}
return reports;
} catch (e) { return []; }
}
}
module.exports = Curator;