This repository has no description
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})