|
| 1 | +import { loadMemories, saveMemories, type Memory } from "./store" |
| 2 | + |
| 3 | +const DREAM_DIR = `${process.env.HOME}/.config/opencode/memory` |
| 4 | +const DREAM_FILE = `${DREAM_DIR}/last-dream.json` |
| 5 | +const CONSOLIDATION_INTERVAL_MS = 24 * 60 * 60 * 1000 |
| 6 | + |
| 7 | +const readLastDream = async (): Promise<number> => { |
| 8 | + const file = Bun.file(DREAM_FILE) |
| 9 | + if (!(await file.exists())) return 0 |
| 10 | + const data = await file.json() |
| 11 | + return data.timestamp ?? 0 |
| 12 | +} |
| 13 | + |
| 14 | +const writeLastDream = async (timestamp: number): Promise<void> => { |
| 15 | + await Bun.write(DREAM_FILE, JSON.stringify({ timestamp }, null, 2)) |
| 16 | +} |
| 17 | + |
| 18 | +export const shouldConsolidate = async (): Promise<boolean> => { |
| 19 | + const last = await readLastDream() |
| 20 | + return Date.now() - last >= CONSOLIDATION_INTERVAL_MS |
| 21 | +} |
| 22 | + |
| 23 | +const buildPrompt = (existing: ReadonlyArray<Memory>, summaries: ReadonlyArray<string>): string => { |
| 24 | + const existingText = existing.length === 0 |
| 25 | + ? "(no existing memories)" |
| 26 | + : existing.map((m) => `- [${m.category}] ${m.title}: ${m.content}`).join("\n") |
| 27 | + |
| 28 | + const summariesText = summaries.join("\n---\n") |
| 29 | + |
| 30 | + return `You are a memory consolidation assistant. Extract and maintain important facts from session summaries. |
| 31 | +
|
| 32 | +EXISTING MEMORIES: |
| 33 | +${existingText} |
| 34 | +
|
| 35 | +NEW SESSION SUMMARIES: |
| 36 | +${summariesText} |
| 37 | +
|
| 38 | +INSTRUCTIONS: |
| 39 | +1. Extract new facts, preferences, or project details from the summaries |
| 40 | +2. Merge duplicates - if a fact already exists, update it rather than creating a new one |
| 41 | +3. Remove outdated information that conflicts with newer data |
| 42 | +4. Categorize each memory: user (preferences/habits), feedback (corrections), project (tech details), reference (facts to remember) |
| 43 | +
|
| 44 | +Respond with a JSON array of memory objects: |
| 45 | +[{"category": "user|feedback|project|reference", "title": "short title", "content": "detailed content", "keywords": ["key1", "key2"]}] |
| 46 | +
|
| 47 | +If no new memories should be extracted, respond with: []` |
| 48 | +} |
| 49 | + |
| 50 | +export const consolidate = async (sessionSummaries: ReadonlyArray<string>): Promise<void> => { |
| 51 | + if (sessionSummaries.length === 0) return |
| 52 | + |
| 53 | + const existing = await loadMemories() |
| 54 | + const _prompt = buildPrompt(existing, sessionSummaries) |
| 55 | + |
| 56 | + try { |
| 57 | + const response = await Bun.stdin.text() // placeholder - in real usage, call LLM |
| 58 | + const parsed = JSON.parse(response) as ReadonlyArray<{ |
| 59 | + category: Memory["category"] |
| 60 | + title: string |
| 61 | + content: string |
| 62 | + keywords: ReadonlyArray<string> |
| 63 | + }> |
| 64 | + |
| 65 | + const now = new Date().toISOString() |
| 66 | + const newMemories: ReadonlyArray<Memory> = parsed.map((item) => ({ |
| 67 | + id: crypto.randomUUID(), |
| 68 | + category: item.category, |
| 69 | + title: item.title, |
| 70 | + content: item.content, |
| 71 | + keywords: item.keywords, |
| 72 | + created_at: now, |
| 73 | + updated_at: now, |
| 74 | + })) |
| 75 | + |
| 76 | + const merged = [...existing, ...newMemories].slice(-200) |
| 77 | + await saveMemories(merged) |
| 78 | + await writeLastDream(Date.now()) |
| 79 | + } catch { |
| 80 | + // Silently fail - don't crash on consolidation errors |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +export const Dream = { |
| 85 | + shouldConsolidate, |
| 86 | + consolidate, |
| 87 | +} |
0 commit comments