opencode-mem 2.0.1 → 2.1.1

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/dist/config.d.ts CHANGED
@@ -28,6 +28,11 @@ export declare const CONFIG: {
28
28
  deduplicationEnabled: boolean;
29
29
  deduplicationSimilarityThreshold: number;
30
30
  userMemoryAnalysisInterval: number;
31
+ userProfileMaxPreferences: number;
32
+ userProfileMaxPatterns: number;
33
+ userProfileMaxWorkflows: number;
34
+ userProfileConfidenceDecayDays: number;
35
+ userProfileChangelogRetentionCount: number;
31
36
  };
32
37
  export declare function isConfigured(): boolean;
33
38
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAmTA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;oBAwBb,aAAa,GACb,kBAAkB,GAClB,WAAW;;;;;;;;;;;;;;CAiBhB,CAAC;AAEF,wBAAgB,YAAY,IAAI,OAAO,CAEtC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AA2WA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;oBAwBb,aAAa,GACb,kBAAkB,GAClB,WAAW;;;;;;;;;;;;;;;;;;;CAyBhB,CAAC;AAEF,wBAAgB,YAAY,IAAI,OAAO,CAEtC"}
package/dist/config.js CHANGED
@@ -56,6 +56,11 @@ const DEFAULTS = {
56
56
  deduplicationEnabled: true,
57
57
  deduplicationSimilarityThreshold: 0.9,
58
58
  userMemoryAnalysisInterval: 10,
59
+ userProfileMaxPreferences: 20,
60
+ userProfileMaxPatterns: 15,
61
+ userProfileMaxWorkflows: 10,
62
+ userProfileConfidenceDecayDays: 30,
63
+ userProfileChangelogRetentionCount: 5,
59
64
  };
