@oh-my-pi/pi-mnemopi 18.0.7 → 18.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/package.json +5 -5
- package/src/core/beam/helpers.ts +9 -3
- package/src/core/beam/recall.ts +18 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.0.9] - 2026-08-28
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed working-memory search returning too few or no results when the most relevant matches had been superseded, ensuring valid older entries are still returned.
|
|
10
|
+
|
|
5
11
|
## [18.0.0] - 2026-08-22
|
|
6
12
|
|
|
7
13
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-mnemopi",
|
|
4
|
-
"version": "18.0.
|
|
4
|
+
"version": "18.0.9",
|
|
5
5
|
"description": "Local SQLite memory engine for Oh My Pi agents",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Stencil Labs, Inc.",
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"fmt": "biome format --write ."
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@oh-my-pi/pi-ai": "18.0.
|
|
43
|
-
"@oh-my-pi/pi-catalog": "18.0.
|
|
44
|
-
"@oh-my-pi/pi-natives": "18.0.
|
|
45
|
-
"@oh-my-pi/pi-utils": "18.0.
|
|
42
|
+
"@oh-my-pi/pi-ai": "18.0.9",
|
|
43
|
+
"@oh-my-pi/pi-catalog": "18.0.9",
|
|
44
|
+
"@oh-my-pi/pi-natives": "18.0.9",
|
|
45
|
+
"@oh-my-pi/pi-utils": "18.0.9"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"fastembed": "2.1.0",
|
package/src/core/beam/helpers.ts
CHANGED
|
@@ -270,7 +270,7 @@ export function cjkLikeSearch(
|
|
|
270
270
|
const conditions = cjkChars.map(() => "content LIKE ? ESCAPE '\\'").join(" OR ");
|
|
271
271
|
try {
|
|
272
272
|
const rows = db
|
|
273
|
-
.query(`SELECT ${idColumn}, content FROM ${table} WHERE ${conditions} LIMIT ?`)
|
|
273
|
+
.query(`SELECT ${idColumn}, content FROM ${table} WHERE superseded_by IS NULL AND (${conditions}) LIMIT ?`)
|
|
274
274
|
.all(...cjkChars.map(ch => `%${ch}%`), k * 5) as Record<string, unknown>[];
|
|
275
275
|
const scored: Array<{ id: string | number; score: number }> = [];
|
|
276
276
|
for (const row of rows) {
|
|
@@ -296,7 +296,10 @@ export function ftsSearch(db: Database, query: string, k = 20): FtsRankResult[]
|
|
|
296
296
|
if (!ftsQuery) return hasCjk(query) ? (cjkLikeSearch(db, query, k, false) as FtsRankResult[]) : [];
|
|
297
297
|
try {
|
|
298
298
|
const rows = db
|
|
299
|
-
.query(
|
|
299
|
+
.query(`SELECT f.rowid, f.rank FROM fts_episodes f
|
|
300
|
+
WHERE f.fts_episodes MATCH ?
|
|
301
|
+
AND EXISTS (SELECT 1 FROM episodic_memory e WHERE e.rowid = f.rowid AND e.superseded_by IS NULL)
|
|
302
|
+
ORDER BY f.rank, f.rowid LIMIT ?`)
|
|
300
303
|
.all(ftsQuery, k) as Record<string, unknown>[];
|
|
301
304
|
if (rows.length === 0 && hasCjk(query)) return cjkLikeSearch(db, query, k, false) as FtsRankResult[];
|
|
302
305
|
return rows.map(row => ({ rowid: Number(row.rowid), rank: Number(row.rank) }));
|
|
@@ -310,7 +313,10 @@ export function ftsSearchWorking(db: Database, query: string, k = 20): WorkingFt
|
|
|
310
313
|
if (!ftsQuery) return hasCjk(query) ? (cjkLikeSearch(db, query, k, true) as WorkingFtsRankResult[]) : [];
|
|
311
314
|
try {
|
|
312
315
|
const rows = db
|
|
313
|
-
.query(
|
|
316
|
+
.query(`SELECT f.id, f.rank FROM fts_working f
|
|
317
|
+
WHERE f.fts_working MATCH ?
|
|
318
|
+
AND EXISTS (SELECT 1 FROM working_memory w WHERE w.id = f.id AND w.superseded_by IS NULL)
|
|
319
|
+
ORDER BY f.rank, f.id LIMIT ?`)
|
|
314
320
|
.all(ftsQuery, k) as Record<string, unknown>[];
|
|
315
321
|
if (rows.length === 0 && hasCjk(query)) return cjkLikeSearch(db, query, k, true) as WorkingFtsRankResult[];
|
|
316
322
|
return rows.map(row => ({ id: String(row.id), rank: Number(row.rank) }));
|
package/src/core/beam/recall.ts
CHANGED
|
@@ -545,15 +545,28 @@ function ftsRows(
|
|
|
545
545
|
): Row[] {
|
|
546
546
|
if (!tableExists(beam, table)) return [];
|
|
547
547
|
try {
|
|
548
|
+
// Superseded rows stay in the FTS mirrors (their content never changed) but must not
|
|
549
|
+
// occupy LIMIT slots — visibility filtering would drop them AFTER they displaced live rows.
|
|
548
550
|
if (table === "fts_working") {
|
|
549
|
-
return queryAll(
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
551
|
+
return queryAll(
|
|
552
|
+
beam,
|
|
553
|
+
// Correlated EXISTS, never `id IN (SELECT ...)`: the IN form makes SQLite build a
|
|
554
|
+
// LIST SUBQUERY over the whole live table on every lexical recall, before the small
|
|
555
|
+
// LIMIT applies. Measured on a 40k-row bank with a selective query: 9.19ms vs
|
|
556
|
+
// 0.042ms. EXISTS probes the primary key only for rows MATCH actually produced.
|
|
557
|
+
`SELECT f.id, f.rank FROM fts_working f
|
|
558
|
+
WHERE f.fts_working MATCH ?
|
|
559
|
+
AND EXISTS (SELECT 1 FROM working_memory w WHERE w.id = f.id AND w.superseded_by IS NULL)
|
|
560
|
+
ORDER BY f.rank, f.id LIMIT ?`,
|
|
561
|
+
[ftsQuery(query, useSynonyms), limit],
|
|
562
|
+
);
|
|
553
563
|
}
|
|
554
564
|
return queryAll(
|
|
555
565
|
beam,
|
|
556
|
-
|
|
566
|
+
`SELECT f.rowid, f.rank FROM fts_episodes f
|
|
567
|
+
WHERE f.fts_episodes MATCH ?
|
|
568
|
+
AND EXISTS (SELECT 1 FROM episodic_memory e WHERE e.rowid = f.rowid AND e.superseded_by IS NULL)
|
|
569
|
+
ORDER BY f.rank, f.rowid LIMIT ?`,
|
|
557
570
|
[ftsQuery(query, useSynonyms), limit],
|
|
558
571
|
);
|
|
559
572
|
} catch {
|