opencode-mem 2.7.0 → 2.7.2

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.
@@ -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,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;;;;;;;IA0B7B,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,SAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsDpD;AAED,eAAO,MAAM,YAAY,mBAA0B,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/services/client.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AA4CpD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,aAAa,CAAkB;;YAIzB,UAAU;IAiBlB,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjE,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAIjC,SAAS,IAAI;QACX,WAAW,EAAE,OAAO,CAAC;QACrB,WAAW,EAAE,OAAO,CAAC;QACrB,KAAK,EAAE,OAAO,CAAC;KAChB;IAQD,KAAK,IAAI,IAAI;IAIP,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;;;;;;;;;;;;;IA6BlD,SAAS,CACb,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,UAAU,CAAC;QAClB,MAAM,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,QAAQ,GAAG,KAAK,CAAC;QACtD,IAAI,CAAC,EAAE,MAAM,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsDpD;AAED,eAAO,MAAM,YAAY,mBAA0B,CAAC"}
@@ -141,9 +141,10 @@ export class LocalMemoryClient {
141
141
  async deleteMemory(memoryId) {
142
142
  try {
143
143
  await this.initialize();
144
- const { scope, hash } = extractScopeFromContainerTag(memoryId);
145
- const shards = shardManager.getAllShards(scope, hash);
146
- for (const shard of shards) {
144
+ const userShards = shardManager.getAllShards("user", "");
145
+ const projectShards = shardManager.getAllShards("project", "");
146
+ const allShards = [...userShards, ...projectShards];
147
+ for (const shard of allShards) {
147
148
  const db = connectionManager.getConnection(shard.dbPath);
148
149
  const memory = vectorSearch.getMemoryById(db, memoryId);
149
150
  if (memory) {
@@ -1,3 +1,3 @@
1
- export declare const safeArray: <T>(arr: T[] | null | undefined) => T[];
2
- export declare const safeObject: <T extends object>(obj: T | null | undefined, fallback: T) => T;
1
+ export declare const safeArray: <T>(arr: any) => T[];
2
+ export declare const safeObject: <T extends object>(obj: any, fallback: T) => T;
3
3
  //# sourceMappingURL=profile-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"profile-utils.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/profile-utils.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,GAAI,CAAC,EAAE,KAAK,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,KAAG,CAAC,EAAe,CAAC;AAC5E,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,MAAM,EAAE,KAAK,CAAC,GAAG,IAAI,GAAG,SAAS,EAAE,UAAU,CAAC,KAAG,CACrE,CAAC"}
1
+ {"version":3,"file":"profile-utils.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/profile-utils.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,GAAI,CAAC,EAAE,KAAK,GAAG,KAAG,CAAC,EA0BxC,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,MAAM,EAAE,KAAK,GAAG,EAAE,UAAU,CAAC,KAAG,CAWpE,CAAC"}
@@ -1,2 +1,45 @@
1
- export const safeArray = (arr) => arr ?? [];
2
- export const safeObject = (obj, fallback) => obj ?? fallback;
1
+ export const safeArray = (arr) => {
2
+ if (!arr)
3
+ return [];
4
+ let result = arr;
5
+ if (typeof result === "string") {
6
+ try {
7
+ result = JSON.parse(result);
8
+ }
9
+ catch {
10
+ try {
11
+ result = JSON.parse(result.trim().replace(/,$/, ""));
12
+ }
13
+ catch {
14
+ return [];
15
+ }
16
+ }
17
+ }
18
+ if (!Array.isArray(result))
19
+ return [];
20
+ const flattened = [];
21
+ const walk = (item) => {
22
+ if (Array.isArray(item)) {
23
+ item.forEach(walk);
24
+ }
25
+ else if (item) {
26
+ flattened.push(item);
27
+ }
28
+ };
29
+ walk(result);
30
+ return flattened;
31
+ };
32
+ export const safeObject = (obj, fallback) => {
33
+ if (!obj)
34
+ return fallback;
35
+ let result = obj;
36
+ if (typeof result === "string") {
37
+ try {
38
+ result = JSON.parse(result);
39
+ }
40
+ catch {
41
+ return fallback;
42
+ }
43
+ }
44
+ return result && typeof result === "object" && !Array.isArray(result) ? result : fallback;
45
+ };
@@ -17,6 +17,7 @@ export declare class UserProfileManager {
17
17
  private rowToProfile;
18
18
  private rowToChangelog;
19
19
  mergeProfileData(existing: UserProfileData, updates: Partial<UserProfileData>): UserProfileData;
20
+ private ensureArray;
20
21
  }
21
22
  export declare const userProfileManager: UserProfileManager;
22
23
  //# sourceMappingURL=user-profile-manager.d.ts.map
@@ -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;CAmFhG;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
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;IAoCT,aAAa,CACX,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,eAAe,EAC5B,yBAAyB,EAAE,MAAM,EACjC,aAAa,EAAE,MAAM,GACpB,IAAI;IAmCP,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;IA0F/F,OAAO,CAAC,WAAW;CAWpB;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
@@ -59,6 +59,11 @@ export class UserProfileManager {
59
59
  createProfile(userId, displayName, userName, userEmail, profileData, promptsAnalyzed) {
60
60
  const id = `profile_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
61
61
  const now = Date.now();
62
+ const cleanedData = {
63
+ preferences: safeArray(profileData.preferences),
64
+ patterns: safeArray(profileData.patterns),
65
+ workflows: safeArray(profileData.workflows),
66
+ };
62
67
  const stmt = this.db.prepare(`
63
68
  INSERT INTO user_profiles (
64
69
  id, user_id, display_name, user_name, user_email,
@@ -67,12 +72,17 @@ export class UserProfileManager {
67
72
  )
68
73
  VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, 1)
69
74
  `);
70
- stmt.run(id, userId, displayName, userName, userEmail, JSON.stringify(profileData), now, now, promptsAnalyzed);
71
- this.addChangelog(id, 1, "create", "Initial profile creation", profileData);
75
+ stmt.run(id, userId, displayName, userName, userEmail, JSON.stringify(cleanedData), now, now, promptsAnalyzed);
76
+ this.addChangelog(id, 1, "create", "Initial profile creation", cleanedData);
72
77
  return id;
73
78
  }
74
79
  updateProfile(profileId, profileData, additionalPromptsAnalyzed, changeSummary) {
75
80
  const now = Date.now();
81
+ const cleanedData = {
82
+ preferences: safeArray(profileData.preferences),
83
+ patterns: safeArray(profileData.patterns),
84
+ workflows: safeArray(profileData.workflows),
85
+ };
76
86
  const getVersionStmt = this.db.prepare(`SELECT version FROM user_profiles WHERE id = ?`);
77
87
  const versionRow = getVersionStmt.get(profileId);
78
88
  const newVersion = (versionRow?.version || 0) + 1;
@@ -84,8 +94,8 @@ export class UserProfileManager {
84
94
  total_prompts_analyzed = total_prompts_analyzed + ?
85
95
  WHERE id = ?
86
96
  `);
87
- updateStmt.run(JSON.stringify(profileData), newVersion, now, additionalPromptsAnalyzed, profileId);
88
- this.addChangelog(profileId, newVersion, "update", changeSummary, profileData);
97
+ updateStmt.run(JSON.stringify(cleanedData), newVersion, now, additionalPromptsAnalyzed, profileId);
98
+ this.addChangelog(profileId, newVersion, "update", changeSummary, cleanedData);
89
99
  this.cleanupOldChangelogs(profileId);
90
100
  }
91
101
  addChangelog(profileId, version, changeType, changeSummary, profileData) {
@@ -191,21 +201,25 @@ export class UserProfileManager {
191
201
  }
192
202
  mergeProfileData(existing, updates) {
193
203
  const merged = {
194
- preferences: safeArray(existing?.preferences),
195
- patterns: safeArray(existing?.patterns),
196
- workflows: safeArray(existing?.workflows),
204
+ preferences: this.ensureArray(existing?.preferences),
205
+ patterns: this.ensureArray(existing?.patterns),
206
+ workflows: this.ensureArray(existing?.workflows),
197
207
  };
198
208
  if (updates.preferences) {
199
- for (const newPref of updates.preferences) {
209
+ const incomingPrefs = this.ensureArray(updates.preferences);
210
+ for (const newPref of incomingPrefs) {
200
211
  const existingIndex = merged.preferences.findIndex((p) => p.category === newPref.category && p.description === newPref.description);
201
212
  if (existingIndex >= 0) {
202
- const existing = merged.preferences[existingIndex];
203
- if (existing) {
213
+ const existingItem = merged.preferences[existingIndex];
214
+ if (existingItem) {
204
215
  merged.preferences[existingIndex] = {
205
216
  ...newPref,
206
- confidence: Math.min(1, existing.confidence + 0.1),
217
+ confidence: Math.min(1, (existingItem.confidence || 0) + 0.1),
207
218
  evidence: [
208
- ...new Set([...safeArray(existing.evidence), ...safeArray(newPref.evidence)]),
219
+ ...new Set([
220
+ ...this.ensureArray(existingItem.evidence),
221
+ ...this.ensureArray(newPref.evidence),
222
+ ]),
209
223
  ].slice(0, 5),
210
224
  lastUpdated: Date.now(),
211
225
  };
@@ -215,18 +229,19 @@ export class UserProfileManager {
215
229
  merged.preferences.push({ ...newPref, lastUpdated: Date.now() });
216
230
  }
217
231
  }
218
- merged.preferences.sort((a, b) => b.confidence - a.confidence);
232
+ merged.preferences.sort((a, b) => (b.confidence || 0) - (a.confidence || 0));
219
233
  merged.preferences = merged.preferences.slice(0, CONFIG.userProfileMaxPreferences);
220
234
  }
221
235
  if (updates.patterns) {
222
- for (const newPattern of updates.patterns) {
236
+ const incomingPatterns = this.ensureArray(updates.patterns);
237
+ for (const newPattern of incomingPatterns) {
223
238
  const existingIndex = merged.patterns.findIndex((p) => p.category === newPattern.category && p.description === newPattern.description);
224
239
  if (existingIndex >= 0) {
225
- const existing = merged.patterns[existingIndex];
226
- if (existing) {
240
+ const existingItem = merged.patterns[existingIndex];
241
+ if (existingItem) {
227
242
  merged.patterns[existingIndex] = {
228
243
  ...newPattern,
229
- frequency: existing.frequency + 1,
244
+ frequency: (existingItem.frequency || 1) + 1,
230
245
  lastSeen: Date.now(),
231
246
  };
232
247
  }
@@ -235,18 +250,19 @@ export class UserProfileManager {
235
250
  merged.patterns.push({ ...newPattern, frequency: 1, lastSeen: Date.now() });
236
251
  }
237
252
  }
238
- merged.patterns.sort((a, b) => b.frequency - a.frequency);
253
+ merged.patterns.sort((a, b) => (b.frequency || 0) - (a.frequency || 0));
239
254
  merged.patterns = merged.patterns.slice(0, CONFIG.userProfileMaxPatterns);
240
255
  }
241
256
  if (updates.workflows) {
242
- for (const newWorkflow of updates.workflows) {
257
+ const incomingWorkflows = this.ensureArray(updates.workflows);
258
+ for (const newWorkflow of incomingWorkflows) {
243
259
  const existingIndex = merged.workflows.findIndex((w) => w.description === newWorkflow.description);
244
260
  if (existingIndex >= 0) {
245
- const existing = merged.workflows[existingIndex];
246
- if (existing) {
261
+ const existingItem = merged.workflows[existingIndex];
262
+ if (existingItem) {
247
263
  merged.workflows[existingIndex] = {
248
264
  ...newWorkflow,
249
- frequency: existing.frequency + 1,
265
+ frequency: (existingItem.frequency || 1) + 1,
250
266
  };
251
267
  }
252
268
  }
@@ -254,10 +270,22 @@ export class UserProfileManager {
254
270
  merged.workflows.push({ ...newWorkflow, frequency: 1 });
255
271
  }
256
272
  }
257
- merged.workflows.sort((a, b) => b.frequency - a.frequency);
273
+ merged.workflows.sort((a, b) => (b.frequency || 0) - (a.frequency || 0));
258
274
  merged.workflows = merged.workflows.slice(0, CONFIG.userProfileMaxWorkflows);
259
275
  }
260
276
  return merged;
261
277
  }
278
+ ensureArray(val) {
279
+ if (typeof val === "string") {
280
+ try {
281
+ const parsed = JSON.parse(val);
282
+ return Array.isArray(parsed) ? parsed : [];
283
+ }
284
+ catch {
285
+ return [];
286
+ }
287
+ }
288
+ return Array.isArray(val) ? val : [];
289
+ }
262
290
  }
263
291
  export const userProfileManager = new UserProfileManager();
package/dist/web/app.js CHANGED
@@ -170,6 +170,7 @@ function groupMemories(items) {
170
170
  function renderCombinedCard(pair) {
171
171
  const { memory, prompt } = pair;
172
172
  const isSelected = state.selectedMemories.has(memory.id);
173
+ const isPinned = memory.isPinned || false;
173
174
  const similarityHtml =
174
175
  memory.similarity !== undefined
175
176
  ? `<span class="similarity-score">${Math.round(memory.similarity * 100)}%</span>`
@@ -180,8 +181,20 @@ function renderCombinedCard(pair) {
180
181
  ? `<div class="tags-list">${memory.tags.map((t) => `<span class="tag-badge">${escapeHtml(t)}</span>`).join("")}</div>`
181
182
  : "";
182
183
 
184
+ const pinButton = isPinned
185
+ ? `<button class="btn-pin pinned" onclick="unpinMemory('${memory.id}')" title="Unpin"><i data-lucide="pin" class="icon icon-filled"></i></button>`
186
+ : `<button class="btn-pin" onclick="pinMemory('${memory.id}')" title="Pin"><i data-lucide="pin" class="icon"></i></button>`;
187
+
188
+ const createdDate = formatDate(memory.createdAt);
189
+ const updatedDate =
190
+ memory.updatedAt && memory.updatedAt !== memory.createdAt ? formatDate(memory.updatedAt) : null;
191
+
192
+ const dateInfo = updatedDate
193
+ ? `<span>Created: ${createdDate}</span><span>Updated: ${updatedDate}</span>`
194
+ : `<span>Created: ${createdDate}</span>`;
195
+
183
196
  return `
184
- <div class="combined-card ${isSelected ? "selected" : ""}" data-id="${memory.id}">
197
+ <div class="combined-card ${isSelected ? "selected" : ""} ${isPinned ? "pinned" : ""}" data-id="${memory.id}">
185
198
  <div class="combined-prompt-section">
186
199
  <div class="combined-header">
187
200
  <span class="badge badge-prompt">USER PROMPT</span>
@@ -201,9 +214,12 @@ function renderCombinedCard(pair) {
201
214
  <span class="badge badge-memory">MEMORY</span>
202
215
  ${memory.memoryType ? `<span class="badge badge-type">${memory.memoryType}</span>` : ""}
203
216
  ${similarityHtml}
217
+ ${isPinned ? '<span class="badge badge-pinned">PINNED</span>' : ""}
204
218
  <span class="memory-display-name">${escapeHtml(memory.displayName || memory.id)}</span>
205
219
  </div>
206
220
  <div class="memory-actions">
221
+ ${pinButton}
222
+ <button class="btn-edit" onclick="editMemory('${memory.id}')"><i data-lucide="edit-3" class="icon"></i></button>
207
223
  <button class="btn-delete" onclick="deleteMemoryWithLink('${memory.id}', true)">
208
224
  <i data-lucide="trash-2" class="icon"></i> Delete Pair
209
225
  </button>
@@ -211,6 +227,10 @@ function renderCombinedCard(pair) {
211
227
  </div>
212
228
  ${tagsHtml}
213
229
  <div class="memory-content markdown-content">${renderMarkdown(memory.content)}</div>
230
+ <div class="memory-footer">
231
+ ${dateInfo}
232
+ <span>ID: ${memory.id}</span>
233
+ </div>
214
234
  </div>
215
235
  </div>
216
236
  `;
@@ -907,10 +927,40 @@ function renderUserProfile() {
907
927
  return;
908
928
  }
909
929
 
910
- const data = profile.profileData;
911
- const preferences = data.preferences || [];
912
- const patterns = data.patterns || [];
913
- const workflows = data.workflows || [];
930
+ let data = profile.profileData;
931
+ if (typeof data === "string") {
932
+ try {
933
+ data = JSON.parse(data);
934
+ } catch (e) {
935
+ console.error("Failed to parse profileData string", e);
936
+ }
937
+ }
938
+
939
+ const parseField = (field) => {
940
+ if (!field) return [];
941
+ let result = field;
942
+ let lastResult = null;
943
+ while (typeof result === "string" && result !== lastResult) {
944
+ lastResult = result;
945
+ try {
946
+ result = JSON.parse(typeof jsonrepair === "function" ? jsonrepair(result) : result);
947
+ } catch {
948
+ break;
949
+ }
950
+ }
951
+ if (!Array.isArray(result)) return [];
952
+ const flattened = [];
953
+ const walk = (item) => {
954
+ if (Array.isArray(item)) item.forEach(walk);
955
+ else if (item && typeof item === "object") flattened.push(item);
956
+ };
957
+ walk(result);
958
+ return flattened;
959
+ };
960
+
961
+ const preferences = parseField(data.preferences);
962
+ const patterns = parseField(data.patterns);
963
+ const workflows = parseField(data.workflows);
914
964
 
915
965
  container.innerHTML = `
916
966
  <div class="profile-header">
@@ -945,18 +995,18 @@ function renderUserProfile() {
945
995
  : `
946
996
  <div class="cards-grid">
947
997
  ${preferences
948
- .sort((a, b) => b.confidence - a.confidence)
998
+ .sort((a, b) => (b.confidence || 0) - (a.confidence || 0))
949
999
  .map(
950
1000
  (p) => `
951
1001
  <div class="compact-card preference-card">
952
1002
  <div class="card-top">
953
- <span class="category-tag">${escapeHtml(p.category)}</span>
954
- <div class="confidence-ring" style="--p:${Math.round(p.confidence * 100)}">
955
- <span>${Math.round(p.confidence * 100)}%</span>
1003
+ <span class="category-tag">${escapeHtml(p.category || "General")}</span>
1004
+ <div class="confidence-ring" style="--p:${Math.round((p.confidence || 0) * 100)}">
1005
+ <span>${Math.round((p.confidence || 0) * 100)}%</span>
956
1006
  </div>
957
1007
  </div>
958
1008
  <div class="card-body">
959
- <p class="card-text">${escapeHtml(p.description)}</p>
1009
+ <p class="card-text">${escapeHtml(p.description || "")}</p>
960
1010
  </div>
961
1011
  ${
962
1012
  p.evidence && p.evidence.length > 0
@@ -989,10 +1039,10 @@ function renderUserProfile() {
989
1039
  (p) => `
990
1040
  <div class="compact-card pattern-card">
991
1041
  <div class="card-top">
992
- <span class="category-tag">${escapeHtml(p.category)}</span>
1042
+ <span class="category-tag">${escapeHtml(p.category || "General")}</span>
993
1043
  </div>
994
1044
  <div class="card-body">
995
- <p class="card-text">${escapeHtml(p.description)}</p>
1045
+ <p class="card-text">${escapeHtml(p.description || "")}</p>
996
1046
  </div>
997
1047
  </div>
998
1048
  `
@@ -1014,16 +1064,16 @@ function renderUserProfile() {
1014
1064
  .map(
1015
1065
  (w) => `
1016
1066
  <div class="workflow-row">
1017
- <div class="workflow-title">${escapeHtml(w.description)}</div>
1067
+ <div class="workflow-title">${escapeHtml(w.description || "")}</div>
1018
1068
  <div class="workflow-steps-horizontal">
1019
- ${w.steps
1069
+ ${(w.steps || [])
1020
1070
  .map(
1021
1071
  (step, i) => `
1022
1072
  <div class="step-node">
1023
1073
  <span class="step-idx">${i + 1}</span>
1024
1074
  <span class="step-content">${escapeHtml(step)}</span>
1025
1075
  </div>
1026
- ${i < w.steps.length - 1 ? '<i data-lucide="arrow-right" class="step-arrow"></i>' : ""}
1076
+ ${i < (w.steps || []).length - 1 ? '<i data-lucide="arrow-right" class="step-arrow"></i>' : ""}
1027
1077
  `
1028
1078
  )
1029
1079
  .join("")}
@@ -9,6 +9,7 @@
9
9
  <script src="https://unpkg.com/lucide@latest"></script>
10
10
  <script src="https://cdn.jsdelivr.net/npm/marked@17.0.1/lib/marked.umd.min.js"></script>
11
11
  <script src="https://cdn.jsdelivr.net/npm/dompurify@3.2.2/dist/purify.min.js"></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/jsonrepair@latest/lib/umd/jsonrepair.min.js"></script>
12
13
  </head>
13
14
  <body>
14
15
  <div class="container">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-mem",
3
- "version": "2.7.0",
3
+ "version": "2.7.2",
4
4
  "description": "OpenCode plugin that gives coding agents persistent memory using local vector database",
5
5
  "type": "module",
6
6
  "main": "dist/plugin.js",