@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,901 @@
|
|
|
1
|
+
import { embeddingService } from "./embedding.js";
|
|
2
|
+
import { shardManager } from "./sqlite/shard-manager.js";
|
|
3
|
+
import { vectorSearch } from "./sqlite/vector-search.js";
|
|
4
|
+
import { connectionManager } from "./sqlite/connection-manager.js";
|
|
5
|
+
import { log } from "./logger.js";
|
|
6
|
+
import { CONFIG } from "../config.js";
|
|
7
|
+
import { userPromptManager } from "./user-prompt/user-prompt-manager.js";
|
|
8
|
+
function safeToISOString(timestamp) {
|
|
9
|
+
try {
|
|
10
|
+
if (timestamp === null || timestamp === undefined) {
|
|
11
|
+
return new Date().toISOString();
|
|
12
|
+
}
|
|
13
|
+
const numValue = typeof timestamp === "bigint" ? Number(timestamp) : Number(timestamp);
|
|
14
|
+
if (isNaN(numValue) || numValue < 0) {
|
|
15
|
+
return new Date().toISOString();
|
|
16
|
+
}
|
|
17
|
+
return new Date(numValue).toISOString();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return new Date().toISOString();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function safeJSONParse(jsonString) {
|
|
24
|
+
if (!jsonString || typeof jsonString !== "string") {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(jsonString);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function extractScopeFromTag(tag) {
|
|
35
|
+
const parts = tag.split("_");
|
|
36
|
+
if (parts.length >= 3) {
|
|
37
|
+
const hash = parts.slice(2).join("_");
|
|
38
|
+
return { scope: "project", hash };
|
|
39
|
+
}
|
|
40
|
+
return { scope: "project", hash: tag };
|
|
41
|
+
}
|
|
42
|
+
function getProjectPathFromTag(tag) {
|
|
43
|
+
const projectShards = shardManager.getAllShards("project", "");
|
|
44
|
+
for (const shard of projectShards) {
|
|
45
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
46
|
+
const tags = vectorSearch.getDistinctTags(db);
|
|
47
|
+
for (const t of tags) {
|
|
48
|
+
if (t.container_tag === tag && t.project_path) {
|
|
49
|
+
return t.project_path;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
export async function handleListTags() {
|
|
56
|
+
try {
|
|
57
|
+
await embeddingService.warmup();
|
|
58
|
+
const projectShards = shardManager.getAllShards("project", "");
|
|
59
|
+
const tagsMap = new Map();
|
|
60
|
+
for (const shard of projectShards) {
|
|
61
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
62
|
+
const tags = vectorSearch.getDistinctTags(db);
|
|
63
|
+
for (const t of tags) {
|
|
64
|
+
if (t.container_tag && !tagsMap.has(t.container_tag)) {
|
|
65
|
+
tagsMap.set(t.container_tag, {
|
|
66
|
+
tag: t.container_tag,
|
|
67
|
+
displayName: t.display_name,
|
|
68
|
+
userName: t.user_name,
|
|
69
|
+
userEmail: t.user_email,
|
|
70
|
+
projectPath: t.project_path,
|
|
71
|
+
projectName: t.project_name,
|
|
72
|
+
gitRepoUrl: t.git_repo_url,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const projectTags = [];
|
|
78
|
+
for (const tagInfo of tagsMap.values()) {
|
|
79
|
+
if (tagInfo.tag.includes("_project_")) {
|
|
80
|
+
projectTags.push(tagInfo);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { success: true, data: { project: projectTags } };
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
log("handleListTags: error", { error: String(error) });
|
|
87
|
+
return { success: false, error: String(error) };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export async function handleListMemories(tag, page = 1, pageSize = 20, includePrompts = true) {
|
|
91
|
+
try {
|
|
92
|
+
await embeddingService.warmup();
|
|
93
|
+
let allMemories = [];
|
|
94
|
+
if (tag) {
|
|
95
|
+
const { scope: tagScope, hash } = extractScopeFromTag(tag);
|
|
96
|
+
const shards = shardManager.getAllShards(tagScope, hash);
|
|
97
|
+
for (const shard of shards) {
|
|
98
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
99
|
+
const memories = vectorSearch.listMemories(db, tag, 10000);
|
|
100
|
+
allMemories.push(...memories);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const shards = shardManager.getAllShards("project", "");
|
|
105
|
+
for (const shard of shards) {
|
|
106
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
107
|
+
const memories = vectorSearch.getAllMemories(db);
|
|
108
|
+
allMemories.push(...memories.filter((m) => m.container_tag?.includes(`_project_`)));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const memoriesWithType = allMemories.map((r) => {
|
|
112
|
+
const metadata = safeJSONParse(r.metadata);
|
|
113
|
+
return {
|
|
114
|
+
type: "memory",
|
|
115
|
+
id: r.id,
|
|
116
|
+
content: r.content,
|
|
117
|
+
memoryType: r.type,
|
|
118
|
+
tags: r.tags ? r.tags.split(",").map((t) => t.trim()) : [],
|
|
119
|
+
createdAt: Number(r.created_at),
|
|
120
|
+
updatedAt: r.updated_at ? Number(r.updated_at) : undefined,
|
|
121
|
+
metadata,
|
|
122
|
+
linkedPromptId: metadata?.promptId,
|
|
123
|
+
displayName: r.display_name,
|
|
124
|
+
userName: r.user_name,
|
|
125
|
+
userEmail: r.user_email,
|
|
126
|
+
projectPath: r.project_path,
|
|
127
|
+
projectName: r.project_name,
|
|
128
|
+
gitRepoUrl: r.git_repo_url,
|
|
129
|
+
isPinned: r.is_pinned === 1,
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
let timeline = memoriesWithType;
|
|
133
|
+
if (includePrompts) {
|
|
134
|
+
const projectPath = tag ? getProjectPathFromTag(tag) : undefined;
|
|
135
|
+
const prompts = userPromptManager.getCapturedPrompts(projectPath);
|
|
136
|
+
const promptsWithType = prompts.map((p) => ({
|
|
137
|
+
type: "prompt",
|
|
138
|
+
id: p.id,
|
|
139
|
+
sessionId: p.sessionId,
|
|
140
|
+
content: p.content,
|
|
141
|
+
createdAt: p.createdAt,
|
|
142
|
+
projectPath: p.projectPath,
|
|
143
|
+
linkedMemoryId: p.linkedMemoryId,
|
|
144
|
+
}));
|
|
145
|
+
timeline = [...memoriesWithType, ...promptsWithType];
|
|
146
|
+
}
|
|
147
|
+
const linkedPairs = new Map();
|
|
148
|
+
const standalone = [];
|
|
149
|
+
for (const item of timeline) {
|
|
150
|
+
if (item.type === "memory" && item.linkedPromptId) {
|
|
151
|
+
if (!linkedPairs.has(item.linkedPromptId)) {
|
|
152
|
+
linkedPairs.set(item.linkedPromptId, { memory: item, prompt: null });
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
linkedPairs.get(item.linkedPromptId).memory = item;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else if (item.type === "prompt" && item.linkedMemoryId) {
|
|
159
|
+
if (!linkedPairs.has(item.id)) {
|
|
160
|
+
linkedPairs.set(item.id, { memory: null, prompt: item });
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
linkedPairs.get(item.id).prompt = item;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
standalone.push(item);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const sortedTimeline = [];
|
|
171
|
+
const pairs = Array.from(linkedPairs.values())
|
|
172
|
+
.filter((p) => p.memory && p.prompt)
|
|
173
|
+
.sort((a, b) => b.memory.createdAt - a.memory.createdAt);
|
|
174
|
+
for (const pair of pairs) {
|
|
175
|
+
sortedTimeline.push(pair.memory);
|
|
176
|
+
sortedTimeline.push(pair.prompt);
|
|
177
|
+
}
|
|
178
|
+
standalone.sort((a, b) => b.createdAt - a.createdAt);
|
|
179
|
+
sortedTimeline.push(...standalone);
|
|
180
|
+
timeline = sortedTimeline;
|
|
181
|
+
const total = timeline.length;
|
|
182
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
183
|
+
const offset = (page - 1) * pageSize;
|
|
184
|
+
const paginatedResults = timeline.slice(offset, offset + pageSize);
|
|
185
|
+
const items = paginatedResults.map((item) => {
|
|
186
|
+
if (item.type === "memory") {
|
|
187
|
+
return {
|
|
188
|
+
type: "memory",
|
|
189
|
+
id: item.id,
|
|
190
|
+
content: item.content,
|
|
191
|
+
memoryType: item.memoryType,
|
|
192
|
+
tags: item.tags,
|
|
193
|
+
createdAt: safeToISOString(item.createdAt),
|
|
194
|
+
updatedAt: item.updatedAt ? safeToISOString(item.updatedAt) : undefined,
|
|
195
|
+
metadata: item.metadata,
|
|
196
|
+
linkedPromptId: item.linkedPromptId,
|
|
197
|
+
displayName: item.displayName,
|
|
198
|
+
userName: item.userName,
|
|
199
|
+
userEmail: item.userEmail,
|
|
200
|
+
projectPath: item.projectPath,
|
|
201
|
+
projectName: item.projectName,
|
|
202
|
+
gitRepoUrl: item.gitRepoUrl,
|
|
203
|
+
isPinned: item.isPinned,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
return {
|
|
208
|
+
type: "prompt",
|
|
209
|
+
id: item.id,
|
|
210
|
+
sessionId: item.sessionId,
|
|
211
|
+
content: item.content,
|
|
212
|
+
createdAt: safeToISOString(item.createdAt),
|
|
213
|
+
projectPath: item.projectPath,
|
|
214
|
+
linkedMemoryId: item.linkedMemoryId,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
return { success: true, data: { items, total, page, pageSize, totalPages } };
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
log("handleListMemories: error", { error: String(error) });
|
|
222
|
+
return { success: false, error: String(error) };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
export async function handleAddMemory(data) {
|
|
226
|
+
try {
|
|
227
|
+
if (!data.content || !data.containerTag) {
|
|
228
|
+
return { success: false, error: "content and containerTag are required" };
|
|
229
|
+
}
|
|
230
|
+
await embeddingService.warmup();
|
|
231
|
+
const tags = (data.tags || []).map((t) => t.trim().toLowerCase());
|
|
232
|
+
const embeddingInput = tags.length > 0 ? `${data.content}\nTags: ${tags.join(", ")}` : data.content;
|
|
233
|
+
const vector = await embeddingService.embedWithTimeout(embeddingInput);
|
|
234
|
+
let tagsVector = undefined;
|
|
235
|
+
if (tags.length > 0) {
|
|
236
|
+
tagsVector = await embeddingService.embedWithTimeout(tags.join(", "));
|
|
237
|
+
}
|
|
238
|
+
const { scope, hash } = extractScopeFromTag(data.containerTag);
|
|
239
|
+
const shard = shardManager.getWriteShard(scope, hash);
|
|
240
|
+
const id = `mem_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
241
|
+
const now = Date.now();
|
|
242
|
+
const record = {
|
|
243
|
+
id,
|
|
244
|
+
content: data.content,
|
|
245
|
+
vector,
|
|
246
|
+
tagsVector,
|
|
247
|
+
containerTag: data.containerTag,
|
|
248
|
+
tags: tags.length > 0 ? tags.join(",") : undefined,
|
|
249
|
+
type: data.type,
|
|
250
|
+
createdAt: now,
|
|
251
|
+
updatedAt: now,
|
|
252
|
+
displayName: data.displayName,
|
|
253
|
+
userName: data.userName,
|
|
254
|
+
userEmail: data.userEmail,
|
|
255
|
+
projectPath: data.projectPath,
|
|
256
|
+
projectName: data.projectName,
|
|
257
|
+
gitRepoUrl: data.gitRepoUrl,
|
|
258
|
+
metadata: JSON.stringify({ source: "api" }),
|
|
259
|
+
};
|
|
260
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
261
|
+
await vectorSearch.insertVector(db, record, shard);
|
|
262
|
+
shardManager.incrementVectorCount(shard.id);
|
|
263
|
+
return { success: true, data: { id } };
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
log("handleAddMemory: error", { error: String(error) });
|
|
267
|
+
return { success: false, error: String(error) };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
export async function handleDeleteMemory(id, cascade = false) {
|
|
271
|
+
try {
|
|
272
|
+
if (!id)
|
|
273
|
+
return { success: false, error: "id is required" };
|
|
274
|
+
const projectShards = shardManager.getAllShards("project", "");
|
|
275
|
+
for (const shard of projectShards) {
|
|
276
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
277
|
+
const memory = vectorSearch.getMemoryById(db, id);
|
|
278
|
+
if (memory) {
|
|
279
|
+
if (cascade) {
|
|
280
|
+
const metadata = safeJSONParse(memory.metadata);
|
|
281
|
+
const linkedPromptId = metadata?.promptId;
|
|
282
|
+
if (linkedPromptId)
|
|
283
|
+
userPromptManager.deletePrompt(linkedPromptId);
|
|
284
|
+
}
|
|
285
|
+
await vectorSearch.deleteVector(db, id, shard);
|
|
286
|
+
shardManager.decrementVectorCount(shard.id);
|
|
287
|
+
return {
|
|
288
|
+
success: true,
|
|
289
|
+
data: { deletedPrompt: cascade && !!safeJSONParse(memory.metadata)?.promptId },
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return { success: false, error: "Memory not found" };
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
log("handleDeleteMemory: error", { error: String(error) });
|
|
297
|
+
return { success: false, error: String(error) };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
export async function handleBulkDelete(ids, cascade = false) {
|
|
301
|
+
try {
|
|
302
|
+
if (!ids || ids.length === 0)
|
|
303
|
+
return { success: false, error: "ids array is required" };
|
|
304
|
+
let deleted = 0;
|
|
305
|
+
for (const id of ids) {
|
|
306
|
+
const result = await handleDeleteMemory(id, cascade);
|
|
307
|
+
if (result.success)
|
|
308
|
+
deleted++;
|
|
309
|
+
}
|
|
310
|
+
return { success: true, data: { deleted } };
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
log("handleBulkDelete: error", { error: String(error) });
|
|
314
|
+
return { success: false, error: String(error) };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
export async function handleUpdateMemory(id, data) {
|
|
318
|
+
try {
|
|
319
|
+
if (!id)
|
|
320
|
+
return { success: false, error: "id is required" };
|
|
321
|
+
await embeddingService.warmup();
|
|
322
|
+
const projectShards = shardManager.getAllShards("project", "");
|
|
323
|
+
let foundShard = null, existingMemory = null;
|
|
324
|
+
for (const shard of projectShards) {
|
|
325
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
326
|
+
const memory = vectorSearch.getMemoryById(db, id);
|
|
327
|
+
if (memory) {
|
|
328
|
+
foundShard = shard;
|
|
329
|
+
existingMemory = memory;
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (!foundShard || !existingMemory)
|
|
334
|
+
return { success: false, error: "Memory not found" };
|
|
335
|
+
const db = connectionManager.getConnection(foundShard.dbPath);
|
|
336
|
+
await vectorSearch.deleteVector(db, id, foundShard);
|
|
337
|
+
shardManager.decrementVectorCount(foundShard.id);
|
|
338
|
+
const newContent = data.content || existingMemory.content;
|
|
339
|
+
const tags = data.tags || (existingMemory.tags ? existingMemory.tags.split(",") : []);
|
|
340
|
+
const vector = await embeddingService.embedWithTimeout(newContent);
|
|
341
|
+
let tagsVector = undefined;
|
|
342
|
+
if (tags.length > 0) {
|
|
343
|
+
tagsVector = await embeddingService.embedWithTimeout(tags.join(", "));
|
|
344
|
+
}
|
|
345
|
+
const updatedRecord = {
|
|
346
|
+
id,
|
|
347
|
+
content: newContent,
|
|
348
|
+
vector,
|
|
349
|
+
tagsVector,
|
|
350
|
+
containerTag: existingMemory.container_tag,
|
|
351
|
+
tags: tags.length > 0 ? tags.join(",") : undefined,
|
|
352
|
+
type: data.type || existingMemory.type,
|
|
353
|
+
createdAt: existingMemory.created_at,
|
|
354
|
+
updatedAt: Date.now(),
|
|
355
|
+
metadata: existingMemory.metadata,
|
|
356
|
+
displayName: existingMemory.display_name,
|
|
357
|
+
userName: existingMemory.user_name,
|
|
358
|
+
userEmail: existingMemory.user_email,
|
|
359
|
+
projectPath: existingMemory.project_path,
|
|
360
|
+
projectName: existingMemory.project_name,
|
|
361
|
+
gitRepoUrl: existingMemory.git_repo_url,
|
|
362
|
+
};
|
|
363
|
+
await vectorSearch.insertVector(db, updatedRecord, foundShard);
|
|
364
|
+
shardManager.incrementVectorCount(foundShard.id);
|
|
365
|
+
return { success: true };
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
log("handleUpdateMemory: error", { error: String(error) });
|
|
369
|
+
return { success: false, error: String(error) };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
export async function handleSearch(query, tag, page = 1, pageSize = 20) {
|
|
373
|
+
try {
|
|
374
|
+
if (!query)
|
|
375
|
+
return { success: false, error: "query is required" };
|
|
376
|
+
await embeddingService.warmup();
|
|
377
|
+
const queryVector = await embeddingService.embedWithTimeout(query);
|
|
378
|
+
let memoryResults = [];
|
|
379
|
+
let promptResults = [];
|
|
380
|
+
if (tag) {
|
|
381
|
+
const { scope, hash } = extractScopeFromTag(tag);
|
|
382
|
+
const shards = shardManager.getAllShards(scope, hash);
|
|
383
|
+
for (const shard of shards) {
|
|
384
|
+
try {
|
|
385
|
+
const results = await vectorSearch.searchInShard(shard, queryVector, tag, pageSize * 2);
|
|
386
|
+
memoryResults.push(...results);
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
log("Shard search error", { shardId: shard.id, error: String(error) });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const projectPath = getProjectPathFromTag(tag);
|
|
393
|
+
promptResults = userPromptManager.searchPrompts(query, projectPath, pageSize * 2);
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
const projectShards = shardManager.getAllShards("project", "");
|
|
397
|
+
const uniqueTags = new Set();
|
|
398
|
+
for (const shard of projectShards) {
|
|
399
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
400
|
+
const tags = vectorSearch.getDistinctTags(db);
|
|
401
|
+
for (const t of tags) {
|
|
402
|
+
if (t.container_tag)
|
|
403
|
+
uniqueTags.add(t.container_tag);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
for (const containerTag of uniqueTags) {
|
|
407
|
+
const { scope, hash } = extractScopeFromTag(containerTag);
|
|
408
|
+
const shards = shardManager.getAllShards(scope, hash);
|
|
409
|
+
for (const shard of shards) {
|
|
410
|
+
try {
|
|
411
|
+
const results = await vectorSearch.searchInShard(shard, queryVector, containerTag, pageSize);
|
|
412
|
+
memoryResults.push(...results);
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
log("Shard search error", { shardId: shard.id, error: String(error) });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
promptResults = userPromptManager.searchPrompts(query, undefined, pageSize * 2);
|
|
420
|
+
}
|
|
421
|
+
const formattedPrompts = promptResults.map((p) => ({
|
|
422
|
+
type: "prompt",
|
|
423
|
+
id: p.id,
|
|
424
|
+
sessionId: p.sessionId,
|
|
425
|
+
content: p.content,
|
|
426
|
+
createdAt: safeToISOString(p.createdAt),
|
|
427
|
+
projectPath: p.projectPath,
|
|
428
|
+
linkedMemoryId: p.linkedMemoryId,
|
|
429
|
+
similarity: 1.0,
|
|
430
|
+
}));
|
|
431
|
+
const formattedMemories = memoryResults.map((r) => ({
|
|
432
|
+
type: "memory",
|
|
433
|
+
id: r.id,
|
|
434
|
+
content: r.memory,
|
|
435
|
+
memoryType: r.metadata?.type,
|
|
436
|
+
tags: r.tags,
|
|
437
|
+
createdAt: safeToISOString(r.metadata?.createdAt),
|
|
438
|
+
updatedAt: r.metadata?.updatedAt ? safeToISOString(r.metadata.updatedAt) : undefined,
|
|
439
|
+
similarity: r.similarity,
|
|
440
|
+
metadata: r.metadata,
|
|
441
|
+
displayName: r.displayName,
|
|
442
|
+
userName: r.userName,
|
|
443
|
+
userEmail: r.userEmail,
|
|
444
|
+
projectPath: r.projectPath,
|
|
445
|
+
projectName: r.projectName,
|
|
446
|
+
gitRepoUrl: r.gitRepoUrl,
|
|
447
|
+
isPinned: r.isPinned === 1,
|
|
448
|
+
linkedPromptId: r.metadata?.promptId,
|
|
449
|
+
}));
|
|
450
|
+
const combinedResults = [...formattedMemories, ...formattedPrompts].sort((a, b) => (b.similarity || 0) - (a.similarity || 0) || b.createdAt.localeCompare(a.createdAt));
|
|
451
|
+
const total = combinedResults.length;
|
|
452
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
453
|
+
const offset = (page - 1) * pageSize;
|
|
454
|
+
const paginatedResults = combinedResults.slice(offset, offset + pageSize);
|
|
455
|
+
const missingPromptIds = new Set();
|
|
456
|
+
const missingMemoryIds = new Set();
|
|
457
|
+
for (const item of paginatedResults) {
|
|
458
|
+
if (item.type === "memory" && item.linkedPromptId) {
|
|
459
|
+
if (!paginatedResults.some((p) => p.id === item.linkedPromptId))
|
|
460
|
+
missingPromptIds.add(item.linkedPromptId);
|
|
461
|
+
}
|
|
462
|
+
else if (item.type === "prompt" && item.linkedMemoryId) {
|
|
463
|
+
if (!paginatedResults.some((m) => m.id === item.linkedMemoryId))
|
|
464
|
+
missingMemoryIds.add(item.linkedMemoryId);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (missingPromptIds.size > 0) {
|
|
468
|
+
const extraPrompts = userPromptManager.getPromptsByIds(Array.from(missingPromptIds));
|
|
469
|
+
for (const p of extraPrompts) {
|
|
470
|
+
paginatedResults.push({
|
|
471
|
+
type: "prompt",
|
|
472
|
+
id: p.id,
|
|
473
|
+
sessionId: p.sessionId,
|
|
474
|
+
content: p.content,
|
|
475
|
+
createdAt: safeToISOString(p.createdAt),
|
|
476
|
+
projectPath: p.projectPath,
|
|
477
|
+
linkedMemoryId: p.linkedMemoryId,
|
|
478
|
+
similarity: 0,
|
|
479
|
+
isContext: true,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (missingMemoryIds.size > 0) {
|
|
484
|
+
const projectShards = shardManager.getAllShards("project", "");
|
|
485
|
+
for (const shard of projectShards) {
|
|
486
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
487
|
+
for (const mid of missingMemoryIds) {
|
|
488
|
+
const m = vectorSearch.getMemoryById(db, mid);
|
|
489
|
+
if (m && !paginatedResults.some((existing) => existing.id === m.id)) {
|
|
490
|
+
paginatedResults.push({
|
|
491
|
+
type: "memory",
|
|
492
|
+
id: m.id,
|
|
493
|
+
content: m.content,
|
|
494
|
+
memoryType: m.type,
|
|
495
|
+
tags: m.tags ? m.tags.split(",").map((t) => t.trim()) : [],
|
|
496
|
+
createdAt: safeToISOString(m.created_at),
|
|
497
|
+
updatedAt: m.updated_at ? safeToISOString(m.updated_at) : undefined,
|
|
498
|
+
similarity: 0,
|
|
499
|
+
metadata: safeJSONParse(m.metadata),
|
|
500
|
+
displayName: m.display_name,
|
|
501
|
+
userName: m.user_name,
|
|
502
|
+
userEmail: m.user_email,
|
|
503
|
+
projectPath: m.project_path,
|
|
504
|
+
projectName: m.project_name,
|
|
505
|
+
gitRepoUrl: m.git_repo_url,
|
|
506
|
+
isPinned: m.is_pinned === 1,
|
|
507
|
+
linkedPromptId: safeJSONParse(m.metadata)?.promptId,
|
|
508
|
+
isContext: true,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return { success: true, data: { items: paginatedResults, total, page, pageSize, totalPages } };
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
log("handleSearch: error", { error: String(error) });
|
|
518
|
+
return { success: false, error: String(error) };
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
export async function handleStats() {
|
|
522
|
+
try {
|
|
523
|
+
await embeddingService.warmup();
|
|
524
|
+
const projectShards = shardManager.getAllShards("project", "");
|
|
525
|
+
let userCount = 0, projectCount = 0;
|
|
526
|
+
const typeCount = {};
|
|
527
|
+
for (const shard of projectShards) {
|
|
528
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
529
|
+
const memories = vectorSearch.getAllMemories(db);
|
|
530
|
+
for (const r of memories) {
|
|
531
|
+
if (r.container_tag?.includes("_user_"))
|
|
532
|
+
userCount++;
|
|
533
|
+
else if (r.container_tag?.includes("_project_"))
|
|
534
|
+
projectCount++;
|
|
535
|
+
if (r.type)
|
|
536
|
+
typeCount[r.type] = (typeCount[r.type] || 0) + 1;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return {
|
|
540
|
+
success: true,
|
|
541
|
+
data: {
|
|
542
|
+
total: userCount + projectCount,
|
|
543
|
+
byScope: { user: userCount, project: projectCount },
|
|
544
|
+
byType: typeCount,
|
|
545
|
+
},
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
log("handleStats: error", { error: String(error) });
|
|
550
|
+
return { success: false, error: String(error) };
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
export async function handlePinMemory(id) {
|
|
554
|
+
try {
|
|
555
|
+
if (!id)
|
|
556
|
+
return { success: false, error: "id is required" };
|
|
557
|
+
const projectShards = shardManager.getAllShards("project", "");
|
|
558
|
+
for (const shard of projectShards) {
|
|
559
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
560
|
+
const memory = vectorSearch.getMemoryById(db, id);
|
|
561
|
+
if (memory) {
|
|
562
|
+
vectorSearch.pinMemory(db, id);
|
|
563
|
+
return { success: true };
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return { success: false, error: "Memory not found" };
|
|
567
|
+
}
|
|
568
|
+
catch (error) {
|
|
569
|
+
log("handlePinMemory: error", { error: String(error) });
|
|
570
|
+
return { success: false, error: String(error) };
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
export async function handleUnpinMemory(id) {
|
|
574
|
+
try {
|
|
575
|
+
if (!id)
|
|
576
|
+
return { success: false, error: "id is required" };
|
|
577
|
+
const projectShards = shardManager.getAllShards("project", "");
|
|
578
|
+
for (const shard of projectShards) {
|
|
579
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
580
|
+
const memory = vectorSearch.getMemoryById(db, id);
|
|
581
|
+
if (memory) {
|
|
582
|
+
vectorSearch.unpinMemory(db, id);
|
|
583
|
+
return { success: true };
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return { success: false, error: "Memory not found" };
|
|
587
|
+
}
|
|
588
|
+
catch (error) {
|
|
589
|
+
log("handleUnpinMemory: error", { error: String(error) });
|
|
590
|
+
return { success: false, error: String(error) };
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
export async function handleRunCleanup() {
|
|
594
|
+
try {
|
|
595
|
+
const { cleanupService } = await import("./cleanup-service.js");
|
|
596
|
+
const result = await cleanupService.runCleanup();
|
|
597
|
+
return { success: true, data: result };
|
|
598
|
+
}
|
|
599
|
+
catch (error) {
|
|
600
|
+
log("handleRunCleanup: error", { error: String(error) });
|
|
601
|
+
return { success: false, error: String(error) };
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
export async function handleRunDeduplication() {
|
|
605
|
+
try {
|
|
606
|
+
const { deduplicationService } = await import("./deduplication-service.js");
|
|
607
|
+
const result = await deduplicationService.detectAndRemoveDuplicates();
|
|
608
|
+
return { success: true, data: result };
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
log("handleRunDeduplication: error", { error: String(error) });
|
|
612
|
+
return { success: false, error: String(error) };
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
export async function handleDetectMigration() {
|
|
616
|
+
try {
|
|
617
|
+
const { migrationService } = await import("./migration-service.js");
|
|
618
|
+
const result = await migrationService.detectDimensionMismatch();
|
|
619
|
+
return { success: true, data: result };
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
log("handleDetectMigration: error", { error: String(error) });
|
|
623
|
+
return { success: false, error: String(error) };
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
export async function handleRunMigration(strategy) {
|
|
627
|
+
try {
|
|
628
|
+
const { migrationService } = await import("./migration-service.js");
|
|
629
|
+
const result = await migrationService.migrateToNewModel(strategy);
|
|
630
|
+
return { success: result.success, data: result };
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
log("handleRunMigration: error", { error: String(error) });
|
|
634
|
+
return { success: false, error: String(error) };
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
export async function handleDeletePrompt(id, cascade = false) {
|
|
638
|
+
try {
|
|
639
|
+
if (!id)
|
|
640
|
+
return { success: false, error: "id is required" };
|
|
641
|
+
const prompt = userPromptManager.getPromptById(id);
|
|
642
|
+
if (!prompt)
|
|
643
|
+
return { success: false, error: "Prompt not found" };
|
|
644
|
+
let deletedMemory = false;
|
|
645
|
+
if (cascade && prompt.linkedMemoryId) {
|
|
646
|
+
const result = await handleDeleteMemory(prompt.linkedMemoryId, false);
|
|
647
|
+
if (result.success)
|
|
648
|
+
deletedMemory = true;
|
|
649
|
+
}
|
|
650
|
+
userPromptManager.deletePrompt(id);
|
|
651
|
+
return { success: true, data: { deletedMemory } };
|
|
652
|
+
}
|
|
653
|
+
catch (error) {
|
|
654
|
+
log("handleDeletePrompt: error", { error: String(error) });
|
|
655
|
+
return { success: false, error: String(error) };
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
export async function handleBulkDeletePrompts(ids, cascade = false) {
|
|
659
|
+
try {
|
|
660
|
+
if (!ids || ids.length === 0)
|
|
661
|
+
return { success: false, error: "ids array is required" };
|
|
662
|
+
let deleted = 0;
|
|
663
|
+
for (const id of ids) {
|
|
664
|
+
const result = await handleDeletePrompt(id, cascade);
|
|
665
|
+
if (result.success)
|
|
666
|
+
deleted++;
|
|
667
|
+
}
|
|
668
|
+
return { success: true, data: { deleted } };
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
671
|
+
log("handleBulkDeletePrompts: error", { error: String(error) });
|
|
672
|
+
return { success: false, error: String(error) };
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
export async function handleGetUserProfile(userId) {
|
|
676
|
+
try {
|
|
677
|
+
const { userProfileManager } = await import("./user-profile/user-profile-manager.js");
|
|
678
|
+
const { getTags } = await import("./tags.js");
|
|
679
|
+
let targetUserId = userId;
|
|
680
|
+
if (!targetUserId) {
|
|
681
|
+
const tags = getTags(process.cwd());
|
|
682
|
+
targetUserId = tags.user.userEmail || "unknown";
|
|
683
|
+
}
|
|
684
|
+
const profile = userProfileManager.getActiveProfile(targetUserId);
|
|
685
|
+
if (!profile)
|
|
686
|
+
return {
|
|
687
|
+
success: true,
|
|
688
|
+
data: {
|
|
689
|
+
exists: false,
|
|
690
|
+
userId: targetUserId,
|
|
691
|
+
message: "No profile found. Keep chatting to build your profile.",
|
|
692
|
+
},
|
|
693
|
+
};
|
|
694
|
+
const profileData = JSON.parse(profile.profileData);
|
|
695
|
+
return {
|
|
696
|
+
success: true,
|
|
697
|
+
data: {
|
|
698
|
+
exists: true,
|
|
699
|
+
id: profile.id,
|
|
700
|
+
userId: profile.userId,
|
|
701
|
+
displayName: profile.displayName,
|
|
702
|
+
userName: profile.userName,
|
|
703
|
+
userEmail: profile.userEmail,
|
|
704
|
+
version: profile.version,
|
|
705
|
+
createdAt: safeToISOString(profile.createdAt),
|
|
706
|
+
lastAnalyzedAt: safeToISOString(profile.lastAnalyzedAt),
|
|
707
|
+
totalPromptsAnalyzed: profile.totalPromptsAnalyzed,
|
|
708
|
+
profileData,
|
|
709
|
+
},
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
log("handleGetUserProfile: error", { error: String(error) });
|
|
714
|
+
return { success: false, error: String(error) };
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
export async function handleGetProfileChangelog(profileId, limit = 5) {
|
|
718
|
+
try {
|
|
719
|
+
if (!profileId)
|
|
720
|
+
return { success: false, error: "profileId is required" };
|
|
721
|
+
const { userProfileManager } = await import("./user-profile/user-profile-manager.js");
|
|
722
|
+
const changelogs = userProfileManager.getProfileChangelogs(profileId, limit);
|
|
723
|
+
const formattedChangelogs = changelogs.map((c) => ({
|
|
724
|
+
id: c.id,
|
|
725
|
+
profileId: c.profileId,
|
|
726
|
+
version: c.version,
|
|
727
|
+
changeType: c.changeType,
|
|
728
|
+
changeSummary: c.changeSummary,
|
|
729
|
+
createdAt: safeToISOString(c.createdAt),
|
|
730
|
+
}));
|
|
731
|
+
return { success: true, data: formattedChangelogs };
|
|
732
|
+
}
|
|
733
|
+
catch (error) {
|
|
734
|
+
log("handleGetProfileChangelog: error", { error: String(error) });
|
|
735
|
+
return { success: false, error: String(error) };
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
export async function handleGetProfileSnapshot(changelogId) {
|
|
739
|
+
try {
|
|
740
|
+
if (!changelogId)
|
|
741
|
+
return { success: false, error: "changelogId is required" };
|
|
742
|
+
const { userProfileManager } = await import("./user-profile/user-profile-manager.js");
|
|
743
|
+
const changelogs = userProfileManager.getProfileChangelogs("", 1000);
|
|
744
|
+
const changelog = changelogs.find((c) => c.id === changelogId);
|
|
745
|
+
if (!changelog)
|
|
746
|
+
return { success: false, error: "Changelog not found" };
|
|
747
|
+
const profileData = JSON.parse(changelog.profileDataSnapshot);
|
|
748
|
+
return {
|
|
749
|
+
success: true,
|
|
750
|
+
data: {
|
|
751
|
+
version: changelog.version,
|
|
752
|
+
createdAt: safeToISOString(changelog.createdAt),
|
|
753
|
+
profileData,
|
|
754
|
+
},
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
catch (error) {
|
|
758
|
+
log("handleGetProfileSnapshot: error", { error: String(error) });
|
|
759
|
+
return { success: false, error: String(error) };
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
export async function handleRefreshProfile(userId) {
|
|
763
|
+
try {
|
|
764
|
+
const { getTags } = await import("./tags.js");
|
|
765
|
+
const { userPromptManager } = await import("./user-prompt/user-prompt-manager.js");
|
|
766
|
+
let targetUserId = userId;
|
|
767
|
+
if (!targetUserId) {
|
|
768
|
+
const tags = getTags(process.cwd());
|
|
769
|
+
targetUserId = tags.user.userEmail || "unknown";
|
|
770
|
+
}
|
|
771
|
+
const unanalyzedCount = userPromptManager.countUnanalyzedForUserLearning();
|
|
772
|
+
return {
|
|
773
|
+
success: true,
|
|
774
|
+
data: {
|
|
775
|
+
message: "Profile refresh queued",
|
|
776
|
+
unanalyzedPrompts: unanalyzedCount,
|
|
777
|
+
note: "Profile will be updated when threshold is reached",
|
|
778
|
+
},
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
catch (error) {
|
|
782
|
+
log("handleRefreshProfile: error", { error: String(error) });
|
|
783
|
+
return { success: false, error: String(error) };
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
export async function handleDetectTagMigration() {
|
|
787
|
+
try {
|
|
788
|
+
const projectShards = shardManager.getAllShards("project", "");
|
|
789
|
+
let untaggedCount = 0;
|
|
790
|
+
for (const shard of projectShards) {
|
|
791
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
792
|
+
const rows = db
|
|
793
|
+
.prepare("SELECT COUNT(*) as count FROM memories WHERE tags IS NULL OR tags = ''")
|
|
794
|
+
.get();
|
|
795
|
+
untaggedCount += rows.count;
|
|
796
|
+
}
|
|
797
|
+
return { success: true, data: { needsMigration: untaggedCount > 0, count: untaggedCount } };
|
|
798
|
+
}
|
|
799
|
+
catch (error) {
|
|
800
|
+
return { success: false, error: String(error) };
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
let migrationProgress = {
|
|
804
|
+
processed: 0,
|
|
805
|
+
total: 0,
|
|
806
|
+
currentBatch: 0,
|
|
807
|
+
totalBatches: 0,
|
|
808
|
+
isComplete: true,
|
|
809
|
+
errors: [],
|
|
810
|
+
};
|
|
811
|
+
export async function handleGetTagMigrationProgress() {
|
|
812
|
+
return { success: true, data: migrationProgress };
|
|
813
|
+
}
|
|
814
|
+
export async function handleRunTagMigrationBatch(batchSize = 5) {
|
|
815
|
+
try {
|
|
816
|
+
const { AIProviderFactory } = await import("./ai/ai-provider-factory.js");
|
|
817
|
+
const { buildMemoryProviderConfig } = await import("./ai/provider-config.js");
|
|
818
|
+
const providerConfig = buildMemoryProviderConfig(CONFIG, {
|
|
819
|
+
maxIterations: 1,
|
|
820
|
+
iterationTimeout: 30000,
|
|
821
|
+
});
|
|
822
|
+
const provider = AIProviderFactory.createProvider(CONFIG.memoryProvider, providerConfig);
|
|
823
|
+
const projectShards = shardManager.getAllShards("project", "");
|
|
824
|
+
let batchProcessed = 0;
|
|
825
|
+
const allMemories = [];
|
|
826
|
+
for (const shard of projectShards) {
|
|
827
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
828
|
+
const memories = db.prepare("SELECT * FROM memories").all();
|
|
829
|
+
for (const m of memories) {
|
|
830
|
+
allMemories.push({ memory: m, shard });
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (migrationProgress.total === 0) {
|
|
834
|
+
migrationProgress.total = allMemories.length;
|
|
835
|
+
migrationProgress.totalBatches = Math.ceil(allMemories.length / batchSize);
|
|
836
|
+
migrationProgress.isComplete = false;
|
|
837
|
+
}
|
|
838
|
+
const startIdx = migrationProgress.processed;
|
|
839
|
+
const endIdx = Math.min(startIdx + batchSize, allMemories.length);
|
|
840
|
+
for (let i = startIdx; i < endIdx; i++) {
|
|
841
|
+
const item = allMemories[i];
|
|
842
|
+
if (!item)
|
|
843
|
+
continue;
|
|
844
|
+
const { memory: m, shard } = item;
|
|
845
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
846
|
+
try {
|
|
847
|
+
let currentTags = m.tags
|
|
848
|
+
? m.tags
|
|
849
|
+
.split(",")
|
|
850
|
+
.map((t) => t.trim().toLowerCase())
|
|
851
|
+
.filter((t) => t)
|
|
852
|
+
: [];
|
|
853
|
+
if (currentTags.length === 0) {
|
|
854
|
+
const prompt = `Generate 2-4 short technical tags for this memory content:\n\n${m.content}\n\nReturn ONLY a comma-separated list of tags.`;
|
|
855
|
+
const result = await provider.executeToolCall("You are a technical tagger.", prompt, {
|
|
856
|
+
type: "function",
|
|
857
|
+
function: {
|
|
858
|
+
name: "save_tags",
|
|
859
|
+
description: "Save generated tags",
|
|
860
|
+
parameters: {
|
|
861
|
+
type: "object",
|
|
862
|
+
properties: { tags: { type: "array", items: { type: "string" } } },
|
|
863
|
+
required: ["tags"],
|
|
864
|
+
},
|
|
865
|
+
},
|
|
866
|
+
}, `migration_${m.id}`);
|
|
867
|
+
if (result.success && result.data?.tags) {
|
|
868
|
+
currentTags = result.data.tags;
|
|
869
|
+
db.prepare("UPDATE memories SET tags = ? WHERE id = ?").run(currentTags.join(","), m.id);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
const vector = await embeddingService.embedWithTimeout(m.content);
|
|
873
|
+
const tagsVector = currentTags.length
|
|
874
|
+
? await embeddingService.embedWithTimeout(currentTags.join(", "))
|
|
875
|
+
: undefined;
|
|
876
|
+
const vectorBuffer = new Uint8Array(vector.buffer);
|
|
877
|
+
db.prepare("UPDATE memories SET vector = ?, updated_at = ? WHERE id = ?").run(vectorBuffer, Date.now(), m.id);
|
|
878
|
+
await vectorSearch.updateVector(db, m.id, vector, shard, tagsVector);
|
|
879
|
+
migrationProgress.processed++;
|
|
880
|
+
batchProcessed++;
|
|
881
|
+
}
|
|
882
|
+
catch (e) {
|
|
883
|
+
const errorMsg = String(e);
|
|
884
|
+
migrationProgress.errors.push(errorMsg);
|
|
885
|
+
log("Migration error for memory", { id: m.id, error: errorMsg });
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
migrationProgress.currentBatch++;
|
|
889
|
+
const hasMore = migrationProgress.processed < migrationProgress.total;
|
|
890
|
+
if (!hasMore) {
|
|
891
|
+
migrationProgress.isComplete = true;
|
|
892
|
+
}
|
|
893
|
+
return {
|
|
894
|
+
success: true,
|
|
895
|
+
data: { processed: migrationProgress.processed, total: migrationProgress.total, hasMore },
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
catch (error) {
|
|
899
|
+
return { success: false, error: String(error) };
|
|
900
|
+
}
|
|
901
|
+
}
|