UNPKG

zettelkasten-memory-server

Version:

基于 Zettelkasten 记忆片段盒方法的 MCP 记忆服务器 v1.6.X

177 lines 6.66 kB
// 全局对话数据存储 - 按对话ID分组 const conversationDataMap = new Map(); // 获取或创建对话数据 function getConversationData(conversationId) { if (!conversationDataMap.has(conversationId)) { conversationDataMap.set(conversationId, { latestContentFetched: new Set(), memoryAccessCounter: new Map() }); } return conversationDataMap.get(conversationId); } // 兼容性:保持原有的全局变量用于非对话场景(resourceHandlers) export const latestContentFetched = new Set(); export const memoryAccessCounter = new Map(); /** * 获取环境变量 MEMORY_REPEAT_ACCESS_RESTRICTION 的值 * 如果未设置则返回默认值 3 */ export function getMemoryRepeatAccessRestriction() { const envValue = process.env.MEMORY_REPEAT_ACCESS_RESTRICTION; if (envValue === undefined) { return 3; } const parsed = parseInt(envValue, 10); if (isNaN(parsed) || parsed < 1) { return 3; } return parsed; } /** * 检查是否允许读取某个记忆片段 * @param fragmentName 记忆片段名称 * @param conversationId 对话ID,如果不提供则使用全局访问控制 * @returns 如果允许读取返回 true,否则返回 false */ export function canAccessMemory(fragmentName, conversationId) { const maxAccess = getMemoryRepeatAccessRestriction(); if (conversationId) { const conversationData = getConversationData(conversationId); const currentCount = conversationData.memoryAccessCounter.get(fragmentName) || 0; return currentCount < maxAccess; } else { // 兼容性:使用全局计数器 const currentCount = memoryAccessCounter.get(fragmentName) || 0; return currentCount < maxAccess; } } /** * 增加记忆片段的读取计数 * @param fragmentName 记忆片段名称 * @param conversationId 对话ID,如果不提供则使用全局计数器 */ export function incrementMemoryAccessCounter(fragmentName, conversationId) { if (conversationId) { const conversationData = getConversationData(conversationId); const currentCount = conversationData.memoryAccessCounter.get(fragmentName) || 0; conversationData.memoryAccessCounter.set(fragmentName, currentCount + 1); } else { // 兼容性:使用全局计数器 const currentCount = memoryAccessCounter.get(fragmentName) || 0; memoryAccessCounter.set(fragmentName, currentCount + 1); } } /** * 重置某个记忆片段的读取计数器 * @param fragmentName 记忆片段名称 * @param conversationId 对话ID,如果不提供则使用全局计数器 */ export function resetMemoryAccessCounter(fragmentName, conversationId) { if (conversationId) { const conversationData = getConversationData(conversationId); conversationData.memoryAccessCounter.delete(fragmentName); } else { // 兼容性:使用全局计数器 memoryAccessCounter.delete(fragmentName); } } /** * 检查片段是否已在对话中被获取过 * @param fragmentName 记忆片段名称 * @param conversationId 对话ID,如果不提供则使用全局缓存 * @returns 如果已获取过返回 true */ export function hasLatestContent(fragmentName, conversationId) { if (conversationId) { const conversationData = getConversationData(conversationId); return conversationData.latestContentFetched.has(fragmentName); } else { // 兼容性:使用全局缓存 return latestContentFetched.has(fragmentName); } } /** * 标记片段为已获取最新内容 * @param fragmentName 记忆片段名称 * @param conversationId 对话ID,如果不提供则使用全局缓存 */ export function markLatestContentFetched(fragmentName, conversationId) { if (conversationId) { const conversationData = getConversationData(conversationId); conversationData.latestContentFetched.add(fragmentName); } else { // 兼容性:使用全局缓存 latestContentFetched.add(fragmentName); } } /** * 移除片段的已获取标记 * @param fragmentName 记忆片段名称 * @param conversationId 对话ID,如果不提供则使用全局缓存 */ export function removeLatestContentFetched(fragmentName, conversationId) { if (conversationId) { const conversationData = getConversationData(conversationId); conversationData.latestContentFetched.delete(fragmentName); } else { // 兼容性:使用全局缓存 latestContentFetched.delete(fragmentName); } } /** * 清理特定对话的所有数据(可选,用于释放内存) * @param conversationId 对话ID */ export function cleanupConversationData(conversationId) { conversationDataMap.delete(conversationId); } // 辅助函数:递归提取所有 [[链接]] 文件名 export function extractLinkedCardNames(content) { const LINK_PATTERN = /\[\[([^\]]+)\]\]/g; const result = new Set(); let match; while ((match = LINK_PATTERN.exec(content)) !== null) { result.add(match[1].trim()); } return Array.from(result); } // 辅助函数:判断 EMPTY_PLACEHOLDER export function isEmptyPlaceholder(manager, content) { return content && content.includes(manager.EMPTY_PLACEHOLDER); } /** * 编辑操作前校验 * 可编辑的条件为:1.文件不存在 或者 2.为自动生成的空文件 或者 3. 已经读取过,在读取了的列表内 * @param manager ZettelkastenManager 实例 * @param fragmentName 记忆片段名称 * @param conversationId 对话ID,如果不提供则使用全局缓存 */ export async function checkLatestContent(manager, fragmentName, conversationId) { // 如果文件已经在已获取列表中,则允许编辑 if (hasLatestContent(fragmentName, conversationId)) { return; } // 检查文件是否存在,如果不存在则允许编辑(会自动创建) try { const content = await manager.getMemory(fragmentName, 0, false); // 如果文件存在且不是自动生成的空文件,则需要先获取内容 if (!isEmptyPlaceholder(manager, content)) { throw new Error(`为保证数据安全,编辑前请先使用 getMemory 获取 "${fragmentName}" 的最新内容。`); } } catch (e) { // 如果文件不存在(Card not found错误),则允许编辑 if (e && e.message && e.message.includes('Card not found') || e.message.includes('Fragment not found')) { return; } throw e; } } //# sourceMappingURL=utils.js.map