@pi-unipi/memory 2.1.0 → 2.1.2
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/mempalace.ts +28 -0
- package/package.json +3 -3
- package/storage.ts +37 -21
package/mempalace.ts
CHANGED
|
@@ -22,6 +22,10 @@ export const DEFAULT_PALACE = path.join(os.homedir(), ".mempalace", "palace");
|
|
|
22
22
|
|
|
23
23
|
const INSTALL_FLAG = path.join(os.homedir(), ".unipi", "memory", ".mempalace-install");
|
|
24
24
|
const MIGRATED_FLAG = path.join(os.homedir(), ".unipi", "memory", ".mempalace-migrated");
|
|
25
|
+
/** Flag written after a successful ping, so subsequent sessions can skip
|
|
26
|
+
* the ~0.5s Python cold-start sanity check. Stale after PING_VERIFIED_TTL_MS. */
|
|
27
|
+
const PING_VERIFIED_FLAG = path.join(os.homedir(), ".unipi", "memory", ".mempalace-ping-verified");
|
|
28
|
+
const PING_VERIFIED_TTL_MS = 24 * 60 * 60 * 1000; // 24h
|
|
25
29
|
|
|
26
30
|
/** Path to the bundled bridge script. */
|
|
27
31
|
const BRIDGE_PATH = path.join(dirname(fileURLToPath(import.meta.url)), "bridge", "mempalace_bridge.py");
|
|
@@ -166,6 +170,30 @@ export function ensureMempalace(): MempalaceInstall | null {
|
|
|
166
170
|
/** Drop the cached install record (forces re-detection next session). */
|
|
167
171
|
export function invalidateInstallCache(): void {
|
|
168
172
|
try { if (fs.existsSync(INSTALL_FLAG)) fs.unlinkSync(INSTALL_FLAG); } catch { /* ignore */ }
|
|
173
|
+
invalidatePingVerified();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Was the palace ping-verified recently enough to trust without re-pinging? */
|
|
177
|
+
export function isPingVerified(): boolean {
|
|
178
|
+
try {
|
|
179
|
+
if (!fs.existsSync(PING_VERIFIED_FLAG)) return false;
|
|
180
|
+
const ts = Number.parseInt(fs.readFileSync(PING_VERIFIED_FLAG, "utf-8").trim(), 10);
|
|
181
|
+
if (!Number.isFinite(ts)) return false;
|
|
182
|
+
return Date.now() - ts < PING_VERIFIED_TTL_MS;
|
|
183
|
+
} catch { return false; }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Mark the palace as ping-verified (written after a successful ping). */
|
|
187
|
+
export function markPingVerified(): void {
|
|
188
|
+
try {
|
|
189
|
+
fs.mkdirSync(path.dirname(PING_VERIFIED_FLAG), { recursive: true });
|
|
190
|
+
fs.writeFileSync(PING_VERIFIED_FLAG, String(Date.now()), "utf-8");
|
|
191
|
+
} catch { /* ignore */ }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Drop the ping-verified flag (forces a real ping next session). */
|
|
195
|
+
export function invalidatePingVerified(): void {
|
|
196
|
+
try { if (fs.existsSync(PING_VERIFIED_FLAG)) fs.unlinkSync(PING_VERIFIED_FLAG); } catch { /* ignore */ }
|
|
169
197
|
}
|
|
170
198
|
|
|
171
199
|
/** Has the one-way legacy migration been completed? */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/memory",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Persistent cross-session memory with MemPalace backend (auto-installed) and SQLite fallback for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"better-sqlite3": "^12.9.0",
|
|
44
44
|
"sqlite-vec": "^0.1.9",
|
|
45
45
|
"js-yaml": "^4.1.0",
|
|
46
|
-
"@pi-unipi/core": "2.1.
|
|
47
|
-
"@pi-unipi/info-screen": "2.1.
|
|
46
|
+
"@pi-unipi/core": "2.1.2",
|
|
47
|
+
"@pi-unipi/info-screen": "2.1.2"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
package/storage.ts
CHANGED
|
@@ -19,6 +19,9 @@ import {
|
|
|
19
19
|
runBridge,
|
|
20
20
|
isMigrated,
|
|
21
21
|
markMigrated,
|
|
22
|
+
isPingVerified,
|
|
23
|
+
markPingVerified,
|
|
24
|
+
invalidatePingVerified,
|
|
22
25
|
DEFAULT_PALACE,
|
|
23
26
|
type MempalaceInstall,
|
|
24
27
|
type MempalaceRecord,
|
|
@@ -262,6 +265,22 @@ export class MemoryStorage {
|
|
|
262
265
|
return this.backend === "mempalace" && this.mempalaceInstall !== null;
|
|
263
266
|
}
|
|
264
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Run a MemPalace bridge command, invalidating the ping-verified flag
|
|
270
|
+
* when the call fails. This ensures a palace that breaks after the
|
|
271
|
+
* startup ping-skip gets re-verified on the next session.
|
|
272
|
+
*/
|
|
273
|
+
private memPalaceCall<T>(cmd: string, args: Record<string, unknown> = {}): T | null {
|
|
274
|
+
const install = this.mempalaceInstall;
|
|
275
|
+
if (!install) return null;
|
|
276
|
+
const result = runBridge<T>(install, this.palacePath, cmd, args);
|
|
277
|
+
if (result === null) {
|
|
278
|
+
// Backend didn't respond — force a real ping next session.
|
|
279
|
+
invalidatePingVerified();
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
|
|
265
284
|
/**
|
|
266
285
|
* Initialize storage. Tries MemPalace first (auto-install + one-way
|
|
267
286
|
* auto-migration of legacy memories); falls back to SQLite if MemPalace
|
|
@@ -325,8 +344,14 @@ export class MemoryStorage {
|
|
|
325
344
|
if (!install) return false;
|
|
326
345
|
|
|
327
346
|
// Sanity ping — if the palace/bridge is broken, fall back.
|
|
328
|
-
|
|
329
|
-
|
|
347
|
+
// Skip the ~0.5s Python cold-start when we ping-verified recently;
|
|
348
|
+
// the flag is invalidated on any backend failure so a broken palace
|
|
349
|
+
// is re-checked on the next session.
|
|
350
|
+
if (!isPingVerified()) {
|
|
351
|
+
const ok = runBridge<string>(install, this.palacePath, "ping");
|
|
352
|
+
if (ok !== "pong") return false;
|
|
353
|
+
markPingVerified();
|
|
354
|
+
}
|
|
330
355
|
|
|
331
356
|
this.mempalaceInstall = install;
|
|
332
357
|
this.backend = "mempalace";
|
|
@@ -539,8 +564,7 @@ export class MemoryStorage {
|
|
|
539
564
|
* consistent and durable as a fallback source.
|
|
540
565
|
*/
|
|
541
566
|
private storeMempalace(record: MemoryRecord): void {
|
|
542
|
-
|
|
543
|
-
runBridge(install, this.palacePath, "store", {
|
|
567
|
+
this.memPalaceCall("store", {
|
|
544
568
|
record: {
|
|
545
569
|
id: record.id,
|
|
546
570
|
title: record.title,
|
|
@@ -572,8 +596,7 @@ export class MemoryStorage {
|
|
|
572
596
|
*/
|
|
573
597
|
syncOrphanedFiles(): number {
|
|
574
598
|
if (this.isMempalace()) {
|
|
575
|
-
const
|
|
576
|
-
const synced = runBridge<number>(install, this.palacePath, "sync_orphaned", {
|
|
599
|
+
const synced = this.memPalaceCall<number>("sync_orphaned", {
|
|
577
600
|
project_dir: this.scopeDir,
|
|
578
601
|
wing: this.projectName,
|
|
579
602
|
});
|
|
@@ -636,8 +659,7 @@ export class MemoryStorage {
|
|
|
636
659
|
*/
|
|
637
660
|
hasByTitle(title: string): boolean {
|
|
638
661
|
if (this.isMempalace()) {
|
|
639
|
-
|
|
640
|
-
return runBridge<boolean>(install, this.palacePath, "has_title", {
|
|
662
|
+
return this.memPalaceCall<boolean>("has_title", {
|
|
641
663
|
wing: this.projectName,
|
|
642
664
|
title,
|
|
643
665
|
}) ?? false;
|
|
@@ -654,9 +676,8 @@ export class MemoryStorage {
|
|
|
654
676
|
*/
|
|
655
677
|
findSimilarByTitle(title: string, threshold = 0.6): Array<{ record: MemoryRecord; similarity: number }> {
|
|
656
678
|
if (this.isMempalace()) {
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
install, this.palacePath, "find_similar",
|
|
679
|
+
const rows = this.memPalaceCall<Array<{ record: MempalaceRecord; similarity: number }>>(
|
|
680
|
+
"find_similar",
|
|
660
681
|
{ wing: this.projectName, title, threshold },
|
|
661
682
|
) ?? [];
|
|
662
683
|
return rows.map((r) => ({ record: toMemoryRecord(r.record), similarity: r.similarity }));
|
|
@@ -694,8 +715,7 @@ export class MemoryStorage {
|
|
|
694
715
|
*/
|
|
695
716
|
getById(id: string): MemoryRecord | null {
|
|
696
717
|
if (this.isMempalace()) {
|
|
697
|
-
const
|
|
698
|
-
const rec = runBridge<MempalaceRecord | null>(install, this.palacePath, "get", { id });
|
|
718
|
+
const rec = this.memPalaceCall<MempalaceRecord | null>("get", { id });
|
|
699
719
|
return rec ? toMemoryRecord(rec) : null;
|
|
700
720
|
}
|
|
701
721
|
if (!this.db) throw new Error("Storage not initialized");
|
|
@@ -721,8 +741,7 @@ export class MemoryStorage {
|
|
|
721
741
|
*/
|
|
722
742
|
getByTitle(title: string): MemoryRecord | null {
|
|
723
743
|
if (this.isMempalace()) {
|
|
724
|
-
const
|
|
725
|
-
const rec = runBridge<MempalaceRecord | null>(install, this.palacePath, "get_by_title", {
|
|
744
|
+
const rec = this.memPalaceCall<MempalaceRecord | null>("get_by_title", {
|
|
726
745
|
wing: this.projectName,
|
|
727
746
|
title,
|
|
728
747
|
});
|
|
@@ -768,8 +787,7 @@ export class MemoryStorage {
|
|
|
768
787
|
*/
|
|
769
788
|
listAll(): Array<{ id: string; title: string; type: string }> {
|
|
770
789
|
if (this.isMempalace()) {
|
|
771
|
-
const
|
|
772
|
-
const items = runBridge<MempalaceListItem[]>(install, this.palacePath, "list", {
|
|
790
|
+
const items = this.memPalaceCall<MempalaceListItem[]>("list", {
|
|
773
791
|
wing: this.projectName,
|
|
774
792
|
}) ?? [];
|
|
775
793
|
return items;
|
|
@@ -785,8 +803,7 @@ export class MemoryStorage {
|
|
|
785
803
|
*/
|
|
786
804
|
delete(id: string): boolean {
|
|
787
805
|
if (this.isMempalace()) {
|
|
788
|
-
const
|
|
789
|
-
const ok = runBridge<boolean>(install, this.palacePath, "delete", { id }) ?? false;
|
|
806
|
+
const ok = this.memPalaceCall<boolean>("delete", { id }) ?? false;
|
|
790
807
|
// Also remove the markdown tier if present.
|
|
791
808
|
try {
|
|
792
809
|
const mdPath = path.join(this.scopeDir, `${id}.md`);
|
|
@@ -833,8 +850,7 @@ export class MemoryStorage {
|
|
|
833
850
|
*/
|
|
834
851
|
search(query: string, limit = 10, embedding?: Float32Array | null): SearchResult[] {
|
|
835
852
|
if (this.isMempalace()) {
|
|
836
|
-
const
|
|
837
|
-
const rows = runBridge<MempalaceSearchResult[]>(install, this.palacePath, "search", {
|
|
853
|
+
const rows = this.memPalaceCall<MempalaceSearchResult[]>("search", {
|
|
838
854
|
query,
|
|
839
855
|
wing: this.projectName,
|
|
840
856
|
limit,
|