zhizhi/src/brain/memory-manager.js

168 lines
5.7 KiB
JavaScript
Raw Normal View History

// src/brain/memory-manager.js
// 记忆管理器 — 三层记忆架构
// 职责:短期/中期/长期记忆管理,记忆候选生成,写入判定
//
// 记忆分层:
// 短期session当前会话消息存在前端 + 进程内存
// 中期task当前任务状态、开发者进度、决策上下文存在进程内存
// 长期persistent身份信息、项目目标、关键决策写入 brain 文件
'use strict';
const fs = require('fs');
const path = require('path');
// 中期记忆存储(进程内存,重启丢失)
const taskMemory = new Map();
// 记忆候选关键词 — 检测哪些信息值得生成记忆候选
const MEMORY_CANDIDATE_PATTERNS = [
{ type: 'identity_change', pattern: /身份|角色|权限|加入团队|离开|转岗/i, priority: 'high' },
{ type: 'project_goal', pattern: /目标|里程碑|milestone|deadline|截止|规划|roadmap/i, priority: 'high' },
{ type: 'module_ownership', pattern: /负责|接手|模块|m\d{2}-|归属|分工/i, priority: 'medium' },
{ type: 'decision', pattern: /决定|确定|方案|选择|采用|放弃|不再|改为/i, priority: 'high' },
{ type: 'todo', pattern: /待办|todo|下一步|接下来|计划|需要做/i, priority: 'medium' },
{ type: 'bug_fix', pattern: /修复|fix|解决|排查到|根因|原因是/i, priority: 'low' },
{ type: 'deployment', pattern: /上线|部署|发布|deploy|release|版本/i, priority: 'medium' },
];
/**
* 判定规则是否值得写入长期记忆
* 不是什么都记只记高优先级的或中等优先级且重复出现的
*/
const WRITE_RULES = {
high: { minOccurrences: 1, description: '首次出现即记录' },
medium: { minOccurrences: 2, description: '重复提及 2 次后记录' },
low: { minOccurrences: 3, description: '反复提及 3 次后记录' },
};
/**
* 分析文本生成记忆候选
* @param {string} text - 要分析的文本
* @param {string} source - 来源 (user/assistant/system)
* @returns {Array<{type: string, priority: string, excerpt: string, shouldPersist: boolean}>}
*/
function generateCandidates(text, source = 'user') {
if (!text || typeof text !== 'string') return [];
const candidates = [];
for (const { type, pattern, priority } of MEMORY_CANDIDATE_PATTERNS) {
if (pattern.test(text)) {
// 提取匹配上下文的前后50字符作为摘要
const match = text.match(pattern);
const idx = match ? match.index : 0;
const start = Math.max(0, idx - 30);
const end = Math.min(text.length, idx + 80);
const excerpt = text.slice(start, end).replace(/\n/g, ' ').trim();
// 检查中期记忆中的出现次数(使用类型+摘要前40字符作为键降低碰撞风险
const memKey = type + ':' + excerpt.slice(0, 40).replace(/\s+/g, '_');
const taskEntry = taskMemory.get(memKey) || { count: 0 };
taskEntry.count++;
taskEntry.lastSeen = Date.now();
taskMemory.set(memKey, taskEntry);
const rule = WRITE_RULES[priority];
const shouldPersist = taskEntry.count >= rule.minOccurrences;
candidates.push({
type,
priority,
excerpt,
source,
shouldPersist,
occurrences: taskEntry.count,
threshold: rule.minOccurrences,
});
}
}
return candidates;
}
/**
* 保存记忆到中期存储任务级别
* @param {string} sessionId
* @param {string} key
* @param {*} value
*/
function setTaskMemory(sessionId, key, value) {
const sKey = sessionId + ':' + key;
taskMemory.set(sKey, { value, updatedAt: Date.now() });
}
/**
* 读取中期记忆
* @param {string} sessionId
* @param {string} key
* @returns {*}
*/
function getTaskMemory(sessionId, key) {
const entry = taskMemory.get(sessionId + ':' + key);
return entry ? entry.value : undefined;
}
/**
* 清理过期的中期记忆超过 2 小时的条目
*/
function cleanupTaskMemory() {
const cutoff = Date.now() - 2 * 60 * 60 * 1000;
for (const [key, entry] of taskMemory.entries()) {
const ts = entry.updatedAt || entry.lastSeen || 0;
if (ts < cutoff) {
taskMemory.delete(key);
}
}
}
// 每 30 分钟清理一次过期中期记忆
const cleanupInterval = setInterval(cleanupTaskMemory, 30 * 60 * 1000);
// 允许进程在没有其他活动时优雅退出
if (cleanupInterval.unref) cleanupInterval.unref();
/**
* 获取长期记忆 brain 文件读取
* @returns {Object}
*/
function loadLongTermMemory() {
const memoryPath = path.join(__dirname, '../../.github/brain/memory.json');
try {
return JSON.parse(fs.readFileSync(memoryPath, 'utf8'));
} catch (e) {
return null;
}
}
/**
* 获取记忆层状态概览
*/
function getMemoryStatus() {
const longTerm = loadLongTermMemory();
return {
layers: {
short_term: { type: 'session', storage: 'frontend + process', description: '当前会话消息' },
mid_term: { type: 'task', storage: 'process memory', entries: taskMemory.size, description: '当前任务/开发者状态' },
long_term: { type: 'persistent', storage: 'brain files', loaded: !!longTerm, description: '身份/目标/决策/待办' },
},
write_rules: WRITE_RULES,
candidate_types: MEMORY_CANDIDATE_PATTERNS.map(p => ({ type: p.type, priority: p.priority })),
recovery_path: [
'.github/brain/memory.json',
'.github/brain/routing-map.json',
'.github/brain/wake-protocol.md',
'.github/persona-brain/dev-status.json',
],
};
}
module.exports = {
generateCandidates,
setTaskMemory,
getTaskMemory,
cleanupTaskMemory,
loadLongTermMemory,
getMemoryStatus,
MEMORY_CANDIDATE_PATTERNS,
WRITE_RULES,
};