60
65
  function isValidRegex(pattern) {
61
66
  try {
@@ -121,18 +126,32 @@ const CONFIG_TEMPLATE = `{
121
126
  // Web Server Settings
122
127
  // ============================================
123
128
 
129
+ // Enable web UI for managing memories (accessible at http://localhost:4747)
124
130
  "webServerEnabled": true,
131
+
132
+ // Port for web UI server
125
133
  "webServerPort": 4747,
134
+
135
+ // Host address for web UI (use 127.0.0.1 for local only, 0.0.0.0 for network access)
126
136
  "webServerHost": "127.0.0.1",
127
137
 
128
138
  // ============================================
129
139
  // Database Settings
130
140
  // ============================================
131
141
 
142
+ // Maximum vectors per database shard (auto-creates new shard when limit reached)
132
143
  "maxVectorsPerShard": 50000,
144
+
145
+ // Automatically delete old memories based on retention period
133
146
  "autoCleanupEnabled": true,
147
+
148
+ // Days to keep memories before auto-cleanup (only if autoCleanupEnabled is true)
134
149
  "autoCleanupRetentionDays": 30,
150
+
151
+ // Automatically detect and remove duplicate memories
135
152
  "deduplicationEnabled": true,
153
+
154
+ // Similarity threshold (0-1) for detecting duplicates (higher = stricter)
136
155
  "deduplicationSimilarityThreshold": 0.90,
137
156
 
138
157
  // ============================================
@@ -178,37 +197,69 @@ const CONFIG_TEMPLATE = `{
178
197
  // "memoryApiUrl": "https://api.groq.com/openai/v1"
179
198
  // "memoryApiKey": "gsk_..."
180
199
 
181
- // Multi-iteration settings (for openai-responses and anthropic)
200
+ // Maximum iterations for multi-turn AI analysis (for openai-responses and anthropic)
182
201
  "autoCaptureMaxIterations": 5,
202
+
203
+ // Timeout per iteration in milliseconds (30 seconds default)
183
204
  "autoCaptureIterationTimeout": 30000,
184
205
 
185
- // Session management
206
+ // Days to keep AI session history before cleanup
186
207
  "aiSessionRetentionDays": 7,
187
208
 
188
209
  // ============================================
189
210
  // User Memory Learning
190
211
  // ============================================
191
212
 
192
- // Analyze user prompts every N prompts to learn patterns and preferences
213
+ // Analyze user prompts every N prompts to build/update your user profile
193
214
  // When N uncaptured prompts accumulate, AI will analyze them to identify:
194
- // - User preferences (code style, communication style)
195
- // - User patterns (recurring topics, problem domains)
196
- // - User workflows (development habits, sequences)
215
+ // - User preferences (code style, communication style, tool preferences)
216
+ // - User patterns (recurring topics, problem domains, technical interests)
217
+ // - User workflows (development habits, sequences, learning style)
218
+ // - Skill level (overall and per-domain assessment)
197
219
  "userMemoryAnalysisInterval": 10,
198
220
 
221
+ // Maximum number of preferences to keep in user profile (sorted by confidence)
222
+ // Preferences are things like "prefers code without comments", "likes concise responses"
223
+ "userProfileMaxPreferences": 20,
224
+
225
+ // Maximum number of patterns to keep in user profile (sorted by frequency)
226
+ // Patterns are recurring topics like "often asks about database optimization"
227
+ "userProfileMaxPatterns": 15,
228
+
229
+ // Maximum number of workflows to keep in user profile (sorted by frequency)
230
+ // Workflows are sequences like "usually asks for tests after implementation"
231
+ "userProfileMaxWorkflows": 10,
232
+
233
+ // Days before preference confidence starts to decay (if not reinforced)
234
+ // Preferences that aren't seen again will gradually lose confidence and be removed
235
+ "userProfileConfidenceDecayDays": 30,
236
+
237
+ // Number of profile versions to keep in changelog (for rollback/debugging)
238
+ // Older versions are automatically cleaned up
239
+ "userProfileChangelogRetentionCount": 5,
240
+
199
241
  // ============================================
200
242
  // Search Settings
201
243
  // ============================================
202
244
 
245
+ // Minimum similarity score (0-1) for memory search results
203
246
  "similarityThreshold": 0.6,
247
+
248
+ // Maximum number of user-scoped memories to return in search
204
249
  "maxMemories": 5,
250
+
251
+ // Maximum number of project-scoped memories to return in search
205
252
  "maxProjectMemories": 10,
206
253
 
207
254
  // ============================================
208
255
  // Advanced Settings
209
256
  // ============================================
210
257
 
258
+ // Inject user profile into AI context (preferences, patterns, workflows)
211
259
  "injectProfile": true,
260
+
261
+ // Additional regex patterns to trigger manual memory capture
262
+ // Default patterns: "remember", "memorize", "save this", "note this", etc.
212
263
  "keywordPatterns": []
213
264
  }
214
265
  `;
@@ -278,6 +329,11 @@ export const CONFIG = {
278
329
  deduplicationEnabled: fileConfig.deduplicationEnabled ?? DEFAULTS.deduplicationEnabled,
279
330
  deduplicationSimilarityThreshold: fileConfig.deduplicationSimilarityThreshold ?? DEFAULTS.deduplicationSimilarityThreshold,
280
331
  userMemoryAnalysisInterval: fileConfig.userMemoryAnalysisInterval ?? DEFAULTS.userMemoryAnalysisInterval,
332
+ userProfileMaxPreferences: fileConfig.userProfileMaxPreferences ?? DEFAULTS.userProfileMaxPreferences,
333
+ userProfileMaxPatterns: fileConfig.userProfileMaxPatterns ?? DEFAULTS.userProfileMaxPatterns,
334
+ userProfileMaxWorkflows: fileConfig.userProfileMaxWorkflows ?? DEFAULTS.userProfileMaxWorkflows,
335
+ userProfileConfidenceDecayDays: fileConfig.userProfileConfidenceDecayDays ?? DEFAULTS.userProfileConfidenceDecayDays,
336
+ userProfileChangelogRetentionCount: fileConfig.userProfileChangelogRetentionCount ?? DEFAULTS.userProfileChangelogRetentionCount,
281
337
  };
282
338
  export function isConfigured() {
283
339
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAyC/D,eAAO,MAAM,iBAAiB,EAAE,MA+kB/B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAyC/D,eAAO,MAAM,iBAAiB,EAAE,MAwlB/B,CAAC"}
package/dist/index.js CHANGED
@@ -184,12 +184,10 @@ export const OpenCodeMemPlugin = async (ctx) => {
184
184
  return;
185
185
  }
186
186
  }
187
- const [profileResult, userMemoriesResult, projectMemoriesListResult] = await Promise.all([
188
- memoryClient.getProfile(tags.user.tag, userMessage),
187
+ const [userMemoriesResult, projectMemoriesListResult] = await Promise.all([
189
188
  memoryClient.searchMemories(userMessage, tags.user.tag),
190
189
  memoryClient.listMemories(tags.project.tag, CONFIG.maxProjectMemories),
191
190
  ]);
192
- const profile = profileResult.success ? profileResult : null;
193
191
  const userMemories = userMemoriesResult.success ? userMemoriesResult : { results: [] };
194
192
  const projectMemoriesList = projectMemoriesListResult.success
195
193
  ? projectMemoriesListResult
@@ -205,7 +203,8 @@ export const OpenCodeMemPlugin = async (ctx) => {
205
203
  total: projectMemoriesList.memories?.length || 0,
206
204
  timing: 0,
207
205
  };
208
- const memoryContext = formatContextForPrompt(profile, userMemories, projectMemories);
206
+ const userId = tags.user.userEmail || null;
207
+ const memoryContext = formatContextForPrompt(userId, userMemories, projectMemories);
209
208
  if (memoryContext) {
210
209
  const contextPart = {
211
210
  id: `memory-context-${Date.now()}`,
@@ -409,18 +408,27 @@ export const OpenCodeMemPlugin = async (ctx) => {
409
408
  });
410
409
  }
411
410
  case "profile": {
412
- const result = await memoryClient.getProfile(tags.user.tag, args.query);
413
- if (!result.success) {
411
+ const { userProfileManager } = await import("./services/user-profile/user-profile-manager.js");
412
+ const userId = tags.user.userEmail || "unknown";
413
+ const profile = userProfileManager.getActiveProfile(userId);
414
+ if (!profile) {
414
415
  return JSON.stringify({
415
- success: false,
416
- error: result.error || "Failed to fetch profile",
416
+ success: true,
417
+ profile: null,
418
+ message: "No user profile found",
417
419
  });
418
420
  }
421
+ const profileData = JSON.parse(profile.profileData);
419
422
  return JSON.stringify({
420
423
  success: true,
421
424
  profile: {
422
- static: result.profile?.static || [],
423
- dynamic: result.profile?.dynamic || [],
425
+ preferences: profileData.preferences,
426
+ patterns: profileData.patterns,
427
+ workflows: profileData.workflows,
428
+ skillLevel: profileData.skillLevel,
429
+ version: profile.version,
430
+ lastAnalyzed: profile.lastAnalyzedAt,
431
+ totalPromptsAnalyzed: profile.totalPromptsAnalyzed,
424
432
  },
425
433
  });
426
434
  }
@@ -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;AACpD,OAAO,KAAK,EAAgB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AA2CpE,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,aAAa,CAAkB;;YAIzB,UAAU;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,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM;;;;;;;;;IAyC/C,SAAS,CACb,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,UAAU,CAAC;QAClB,MAAM,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,QAAQ,GAAG,KAAK,CAAC;QACtD,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB;;;;;;;;;IAqDG,YAAY,CAAC,QAAQ,EAAE,MAAM;;;;;;;IA2B7B,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,SAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuDpD;AAED,eAAO,MAAM,YAAY,mBAA0B,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"}
@@ -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
- interface ProfileResponse {
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":"AAEA,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,UAAU,eAAe;IACvB,OAAO,CAAC,EAAE;QACR,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;CACH;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,eAAe,GAAG,IAAI,EAC/B,YAAY,EAAE,uBAAuB,EACrC,eAAe,EAAE,uBAAuB,GACvC,MAAM,CA8CR"}
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"}
@@ -1,19 +1,11 @@
1
1
  import { CONFIG } from "../config.js";
2
- export function formatContextForPrompt(profile, userMemories, projectMemories) {
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 && profile?.profile) {
5
- const { static: staticFacts, dynamic: dynamicFacts } = profile.profile;
6
- if (staticFacts.length > 0) {
7
- parts.push("\nUser Profile:");
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":"user-memory-learning.d.ts","sourceRoot":"","sources":["../../src/services/user-memory-learning.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAQvD,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAyEf"}
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 context = buildUserAnalysisContext(prompts);
18
- const memories = await analyzeUserPatterns(ctx, context);
19
- if (!memories || memories.length === 0) {
20
- log("User memory learning: no patterns identified", { promptCount: prompts.length });
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
- const tags = getTags(directory);
25
- let savedCount = 0;
26
- for (const memory of memories) {
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
- userPromptManager.markMultipleAsUserLearningCaptured(prompts.map((p) => p.id));
42
- if (savedCount > 0) {
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 Memory Learning Failed",
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 buildUserAnalysisContext(prompts) {
70
- return `# User Prompt History Analysis
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
- Analyze the following ${prompts.length} user prompts to identify patterns, preferences, and workflows.
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
- ## Prompts
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 Instructions
93
+ ## Analysis Guidelines
79
94
 
80
- Identify user patterns and preferences from these prompts. Look for:
95
+ Identify and ${existingProfile ? "update" : "create"}:
81
96
 
82
- 1. **Preferences**: How the user likes things done
83
- - Code style preferences (e.g., "prefers code without comments")
84
- - Communication preferences (e.g., "likes concise responses")
85
- - Tool preferences (e.g., "prefers TypeScript over JavaScript")
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**: Recurring topics or requests
88
- - Technical topics (e.g., "often asks about database optimization")
89
- - Problem domains (e.g., "frequently works on authentication")
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**n sequences or habits
93
- - Development flow (e.g., "usually asks for tests after implementation")
94
- - Review habits (e.g., "always requests code review before committing")
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
- Generate 1-5 key insights as memories. Only include patterns that are clearly evident from multiple prompts.`;
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 analyzeUserPatterns(ctx, context) {
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,73 @@ 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 fy patterns, preferences, and workflows.
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
- IMPORTANT: All memories must have scope set to "user".`;
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: "save_user_memories",
123
- description: "Save identified user patterns and preferences",
137
+ name: "update_user_profile",
138
+ description: existingProfile ? "Update existing user profile with new insights" : "Create new user profile",
124
139
  parameters: {
125
140
  type: "object",
126
141
  properties: {
127
- memories: {
142
+ preferences: {
128
143
  type: "array",
129
- description: "Array of identified patterns (1-5 memories)",
130
144
  items: {
131
145
  type: "object",
132
146
  properties: {
133
- summary: {
134
- type: "string",
135
- description: "Clear description of the pattern or preference",
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
- },
147
+ category: { type: "string" },
148
+ description: { type: "string" },
149
+ confidence: { type: "number", minimum: 0, maximum: 1 },
150
+ evidence: { type: "array", items: { type: "string" }, maxItems: 3 },
150
151
  },
151
- required: ["summary", "scope", "type"],
152
+ required: ["category", "description", "confidence", "evidence"],
152
153
  },
153
154
  },
155
+ patterns: {
156
+ type: "array",
157
+ items: {
158
+ type: "object",
159
+ properties: {
160
+ category: { type: "string" },
161
+ description: { type: "string" },
162
+ },
163
+ required: ["category", "description"],
164
+ },
165
+ },
166
+ workflows: {
167
+ type: "array",
168
+ items: {
169
+ type: "object",
170
+ properties: {
171
+ description: { type: "string" },
172
+ steps: { type: "array", items: { type: "string" } },
173
+ },
174
+ required: ["description", "steps"],
175
+ },
176
+ },
177
+ skillLevel: {
178
+ type: "object",
179
+ properties: {
180
+ overall: { type: "string", enum: ["beginner", "intermediate", "advanced"] },
181
+ domains: { type: "object", additionalProperties: { type: "string" } },
182
+ },
183
+ required: ["overall", "domains"],
184
+ },
154
185
  },
155
- required: ["memories"],
186
+ required: ["preferences", "patterns", "workflows", "skillLevel"],
156
187
  },
157
188
  },
158
189
  };
159
- const result = await provider.executeToolCall(systemPrompt, context, toolSchema, "user-learning");
190
+ const result = await provider.executeToolCall(systemPrompt, context, toolSchema, "user-profile");
160
191
  if (!result.success || !result.data) {
161
- throw new Error(result.error || "Failed to analyze user patterns");
192
+ throw new Error(result.error || "Failed to analyze user profile");
193
+ }
194
+ const rawData = result.data;
195
+ if (existingProfile) {
196
+ const existingData = JSON.parse(existingProfile.profileData);
197
+ return userProfileManager.mergeProfileData(existingData, rawData);
162
198
  }
163
- return result.data.memories || [];
199
+ return rawData;
164
200
  }
