From 49f75afcf4e96726151bfc8976471eca1012d9fd Mon Sep 17 00:00:00 2001 From: Shinwoo PARK Date: Thu, 14 May 2026 22:36:04 +0900 Subject: [PATCH] feat: expose space fact listing --- src/memory.ts | 15 +++++++++++ src/types.ts | 1 + tests/memory.test.ts | 60 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 tests/memory.test.ts diff --git a/src/memory.ts b/src/memory.ts index c5d634b..458ebab 100644 --- a/src/memory.ts +++ b/src/memory.ts @@ -47,6 +47,10 @@ export class InMemoryMemoryStore implements BoxBrainMemoryStore { return stored; } + async listFacts(spaceId: string): Promise { + return [...(this.facts.get(spaceId) ?? [])].sort((a, b) => a.createdAt.localeCompare(b.createdAt)); + } + async findFacts(spaceId: string, topics: string[]): Promise { const facts = this.facts.get(spaceId) ?? []; if (topics.length === 0) return [...facts]; @@ -120,6 +124,17 @@ export class IdentityDbMemoryStore implements BoxBrainMemoryStore { return this.fromIdentityFact(stored); } + async listFacts(spaceId: string): Promise { + const topics = await this.options.db.listTopics({ spaceName: spaceId, includeFacts: true }); + const collected = new Map(); + for (const topic of topics) { + for (const fact of topic.facts) { + collected.set(fact.id, this.fromIdentityFact(fact)); + } + } + return [...collected.values()].sort((a, b) => a.createdAt.localeCompare(b.createdAt)); + } + async findFacts(spaceId: string, topics: string[]): Promise { const uniqueTopics = normalizeTopics(topics); const collected = new Map(); diff --git a/src/types.ts b/src/types.ts index 1e088c5..97058ab 100644 --- a/src/types.ts +++ b/src/types.ts @@ -167,6 +167,7 @@ export interface BoxBrainMemoryStore { createSpace(input: { displayName: string; seedMessage: string; now: string }): Promise; getSpace(spaceId: string): Promise; addFact(spaceId: string, fact: FactDraft): Promise; + listFacts(spaceId: string): Promise; findFacts(spaceId: string, topics: string[]): Promise; saveScheduleEntries(spaceId: string, entries: ScheduleEntry[]): Promise; listScheduleEntries(spaceId: string, fromInclusive: string, toExclusive: string): Promise; diff --git a/tests/memory.test.ts b/tests/memory.test.ts new file mode 100644 index 0000000..562ef01 --- /dev/null +++ b/tests/memory.test.ts @@ -0,0 +1,60 @@ +import { mkdtemp, rm } from 'node:fs/promises'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; +import { describe, expect, it } from 'vitest'; +import { createSqliteIdentityMemoryStore, InMemoryMemoryStore } from '../src'; + +async function seedFacts(memory: InMemoryMemoryStore, spaceId: string) { + await memory.addFact(spaceId, { + statement: 'Mina likes quiet cafes.', + topics: ['persona', 'Mina'], + source: 'test', + }); + await memory.addFact(spaceId, { + statement: 'The user prefers short replies.', + topics: ['user'], + source: 'test', + }); +} + +describe('BoxBrain memory stores', () => { + it('lists every fact in an in-memory persona space without requiring topic guesses', async () => { + const memory = new InMemoryMemoryStore(); + const space = await memory.createSpace({ displayName: 'Mina', seedMessage: 'seed', now: '2026-05-14T00:00:00.000Z' }); + await seedFacts(memory, space.id); + + const facts = await memory.listFacts(space.id); + + expect(facts.map((fact) => fact.statement)).toEqual([ + 'Mina likes quiet cafes.', + 'The user prefers short replies.', + ]); + }); + + it('lists every fact in a SQLite IdentityDB-backed persona space', async () => { + const dir = await mkdtemp(join(tmpdir(), 'boxbrain-memory-')); + try { + const memory = await createSqliteIdentityMemoryStore(join(dir, 'boxbrain.sqlite')); + const space = await memory.createSpace({ displayName: 'Mina', seedMessage: 'seed', now: '2026-05-14T00:00:00.000Z' }); + await memory.addFact(space.id, { + statement: 'Mina likes quiet cafes.', + topics: ['persona', 'Mina'], + source: 'test', + }); + await memory.addFact(space.id, { + statement: 'The user prefers short replies.', + topics: ['user'], + source: 'test', + }); + + const facts = await memory.listFacts(space.id); + + expect(facts.map((fact) => fact.statement)).toEqual([ + 'Mina likes quiet cafes.', + 'The user prefers short replies.', + ]); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); +});