opencode-mem 2.5.0 → 2.6.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 +2 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -162
- package/dist/services/api-handlers.d.ts +14 -0
- package/dist/services/api-handlers.d.ts.map +1 -1
- package/dist/services/api-handlers.js +145 -97
- package/dist/services/auto-capture.d.ts.map +1 -1
- package/dist/services/auto-capture.js +11 -3
- package/dist/services/client.d.ts +1 -0
- package/dist/services/client.d.ts.map +1 -1
- package/dist/services/client.js +8 -1
- package/dist/services/sqlite/connection-manager.d.ts +1 -0
- package/dist/services/sqlite/connection-manager.d.ts.map +1 -1
- package/dist/services/sqlite/connection-manager.js +20 -0
- package/dist/services/sqlite/shard-manager.d.ts +1 -0
- package/dist/services/sqlite/shard-manager.d.ts.map +1 -1
- package/dist/services/sqlite/shard-manager.js +32 -18
- package/dist/services/sqlite/types.d.ts +3 -0
- package/dist/services/sqlite/types.d.ts.map +1 -1
- package/dist/services/sqlite/vector-search.d.ts.map +1 -1
- package/dist/services/sqlite/vector-search.js +67 -43
- package/dist/services/tags.js +3 -3
- package/dist/services/web-server-worker.js +9 -1
- package/dist/web/app.js +111 -1
- package/dist/web/index.html +28 -0
- package/dist/web/styles.css +52 -3
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import { shardManager } from "./sqlite/shard-manager.js";
|
|
|
3
3
|
import { vectorSearch } from "./sqlite/vector-search.js";
|
|
4
4
|
import { connectionManager } from "./sqlite/connection-manager.js";
|
|
5
5
|
import { log } from "./logger.js";
|
|
6
|
+
import { CONFIG } from "../config.js";
|
|
6
7
|
import { userPromptManager } from "./user-prompt/user-prompt-manager.js";
|
|
7
8
|
function safeToISOString(timestamp) {
|
|
8
9
|
try {
|
|
@@ -55,9 +56,8 @@ export async function handleListTags() {
|
|
|
55
56
|
try {
|
|
56
57
|
await embeddingService.warmup();
|
|
57
58
|
const projectShards = shardManager.getAllShards("project", "");
|
|
58
|
-
const allShards = [...projectShards];
|
|
59
59
|
const tagsMap = new Map();
|
|
60
|
-
for (const shard of
|
|
60
|
+
for (const shard of projectShards) {
|
|
61
61
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
62
62
|
const tags = vectorSearch.getDistinctTags(db);
|
|
63
63
|
for (const t of tags) {
|
|
@@ -80,10 +80,7 @@ export async function handleListTags() {
|
|
|
80
80
|
projectTags.push(tagInfo);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
return {
|
|
84
|
-
success: true,
|
|
85
|
-
data: { project: projectTags },
|
|
86
|
-
};
|
|
83
|
+
return { success: true, data: { project: projectTags } };
|
|
87
84
|
}
|
|
88
85
|
catch (error) {
|
|
89
86
|
log("handleListTags: error", { error: String(error) });
|
|
@@ -118,6 +115,7 @@ export async function handleListMemories(tag, page = 1, pageSize = 20, includePr
|
|
|
118
115
|
id: r.id,
|
|
119
116
|
content: r.content,
|
|
120
117
|
memoryType: r.type,
|
|
118
|
+
tags: r.tags ? r.tags.split(",").map((t) => t.trim()) : [],
|
|
121
119
|
createdAt: Number(r.created_at),
|
|
122
120
|
updatedAt: r.updated_at ? Number(r.updated_at) : undefined,
|
|
123
121
|
metadata,
|
|
@@ -191,6 +189,7 @@ export async function handleListMemories(tag, page = 1, pageSize = 20, includePr
|
|
|
191
189
|
id: item.id,
|
|
192
190
|
content: item.content,
|
|
193
191
|
memoryType: item.memoryType,
|
|
192
|
+
tags: item.tags,
|
|
194
193
|
createdAt: safeToISOString(item.createdAt),
|
|
195
194
|
updatedAt: item.updatedAt ? safeToISOString(item.updatedAt) : undefined,
|
|
196
195
|
metadata: item.metadata,
|
|
@@ -216,16 +215,7 @@ export async function handleListMemories(tag, page = 1, pageSize = 20, includePr
|
|
|
216
215
|
};
|
|
217
216
|
}
|
|
218
217
|
});
|
|
219
|
-
return {
|
|
220
|
-
success: true,
|
|
221
|
-
data: {
|
|
222
|
-
items,
|
|
223
|
-
total,
|
|
224
|
-
page,
|
|
225
|
-
pageSize,
|
|
226
|
-
totalPages,
|
|
227
|
-
},
|
|
228
|
-
};
|
|
218
|
+
return { success: true, data: { items, total, page, pageSize, totalPages } };
|
|
229
219
|
}
|
|
230
220
|
catch (error) {
|
|
231
221
|
log("handleListMemories: error", { error: String(error) });
|
|
@@ -238,7 +228,13 @@ export async function handleAddMemory(data) {
|
|
|
238
228
|
return { success: false, error: "content and containerTag are required" };
|
|
239
229
|
}
|
|
240
230
|
await embeddingService.warmup();
|
|
241
|
-
const
|
|
231
|
+
const tags = data.tags || [];
|
|
232
|
+
const embeddingInput = tags.length > 0 ? `${data.content}\nTags: ${tags.join(", ")}` : data.content;
|
|
233
|
+
const vector = await embeddingService.embedWithTimeout(embeddingInput);
|
|
234
|
+
let tagsVector = undefined;
|
|
235
|
+
if (tags.length > 0) {
|
|
236
|
+
tagsVector = await embeddingService.embedWithTimeout(tags.join(", "));
|
|
237
|
+
}
|
|
242
238
|
const { scope, hash } = extractScopeFromTag(data.containerTag);
|
|
243
239
|
const shard = shardManager.getWriteShard(scope, hash);
|
|
244
240
|
const id = `mem_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
@@ -247,7 +243,9 @@ export async function handleAddMemory(data) {
|
|
|
247
243
|
id,
|
|
248
244
|
content: data.content,
|
|
249
245
|
vector,
|
|
246
|
+
tagsVector,
|
|
250
247
|
containerTag: data.containerTag,
|
|
248
|
+
tags: tags.length > 0 ? tags.join(",") : undefined,
|
|
251
249
|
type: data.type,
|
|
252
250
|
createdAt: now,
|
|
253
251
|
updatedAt: now,
|
|
@@ -257,9 +255,7 @@ export async function handleAddMemory(data) {
|
|
|
257
255
|
projectPath: data.projectPath,
|
|
258
256
|
projectName: data.projectName,
|
|
259
257
|
gitRepoUrl: data.gitRepoUrl,
|
|
260
|
-
metadata: JSON.stringify({
|
|
261
|
-
source: "api",
|
|
262
|
-
}),
|
|
258
|
+
metadata: JSON.stringify({ source: "api" }),
|
|
263
259
|
};
|
|
264
260
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
265
261
|
vectorSearch.insertVector(db, record);
|
|
@@ -273,27 +269,25 @@ export async function handleAddMemory(data) {
|
|
|
273
269
|
}
|
|
274
270
|
export async function handleDeleteMemory(id, cascade = false) {
|
|
275
271
|
try {
|
|
276
|
-
if (!id)
|
|
272
|
+
if (!id)
|
|
277
273
|
return { success: false, error: "id is required" };
|
|
278
|
-
}
|
|
279
274
|
const projectShards = shardManager.getAllShards("project", "");
|
|
280
|
-
const
|
|
281
|
-
let deletedPrompt = false;
|
|
282
|
-
for (const shard of allShards) {
|
|
275
|
+
for (const shard of projectShards) {
|
|
283
276
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
284
277
|
const memory = vectorSearch.getMemoryById(db, id);
|
|
285
278
|
if (memory) {
|
|
286
279
|
if (cascade) {
|
|
287
280
|
const metadata = safeJSONParse(memory.metadata);
|
|
288
281
|
const linkedPromptId = metadata?.promptId;
|
|
289
|
-
if (linkedPromptId)
|
|
282
|
+
if (linkedPromptId)
|
|
290
283
|
userPromptManager.deletePrompt(linkedPromptId);
|
|
291
|
-
deletedPrompt = true;
|
|
292
|
-
}
|
|
293
284
|
}
|
|
294
285
|
vectorSearch.deleteVector(db, id);
|
|
295
286
|
shardManager.decrementVectorCount(shard.id);
|
|
296
|
-
return {
|
|
287
|
+
return {
|
|
288
|
+
success: true,
|
|
289
|
+
data: { deletedPrompt: cascade && !!safeJSONParse(memory.metadata)?.promptId },
|
|
290
|
+
};
|
|
297
291
|
}
|
|
298
292
|
}
|
|
299
293
|
return { success: false, error: "Memory not found" };
|
|
@@ -305,15 +299,13 @@ export async function handleDeleteMemory(id, cascade = false) {
|
|
|
305
299
|
}
|
|
306
300
|
export async function handleBulkDelete(ids, cascade = false) {
|
|
307
301
|
try {
|
|
308
|
-
if (!ids || ids.length === 0)
|
|
302
|
+
if (!ids || ids.length === 0)
|
|
309
303
|
return { success: false, error: "ids array is required" };
|
|
310
|
-
}
|
|
311
304
|
let deleted = 0;
|
|
312
305
|
for (const id of ids) {
|
|
313
306
|
const result = await handleDeleteMemory(id, cascade);
|
|
314
|
-
if (result.success)
|
|
307
|
+
if (result.success)
|
|
315
308
|
deleted++;
|
|
316
|
-
}
|
|
317
309
|
}
|
|
318
310
|
return { success: true, data: { deleted } };
|
|
319
311
|
}
|
|
@@ -324,15 +316,12 @@ export async function handleBulkDelete(ids, cascade = false) {
|
|
|
324
316
|
}
|
|
325
317
|
export async function handleUpdateMemory(id, data) {
|
|
326
318
|
try {
|
|
327
|
-
if (!id)
|
|
319
|
+
if (!id)
|
|
328
320
|
return { success: false, error: "id is required" };
|
|
329
|
-
}
|
|
330
321
|
await embeddingService.warmup();
|
|
331
322
|
const projectShards = shardManager.getAllShards("project", "");
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
let existingMemory = null;
|
|
335
|
-
for (const shard of allShards) {
|
|
323
|
+
let foundShard = null, existingMemory = null;
|
|
324
|
+
for (const shard of projectShards) {
|
|
336
325
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
337
326
|
const memory = vectorSearch.getMemoryById(db, id);
|
|
338
327
|
if (memory) {
|
|
@@ -341,19 +330,25 @@ export async function handleUpdateMemory(id, data) {
|
|
|
341
330
|
break;
|
|
342
331
|
}
|
|
343
332
|
}
|
|
344
|
-
if (!foundShard || !existingMemory)
|
|
333
|
+
if (!foundShard || !existingMemory)
|
|
345
334
|
return { success: false, error: "Memory not found" };
|
|
346
|
-
}
|
|
347
335
|
const db = connectionManager.getConnection(foundShard.dbPath);
|
|
348
336
|
vectorSearch.deleteVector(db, id);
|
|
349
337
|
shardManager.decrementVectorCount(foundShard.id);
|
|
350
338
|
const newContent = data.content || existingMemory.content;
|
|
339
|
+
const tags = data.tags || (existingMemory.tags ? existingMemory.tags.split(",") : []);
|
|
351
340
|
const vector = await embeddingService.embedWithTimeout(newContent);
|
|
341
|
+
let tagsVector = undefined;
|
|
342
|
+
if (tags.length > 0) {
|
|
343
|
+
tagsVector = await embeddingService.embedWithTimeout(tags.join(", "));
|
|
344
|
+
}
|
|
352
345
|
const updatedRecord = {
|
|
353
346
|
id,
|
|
354
347
|
content: newContent,
|
|
355
348
|
vector,
|
|
349
|
+
tagsVector,
|
|
356
350
|
containerTag: existingMemory.container_tag,
|
|
351
|
+
tags: tags.length > 0 ? tags.join(",") : undefined,
|
|
357
352
|
type: data.type || existingMemory.type,
|
|
358
353
|
createdAt: existingMemory.created_at,
|
|
359
354
|
updatedAt: Date.now(),
|
|
@@ -376,9 +371,8 @@ export async function handleUpdateMemory(id, data) {
|
|
|
376
371
|
}
|
|
377
372
|
export async function handleSearch(query, tag, page = 1, pageSize = 20) {
|
|
378
373
|
try {
|
|
379
|
-
if (!query)
|
|
374
|
+
if (!query)
|
|
380
375
|
return { success: false, error: "query is required" };
|
|
381
|
-
}
|
|
382
376
|
await embeddingService.warmup();
|
|
383
377
|
const queryVector = await embeddingService.embedWithTimeout(query);
|
|
384
378
|
let memoryResults = [];
|
|
@@ -400,15 +394,13 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
|
|
|
400
394
|
}
|
|
401
395
|
else {
|
|
402
396
|
const projectShards = shardManager.getAllShards("project", "");
|
|
403
|
-
const allShards = [...projectShards];
|
|
404
397
|
const uniqueTags = new Set();
|
|
405
|
-
for (const shard of
|
|
398
|
+
for (const shard of projectShards) {
|
|
406
399
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
407
400
|
const tags = vectorSearch.getDistinctTags(db);
|
|
408
401
|
for (const t of tags) {
|
|
409
|
-
if (t.container_tag)
|
|
402
|
+
if (t.container_tag)
|
|
410
403
|
uniqueTags.add(t.container_tag);
|
|
411
|
-
}
|
|
412
404
|
}
|
|
413
405
|
}
|
|
414
406
|
for (const containerTag of uniqueTags) {
|
|
@@ -441,6 +433,7 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
|
|
|
441
433
|
id: r.id,
|
|
442
434
|
content: r.memory,
|
|
443
435
|
memoryType: r.metadata?.type,
|
|
436
|
+
tags: r.tags,
|
|
444
437
|
createdAt: safeToISOString(r.metadata?.createdAt),
|
|
445
438
|
updatedAt: r.metadata?.updatedAt ? safeToISOString(r.metadata.updatedAt) : undefined,
|
|
446
439
|
similarity: r.similarity,
|
|
@@ -463,13 +456,11 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
|
|
|
463
456
|
const missingMemoryIds = new Set();
|
|
464
457
|
for (const item of paginatedResults) {
|
|
465
458
|
if (item.type === "memory" && item.linkedPromptId) {
|
|
466
|
-
|
|
467
|
-
if (!hasPrompt)
|
|
459
|
+
if (!paginatedResults.some((p) => p.id === item.linkedPromptId))
|
|
468
460
|
missingPromptIds.add(item.linkedPromptId);
|
|
469
461
|
}
|
|
470
462
|
else if (item.type === "prompt" && item.linkedMemoryId) {
|
|
471
|
-
|
|
472
|
-
if (!hasMemory)
|
|
463
|
+
if (!paginatedResults.some((m) => m.id === item.linkedMemoryId))
|
|
473
464
|
missingMemoryIds.add(item.linkedMemoryId);
|
|
474
465
|
}
|
|
475
466
|
}
|
|
@@ -501,6 +492,7 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
|
|
|
501
492
|
id: m.id,
|
|
502
493
|
content: m.content,
|
|
503
494
|
memoryType: m.type,
|
|
495
|
+
tags: m.tags ? m.tags.split(",").map((t) => t.trim()) : [],
|
|
504
496
|
createdAt: safeToISOString(m.created_at),
|
|
505
497
|
updatedAt: m.updated_at ? safeToISOString(m.updated_at) : undefined,
|
|
506
498
|
similarity: 0,
|
|
@@ -519,16 +511,7 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
|
|
|
519
511
|
}
|
|
520
512
|
}
|
|
521
513
|
}
|
|
522
|
-
return {
|
|
523
|
-
success: true,
|
|
524
|
-
data: {
|
|
525
|
-
items: paginatedResults,
|
|
526
|
-
total,
|
|
527
|
-
page,
|
|
528
|
-
pageSize,
|
|
529
|
-
totalPages,
|
|
530
|
-
},
|
|
531
|
-
};
|
|
514
|
+
return { success: true, data: { items: paginatedResults, total, page, pageSize, totalPages } };
|
|
532
515
|
}
|
|
533
516
|
catch (error) {
|
|
534
517
|
log("handleSearch: error", { error: String(error) });
|
|
@@ -539,23 +522,18 @@ export async function handleStats() {
|
|
|
539
522
|
try {
|
|
540
523
|
await embeddingService.warmup();
|
|
541
524
|
const projectShards = shardManager.getAllShards("project", "");
|
|
542
|
-
|
|
543
|
-
let userCount = 0;
|
|
544
|
-
let projectCount = 0;
|
|
525
|
+
let userCount = 0, projectCount = 0;
|
|
545
526
|
const typeCount = {};
|
|
546
|
-
for (const shard of
|
|
527
|
+
for (const shard of projectShards) {
|
|
547
528
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
548
529
|
const memories = vectorSearch.getAllMemories(db);
|
|
549
530
|
for (const r of memories) {
|
|
550
|
-
if (r.container_tag?.includes("_user_"))
|
|
531
|
+
if (r.container_tag?.includes("_user_"))
|
|
551
532
|
userCount++;
|
|
552
|
-
|
|
553
|
-
else if (r.container_tag?.includes("_project_")) {
|
|
533
|
+
else if (r.container_tag?.includes("_project_"))
|
|
554
534
|
projectCount++;
|
|
555
|
-
|
|
556
|
-
if (r.type) {
|
|
535
|
+
if (r.type)
|
|
557
536
|
typeCount[r.type] = (typeCount[r.type] || 0) + 1;
|
|
558
|
-
}
|
|
559
537
|
}
|
|
560
538
|
}
|
|
561
539
|
return {
|
|
@@ -574,12 +552,10 @@ export async function handleStats() {
|
|
|
574
552
|
}
|
|
575
553
|
export async function handlePinMemory(id) {
|
|
576
554
|
try {
|
|
577
|
-
if (!id)
|
|
555
|
+
if (!id)
|
|
578
556
|
return { success: false, error: "id is required" };
|
|
579
|
-
}
|
|
580
557
|
const projectShards = shardManager.getAllShards("project", "");
|
|
581
|
-
const
|
|
582
|
-
for (const shard of allShards) {
|
|
558
|
+
for (const shard of projectShards) {
|
|
583
559
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
584
560
|
const memory = vectorSearch.getMemoryById(db, id);
|
|
585
561
|
if (memory) {
|
|
@@ -596,12 +572,10 @@ export async function handlePinMemory(id) {
|
|
|
596
572
|
}
|
|
597
573
|
export async function handleUnpinMemory(id) {
|
|
598
574
|
try {
|
|
599
|
-
if (!id)
|
|
575
|
+
if (!id)
|
|
600
576
|
return { success: false, error: "id is required" };
|
|
601
|
-
}
|
|
602
577
|
const projectShards = shardManager.getAllShards("project", "");
|
|
603
|
-
const
|
|
604
|
-
for (const shard of allShards) {
|
|
578
|
+
for (const shard of projectShards) {
|
|
605
579
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
606
580
|
const memory = vectorSearch.getMemoryById(db, id);
|
|
607
581
|
if (memory) {
|
|
@@ -662,19 +636,16 @@ export async function handleRunMigration(strategy) {
|
|
|
662
636
|
}
|
|
663
637
|
export async function handleDeletePrompt(id, cascade = false) {
|
|
664
638
|
try {
|
|
665
|
-
if (!id)
|
|
639
|
+
if (!id)
|
|
666
640
|
return { success: false, error: "id is required" };
|
|
667
|
-
}
|
|
668
641
|
const prompt = userPromptManager.getPromptById(id);
|
|
669
|
-
if (!prompt)
|
|
642
|
+
if (!prompt)
|
|
670
643
|
return { success: false, error: "Prompt not found" };
|
|
671
|
-
}
|
|
672
644
|
let deletedMemory = false;
|
|
673
645
|
if (cascade && prompt.linkedMemoryId) {
|
|
674
646
|
const result = await handleDeleteMemory(prompt.linkedMemoryId, false);
|
|
675
|
-
if (result.success)
|
|
647
|
+
if (result.success)
|
|
676
648
|
deletedMemory = true;
|
|
677
|
-
}
|
|
678
649
|
}
|
|
679
650
|
userPromptManager.deletePrompt(id);
|
|
680
651
|
return { success: true, data: { deletedMemory } };
|
|
@@ -686,15 +657,13 @@ export async function handleDeletePrompt(id, cascade = false) {
|
|
|
686
657
|
}
|
|
687
658
|
export async function handleBulkDeletePrompts(ids, cascade = false) {
|
|
688
659
|
try {
|
|
689
|
-
if (!ids || ids.length === 0)
|
|
660
|
+
if (!ids || ids.length === 0)
|
|
690
661
|
return { success: false, error: "ids array is required" };
|
|
691
|
-
}
|
|
692
662
|
let deleted = 0;
|
|
693
663
|
for (const id of ids) {
|
|
694
664
|
const result = await handleDeletePrompt(id, cascade);
|
|
695
|
-
if (result.success)
|
|
665
|
+
if (result.success)
|
|
696
666
|
deleted++;
|
|
697
|
-
}
|
|
698
667
|
}
|
|
699
668
|
return { success: true, data: { deleted } };
|
|
700
669
|
}
|
|
@@ -713,7 +682,7 @@ export async function handleGetUserProfile(userId) {
|
|
|
713
682
|
targetUserId = tags.user.userEmail || "unknown";
|
|
714
683
|
}
|
|
715
684
|
const profile = userProfileManager.getActiveProfile(targetUserId);
|
|
716
|
-
if (!profile)
|
|
685
|
+
if (!profile)
|
|
717
686
|
return {
|
|
718
687
|
success: true,
|
|
719
688
|
data: {
|
|
@@ -722,7 +691,6 @@ export async function handleGetUserProfile(userId) {
|
|
|
722
691
|
message: "No profile found. Keep chatting to build your profile.",
|
|
723
692
|
},
|
|
724
693
|
};
|
|
725
|
-
}
|
|
726
694
|
const profileData = JSON.parse(profile.profileData);
|
|
727
695
|
return {
|
|
728
696
|
success: true,
|
|
@@ -748,9 +716,8 @@ export async function handleGetUserProfile(userId) {
|
|
|
748
716
|
}
|
|
749
717
|
export async function handleGetProfileChangelog(profileId, limit = 5) {
|
|
750
718
|
try {
|
|
751
|
-
if (!profileId)
|
|
719
|
+
if (!profileId)
|
|
752
720
|
return { success: false, error: "profileId is required" };
|
|
753
|
-
}
|
|
754
721
|
const { userProfileManager } = await import("./user-profile/user-profile-manager.js");
|
|
755
722
|
const changelogs = userProfileManager.getProfileChangelogs(profileId, limit);
|
|
756
723
|
const formattedChangelogs = changelogs.map((c) => ({
|
|
@@ -770,15 +737,13 @@ export async function handleGetProfileChangelog(profileId, limit = 5) {
|
|
|
770
737
|
}
|
|
771
738
|
export async function handleGetProfileSnapshot(changelogId) {
|
|
772
739
|
try {
|
|
773
|
-
if (!changelogId)
|
|
740
|
+
if (!changelogId)
|
|
774
741
|
return { success: false, error: "changelogId is required" };
|
|
775
|
-
}
|
|
776
742
|
const { userProfileManager } = await import("./user-profile/user-profile-manager.js");
|
|
777
743
|
const changelogs = userProfileManager.getProfileChangelogs("", 1000);
|
|
778
744
|
const changelog = changelogs.find((c) => c.id === changelogId);
|
|
779
|
-
if (!changelog)
|
|
745
|
+
if (!changelog)
|
|
780
746
|
return { success: false, error: "Changelog not found" };
|
|
781
|
-
}
|
|
782
747
|
const profileData = JSON.parse(changelog.profileDataSnapshot);
|
|
783
748
|
return {
|
|
784
749
|
success: true,
|
|
@@ -818,3 +783,86 @@ export async function handleRefreshProfile(userId) {
|
|
|
818
783
|
return { success: false, error: String(error) };
|
|
819
784
|
}
|
|
820
785
|
}
|
|
786
|
+
export async function handleDetectTagMigration() {
|
|
787
|
+
try {
|
|
788
|
+
const projectShards = shardManager.getAllShards("project", "");
|
|
789
|
+
let untaggedCount = 0;
|
|
790
|
+
for (const shard of projectShards) {
|
|
791
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
792
|
+
const rows = db
|
|
793
|
+
.prepare("SELECT COUNT(*) as count FROM memories WHERE tags IS NULL OR tags = ''")
|
|
794
|
+
.get();
|
|
795
|
+
untaggedCount += rows.count;
|
|
796
|
+
}
|
|
797
|
+
return { success: true, data: { needsMigration: untaggedCount > 0, count: untaggedCount } };
|
|
798
|
+
}
|
|
799
|
+
catch (error) {
|
|
800
|
+
return { success: false, error: String(error) };
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
export async function handleRunTagMigration() {
|
|
804
|
+
try {
|
|
805
|
+
const startTime = Date.now();
|
|
806
|
+
const { AIProviderFactory } = await import("./ai/ai-provider-factory.js");
|
|
807
|
+
const providerConfig = {
|
|
808
|
+
model: CONFIG.memoryModel,
|
|
809
|
+
apiUrl: CONFIG.memoryApiUrl,
|
|
810
|
+
apiKey: CONFIG.memoryApiKey,
|
|
811
|
+
maxIterations: 1,
|
|
812
|
+
iterationTimeout: 30000,
|
|
813
|
+
};
|
|
814
|
+
const provider = AIProviderFactory.createProvider(CONFIG.memoryProvider, providerConfig);
|
|
815
|
+
const projectShards = shardManager.getAllShards("project", "");
|
|
816
|
+
let processed = 0;
|
|
817
|
+
for (const shard of projectShards) {
|
|
818
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
819
|
+
const memories = db.prepare("SELECT * FROM memories").all();
|
|
820
|
+
for (const m of memories) {
|
|
821
|
+
try {
|
|
822
|
+
let currentTags = m.tags
|
|
823
|
+
? m.tags
|
|
824
|
+
.split(",")
|
|
825
|
+
.map((t) => t.trim())
|
|
826
|
+
.filter((t) => t)
|
|
827
|
+
: [];
|
|
828
|
+
if (currentTags.length === 0) {
|
|
829
|
+
const prompt = `Generate 2-4 short technical tags for this memory content:\n\n${m.content}\n\nReturn ONLY a comma-separated list of tags.`;
|
|
830
|
+
const result = await provider.executeToolCall("You are a technical tagger.", prompt, {
|
|
831
|
+
type: "function",
|
|
832
|
+
function: {
|
|
833
|
+
name: "save_tags",
|
|
834
|
+
description: "Save generated tags",
|
|
835
|
+
parameters: {
|
|
836
|
+
type: "object",
|
|
837
|
+
properties: { tags: { type: "array", items: { type: "string" } } },
|
|
838
|
+
required: ["tags"],
|
|
839
|
+
},
|
|
840
|
+
},
|
|
841
|
+
}, `migration_${m.id}`);
|
|
842
|
+
if (result.success && result.data?.tags) {
|
|
843
|
+
currentTags = result.data.tags;
|
|
844
|
+
db.prepare("UPDATE memories SET tags = ? WHERE id = ?").run(currentTags.join(","), m.id);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
const vector = await embeddingService.embedWithTimeout(m.content);
|
|
848
|
+
const vectorBuffer = new Uint8Array(vector.buffer);
|
|
849
|
+
db.prepare("UPDATE memories SET vector = ?, updated_at = ? WHERE id = ?").run(vectorBuffer, Date.now(), m.id);
|
|
850
|
+
db.prepare("INSERT OR REPLACE INTO vec_memories (memory_id, embedding) VALUES (?, ?)").run(m.id, vectorBuffer);
|
|
851
|
+
if (currentTags.length > 0) {
|
|
852
|
+
const tagsVector = await embeddingService.embedWithTimeout(currentTags.join(", "));
|
|
853
|
+
const tagsVectorBuffer = new Uint8Array(tagsVector.buffer);
|
|
854
|
+
db.prepare("INSERT OR REPLACE INTO vec_tags (memory_id, embedding) VALUES (?, ?)").run(m.id, tagsVectorBuffer);
|
|
855
|
+
}
|
|
856
|
+
processed++;
|
|
857
|
+
}
|
|
858
|
+
catch (e) {
|
|
859
|
+
log("Migration error for memory", { id: m.id, error: String(e) });
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return { success: true, data: { success: true, processed, duration: Date.now() - startTime } };
|
|
864
|
+
}
|
|
865
|
+
catch (error) {
|
|
866
|
+
return { success: false, error: String(error) };
|
|
867
|
+
}
|
|
868
|
+
}
|
|
@@ -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,CAqGf"}
|
|
@@ -46,6 +46,7 @@ export async function performAutoCapture(ctx, sessionID, directory) {
|
|
|
46
46
|
const result = await memoryClient.addMemory(summaryResult.summary, tags.project.tag, {
|
|
47
47
|
source: "auto-capture",
|
|
48
48
|
type: summaryResult.type,
|
|
49
|
+
tags: summaryResult.tags,
|
|
49
50
|
sessionID,
|
|
50
51
|
promptId: prompt.id,
|
|
51
52
|
captureTimestamp: Date.now(),
|
|
@@ -209,7 +210,8 @@ RULES:
|
|
|
209
210
|
2. SKIP non-technical by returning type="skip"
|
|
210
211
|
3. NO meta-commentary or behavior analysis
|
|
211
212
|
4. Include specific file names, functions, technical details
|
|
212
|
-
5.
|
|
213
|
+
5. Generate 2-4 technical tags (e.g., "react", "auth", "bug-fix")
|
|
214
|
+
6. You MUST write the summary in ${langName}.
|
|
213
215
|
|
|
214
216
|
FORMAT:
|
|
215
217
|
## Request
|
|
@@ -222,7 +224,7 @@ SKIP if: greetings, casual chat, no code/decisions made
|
|
|
222
224
|
CAPTURE if: code changed, bug fixed, feature added, decision made`;
|
|
223
225
|
const aiPrompt = `${context}
|
|
224
226
|
|
|
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.`;
|
|
227
|
+
Analyze this conversation. If it contains technical work (code, bugs, features, decisions), create a concise summary and relevant tags. If it's non-technical (greetings, casual chat, incomplete requests), return type="skip" with empty summary.`;
|
|
226
228
|
const toolSchema = {
|
|
227
229
|
type: "function",
|
|
228
230
|
function: {
|
|
@@ -239,8 +241,13 @@ Analyze this conversation. If it contains technical work (code, bugs, features,
|
|
|
239
241
|
type: "string",
|
|
240
242
|
description: "Type of memory: 'skip' for non-technical conversations, or technical type (feature, bug-fix, refactor, analysis, configuration, discussion, other)",
|
|
241
243
|
},
|
|
244
|
+
tags: {
|
|
245
|
+
type: "array",
|
|
246
|
+
items: { type: "string" },
|
|
247
|
+
description: "List of 2-4 technical tags related to the memory",
|
|
248
|
+
},
|
|
242
249
|
},
|
|
243
|
-
required: ["summary", "type"],
|
|
250
|
+
required: ["summary", "type", "tags"],
|
|
244
251
|
},
|
|
245
252
|
},
|
|
246
253
|
};
|
|
@@ -251,5 +258,6 @@ Analyze this conversation. If it contains technical work (code, bugs, features,
|
|
|
251
258
|
return {
|
|
252
259
|
summary: result.data.summary,
|
|
253
260
|
type: result.data.type,
|
|
261
|
+
tags: result.data.tags || [],
|
|
254
262
|
};
|
|
255
263
|
}
|
|
@@ -28,6 +28,7 @@ export declare class LocalMemoryClient {
|
|
|
28
28
|
addMemory(content: string, containerTag: string, metadata?: {
|
|
29
29
|
type?: MemoryType;
|
|
30
30
|
source?: "manual" | "auto-capture" | "import" | "api";
|
|
31
|
+
tags?: string[];
|
|
31
32
|
tool?: string;
|
|
32
33
|
sessionID?: string;
|
|
33
34
|
reasoning?: string;
|
|
@@ -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;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;;;;;;;;;
|
|
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,EAAE,CAAC;QAChB,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;;;;;;;;;IA+DG,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
|
@@ -99,17 +99,24 @@ export class LocalMemoryClient {
|
|
|
99
99
|
async addMemory(content, containerTag, metadata) {
|
|
100
100
|
try {
|
|
101
101
|
await this.initialize();
|
|
102
|
+
const tags = metadata?.tags || [];
|
|
102
103
|
const vector = await embeddingService.embedWithTimeout(content);
|
|
104
|
+
let tagsVector = undefined;
|
|
105
|
+
if (tags.length > 0) {
|
|
106
|
+
tagsVector = await embeddingService.embedWithTimeout(tags.join(", "));
|
|
107
|
+
}
|
|
103
108
|
const { scope, hash } = extractScopeFromContainerTag(containerTag);
|
|
104
109
|
const shard = shardManager.getWriteShard(scope, hash);
|
|
105
110
|
const id = `mem_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
106
111
|
const now = Date.now();
|
|
107
|
-
const { displayName, userName, userEmail, projectPath, projectName, gitRepoUrl, type, ...dynamicMetadata } = metadata || {};
|
|
112
|
+
const { displayName, userName, userEmail, projectPath, projectName, gitRepoUrl, type, tags: _tags, ...dynamicMetadata } = metadata || {};
|
|
108
113
|
const record = {
|
|
109
114
|
id,
|
|
110
115
|
content,
|
|
111
116
|
vector,
|
|
117
|
+
tagsVector,
|
|
112
118
|
containerTag,
|
|
119
|
+
tags: tags.length > 0 ? tags.join(",") : undefined,
|
|
113
120
|
type,
|
|
114
121
|
createdAt: now,
|
|
115
122
|
updatedAt: now,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/connection-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAOtC,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAAoC;IACvD,OAAO,CAAC,gBAAgB,CAAS;IAEjC,OAAO,CAAC,eAAe;IA+EvB,OAAO,CAAC,YAAY;
|
|
1
|
+
{"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/connection-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAOtC,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAAoC;IACvD,OAAO,CAAC,gBAAgB,CAAS;IAEjC,OAAO,CAAC,eAAe;IA+EvB,OAAO,CAAC,YAAY;IAuBpB,OAAO,CAAC,aAAa;IAqBrB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ;IAmBvC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IASrC,QAAQ,IAAI,IAAI;CAWjB;AAED,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
|
|
@@ -97,6 +97,26 @@ export class ConnectionManager {
|
|
|
97
97
|
`1. Install: brew install sqlite\n` +
|
|
98
98
|
`2. Configure customSqlitePath in ~/.config/opencode/opencode-mem.jsonc`);
|
|
99
99
|
}
|
|
100
|
+
this.migrateSchema(db);
|
|
101
|
+
}
|
|
102
|
+
migrateSchema(db) {
|
|
103
|
+
try {
|
|
104
|
+
const columns = db.prepare("PRAGMA table_info(memories)").all();
|
|
105
|
+
const hasTags = columns.some((c) => c.name === "tags");
|
|
106
|
+
if (!hasTags && columns.length > 0) {
|
|
107
|
+
db.run("ALTER TABLE memories ADD COLUMN tags TEXT");
|
|
108
|
+
log("Migrated schema: added tags column to memories table");
|
|
109
|
+
}
|
|
110
|
+
db.run(`
|
|
111
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_tags USING vec0(
|
|
112
|
+
memory_id TEXT PRIMARY KEY,
|
|
113
|
+
embedding BLOB float32
|
|
114
|
+
)
|
|
115
|
+
`);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
log("Schema migration error", { error: String(error) });
|
|
119
|
+
}
|
|
100
120
|
}
|
|
101
121
|
getConnection(dbPath) {
|
|
102
122
|
if (this.connections.has(dbPath)) {
|
|
@@ -5,6 +5,7 @@ export declare class ShardManager {
|
|
|
5
5
|
constructor();
|
|
6
6
|
private initMetadataDb;
|
|
7
7
|
private getShardPath;
|
|
8
|
+
private resolveStoredPath;
|
|
8
9
|
getActiveShard(scope: "user" | "project", scopeHash: string): ShardInfo | null;
|
|
9
10
|
getAllShards(scope: "user" | "project", scopeHash: string): ShardInfo[];
|
|
10
11
|
createShard(scope: "user" | "project", scopeHash: string, shardIndex: number): ShardInfo;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shard-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/shard-manager.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI5C,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAW;IAC7B,OAAO,CAAC,YAAY,CAAS;;IAQ7B,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,YAAY;IAKpB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAsB9E,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE;IAgCvE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS;
|
|
1
|
+
{"version":3,"file":"shard-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/shard-manager.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI5C,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAW;IAC7B,OAAO,CAAC,YAAY,CAAS;;IAQ7B,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,iBAAiB;IAKzB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAsB9E,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE;IAgCvE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS;IA6BxF,OAAO,CAAC,WAAW;IA2DnB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS;IAetE,OAAO,CAAC,iBAAiB;IAQzB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAO3C,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAO3C,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAkBhD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAuBnC;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
|