zhizhi/src/brain/memory-manager.js

168 lines
5.6 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.

// 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();
// 检查中期记忆中的出现次数
const memKey = type + ':' + excerpt.slice(0, 20);
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,
};