opencode-mem 2.0.1 → 2.2.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 +54 -11
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +62 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -11
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +0 -4
- package/dist/services/ai/providers/anthropic-messages.d.ts.map +1 -1
- package/dist/services/ai/providers/anthropic-messages.js +11 -17
- package/dist/services/ai/providers/openai-chat-completion.d.ts.map +1 -1
- package/dist/services/ai/providers/openai-chat-completion.js +11 -17
- package/dist/services/ai/providers/openai-responses.d.ts.map +1 -1
- package/dist/services/ai/providers/openai-responses.js +11 -17
- package/dist/services/api-handlers.d.ts +4 -0
- package/dist/services/api-handlers.d.ts.map +1 -1
- package/dist/services/api-handlers.js +155 -1
- package/dist/services/auto-capture.js +2 -2
- package/dist/services/client.d.ts +1 -16
- package/dist/services/client.d.ts.map +1 -1
- package/dist/services/client.js +0 -35
- package/dist/services/context.d.ts +1 -7
- package/dist/services/context.d.ts.map +1 -1
- package/dist/services/context.js +6 -14
- package/dist/services/deduplication-service.d.ts.map +1 -1
- package/dist/services/deduplication-service.js +1 -3
- package/dist/services/logger.js +1 -1
- package/dist/services/user-memory-learning.d.ts.map +1 -1
- package/dist/services/user-memory-learning.js +122 -84
- 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/types.d.ts +51 -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 +22 -0
- package/dist/services/user-profile/user-profile-manager.d.ts.map +1 -0
- package/dist/services/user-profile/user-profile-manager.js +267 -0
- package/dist/services/web-server-worker.js +29 -1
- package/dist/types/index.d.ts +1 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/web/app.js +249 -29
- package/dist/web/index.html +31 -5
- package/dist/web/styles.css +309 -0
- package/package.json +1 -1
|
@@ -149,7 +149,40 @@ export async function handleListMemories(tag, page = 1, pageSize = 20, scope, in
|
|
|
149
149
|
}));
|
|
150
150
|
timeline = [...memoriesWithType, ...promptsWithType];
|
|
151
151
|
}
|
|
152
|
-
|
|
152
|
+
const linkedPairs = new Map();
|
|
153
|
+
const standalone = [];
|
|
154
|
+
for (const item of timeline) {
|
|
155
|
+
if (item.type === "memory" && item.linkedPromptId) {
|
|
156
|
+
if (!linkedPairs.has(item.linkedPromptId)) {
|
|
157
|
+
linkedPairs.set(item.linkedPromptId, { memory: item, prompt: null });
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
linkedPairs.get(item.linkedPromptId).memory = item;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else if (item.type === "prompt" && item.linkedMemoryId) {
|
|
164
|
+
if (!linkedPairs.has(item.id)) {
|
|
165
|
+
linkedPairs.set(item.id, { memory: null, prompt: item });
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
linkedPairs.get(item.id).prompt = item;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
standalone.push(item);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const sortedTimeline = [];
|
|
176
|
+
const pairs = Array.from(linkedPairs.values())
|
|
177
|
+
.filter((p) => p.memory && p.prompt)
|
|
178
|
+
.sort((a, b) => b.memory.createdAt - a.memory.createdAt);
|
|
179
|
+
for (const pair of pairs) {
|
|
180
|
+
sortedTimeline.push(pair.memory);
|
|
181
|
+
sortedTimeline.push(pair.prompt);
|
|
182
|
+
}
|
|
183
|
+
standalone.sort((a, b) => b.createdAt - a.createdAt);
|
|
184
|
+
sortedTimeline.push(...standalone);
|
|
185
|
+
timeline = sortedTimeline;
|
|
153
186
|
const total = timeline.length;
|
|
154
187
|
const totalPages = Math.ceil(total / pageSize);
|
|
155
188
|
const offset = (page - 1) * pageSize;
|
|
@@ -211,6 +244,12 @@ export async function handleAddMemory(data) {
|
|
|
211
244
|
await embeddingService.warmup();
|
|
212
245
|
const vector = await embeddingService.embedWithTimeout(data.content);
|
|
213
246
|
const { scope, hash } = extractScopeFromTag(data.containerTag);
|
|
247
|
+
if (scope === "user") {
|
|
248
|
+
return {
|
|
249
|
+
success: false,
|
|
250
|
+
error: "User-scoped memories are deprecated. Use user profile system instead.",
|
|
251
|
+
};
|
|
252
|
+
}
|
|
214
253
|
const shard = shardManager.getWriteShard(scope, hash);
|
|
215
254
|
const id = `mem_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
216
255
|
const now = Date.now();
|
|
@@ -605,3 +644,118 @@ export async function handleBulkDeletePrompts(ids, cascade = false) {
|
|
|
605
644
|
return { success: false, error: String(error) };
|
|
606
645
|
}
|
|
607
646
|
}
|
|
647
|
+
export async function handleGetUserProfile(userId) {
|
|
648
|
+
try {
|
|
649
|
+
const { userProfileManager } = await import("./user-profile/user-profile-manager.js");
|
|
650
|
+
const { getTags } = await import("./tags.js");
|
|
651
|
+
let targetUserId = userId;
|
|
652
|
+
if (!targetUserId) {
|
|
653
|
+
const tags = getTags(process.cwd());
|
|
654
|
+
targetUserId = tags.user.userEmail || "unknown";
|
|
655
|
+
}
|
|
656
|
+
const profile = userProfileManager.getActiveProfile(targetUserId);
|
|
657
|
+
if (!profile) {
|
|
658
|
+
return {
|
|
659
|
+
success: true,
|
|
660
|
+
data: {
|
|
661
|
+
exists: false,
|
|
662
|
+
userId: targetUserId,
|
|
663
|
+
message: "No profile found. Keep chatting to build your profile.",
|
|
664
|
+
},
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
const profileData = JSON.parse(profile.profileData);
|
|
668
|
+
return {
|
|
669
|
+
success: true,
|
|
670
|
+
data: {
|
|
671
|
+
exists: true,
|
|
672
|
+
id: profile.id,
|
|
673
|
+
userId: profile.userId,
|
|
674
|
+
displayName: profile.displayName,
|
|
675
|
+
userName: profile.userName,
|
|
676
|
+
userEmail: profile.userEmail,
|
|
677
|
+
version: profile.version,
|
|
678
|
+
createdAt: safeToISOString(profile.createdAt),
|
|
679
|
+
lastAnalyzedAt: safeToISOString(profile.lastAnalyzedAt),
|
|
680
|
+
totalPromptsAnalyzed: profile.totalPromptsAnalyzed,
|
|
681
|
+
profileData,
|
|
682
|
+
},
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
log("handleGetUserProfile: error", { error: String(error) });
|
|
687
|
+
return { success: false, error: String(error) };
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
export async function handleGetProfileChangelog(profileId, limit = 5) {
|
|
691
|
+
try {
|
|
692
|
+
if (!profileId) {
|
|
693
|
+
return { success: false, error: "profileId is required" };
|
|
694
|
+
}
|
|
695
|
+
const { userProfileManager } = await import("./user-profile/user-profile-manager.js");
|
|
696
|
+
const changelogs = userProfileManager.getProfileChangelogs(profileId, limit);
|
|
697
|
+
const formattedChangelogs = changelogs.map((c) => ({
|
|
698
|
+
id: c.id,
|
|
699
|
+
profileId: c.profileId,
|
|
700
|
+
version: c.version,
|
|
701
|
+
changeType: c.changeType,
|
|
702
|
+
changeSummary: c.changeSummary,
|
|
703
|
+
createdAt: safeToISOString(c.createdAt),
|
|
704
|
+
}));
|
|
705
|
+
return { success: true, data: formattedChangelogs };
|
|
706
|
+
}
|
|
707
|
+
catch (error) {
|
|
708
|
+
log("handleGetProfileChangelog: error", { error: String(error) });
|
|
709
|
+
return { success: false, error: String(error) };
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
export async function handleGetProfileSnapshot(changelogId) {
|
|
713
|
+
try {
|
|
714
|
+
if (!changelogId) {
|
|
715
|
+
return { success: false, error: "changelogId is required" };
|
|
716
|
+
}
|
|
717
|
+
const { userProfileManager } = await import("./user-profile/user-profile-manager.js");
|
|
718
|
+
const changelogs = userProfileManager.getProfileChangelogs("", 1000);
|
|
719
|
+
const changelog = changelogs.find((c) => c.id === changelogId);
|
|
720
|
+
if (!changelog) {
|
|
721
|
+
return { success: false, error: "Changelog not found" };
|
|
722
|
+
}
|
|
723
|
+
const profileData = JSON.parse(changelog.profileDataSnapshot);
|
|
724
|
+
return {
|
|
725
|
+
success: true,
|
|
726
|
+
data: {
|
|
727
|
+
version: changelog.version,
|
|
728
|
+
createdAt: safeToISOString(changelog.createdAt),
|
|
729
|
+
profileData,
|
|
730
|
+
},
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
catch (error) {
|
|
734
|
+
log("handleGetProfileSnapshot: error", { error: String(error) });
|
|
735
|
+
return { success: false, error: String(error) };
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
export async function handleRefreshProfile(userId) {
|
|
739
|
+
try {
|
|
740
|
+
const { getTags } = await import("./tags.js");
|
|
741
|
+
const { userPromptManager } = await import("./user-prompt/user-prompt-manager.js");
|
|
742
|
+
let targetUserId = userId;
|
|
743
|
+
if (!targetUserId) {
|
|
744
|
+
const tags = getTags(process.cwd());
|
|
745
|
+
targetUserId = tags.user.userEmail || "unknown";
|
|
746
|
+
}
|
|
747
|
+
const unanalyzedCount = userPromptManager.countUnanalyzedForUserLearning();
|
|
748
|
+
return {
|
|
749
|
+
success: true,
|
|
750
|
+
data: {
|
|
751
|
+
message: "Profile refresh queued",
|
|
752
|
+
unanalyzedPrompts: unanalyzedCount,
|
|
753
|
+
note: "Profile will be updated when threshold is reached",
|
|
754
|
+
},
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
catch (error) {
|
|
758
|
+
log("handleRefreshProfile: error", { error: String(error) });
|
|
759
|
+
return { success: false, error: String(error) };
|
|
760
|
+
}
|
|
761
|
+
}
|
|
@@ -37,7 +37,7 @@ export async function performAutoCapture(ctx, sessionID, directory) {
|
|
|
37
37
|
const tags = getTags(directory);
|
|
38
38
|
const latestMemory = await getLatestProjectMemory(tags.project.tag);
|
|
39
39
|
const context = buildMarkdownContext(prompt.content, textResponses, toolCalls, latestMemory);
|
|
40
|
-
const summaryResult = await generateSummary(
|
|
40
|
+
const summaryResult = await generateSummary(context, sessionID);
|
|
41
41
|
if (!summaryResult || summaryResult.type === "skip") {
|
|
42
42
|
log("Auto-capture: skipped non-technical conversation", { sessionID });
|
|
43
43
|
userPromptManager.deletePrompt(prompt.id);
|
|
@@ -179,7 +179,7 @@ function buildMarkdownContext(userPrompt, textResponses, toolCalls, latestMemory
|
|
|
179
179
|
}
|
|
180
180
|
return sections.join("\n");
|
|
181
181
|
}
|
|
182
|
-
async function generateSummary(
|
|
182
|
+
async function generateSummary(context, sessionID) {
|
|
183
183
|
if (!CONFIG.memoryModel || !CONFIG.memoryApiUrl || !CONFIG.memoryApiKey) {
|
|
184
184
|
throw new Error("External API not configured for auto-capture");
|
|
185
185
|
}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
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
2
|
export declare class LocalMemoryClient {
|
|
8
3
|
private initPromise;
|
|
9
4
|
private isInitialized;
|
|
@@ -19,7 +14,7 @@ export declare class LocalMemoryClient {
|
|
|
19
14
|
close(): void;
|
|
20
15
|
searchMemories(query: string, containerTag: string): Promise<{
|
|
21
16
|
success: true;
|
|
22
|
-
results: SearchResult[];
|
|
17
|
+
results: import("./sqlite/types.js").SearchResult[];
|
|
23
18
|
total: number;
|
|
24
19
|
timing: number;
|
|
25
20
|
error?: undefined;
|
|
@@ -30,15 +25,6 @@ export declare class LocalMemoryClient {
|
|
|
30
25
|
total: number;
|
|
31
26
|
timing: number;
|
|
32
27
|
}>;
|
|
33
|
-
getProfile(containerTag: string, query?: string): Promise<{
|
|
34
|
-
success: true;
|
|
35
|
-
profile: ProfileData;
|
|
36
|
-
error?: undefined;
|
|
37
|
-
} | {
|
|
38
|
-
success: false;
|
|
39
|
-
error: string;
|
|
40
|
-
profile: null;
|
|
41
|
-
}>;
|
|
42
28
|
addMemory(content: string, containerTag: string, metadata?: {
|
|
43
29
|
type?: MemoryType;
|
|
44
30
|
source?: "manual" | "auto-capture" | "import" | "api";
|
|
@@ -101,5 +87,4 @@ export declare class LocalMemoryClient {
|
|
|
101
87
|
}>;
|
|
102
88
|
}
|
|
103
89
|
export declare const memoryClient: LocalMemoryClient;
|
|
104
|
-
export {};
|
|
105
90
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -1 +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;
|
|
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,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;;;;;;;;;IAqDG,YAAY,CAAC,QAAQ,EAAE,MAAM;;;;;;;IA2B7B,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,SAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuDpD;AAED,eAAO,MAAM,YAAY,mBAA0B,CAAC"}
|
package/dist/services/client.js
CHANGED
|
@@ -96,41 +96,6 @@ export class LocalMemoryClient {
|
|
|
96
96
|
return { success: false, error: errorMessage, results: [], total: 0, timing: 0 };
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
-
async getProfile(containerTag, query) {
|
|
100
|
-
try {
|
|
101
|
-
await this.initialize();
|
|
102
|
-
const { scope, hash } = extractScopeFromContainerTag(containerTag);
|
|
103
|
-
const shards = shardManager.getAllShards(scope, hash);
|
|
104
|
-
if (shards.length === 0) {
|
|
105
|
-
log("getProfile: no shards found", { containerTag });
|
|
106
|
-
return { success: true, profile: { static: [], dynamic: [] } };
|
|
107
|
-
}
|
|
108
|
-
const staticFacts = [];
|
|
109
|
-
const dynamicFacts = [];
|
|
110
|
-
for (const shard of shards) {
|
|
111
|
-
const db = connectionManager.getConnection(shard.dbPath);
|
|
112
|
-
const memories = vectorSearch.listMemories(db, containerTag, CONFIG.maxProfileItems * 2);
|
|
113
|
-
for (const m of memories) {
|
|
114
|
-
if (m.type === "preference") {
|
|
115
|
-
staticFacts.push(m.content);
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
dynamicFacts.push(m.content);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
const profile = {
|
|
123
|
-
static: staticFacts.slice(0, CONFIG.maxProfileItems),
|
|
124
|
-
dynamic: dynamicFacts.slice(0, CONFIG.maxProfileItems),
|
|
125
|
-
};
|
|
126
|
-
return { success: true, profile };
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
130
|
-
log("getProfile: error", { error: errorMessage });
|
|
131
|
-
return { success: false, error: errorMessage, profile: null };
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
99
|
async addMemory(content, containerTag, metadata) {
|
|
135
100
|
try {
|
|
136
101
|
await this.initialize();
|
|
@@ -6,12 +6,6 @@ interface MemoryResultMinimal {
|
|
|
6
6
|
interface MemoriesResponseMinimal {
|
|
7
7
|
results?: MemoryResultMinimal[];
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
profile?: {
|
|
11
|
-
static: string[];
|
|
12
|
-
dynamic: string[];
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
export declare function formatContextForPrompt(profile: ProfileResponse | null, userMemories: MemoriesResponseMinimal, projectMemories: MemoriesResponseMinimal): string;
|
|
9
|
+
export declare function formatContextForPrompt(userId: string | null, userMemories: MemoriesResponseMinimal, projectMemories: MemoriesResponseMinimal): string;
|
|
16
10
|
export {};
|
|
17
11
|
//# sourceMappingURL=context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/services/context.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/services/context.ts"],"names":[],"mappings":"AAGA,UAAU,mBAAmB;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,uBAAuB;IAC/B,OAAO,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACjC;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,YAAY,EAAE,uBAAuB,EACrC,eAAe,EAAE,uBAAuB,GACvC,MAAM,CAmCR"}
|
package/dist/services/context.js
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import { CONFIG } from "../config.js";
|
|
2
|
-
|
|
2
|
+
import { getUserProfileContext } from "./user-profile/profile-context.js";
|
|
3
|
+
export function formatContextForPrompt(userId, userMemories, projectMemories) {
|
|
3
4
|
const parts = ["[MEMORY]"];
|
|
4
|
-
if (CONFIG.injectProfile &&
|
|
5
|
-
const
|
|
6
|
-
if (
|
|
7
|
-
parts.push("\
|
|
8
|
-
staticFacts.slice(0, CONFIG.maxProfileItems).forEach((fact) => {
|
|
9
|
-
parts.push(`- ${fact}`);
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
if (dynamicFacts.length > 0) {
|
|
13
|
-
parts.push("\nRecent Context:");
|
|
14
|
-
dynamicFacts.slice(0, CONFIG.maxProfileItems).forEach((fact) => {
|
|
15
|
-
parts.push(`- ${fact}`);
|
|
16
|
-
});
|
|
5
|
+
if (CONFIG.injectProfile && userId) {
|
|
6
|
+
const profileContext = getUserProfileContext(userId);
|
|
7
|
+
if (profileContext) {
|
|
8
|
+
parts.push("\n" + profileContext);
|
|
17
9
|
}
|
|
18
10
|
}
|
|
19
11
|
const projectResults = projectMemories.results || [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deduplication-service.d.ts","sourceRoot":"","sources":["../../src/services/deduplication-service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deduplication-service.d.ts","sourceRoot":"","sources":["../../src/services/deduplication-service.ts"],"names":[],"mappings":"AAMA,UAAU,cAAc;IACtB,cAAc,EAAE;QACd,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,UAAU,EAAE,KAAK,CAAC;QAChB,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACJ;AAED,UAAU,mBAAmB;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,mBAAmB,EAAE,cAAc,EAAE,CAAC;CACvC;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,SAAS,CAAkB;IAE7B,yBAAyB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IA+G/D,OAAO,CAAC,gBAAgB;IAoBxB,SAAS;;;;;CAOV;AAED,eAAO,MAAM,oBAAoB,sBAA6B,CAAC"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { embeddingService } from "./embedding.js";
|
|
2
1
|
import { shardManager } from "./sqlite/shard-manager.js";
|
|
3
2
|
import { vectorSearch } from "./sqlite/vector-search.js";
|
|
4
3
|
import { connectionManager } from "./sqlite/connection-manager.js";
|
|
@@ -34,10 +33,9 @@ export class DeduplicationService {
|
|
|
34
33
|
}
|
|
35
34
|
contentMap.get(key).push(memory);
|
|
36
35
|
}
|
|
37
|
-
for (const [
|
|
36
|
+
for (const [, duplicates] of contentMap) {
|
|
38
37
|
if (duplicates.length > 1) {
|
|
39
38
|
duplicates.sort((a, b) => Number(b.created_at) - Number(a.created_at));
|
|
40
|
-
const keep = duplicates[0];
|
|
41
39
|
const toDelete = duplicates.slice(1);
|
|
42
40
|
for (const dup of toDelete) {
|
|
43
41
|
try {
|
package/dist/services/logger.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { appendFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
2
2
|
import { homedir } from "os";
|
|
3
|
-
import { join
|
|
3
|
+
import { join } from "path";
|
|
4
4
|
const LOG_DIR = join(homedir(), ".opencode-mem");
|
|
5
5
|
const LOG_FILE = join(LOG_DIR, "opencode-mem.log");
|
|
6
6
|
if (!existsSync(LOG_DIR)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-memory-learning.d.ts","sourceRoot":"","sources":["../../src/services/user-memory-learning.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"user-memory-learning.d.ts","sourceRoot":"","sources":["../../src/services/user-memory-learning.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AASvD,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CA8Ef"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { memoryClient } from "./client.js";
|
|
2
1
|
import { getTags } from "./tags.js";
|
|
3
2
|
import { log } from "./logger.js";
|
|
4
3
|
import { CONFIG } from "../config.js";
|
|
5
4
|
import { userPromptManager } from "./user-prompt/user-prompt-manager.js";
|
|
5
|
+
import { userProfileManager } from "./user-profile/user-profile-manager.js";
|
|
6
6
|
export async function performUserMemoryLearning(ctx, directory) {
|
|
7
7
|
try {
|
|
8
8
|
const count = userPromptManager.countUnanalyzedForUserLearning();
|
|
@@ -14,50 +14,41 @@ export async function performUserMemoryLearning(ctx, directory) {
|
|
|
14
14
|
if (prompts.length === 0) {
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const tags = getTags(directory);
|
|
18
|
+
const userId = tags.user.userEmail || "unknown";
|
|
19
|
+
const existingProfile = userProfileManager.getActiveProfile(userId);
|
|
20
|
+
const context = buildUserAnalysisContext(prompts, existingProfile);
|
|
21
|
+
const updatedProfileData = await analyzeUserProfile(context, existingProfile);
|
|
22
|
+
if (!updatedProfileData) {
|
|
23
|
+
log("User memory learning: no profile updates", { promptCount: prompts.length });
|
|
21
24
|
userPromptManager.markMultipleAsUserLearningCaptured(prompts.map((p) => p.id));
|
|
22
25
|
return;
|
|
23
26
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const result = await memoryClient.addMemory(memory.summary, tags.user.tag, {
|
|
28
|
-
type: memory.type,
|
|
29
|
-
source: "user-learning",
|
|
30
|
-
promptCount: prompts.length,
|
|
31
|
-
analysisTimestamp: Date.now(),
|
|
32
|
-
reasoning: memory.reasoning,
|
|
33
|
-
displayName: tags.user.displayName,
|
|
34
|
-
userName: tags.user.userName,
|
|
35
|
-
userEmail: tags.user.userEmail,
|
|
36
|
-
});
|
|
37
|
-
if (result.success) {
|
|
38
|
-
savedCount++;
|
|
39
|
-
}
|
|
27
|
+
if (existingProfile) {
|
|
28
|
+
const changeSummary = generateChangeSummary(JSON.parse(existingProfile.profileData), updatedProfileData);
|
|
29
|
+
userProfileManager.updateProfile(existingProfile.id, updatedProfileData, prompts.length, changeSummary);
|
|
40
30
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
await ctx.client?.tui
|
|
44
|
-
.showToast({
|
|
45
|
-
body: {
|
|
46
|
-
title: "User Memory Learning",
|
|
47
|
-
message: `Learned ${savedCount} pattern${savedCount > 1 ? "s" : ""} from ${prompts.length} prompts`,
|
|
48
|
-
variant: "success",
|
|
49
|
-
duration: 3000,
|
|
50
|
-
},
|
|
51
|
-
})
|
|
52
|
-
.catch(() => { });
|
|
31
|
+
else {
|
|
32
|
+
userProfileManager.createProfile(userId, tags.user.displayName || "Unknown", tags.user.userName || "unknown", tags.user.userEmail || "unknown", updatedProfileData, prompts.length);
|
|
53
33
|
}
|
|
34
|
+
userPromptManager.markMultipleAsUserLearningCaptured(prompts.map((p) => p.id));
|
|
35
|
+
await ctx.client?.tui
|
|
36
|
+
.showToast({
|
|
37
|
+
body: {
|
|
38
|
+
title: "User Profile Updated",
|
|
39
|
+
message: `Analyzed ${prompts.length} prompts and updated your profile`,
|
|
40
|
+
variant: "success",
|
|
41
|
+
duration: 3000,
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
.catch(() => { });
|
|
54
45
|
}
|
|
55
46
|
catch (error) {
|
|
56
47
|
log("User memory learning error", { error: String(error) });
|
|
57
48
|
await ctx.client?.tui
|
|
58
49
|
.showToast({
|
|
59
50
|
body: {
|
|
60
|
-
title: "User
|
|
51
|
+
title: "User Profile Update Failed",
|
|
61
52
|
message: String(error),
|
|
62
53
|
variant: "error",
|
|
63
54
|
duration: 5000,
|
|
@@ -66,37 +57,63 @@ export async function performUserMemoryLearning(ctx, directory) {
|
|
|
66
57
|
.catch(() => { });
|
|
67
58
|
}
|
|
68
59
|
}
|
|
69
|
-
function
|
|
70
|
-
|
|
60
|
+
function generateChangeSummary(oldProfile, newProfile) {
|
|
61
|
+
const changes = [];
|
|
62
|
+
const prefDiff = newProfile.preferences.length - oldProfile.preferences.length;
|
|
63
|
+
if (prefDiff > 0)
|
|
64
|
+
changes.push(`+${prefDiff} preferences`);
|
|
65
|
+
const patternDiff = newProfile.patterns.length - oldProfile.patterns.length;
|
|
66
|
+
if (patternDiff > 0)
|
|
67
|
+
changes.push(`+${patternDiff} patterns`);
|
|
68
|
+
const workflowDiff = newProfile.workflows.length - oldProfile.workflows.length;
|
|
69
|
+
if (workflowDiff > 0)
|
|
70
|
+
changes.push(`+${workflowDiff} workflows`);
|
|
71
|
+
return changes.length > 0 ? changes.join(", ") : "Profile refinement";
|
|
72
|
+
}
|
|
73
|
+
function buildUserAnalysisContext(prompts, existingProfile) {
|
|
74
|
+
const existingProfileSection = existingProfile
|
|
75
|
+
? `
|
|
76
|
+
## Existing User Profile
|
|
77
|
+
|
|
78
|
+
${existingProfile.profileData}
|
|
71
79
|
|
|
72
|
-
|
|
80
|
+
**Instructions**: Merge new insights with the existing profile. Update confidence scores for reinforced patterns, add new patterns, and refine existing ones.`
|
|
81
|
+
: `
|
|
82
|
+
**Instructions**: Create a new user profile from scratch based on the prompts below.`;
|
|
83
|
+
return `# User Profile Analysis
|
|
73
84
|
|
|
74
|
-
|
|
85
|
+
Analyze ${prompts.length} user prompts to ${existingProfile ? "update" : "create"} the user profile.
|
|
86
|
+
|
|
87
|
+
${existingProfileSection}
|
|
88
|
+
|
|
89
|
+
## Recent Prompts
|
|
75
90
|
|
|
76
91
|
${prompts.map((p, i) => `${i + 1}. ${p.content}`).join("\n\n")}
|
|
77
92
|
|
|
78
|
-
## Analysis
|
|
93
|
+
## Analysis Guidelines
|
|
79
94
|
|
|
80
|
-
Identify
|
|
95
|
+
Identify and ${existingProfile ? "update" : "create"}:
|
|
81
96
|
|
|
82
|
-
1. **Preferences
|
|
83
|
-
- Code style
|
|
84
|
-
-
|
|
85
|
-
-
|
|
97
|
+
1. **Preferences** (max ${CONFIG.userProfileMaxPreferences})
|
|
98
|
+
- Code style, communication style, tool preferences
|
|
99
|
+
- Assign confidence 0.5-1.0 based on evidence strength
|
|
100
|
+
- Include 1-3 example prompts as evidence
|
|
86
101
|
|
|
87
|
-
2. **Patterns
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
- Skill level indicators (e.g., "asks detailed questions about async patterns")
|
|
102
|
+
2. **Patterns** (max ${CONFIG.userProfileMaxPatterns})
|
|
103
|
+
- Recurring topics, problem domains, technical interests
|
|
104
|
+
- Track frequency of occurrence
|
|
91
105
|
|
|
92
|
-
3. **Workflows**
|
|
93
|
-
- Development
|
|
94
|
-
-
|
|
95
|
-
- Learning style (e.g., "prefers examples over explanations")
|
|
106
|
+
3. **Workflows** (max ${CONFIG.userProfileMaxWorkflows})
|
|
107
|
+
- Development sequences, habits, learning style
|
|
108
|
+
- Break down into steps if applicable
|
|
96
109
|
|
|
97
|
-
|
|
110
|
+
4. **Skill Level**
|
|
111
|
+
- Overall: beginner/intermediate/advanced
|
|
112
|
+
- Per-domain assessment (e.g., typescript: advanced, docker: beginner)
|
|
113
|
+
|
|
114
|
+
${existingProfile ? "Merge with existing profile, incrementing frequencies and updating confidence scores." : "Create initial profile with conservative confidence scores."}`;
|
|
98
115
|
}
|
|
99
|
-
async function
|
|
116
|
+
async function analyzeUserProfile(context, existingProfile) {
|
|
100
117
|
if (!CONFIG.memoryModel || !CONFIG.memoryApiUrl || !CONFIG.memoryApiKey) {
|
|
101
118
|
throw new Error("External API not configured for user memory learning");
|
|
102
119
|
}
|
|
@@ -111,54 +128,75 @@ async function analyzeUserPatterns(ctx, context) {
|
|
|
111
128
|
const provider = AIProviderFactory.createProvider(CONFIG.memoryProvider, providerConfig);
|
|
112
129
|
const systemPrompt = `You are a user behavior analyst for a coding assistant.
|
|
113
130
|
|
|
114
|
-
Your task is to analyze user prompts
|
|
115
|
-
|
|
116
|
-
Use the save_user_memories tool to save identified patterns. Only save patterns that are clearly evident from the prompts.
|
|
131
|
+
Your task is to analyze user prompts and ${existingProfile ? "update" : "create"} a comprehensive user profile.
|
|
117
132
|
|
|
118
|
-
|
|
133
|
+
Use the update_user_profile tool to save the ${existingProfile ? "updated" : "new"} profile.`;
|
|
119
134
|
const toolSchema = {
|
|
120
135
|
type: "function",
|
|
121
136
|
function: {
|
|
122
|
-
name: "
|
|
123
|
-
description:
|
|
137
|
+
name: "update_user_profile",
|
|
138
|
+
description: existingProfile
|
|
139
|
+
? "Update existing user profile with new insights"
|
|
140
|
+
: "Create new user profile",
|
|
124
141
|
parameters: {
|
|
125
142
|
type: "object",
|
|
126
143
|
properties: {
|
|
127
|
-
|
|
144
|
+
preferences: {
|
|
128
145
|
type: "array",
|
|
129
|
-
description: "Array of identified patterns (1-5 memories)",
|
|
130
146
|
items: {
|
|
131
147
|
type: "object",
|
|
132
148
|
properties: {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
},
|
|
137
|
-
scope: {
|
|
138
|
-
type: "string",
|
|
139
|
-
enum: ["user"],
|
|
140
|
-
description: "Memory scope (must be 'user' for user learning)",
|
|
141
|
-
},
|
|
142
|
-
type: {
|
|
143
|
-
type: "string",
|
|
144
|
-
description: "Type of insight (e.g., preference, pattern, workflow, skill-level, communication-style)",
|
|
145
|
-
},
|
|
146
|
-
reasoning: {
|
|
147
|
-
type: "string",
|
|
148
|
-
description: "Why this pattern is significant (optial)",
|
|
149
|
-
},
|
|
149
|
+
category: { type: "string" },
|
|
150
|
+
description: { type: "string" },
|
|
151
|
+
confidence: { type: "number", minimum: 0, maximum: 1 },
|
|
152
|
+
evidence: { type: "array", items: { type: "string" }, maxItems: 3 },
|
|
150
153
|
},
|
|
151
|
-
required: ["
|
|
154
|
+
required: ["category", "description", "confidence", "evidence"],
|
|
152
155
|
},
|
|
153
156
|
},
|
|
157
|
+
patterns: {
|
|
158
|
+
type: "array",
|
|
159
|
+
items: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
category: { type: "string" },
|
|
163
|
+
description: { type: "string" },
|
|
164
|
+
},
|
|
165
|
+
required: ["category", "description"],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
workflows: {
|
|
169
|
+
type: "array",
|
|
170
|
+
items: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {
|
|
173
|
+
description: { type: "string" },
|
|
174
|
+
steps: { type: "array", items: { type: "string" } },
|
|
175
|
+
},
|
|
176
|
+
required: ["description", "steps"],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
skillLevel: {
|
|
180
|
+
type: "object",
|
|
181
|
+
properties: {
|
|
182
|
+
overall: { type: "string", enum: ["beginner", "intermediate", "advanced"] },
|
|
183
|
+
domains: { type: "object", additionalProperties: { type: "string" } },
|
|
184
|
+
},
|
|
185
|
+
required: ["overall", "domains"],
|
|
186
|
+
},
|
|
154
187
|
},
|
|
155
|
-
required: ["
|
|
188
|
+
required: ["preferences", "patterns", "workflows", "skillLevel"],
|
|
156
189
|
},
|
|
157
190
|
},
|
|
158
191
|
};
|
|
159
|
-
const result = await provider.executeToolCall(systemPrompt, context, toolSchema, "user-
|
|
192
|
+
const result = await provider.executeToolCall(systemPrompt, context, toolSchema, "user-profile");
|
|
160
193
|
if (!result.success || !result.data) {
|
|
161
|
-
throw new Error(result.error || "Failed to analyze user
|
|
194
|
+
throw new Error(result.error || "Failed to analyze user profile");
|
|
195
|
+
}
|
|
196
|
+
const rawData = result.data;
|
|
197
|
+
if (existingProfile) {
|
|
198
|
+
const existingData = JSON.parse(existingProfile.profileData);
|
|
199
|
+
return userProfileManager.mergeProfileData(existingData, rawData);
|
|
162
200
|
}
|
|
163
|
-
return
|
|
201
|
+
return rawData;
|
|
164
202
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile-context.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/profile-context.ts"],"names":[],"mappings":"AAGA,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA6CnE"}
|