/** * 压缩官 — 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;