zettelkasten-memory-server
Version:
基于 Zettelkasten 记忆片段盒方法的 MCP 记忆服务器 v1.6.X
177 lines • 6.66 kB
JavaScript
// 全局对话数据存储 - 按对话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