@chendpoc/pi-memory 0.1.0 → 0.1.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.
Files changed (67) hide show
  1. package/README.md +156 -111
  2. package/dist/adapters/ollamaClient.d.ts +11 -0
  3. package/dist/adapters/ollamaClient.d.ts.map +1 -0
  4. package/dist/adapters/ollamaClient.js +122 -0
  5. package/dist/adapters/ollamaClient.js.map +1 -0
  6. package/dist/adapters/openaiCompatClient.d.ts +11 -0
  7. package/dist/adapters/openaiCompatClient.d.ts.map +1 -0
  8. package/dist/adapters/openaiCompatClient.js +118 -0
  9. package/dist/adapters/openaiCompatClient.js.map +1 -0
  10. package/dist/cli.js +2 -2
  11. package/dist/cli.js.map +1 -1
  12. package/dist/fallback/sessionIndex.d.ts.map +1 -1
  13. package/dist/fallback/sessionIndex.js +90 -25
  14. package/dist/fallback/sessionIndex.js.map +1 -1
  15. package/dist/fallback/sessionSearch.d.ts +1 -1
  16. package/dist/fallback/sessionSearch.d.ts.map +1 -1
  17. package/dist/fallback/sessionSearch.js +101 -28
  18. package/dist/fallback/sessionSearch.js.map +1 -1
  19. package/dist/index.d.ts +4 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +4 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/local/graphQuery.d.ts +21 -0
  24. package/dist/local/graphQuery.d.ts.map +1 -0
  25. package/dist/local/graphQuery.js +170 -0
  26. package/dist/local/graphQuery.js.map +1 -0
  27. package/dist/paths.js +1 -1
  28. package/dist/paths.js.map +1 -1
  29. package/dist/pi-extension.d.ts.map +1 -1
  30. package/dist/pi-extension.js +57 -17
  31. package/dist/pi-extension.js.map +1 -1
  32. package/dist/service.d.ts +10 -10
  33. package/dist/service.d.ts.map +1 -1
  34. package/dist/service.js +72 -30
  35. package/dist/service.js.map +1 -1
  36. package/dist/settings.d.ts +38 -0
  37. package/dist/settings.d.ts.map +1 -0
  38. package/dist/settings.js +68 -0
  39. package/dist/settings.js.map +1 -0
  40. package/dist/sidecar/process.d.ts.map +1 -1
  41. package/dist/sidecar/process.js +16 -4
  42. package/dist/sidecar/process.js.map +1 -1
  43. package/dist/trainer/sessionLoader.d.ts +2 -2
  44. package/dist/trainer/sessionLoader.d.ts.map +1 -1
  45. package/dist/trainer/sessionLoader.js +115 -39
  46. package/dist/trainer/sessionLoader.js.map +1 -1
  47. package/package.json +8 -4
  48. package/src/adapters/ollamaClient.ts +179 -0
  49. package/src/adapters/openaiCompatClient.ts +155 -0
  50. package/src/cache/memoryCaches.ts +72 -0
  51. package/src/cli.ts +4 -3
  52. package/src/fallback/llmRerank.ts +8 -1
  53. package/src/fallback/sessionIndex.ts +78 -40
  54. package/src/fallback/sessionSearch.ts +107 -27
  55. package/src/index.ts +28 -0
  56. package/src/local/graphQuery.ts +252 -0
  57. package/src/paths.ts +1 -1
  58. package/src/pi-extension.ts +164 -36
  59. package/src/preflight/detectIntents.ts +6 -0
  60. package/src/preflight/hook.ts +68 -5
  61. package/src/preflight/render.ts +28 -3
  62. package/src/service.ts +133 -29
  63. package/src/settings.ts +126 -0
  64. package/src/sidecar/process.ts +19 -4
  65. package/src/tools/memoryRecall.ts +33 -9
  66. package/src/trainer/scheduler.ts +3 -0
  67. package/src/trainer/sessionLoader.ts +128 -42
