@oh-my-pi/pi-mnemopi 18.1.22 → 18.2.1

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,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.2.1] - 2026-09-15
6
+
7
+ ### Fixed
8
+
9
+ - Fixed an explicitly invalidated memory still being returned by an identical repeat query until the recall cache expired.
10
+ - Fixed recall continuing to serve a stale, pre-embedding ranking for up to an hour after background embeddings finished, when the enhanced recall cache is enabled.
11
+
5
12
  ## [18.0.11] - 2026-08-29
6
13
 
7
14
  ### Fixed
@@ -10843,7 +10843,7 @@ RUST RUNTIME DEPENDENCY LICENSES
10843
10843
 
10844
10844
  Generated from Cargo.lock by cargo-about 0.8.2 in locked, offline, workspace, all-feature, all-target mode, then restricted using cargo metadata to normal and build edges reachable from the workspace (development-only edges are excluded). cargo-deny independently evaluates the complete all-target graph.
10845
10845
 
10846
- Native Opus (audiopus/audiopus_sys) and PCRE2 (pcre2/pcre2-sys) are covered by the exact crate license payloads below. inferno 0.12.8 is package-scoped as CDDL-1.0: its CDDL-covered source remains available in the locked crates.io source archive at https://crates.io/crates/inferno/0.12.8 and https://github.com/jonhoo/inferno. The full locked license text follows below.
10846
+ Native Opus (opus/opusic-sys) and PCRE2 (pcre2/pcre2-sys) are covered by the exact crate license payloads below. inferno 0.12.8 is package-scoped as CDDL-1.0: its CDDL-covered source remains available in the locked crates.io source archive at https://crates.io/crates/inferno/0.12.8 and https://github.com/jonhoo/inferno. The full locked license text follows below.
10847
10847
 
10848
10848
  -------------------------------------------------------------------------------
10849
10849
  License: Apache License 2.0 (Apache-2.0)
@@ -12509,6 +12509,58 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
12509
12509
  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
12510
12510
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12511
12511
 
