@oh-my-pi/pi-mnemopi 17.2.10 → 17.2.12

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
+ ## [17.2.11] - 2026-08-07
6
+
7
+ ### Fixed
8
+
9
+ - Fixed an issue where an interrupted local embedding model download could permanently corrupt the cache and silently disable semantic recall. The system now automatically detects incomplete model files, clears the corrupted cache, and retries the download.
10
+
5
11
  ## [17.2.10] - 2026-08-06
6
12
 
7
13
  ### Changed
@@ -31,6 +31,24 @@ export type LocalModelInitializer = (options: LocalModelInitOptions) => Promise<
31
31
  * @internal exported for tests
32
32
  */
33
33
  export declare function quarantineCorruptModelFile(message: string, cacheDir?: string): Promise<boolean>;
34
+ /**
35
+ * Recover from a partially-extracted fastembed model cache. An interrupted
36
+ * archive download/extraction leaves `<cacheDir>/<model>/` populated with
37
+ * sidecars and a truncated `model.onnx_data` but WITHOUT the graph file
38
+ * (`model.onnx` / `model_optimized.onnx`), so `FlagEmbedding.init` throws
39
+ * `Model file not found at .../model.onnx`. Upstream `retrieveModel`
40
+ * short-circuits on the existing model dir and never re-downloads, and
41
+ * `downloadFileFromGCS` reuses a leftover partial `<model>.tar.gz` as-is, so
42
+ * the cache is stuck broken forever: recall silently dies and the rebuild
43
+ * queue grows every session. Delete the incomplete model dir AND the leftover
44
+ * partial archive so the retry re-downloads from scratch. The model path is
45
+ * error-message CONTENT, so the dir is only removed when it is a direct child
46
+ * of the fastembed cache root — never touch a directory a dependency merely
47
+ * names. Returns true when a retry is worth attempting (the incomplete cache
48
+ * was cleared, or already gone from a concurrent heal).
49
+ * @internal exported for tests
50
+ */
51
+ export declare function clearIncompleteModelCache(message: string, cacheDir?: string): Promise<boolean>;
34
52
  /**
35
53
  * Shared local-model initializer: FlagEmbedding.init with BOTH cache heals.
36
54
  * Missing sidecars (config/tokenizer/tokens map) re-fetch and retry; a
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-mnemopi",
4
- "version": "17.2.10",
4
+ "version": "17.2.12",
5
5
  "description": "Local SQLite memory engine for Oh My Pi agents",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -39,10 +39,10 @@
39
39
  "fmt": "biome format --write ."
40
40
  },
41
41
  "dependencies": {
42
- "@oh-my-pi/pi-ai": "17.2.10",
43
- "@oh-my-pi/pi-catalog": "17.2.10",
44
- "@oh-my-pi/pi-natives": "17.2.10",
45
- "@oh-my-pi/pi-utils": "17.2.10"
42
+ "@oh-my-pi/pi-ai": "17.2.12",
43
+ "@oh-my-pi/pi-catalog": "17.2.12",
44
+ "@oh-my-pi/pi-natives": "17.2.12",
45
+ "@oh-my-pi/pi-utils": "17.2.12"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "fastembed": "2.1.0",
@@ -92,6 +92,41 @@ export async function quarantineCorruptModelFile(message: string, cacheDir?: str
92
92
  return true;
93
93
  }
94
94
 
95
+ /**
96
+ * Recover from a partially-extracted fastembed model cache. An interrupted
97
+ * archive download/extraction leaves `<cacheDir>/<model>/` populated with
98
+ * sidecars and a truncated `model.onnx_data` but WITHOUT the graph file
99
+ * (`model.onnx` / `model_optimized.onnx`), so `FlagEmbedding.init` throws
100
+ * `Model file not found at .../model.onnx`. Upstream `retrieveModel`
101
+ * short-circuits on the existing model dir and never re-downloads, and
102
+ * `downloadFileFromGCS` reuses a leftover partial `<model>.tar.gz` as-is, so
103
+ * the cache is stuck broken forever: recall silently dies and the rebuild
104
+ * queue grows every session. Delete the incomplete model dir AND the leftover
105
+ * partial archive so the retry re-downloads from scratch. The model path is
106
+ * error-message CONTENT, so the dir is only removed when it is a direct child
107
+ * of the fastembed cache root — never touch a directory a dependency merely
108
+ * names. Returns true when a retry is worth attempting (the incomplete cache
109
+ * was cleared, or already gone from a concurrent heal).
110
+ * @internal exported for tests
111
+ */
112
+ export async function clearIncompleteModelCache(message: string, cacheDir?: string): Promise<boolean> {
113
+ const match = /Model file not found at (.+?\.onnx)\b/i.exec(message);
114
+ if (!match) return false;
115
+ const modelFile = nodePath.resolve(match[1]);
116
+ const modelDir = nodePath.dirname(modelFile);
117
+ const cacheRoot = nodePath.resolve(cacheDir ?? getFastembedCacheDir());
118
+ // Only a direct child of the cache root: `<cacheRoot>/<model>`.
119
+ if (nodePath.dirname(modelDir) !== cacheRoot) return false;
120
+ try {
121
+ await fsp.rm(modelDir, { recursive: true, force: true });
122
+ await fsp.rm(`${modelDir}.tar.gz`, { force: true });
123
+ logger.warn("mnemopi: cleared incomplete local embedding model cache; re-downloading", { modelDir });
124
+ } catch {
125
+ // Concurrent heal or vanished dir: the single retry stays safe.
126
+ }
127
+ return true;
128
+ }
129
+
95
130
  const SIDECAR_ERROR_RE =
96
131
  /(?:Config file not found at .*config|Tokenizer file not found at .*tokenizer|Tokens map file not found at .*special_tokens_map)/u;
97
132
 
@@ -124,6 +159,9 @@ export async function defaultLocalModelInitializer(options: LocalModelInitOption
124
159
  if (/Protobuf parsing failed/i.test(message) && (await quarantineCorruptModelFile(message, cacheDir))) {
125
160
  return initWithSidecarHeal();
126
161
  }
162
+ if (await clearIncompleteModelCache(message, cacheDir)) {
163
+ return initWithSidecarHeal();
164
+ }
127
165
  throw error;
128
166
  }
129
167
  }