@@ -29,6 +29,21 @@ interface PiSessionFile {
29
29
  messages?: PiSessionMessage[];
30
30
  }
31
31
 
32
+ interface JsonlSessionHeader {
33
+ type: "session";
34
+ id?: string;
35
+ timestamp?: string;
36
+ title?: string;
37
+ }
38
+
39
+ interface JsonlMessageLine {
40
+ type: "message";
41
+ message?: {
42
+ role?: string;
43
+ content?: unknown;
44
+ };
45
+ }
46
+
32
47
  function messageText(content: unknown): string {
33
48
  if (typeof content === "string") return content;
34
49
  if (!Array.isArray(content)) return "";
@@ -53,72 +68,143 @@ export interface SessionLoaderOptions {
53
68
  }
54
69
 
55
70
  /**
56
- * Scan session JSON files, parse Pi session format, optionally filter by
57
- * modified-after timestamp for incremental training.
71
+ * Collect all session files recursively (supports project subdirectories).
72
+ * Returns both .json and .jsonl files.
58
73
  */
59
- export async function loadSessions(
60
- opts: SessionLoaderOptions,
61
- ): Promise<LoadedSession[]> {
62
- const { sessionsDir, modifiedAfter } = opts;
63
- if (!sessionsDir.trim()) return [];
64
-
65
- let entries: string[];
74
+ async function collectSessionFiles(dir: string): Promise<string[]> {
75
+ let names: string[];
66
76
  try {
67
- entries = await fs.readdir(sessionsDir);
77
+ names = await fs.readdir(dir);
68
78
  } catch {
69
79
  return [];
70
80
  }
71
81
 
72
- const sessions: LoadedSession[] = [];
82
+ const files: string[] = [];
83
+ for (const name of names) {
84
+ const full = path.join(dir, name);
85
+ let st;
86
+ try { st = await fs.stat(full); } catch { continue; }
87
+ if (st.isDirectory()) {
88
+ const sub = await collectSessionFiles(full);
89
+ files.push(...sub);
90
+ } else if (st.isFile() && (name.endsWith(".json") || name.endsWith(".jsonl"))) {
91
+ files.push(full);
92
+ }
93
+ }
94
+ return files;
95
+ }
73
96
 
74
- for (const name of entries) {
75
- if (!name.endsWith(".json")) continue;
76
- const filePath = path.join(sessionsDir, name);
97
+ function parseJsonlSession(raw: string, filePath: string): LoadedSession | null {
98
+ const lines = raw.split("\n").filter((l) => l.trim());
99
+ if (lines.length === 0) return null;
77
100
 
78
- let st: Awaited<ReturnType<typeof fs.stat>>;
101
+ let header: JsonlSessionHeader | null = null;
102
+ const turns: SessionTurn[] = [];
103
+ let turnIndex = 0;
104
+
105
+ for (const line of lines) {
106
+ let obj: Record<string, unknown>;
79
107
  try {
80
- st = await fs.stat(filePath);
108
+ obj = JSON.parse(line) as Record<string, unknown>;
81
109
  } catch {
82
110
  continue;
83
111
  }
84
- if (!st.isFile()) continue;
85
112
 
86
- if (modifiedAfter && st.mtime <= modifiedAfter) {
113
+ if (obj.type === "session" && !header) {
114
+ header = obj as unknown as JsonlSessionHeader;
87
115
  continue;
88
116
  }
89
117
 
90
- let session: PiSessionFile;
118
+ if (obj.type === "message") {
119
+ const msg = (obj as unknown as JsonlMessageLine).message;
120
+ if (!msg?.role || !msg.content) continue;
121
+ if (msg.role !== "user" && msg.role !== "assistant") continue;
122
+ const text = messageText(msg.content);
123
+ if (!text.trim()) continue;
124
+ turns.push({ role: msg.role, content: text, turnIndex: turnIndex++ });
125
+ }
126
+ }
127
+
128
+ if (turns.length === 0) return null;
129
+
130
+ return {
131
+ id: header?.id ?? path.basename(filePath, path.extname(filePath)),
132
+ title: header?.title ?? "",
133
+ createdAt: header?.timestamp ?? "",
134
+ filePath,
135
+ modifiedAt: new Date(),
136
+ turns,
137
+ };
138
+ }
139
+
140
+ function parseJsonSession(raw: string, filePath: string): LoadedSession | null {
141
+ let session: PiSessionFile;
142
+ try {
143
+ session = JSON.parse(raw) as PiSessionFile;
144
+ } catch {
145
+ return null;
146
+ }
147
+
148
+ if (!session.messages || session.messages.length === 0) return null;
149
+
150
+ const turns: SessionTurn[] = [];
151
+ for (let i = 0; i < session.messages.length; i++) {
152
+ const msg = session.messages[i]!;
153
+ const text = messageText(msg.content);
154
+ if (!text.trim()) continue;
155
+ turns.push({ role: msg.role ?? "unknown", content: text, turnIndex: i });
156
+ }
157
+
158
+ if (turns.length === 0) return null;
159
+
160
+ return {
161
+ id: session.id ?? path.basename(filePath, ".json"),
162
+ title: session.title ?? "",
163
+ createdAt: session.created_at ?? "",
164
+ filePath,
165
+ modifiedAt: new Date(),
166
+ turns,
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Scan session files (JSON + JSONL, recursive subdirectories), parse Pi session
172
+ * format, optionally filter by modified-after timestamp for incremental training.
173
+ */
174
+ export async function loadSessions(
175
+ opts: SessionLoaderOptions,
176
+ ): Promise<LoadedSession[]> {
177
+ const { sessionsDir, modifiedAfter } = opts;
178
+ if (!sessionsDir.trim()) return [];
179
+
180
+ const filePaths = await collectSessionFiles(sessionsDir);
181
+ const sessions: LoadedSession[] = [];
182
+
183
+ for (const filePath of filePaths) {
184
+ let st: Awaited<ReturnType<typeof fs.stat>>;
91
185
  try {
92
- const raw = await fs.readFile(filePath, "utf8");
93
- session = JSON.parse(raw) as PiSessionFile;
186
+ st = await fs.stat(filePath);
94
187
  } catch {
95
188
  continue;
96
189
  }
190
+ if (!st.isFile()) continue;
191
+ if (modifiedAfter && st.mtime <= modifiedAfter) continue;
97
192
 
98
- if (!session.messages || session.messages.length === 0) continue;
99
-
100
- const turns: SessionTurn[] = [];
101
- for (let i = 0; i < session.messages.length; i++) {
102
- const msg = session.messages[i]!;
103
- const text = messageText(msg.content);
104
- if (!text.trim()) continue;
105
- turns.push({
106
- role: msg.role ?? "unknown",
107
- content: text,
108
- turnIndex: i,
109
- });
193
+ let raw: string;
194
+ try {
195
+ raw = await fs.readFile(filePath, "utf8");
196
+ } catch {
197
+ continue;
110
198
  }
111
199
 
112
- if (turns.length === 0) continue;
200
+ const isJsonl = filePath.endsWith(".jsonl");
201
+ const parsed = isJsonl
202
+ ? parseJsonlSession(raw, filePath)
203
+ : parseJsonSession(raw, filePath);
113
204
 
114
- sessions.push({
115
- id: session.id ?? path.basename(name, ".json"),
116
- title: session.title ?? "",
117
- createdAt: session.created_at ?? "",
118
- filePath,
119
- modifiedAt: st.mtime,
120
- turns,
121
- });
205
+ if (!parsed) continue;
206
+ parsed.modifiedAt = st.mtime;
207
+ sessions.push(parsed);
122
208
  }
123
209
 
124
210
  sessions.sort((a, b) => a.modifiedAt.getTime() - b.modifiedAt.getTime());