@linxiraos/pi-mnemopi 1.1.5 → 1.1.7

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,14 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.1.7] - 2026-09-01
6
+
7
+ - 同步上游 OMP v18.0.11(`b8ce33a58911c26bed1d84f0db9a5e2e727c49a2`)。
8
+
9
+ ## [1.1.6] - 2026-08-30
10
+
11
+ - 同步上游 OMP v18.0.9(`cc14e04f075d`)。
12
+
5
13
  ## [1.1.5] - 2026-08-26
6
14
 
7
15
  - 随 1.1.5 版本线对齐发布:OMP v18.0.6 同步未触及本包,无独立功能变更。
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@linxiraos/pi-mnemopi",
4
- "version": "1.1.5",
4
+ "version": "1.1.7",
5
5
  "description": "Local SQLite memory engine for Oh My Pi agents",
6
6
  "homepage": "https://linxira-os.github.io/zeta/",
7
7
  "author": "Can Boluk",
@@ -39,10 +39,10 @@
39
39
  "fmt": "biome format --write ."
40
40
  },
41
41
  "dependencies": {
42
- "@linxiraos/pi-ai": "1.1.5",
43
- "@linxiraos/pi-catalog": "1.1.5",
44
- "@linxiraos/pi-natives": "1.1.5",
45
- "@linxiraos/pi-utils": "1.1.5"
42
+ "@linxiraos/pi-ai": "1.1.7",
43
+ "@linxiraos/pi-catalog": "1.1.7",
44
+ "@linxiraos/pi-natives": "1.1.7",
45
+ "@linxiraos/pi-utils": "1.1.7"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "fastembed": "2.1.0",
@@ -270,8 +270,9 @@ 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 ?`)
274
- .all(...cjkChars.map(ch => `%${ch}%`), k * 5) as Record<string, unknown>[];
273
+ .query(`SELECT ${idColumn}, content FROM ${table} WHERE superseded_by IS NULL AND (valid_until IS NULL OR valid_until > ?)
274
+ AND (${conditions}) LIMIT ?`)
275
+ .all(new Date().toISOString(), ...cjkChars.map(ch => `%${ch}%`), k * 5) as Record<string, unknown>[];
275
276
  const scored: Array<{ id: string | number; score: number }> = [];
276
277
  for (const row of rows) {
277
278
  const content = String(row.content ?? "");
@@ -296,8 +297,12 @@ export function ftsSearch(db: Database, query: string, k = 20): FtsRankResult[]
296
297
  if (!ftsQuery) return hasCjk(query) ? (cjkLikeSearch(db, query, k, false) as FtsRankResult[]) : [];
297
298
  try {
298
299
  const rows = db
299
- .query("SELECT rowid, rank FROM fts_episodes WHERE fts_episodes MATCH ? ORDER BY rank, rowid LIMIT ?")
300
- .all(ftsQuery, k) as Record<string, unknown>[];
300
+ .query(`SELECT f.rowid, f.rank FROM fts_episodes f
301
+ WHERE f.fts_episodes MATCH ?
302
+ AND EXISTS (SELECT 1 FROM episodic_memory e WHERE e.rowid = f.rowid AND e.superseded_by IS NULL
303
+ AND (e.valid_until IS NULL OR e.valid_until > ?))
304
+ ORDER BY f.rank, f.rowid LIMIT ?`)
305
+ .all(ftsQuery, new Date().toISOString(), k) as Record<string, unknown>[];
301
306
  if (rows.length === 0 && hasCjk(query)) return cjkLikeSearch(db, query, k, false) as FtsRankResult[];
302
307
  return rows.map(row => ({ rowid: Number(row.rowid), rank: Number(row.rank) }));
303
308
  } catch {
@@ -310,8 +315,12 @@ export function ftsSearchWorking(db: Database, query: string, k = 20): WorkingFt
310
315
  if (!ftsQuery) return hasCjk(query) ? (cjkLikeSearch(db, query, k, true) as WorkingFtsRankResult[]) : [];
311
316
  try {
312
317
  const rows = db
313
- .query("SELECT id, rank FROM fts_working WHERE fts_working MATCH ? ORDER BY rank, id LIMIT ?")
314
- .all(ftsQuery, k) as Record<string, unknown>[];
318
+ .query(`SELECT f.id, f.rank FROM fts_working f
319
+ WHERE f.fts_working MATCH ?
320
+ AND EXISTS (SELECT 1 FROM working_memory w WHERE w.id = f.id AND w.superseded_by IS NULL
321
+ AND (w.valid_until IS NULL OR w.valid_until > ?))
322
+ ORDER BY f.rank, f.id LIMIT ?`)
323
+ .all(ftsQuery, new Date().toISOString(), k) as Record<string, unknown>[];
315
324
  if (rows.length === 0 && hasCjk(query)) return cjkLikeSearch(db, query, k, true) as WorkingFtsRankResult[];
316
325
  return rows.map(row => ({ id: String(row.id), rank: Number(row.rank) }));
317
326
  } catch {
@@ -545,16 +545,31 @@ 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
+ AND (w.valid_until IS NULL OR w.valid_until > ?))
561
+ ORDER BY f.rank, f.id LIMIT ?`,
562
+ [ftsQuery(query, useSynonyms), nowIso(), limit],
563
+ );
553
564
  }
554
565
  return queryAll(
555
566
  beam,
556
- "SELECT rowid, rank FROM fts_episodes WHERE fts_episodes MATCH ? ORDER BY rank, rowid LIMIT ?",
557
- [ftsQuery(query, useSynonyms), limit],
567
+ `SELECT f.rowid, f.rank FROM fts_episodes f
568
+ WHERE f.fts_episodes MATCH ?
569
+ AND EXISTS (SELECT 1 FROM episodic_memory e WHERE e.rowid = f.rowid AND e.superseded_by IS NULL
570
+ AND (e.valid_until IS NULL OR e.valid_until > ?))
571
+ ORDER BY f.rank, f.rowid LIMIT ?`,
572
+ [ftsQuery(query, useSynonyms), nowIso(), limit],
558
573
  );
559
574
  } catch {
560
575
  return [];