opencode-mem 1.0.0

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 (84) hide show
  1. package/README.md +588 -0
  2. package/dist/config.d.ts +33 -0
  3. package/dist/config.d.ts.map +1 -0
  4. package/dist/config.js +258 -0
  5. package/dist/index.d.ts +3 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +618 -0
  8. package/dist/plugin.d.ts +5 -0
  9. package/dist/plugin.d.ts.map +1 -0
  10. package/dist/plugin.js +15 -0
  11. package/dist/services/api-handlers.d.ts +102 -0
  12. package/dist/services/api-handlers.d.ts.map +1 -0
  13. package/dist/services/api-handlers.js +494 -0
  14. package/dist/services/auto-capture.d.ts +32 -0
  15. package/dist/services/auto-capture.d.ts.map +1 -0
  16. package/dist/services/auto-capture.js +451 -0
  17. package/dist/services/cleanup-service.d.ts +20 -0
  18. package/dist/services/cleanup-service.d.ts.map +1 -0
  19. package/dist/services/cleanup-service.js +88 -0
  20. package/dist/services/client.d.ts +104 -0
  21. package/dist/services/client.d.ts.map +1 -0
  22. package/dist/services/client.js +251 -0
  23. package/dist/services/compaction.d.ts +92 -0
  24. package/dist/services/compaction.d.ts.map +1 -0
  25. package/dist/services/compaction.js +421 -0
  26. package/dist/services/context.d.ts +17 -0
  27. package/dist/services/context.d.ts.map +1 -0
  28. package/dist/services/context.js +41 -0
  29. package/dist/services/deduplication-service.d.ts +30 -0
  30. package/dist/services/deduplication-service.d.ts.map +1 -0
  31. package/dist/services/deduplication-service.js +131 -0
  32. package/dist/services/embedding.d.ts +10 -0
  33. package/dist/services/embedding.d.ts.map +1 -0
  34. package/dist/services/embedding.js +77 -0
  35. package/dist/services/jsonc.d.ts +7 -0
  36. package/dist/services/jsonc.d.ts.map +1 -0
  37. package/dist/services/jsonc.js +76 -0
  38. package/dist/services/logger.d.ts +2 -0
  39. package/dist/services/logger.d.ts.map +1 -0
  40. package/dist/services/logger.js +16 -0
  41. package/dist/services/migration-service.d.ts +42 -0
  42. package/dist/services/migration-service.d.ts.map +1 -0
  43. package/dist/services/migration-service.js +258 -0
  44. package/dist/services/privacy.d.ts +4 -0
  45. package/dist/services/privacy.d.ts.map +1 -0
  46. package/dist/services/privacy.js +10 -0
  47. package/dist/services/sqlite/connection-manager.d.ts +10 -0
  48. package/dist/services/sqlite/connection-manager.d.ts.map +1 -0
  49. package/dist/services/sqlite/connection-manager.js +45 -0
  50. package/dist/services/sqlite/shard-manager.d.ts +20 -0
  51. package/dist/services/sqlite/shard-manager.d.ts.map +1 -0
  52. package/dist/services/sqlite/shard-manager.js +221 -0
  53. package/dist/services/sqlite/types.d.ts +39 -0
  54. package/dist/services/sqlite/types.d.ts.map +1 -0
  55. package/dist/services/sqlite/types.js +1 -0
  56. package/dist/services/sqlite/vector-search.d.ts +18 -0
  57. package/dist/services/sqlite/vector-search.d.ts.map +1 -0
  58. package/dist/services/sqlite/vector-search.js +129 -0
  59. package/dist/services/sqlite-client.d.ts +116 -0
  60. package/dist/services/sqlite-client.d.ts.map +1 -0
  61. package/dist/services/sqlite-client.js +284 -0
  62. package/dist/services/tags.d.ts +20 -0
  63. package/dist/services/tags.d.ts.map +1 -0
  64. package/dist/services/tags.js +76 -0
  65. package/dist/services/web-server-lock.d.ts +12 -0
  66. package/dist/services/web-server-lock.d.ts.map +1 -0
  67. package/dist/services/web-server-lock.js +157 -0
  68. package/dist/services/web-server-worker.d.ts +2 -0
  69. package/dist/services/web-server-worker.d.ts.map +1 -0
  70. package/dist/services/web-server-worker.js +221 -0
  71. package/dist/services/web-server.d.ts +22 -0
  72. package/dist/services/web-server.d.ts.map +1 -0
  73. package/dist/services/web-server.js +134 -0
  74. package/dist/types/index.d.ts +48 -0
  75. package/dist/types/index.d.ts.map +1 -0
  76. package/dist/types/index.js +1 -0
  77. package/dist/web/app.d.ts +2 -0
  78. package/dist/web/app.d.ts.map +1 -0
  79. package/dist/web/app.js +691 -0
  80. package/dist/web/favicon.ico +0 -0
  81. package/dist/web/favicon.svg +14 -0
  82. package/dist/web/index.html +202 -0
  83. package/dist/web/styles.css +851 -0
  84. package/package.json +52 -0
