pi-hermes-memory 0.6.7 → 0.6.8
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/README.md +18 -3
- package/docs/images/memory-architecture.svg +1 -1
- package/docs/images/session-lifecycle.svg +1 -1
- package/docs/images/source-architecture.svg +1 -1
- package/docs/mermaid/memory-architecture.mmd +22 -4
- package/docs/mermaid/session-lifecycle.mmd +34 -31
- package/docs/mermaid/source-architecture.mmd +17 -1
- package/package.json +1 -1
- package/src/handlers/correction-detector.ts +28 -3
- package/src/handlers/learn-memory.ts +16 -10
- package/src/handlers/preview-context.ts +67 -0
- package/src/handlers/sync-markdown-memories.ts +136 -0
- package/src/index.ts +7 -3
- package/src/store/sqlite-memory-store.ts +472 -46
- package/src/tools/memory-tool.ts +155 -4
- package/src/types.ts +3 -1
package/src/tools/memory-tool.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|