@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 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.7",
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.7",
43
- "@oh-my-pi/pi-catalog": "18.0.7",
44
- "@oh-my-pi/pi-natives": "18.0.7",
45
- "@oh-my-pi/pi-utils": "18.0.7"
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",
@@ -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("SELECT rowid, rank FROM fts_episodes WHERE fts_episodes MATCH ? ORDER BY rank, rowid LIMIT ?")
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("SELECT id, rank FROM fts_working WHERE fts_working MATCH ? ORDER BY rank, id LIMIT ?")
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) }));
@@ -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(beam, "SELECT id, rank FROM fts_working WHERE fts_working MATCH ? ORDER BY rank, id LIMIT ?", [
550
- ftsQuery(query, useSynonyms),
551
- limit,
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
- "SELECT rowid, rank FROM fts_episodes WHERE fts_episodes MATCH ? ORDER BY rank, rowid LIMIT ?",
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 {