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 +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 -10
- 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/user-memory-learning.d.ts.map +1 -1
- package/dist/services/user-memory-learning.js +120 -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/types/index.d.ts +1 -3
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
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
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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 [
|
|
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
|
|
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
|
|
413
|
-
|
|
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:
|
|
416
|
-
|
|
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
|
-
|
|
423
|
-
|
|
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;
|
|
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":"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,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
|
|
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 ? "Update existing user profile with new insights" : "Create new user profile",
|
|
124
139
|
parameters: {
|
|
125
140
|
type: "object",
|
|
126
141
|
properties: {
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
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: ["
|
|
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: ["
|
|
186
|
+
required: ["preferences", "patterns", "workflows", "skillLevel"],
|
|
156
187
|
},
|
|
157
188
|
},
|
|
158
189
|
};
|
|
159
|
-
const result = await provider.executeToolCall(systemPrompt, context, toolSchema, "user-
|
|
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
|
|
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
|
|
199
|
+
return rawData;
|
|
164
200
|
}
|
|
@@ -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();
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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"
|
|
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,
|
|
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"}
|