Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ interface SupermemoryConfig {
filterPrompt?: string;
keywordPatterns?: string[];
compactionThreshold?: number;
/** Re-inject context every N messages. 0 = first message only (default). */
reinjectEveryN?: number;
/** Keywords that trigger immediate context re-injection. */
recallKeywordPatterns?: string[];
}

const DEFAULT_KEYWORD_PATTERNS = [
Expand Down Expand Up @@ -57,6 +61,19 @@ const DEFAULT_KEYWORD_PATTERNS = [
"ノートして",
];

const DEFAULT_RECALL_KEYWORD_PATTERNS = [
// English
"recall",
"what\\s+do\\s+you\\s+remember",
"check\\s+memory",
"search\\s+memory",
// Japanese
"思い出して",
"記憶を?検索",
"メモリ[ーを]?確認",
"何か覚えてる",
];

const DEFAULTS: Required<Omit<SupermemoryConfig, "apiKey" | "userContainerTag" | "projectContainerTag">> = {
similarityThreshold: 0.6,
maxMemories: 5,
Expand All @@ -67,6 +84,8 @@ const DEFAULTS: Required<Omit<SupermemoryConfig, "apiKey" | "userContainerTag" |
filterPrompt: "You are a stateful coding agent. Remember all the information, including but not limited to user's coding preferences, tech stack, behaviours, workflows, and any other relevant details.",
keywordPatterns: [],
compactionThreshold: 0.80,
reinjectEveryN: 0,
recallKeywordPatterns: [],
};

function isValidRegex(pattern: string): boolean {
Expand Down Expand Up @@ -127,6 +146,11 @@ export const CONFIG = {
...(fileConfig.keywordPatterns ?? []).filter(isValidRegex),
],
compactionThreshold: validateCompactionThreshold(fileConfig.compactionThreshold),
reinjectEveryN: fileConfig.reinjectEveryN ?? DEFAULTS.reinjectEveryN,
recallKeywordPatterns: [
...DEFAULT_RECALL_KEYWORD_PATTERNS,
...(fileConfig.recallKeywordPatterns ?? []).filter(isValidRegex),
],
};

export function isConfigured(): boolean {
Expand Down
26 changes: 22 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const CODE_BLOCK_PATTERN = /```[\s\S]*?```/g;
const INLINE_CODE_PATTERN = /`[^`]+`/g;

const MEMORY_KEYWORD_PATTERN = new RegExp(`\\b(${CONFIG.keywordPatterns.join("|")})\\b`, "i");
const RECALL_KEYWORD_PATTERN = new RegExp(`\\b(${CONFIG.recallKeywordPatterns.join("|")})\\b`, "i");

const MEMORY_NUDGE_MESSAGE = `[MEMORY TRIGGER DETECTED]
The user wants you to remember something. You MUST use the \`supermemory\` tool with \`mode: "add"\` to save this information.
Expand All @@ -37,10 +38,15 @@ function detectMemoryKeyword(text: string): boolean {
return MEMORY_KEYWORD_PATTERN.test(textWithoutCode);
}

function detectRecallKeyword(text: string): boolean {
const textWithoutCode = removeCodeBlocks(text);
return RECALL_KEYWORD_PATTERN.test(textWithoutCode);
}

export const SupermemoryPlugin: Plugin = async (ctx: PluginInput) => {
const { directory } = ctx;
const tags = getTags(directory);
const injectedSessions = new Set<string>();
const sessionMessageCount = new Map<string, number>();
log("Plugin init", { directory, tags, configured: isConfigured() });

if (!isConfigured()) {
Expand Down Expand Up @@ -171,10 +177,20 @@ export const SupermemoryPlugin: Plugin = async (ctx: PluginInput) => {
output.parts.push(nudgePart);
}

const isFirstMessage = !injectedSessions.has(input.sessionID);
// Determine whether to inject context
const count = (sessionMessageCount.get(input.sessionID) || 0) + 1;
sessionMessageCount.set(input.sessionID, count);

const isFirstMessage = count === 1;
const recallTriggered = detectRecallKeyword(userMessage);
const periodicReinject = CONFIG.reinjectEveryN > 0 && count % CONFIG.reinjectEveryN === 0;
const shouldInjectContext = isFirstMessage || recallTriggered || periodicReinject;

if (isFirstMessage) {
injectedSessions.add(input.sessionID);
if (shouldInjectContext) {
log("chat.message: injecting context", {
reason: isFirstMessage ? "first-message" : recallTriggered ? "recall-keyword" : "periodic",
messageCount: count,
});

const [profileResult, userMemoriesResult, projectMemoriesListResult] = await Promise.all([
supermemoryClient.getProfile(tags.user, userMessage),
Expand Down Expand Up @@ -220,6 +236,7 @@ export const SupermemoryPlugin: Plugin = async (ctx: PluginInput) => {
log("chat.message: context injected", {
duration,
contextLength: memoryContext.length,
reason: isFirstMessage ? "first-message" : recallTriggered ? "recall-keyword" : "periodic",
});
}
}
Expand Down Expand Up @@ -540,6 +557,7 @@ export const SupermemoryPlugin: Plugin = async (ctx: PluginInput) => {
const sessionInfo = props?.info as { id?: string } | undefined;
if (sessionInfo?.id) {
await saveSessionSummary(sessionInfo.id);
sessionMessageCount.delete(sessionInfo.id);
}
}

Expand Down