pi-hermes-memory 0.6.7 → 0.6.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.
@@ -8,10 +8,141 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
8
8
  import { Type } from "typebox";
9
9
  import { StringEnum } from "@mariozechner/pi-ai";
10
10
  import { MemoryStore } from "../store/memory-store.js";
11
+ import { DatabaseManager } from "../store/db.js";
12
+ import {
13
+ formatFailureMemoryContent,
14
+ removeSyncedMemories,
15
+ replaceSyncedMemories,
16
+ syncMemoryEntry,
17
+ } from "../store/sqlite-memory-store.js";
11
18
  import { MEMORY_TOOL_DESCRIPTION } from "../constants.js";
12
- import type { MemoryCategory } from "../types.js";
19
+ import type { MemoryCategory, MemoryResult } from "../types.js";
13
20
 
14
- export function registerMemoryTool(pi: ExtensionAPI, store: MemoryStore, projectStore: MemoryStore | null): void {
21
+ function appendSyncWarning(result: MemoryResult, warning: string): MemoryResult {
22
+ const warnings = [...(((result as any).warnings ?? []) as string[]), warning];
23
+ const message = result.message ? `${result.message} Warning: ${warning}` : warning;
24
+ return {
25
+ ...result,
26
+ message,
27
+ warning,
28
+ warnings,
29
+ } as MemoryResult;
30
+ }
31
+
32
+ function sqliteProjectFor(rawTarget: "memory" | "user" | "project" | "failure", projectName?: string | null): string | null | undefined {
33
+ if (rawTarget === "project") return projectName?.trim() || null;
34
+ if (rawTarget === "memory") return null;
35
+ if (rawTarget === "user") return null;
36
+ if (rawTarget === "failure") return null;
37
+ return undefined;
38
+ }
39
+
40
+ function sqliteTargetFor(rawTarget: "memory" | "user" | "project" | "failure"): "memory" | "user" | "failure" {
41
+ if (rawTarget === "project") return "memory";
42
+ return rawTarget;
43
+ }
44
+
45
+ async function syncAddToSqlite(
46
+ rawTarget: "memory" | "user" | "project" | "failure",
47
+ content: string,
48
+ category: MemoryCategory | undefined,
49
+ failureReason: string | undefined,
50
+ dbManager: DatabaseManager | null,
51
+ projectName?: string | null,
52
+ ): Promise<string | null> {
53
+ if (!dbManager) return null;
54
+
55
+ try {
56
+ const sqliteTarget = sqliteTargetFor(rawTarget);
57
+ const sqliteProject = sqliteProjectFor(rawTarget, projectName);
58
+
59
+ if (rawTarget === "failure") {
60
+ const failureCategory = category ?? "failure";
61
+ syncMemoryEntry(dbManager, {
62
+ content: formatFailureMemoryContent(content, {
63
+ category: failureCategory,
64
+ failureReason,
65
+ }),
66
+ target: "failure",
67
+ project: sqliteProject ?? null,
68
+ category: failureCategory,
69
+ failureReason,
70
+ });
71
+ return null;
72
+ }
73
+
74
+ syncMemoryEntry(dbManager, {
75
+ content,
76
+ target: sqliteTarget,
77
+ project: sqliteProject ?? null,
78
+ });
79
+ return null;
80
+ } catch (err) {
81
+ return `Saved to Markdown, but SQLite search sync failed: ${err instanceof Error ? err.message : String(err)}`;
82
+ }
83
+ }
84
+
85
+ async function syncReplaceToSqlite(
86
+ rawTarget: "memory" | "user" | "project" | "failure",
87
+ oldText: string,
88
+ newContent: string,
89
+ dbManager: DatabaseManager | null,
90
+ projectName?: string | null,
91
+ ): Promise<string | null> {
92
+ if (!dbManager) return null;
93
+
94
+ try {
95
+ const sqliteTarget = sqliteTargetFor(rawTarget);
96
+ const sqliteProject = sqliteProjectFor(rawTarget, projectName);
97
+ const syncResult = replaceSyncedMemories(dbManager, oldText, {
98
+ content: newContent,
99
+ target: sqliteTarget,
100
+ project: sqliteProject,
101
+ });
102
+
103
+ if (syncResult.matched === 0) {
104
+ return "Saved to Markdown, but no matching SQLite memory row was updated. Run /memory-sync-markdown if search results look stale.";
105
+ }
106
+
107
+ return null;
108
+ } catch (err) {
109
+ return `Saved to Markdown, but SQLite search sync failed: ${err instanceof Error ? err.message : String(err)}`;
110
+ }
111
+ }
112
+
113
+ async function syncRemoveFromSqlite(
114
+ rawTarget: "memory" | "user" | "project" | "failure",
115
+ oldText: string,
116
+ dbManager: DatabaseManager | null,
117
+ projectName?: string | null,
118
+ ): Promise<string | null> {
119
+ if (!dbManager) return null;
120
+
121
+ try {
122
+ const sqliteTarget = sqliteTargetFor(rawTarget);
123
+ const sqliteProject = sqliteProjectFor(rawTarget, projectName);
124
+ const syncResult = removeSyncedMemories(dbManager, oldText, {
125
+ target: sqliteTarget,
126
+ project: sqliteProject,
127
+ });
128
+
129
+ if (syncResult.matched === 0) {
130
+ return "Saved to Markdown, but no matching SQLite memory row was removed. Run /memory-sync-markdown if search results look stale.";
131
+ }
132
+
133
+ return null;
134
+ } catch (err) {
135
+ return `Saved to Markdown, but SQLite search sync failed: ${err instanceof Error ? err.message : String(err)}`;
136
+ }
137
+ }
138
+
139
+ export function registerMemoryTool(
140
+ pi: ExtensionAPI,
141
+ store: MemoryStore,
142
+ projectStore: MemoryStore | null,
143
+ dbManager: DatabaseManager | null = null,
144
+ projectName?: string | null,
145
+ ): void {
15
146
  pi.registerTool({
16
147
  name: "memory",
17
148
  label: "Memory",
@@ -62,7 +193,8 @@ export function registerMemoryTool(pi: ExtensionAPI, store: MemoryStore, project
62
193
  // After the guard above, activeStore is guaranteed non-null when rawTarget === 'project'
63
194
  const store_ = activeStore!;
64
195
 
65
- let result;
196
+ let result: MemoryResult;
197
+ let syncWarning: string | null = null;
66
198
  switch (action) {
67
199
  case "add":
68
200
  if (!content) {
@@ -86,8 +218,14 @@ export function registerMemoryTool(pi: ExtensionAPI, store: MemoryStore, project
86
218
  category: memoryCategory,
87
219
  failureReason: failure_reason,
88
220
  });
221
+ if (result.success) {
222
+ syncWarning = await syncAddToSqlite(rawTarget, content, memoryCategory, failure_reason, dbManager, projectName);
223
+ }
89
224
  } else {
90
225
  result = await store_.add(target, content);
226
+ if (result.success) {
227
+ syncWarning = await syncAddToSqlite(rawTarget, content, undefined, undefined, dbManager, projectName);
228
+ }
91
229
  }
92
230
  break;
93
231
 
@@ -121,6 +259,9 @@ export function registerMemoryTool(pi: ExtensionAPI, store: MemoryStore, project
121
259
  };
122
260
  }
123
261
  result = await store_.replace(target, old_text, content);
262
+ if (result.success) {
263
+ syncWarning = await syncReplaceToSqlite(rawTarget, old_text, content, dbManager, projectName);
264
+ }
124
265
  break;
125
266
 
126
267
  case "remove":
@@ -139,6 +280,9 @@ export function registerMemoryTool(pi: ExtensionAPI, store: MemoryStore, project
139
280
  };
140
281
  }
141
282
  result = await store_.remove(target, old_text);
283
+ if (result.success) {
284
+ syncWarning = await syncRemoveFromSqlite(rawTarget, old_text, dbManager, projectName);
285
+ }
142
286
  break;
143
287
 
144
288
  default:
@@ -148,9 +292,16 @@ export function registerMemoryTool(pi: ExtensionAPI, store: MemoryStore, project
148
292
  };
149
293
  }
150
294
 
295
+ if (syncWarning && result.success) {
296
+ result = appendSyncWarning(result, syncWarning);
297
+ }
298
+
151
299
  // Tag project results so the caller knows the scope
152
300
  if (rawTarget === "project" && result.success) {
153
- (result as any).target = "project";
301
+ result = {
302
+ ...result,
303
+ target: "project",
304
+ };
154
305
  }
155
306
 
156
307
  return {
package/src/types.ts CHANGED
@@ -53,7 +53,9 @@ export interface MemoryResult {
53
53
  success: boolean;
54
54
  error?: string;
55
55
  message?: string;
56
- target?: "memory" | "user" | "failure";
56
+ warning?: string;
57
+ warnings?: string[];
58
+ target?: "memory" | "user" | "failure" | "project";
57
59
  entries?: string[];
58
60
  usage?: string;
59
61
  entry_count?: number;