feat: add sleepMemory implementation

This commit is contained in:
2026-06-25 22:32:48 +09:00
parent 8d0cca5ae8
commit cf584bd00b
4 changed files with 165 additions and 0 deletions

View File

@@ -1072,3 +1072,98 @@ describe("Brain.sendMessage — tool-calling flow", () => {
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();
});
});

View File

@@ -135,6 +135,49 @@ export class Brain {
return await runCreateMonthlyScheduleSteps(this, datetime, message, noopRunner);
}
async sleepMemory(
datetime: Date = new Date(),
history: ReadonlyArray<MessageHistoryEntry>,
): Promise<string | null> {
if (history.length === 0) return null;
try {
const dateKey = formatDateKey(datetime);
const instruction = await loadPrompt("MEMOIR");
const historyBlock = translateMessageHistory(
this.brainbase.displayName,
history,
);
const promptMessage = [
`Date: ${dateKey}`,
`Personality: ${this.brainbase.baseSystemPrompt}`,
`Conversation log:`,
historyBlock,
].join("\n\n");
const memoir = await llm.call<string>(llm.models.identity, {
instruction,
message: promptMessage,
});
await this.add({
customId: `daily-journal:${dateKey}`,
content: memoir,
metadata: {
kind: "daily-journal",
source: "sleepMemory",
date: dateKey,
},
});
return memoir;
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
logger.error(`sleepMemory failed: ${reason}`);
return null;
}
}
async getTodayScheduledAvailability(
datetime: Date,
): Promise<AvailabilityWindows | null> {