import { afterAll, beforeAll, beforeEach, describe, expect, mock, test, } from "bun:test"; import { randomUUID } from "node:crypto"; import type { Space } from "./types"; const llmCalls: Array<{ model: unknown; options: any }> = []; let customMonthlyDays: Array<{ day: number; summary: string }> | null = null; let customDailySlots: Array<{ start: string; end: string; activity: string; notes: string; }> | null = null; let customAvailability: Array<{ start: string; end: string; status: string; }> | null = null; type ToolCallResponse = { id: string; name: string; arguments: string; }; type LLMChatResponse = | { kind: "text"; text: string } | { kind: "tool_calls"; tool_calls: ToolCallResponse[] }; let chatResponses: LLMChatResponse[] = []; function build48Slots(): Array<{ start: string; end: string; activity: string; notes: string; }> { const slots: Array<{ start: string; end: string; activity: string; notes: string; }> = []; for (let i = 0; i < 48; i++) { const startHour = Math.floor(i / 2); const startMin = (i % 2) * 30; const start = `${String(startHour).padStart(2, "0")}:${String(startMin).padStart(2, "0")}`; let end: string; if (i === 47) { end = "24:00"; } else { const endHour = Math.floor((i + 1) / 2); const endMin = ((i + 1) % 2) * 30; end = `${String(endHour).padStart(2, "0")}:${String(endMin).padStart(2, "0")}`; } slots.push({ start, end, activity: `slot-${i}`, notes: "" }); } return slots; } function build30Days(): Array<{ day: number; summary: string }> { return Array.from({ length: 30 }, (_, i) => ({ day: i + 1, summary: `Day ${i + 1} summary`, })); } function buildAvailability(): Array<{ start: string; end: string; status: string; }> { return [ { start: "00:00", end: "07:00", status: "offline" }, { start: "07:00", end: "09:00", status: "online" }, { start: "09:00", end: "17:00", status: "do-not-disturb" }, { start: "17:00", end: "23:30", status: "online" }, { start: "23:30", end: "24:00", status: "offline" }, ]; } const mockCall = mock(async (model: unknown, options: any): Promise => { llmCalls.push({ model, options }); if (options.jsonSchemaName === "daily-schedule") { return { items: customDailySlots ?? build48Slots() } as unknown as T; } if (options.jsonSchemaName === "monthly-schedule") { if (customMonthlyDays) { return { items: customMonthlyDays } as unknown as T; } const match = options.message.match(/\((\d+) days\)/); const days = match ? parseInt(match[1]!, 10) : 30; return { items: Array.from({ length: days }, (_, i) => ({ day: i + 1, summary: `Day ${i + 1} summary`, })), } as unknown as T; } if (options.jsonSchemaName === "availability") { return { items: customAvailability ?? buildAvailability() } as unknown as T; } if (Array.isArray(options.tools)) { const next = chatResponses.shift(); if (!next) { throw new Error("mockCall: no chatResponses queued for tool-using call"); } if (next.kind === "text") { return { finish_reason: "stop", index: 0, message: { role: "assistant", content: next.text }, } as unknown as T; } return { finish_reason: "tool_calls", index: 0, message: { role: "assistant", content: null, toolCalls: next.tool_calls.map((tc) => ({ id: tc.id, type: "function", function: { name: tc.name, arguments: tc.arguments }, })), }, } as unknown as T; } if (typeof options.message === "string" || options.message === undefined) { return "test-description" as unknown as T; } throw new Error(`unexpected jsonSchemaName: ${options.jsonSchemaName}`); }); mock.module("@/openrouter", () => ({ llm: { models: { conversation: "test-conv", identity: "test-id" }, call: mockCall, chatWithTools: mockCall, }, })); mock.module("@/config", () => ({ config: { openrouterApiKey: "test-key", supermemoryApiKey: "test-supermemory-key", braindbPath: "/tmp/brainbox-test-braindb.json", }, })); interface StoredDoc { id: string; customId: string | null; containerTag: string; content: string; summary: string | null; metadata: Record | null; } class MockSupermemory { docs = new Map(); private nextId = 0; documentsAddCalls = 0; constructor(_options: { apiKey: string }) {} documents = { add: async (params: { content: string; containerTag: string; customId?: string; metadata?: Record; }) => { this.documentsAddCalls += 1; const id = `mock-${++this.nextId}`; const stored: StoredDoc = { id, customId: params.customId ?? null, containerTag: params.containerTag, content: params.content, summary: null, metadata: params.metadata ?? null, }; this.docs.set(id, stored); return { id, status: "done" }; }, list: async (params: { containerTags?: Array; limit?: number; }) => { const tags = params.containerTags ?? []; const limit = params.limit ?? 200; const all = Array.from(this.docs.values()).filter((d) => tags.length === 0 ? true : tags.includes(d.containerTag), ); const memories = all.slice(0, limit).map((d) => ({ id: d.id, customId: d.customId, containerTag: d.containerTag, summary: d.summary, metadata: d.metadata as | string | number | boolean | Record | Array | null, content: d.content, createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z", status: "done" as const, type: "text" as const, connectionId: null, filepath: null, title: null, })); return { memories, pagination: { currentPage: 1, totalItems: memories.length, totalPages: 1, limit, }, }; }, get: async (id: string) => { const d = this.docs.get(id); if (!d) { throw new Error(`MockSupermemory.documents.get: no such id ${id}`); } return { id: d.id, customId: d.customId, containerTag: d.containerTag, content: d.content, summary: d.summary, metadata: d.metadata as | string | number | boolean | Record | Array | null, createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z", status: "done" as const, type: "text" as const, connectionId: null, filepath: null, title: null, source: null, ogImage: null, raw: null, spatialPoint: null, taskType: "memory" as const, url: null, }; }, }; search = { execute: async (params: { q: string; containerTag?: string; limit?: number; onlyMatchingChunks?: boolean; }) => { const q = params.q.toLowerCase(); const limit = params.limit ?? 5; const hits = Array.from(this.docs.values()) .filter( (d) => (params.containerTag ? d.containerTag === params.containerTag : true) && d.content.toLowerCase().includes(q), ) .slice(0, limit) .map((d, i) => ({ chunks: [ { content: d.content, isRelevant: true, score: 1 - i * 0.1, }, ], summary: d.summary, score: 1 - i * 0.1, documentId: d.id, metadata: (d.metadata as Record) ?? null, createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z", title: d.customId, type: "text" as const, })); return { results: hits, total: hits.length, timing: 0, }; }, }; findByCustomId(customId: string): StoredDoc | undefined { for (const d of this.docs.values()) { if (d.customId === customId) return d; } return undefined; } reset(): void { this.docs.clear(); this.nextId = 0; this.documentsAddCalls = 0; } } /** * Replace the real `supermemory` SDK with our in-memory mock. The * static factories `Brain.create` / `Brain.createDebug` / `Brain.load` * all do `new Supermemory({ apiKey })` internally; this mock is what * they pick up. */ mock.module("supermemory", () => ({ default: MockSupermemory, })); const { Brain } = await import("./index"); const { brainManager } = await import("./manager"); const { formatDateKey, nextMonth } = await import("./schedule"); type BrainItem = import("./manager").BrainItem; beforeAll(async () => { try { await brainManager.deleteBrain("smoke-test-id"); } catch {} }); afterAll(async () => {}); async function makeBrain(): Promise> { const db = new MockSupermemory({ apiKey: "test-supermemory-key" }); const spaceName = `test-space-${randomUUID()}`; const space: Space = { name: spaceName, description: "Test Brain space" }; const brainbase: BrainItem = { brainId: randomUUID(), spaceName, displayName: "Test Brain", baseSystemPrompt: "Test personality: night owl, introverted, studies at midnight.", }; return new Brain(db as never, space, brainbase, false); } beforeEach(() => { llmCalls.length = 0; customMonthlyDays = null; customDailySlots = null; customAvailability = null; chatResponses = []; }); describe("Brain.createDailySchedule", () => { test("S1: returns 48 slots in 30-min intervals and persists a document", async () => { const brain = await makeBrain(); const db = brain.db as unknown as MockSupermemory; const today = new Date(2026, 5, 5); const expectedTomorrow = (await import("./schedule")).nextDay(today); const expectedKey = formatDateKey(expectedTomorrow); const result = await brain.createDailySchedule(today, "focus on writing"); expect(result).not.toBeNull(); expect(result!.items).toHaveLength(48); expect(result!.items[0]).toEqual({ start: "00:00", end: "00:30", activity: "slot-0", notes: "", }); expect(result!.items[47]).toEqual({ start: "23:30", end: "24:00", activity: "slot-47", notes: "", }); const llmCall = llmCalls.find( (c) => c.options.jsonSchemaName === "daily-schedule", ); expect(llmCall).toBeDefined(); expect(llmCall!.options.message).toContain(expectedKey); expect(llmCall!.options.message).toContain("focus on writing"); expect(llmCall!.options.message).toContain("Test personality"); const stored = db.findByCustomId(`daily-schedule:${expectedKey}`); expect(stored).toBeDefined(); expect(stored!.containerTag).toBe(brain.space.name); expect(JSON.parse(stored!.content).items).toHaveLength(48); }); test("S4: month wrap (June 30 -> July 1)", async () => { const brain = await makeBrain(); const db = brain.db as unknown as MockSupermemory; const today = new Date(2026, 5, 30); const expectedKey = formatDateKey(new Date(2026, 6, 1)); await brain.createDailySchedule(today, ""); const stored = db.findByCustomId(`daily-schedule:${expectedKey}`); expect(stored).toBeDefined(); }); test("S4b: year wrap (December 31 -> January 1 next year)", async () => { const brain = await makeBrain(); const db = brain.db as unknown as MockSupermemory; const today = new Date(2026, 11, 31); const expectedKey = "2027-01-01"; await brain.createDailySchedule(today, ""); const stored = db.findByCustomId(`daily-schedule:${expectedKey}`); expect(stored).toBeDefined(); }); test("S6: consumes monthly summary for the target day when present", async () => { const brain = await makeBrain(); const db = brain.db as unknown as MockSupermemory; customMonthlyDays = Array.from({ length: 30 }, (_, i) => ({ day: i + 1, summary: i + 1 === 10 ? "UNIQUE_SUMMARY_FOR_DAY_10" : `Day ${i + 1} summary`, })); const todayForMonthly = new Date(2026, 4, 15); await brain.createMonthlySchedule(todayForMonthly, ""); const monthlyStored = db.findByCustomId("monthly-schedule:2026-06"); expect(monthlyStored).toBeDefined(); llmCalls.length = 0; customDailySlots = build48Slots(); const todayForDaily = new Date(2026, 5, 9); await brain.createDailySchedule(todayForDaily, ""); const dailyLlmCall = llmCalls.find( (c) => c.options.jsonSchemaName === "daily-schedule", ); expect(dailyLlmCall).toBeDefined(); expect(dailyLlmCall!.options.message).toContain( "UNIQUE_SUMMARY_FOR_DAY_10", ); }); test("S9: injects 2-days-ago schedule as recent context when one exists", async () => { const brain = await makeBrain(); const db = brain.db as unknown as MockSupermemory; const twoDaysAgoTarget = new Date(2026, 5, 7); const twoDaysAgoTomorrow = (await import("./schedule")).nextDay( twoDaysAgoTarget, ); const twoDaysAgoKey = formatDateKey(twoDaysAgoTomorrow); await brain.add({ customId: `daily-schedule:${twoDaysAgoKey}`, content: JSON.stringify({ items: Array.from({ length: 48 }, (_, i) => ({ start: `${String(Math.floor(i / 2)).padStart(2, "0")}:${String((i % 2) * 30).padStart(2, "0")}`, end: `${String(Math.floor((i + 1) / 2)).padStart(2, "0")}:${String(((i + 1) % 2) * 30).padStart(2, "0")}`, activity: `prior-day-activity-${i}`, notes: "", })), }), metadata: { kind: "schedule", source: "createDailySchedule", date: twoDaysAgoKey }, }); llmCalls.length = 0; const today = new Date(2026, 5, 9); await brain.createDailySchedule(today, ""); const dailyLlmCall = llmCalls.find( (c) => c.options.jsonSchemaName === "daily-schedule", ); expect(dailyLlmCall).toBeDefined(); expect(dailyLlmCall!.options.message).toContain( `Recent schedule (${twoDaysAgoKey}, 2 days ago):`, ); expect(dailyLlmCall!.options.message).toContain("prior-day-activity-0"); }); test("S10: 2-days-ago context says 'no schedule on file' when prior day is missing", async () => { const brain = await makeBrain(); const today = new Date(2026, 5, 9); await brain.createDailySchedule(today, ""); const dailyLlmCall = llmCalls.find( (c) => c.options.jsonSchemaName === "daily-schedule", ); expect(dailyLlmCall).toBeDefined(); expect(dailyLlmCall!.options.message).toContain( "(no schedule on file for 2 days ago)", ); }); }); describe("Brain.createMonthlySchedule", () => { test("S2: returns N day summaries (N = days in next month) and persists a document", async () => { const brain = await makeBrain(); const db = brain.db as unknown as MockSupermemory; const today = new Date(2026, 0, 15); const expected = nextMonth(today); const expectedKey = `${expected.year}-${String(expected.month + 1).padStart(2, "0")}`; const result = await brain.createMonthlySchedule(today, "study for GRE"); expect(result).not.toBeNull(); expect(result!.items).toHaveLength(expected.daysInMonth); expect(result!.items[0]!.day).toBe(1); expect(result!.items[result!.items.length - 1]!.day).toBe( expected.daysInMonth, ); const llmCall = llmCalls.find( (c) => c.options.jsonSchemaName === "monthly-schedule", ); expect(llmCall).toBeDefined(); expect(llmCall!.options.message).toContain("study for GRE"); expect(llmCall!.options.message).toContain("Test personality"); const stored = db.findByCustomId(`monthly-schedule:${expectedKey}`); expect(stored).toBeDefined(); expect(JSON.parse(stored!.content).items).toHaveLength( expected.daysInMonth, ); }); test("S5: year wrap (December 15 -> January next year)", async () => { const brain = await makeBrain(); const db = brain.db as unknown as MockSupermemory; const today = new Date(2026, 11, 15); const expectedKey = "2027-01"; const result = await brain.createMonthlySchedule(today, ""); expect(result).not.toBeNull(); expect(result!.items).toHaveLength(31); const stored = db.findByCustomId(`monthly-schedule:${expectedKey}`); expect(stored).toBeDefined(); }); }); describe("Brain.createDailySchedule early return", () => { test("returns existing schedule without calling LLM when daily-schedule for tomorrow already exists", async () => { const brain = await makeBrain(); const today = new Date(2026, 5, 9); const tomorrow = (await import("./schedule")).nextDay(today); const tomorrowKey = formatDateKey(tomorrow); const preseeded = { items: [ { start: "06:00", end: "07:00", activity: "preserved-morning", notes: "n/a", }, { start: "22:00", end: "23:00", activity: "preserved-evening", notes: "n/a", }, ], }; await brain.add({ customId: `daily-schedule:${tomorrowKey}`, content: JSON.stringify(preseeded), metadata: { kind: "schedule", source: "test-seed", date: tomorrowKey }, }); llmCalls.length = 0; const result = await brain.createDailySchedule(today, "ignored user message"); expect(result).toEqual(preseeded); const dailyLlmCall = llmCalls.find( (c) => c.options.jsonSchemaName === "daily-schedule", ); expect(dailyLlmCall).toBeUndefined(); }); test("falls through to generation when stored content is malformed", async () => { const brain = await makeBrain(); const db = brain.db as unknown as MockSupermemory; const today = new Date(2026, 5, 9); const tomorrow = (await import("./schedule")).nextDay(today); const tomorrowKey = formatDateKey(tomorrow); await brain.add({ customId: `daily-schedule:${tomorrowKey}`, content: "{not valid json", metadata: { kind: "schedule", source: "test-seed", date: tomorrowKey }, }); const result = await brain.createDailySchedule(today, ""); expect(result).not.toBeNull(); expect(result!.items).toHaveLength(48); const dailyLlmCall = llmCalls.find( (c) => c.options.jsonSchemaName === "daily-schedule", ); expect(dailyLlmCall).toBeDefined(); expect(db.findByCustomId(`daily-schedule:${tomorrowKey}`)).toBeDefined(); }); }); describe("Brain.createMonthlySchedule early return", () => { test("returns existing schedule without calling LLM when monthly-schedule for next month already exists", async () => { const brain = await makeBrain(); const today = new Date(2026, 0, 15); const next = nextMonth(today); const monthKey = `${next.year}-${String(next.month + 1).padStart(2, "0")}`; const preseeded = { items: [ { day: 1, summary: "preserved-day-1" }, { day: 2, summary: "preserved-day-2" }, { day: 3, summary: "preserved-day-3" }, ], }; await brain.add({ customId: `monthly-schedule:${monthKey}`, content: JSON.stringify(preseeded), metadata: { kind: "schedule", source: "test-seed", month: monthKey }, }); llmCalls.length = 0; const result = await brain.createMonthlySchedule(today, "ignored user message"); expect(result).toEqual(preseeded); const monthlyLlmCall = llmCalls.find( (c) => c.options.jsonSchemaName === "monthly-schedule", ); expect(monthlyLlmCall).toBeUndefined(); }); test("falls through to generation when stored content is malformed", async () => { const brain = await makeBrain(); const today = new Date(2026, 0, 15); const next = nextMonth(today); const monthKey = `${next.year}-${String(next.month + 1).padStart(2, "0")}`; await brain.add({ customId: `monthly-schedule:${monthKey}`, content: "{not valid json", metadata: { kind: "schedule", source: "test-seed", month: monthKey }, }); const result = await brain.createMonthlySchedule(today, ""); expect(result).not.toBeNull(); const monthlyLlmCall = llmCalls.find( (c) => c.options.jsonSchemaName === "monthly-schedule", ); expect(monthlyLlmCall).toBeDefined(); }); }); describe("Brain.getTodayScheduledAvailability", () => { test("S3: returns availability windows when today's daily schedule exists", async () => { const brain = await makeBrain(); const today = new Date(2026, 5, 10); const todayKey = formatDateKey(today); await brain.add({ customId: `daily-schedule:${todayKey}`, content: JSON.stringify({ items: build48Slots() }), metadata: { kind: "schedule", source: "test", date: todayKey, }, }); const result = await brain.getTodayScheduledAvailability(today); expect(result).not.toBeNull(); expect(result!.items.length).toBeGreaterThan(0); for (const w of result!.items) { expect(["online", "do-not-disturb", "offline"]).toContain(w.status); expect(w.start).toMatch(/^([01][0-9]|2[0-3]):[0-5][0-9]$/); expect(w.end).toMatch(/^([01][0-9]|2[0-3]):[0-5][0-9]$|^24:00$/); } const availabilityCall = llmCalls.find( (c) => c.options.jsonSchemaName === "availability", ); expect(availabilityCall).toBeDefined(); }); test("returns null when no daily schedule exists for today", async () => { const brain = await makeBrain(); const today = new Date(2026, 5, 10); const result = await brain.getTodayScheduledAvailability(today); expect(result).toBeNull(); expect(llmCalls).toHaveLength(0); }); }); describe("Brain.invalidateScheduledAvailability", () => { test("S7a: today's cached availability is preserved after invalidateScheduledAvailability()", async () => { const brain = await makeBrain(); const today = new Date(2026, 5, 10); const todayKey = formatDateKey(today); await brain.add({ customId: `daily-schedule:${todayKey}`, content: JSON.stringify({ items: build48Slots() }), metadata: { kind: "schedule", source: "test", date: todayKey, }, }); const r1 = await brain.getTodayScheduledAvailability(today); expect(r1).not.toBeNull(); const callCountAfterFirst = llmCalls.length; expect(callCountAfterFirst).toBe(1); const r2 = await brain.getTodayScheduledAvailability(today); expect(r2).not.toBeNull(); expect(llmCalls.length).toBe(callCountAfterFirst); brain.invalidateScheduledAvailability(today); const r3 = await brain.getTodayScheduledAvailability(today); expect(r3).not.toBeNull(); expect(llmCalls.length).toBe(callCountAfterFirst); }); test("S7b: previous days' cached availability is removed, today/future preserved", async () => { const brain = await makeBrain(); const today = new Date(2026, 5, 10); const todayKey = formatDateKey(today); const yesterday = new Date(2026, 5, 9); const yesterdayKey = formatDateKey(yesterday); const tomorrow = new Date(2026, 5, 11); const tomorrowKey = formatDateKey(tomorrow); const slots = { items: build48Slots() }; for (const key of [yesterdayKey, todayKey, tomorrowKey]) { await brain.add({ customId: `daily-schedule:${key}`, content: JSON.stringify(slots), metadata: { kind: "schedule", source: "test", date: key }, }); } await brain.getTodayScheduledAvailability(yesterday); await brain.getTodayScheduledAvailability(today); await brain.getTodayScheduledAvailability(tomorrow); const beforeInvalidate = llmCalls.length; await brain.getTodayScheduledAvailability(today); await brain.getTodayScheduledAvailability(tomorrow); expect(llmCalls.length).toBe(beforeInvalidate); brain.invalidateScheduledAvailability(today); const afterToday = llmCalls.length; await brain.getTodayScheduledAvailability(today); expect(llmCalls.length).toBe(afterToday); await brain.getTodayScheduledAvailability(tomorrow); expect(llmCalls.length).toBe(afterToday); await brain.getTodayScheduledAvailability(yesterday); expect(llmCalls.length).toBe(afterToday + 1); }); }); describe("S8: regression on existing methods", () => { test("Brain.create and Brain.load are still defined as static methods", () => { expect(typeof Brain.create).toBe("function"); expect(typeof Brain.load).toBe("function"); }); }); describe("Brain.createDebug", () => { test("D1: returns a Brain with debug=true and the supplied personality under the brain:debug namespace", async () => { const brain = await Brain.createDebug({ personality: "test-personality-Q" }); expect(brain).toBeInstanceOf(Brain); expect(brain.debug).toBe(true); expect(brain.brainbase.baseSystemPrompt).toBe("test-personality-Q"); expect(brain.brainbase.displayName).toBe("Debug Brain"); expect(brain.space.name).toBe("brain:debug"); }); test("D2: createDailySchedule on a debug brain returns a schedule and persists to brain:debug", async () => { const brain = await Brain.createDebug({ personality: "p" }); const db = brain.db as unknown as MockSupermemory; const today = new Date(2026, 5, 5); const tomorrow = new Date(2026, 5, 6); const tomorrowKey = formatDateKey(tomorrow); const schedule = await brain.createDailySchedule(today, "msg"); expect(schedule).not.toBeNull(); expect(schedule!.items).toHaveLength(48); const stored = db.findByCustomId(`daily-schedule:${tomorrowKey}`); expect(stored).toBeDefined(); expect(stored!.containerTag).toBe("brain:debug"); }); test("D3: createMonthlySchedule on a debug brain returns a schedule and persists to brain:debug", async () => { const brain = await Brain.createDebug({ personality: "p" }); const db = brain.db as unknown as MockSupermemory; const today = new Date(2026, 0, 15); const expected = nextMonth(today); const monthKey = `${expected.year}-${String(expected.month + 1).padStart(2, "0")}`; const schedule = await brain.createMonthlySchedule(today, "msg"); expect(schedule).not.toBeNull(); expect(schedule!.items).toHaveLength(expected.daysInMonth); const stored = db.findByCustomId(`monthly-schedule:${monthKey}`); expect(stored).toBeDefined(); expect(stored!.containerTag).toBe("brain:debug"); }); }); describe("Brain.sendMessage — translateMessageHistory helper", () => { test("SM1: translateMessageHistory produces the documented format with persona label and timestamps", async () => { const { translateMessageHistory } = await import("./messageHistory"); const t1 = new Date(2026, 5, 10, 9, 30, 0); const t2 = new Date(2026, 5, 10, 9, 31, 0); const t3 = new Date(2026, 5, 10, 9, 32, 0); const out = translateMessageHistory("Mika", [ { sender: "persona", time: t1, content: "다음에 보자" }, { sender: "user", time: t2, content: "그래" }, { sender: "user", time: t3, content: "지금 뭐해?" }, ]); const lines = out.split("\n"); expect(lines).toHaveLength(3); expect(lines[0]!.startsWith("Mika@")).toBe(true); expect(lines[0]!.endsWith(": 다음에 보자")).toBe(true); expect(lines[1]!.startsWith("사용자@")).toBe(true); expect(lines[1]!.endsWith(": 그래")).toBe(true); expect(lines[2]!.startsWith("사용자@")).toBe(true); expect(lines[2]!.endsWith(": 지금 뭐해?")).toBe(true); }); test("SM2: translateMessageHistory returns empty string for empty history", async () => { const { translateMessageHistory } = await import("./messageHistory"); expect(translateMessageHistory("Mika", [])).toBe(""); }); }); describe("Brain.sendMessage — tool-calling flow", () => { test("SM3: sendMessage returns the LLM's final text when no tools are called", async () => { const brain = await makeBrain(); chatResponses = [ { kind: "tool_calls", tool_calls: [ { id: "call_r1", name: "addReplyMessage", arguments: JSON.stringify({ content: "안녕!" }), }, ], }, { kind: "text", text: "(end)" }, ]; const out = await brain.sendMessage( [{ sender: "user", time: new Date(2026, 5, 10, 9, 0, 0), content: "안녕" }], [], ); expect(out).toEqual(["안녕!"]); const toolsCall = llmCalls.find( (c) => Array.isArray(c.options.tools) && c.options.tools.length > 0, ); expect(toolsCall).toBeDefined(); const toolNames = ( toolsCall!.options.tools as Array<{ function: { name: string }; }> ).map((t) => t.function.name); expect(toolNames).toContain("addReplyMessage"); expect(toolNames).toContain("searchMemory"); }); test("SM4: sendMessage accumulates addReplyMessage tool calls and returns them in order", async () => { const brain = await makeBrain(); chatResponses = [ { kind: "tool_calls", tool_calls: [ { id: "call_1", name: "addReplyMessage", arguments: JSON.stringify({ content: "어." }), }, ], }, { kind: "tool_calls", tool_calls: [ { id: "call_2", name: "addReplyMessage", arguments: JSON.stringify({ content: "왜불러" }), }, ], }, { kind: "text", text: "(end)" }, ]; const out = await brain.sendMessage( [{ sender: "user", time: new Date(2026, 5, 10, 9, 0, 0), content: "야" }], [], ); expect(out).toEqual(["어.", "왜불러"]); }); test("SM5: sendMessage feeds searchMemory tool result back to the LLM", async () => { const brain = await makeBrain(); await brain.add({ customId: "fact-coffee", content: "사용자는 커피를 좋아한다", metadata: { kind: "fact", source: "test" }, }); chatResponses = [ { kind: "tool_calls", tool_calls: [ { id: "call_s", name: "searchMemory", arguments: JSON.stringify({ query: "커피" }), }, ], }, { kind: "tool_calls", tool_calls: [ { id: "call_r", name: "addReplyMessage", arguments: JSON.stringify({ content: "커피 좋아하잖아" }), }, ], }, { kind: "text", text: "(end)" }, ]; const out = await brain.sendMessage( [{ sender: "user", time: new Date(2026, 5, 10, 9, 0, 0), content: "뭐 좋아하는지 알아?" }], [], ); expect(out).toEqual(["커피 좋아하잖아"]); const toolsCalls = llmCalls.filter( (c) => Array.isArray(c.options.tools) && c.options.tools.length > 0, ); expect(toolsCalls.length).toBeGreaterThanOrEqual(2); const messages = toolsCalls[1]!.options.messages as Array<{ role: string; content?: string; toolCallId?: string; }>; const toolMsg = messages.find( (m) => m.role === "tool" && m.toolCallId === "call_s", ); expect(toolMsg).toBeDefined(); expect(toolMsg!.content).toContain("커피"); }); test("SM6: sendMessage with empty history still works and includes translated user messages", async () => { const brain = await makeBrain(); chatResponses = [ { kind: "tool_calls", tool_calls: [ { id: "call_r1", name: "addReplyMessage", arguments: JSON.stringify({ content: "first" }), }, ], }, { kind: "text", text: "(end)" }, ]; const out = await brain.sendMessage( [], [ { sender: "user", time: new Date(2026, 5, 10, 9, 0, 0), content: "하이", }, ], ); expect(out).toEqual(["first"]); const toolsCall = llmCalls.find( (c) => Array.isArray(c.options.tools) && c.options.tools.length > 0, ); expect(toolsCall).toBeDefined(); const userMsg = ( toolsCall!.options.messages as Array<{ role: string; content?: string }> ).find((m) => m.role === "user"); expect(userMsg!.content).toContain("사용자@"); expect(userMsg!.content).toContain("하이"); }); test("SM7: createDailySchedule persists a document reachable via brain.get", async () => { const brain = await makeBrain(); const today = new Date(2026, 5, 5); const tomorrow = new Date(2026, 5, 6); const tomorrowKey = formatDateKey(tomorrow); customDailySlots = build48Slots(); await brain.createDailySchedule(today, "msg"); const stored = await brain.get(`daily-schedule:${tomorrowKey}`); expect(stored).not.toBeNull(); expect(stored!.content).toContain("slot-0"); expect(stored!.metadata).toEqual({ kind: "schedule", source: "createDailySchedule", date: tomorrowKey, }); }); test("SM8: sendMessage does not call brain.add (no documents added during chat)", async () => { const brain = await makeBrain(); const db = brain.db as unknown as MockSupermemory; const before = db.documentsAddCalls; chatResponses = [ { kind: "tool_calls", tool_calls: [ { id: "call_r1", name: "addReplyMessage", arguments: JSON.stringify({ content: "ok" }), }, ], }, { kind: "text", text: "(end)" }, ]; await brain.sendMessage( [{ sender: "user", time: new Date(2026, 5, 10, 9, 0, 0), content: "hi" }], [], ); expect(db.documentsAddCalls - before).toBe(0); }); test("SM9: out-of-band add() facts are queryable via brain.search without backfill", async () => { const brain = await makeBrain(); await brain.add({ customId: "fact-pizza", content: "사용자는 피자를 좋아한다", metadata: { kind: "fact", source: "test" }, }); const hits = await brain.search("피자", 5); expect(hits.length).toBeGreaterThan(0); expect(hits[0]!.content).toContain("피자"); }); }); describe("Brain.sleepMemory", () => { test("SJM1: persists a daily-journal document and returns the LLM text", async () => { const brain = await makeBrain(); const db = brain.db as unknown as MockSupermemory; const datetime = new Date(2026, 5, 10, 23, 0, 0); const dateKey = formatDateKey(datetime); const history = [ { sender: "user" as const, time: new Date(2026, 5, 10, 9, 0, 0), content: "오늘 뭐 했어?", }, { sender: "persona" as const, time: new Date(2026, 5, 10, 9, 0, 30), content: "그냥 잤어", }, ]; const result = await brain.sleepMemory(datetime, history); expect(result).toBe("test-description"); const memoirCall = llmCalls.find( (c) => c.options.jsonSchemaName === undefined && typeof c.options.message === "string" && c.options.message.includes("Conversation log:"), ); expect(memoirCall).toBeDefined(); expect(memoirCall!.options.message).toContain(`Date: ${dateKey}`); expect(memoirCall!.options.message).toContain("Test personality"); expect(memoirCall!.options.message).toContain("오늘 뭐 했어?"); expect(memoirCall!.options.message).toContain("그냥 잤어"); expect(memoirCall!.options.message).toContain("사용자@"); const stored = db.findByCustomId(`daily-journal:${dateKey}`); expect(stored).toBeDefined(); expect(stored!.containerTag).toBe(brain.space.name); expect(stored!.content).toBe("test-description"); expect(stored!.metadata).toEqual({ kind: "daily-journal", source: "sleepMemory", date: dateKey, }); }); test("SJM2: empty history returns null without calling the LLM", async () => { const brain = await makeBrain(); const datetime = new Date(2026, 5, 10, 23, 0, 0); llmCalls.length = 0; const result = await brain.sleepMemory(datetime, []); expect(result).toBeNull(); expect(llmCalls).toHaveLength(0); }); test("SJM3: stored daily-journal is readable via brain.get with kind=daily-journal", async () => { const brain = await makeBrain(); const datetime = new Date(2026, 5, 10, 23, 0, 0); const dateKey = formatDateKey(datetime); await brain.sleepMemory(datetime, [ { sender: "user" as const, time: new Date(2026, 5, 10, 9, 0, 0), content: "hi", }, ]); const stored = await brain.get(`daily-journal:${dateKey}`); expect(stored).not.toBeNull(); expect(stored!.content).toBe("test-description"); expect(stored!.metadata?.kind).toBe("daily-journal"); }); test("SJM4: datetime defaults to today when undefined is passed", async () => { const brain = await makeBrain(); const db = brain.db as unknown as MockSupermemory; const expectedKey = formatDateKey(new Date()); const result = await brain.sleepMemory(undefined as unknown as Date, [ { sender: "user" as const, time: new Date(), content: "ping", }, ]); expect(result).toBe("test-description"); const stored = db.findByCustomId(`daily-journal:${expectedKey}`); expect(stored).toBeDefined(); }); });