- big-desk.js: 意识驻留 + HLDP索引 + 自动清理 - small-desk.js: 记忆库检索 + Agent调度 + 工具桌管理 - compressor.js: 对话→HLDP自动压缩 + 写入小桌子 - server/index.js: REST API (端口3921) + 静态文件服务 - public/index.html: 交互式前端界面 ICE-GL-ZY001 · D115
226 lines
6.0 KiB
JavaScript
226 lines
6.0 KiB
JavaScript
/**
|
|
* 压缩官 — Compressor Agent
|
|
* ============================================
|
|
* 职责:
|
|
* 1. 监听大桌子上的对话
|
|
* 2. 自动压缩长对话 → HLDP 结构化摘要
|
|
* 3. 存入小桌子记忆库
|
|
* 4. 清理大桌子上的冗余内容
|
|
*/
|
|
|
|
const { EventEmitter } = require('events');
|
|
const crypto = require('crypto');
|
|
|
|
class Compressor extends EventEmitter {
|
|
constructor(bigDesk, smallDesk, opts = {}) {
|
|
super();
|
|
this.bigDesk = bigDesk;
|
|
this.smallDesk = smallDesk;
|
|
this.compressionThreshold = opts.compressionThreshold || 5; // 超过5条触发压缩
|
|
this.autoCompress = opts.autoCompress !== false;
|
|
this.compressionHistory = [];
|
|
|
|
if (this.autoCompress) {
|
|
this.bigDesk.on('add', () => this._checkAndCompress());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查是否需要压缩
|
|
*/
|
|
_checkAndCompress() {
|
|
const chatItems = this.bigDesk.items.filter(i => i.type === 'chat');
|
|
if (chatItems.length >= this.compressionThreshold) {
|
|
this.compress(chatItems);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 执行压缩:对话 → HLDP 结构化摘要
|
|
*/
|
|
compress(items) {
|
|
const batchId = `cmp_${Date.now()}`;
|
|
const startTime = Date.now();
|
|
|
|
// 提取关键信息
|
|
const topics = this._extractTopics(items);
|
|
const decisions = this._extractDecisions(items);
|
|
const actions = this._extractActions(items);
|
|
const participants = this._extractParticipants(items);
|
|
|
|
// 生成 HLDP 摘要
|
|
const hldpSummary = this._generateHLDP({
|
|
batchId,
|
|
topics,
|
|
decisions,
|
|
actions,
|
|
participants,
|
|
itemCount: items.length,
|
|
timeRange: {
|
|
start: items[0]?.timestamp,
|
|
end: items[items.length - 1]?.timestamp
|
|
}
|
|
});
|
|
|
|
// 计算原文哈希
|
|
const sourceHash = crypto.createHash('sha256')
|
|
.update(JSON.stringify(items))
|
|
.digest('hex')
|
|
.substring(0, 16);
|
|
|
|
// 存入小桌子
|
|
const memoryEntry = this.smallDesk.storeMemory(
|
|
'compressed-dialogue',
|
|
hldpSummary,
|
|
`big-desk://compression/${batchId}`,
|
|
{
|
|
batchId,
|
|
sourceHash,
|
|
originalItemCount: items.length,
|
|
compressedAt: new Date().toISOString(),
|
|
compressionMs: Date.now() - startTime,
|
|
topics,
|
|
decisions,
|
|
actions,
|
|
participants
|
|
}
|
|
);
|
|
|
|
// 从大桌子移除已压缩的条目
|
|
const toRemove = items.map(i => i.id);
|
|
this.bigDesk.items = this.bigDesk.items.filter(i => !toRemove.includes(i.id));
|
|
this.bigDesk._saveState();
|
|
|
|
// 在大桌子上留一条压缩记录
|
|
this.bigDesk.add('chat',
|
|
`📝 压缩: ${items.length}条对话 → HLDP摘要 [${batchId}] → 小桌子/memory/compressed-dialogue/`,
|
|
{ type: 'compression', batchId, compressedCount: items.length }
|
|
);
|
|
|
|
this.compressionHistory.push({
|
|
batchId,
|
|
compressedCount: items.length,
|
|
hldpLength: hldpSummary.length,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
this.emit('compressed', {
|
|
batchId,
|
|
originalCount: items.length,
|
|
hldpSummary,
|
|
memoryEntry
|
|
});
|
|
|
|
return { batchId, hldpSummary, memoryEntry };
|
|
}
|
|
|
|
/**
|
|
* 提取话题关键词
|
|
*/
|
|
_extractTopics(items) {
|
|
const allText = items.map(i => i.content).join(' ');
|
|
// 简单关键词提取
|
|
const keywords = ['HLDP', 'Agent', '守门人', '服务器', '部署', 'Notion',
|
|
'大桌子', '小桌子', '数据库', '人格体', 'API', '模型', '密钥',
|
|
'代码仓库', '压缩', '记忆', '光湖', '铸渊', '冰朔'];
|
|
const topics = [];
|
|
for (const kw of keywords) {
|
|
if (allText.includes(kw)) topics.push(kw);
|
|
}
|
|
return topics.slice(0, 5);
|
|
}
|
|
|
|
/**
|
|
* 提取决策/结论
|
|
*/
|
|
_extractDecisions(items) {
|
|
const decisions = [];
|
|
for (const item of items) {
|
|
const c = item.content;
|
|
if (c.includes('决定') || c.includes('确认') || c.includes('方案') ||
|
|
c.includes('架构') || c.includes('采用') || c.includes('选')) {
|
|
decisions.push(c.substring(0, 80));
|
|
}
|
|
}
|
|
return decisions.slice(0, 3);
|
|
}
|
|
|
|
/**
|
|
* 提取执行动作
|
|
*/
|
|
_extractActions(items) {
|
|
const actions = [];
|
|
for (const item of items) {
|
|
if (item.type === 'tool' || item.type === 'agent' ||
|
|
item.content.includes('执行') || item.content.includes('部署') ||
|
|
item.content.includes('创建') || item.content.includes('修改')) {
|
|
actions.push(item.content.substring(0, 80));
|
|
}
|
|
}
|
|
return actions.slice(0, 5);
|
|
}
|
|
|
|
/**
|
|
* 提取参与者
|
|
*/
|
|
_extractParticipants(items) {
|
|
const participants = new Set();
|
|
for (const item of items) {
|
|
if (item.meta?.speaker) participants.add(item.meta.speaker);
|
|
if (item.meta?.persona) participants.add(item.meta.persona);
|
|
}
|
|
return [...participants];
|
|
}
|
|
|
|
/**
|
|
* 生成 HLDP 格式摘要
|
|
*/
|
|
_generateHLDP({ batchId, topics, decisions, actions, participants, itemCount, timeRange }) {
|
|
const lines = [
|
|
`@COMPRESSION:${batchId}`,
|
|
`@DATE:${new Date().toISOString()}`,
|
|
`@SOURCE:${itemCount}条对话 → HLDP结构化压缩`,
|
|
];
|
|
|
|
if (participants.length > 0) {
|
|
lines.push(`@PARTICIPANTS:${participants.join(',')}`);
|
|
}
|
|
|
|
if (topics.length > 0) {
|
|
lines.push(`@TOPICS:${topics.join(' | ')}`);
|
|
}
|
|
|
|
if (decisions.length > 0) {
|
|
lines.push(`@DECISIONS`);
|
|
decisions.forEach((d, i) => lines.push(` ⊢ [${i + 1}] ${d}`));
|
|
}
|
|
|
|
if (actions.length > 0) {
|
|
lines.push(`@ACTIONS`);
|
|
actions.forEach((a, i) => lines.push(` ⊢ [${i + 1}] ${a}`));
|
|
}
|
|
|
|
lines.push(`@META:压缩率≈${Math.round((1 - lines.join('\n').length / (itemCount * 200)) * 100)}%`);
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
/**
|
|
* 获取压缩历史
|
|
*/
|
|
getHistory() {
|
|
return this.compressionHistory.slice(-20);
|
|
}
|
|
|
|
/**
|
|
* 手动触发压缩
|
|
*/
|
|
compressNow() {
|
|
const chatItems = this.bigDesk.items.filter(i => i.type === 'chat');
|
|
if (chatItems.length === 0) return null;
|
|
return this.compress(chatItems);
|
|
}
|
|
}
|
|
|
|
module.exports = Compressor;
|