@@ -0,0 +1,2 @@
1
+ export declare function getUserProfileContext(userId: string): string | null;
2
+ //# sourceMappingURL=profile-context.d.ts.map
@@ -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"}
@@ -0,0 +1,40 @@
1
+ import { userProfileManager } from "./user-profile-manager.js";
2
+ export function getUserProfileContext(userId) {
3
+ const profile = userProfileManager.getActiveProfile(userId);
4
+ if (!profile) {
5
+ return null;
6
+ }
7
+ const profileData = JSON.parse(profile.profileData);
8
+ const parts = [];
9
+ if (profileData.preferences.length > 0) {
10
+ parts.push("User Preferences:");
11
+ profileData.preferences
12
+ .sort((a, b) => b.confidence - a.confidence)
13
+ .slice(0, 5)
14
+ .forEach((pref) => {
15
+ parts.push(`- [${pref.category}] ${pref.description}`);
16
+ });
17
+ }
18
+ if (profileData.patterns.length > 0) {
19
+ parts.push("\nUser Patterns:");
20
+ profileData.patterns
21
+ .sort((a, b) => b.frequency - a.frequency)
22
+ .slice(0, 5)
23
+ .forEach((pattern) => {
24
+ parts.push(`- [${pattern.category}] ${pattern.description}`);
25
+ });
26
+ }
27
+ if (profileData.workflows.length > 0) {
28
+ parts.push("\nUser Workflows:");
29
+ profileData.workflows
30
+ .sort((a, b) => b.frequency - a.frequency)
31
+ .slice(0, 3)
32
+ .forEach((workflow) => {
33
+ parts.push(`- ${workflow.description}`);
34
+ });
35
+ }
36
+ if (parts.length === 0) {
37
+ return null;
38
+ }
39
+ return parts.join("\n");
40
+ }
@@ -0,0 +1,51 @@
1
+ export interface UserProfilePreference {
2
+ category: string;
3
+ description: string;
4
+ confidence: number;
5
+ evidence: string[];
6
+ lastUpdated: number;
7
+ }
8
+ export interface UserProfilePattern {
9
+ category: string;
10
+ description: string;
11
+ frequency: number;
12
+ lastSeen: number;
13
+ }
14
+ export interface UserProfileWorkflow {
15
+ description: string;
16
+ steps: string[];
17
+ frequency: number;
18
+ }
19
+ export interface UserProfileSkillLevel {
20
+ overall: string;
21
+ domains: Record<string, string>;
22
+ }
23
+ export interface UserProfileData {
24
+ preferences: UserProfilePreference[];
25
+ patterns: UserProfilePattern[];
26
+ workflows: UserProfileWorkflow[];
27
+ skillLevel: UserProfileSkillLevel;
28
+ }
29
+ export interface UserProfile {
30
+ id: string;
31
+ userId: string;
32
+ displayName: string;
33
+ userName: string;
34
+ userEmail: string;
35
+ profileData: string;
36
+ version: number;
37
+ createdAt: number;
38
+ lastAnalyzedAt: number;
39
+ totalPromptsAnalyzed: number;
40
+ isActive: boolean;
41
+ }
42
+ export interface UserProfileChangelog {
43
+ id: string;
44
+ profileId: string;
45
+ version: number;
46
+ changeType: string;
47
+ changeSummary: string;
48
+ profileDataSnapshot: string;
49
+ createdAt: number;
50
+ }
51
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,qBAAqB,EAAE,CAAC;IACrC,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,UAAU,EAAE,qBAAqB,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import type { UserProfile, UserProfileChangelog, UserProfileData } from "./types.js";
2
+ export declare class UserProfileManager {
3
+ private db;
4
+ private readonly dbPath;
5
+ constructor();
6
+ private initDatabase;
7
+ getActiveProfile(userId: string): UserProfile | null;
8
+ createProfile(userId: string, displayName: string, userName: string, userEmail: string, profileData: UserProfileData, promptsAnalyzed: number): string;
9
+ updateProfile(profileId: string, profileData: UserProfileData, additionalPromptsAnalyzed: number, changeSummary: string): void;
10
+ private addChangelog;
11
+ private cleanupOldChangelogs;
12
+ getProfileChangelogs(profileId: string, limit?: number): UserProfileChangelog[];
13
+ applyConfidenceDecay(profileId: string): void;
14
+ deleteProfile(profileId: string): void;
15
+ getProfileById(profileId: string): UserProfile | null;
16
+ getAllActiveProfiles(): UserProfile[];
17
+ private rowToProfile;
18
+ private rowToChangelog;
19
+ mergeProfileData(existing: UserProfileData, updates: Partial<UserProfileData>): UserProfileData;
20
+ }
21
+ export declare const userProfileManager: UserProfileManager;
22
+ //# sourceMappingURL=user-profile-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-profile-manager.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/user-profile-manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,WAAW,EACX,oBAAoB,EACpB,eAAe,EAChB,MAAM,YAAY,CAAC;AAIpB,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,EAAE,CAAW;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;IAQhC,OAAO,CAAC,YAAY;IA0CpB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAapD,aAAa,CACX,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,eAAe,EAC5B,eAAe,EAAE,MAAM,GACtB,MAAM;IA8BT,aAAa,CACX,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,eAAe,EAC5B,yBAAyB,EAAE,MAAM,EACjC,aAAa,EAAE,MAAM,GACpB,IAAI;IAuBP,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,oBAAoB;IAiB5B,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,oBAAoB,EAAE;IAYnF,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IA2B7C,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKtC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAOrD,oBAAoB,IAAI,WAAW,EAAE;IAMrC,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,cAAc;IAYtB,gBAAgB,CACd,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAChC,eAAe;CAyFnB;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
@@ -0,0 +1,267 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { join } from "node:path";
3
+ import { connectionManager } from "../sqlite/connection-manager.js";
4
+ import { CONFIG } from "../../config.js";
5
+ const USER_PROFILES_DB_NAME = "user-profiles.db";
6
+ export class UserProfileManager {
7
+ db;
8
+ dbPath;
9
+ constructor() {
10
+ this.dbPath = join(CONFIG.storagePath, USER_PROFILES_DB_NAME);
11
+ this.db = connectionManager.getConnection(this.dbPath);
12
+ this.initDatabase();
13
+ }
14
+ initDatabase() {
15
+ this.db.run(`
16
+ CREATE TABLE IF NOT EXISTS user_profiles (
17
+ id TEXT PRIMARY KEY,
18
+ user_id TEXT NOT NULL UNIQUE,
19
+ display_name TEXT NOT NULL,
20
+ user_name TEXT NOT NULL,
21
+ user_email TEXT NOT NULL,
22
+ profile_data TEXT NOT NULL,
23
+ version INTEGER NOT NULL DEFAULT 1,
24
+ created_at INTEGER NOT NULL,
25
+ last_analyzed_at INTEGER NOT NULL,
26
+ total_prompts_analyzed INTEGER NOT NULL DEFAULT 0,
27
+ is_active BOOLEAN NOT NULL DEFAULT 1
28
+ )
29
+ `);
30
+ this.db.run(`
31
+ CREATE TABLE IF NOT EXISTS user_profile_changelogs (
32
+ id TEXT PRIMARY KEY,
33
+ profile_id TEXT NOT NULL,
34
+ version INTEGER NOT NULL,
35
+ change_type TEXT NOT NULL,
36
+ change_summary TEXT NOT NULL,
37
+ profile_data_snapshot TEXT NOT NULL,
38
+ created_at INTEGER NOT NULL,
39
+ FOREIGN KEY (profile_id) REFERENCES user_profiles(id) ON DELETE CASCADE
40
+ )
41
+ `);
42
+ this.db.run("CREATE INDEX IF NOT EXISTS idx_user_profiles_user_id ON user_profiles(user_id)");
43
+ this.db.run("CREATE INDEX IF NOT EXISTS idx_user_profiles_is_active ON user_profiles(is_active)");
44
+ this.db.run("CREATE INDEX IF NOT EXISTS idx_user_profile_changelogs_profile_id ON user_profile_changelogs(profile_id)");
45
+ this.db.run("CREATE INDEX IF NOT EXISTS idx_user_profile_changelogs_version ON user_profile_changelogs(version DESC)");
46
+ }
47
+ getActiveProfile(userId) {
48
+ const stmt = this.db.prepare(`
49
+ SELECT * FROM user_profiles
50
+ WHERE user_id = ? AND is_active = 1
51
+ LIMIT 1
52
+ `);
53
+ const row = stmt.get(userId);
54
+ if (!row)
55
+ return null;
56
+ return this.rowToProfile(row);
57
+ }
58
+ createProfile(userId, displayName, userName, userEmail, profileData, promptsAnalyzed) {
59
+ const id = `profile_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
60
+ const now = Date.now();
61
+ const stmt = this.db.prepare(`
62
+ INSERT INTO user_profiles (
63
+ id, user_id, display_name, user_name, user_email,
64
+ profile_data, version, created_at, last_analyzed_at,
65
+ total_prompts_analyzed, is_active
66
+ )
67
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, 1)
68
+ `);
69
+ stmt.run(id, userId, displayName, userName, userEmail, JSON.stringify(profileData), now, now, promptsAnalyzed);
70
+ this.addChangelog(id, 1, "create", "Initial profile creation", profileData);
71
+ return id;
72
+ }
73
+ updateProfile(profileId, profileData, additionalPromptsAnalyzed, changeSummary) {
74
+ const now = Date.now();
75
+ const getVersionStmt = this.db.prepare(`SELECT version FROM user_profiles WHERE id = ?`);
76
+ const versionRow = getVersionStmt.get(profileId);
77
+ const newVersion = (versionRow?.version || 0) + 1;
78
+ const updateStmt = this.db.prepare(`
79
+ UPDATE user_profiles
80
+ SET profile_data = ?,
81
+ version = ?,
82
+ last_analyzed_at = ?,
83
+ total_prompts_analyzed = total_prompts_analyzed + ?
84
+ WHERE id = ?
85
+ `);
86
+ updateStmt.run(JSON.stringify(profileData), newVersion, now, additionalPromptsAnalyzed, profileId);
87
+ this.addChangelog(profileId, newVersion, "update", changeSummary, profileData);
88
+ this.cleanupOldChangelogs(profileId);
89
+ }
90
+ addChangelog(profileId, version, changeType, changeSummary, profileData) {
91
+ const id = `changelog_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
92
+ const now = Date.now();
93
+ const stmt = this.db.prepare(`
94
+ INSERT INTO user_profile_changelogs (
95
+ id, profile_id, version, change_type, change_summary,
96
+ profile_data_snapshot, created_at
97
+ )
98
+ VALUES (?, ?, ?, ?, ?, ?, ?)
99
+ `);
100
+ stmt.run(id, profileId, version, changeType, changeSummary, JSON.stringify(profileData), now);
101
+ }
102
+ cleanupOldChangelogs(profileId) {
103
+ const retentionCount = CONFIG.userProfileChangelogRetentionCount;
104
+ const stmt = this.db.prepare(`
105
+ DELETE FROM user_profile_changelogs
106
+ WHERE profile_id = ?
107
+ AND id NOT IN (
108
+ SELECT id FROM user_profile_changelogs
109
+ WHERE profile_id = ?
110
+ ORDER BY version DESC
111
+ LIMIT ?
112
+ )
113
+ `);
114
+ stmt.run(profileId, profileId, retentionCount);
115
+ }
116
+ getProfileChangelogs(profileId, limit = 10) {
117
+ const stmt = this.db.prepare(`
118
+ SELECT * FROM user_profile_changelogs
119
+ WHERE profile_id = ?
120
+ ORDER BY version DESC
121
+ LIMIT ?
122
+ `);
123
+ const rows = stmt.all(profileId, limit);
124
+ return rows.map((row) => this.rowToChangelog(row));
125
+ }
126
+ applyConfidenceDecay(profileId) {
127
+ const profile = this.getProfileById(profileId);
128
+ if (!profile)
129
+ return;
130
+ const profileData = JSON.parse(profile.profileData);
131
+ const now = Date.now();
132
+ const decayThreshold = CONFIG.userProfileConfidenceDecayDays * 24 * 60 * 60 * 1000;
133
+ let hasChanges = false;
134
+ profileData.preferences = profileData.preferences
135
+ .map((pref) => {
136
+ const age = now - pref.lastUpdated;
137
+ if (age > decayThreshold) {
138
+ hasChanges = true;
139
+ const decayFactor = Math.max(0.5, 1 - (age - decayThreshold) / decayThreshold);
140
+ return { ...pref, confidence: pref.confidence * decayFactor };
141
+ }
142
+ return pref;
143
+ })
144
+ .filter((pref) => pref.confidence >= 0.3);
145
+ if (hasChanges) {
146
+ this.updateProfile(profileId, profileData, 0, "Applied confidence decay to preferences");
147
+ }
148
+ }
149
+ deleteProfile(profileId) {
150
+ const stmt = this.db.prepare(`DELETE FROM user_profiles WHERE id = ?`);
151
+ stmt.run(profileId);
152
+ }
153
+ getProfileById(profileId) {
154
+ const stmt = this.db.prepare(`SELECT * FROM user_profiles WHERE id = ?`);
155
+ const row = stmt.get(profileId);
156
+ if (!row)
157
+ return null;
158
+ return this.rowToProfile(row);
159
+ }
160
+ getAllActiveProfiles() {
161
+ const stmt = this.db.prepare(`SELECT * FROM user_profiles WHERE is_active = 1`);
162
+ const rows = stmt.all();
163
+ return rows.map((row) => this.rowToProfile(row));
164
+ }
165
+ rowToProfile(row) {
166
+ return {
167
+ id: row.id,
168
+ userId: row.user_id,
169
+ displayName: row.display_name,
170
+ userName: row.user_name,
171
+ userEmail: row.user_email,
172
+ profileData: row.profile_data,
173
+ version: row.version,
174
+ createdAt: row.created_at,
175
+ lastAnalyzedAt: row.last_analyzed_at,
176
+ totalPromptsAnalyzed: row.total_prompts_analyzed,
177
+ isActive: row.is_active === 1,
178
+ };
179
+ }
180
+ rowToChangelog(row) {
181
+ return {
182
+ id: row.id,
183
+ profileId: row.profile_id,
184
+ version: row.version,
185
+ changeType: row.change_type,
186
+ changeSummary: row.change_summary,
187
+ profileDataSnapshot: row.profile_data_snapshot,
188
+ createdAt: row.created_at,
189
+ };
190
+ }
191
+ mergeProfileData(existing, updates) {
192
+ const merged = {
193
+ preferences: [...existing.preferences],
194
+ patterns: [...existing.patterns],
195
+ workflows: [...existing.workflows],
196
+ skillLevel: { ...existing.skillLevel },
197
+ };
198
+ if (updates.preferences) {
199
+ for (const newPref of updates.preferences) {
200
+ const existingIndex = merged.preferences.findIndex((p) => p.category === newPref.category && p.description === newPref.description);
201
+ if (existingIndex >= 0) {
202
+ const existing = merged.preferences[existingIndex];
203
+ if (existing) {
204
+ merged.preferences[existingIndex] = {
205
+ ...newPref,
206
+ confidence: Math.min(1, existing.confidence + 0.1),
207
+ evidence: [...new Set([...existing.evidence, ...newPref.evidence])].slice(0, 5),
208
+ lastUpdated: Date.now(),
209
+ };
210
+ }
211
+ }
212
+ else {
213
+ merged.preferences.push({ ...newPref, lastUpdated: Date.now() });
214
+ }
215
+ }
216
+ merged.preferences.sort((a, b) => b.confidence - a.confidence);
217
+ merged.preferences = merged.preferences.slice(0, CONFIG.userProfileMaxPreferences);
218
+ }
219
+ if (updates.patterns) {
220
+ for (const newPattern of updates.patterns) {
221
+ const existingIndex = merged.patterns.findIndex((p) => p.category === newPattern.category && p.description === newPattern.description);
222
+ if (existingIndex >= 0) {
223
+ const existing = merged.patterns[existingIndex];
224
+ if (existing) {
225
+ merged.patterns[existingIndex] = {
226
+ ...newPattern,
227
+ frequency: existing.frequency + 1,
228
+ lastSeen: Date.now(),
229
+ };
230
+ }
231
+ }
232
+ else {
233
+ merged.patterns.push({ ...newPattern, frequency: 1, lastSeen: Date.now() });
234
+ }
235
+ }
236
+ merged.patterns.sort((a, b) => b.frequency - a.frequency);
237
+ merged.patterns = merged.patterns.slice(0, CONFIG.userProfileMaxPatterns);
238
+ }
239
+ if (updates.workflows) {
240
+ for (const newWorkflow of updates.workflows) {
241
+ const existingIndex = merged.workflows.findIndex((w) => w.description === newWorkflow.description);
242
+ if (existingIndex >= 0) {
243
+ const existing = merged.workflows[existingIndex];
244
+ if (existing) {
245
+ merged.workflows[existingIndex] = {
246
+ ...newWorkflow,
247
+ frequency: existing.frequency + 1,
248
+ };
249
+ }
250
+ }
251
+ else {
252
+ merged.workflows.push({ ...newWorkflow, frequency: 1 });
253
+ }
254
+ }
255
+ merged.workflows.sort((a, b) => b.frequency - a.frequency);
256
+ merged.workflows = merged.workflows.slice(0, CONFIG.userProfileMaxWorkflows);
257
+ }
258
+ if (updates.skillLevel) {
259
+ merged.skillLevel = {
260
+ overall: updates.skillLevel.overall || merged.skillLevel.overall,
261
+ domains: { ...merged.skillLevel.domains, ...updates.skillLevel.domains },
262
+ };
263
+ }
264
+ return merged;
265
+ }
266
+ }
267
+ export const userProfileManager = new UserProfileManager();
@@ -32,13 +32,11 @@ export interface ConversationIngestResponse {
32
32
  }