@@ -0,0 +1,451 @@
1
+ import { memoryClient } from "./client.js";
2
+ import { getTags } from "./tags.js";
3
+ import { log } from "./logger.js";
4
+ import { CONFIG } from "../config.js";
5
+ export class AutoCaptureService {
6
+ buffers = new Map();
7
+ capturing = new Set();
8
+ tokenThreshold;
9
+ minTokens;
10
+ enabled;
11
+ maxMemories;
12
+ constructor() {
13
+ this.tokenThreshold = CONFIG.autoCaptureTokenThreshold;
14
+ this.minTokens = CONFIG.autoCaptureMinTokens;
15
+ this.maxMemories = CONFIG.autoCaptureMaxMemories;
16
+ this.enabled =
17
+ CONFIG.autoCaptureEnabled &&
18
+ !!CONFIG.memoryModel &&
19
+ !!CONFIG.memoryApiUrl &&
20
+ !!CONFIG.memoryApiKey;
21
+ if (CONFIG.autoCaptureEnabled && !this.enabled) {
22
+ log("Auto-capture disabled: external API not configured (memoryModel, memoryApiUrl, memoryApiKey required)");
23
+ }
24
+ if (this.enabled && CONFIG.memoryApiUrl?.includes("ollama")) {
25
+ log("Warning: Ollama may not support tool calling. Auto-capture might fail.");
26
+ }
27
+ }
28
+ isEnabled() {
29
+ return this.enabled;
30
+ }
31
+ getDisabledReason() {
32
+ if (!CONFIG.autoCaptureEnabled)
33
+ return "Auto-capture disabled in config";
34
+ if (!CONFIG.memoryModel)
35
+ return "memoryModel not configured";
36
+ if (!CONFIG.memoryApiUrl)
37
+ return "memoryApiUrl not configured";
38
+ if (!CONFIG.memoryApiKey)
39
+ return "memoryApiKey not configured";
40
+ return null;
41
+ }
42
+ toggle() {
43
+ this.enabled = !this.enabled;
44
+ return this.enabled;
45
+ }
46
+ getOrCreateBuffer(sessionID) {
47
+ if (!this.buffers.has(sessionID)) {
48
+ this.buffers.set(sessionID, {
49
+ sessionID,
50
+ lastCaptureTokens: 0,
51
+ lastCaptureTime: Date.now(),
52
+ lastCapturedMessageIndex: -1,
53
+ });
54
+ }
55
+ return this.buffers.get(sessionID);
56
+ }
57
+ checkTokenThreshold(sessionID, totalTokens) {
58
+ if (!this.enabled)
59
+ return false;
60
+ if (this.capturing.has(sessionID))
61
+ return false;
62
+ const buffer = this.getOrCreateBuffer(sessionID);
63
+ if (totalTokens < this.minTokens)
64
+ return false;
65
+ const tokensSinceCapture = totalTokens - buffer.lastCaptureTokens;
66
+ if (tokensSinceCapture >= this.tokenThreshold) {
67
+ buffer.lastCaptureTokens = totalTokens;
68
+ return true;
69
+ }
70
+ return false;
71
+ }
72
+ getSystemPrompt(hasContext) {
73
+ const summaryGuidance = CONFIG.autoCaptureSummaryMaxLength > 0
74
+ ? `Keep summaries under ${CONFIG.autoCaptureSummaryMaxLength} characters.`
75
+ : "Extract key details and important information. Be concise but complete.";
76
+ const contextNote = hasContext
77
+ ? `\n\nIMPORTANT: Messages marked [CONTEXT] were already analyzed in previous capture. They are provided for context only. Focus your extraction on messages marked [NEW]. Do not duplicate memories from context messages.`
78
+ : "";
79
+ return `You are a memory extraction assistant analyzing PAST conversations between a USER and an AI ASSISTANT.
80
+
81
+ IMPORTANT CONTEXT:
82
+ - The conversation below has ALREADY HAPPENED
83
+ - You are NOT the assistant in this conversation
84
+ - Your job is to EXTRACT MEMORIES from this past conversation
85
+ - DO NOT try to continue or respond to the conversation
86
+ - DO NOT execute any tasks mentioned in the conversation${contextNote}
87
+
88
+ EXTRACTION GUIDELINES:
89
+
90
+ Categorize each memory by scope:
91
+ - "user": Cross-project user behaviors, preferences, patterns, requests
92
+ Examples: "prefers TypeScript", "likes concise responses", "often asks about complexity analysis"
93
+ - "project": Project-specific knowledge, decisions, architecture, context
94
+ Examples: "uses Bun runtime", "API at /api/v1", "working on opencode-mem plugin"
95
+
96
+ Memory categorization:
97
+ - Choose appropriate type: preference, architecture, workflow, bug-fix, configuration, pattern, request, context
98
+ - Be specific and descriptive with categories
99
+ - Focus on WHAT WAS DISCUSSED, not what should be done
100
+
101
+ Summary guidelines:
102
+ - ${summaryGuidance}
103
+ - Only extract memories worth long-term retention
104
+ - Be selective: quality over quantity
105
+ - Each memory should be atomic and independent
106
+ - Maximum ${this.maxMemories} memories per capture
107
+ - Extract facts, decisions, and context - NOT tasks or actions
108
+
109
+ Use the save_memories function to save extracteories.`;
110
+ }
111
+ markCapturing(sessionID) {
112
+ this.capturing.add(sessionID);
113
+ }
114
+ clearBuffer(sessionID) {
115
+ const buffer = this.buffers.get(sessionID);
116
+ if (buffer) {
117
+ this.buffers.set(sessionID, {
118
+ sessionID,
119
+ lastCaptureTokens: buffer.lastCaptureTokens,
120
+ lastCaptureTime: Date.now(),
121
+ lastCapturedMessageIndex: buffer.lastCapturedMessageIndex,
122
+ });
123
+ }
124
+ this.capturing.delete(sessionID);
125
+ }
126
+ getStats(sessionID) {
127
+ const buffer = this.buffers.get(sessionID);
128
+ if (!buffer)
129
+ return null;
130
+ return {
131
+ lastCaptureTokens: buffer.lastCaptureTokens,
132
+ timeSinceCapture: Date.now() - buffer.lastCaptureTime,
133
+ };
134
+ }
135
+ cleanup(sessionID) {
136
+ this.buffers.delete(sessionID);
137
+ this.capturing.delete(sessionID);
138
+ }
139
+ }
140
+ export async function performAutoCapture(ctx, service, sessionID, directory) {
141
+ try {
142
+ service.markCapturing(sessionID);
143
+ await ctx.client?.tui
144
+ .showToast({
145
+ body: {
146
+ title: "Auto-Capture",
147
+ message: "Analyzing conversation...",
148
+ variant: "info",
149
+ duration: 2000,
150
+ },
151
+ })
152
+ .catch(() => { });
153
+ if (!ctx.client) {
154
+ throw new Error("Client not available");
155
+ }
156
+ const response = await ctx.client.session.messages({
157
+ path: { id: sessionID },
158
+ });
159
+ if (!response.data) {
160
+ log("Auto-capture failed: no data in response", { sessionID });
161
+ service.clearBuffer(sessionID);
162
+ return;
163
+ }
164
+ const allMessages = response.data;
165
+ if (allMessages.length === 0) {
166
+ service.clearBuffer(sessionID);
167
+ return;
168
+ }
169
+ const buffer = service.getOrCreateBuffer(sessionID);
170
+ const lastIndex = buffer.lastCapturedMessageIndex;
171
+ if (allMessages.length <= lastIndex) {
172
+ buffer.lastCapturedMessageIndex = -1;
173
+ log("Auto-capture: message deletion detected, resetting index", { sessionID });
174
+ }
175
+ const contextWindow = CONFIG.autoCaptureContextWindow;
176
+ const startIndex = Math.max(0, lastIndex - contextWindow + 1);
177
+ const messagesToAnalyze = allMessages.slice(startIndex);
178
+ if (messagesToAnalyze.length === 0) {
179
+ service.clearBuffer(sessionID);
180
+ return;
181
+ }
182
+ const userMessages = messagesToAnalyze.filter((m) => m?.info?.role === "user");
183
+ const assistantMessages = messagesToAnalyze.filter((m) => m?.info?.role === "assistant");
184
+ if (userMessages.length === 0 || assistantMessages.length === 0) {
185
+ service.clearBuffer(sessionID);
186
+ return;
187
+ }
188
+ let hasCompletePair = false;
189
+ for (let i = 0; i < messagesToAnalyze.length - 1; i++) {
190
+ const current = messagesToAnalyze[i];
191
+ const next = messagesToAnalyze[i + 1];
192
+ if (current?.info?.role === "user" && next?.info?.role === "assistant") {
193
+ hasCompletePair = true;
194
+ break;
195
+ }
196
+ }
197
+ if (!hasCompletePair) {
198
+ service.clearBuffer(sessionID);
199
+ return;
200
+ }
201
+ const conversationParts = [];
202
+ for (let i = 0; i < messagesToAnalyze.length; i++) {
203
+ const msg = messagesToAnalyze[i];
204
+ if (!msg)
205
+ continue;
206
+ const globalIndex = startIndex + i;
207
+ const isNewMessage = globalIndex > lastIndex;
208
+ const role = msg.info?.role;
209
+ if (role !== "user" && role !== "assistant")
210
+ continue;
211
+ const roleLabel = role.toUpperCase();
212
+ const marker = isNewMessage ? "[NEW]" : "[CONTEXT]";
213
+ let content = "";
214
+ if (msg.parts && Array.isArray(msg.parts)) {
215
+ const textParts = msg.parts.filter((p) => p.type === "text" && p.text);
216
+ content = textParts.map((p) => p.text).join("\n");
217
+ const toolParts = msg.parts.filter((p) => p.type === "tool");
218
+ if (toolParts.length > 0) {
219
+ content += "\n[Tools: " + toolParts.map((p) => p.name || "unknown").join(", ") + "]";
220
+ }
221
+ }
222
+ if (content) {
223
+ conversationParts.push(`${marker} ${roleLabel}: ${content}`);
224
+ }
225
+ }
226
+ if (conversationParts.length === 0) {
227
+ service.clearBuffer(sessionID);
228
+ return;
229
+ }
230
+ const conversationBody = conversationParts.join("\n\n");
231
+ const newMessageCount = allMessages.length - lastIndex - 1;
232
+ const contextMessageCount = messagesToAnalyze.length - newMessageCount;
233
+ const conversationText = `=== CONVERSATION TO ANALYZE ===
234
+
235
+ Metadata:
236
+ - Total messages in session: ${allMessages.length}
237
+ - Messages in this analysis: ${messagesToAnalyze.length}
238
+ - Context messages (already captured): ${contextMessageCount}
239
+ - New messages (focus here): ${newMessageCount}
240
+ ${lastIndex >= 0 ? `- Previous capture ended at message index: ${lastIndex}` : "- This is the first capture for this session"}
241
+
242
+ The following is a past conversation between a USER and an AI ASSISTANT.
243
+ Extract meaningful memories from this conversation.
244
+
245
+ ${conversationBody}
246
+
247
+ === END OF CONVERSATION ===`;
248
+ const systemPrompt = service.getSystemPrompt(lastIndex >= 0);
249
+ const captureResponse = await summarizeWithAI(ctx, sessionID, systemPrompt, conversationText);
250
+ if (!captureResponse || !captureResponse.memories || captureResponse.memories.length === 0) {
251
+ service.clearBuffer(sessionID);
252
+ return;
253
+ }
254
+ const tags = getTags(directory);
255
+ const results = [];
256
+ for (const memory of captureResponse.memories.slice(0, CONFIG.autoCaptureMaxMemories)) {
257
+ if (!memory.summary || !memory.scope || !memory.type)
258
+ continue;
259
+ const tagInfo = memory.scope === "user" ? tags.user : tags.project;
260
+ const result = await memoryClient.addMemory(memory.summary, tagInfo.tag, {
261
+ type: memory.type,
262
+ source: "auto-capture",
263
+ sessionID,
264
+ reasoning: memory.reasoning,
265
+ captureTimestamp: Date.now(),
266
+ displayName: tagInfo.displayName,
267
+ userName: tagInfo.userName,
268
+ userEmail: tagInfo.userEmail,
269
+ projectPath: tagInfo.projectPath,
270
+ projectName: tagInfo.projectName,
271
+ gitRepoUrl: tagInfo.gitRepoUrl,
272
+ });
273
+ if (result.success) {
274
+ results.push({ scope: memory.scope, id: result.id });
275
+ }
276
+ }
277
+ if (results.length === 0) {
278
+ service.clearBuffer(sessionID);
279
+ return;
280
+ }
281
+ const userCount = results.filter((r) => r.scope === "user").length;
282
+ const projectCount = results.filter((r) => r.scope === "project").length;
283
+ await ctx.client?.tui
284
+ .showToast({
285
+ body: {
286
+ title: "Memory Captured",
287
+ message: `Saved ${userCount} user + ${projectCount} project memories`,
288
+ variant: "success",
289
+ duration: 3000,
290
+ },
291
+ })
292
+ .catch(() => { });
293
+ log("Auto-capture: success", {
294
+ sessionID,
295
+ userCount,
296
+ projectCount,
297
+ total: results.length,
298
+ });
299
+ buffer.lastCapturedMessageIndex = allMessages.length - 1;
300
+ service.clearBuffer(sessionID);
301
+ }
302
+ catch (error) {
303
+ log("Auto-capture error", { sessionID, error: String(error) });
304
+ await ctx.client?.tui
305
+ .showToast({
306
+ body: {
307
+ title: "Auto-Capture Failed",
308
+ message: String(error),
309
+ variant: "error",
310
+ duration: 5000,
311
+ },
312
+ })
313
+ .catch(() => { });
314
+ service.clearBuffer(sessionID);
315
+ }
316
+ }
317
+ async function summarizeWithAI(ctx, sessionID, systemPrompt, conversationPrompt) {
318
+ if (!ctx.client) {
319
+ throw new Error("Client not available");
320
+ }
321
+ if (!CONFIG.memoryModel || !CONFIG.memoryApiUrl || !CONFIG.memoryApiKey) {
322
+ throw new Error("External API not configured. Auto-capture requires memoryModel, memoryApiUrl, and memoryApiKey.");
323
+ }
324
+ return await callExternalAPIWithToolCalling(systemPrompt, conversationPrompt);
325
+ }
326
+ function createToolCallSchema() {
327
+ const summaryDescription = CONFIG.autoCaptureSummaryMaxLength > 0
328
+ ? `Memory summary (maximum ${CONFIG.autoCaptureSummaryMaxLength} characters). Focus on most critical information.`
329
+ : "Memory summary with key details and important information. Be concise but complete.";
330
+ return {
331
+ type: "function",
332
+ function: {
333
+ name: "save_memories",
334
+ description: "Save extracted memories from conversation analysis",
335
+ parameters: {
336
+ type: "object",
337
+ properties: {
338
+ memories: {
339
+ type: "array",
340
+ description: "Array of memories extracted from the conversation",
341
+ items: {
342
+ type: "object",
343
+ properties: {
344
+ summary: {
345
+ type: "string",
346
+ description: summaryDescription,
347
+ },
348
+ scope: {
349
+ type: "string",
350
+ enum: ["user", "project"],
351
+ description: "user: cross-project user preferences/behaviors. project: project-specific knowledge/decisions.",
352
+ },
353
+ type: {
354
+ type: "string",
355
+ description: "Category of this memory (e.g., preference, architecture, workflow, bug-fix, configuration, pattern, etc). Choose the most appropriate category.",
356
+ },
357
+ reasoning: {
358
+ type: "string",
359
+ description: "Why this memory is important and worth retaining",
360
+ },
361
+ },
362
+ required: ["summary", "scope", "type"],
363
+ },
364
+ },
365
+ },
366
+ required: ["memories"],
367
+ },
368
+ },
369
+ };
370
+ }
371
+ async function callExternalAPIWithToolCalling(systemPrompt, conversationPrompt) {
372
+ const controller = new AbortController();
373
+ const timeout = setTimeout(() => controller.abort(), 30000);
374
+ try {
375
+ const tools = [createToolCallSchema()];
376
+ const requestBody = {
377
+ model: CONFIG.memoryModel,
378
+ messages: [
379
+ { role: "system", content: systemPrompt },
380
+ { role: "user", content: conversationPrompt },
381
+ ],
382
+ tools: tools,
383
+ tool_choice: { type: "function", name: "save_memories" },
384
+ temperature: 0.3,
385
+ };
386
+ const response = await fetch(`${CONFIG.memoryApiUrl}/chat/completions`, {
387
+ method: "POST",
388
+ headers: {
389
+ "Content-Type": "application/json",
390
+ Authorization: `Bearer ${CONFIG.memoryApiKey}`,
391
+ },
392
+ body: JSON.stringify(requestBody),
393
+ signal: controller.signal,
394
+ });
395
+ if (!response.ok) {
396
+ const errorText = await response.text().catch(() => response.statusText);
397
+ log("Auto-capture API error", {
398
+ status: response.status,
399
+ error: errorText,
400
+ });
401
+ throw new Error(`API error: ${response.status} - ${errorText}`);
402
+ }
403
+ const data = (await response.json());
404
+ if (!data.choices || !data.choices[0]) {
405
+ throw new Error("Invalid API response format");
406
+ }
407
+ const choice = data.choices[0];
408
+ if (!choice.message.tool_calls || choice.message.tool_calls.length === 0) {
409
+ log("Auto-capture: tool calling not used", {
410
+ finishReason: choice.finish_reason,
411
+ });
412
+ throw new Error("Tool calling not supported or not used by provider");
413
+ }
414
+ const toolCall = choice.message.tool_calls[0];
415
+ if (!toolCall || toolCall.function.name !== "save_memories") {
416
+ throw new Error("Invalid tool call response");
417
+ }
418
+ const parsed = JSON.parse(toolCall.function.arguments);
419
+ return validateCaptureResponse(parsed);
420
+ }
421
+ catch (error) {
422
+ if (error instanceof Error && error.name === "AbortError") {
423
+ throw new Error("API request timeout (30s)");
424
+ }
425
+ throw error;
426
+ }
427
+ finally {
428
+ clearTimeout(timeout);
429
+ }
430
+ }
431
+ function validateCaptureResponse(data) {
432
+ if (!data || typeof data !== "object") {
433
+ throw new Error("Response is not an object");
434
+ }
435
+ if (!Array.isArray(data.memories)) {
436
+ throw new Error("memories field is not an array");
437
+ }
438
+ const validMemories = data.memories.filter((m) => {
439
+ return (m &&
440
+ typeof m === "object" &&
441
+ typeof m.summary === "string" &&
442
+ m.summary.trim().length > 0 &&
443
+ (m.scope === "user" || m.scope === "project") &&
444
+ typeof m.type === "string" &&
445
+ m.type.trim().length > 0);
446
+ });
447
+ if (validMemories.length === 0) {
448
+ throw new Error("No valid memories in response");
449
+ }
450
+ return { memories: validMemories };
451
+ }
@@ -0,0 +1,20 @@
1
+ interface CleanupResult {
2
+ deletedCount: number;
3
+ userCount: number;
4
+ projectCount: number;
5
+ }
6
+ export declare class CleanupService {
7
+ private lastCleanupTime;
8
+ private isRunning;
9
+ shouldRunCleanup(): Promise<boolean>;
10
+ runCleanup(): Promise<CleanupResult>;
11
+ getStatus(): {
12
+ enabled: boolean;
13
+ retentionDays: number;
14
+ lastCleanupTime: number;
15
+ isRunning: boolean;
16
+ };
17
+ }
18
+ export declare const cleanupService: CleanupService;
19
+ export {};
20
+ //# sourceMappingURL=cleanup-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanup-service.d.ts","sourceRoot":"","sources":["../../src/services/cleanup-service.ts"],"names":[],"mappings":"AAMA,UAAU,aAAa;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,SAAS,CAAkB;IAE7B,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IAcpC,UAAU,IAAI,OAAO,CAAC,aAAa,CAAC;IAqE1C,SAAS;;;;;;CAQV;AAED,eAAO,MAAM,cAAc,gBAAuB,CAAC"}
@@ -0,0 +1,88 @@
1
+ import { shardManager } from "./sqlite/shard-manager.js";
2
+ import { vectorSearch } from "./sqlite/vector-search.js";
3
+ import { connectionManager } from "./sqlite/connection-manager.js";
4
+ import { CONFIG } from "../config.js";
5
+ import { log } from "./logger.js";
6
+ export class CleanupService {
7
+ lastCleanupTime = 0;
8
+ isRunning = false;
9
+ async shouldRunCleanup() {
10
+ if (!CONFIG.autoCleanupEnabled)
11
+ return false;
12
+ if (this.isRunning)
13
+ return false;
14
+ const now = Date.now();
15
+ const oneDayMs = 24 * 60 * 60 * 1000;
16
+ if (now - this.lastCleanupTime < oneDayMs) {
17
+ return false;
18
+ }
19
+ return true;
20
+ }
21
+ async runCleanup() {
22
+ if (this.isRunning) {
23
+ throw new Error("Cleanup already running");
24
+ }
25
+ this.isRunning = true;
26
+ this.lastCleanupTime = Date.now();
27
+ try {
28
+ log("Cleanup: starting", { retentionDays: CONFIG.autoCleanupRetentionDays });
29
+ const cutoffTime = Date.now() - CONFIG.autoCleanupRetentionDays * 24 * 60 * 60 * 1000;
30
+ const userShards = shardManager.getAllShards("user", "");
31
+ const projectShards = shardManager.getAllShards("project", "");
32
+ const allShards = [...userShards, ...projectShards];
33
+ let totalDeleted = 0;
34
+ let userDeleted = 0;
35
+ let projectDeleted = 0;
36
+ for (const shard of allShards) {
37
+ const db = connectionManager.getConnection(shard.dbPath);
38
+ const oldMemories = db
39
+ .prepare(`
40
+ SELECT id, container_tag FROM memories
41
+ WHERE updated_at < ? AND is_pinned = 0
42
+ `)
43
+ .all(cutoffTime);
44
+ if (oldMemories.length === 0)
45
+ continue;
46
+ for (const memory of oldMemories) {
47
+ try {
48
+ vectorSearch.deleteVector(db, memory.id);
49
+ shardManager.decrementVectorCount(shard.id);
50
+ totalDeleted++;
51
+ if (memory.container_tag?.includes("_user_")) {
52
+ userDeleted++;
53
+ }
54
+ else if (memory.container_tag?.includes("_project_")) {
55
+ projectDeleted++;
56
+ }
57
+ }
58
+ catch (error) {
59
+ log("Cleanup: delete error", { memoryId: memory.id, error: String(error) });
60
+ }
61
+ }
62
+ }
63
+ log("Cleanup: completed", {
64
+ totalDeleted,
65
+ userDeleted,
66
+ projectDeleted,
67
+ cutoffTime: new Date(cutoffTime).toISOString(),
68
+ });
69
+ return {
70
+ deletedCount: totalDeleted,
71
+ userCount: userDeleted,
72
+ projectCount: projectDeleted,
73
+ };
74
+ }
75
+ finally {
76
+ this.isRunning = false;
77
+ }
78
+ }
79
+ getStatus() {
80
+ return {
81
+ enabled: CONFIG.autoCleanupEnabled,
82
+ retentionDays: CONFIG.autoCleanupRetentionDays,
83
+ lastCleanupTime: this.lastCleanupTime,
84
+ isRunning: this.isRunning,
85
+ };
86
+ }
87
+ }
88
+ export const cleanupService = new CleanupService();
@@ -0,0 +1,104 @@
1
+ import type { MemoryType } from "../types/index.js";
2
+ import type { SearchResult } from "./sqlite/types.js";
3
+ interface ProfileData {
4
+ static: string[];
5
+ dynamic: string[];
6
+ }
7
+ export declare class LocalMemoryClient {
8
+ private initPromise;
9
+ private isInitialized;
10
+ constructor();
11
+ private initialize;
12
+ warmup(progressCallback?: (progress: any) => void): Promise<void>;
13
+ isReady(): Promise<boolean>;
14
+ getStatus(): {
15
+ dbConnected: boolean;
16
+ modelLoaded: boolean;
17
+ ready: boolean;
18
+ };
19
+ searchMemories(query: string, containerTag: string): Promise<{
20
+ success: true;
21
+ results: SearchResult[];
22
+ total: number;
23
+ timing: number;
24
+ error?: undefined;
25
+ } | {
26
+ success: false;
27
+ error: string;
28
+ results: never[];
29
+ total: number;
30
+ timing: number;
31
+ }>;
32
+ getProfile(containerTag: string, query?: string): Promise<{
33
+ success: true;
34
+ profile: ProfileData;
35
+ error?: undefined;
36
+ } | {
37
+ success: false;
38
+ error: string;
39
+ profile: null;
40
+ }>;
41
+ addMemory(content: string, containerTag: string, metadata?: {
42
+ type?: MemoryType;
43
+ source?: "manual" | "auto-capture" | "import" | "api";
44
+ tool?: string;
45
+ sessionID?: string;
46
+ reasoning?: string;
47
+ captureTimestamp?: number;
48
+ displayName?: string;
49
+ userName?: string;
50
+ userEmail?: string;
51
+ projectPath?: string;
52
+ projectName?: string;
53
+ gitRepoUrl?: string;
54
+ [key: string]: unknown;
55
+ }): Promise<{
56
+ success: true;
57
+ id: string;
58
+ error?: undefined;
59
+ } | {
60
+ success: false;
61
+ error: string;
62
+ id?: undefined;
63
+ }>;
64
+ deleteMemory(memoryId: string): Promise<{
65
+ success: boolean;
66
+ error?: undefined;
67
+ } | {
68
+ success: boolean;
69
+ error: string;
70
+ }>;
71
+ listMemories(containerTag: string, limit?: number): Promise<{
72
+ success: true;
73
+ memories: {
74
+ id: any;
75
+ summary: any;
76
+ createdAt: string;
77
+ metadata: any;
78
+ displayName: any;
79
+ userName: any;
80
+ userEmail: any;
81
+ projectPath: any;
82
+ projectName: any;
83
+ gitRepoUrl: any;
84
+ }[];
85
+ pagination: {
86
+ currentPage: number;
87
+ totalItems: number;
88
+ totalPages: number;
89
+ };
90
+ error?: undefined;
91
+ } | {
92
+ success: false;
93
+ error: string;
94
+ memories: never[];
95
+ pagination: {
96
+ currentPage: number;
97
+ totalItems: number;
98
+ totalPages: number;
99
+ };
100
+ }>;
101
+ }
102
+ export declare const memoryClient: LocalMemoryClient;
103
+ export {};
104
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/services/client.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAgB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AA2CpE,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,aAAa,CAAkB;;YAIzB,UAAU;IAkBlB,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjE,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAIjC,SAAS,IAAI;QACX,WAAW,EAAE,OAAO,CAAC;QACrB,WAAW,EAAE,OAAO,CAAC;QACrB,KAAK,EAAE,OAAO,CAAC;KAChB;IAQK,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;;;;;;;;;;;;;IA+BlD,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM;;;;;;;;;IA2C/C,SAAS,CACb,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,UAAU,CAAC;QAClB,MAAM,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,QAAQ,GAAG,KAAK,CAAC;QACtD,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB;;;;;;;;;IAuDG,YAAY,CAAC,QAAQ,EAAE,MAAM;;;;;;;IA6B7B,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,SAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDpD;AAED,eAAO,MAAM,YAAY,mBAA0B,CAAC"}