12512
+ -------------------------------------------------------------------------------
12513
+ License: BSD 3-Clause "New" or "Revised" License (BSD-3-Clause)
12514
+ Used by:
12515
+ - opusic-sys 0.7.5 (https://github.com/DoumanAsh/opusic-sys)
12516
+
12517
+ Full license text:
12518
+
12519
+ Copyright 2001-2023 Xiph.Org, Skype Limited, Octasic,
12520
+ Jean-Marc Valin, Timothy B. Terriberry,
12521
+ CSIRO, Gregory Maxwell, Mark Borgerding,
12522
+ Erik de Castro Lopo, Mozilla, Amazon
12523
+
12524
+ Redistribution and use in source and binary forms, with or without
12525
+ modification, are permitted provided that the following conditions
12526
+ are met:
12527
+
12528
+ - Redistributions of source code must retain the above copyright
12529
+ notice, this list of conditions and the following disclaimer.
12530
+
12531
+ - Redistributions in binary form must reproduce the above copyright
12532
+ notice, this list of conditions and the following disclaimer in the
12533
+ documentation and/or other materials provided with the distribution.
12534
+
12535
+ - Neither the name of Internet Society, IETF or IETF Trust, nor the
12536
+ names of specific contributors, may be used to endorse or promote
12537
+ products derived from this software without specific prior written
12538
+ permission.
12539
+
12540
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
12541
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
12542
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
12543
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
12544
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
12545
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
12546
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
12547
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
12548
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
12549
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
12550
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12551
+
12552
+ Opus is subject to the royalty-free patent licenses which are
12553
+ specified at:
12554
+
12555
+ Xiph.Org Foundation:
12556
+ https://datatracker.ietf.org/ipr/1524/
12557
+
12558
+ Microsoft Corporation:
12559
+ https://datatracker.ietf.org/ipr/1914/
12560
+
12561
+ Broadcom Corporation:
12562
+ https://datatracker.ietf.org/ipr/1526/
12563
+
12512
12564
  -------------------------------------------------------------------------------
12513
12565
  License: BSD 3-Clause "New" or "Revised" License (BSD-3-Clause)
12514
12566
  Used by:
@@ -13290,29 +13342,6 @@ jurisdiction of the Federal Courts of the Northern District of
13290
13342
  California and the state courts of the State of California, with
13291
13343
  venue lying in Santa Clara County, California.
13292
13344
 
13293
- -------------------------------------------------------------------------------
13294
- License: ISC License (ISC)
13295
- Used by:
13296
- - audiopus_sys 0.2.2 (https://github.com/lakelezz/audiopus_sys.git)
13297
-
13298
- Full license text:
13299
-
13300
- ISC License
13301
-
13302
- Copyright (c) 2019, Lakelezz
13303
-
13304
- Permission to use, copy, modify, and/or distribute this software for any
13305
- purpose with or without fee is hereby granted, provided that the above
13306
- copyright notice and this permission notice appear in all copies.
13307
-
13308
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13309
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13310
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13311
- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13312
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13313
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13314
- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
13315
-
13316
13345
  -------------------------------------------------------------------------------
13317
13346
  License: ISC License (ISC)
13318
13347
  Used by:
@@ -18734,7 +18763,7 @@ DEALINGS IN THE SOFTWARE.
18734
18763
  -------------------------------------------------------------------------------
18735
18764
  License: MIT License (MIT)
18736
18765
  Used by:
18737
- - opus 0.3.1 (https://github.com/SpaceManiac/opus-rs)
18766
+ - opus 0.4.0 (https://github.com/SpaceManiac/opus-rs)
18738
18767
 
18739
18768
  Full license text:
18740
18769
 
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.1.22",
4
+ "version": "18.2.1",
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": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
40
40
  },
41
41
  "dependencies": {
42
- "@oh-my-pi/pi-ai": "18.1.22",
43
- "@oh-my-pi/pi-catalog": "18.1.22",
44
- "@oh-my-pi/pi-natives": "18.1.22",
45
- "@oh-my-pi/pi-utils": "18.1.22"
42
+ "@oh-my-pi/pi-ai": "18.2.1",
43
+ "@oh-my-pi/pi-catalog": "18.2.1",
44
+ "@oh-my-pi/pi-natives": "18.2.1",
45
+ "@oh-my-pi/pi-utils": "18.2.1"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "fastembed": "2.1.0",
@@ -576,8 +576,26 @@ export function extractAndStoreFacts(
576
576
  const rawUnit = match[2] ?? "";
577
577
  let unit = rawUnit.toLowerCase();
578
578
  if (unit.endsWith("s") && !unit.endsWith("ms")) unit = unit.slice(0, -1);
579
- const prefixWords = text
580
- .slice(Math.max(0, (match.index ?? 0) - 50), match.index ?? 0)
579
+ // The naive fixed 50-char lookbehind can cross a markdown table cell, a code fence, or
580
+ // a sentence boundary and glue unrelated words into one underscore-joined key (e.g.
581
+ // "iam-role_smrx/sm-secrets_ssm-parameters_api" -> "2api" from a table row, or
582
+ // "cadence_90/60/5_30-_second" from a run-on list). Clamping the window to start after
583
+ // the nearest preceding structural boundary -- newline, table pipe, sentence-ending
584
+ // punctuation, or a backtick -- keeps the phrase within one cell/sentence/line, matching
585
+ // what a human would read as "the words right before this number" rather than 50 raw
586
+ // characters regardless of what they cross.
587
+ //
588
+ // A bare `.` is NOT always a sentence boundary: "v1.2", "3.14", "18.0.8" all contain
589
+ // dots that are part of a version/decimal number, not sentence punctuation. Only count
590
+ // `.`/`!`/`?` as a boundary when followed by whitespace or the end of the window -- i.e.
591
+ // it actually ends a sentence.
592
+ const windowStart = Math.max(0, (match.index ?? 0) - 50);
593
+ let window = text.slice(windowStart, match.index ?? 0);
594
+ const boundaryMatch = /\n|\||`|[.!?](?=\s|$)|;/g;
595
+ let lastBoundary = -1;
596
+ for (let m = boundaryMatch.exec(window); m; m = boundaryMatch.exec(window)) lastBoundary = m.index;
597
+ if (lastBoundary !== -1) window = window.slice(lastBoundary + 1);
598
+ const prefixWords = window
581
599
  .replace(/`[^`]*`/g, " ")
582
600
  .split(/\s+/)
583
601
  .map(w => w.replace(/[.,:;!?()[\]"'`*_]/g, ""))
@@ -794,15 +794,31 @@ async function runEmbedding(beam: BeamMemoryState, items: readonly EmbedItem[]):
794
794
  using insertEmbedding = beam.db.prepare(
795
795
  "INSERT OR REPLACE INTO memory_embeddings(memory_id, embedding_json, model) VALUES (?, ?, ?)",
796
796
  );
797
+ let committed = 0;
797
798
  const insertMany = beam.db.transaction((rows: readonly EmbedItem[]) => {
798
799
  for (let i = 0; i < rows.length; i += 1) {
799
800
  const vector = matrix[i];
800
801
  const item = rows[i];
801
802
  if (vector === undefined || item === undefined) continue;
802
803
  insertEmbedding.run(item.memoryId, JSON.stringify(Array.from(vector)), model);
804
+ committed += 1;
803
805
  }
804
806
  });
805
807
  insertMany(items);
808
+ // A recall taken while these vectors were still generating cached an FTS-only ranking, and
809
+ // committing vectors changes what recall returns -- so that cache must be dropped, or the
810
+ // pre-embedding order keeps being served for the whole cache TTL. Gated on `committed`: a
811
+ // batch whose provider returned a short or empty matrix inserts nothing, changes no ranking,
812
+ // and invalidating there would only discard a still-valid cache. Only the query cache is
813
+ // affected; the polyphonic subject dictionary is built from facts/gists, which an embedding
814
+ // batch never touches.
815
+ if (committed > 0) {
816
+ const caches = beam.caches as
817
+ | { queryCache?: { invalidate?: () => void }; _queryCache?: { invalidate?: () => void } }
818
+ | undefined;
819
+ caches?.queryCache?.invalidate?.();
820
+ caches?._queryCache?.invalidate?.();
821
+ }
806
822
  } catch (error) {
807
823
  // Background embedding generation is best-effort: a failing provider, a closed DB
808
824
  // during shutdown, or a transient API error must never disrupt the synchronous
@@ -651,7 +651,16 @@ export function invalidate(beam: BeamMemoryState, memoryId: string, replacementI
651
651
  `,
652
652
  [now, replacementId, memoryId, beam.sessionId],
653
653
  );
654
- if (working.changes > 0) return true;
654
+ if (working.changes > 0) {
655
+ // Recall filters `valid_until`/`superseded_by` in SQL, but the enhanced path consults the
656
+ // query cache BEFORE it reaches SQL. Without this the row a caller just retired keeps being
657
+ // served to an identical query for the rest of the cache TTL -- the one thing an explicit
658
+ // invalidation is supposed to guarantee against. Every other mutator here already does this;
659
+ // this one was the omission. Gated on an actual row change, like `forgetWorking`, so a
660
+ // no-op invalidation never discards a valid cache.
661
+ invalidateCaches(beam);
662
+ return true;
663
+ }
655
664
  const episodic = beam.db.run(
656
665
  `
657
666
  UPDATE episodic_memory
@@ -660,7 +669,11 @@ export function invalidate(beam: BeamMemoryState, memoryId: string, replacementI
660
669
  `,
661
670
  [now, replacementId, memoryId, beam.sessionId],
662
671
  );
663
- return episodic.changes > 0;
672
+ if (episodic.changes > 0) {
673
+ invalidateCaches(beam);
674
+ return true;
675
+ }
676
+ return false;
664
677
  }
665
678
 
666
679
  export function getWorkingStats(
@@ -189,18 +189,20 @@ export async function callConfiguredCompletion(
189
189
  return null;
190
190
  }
191
191
  try {
192
- const message = await retryTransientCompletion(() =>
193
- completeSimple(
194
- model,
195
- {
196
- messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
197
- },
198
- {
199
- apiKey: llmApiKey() || undefined,
200
- maxTokens: opts.maxTokens ?? llmMaxTokens(),
201
- temperature,
202
- },
203
- ),
192
+ const message = await retryTransientCompletion(
193
+ () =>
194
+ completeSimple(
195
+ model,
196
+ {
197
+ messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
198
+ },
199
+ {
200
+ apiKey: llmApiKey() || undefined,
201
+ maxTokens: opts.maxTokens ?? llmMaxTokens(),
202
+ temperature,
203
+ },
204
+ ),
205
+ { provider: model.provider },
204
206
  );
205
207
  return assistantText(message).trim() || null;
206
208
  } catch {