opencode-mem 2.3.7 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -19
- package/dist/config.d.ts +4 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +33 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -82
- package/dist/services/ai/validators/user-profile-validator.d.ts +0 -1
- package/dist/services/ai/validators/user-profile-validator.d.ts.map +1 -1
- package/dist/services/ai/validators/user-profile-validator.js +0 -17
- package/dist/services/api-handlers.d.ts +32 -3
- package/dist/services/api-handlers.d.ts.map +1 -1
- package/dist/services/api-handlers.js +89 -13
- package/dist/services/auto-capture.d.ts.map +1 -1
- package/dist/services/auto-capture.js +34 -34
- package/dist/services/language-detector.d.ts +3 -0
- package/dist/services/language-detector.d.ts.map +1 -0
- package/dist/services/language-detector.js +16 -0
- package/dist/services/secret-resolver.d.ts +2 -0
- package/dist/services/secret-resolver.d.ts.map +1 -0
- package/dist/services/secret-resolver.js +55 -0
- package/dist/services/user-memory-learning.d.ts.map +1 -1
- package/dist/services/user-memory-learning.js +27 -33
- package/dist/services/user-profile/types.d.ts +0 -5
- package/dist/services/user-profile/types.d.ts.map +1 -1
- package/dist/services/user-profile/user-profile-manager.d.ts.map +1 -1
- package/dist/services/user-profile/user-profile-manager.js +0 -7
- package/dist/services/user-prompt/user-prompt-manager.d.ts +2 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts.map +1 -1
- package/dist/services/user-prompt/user-prompt-manager.js +21 -0
- package/dist/web/app.js +191 -112
- package/dist/web/styles.css +202 -122
- package/package.json +3 -1
|
@@ -381,19 +381,22 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
|
|
|
381
381
|
}
|
|
382
382
|
await embeddingService.warmup();
|
|
383
383
|
const queryVector = await embeddingService.embedWithTimeout(query);
|
|
384
|
-
let
|
|
384
|
+
let memoryResults = [];
|
|
385
|
+
let promptResults = [];
|
|
385
386
|
if (tag) {
|
|
386
387
|
const { scope, hash } = extractScopeFromTag(tag);
|
|
387
388
|
const shards = shardManager.getAllShards(scope, hash);
|
|
388
389
|
for (const shard of shards) {
|
|
389
390
|
try {
|
|
390
391
|
const results = vectorSearch.searchInShard(shard, queryVector, tag, pageSize * 2);
|
|
391
|
-
|
|
392
|
+
memoryResults.push(...results);
|
|
392
393
|
}
|
|
393
394
|
catch (error) {
|
|
394
395
|
log("Shard search error", { shardId: shard.id, error: String(error) });
|
|
395
396
|
}
|
|
396
397
|
}
|
|
398
|
+
const projectPath = getProjectPathFromTag(tag);
|
|
399
|
+
promptResults = userPromptManager.searchPrompts(query, projectPath, pageSize * 2);
|
|
397
400
|
}
|
|
398
401
|
else {
|
|
399
402
|
const projectShards = shardManager.getAllShards("project", "");
|
|
@@ -414,26 +417,33 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
|
|
|
414
417
|
for (const shard of shards) {
|
|
415
418
|
try {
|
|
416
419
|
const results = vectorSearch.searchInShard(shard, queryVector, containerTag, pageSize);
|
|
417
|
-
|
|
420
|
+
memoryResults.push(...results);
|
|
418
421
|
}
|
|
419
422
|
catch (error) {
|
|
420
423
|
log("Shard search error", { shardId: shard.id, error: String(error) });
|
|
421
424
|
}
|
|
422
425
|
}
|
|
423
426
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
427
|
+
promptResults = userPromptManager.searchPrompts(query, undefined, pageSize * 2);
|
|
428
|
+
}
|
|
429
|
+
const formattedPrompts = promptResults.map((p) => ({
|
|
430
|
+
type: "prompt",
|
|
431
|
+
id: p.id,
|
|
432
|
+
sessionId: p.sessionId,
|
|
433
|
+
content: p.content,
|
|
434
|
+
createdAt: safeToISOString(p.createdAt),
|
|
435
|
+
projectPath: p.projectPath,
|
|
436
|
+
linkedMemoryId: p.linkedMemoryId,
|
|
437
|
+
similarity: 1.0,
|
|
438
|
+
}));
|
|
439
|
+
const formattedMemories = memoryResults.map((r) => ({
|
|
440
|
+
type: "memory",
|
|
431
441
|
id: r.id,
|
|
432
442
|
content: r.memory,
|
|
433
|
-
|
|
443
|
+
memoryType: r.metadata?.type,
|
|
434
444
|
createdAt: safeToISOString(r.metadata?.createdAt),
|
|
435
445
|
updatedAt: r.metadata?.updatedAt ? safeToISOString(r.metadata.updatedAt) : undefined,
|
|
436
|
-
similarity:
|
|
446
|
+
similarity: r.similarity,
|
|
437
447
|
metadata: r.metadata,
|
|
438
448
|
displayName: r.displayName,
|
|
439
449
|
userName: r.userName,
|
|
@@ -442,11 +452,77 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
|
|
|
442
452
|
projectName: r.projectName,
|
|
443
453
|
gitRepoUrl: r.gitRepoUrl,
|
|
444
454
|
isPinned: r.isPinned === 1,
|
|
455
|
+
linkedPromptId: r.metadata?.promptId,
|
|
445
456
|
}));
|
|
457
|
+
const combinedResults = [...formattedMemories, ...formattedPrompts].sort((a, b) => (b.similarity || 0) - (a.similarity || 0) || b.createdAt.localeCompare(a.createdAt));
|
|
458
|
+
const total = combinedResults.length;
|
|
459
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
460
|
+
const offset = (page - 1) * pageSize;
|
|
461
|
+
const paginatedResults = combinedResults.slice(offset, offset + pageSize);
|
|
462
|
+
const missingPromptIds = new Set();
|
|
463
|
+
const missingMemoryIds = new Set();
|
|
464
|
+
for (const item of paginatedResults) {
|
|
465
|
+
if (item.type === "memory" && item.linkedPromptId) {
|
|
466
|
+
const hasPrompt = paginatedResults.some((p) => p.id === item.linkedPromptId);
|
|
467
|
+
if (!hasPrompt)
|
|
468
|
+
missingPromptIds.add(item.linkedPromptId);
|
|
469
|
+
}
|
|
470
|
+
else if (item.type === "prompt" && item.linkedMemoryId) {
|
|
471
|
+
const hasMemory = paginatedResults.some((m) => m.id === item.linkedMemoryId);
|
|
472
|
+
if (!hasMemory)
|
|
473
|
+
missingMemoryIds.add(item.linkedMemoryId);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (missingPromptIds.size > 0) {
|
|
477
|
+
const extraPrompts = userPromptManager.getPromptsByIds(Array.from(missingPromptIds));
|
|
478
|
+
for (const p of extraPrompts) {
|
|
479
|
+
paginatedResults.push({
|
|
480
|
+
type: "prompt",
|
|
481
|
+
id: p.id,
|
|
482
|
+
sessionId: p.sessionId,
|
|
483
|
+
content: p.content,
|
|
484
|
+
createdAt: safeToISOString(p.createdAt),
|
|
485
|
+
projectPath: p.projectPath,
|
|
486
|
+
linkedMemoryId: p.linkedMemoryId,
|
|
487
|
+
similarity: 0,
|
|
488
|
+
isContext: true,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (missingMemoryIds.size > 0) {
|
|
493
|
+
const projectShards = shardManager.getAllShards("project", "");
|
|
494
|
+
for (const shard of projectShards) {
|
|
495
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
496
|
+
for (const mid of missingMemoryIds) {
|
|
497
|
+
const m = vectorSearch.getMemoryById(db, mid);
|
|
498
|
+
if (m && !paginatedResults.some((existing) => existing.id === m.id)) {
|
|
499
|
+
paginatedResults.push({
|
|
500
|
+
type: "memory",
|
|
501
|
+
id: m.id,
|
|
502
|
+
content: m.content,
|
|
503
|
+
memoryType: m.type,
|
|
504
|
+
createdAt: safeToISOString(m.created_at),
|
|
505
|
+
updatedAt: m.updated_at ? safeToISOString(m.updated_at) : undefined,
|
|
506
|
+
similarity: 0,
|
|
507
|
+
metadata: safeJSONParse(m.metadata),
|
|
508
|
+
displayName: m.display_name,
|
|
509
|
+
userName: m.user_name,
|
|
510
|
+
userEmail: m.user_email,
|
|
511
|
+
projectPath: m.project_path,
|
|
512
|
+
projectName: m.project_name,
|
|
513
|
+
gitRepoUrl: m.git_repo_url,
|
|
514
|
+
isPinned: m.is_pinned === 1,
|
|
515
|
+
linkedPromptId: safeJSONParse(m.metadata)?.promptId,
|
|
516
|
+
isContext: true,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
446
522
|
return {
|
|
447
523
|
success: true,
|
|
448
524
|
data: {
|
|
449
|
-
items:
|
|
525
|
+
items: paginatedResults,
|
|
450
526
|
total,
|
|
451
527
|
page,
|
|
452
528
|
pageSize,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-capture.d.ts","sourceRoot":"","sources":["../../src/services/auto-capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAcvD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"auto-capture.d.ts","sourceRoot":"","sources":["../../src/services/auto-capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAcvD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAoGf"}
|
|
@@ -37,7 +37,7 @@ export async function performAutoCapture(ctx, sessionID, directory) {
|
|
|
37
37
|
const tags = getTags(directory);
|
|
38
38
|
const latestMemory = await getLatestProjectMemory(tags.project.tag);
|
|
39
39
|
const context = buildMarkdownContext(prompt.content, textResponses, toolCalls, latestMemory);
|
|
40
|
-
const summaryResult = await generateSummary(context, sessionID);
|
|
40
|
+
const summaryResult = await generateSummary(context, sessionID, prompt.content);
|
|
41
41
|
if (!summaryResult || summaryResult.type === "skip") {
|
|
42
42
|
log("Auto-capture: skipped non-technical conversation", { sessionID });
|
|
43
43
|
userPromptManager.deletePrompt(prompt.id);
|
|
@@ -59,31 +59,35 @@ export async function performAutoCapture(ctx, sessionID, directory) {
|
|
|
59
59
|
if (result.success) {
|
|
60
60
|
userPromptManager.linkMemoryToPrompt(prompt.id, result.id);
|
|
61
61
|
userPromptManager.markAsCaptured(prompt.id);
|
|
62
|
+
if (CONFIG.showAutoCaptureToasts) {
|
|
63
|
+
await ctx.client?.tui
|
|
64
|
+
.showToast({
|
|
65
|
+
body: {
|
|
66
|
+
title: "Memory Captured",
|
|
67
|
+
message: "Project memory saved from conversation",
|
|
68
|
+
variant: "success",
|
|
69
|
+
duration: 3000,
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
.catch(() => { });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
log("Auto-capture error", { sessionID, error: String(error) });
|
|
78
|
+
if (CONFIG.showErrorToasts) {
|
|
62
79
|
await ctx.client?.tui
|
|
63
80
|
.showToast({
|
|
64
81
|
body: {
|
|
65
|
-
title: "
|
|
66
|
-
message:
|
|
67
|
-
variant: "
|
|
68
|
-
duration:
|
|
82
|
+
title: "Auto-Capture Failed",
|
|
83
|
+
message: String(error),
|
|
84
|
+
variant: "error",
|
|
85
|
+
duration: 5000,
|
|
69
86
|
},
|
|
70
87
|
})
|
|
71
88
|
.catch(() => { });
|
|
72
89
|
}
|
|
73
90
|
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
log("Auto-capture error", { sessionID, error: String(error) });
|
|
76
|
-
await ctx.client?.tui
|
|
77
|
-
.showToast({
|
|
78
|
-
body: {
|
|
79
|
-
title: "Auto-Capture Failed",
|
|
80
|
-
message: String(error),
|
|
81
|
-
variant: "error",
|
|
82
|
-
duration: 5000,
|
|
83
|
-
},
|
|
84
|
-
})
|
|
85
|
-
.catch(() => { });
|
|
86
|
-
}
|
|
87
91
|
}
|
|
88
92
|
function extractAIContent(messages) {
|
|
89
93
|
const textResponses = [];
|
|
@@ -180,11 +184,12 @@ function buildMarkdownContext(userPrompt, textResponses, toolCalls, latestMemory
|
|
|
180
184
|
}
|
|
181
185
|
return sections.join("\n");
|
|
182
186
|
}
|
|
183
|
-
async function generateSummary(context, sessionID) {
|
|
187
|
+
async function generateSummary(context, sessionID, userPrompt) {
|
|
184
188
|
if (!CONFIG.memoryModel || !CONFIG.memoryApiUrl) {
|
|
185
189
|
throw new Error("External API not configured for auto-capture");
|
|
186
190
|
}
|
|
187
191
|
const { AIProviderFactory } = await import("./ai/ai-provider-factory.js");
|
|
192
|
+
const { detectLanguage, getLanguageName } = await import("./language-detector.js");
|
|
188
193
|
const providerConfig = {
|
|
189
194
|
model: CONFIG.memoryModel,
|
|
190
195
|
apiUrl: CONFIG.memoryApiUrl,
|
|
@@ -193,6 +198,10 @@ async function generateSummary(context, sessionID) {
|
|
|
193
198
|
iterationTimeout: CONFIG.autoCaptureIterationTimeout,
|
|
194
199
|
};
|
|
195
200
|
const provider = AIProviderFactory.createProvider(CONFIG.memoryProvider, providerConfig);
|
|
201
|
+
const targetLang = CONFIG.autoCaptureLanguage === "auto" || !CONFIG.autoCaptureLanguage
|
|
202
|
+
? detectLanguage(userPrompt)
|
|
203
|
+
: CONFIG.autoCaptureLanguage;
|
|
204
|
+
const langName = getLanguageName(targetLang);
|
|
196
205
|
const systemPrompt = `You are a technical memory recorder for a software development project.
|
|
197
206
|
|
|
198
207
|
RULES:
|
|
@@ -200,27 +209,18 @@ RULES:
|
|
|
200
209
|
2. SKIP non-technical by returning type="skip"
|
|
201
210
|
3. NO meta-commentary or behavior analysis
|
|
202
211
|
4. Include specific file names, functions, technical details
|
|
212
|
+
5. You MUST write the summary in ${langName}.
|
|
203
213
|
|
|
204
214
|
FORMAT:
|
|
205
215
|
## Request
|
|
206
|
-
[1-2 sentences: what was requested]
|
|
216
|
+
[1-2 sentences: what was requested, in ${langName}]
|
|
207
217
|
|
|
208
218
|
## Outcome
|
|
209
|
-
[1-2 sentences: what was done, include files/functions]
|
|
219
|
+
[1-2 sentences: what was done, include files/functions, in ${langName}]
|
|
210
220
|
|
|
211
221
|
SKIP if: greetings, casual chat, no code/decisions made
|
|
212
|
-
CAPTURE if: code changed, bug fixed, feature added, decision made
|
|
213
|
-
|
|
214
|
-
EXAMPLES:
|
|
215
|
-
Technical → type="feature":
|
|
216
|
-
## Request
|
|
217
|
-
Fix function returning null.
|
|
218
|
-
## Outcome
|
|
219
|
-
Changed searchMemories() to listMemories() in auto-capture.ts:166.
|
|
220
|
-
|
|
221
|
-
Non-technical → type="skip", summary="":
|
|
222
|
-
User greeted, AI introduced capabilities.`;
|
|
223
|
-
const userPrompt = `${context}
|
|
222
|
+
CAPTURE if: code changed, bug fixed, feature added, decision made`;
|
|
223
|
+
const aiPrompt = `${context}
|
|
224
224
|
|
|
225
225
|
Analyze this conversation. If it contains technical work (code, bugs, features, decisions), create a concise summary. If it's non-technical (greetings, casual chat, incomplete requests), return type="skip" with empty summary.`;
|
|
226
226
|
const toolSchema = {
|
|
@@ -244,7 +244,7 @@ Analyze this conversation. If it contains technical work (code, bugs, features,
|
|
|
244
244
|
},
|
|
245
245
|
},
|
|
246
246
|
};
|
|
247
|
-
const result = await provider.executeToolCall(systemPrompt,
|
|
247
|
+
const result = await provider.executeToolCall(systemPrompt, aiPrompt, toolSchema, sessionID);
|
|
248
248
|
if (!result.success || !result.data) {
|
|
249
249
|
throw new Error(result.error || "Failed to generate summary");
|
|
250
250
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"language-detector.d.ts","sourceRoot":"","sources":["../../src/services/language-detector.ts"],"names":[],"mappings":"AAGA,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYnD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGpD"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { franc } from "franc-min";
|
|
2
|
+
import { iso6393, iso6393To1 } from "iso-639-3";
|
|
3
|
+
export function detectLanguage(text) {
|
|
4
|
+
if (!text || text.trim().length === 0) {
|
|
5
|
+
return "en";
|
|
6
|
+
}
|
|
7
|
+
const detected = franc(text, { minLength: 10 });
|
|
8
|
+
if (detected === "und") {
|
|
9
|
+
return "en";
|
|
10
|
+
}
|
|
11
|
+
return iso6393To1[detected] || "en";
|
|
12
|
+
}
|
|
13
|
+
export function getLanguageName(code) {
|
|
14
|
+
const lang = iso6393.find((l) => l.iso6391 === code);
|
|
15
|
+
return lang?.name || "English";
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-resolver.d.ts","sourceRoot":"","sources":["../../src/services/secret-resolver.ts"],"names":[],"mappings":"AAiCA,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAkChF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir, platform } from "node:os";
|
|
4
|
+
function expandPath(path) {
|
|
5
|
+
if (path.startsWith("~/")) {
|
|
6
|
+
return join(homedir(), path.slice(2));
|
|
7
|
+
}
|
|
8
|
+
if (path === "~") {
|
|
9
|
+
return homedir();
|
|
10
|
+
}
|
|
11
|
+
return path;
|
|
12
|
+
}
|
|
13
|
+
function checkFilePermissions(filePath) {
|
|
14
|
+
if (platform() === "win32") {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const stats = statSync(filePath);
|
|
19
|
+
const mode = stats.mode & 0o777;
|
|
20
|
+
if (mode > 0o600) {
|
|
21
|
+
console.warn(`Warning: Secret file ${filePath} has permissive permissions (${mode.toString(8)}). Recommend chmod 600.`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.warn(`Warning: Could not check file permissions for ${filePath}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function resolveSecretValue(value) {
|
|
29
|
+
if (!value) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
if (value.startsWith("file://")) {
|
|
33
|
+
const filePath = expandPath(value.slice(7));
|
|
34
|
+
if (!existsSync(filePath)) {
|
|
35
|
+
throw new Error(`Secret file not found: ${filePath}`);
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
checkFilePermissions(filePath);
|
|
39
|
+
const content = readFileSync(filePath, "utf-8");
|
|
40
|
+
return content.trim();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw new Error(`Failed to read secret file ${filePath}: ${error}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (value.startsWith("env://")) {
|
|
47
|
+
const envVar = value.slice(6);
|
|
48
|
+
const envValue = process.env[envVar];
|
|
49
|
+
if (!envValue) {
|
|
50
|
+
throw new Error(`Environment variable not found: ${envVar}`);
|
|
51
|
+
}
|
|
52
|
+
return envValue;
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
@@ -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;AASvD,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,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,0BAA0B,CAC9C,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAuFf"}
|
|
@@ -32,16 +32,18 @@ export async function performUserProfileLearning(ctx, directory) {
|
|
|
32
32
|
userProfileManager.createProfile(userId, tags.user.displayName || "Unknown", tags.user.userName || "unknown", tags.user.userEmail || "unknown", updatedProfileData, prompts.length);
|
|
33
33
|
}
|
|
34
34
|
userPromptManager.markMultipleAsUserLearningCaptured(prompts.map((p) => p.id));
|
|
35
|
-
|
|
36
|
-
.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
if (CONFIG.showUserProfileToasts) {
|
|
36
|
+
await ctx.client?.tui
|
|
37
|
+
.showToast({
|
|
38
|
+
body: {
|
|
39
|
+
title: "User Profile Updated",
|
|
40
|
+
message: `Analyzed ${prompts.length} prompts and updated your profile`,
|
|
41
|
+
variant: "success",
|
|
42
|
+
duration: 3000,
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
.catch(() => { });
|
|
46
|
+
}
|
|
45
47
|
}
|
|
46
48
|
catch (error) {
|
|
47
49
|
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
@@ -50,16 +52,18 @@ export async function performUserProfileLearning(ctx, directory) {
|
|
|
50
52
|
stack: errorStack,
|
|
51
53
|
errorType: error instanceof Error ? error.constructor.name : typeof error,
|
|
52
54
|
});
|
|
53
|
-
|
|
54
|
-
.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
if (CONFIG.showErrorToasts) {
|
|
56
|
+
await ctx.client?.tui
|
|
57
|
+
.showToast({
|
|
58
|
+
body: {
|
|
59
|
+
title: "User Profile Update Failed",
|
|
60
|
+
message: String(error),
|
|
61
|
+
variant: "error",
|
|
62
|
+
duration: 5000,
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
.catch(() => { });
|
|
66
|
+
}
|
|
63
67
|
}
|
|
64
68
|
}
|
|
65
69
|
function generateChangeSummary(oldProfile, newProfile) {
|
|
@@ -112,10 +116,6 @@ Identify and ${existingProfile ? "update" : "create"}:
|
|
|
112
116
|
- Development sequences, habits, learning style
|
|
113
117
|
- Break down into steps if applicable
|
|
114
118
|
|
|
115
|
-
4. **Skill Level**
|
|
116
|
-
- Overall: beginner/intermediate/advanced
|
|
117
|
-
- Per-domain assessment (e.g., typescript: advanced, docker: beginner)
|
|
118
|
-
|
|
119
119
|
${existingProfile ? "Merge with existing profile, incrementing frequencies and updating confidence scores." : "Create initial profile with conservative confidence scores."}`;
|
|
120
120
|
}
|
|
121
121
|
async function analyzeUserProfile(context, existingProfile) {
|
|
@@ -140,6 +140,8 @@ async function analyzeUserProfile(context, existingProfile) {
|
|
|
140
140
|
|
|
141
141
|
Your task is to analyze user prompts and ${existingProfile ? "update" : "create"} a comprehensive user profile.
|
|
142
142
|
|
|
143
|
+
CRITICAL: Detect the language used by the user in their prompts. You MUST output all descriptions, categories, and text in the SAME language as the user's prompts.
|
|
144
|
+
|
|
143
145
|
Use the update_user_profile tool to save the ${existingProfile ? "updated" : "new"} profile.`;
|
|
144
146
|
const toolSchema = {
|
|
145
147
|
type: "function",
|
|
@@ -186,16 +188,8 @@ Use the update_user_profile tool to save the ${existingProfile ? "updated" : "ne
|
|
|
186
188
|
required: ["description", "steps"],
|
|
187
189
|
},
|
|
188
190
|
},
|
|
189
|
-
skillLevel: {
|
|
190
|
-
type: "object",
|
|
191
|
-
properties: {
|
|
192
|
-
overall: { type: "string", enum: ["beginner", "intermediate", "advanced"] },
|
|
193
|
-
domains: { type: "object", additionalProperties: { type: "string" } },
|
|
194
|
-
},
|
|
195
|
-
required: ["overall", "domains"],
|
|
196
|
-
},
|
|
197
191
|
},
|
|
198
|
-
required: ["preferences", "patterns", "workflows"
|
|
192
|
+
required: ["preferences", "patterns", "workflows"],
|
|
199
193
|
},
|
|
200
194
|
},
|
|
201
195
|
};
|
|
@@ -16,15 +16,10 @@ export interface UserProfileWorkflow {
|
|
|
16
16
|
steps: string[];
|
|
17
17
|
frequency: number;
|
|
18
18
|
}
|
|
19
|
-
export interface UserProfileSkillLevel {
|
|
20
|
-
overall: string;
|
|
21
|
-
domains: Record<string, string>;
|
|
22
|
-
}
|
|
23
19
|
export interface UserProfileData {
|
|
24
20
|
preferences: UserProfilePreference[];
|
|
25
21
|
patterns: UserProfilePattern[];
|
|
26
22
|
workflows: UserProfileWorkflow[];
|
|
27
|
-
skillLevel: UserProfileSkillLevel;
|
|
28
23
|
}
|
|
29
24
|
export interface UserProfile {
|
|
30
25
|
id: string;
|
|
@@ -1 +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,
|
|
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,eAAe;IAC9B,WAAW,EAAE,qBAAqB,EAAE,CAAC;IACrC,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,SAAS,EAAE,mBAAmB,EAAE,CAAC;CAClC;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"}
|
|
@@ -1 +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,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAKrF,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;IA6BP,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,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe;
|
|
1
|
+
{"version":3,"file":"user-profile-manager.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/user-profile-manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAKrF,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;IA6BP,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,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe;CAmFhG;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
|
|
@@ -194,7 +194,6 @@ export class UserProfileManager {
|
|
|
194
194
|
preferences: safeArray(existing?.preferences),
|
|
195
195
|
patterns: safeArray(existing?.patterns),
|
|
196
196
|
workflows: safeArray(existing?.workflows),
|
|
197
|
-
skillLevel: safeObject(existing?.skillLevel, { overall: "intermediate", domains: {} }),
|
|
198
197
|
};
|
|
199
198
|
if (updates.preferences) {
|
|
200
199
|
for (const newPref of updates.preferences) {
|
|
@@ -258,12 +257,6 @@ export class UserProfileManager {
|
|
|
258
257
|
merged.workflows.sort((a, b) => b.frequency - a.frequency);
|
|
259
258
|
merged.workflows = merged.workflows.slice(0, CONFIG.userProfileMaxWorkflows);
|
|
260
259
|
}
|
|
261
|
-
if (updates.skillLevel) {
|
|
262
|
-
merged.skillLevel = {
|
|
263
|
-
overall: updates.skillLevel.overall || merged.skillLevel.overall,
|
|
264
|
-
domains: { ...merged.skillLevel.domains, ...safeObject(updates.skillLevel.domains, {}) },
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
260
|
return merged;
|
|
268
261
|
}
|
|
269
262
|
}
|
|
@@ -32,6 +32,8 @@ export declare class UserPromptManager {
|
|
|
32
32
|
linkMemoryToPrompt(promptId: string, memoryId: string): void;
|
|
33
33
|
getPromptById(promptId: string): UserPrompt | null;
|
|
34
34
|
getCapturedPrompts(projectPath?: string): UserPrompt[];
|
|
35
|
+
searchPrompts(query: string, projectPath?: string, limit?: number): UserPrompt[];
|
|
36
|
+
getPromptsByIds(ids: string[]): UserPrompt[];
|
|
35
37
|
private rowToPrompt;
|
|
36
38
|
}
|
|
37
39
|
export declare const userPromptManager: UserPromptManager;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-prompt-manager.d.ts","sourceRoot":"","sources":["../../../src/services/user-prompt/user-prompt-manager.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,EAAE,CAAW;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;IAQhC,OAAO,CAAC,YAAY;IA+BpB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;IAa9F,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAc7D,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKpC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKtC,sBAAsB,IAAI,MAAM;IAMhC,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE;IAYjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAUjD,8BAA8B,IAAI,MAAM;IAQxC,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE;IAYtD,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKlD,kCAAkC,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAU7D,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,EAAE,CAAA;KAAE;IAiBpF,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK5D,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAOlD,kBAAkB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE;IAgBtD,OAAO,CAAC,WAAW;CAapB;AAED,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
|
|
1
|
+
{"version":3,"file":"user-prompt-manager.d.ts","sourceRoot":"","sources":["../../../src/services/user-prompt/user-prompt-manager.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,EAAE,CAAW;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;IAQhC,OAAO,CAAC,YAAY;IA+BpB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;IAa9F,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAc7D,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKpC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKtC,sBAAsB,IAAI,MAAM;IAMhC,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE;IAYjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAUjD,8BAA8B,IAAI,MAAM;IAQxC,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE;IAYtD,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKlD,kCAAkC,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAU7D,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,EAAE,CAAA;KAAE;IAiBpF,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK5D,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAOlD,kBAAkB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE;IAgBtD,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,UAAU,EAAE;IAiBpF,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE;IAQ5C,OAAO,CAAC,WAAW;CAapB;AAED,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
|
|
@@ -147,6 +147,27 @@ export class UserPromptManager {
|
|
|
147
147
|
const rows = stmt.all(...params);
|
|
148
148
|
return rows.map((row) => this.rowToPrompt(row));
|
|
149
149
|
}
|
|
150
|
+
searchPrompts(query, projectPath, limit = 20) {
|
|
151
|
+
let sql = `SELECT * FROM user_prompts WHERE content LIKE ? AND captured = 1`;
|
|
152
|
+
const params = [`%${query}%`];
|
|
153
|
+
if (projectPath) {
|
|
154
|
+
sql += ` AND project_path = ?`;
|
|
155
|
+
params.push(projectPath);
|
|
156
|
+
}
|
|
157
|
+
sql += ` ORDER BY created_at DESC LIMIT ?`;
|
|
158
|
+
params.push(limit);
|
|
159
|
+
const stmt = this.db.prepare(sql);
|
|
160
|
+
const rows = stmt.all(...params);
|
|
161
|
+
return rows.map((row) => this.rowToPrompt(row));
|
|
162
|
+
}
|
|
163
|
+
getPromptsByIds(ids) {
|
|
164
|
+
if (ids.length === 0)
|
|
165
|
+
return [];
|
|
166
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
167
|
+
const stmt = this.db.prepare(`SELECT * FROM user_prompts WHERE id IN (${placeholders})`);
|
|
168
|
+
const rows = stmt.all(...ids);
|
|
169
|
+
return rows.map((row) => this.rowToPrompt(row));
|
|
170
|
+
}
|
|
150
171
|
rowToPrompt(row) {
|
|
151
172
|
return {
|
|
152
173
|
id: row.id,
|