33
33
  export interface MemoryMetadata {
34
34
  type?: MemoryType;
35
- source?: "manual" | "auto-capture" | "import" | "api" | "user-learning";
35
+ source?: "manual" | "auto-capture" | "import" | "api";
36
36
  tool?: string;
37
37
  sessionID?: string;
38
38
  reasoning?: string;
39
39
  captureTimestamp?: number;
40
- promptCount?: number;
41
- analysisTimestamp?: number;
42
40
  promptId?: string;
43
41
  displayName?: string;
44
42
  userName?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;AAE7C,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC;AAEhC,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC;AAExE,MAAM,MAAM,uBAAuB,GAC/B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAErD,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,uBAAuB,EAAE,CAAC;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,MAAM,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,QAAQ,GAAG,KAAK,GAAG,eAAe,CAAC;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,kBAAkB,GAAG,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;AAE7C,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC;AAEhC,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC;AAExE,MAAM,MAAM,uBAAuB,GAC/B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAErD,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,uBAAuB,EAAE,CAAC;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,MAAM,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,QAAQ,GAAG,KAAK,CAAC;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,kBAAkB,GAAG,WAAW,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-mem",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "description": "OpenCode plugin that gives coding agents persistent memory using local vector database",
5
5
  "type": "module",
6
6
  "main": "dist/plugin.js",