2026-03-10 17:57:26 +08:00
|
|
|
|
// 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();
|
|
|
|
|
|
|
2026-03-10 18:02:39 +08:00
|
|
|
|
// 检查中期记忆中的出现次数(使用类型+摘要前40字符作为键,降低碰撞风险)
|
|
|
|
|
|
const memKey = type + ':' + excerpt.slice(0, 40).replace(/\s+/g, '_');
|
2026-03-10 17:57:26 +08:00
|
|
|
|
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,
|
|
|
|
|
|
};
|