This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

niri / src / memory-search.test.ts
3.8 kB 100 lines
1import test from "node:test" 2import assert from "node:assert/strict" 3import { spawn } from "node:child_process" 4import fs from "node:fs/promises" 5import os from "node:os" 6import path from "node:path" 7import { pathToFileURL } from "node:url" 8 9test("searchMemory includes semantic-only vector candidates and recall accepts them", async (t) => { 10 const home = await fs.mkdtemp(path.join(os.tmpdir(), "niri-memory-search-")) 11 t.after(async () => { 12 await fs.rm(home, { recursive: true, force: true }) 13 }) 14 15 const moduleUrl = (filePath: string) => pathToFileURL(path.join(process.cwd(), filePath)).href 16 const script = ` 17 import assert from "node:assert/strict" 18 import path from "node:path" 19 20 const [{ initDb, getDb, isVecAvailable, MEMORY_EMBEDDING_DIMENSIONS }, { vectorParam }, search] = 21 await Promise.all([ 22 import(${JSON.stringify(moduleUrl("src/db.ts"))}), 23 import(${JSON.stringify(moduleUrl("src/memory/sync.ts"))}), 24 import(${JSON.stringify(moduleUrl("src/memory/search.ts"))}), 25 ]) 26 27 initDb() 28 if (!isVecAvailable()) process.exit(0) 29 30 const db = getDb() 31 const docPath = path.join(process.env.NIRI_HOME, "memories", "journal", "2026-06-06.md") 32 const documentId = Number( 33 db 34 .prepare(\` 35 insert into memory_documents (path, kind, title, mtime_ms, content_hash, updated_at) 36 values (?, 'journal', 'capsule notes', 1, 'hash', datetime('now')) 37 \`) 38 .run(docPath).lastInsertRowid, 39 ) 40 const chunkId = Number( 41 (() => { 42 const longChunkText = 43 "the bauble protocol keeps archive capsules near the console. " + 44 "full chunk content should survive agent recall. ".repeat(12) + 45 "semantic-tail-marker" 46 return db 47 .prepare(\` 48 insert into memory_chunks (document_id, chunk_index, title, heading_path, chunk_text, tags) 49 values (?, 0, 'bauble protocol', null, ?, 'capsule') 50 \`) 51 .run(documentId, longChunkText).lastInsertRowid 52 })(), 53 ) 54 55 const queryVector = Array.from({ length: MEMORY_EMBEDDING_DIMENSIONS }, (_, index) => (index === 0 ? 1 : 0)) 56 db 57 .prepare("insert or replace into memory_chunk_vec(rowid, embedding) values (?, ?)") 58 .run(BigInt(chunkId), vectorParam(queryVector)) 59 60 const profile = await search.buildSearchProfile({ 61 sender: null, 62 source: null, 63 body: "vector memory retrieval context", 64 }) 65 const hits = await search.searchMemory(profile, {}, 1, 5, { 66 vector: queryVector, 67 chatterSimilarity: null, 68 recallIntentSimilarity: 1, 69 }) 70 71 assert.equal(hits[0]?.chunkId, chunkId) 72 assert.ok((hits[0]?.semanticSimilarity ?? 0) > 0.99) 73 assert.equal(search.isRelevant(hits, profile), true) 74 75 const result = search.toMemorySearchResult(hits[0]) 76 assert.ok(result.content.endsWith("semantic-tail-marker")) 77 assert.equal(result.preview, result.content) 78 assert.ok(search.buildMemoryRecallMessage(hits).includes("semantic-tail-marker")) 79 ` 80 81 const result = await new Promise<{ code: number | null; stdout: string; stderr: string }>((resolve, reject) => { 82 const child = spawn(process.execPath, ["--import", "tsx", "--input-type=module", "-e", script], { 83 cwd: process.cwd(), 84 env: { ...process.env, NIRI_HOME: home }, 85 stdio: ["ignore", "pipe", "pipe"], 86 }) 87 let stdout = "" 88 let stderr = "" 89 child.stdout.setEncoding("utf-8").on("data", (chunk) => { 90 stdout += chunk 91 }) 92 child.stderr.setEncoding("utf-8").on("data", (chunk) => { 93 stderr += chunk 94 }) 95 child.on("error", reject) 96 child.on("close", (code) => resolve({ code, stdout, stderr })) 97 }) 98 99 assert.equal(result.code, 0, [result.stdout, result.stderr].filter(Boolean).join("\n")) 100})