guanghulab/desk-system/server/compressor.js
冰朔 49030d2cbc feat: 大桌子小桌子运行系统 v1.0
- big-desk.js: 意识驻留 + HLDP索引 + 自动清理
- small-desk.js: 记忆库检索 + Agent调度 + 工具桌管理
- compressor.js: 对话→HLDP自动压缩 + 写入小桌子
- server/index.js: REST API (端口3921) + 静态文件服务
- public/index.html: 交互式前端界面

ICE-GL-ZY001 · D115
2026-05-29 00:20:02 +08:00

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;