@xiaoxiamimengfb/my-opencode-mem 2.12.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.
- package/README.md +155 -0
- package/dist/config.d.ts +58 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +411 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +427 -0
- package/dist/plugin.d.ts +5 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +4 -0
- package/dist/services/ai/ai-provider-factory.d.ts +8 -0
- package/dist/services/ai/ai-provider-factory.d.ts.map +1 -0
- package/dist/services/ai/ai-provider-factory.js +28 -0
- package/dist/services/ai/opencode-provider.d.ts +30 -0
- package/dist/services/ai/opencode-provider.d.ts.map +1 -0
- package/dist/services/ai/opencode-provider.js +332 -0
- package/dist/services/ai/provider-config.d.ts +17 -0
- package/dist/services/ai/provider-config.d.ts.map +1 -0
- package/dist/services/ai/provider-config.js +14 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts +12 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts.map +1 -0
- package/dist/services/ai/providers/anthropic-messages.js +184 -0
- package/dist/services/ai/providers/base-provider.d.ts +25 -0
- package/dist/services/ai/providers/base-provider.d.ts.map +1 -0
- package/dist/services/ai/providers/base-provider.js +23 -0
- package/dist/services/ai/providers/google-gemini.d.ts +16 -0
- package/dist/services/ai/providers/google-gemini.d.ts.map +1 -0
- package/dist/services/ai/providers/google-gemini.js +228 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts +13 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts.map +1 -0
- package/dist/services/ai/providers/openai-chat-completion.js +277 -0
- package/dist/services/ai/providers/openai-responses.d.ts +14 -0
- package/dist/services/ai/providers/openai-responses.d.ts.map +1 -0
- package/dist/services/ai/providers/openai-responses.js +182 -0
- package/dist/services/ai/session/ai-session-manager.d.ts +21 -0
- package/dist/services/ai/session/ai-session-manager.d.ts.map +1 -0
- package/dist/services/ai/session/ai-session-manager.js +166 -0
- package/dist/services/ai/session/session-types.d.ts +43 -0
- package/dist/services/ai/session/session-types.d.ts.map +1 -0
- package/dist/services/ai/session/session-types.js +1 -0
- package/dist/services/ai/tools/tool-schema.d.ts +41 -0
- package/dist/services/ai/tools/tool-schema.d.ts.map +1 -0
- package/dist/services/ai/tools/tool-schema.js +24 -0
- package/dist/services/ai/validators/user-profile-validator.d.ts +13 -0
- package/dist/services/ai/validators/user-profile-validator.d.ts.map +1 -0
- package/dist/services/ai/validators/user-profile-validator.js +111 -0
- package/dist/services/api-handlers.d.ts +164 -0
- package/dist/services/api-handlers.d.ts.map +1 -0
- package/dist/services/api-handlers.js +901 -0
- package/dist/services/auto-capture.d.ts +3 -0
- package/dist/services/auto-capture.d.ts.map +1 -0
- package/dist/services/auto-capture.js +306 -0
- package/dist/services/cleanup-service.d.ts +23 -0
- package/dist/services/cleanup-service.d.ts.map +1 -0
- package/dist/services/cleanup-service.js +102 -0
- package/dist/services/client.d.ts +118 -0
- package/dist/services/client.d.ts.map +1 -0
- package/dist/services/client.js +251 -0
- package/dist/services/context.d.ts +11 -0
- package/dist/services/context.d.ts.map +1 -0
- package/dist/services/context.js +24 -0
- package/dist/services/deduplication-service.d.ts +30 -0
- package/dist/services/deduplication-service.d.ts.map +1 -0
- package/dist/services/deduplication-service.js +124 -0
- package/dist/services/embedding.d.ts +15 -0
- package/dist/services/embedding.d.ts.map +1 -0
- package/dist/services/embedding.js +106 -0
- package/dist/services/jsonc.d.ts +7 -0
- package/dist/services/jsonc.d.ts.map +1 -0
- package/dist/services/jsonc.js +76 -0
- package/dist/services/language-detector.d.ts +3 -0
- package/dist/services/language-detector.d.ts.map +1 -0
- package/dist/services/language-detector.js +16 -0
- package/dist/services/logger.d.ts +2 -0
- package/dist/services/logger.d.ts.map +1 -0
- package/dist/services/logger.js +51 -0
- package/dist/services/migration-service.d.ts +42 -0
- package/dist/services/migration-service.d.ts.map +1 -0
- package/dist/services/migration-service.js +250 -0
- package/dist/services/privacy.d.ts +3 -0
- package/dist/services/privacy.d.ts.map +1 -0
- package/dist/services/privacy.js +7 -0
- package/dist/services/secret-resolver.d.ts +2 -0
- package/dist/services/secret-resolver.d.ts.map +1 -0
- package/dist/services/secret-resolver.js +55 -0
- package/dist/services/sqlite/connection-manager.d.ts +13 -0
- package/dist/services/sqlite/connection-manager.d.ts.map +1 -0
- package/dist/services/sqlite/connection-manager.js +74 -0
- package/dist/services/sqlite/shard-manager.d.ts +23 -0
- package/dist/services/sqlite/shard-manager.d.ts.map +1 -0
- package/dist/services/sqlite/shard-manager.js +288 -0
- package/dist/services/sqlite/sqlite-bootstrap.d.ts +2 -0
- package/dist/services/sqlite/sqlite-bootstrap.d.ts.map +1 -0
- package/dist/services/sqlite/sqlite-bootstrap.js +8 -0
- package/dist/services/sqlite/types.d.ts +42 -0
- package/dist/services/sqlite/types.d.ts.map +1 -0
- package/dist/services/sqlite/types.js +1 -0
- package/dist/services/sqlite/vector-search.d.ts +29 -0
- package/dist/services/sqlite/vector-search.d.ts.map +1 -0
- package/dist/services/sqlite/vector-search.js +268 -0
- package/dist/services/tags.d.ts +24 -0
- package/dist/services/tags.d.ts.map +1 -0
- package/dist/services/tags.js +146 -0
- package/dist/services/user-memory-learning.d.ts +3 -0
- package/dist/services/user-memory-learning.d.ts.map +1 -0
- package/dist/services/user-memory-learning.js +231 -0
- package/dist/services/user-profile/profile-context.d.ts +2 -0
- package/dist/services/user-profile/profile-context.d.ts.map +1 -0
- package/dist/services/user-profile/profile-context.js +40 -0
- package/dist/services/user-profile/profile-utils.d.ts +3 -0
- package/dist/services/user-profile/profile-utils.d.ts.map +1 -0
- package/dist/services/user-profile/profile-utils.js +45 -0
- package/dist/services/user-profile/types.d.ts +46 -0
- package/dist/services/user-profile/types.d.ts.map +1 -0
- package/dist/services/user-profile/types.js +1 -0
- package/dist/services/user-profile/user-profile-manager.d.ts +23 -0
- package/dist/services/user-profile/user-profile-manager.d.ts.map +1 -0
- package/dist/services/user-profile/user-profile-manager.js +292 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts +41 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts.map +1 -0
- package/dist/services/user-prompt/user-prompt-manager.js +192 -0
- package/dist/services/vector-backends/backend-factory.d.ts +3 -0
- package/dist/services/vector-backends/backend-factory.d.ts.map +1 -0
- package/dist/services/vector-backends/backend-factory.js +104 -0
- package/dist/services/vector-backends/exact-scan-backend.d.ts +39 -0
- package/dist/services/vector-backends/exact-scan-backend.d.ts.map +1 -0
- package/dist/services/vector-backends/exact-scan-backend.js +63 -0
- package/dist/services/vector-backends/types.d.ts +51 -0
- package/dist/services/vector-backends/types.d.ts.map +1 -0
- package/dist/services/vector-backends/types.js +1 -0
- package/dist/services/vector-backends/usearch-backend.d.ts +47 -0
- package/dist/services/vector-backends/usearch-backend.d.ts.map +1 -0
- package/dist/services/vector-backends/usearch-backend.js +174 -0
- package/dist/services/web-server-worker.d.ts +2 -0
- package/dist/services/web-server-worker.d.ts.map +1 -0
- package/dist/services/web-server-worker.js +283 -0
- package/dist/services/web-server.d.ts +31 -0
- package/dist/services/web-server.d.ts.map +1 -0
- package/dist/services/web-server.js +356 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/web/app.d.ts +2 -0
- package/dist/web/app.d.ts.map +1 -0
- package/dist/web/app.js +1194 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/i18n.d.ts +2 -0
- package/dist/web/i18n.d.ts.map +1 -0
- package/dist/web/i18n.js +265 -0
- package/dist/web/index.html +284 -0
- package/dist/web/styles.css +1631 -0
- package/package.json +71 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-capture.d.ts","sourceRoot":"","sources":["../../src/services/auto-capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAgBvD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CA2Ff"}
|
|
@@ -0,0 +1,306 @@
|
|
|
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
|
+
import { userPromptManager } from "./user-prompt/user-prompt-manager.js";
|
|
6
|
+
const MAX_TOOL_INPUT_LENGTH = 100;
|
|
7
|
+
let isCaptureRunning = false;
|
|
8
|
+
export async function performAutoCapture(ctx, sessionID, directory) {
|
|
9
|
+
if (isCaptureRunning)
|
|
10
|
+
return;
|
|
11
|
+
isCaptureRunning = true;
|
|
12
|
+
try {
|
|
13
|
+
const prompt = userPromptManager.getLastUncapturedPrompt(sessionID);
|
|
14
|
+
if (!prompt) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (!userPromptManager.claimPrompt(prompt.id)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (!ctx.client) {
|
|
21
|
+
throw new Error("Client not available");
|
|
22
|
+
}
|
|
23
|
+
const response = await ctx.client.session.messages({
|
|
24
|
+
path: { id: sessionID },
|
|
25
|
+
});
|
|
26
|
+
if (!response.data) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const messages = response.data;
|
|
30
|
+
const promptIndex = messages.findIndex((m) => m.info?.id === prompt.messageId);
|
|
31
|
+
if (promptIndex === -1) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const aiMessages = messages.slice(promptIndex + 1);
|
|
35
|
+
if (aiMessages.length === 0) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const { textResponses, toolCalls } = extractAIContent(aiMessages);
|
|
39
|
+
if (textResponses.length === 0 && toolCalls.length === 0) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const tags = getTags(directory);
|
|
43
|
+
const latestMemory = await getLatestProjectMemory(tags.project.tag);
|
|
44
|
+
const context = buildMarkdownContext(prompt.content, textResponses, toolCalls, latestMemory);
|
|
45
|
+
const summaryResult = await generateSummary(context, sessionID, prompt.content);
|
|
46
|
+
if (!summaryResult || summaryResult.type === "skip") {
|
|
47
|
+
userPromptManager.deletePrompt(prompt.id);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const result = await memoryClient.addMemory(summaryResult.summary, tags.project.tag, {
|
|
51
|
+
source: "auto-capture",
|
|
52
|
+
type: summaryResult.type,
|
|
53
|
+
tags: summaryResult.tags,
|
|
54
|
+
sessionID,
|
|
55
|
+
promptId: prompt.id,
|
|
56
|
+
captureTimestamp: Date.now(),
|
|
57
|
+
displayName: tags.project.displayName,
|
|
58
|
+
userName: tags.project.userName,
|
|
59
|
+
userEmail: tags.project.userEmail,
|
|
60
|
+
projectPath: tags.project.projectPath,
|
|
61
|
+
projectName: tags.project.projectName,
|
|
62
|
+
gitRepoUrl: tags.project.gitRepoUrl,
|
|
63
|
+
});
|
|
64
|
+
if (result.success) {
|
|
65
|
+
userPromptManager.linkMemoryToPrompt(prompt.id, result.id);
|
|
66
|
+
userPromptManager.markAsCaptured(prompt.id);
|
|
67
|
+
if (CONFIG.showAutoCaptureToasts) {
|
|
68
|
+
await ctx.client?.tui
|
|
69
|
+
.showToast({
|
|
70
|
+
body: {
|
|
71
|
+
title: "Memory Captured",
|
|
72
|
+
message: "Project memory saved from conversation",
|
|
73
|
+
variant: "success",
|
|
74
|
+
duration: 3000,
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
.catch(() => { });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
isCaptureRunning = false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function extractAIContent(messages) {
|
|
86
|
+
const textResponses = [];
|
|
87
|
+
const toolCalls = [];
|
|
88
|
+
for (const msg of messages) {
|
|
89
|
+
if (msg.info?.role !== "assistant")
|
|
90
|
+
continue;
|
|
91
|
+
if (!msg.parts || !Array.isArray(msg.parts))
|
|
92
|
+
continue;
|
|
93
|
+
const textParts = msg.parts.filter((p) => p.type === "text" && p.text);
|
|
94
|
+
if (textParts.length > 0) {
|
|
95
|
+
const text = textParts.map((p) => p.text).join("\n");
|
|
96
|
+
if (text.trim()) {
|
|
97
|
+
textResponses.push(text.trim());
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const toolParts = msg.parts.filter((p) => p.type === "tool");
|
|
101
|
+
for (const tool of toolParts) {
|
|
102
|
+
const name = tool.tool || "unknown";
|
|
103
|
+
let input = "";
|
|
104
|
+
if (tool.state?.input) {
|
|
105
|
+
const inputObj = tool.state.input;
|
|
106
|
+
if (typeof inputObj === "string") {
|
|
107
|
+
input = inputObj;
|
|
108
|
+
}
|
|
109
|
+
else if (typeof inputObj === "object") {
|
|
110
|
+
const params = [];
|
|
111
|
+
for (const [key, value] of Object.entries(inputObj)) {
|
|
112
|
+
params.push(`${key}: ${JSON.stringify(value)}`);
|
|
113
|
+
}
|
|
114
|
+
input = params.join(", ");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (input.length > MAX_TOOL_INPUT_LENGTH) {
|
|
118
|
+
input = input.substring(0, MAX_TOOL_INPUT_LENGTH) + "...";
|
|
119
|
+
}
|
|
120
|
+
toolCalls.push({ name, input });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { textResponses, toolCalls };
|
|
124
|
+
}
|
|
125
|
+
async function getLatestProjectMemory(containerTag) {
|
|
126
|
+
try {
|
|
127
|
+
const result = await memoryClient.listMemories(containerTag, 1);
|
|
128
|
+
if (!result.success || result.memories.length === 0) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const latest = result.memories[0];
|
|
132
|
+
if (!latest) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const content = latest.summary;
|
|
136
|
+
if (content.length <= 500) {
|
|
137
|
+
return content;
|
|
138
|
+
}
|
|
139
|
+
return content.substring(0, 500) + "...";
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function buildMarkdownContext(userPrompt, textResponses, toolCalls, latestMemory) {
|
|
146
|
+
const sections = [];
|
|
147
|
+
if (latestMemory) {
|
|
148
|
+
sections.push(`## Previous Memory Context`);
|
|
149
|
+
sections.push(`---`);
|
|
150
|
+
sections.push(latestMemory);
|
|
151
|
+
sections.push(`---\n`);
|
|
152
|
+
}
|
|
153
|
+
sections.push(`## User Request`);
|
|
154
|
+
sections.push(`---`);
|
|
155
|
+
sections.push(userPrompt);
|
|
156
|
+
sections.push(`---\n`);
|
|
157
|
+
if (textResponses.length > 0) {
|
|
158
|
+
sections.push(`## AI Response`);
|
|
159
|
+
sections.push(`---`);
|
|
160
|
+
sections.push(textResponses.join("\n\n"));
|
|
161
|
+
sections.push(`---\n`);
|
|
162
|
+
}
|
|
163
|
+
if (toolCalls.length > 0) {
|
|
164
|
+
sections.push(`## Tools Used`);
|
|
165
|
+
sections.push(`---`);
|
|
166
|
+
for (const tool of toolCalls) {
|
|
167
|
+
if (tool.input) {
|
|
168
|
+
sections.push(`- ${tool.name}(${tool.input})`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
sections.push(`- ${tool.name}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
sections.push(`---\n`);
|
|
175
|
+
}
|
|
176
|
+
return sections.join("\n");
|
|
177
|
+
}
|
|
178
|
+
async function generateSummary(context, sessionID, userPrompt) {
|
|
179
|
+
// Opencode provider path (when opencodeProvider + opencodeModel configured)
|
|
180
|
+
if (CONFIG.opencodeProvider && CONFIG.opencodeModel) {
|
|
181
|
+
if (CONFIG.memoryModel) {
|
|
182
|
+
log("opencodeProvider takes precedence over memoryModel for auto-capture");
|
|
183
|
+
}
|
|
184
|
+
const { isProviderConnected, getStatePath, generateStructuredOutput } = await import("./ai/opencode-provider.js");
|
|
185
|
+
if (!isProviderConnected(CONFIG.opencodeProvider)) {
|
|
186
|
+
throw new Error(`opencode provider '${CONFIG.opencodeProvider}' is not connected. Check your opencode provider configuration.`);
|
|
187
|
+
}
|
|
188
|
+
const { detectLanguage, getLanguageName } = await import("./language-detector.js");
|
|
189
|
+
const targetLang = CONFIG.autoCaptureLanguage === "auto" || !CONFIG.autoCaptureLanguage
|
|
190
|
+
? detectLanguage(userPrompt)
|
|
191
|
+
: CONFIG.autoCaptureLanguage;
|
|
192
|
+
const langName = getLanguageName(targetLang);
|
|
193
|
+
const systemPrompt = `You are a technical memory recorder for a software development project.
|
|
194
|
+
|
|
195
|
+
RULES:
|
|
196
|
+
1. ONLY capture technical work (code, bugs, features, architecture, config)
|
|
197
|
+
2. SKIP non-technical by returning type="skip"
|
|
198
|
+
3. NO meta-commentary or behavior analysis
|
|
199
|
+
4. Include specific file names, functions, technical details
|
|
200
|
+
5. Generate 2-4 technical tags (e.g., "react", "auth", "bug-fix")
|
|
201
|
+
6. You MUST write the summary in ${langName}.
|
|
202
|
+
|
|
203
|
+
FORMAT:
|
|
204
|
+
## Request
|
|
205
|
+
[1-2 sentences: what was requested, in ${langName}]
|
|
206
|
+
|
|
207
|
+
## Outcome
|
|
208
|
+
[1-2 sentences: what was done, include files/functions, in ${langName}]
|
|
209
|
+
|
|
210
|
+
SKIP if: greetings, casual chat, no code/decisions made
|
|
211
|
+
CAPTURE if: code changed, bug fixed, feature added, decision made`;
|
|
212
|
+
const aiPrompt = `${context}
|
|
213
|
+
|
|
214
|
+
Analyze this conversation. If it contains technical work (code, bugs, features, decisions), create a concise summary and relevant tags. If it's non-technical (greetings, casual chat, incomplete requests), return type="skip" with empty summary.`;
|
|
215
|
+
const { z } = await import("zod");
|
|
216
|
+
const schema = z.object({
|
|
217
|
+
summary: z.string(),
|
|
218
|
+
type: z.string(),
|
|
219
|
+
tags: z.array(z.string()),
|
|
220
|
+
});
|
|
221
|
+
const result = await generateStructuredOutput({
|
|
222
|
+
providerName: CONFIG.opencodeProvider,
|
|
223
|
+
modelId: CONFIG.opencodeModel,
|
|
224
|
+
statePath: getStatePath(),
|
|
225
|
+
systemPrompt,
|
|
226
|
+
userPrompt: aiPrompt,
|
|
227
|
+
schema,
|
|
228
|
+
temperature: CONFIG.memoryTemperature === false ? undefined : (CONFIG.memoryTemperature ?? 0.3),
|
|
229
|
+
});
|
|
230
|
+
return {
|
|
231
|
+
summary: result.summary,
|
|
232
|
+
type: result.type,
|
|
233
|
+
tags: (result.tags || []).map((t) => t.toLowerCase().trim()),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
// Existing manual config path
|
|
237
|
+
if (!CONFIG.memoryModel || !CONFIG.memoryApiUrl) {
|
|
238
|
+
throw new Error("External API not configured for auto-capture");
|
|
239
|
+
}
|
|
240
|
+
const { AIProviderFactory } = await import("./ai/ai-provider-factory.js");
|
|
241
|
+
const { buildMemoryProviderConfig } = await import("./ai/provider-config.js");
|
|
242
|
+
const { detectLanguage, getLanguageName } = await import("./language-detector.js");
|
|
243
|
+
const providerConfig = buildMemoryProviderConfig(CONFIG);
|
|
244
|
+
const provider = AIProviderFactory.createProvider(CONFIG.memoryProvider, providerConfig);
|
|
245
|
+
const targetLang = CONFIG.autoCaptureLanguage === "auto" || !CONFIG.autoCaptureLanguage
|
|
246
|
+
? detectLanguage(userPrompt)
|
|
247
|
+
: CONFIG.autoCaptureLanguage;
|
|
248
|
+
const langName = getLanguageName(targetLang);
|
|
249
|
+
const systemPrompt = `You are a technical memory recorder for a software development project.
|
|
250
|
+
|
|
251
|
+
RULES:
|
|
252
|
+
1. ONLY capture technical work (code, bugs, features, architecture, config)
|
|
253
|
+
2. SKIP non-technical by returning type="skip"
|
|
254
|
+
3. NO meta-commentary or behavior analysis
|
|
255
|
+
4. Include specific file names, functions, technical details
|
|
256
|
+
5. Generate 2-4 technical tags (e.g., "react", "auth", "bug-fix")
|
|
257
|
+
6. You MUST write the summary in ${langName}.
|
|
258
|
+
|
|
259
|
+
FORMAT:
|
|
260
|
+
## Request
|
|
261
|
+
[1-2 sentences: what was requested, in ${langName}]
|
|
262
|
+
|
|
263
|
+
## Outcome
|
|
264
|
+
[1-2 sentences: what was done, include files/functions, in ${langName}]
|
|
265
|
+
|
|
266
|
+
SKIP if: greetings, casual chat, no code/decisions made
|
|
267
|
+
CAPTURE if: code changed, bug fixed, feature added, decision made`;
|
|
268
|
+
const aiPrompt = `${context}
|
|
269
|
+
|
|
270
|
+
Analyze this conversation. If it contains technical work (code, bugs, features, decisions), create a concise summary and relevant tags. If it's non-technical (greetings, casual chat, incomplete requests), return type="skip" with empty summary.`;
|
|
271
|
+
const toolSchema = {
|
|
272
|
+
type: "function",
|
|
273
|
+
function: {
|
|
274
|
+
name: "save_memory",
|
|
275
|
+
description: "Save the conversation summary as a memory",
|
|
276
|
+
parameters: {
|
|
277
|
+
type: "object",
|
|
278
|
+
properties: {
|
|
279
|
+
summary: {
|
|
280
|
+
type: "string",
|
|
281
|
+
description: "Markdown-formatted summary of the conversation",
|
|
282
|
+
},
|
|
283
|
+
type: {
|
|
284
|
+
type: "string",
|
|
285
|
+
description: "Type of memory: 'skip' for non-technical conversations, or technical type (feature, bug-fix, refactor, analysis, configuration, discussion, other)",
|
|
286
|
+
},
|
|
287
|
+
tags: {
|
|
288
|
+
type: "array",
|
|
289
|
+
items: { type: "string" },
|
|
290
|
+
description: "List of 2-4 technical tags related to the memory",
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
required: ["summary", "type", "tags"],
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
const result = await provider.executeToolCall(systemPrompt, aiPrompt, toolSchema, sessionID);
|
|
298
|
+
if (!result.success || !result.data) {
|
|
299
|
+
throw new Error(result.error || "Failed to generate summary");
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
summary: result.data.summary,
|
|
303
|
+
type: result.data.type,
|
|
304
|
+
tags: (result.data.tags || []).map((t) => t.toLowerCase().trim()),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface CleanupResult {
|
|
2
|
+
deletedCount: number;
|
|
3
|
+
userCount: number;
|
|
4
|
+
projectCount: number;
|
|
5
|
+
promptsDeleted: number;
|
|
6
|
+
linkedMemoriesDeleted: number;
|
|
7
|
+
pinnedMemoriesSkipped: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class CleanupService {
|
|
10
|
+
private lastCleanupTime;
|
|
11
|
+
private isRunning;
|
|
12
|
+
shouldRunCleanup(): Promise<boolean>;
|
|
13
|
+
runCleanup(): Promise<CleanupResult>;
|
|
14
|
+
getStatus(): {
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
retentionDays: number;
|
|
17
|
+
lastCleanupTime: number;
|
|
18
|
+
isRunning: boolean;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export declare const cleanupService: CleanupService;
|
|
22
|
+
export {};
|
|
23
|
+
//# 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":"AAOA,UAAU,aAAa;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,qBAAqB,EAAE,MAAM,CAAC;CAC/B;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;IAsF1C,SAAS;;;;;;CAQV;AAED,eAAO,MAAM,cAAc,gBAAuB,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
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
|
+
import { userPromptManager } from "./user-prompt/user-prompt-manager.js";
|
|
7
|
+
export class CleanupService {
|
|
8
|
+
lastCleanupTime = 0;
|
|
9
|
+
isRunning = false;
|
|
10
|
+
async shouldRunCleanup() {
|
|
11
|
+
if (!CONFIG.autoCleanupEnabled)
|
|
12
|
+
return false;
|
|
13
|
+
if (this.isRunning)
|
|
14
|
+
return false;
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
const oneDayMs = 24 * 60 * 60 * 1000;
|
|
17
|
+
if (now - this.lastCleanupTime < oneDayMs) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
async runCleanup() {
|
|
23
|
+
if (this.isRunning) {
|
|
24
|
+
throw new Error("Cleanup already running");
|
|
25
|
+
}
|
|
26
|
+
this.isRunning = true;
|
|
27
|
+
this.lastCleanupTime = Date.now();
|
|
28
|
+
try {
|
|
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
|
+
const pinnedMemoryIds = new Set();
|
|
34
|
+
for (const shard of allShards) {
|
|
35
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
36
|
+
const pinned = db.prepare(`SELECT id FROM memories WHERE is_pinned = 1`).all();
|
|
37
|
+
pinned.forEach((row) => pinnedMemoryIds.add(row.id));
|
|
38
|
+
}
|
|
39
|
+
const promptCleanupResult = userPromptManager.deleteOldPrompts(cutoffTime);
|
|
40
|
+
const linkedMemoryIds = new Set(promptCleanupResult.linkedMemoryIds);
|
|
41
|
+
const protectedMemoryIds = new Set([...pinnedMemoryIds, ...linkedMemoryIds]);
|
|
42
|
+
let totalDeleted = 0;
|
|
43
|
+
let userDeleted = 0;
|
|
44
|
+
let projectDeleted = 0;
|
|
45
|
+
let linkedMemoriesDeleted = 0;
|
|
46
|
+
let pinnedSkipped = 0;
|
|
47
|
+
for (const shard of allShards) {
|
|
48
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
49
|
+
const oldMemories = db
|
|
50
|
+
.prepare(`
|
|
51
|
+
SELECT id, container_tag, is_pinned FROM memories
|
|
52
|
+
WHERE updated_at < ?
|
|
53
|
+
`)
|
|
54
|
+
.all(cutoffTime);
|
|
55
|
+
for (const memory of oldMemories) {
|
|
56
|
+
try {
|
|
57
|
+
if (memory.is_pinned === 1) {
|
|
58
|
+
pinnedSkipped++;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (protectedMemoryIds.has(memory.id)) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
await vectorSearch.deleteVector(db, memory.id, shard);
|
|
65
|
+
shardManager.decrementVectorCount(shard.id);
|
|
66
|
+
totalDeleted++;
|
|
67
|
+
if (memory.container_tag?.includes("_user_")) {
|
|
68
|
+
userDeleted++;
|
|
69
|
+
}
|
|
70
|
+
else if (memory.container_tag?.includes("_project_")) {
|
|
71
|
+
projectDeleted++;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
log("Cleanup: delete error", { memoryId: memory.id, error: String(error) });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const promptsDeleted = promptCleanupResult.deleted - linkedMemoryIds.size;
|
|
80
|
+
return {
|
|
81
|
+
deletedCount: totalDeleted,
|
|
82
|
+
userCount: userDeleted,
|
|
83
|
+
projectCount: projectDeleted,
|
|
84
|
+
promptsDeleted,
|
|
85
|
+
linkedMemoriesDeleted,
|
|
86
|
+
pinnedMemoriesSkipped: pinnedSkipped,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
this.isRunning = false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
getStatus() {
|
|
94
|
+
return {
|
|
95
|
+
enabled: CONFIG.autoCleanupEnabled,
|
|
96
|
+
retentionDays: CONFIG.autoCleanupRetentionDays,
|
|
97
|
+
lastCleanupTime: this.lastCleanupTime,
|
|
98
|
+
isRunning: this.isRunning,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export const cleanupService = new CleanupService();
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { MemoryType } from "../types/index.js";
|
|
2
|
+
export declare class LocalMemoryClient {
|
|
3
|
+
private initPromise;
|
|
4
|
+
private isInitialized;
|
|
5
|
+
constructor();
|
|
6
|
+
private initialize;
|
|
7
|
+
warmup(progressCallback?: (progress: any) => void): Promise<void>;
|
|
8
|
+
isReady(): Promise<boolean>;
|
|
9
|
+
getStatus(): {
|
|
10
|
+
dbConnected: boolean;
|
|
11
|
+
modelLoaded: boolean;
|
|
12
|
+
ready: boolean;
|
|
13
|
+
};
|
|
14
|
+
close(): void;
|
|
15
|
+
searchMemories(query: string, containerTag: string): Promise<{
|
|
16
|
+
success: true;
|
|
17
|
+
results: import("./sqlite/types.js").SearchResult[];
|
|
18
|
+
total: number;
|
|
19
|
+
timing: number;
|
|
20
|
+
error?: undefined;
|
|
21
|
+
} | {
|
|
22
|
+
success: false;
|
|
23
|
+
error: string;
|
|
24
|
+
results: never[];
|
|
25
|
+
total: number;
|
|
26
|
+
timing: number;
|
|
27
|
+
}>;
|
|
28
|
+
addMemory(content: string, containerTag: string, metadata?: {
|
|
29
|
+
type?: MemoryType;
|
|
30
|
+
source?: "manual" | "auto-capture" | "import" | "api";
|
|
31
|
+
tags?: string[];
|
|
32
|
+
tool?: string;
|
|
33
|
+
sessionID?: string;
|
|
34
|
+
reasoning?: string;
|
|
35
|
+
captureTimestamp?: number;
|
|
36
|
+
displayName?: string;
|
|
37
|
+
userName?: string;
|
|
38
|
+
userEmail?: string;
|
|
39
|
+
projectPath?: string;
|
|
40
|
+
projectName?: string;
|
|
41
|
+
gitRepoUrl?: string;
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
}): Promise<{
|
|
44
|
+
success: true;
|
|
45
|
+
id: string;
|
|
46
|
+
error?: undefined;
|
|
47
|
+
} | {
|
|
48
|
+
success: false;
|
|
49
|
+
error: string;
|
|
50
|
+
id?: undefined;
|
|
51
|
+
}>;
|
|
52
|
+
deleteMemory(memoryId: string): Promise<{
|
|
53
|
+
success: boolean;
|
|
54
|
+
error?: undefined;
|
|
55
|
+
} | {
|
|
56
|
+
success: boolean;
|
|
57
|
+
error: string;
|
|
58
|
+
}>;
|
|
59
|
+
listMemories(containerTag: string, limit?: number): Promise<{
|
|
60
|
+
success: true;
|
|
61
|
+
memories: {
|
|
62
|
+
id: any;
|
|
63
|
+
summary: any;
|
|
64
|
+
createdAt: string;
|
|
65
|
+
metadata: any;
|
|
66
|
+
displayName: any;
|
|
67
|
+
userName: any;
|
|
68
|
+
userEmail: any;
|
|
69
|
+
projectPath: any;
|
|
70
|
+
projectName: any;
|
|
71
|
+
gitRepoUrl: any;
|
|
72
|
+
}[];
|
|
73
|
+
pagination: {
|
|
74
|
+
currentPage: number;
|
|
75
|
+
totalItems: number;
|
|
76
|
+
totalPages: number;
|
|
77
|
+
};
|
|
78
|
+
error?: undefined;
|
|
79
|
+
} | {
|
|
80
|
+
success: false;
|
|
81
|
+
error: string;
|
|
82
|
+
memories: never[];
|
|
83
|
+
pagination: {
|
|
84
|
+
currentPage: number;
|
|
85
|
+
totalItems: number;
|
|
86
|
+
totalPages: number;
|
|
87
|
+
};
|
|
88
|
+
}>;
|
|
89
|
+
searchMemoriesBySessionID(sessionID: string, containerTag: string, limit?: number): Promise<{
|
|
90
|
+
success: true;
|
|
91
|
+
results: {
|
|
92
|
+
id: any;
|
|
93
|
+
memory: any;
|
|
94
|
+
similarity: number;
|
|
95
|
+
tags: any;
|
|
96
|
+
metadata: any;
|
|
97
|
+
containerTag: any;
|
|
98
|
+
displayName: any;
|
|
99
|
+
userName: any;
|
|
100
|
+
userEmail: any;
|
|
101
|
+
projectPath: any;
|
|
102
|
+
projectName: any;
|
|
103
|
+
gitRepoUrl: any;
|
|
104
|
+
createdAt: any;
|
|
105
|
+
}[];
|
|
106
|
+
total: number;
|
|
107
|
+
timing: number;
|
|
108
|
+
error?: undefined;
|
|
109
|
+
} | {
|
|
110
|
+
success: false;
|
|
111
|
+
error: string;
|
|
112
|
+
results: never[];
|
|
113
|
+
total: number;
|
|
114
|
+
timing: number;
|
|
115
|
+
}>;
|
|
116
|
+
}
|
|
117
|
+
export declare const memoryClient: LocalMemoryClient;
|
|
118
|
+
//# 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;AA4CpD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,aAAa,CAAkB;;YAIzB,UAAU;IAiBlB,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;IAQD,KAAK,IAAI,IAAI;IAIP,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;;;;;;;;;;;;;IA6BlD,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,EAAE,CAAC;QAChB,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;;;;;;;;;IA+DG,YAAY,CAAC,QAAQ,EAAE,MAAM;;;;;;;IA2B7B,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,SAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAuD7C,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4C5F;AAED,eAAO,MAAM,YAAY,mBAA0B,CAAC"}
|