opencode-mem 2.7.1 → 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,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
@@ -927,10 +927,40 @@ function renderUserProfile() {
927
927
  return;
928
928
  }
929
929
 
930
- const data = profile.profileData;
931
- const preferences = data.preferences || [];
932
- const patterns = data.patterns || [];
933
- 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);
934
964
 
935
965
  container.innerHTML = `
936
966
  <div class="profile-header">
@@ -965,18 +995,18 @@ function renderUserProfile() {
965
995
  : `
966
996
  <div class="cards-grid">
967
997
  ${preferences
968
- .sort((a, b) => b.confidence - a.confidence)
998
+ .sort((a, b) => (b.confidence || 0) - (a.confidence || 0))
969
999
  .map(
970
1000
  (p) => `
971
1001
  <div class="compact-card preference-card">
972
1002
  <div class="card-top">
973
- <span class="category-tag">${escapeHtml(p.category)}</span>
974
- <div class="confidence-ring" style="--p:${Math.round(p.confidence * 100)}">
975
- <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>
976
1006
  </div>
977
1007
  </div>
978
1008
  <div class="card-body">
979
- <p class="card-text">${escapeHtml(p.description)}</p>
1009
+ <p class="card-text">${escapeHtml(p.description || "")}</p>
980
1010
  </div>
981
1011
  ${
982
1012
  p.evidence && p.evidence.length > 0
@@ -1009,10 +1039,10 @@ function renderUserProfile() {
1009
1039
  (p) => `
1010
1040
  <div class="compact-card pattern-card">
1011
1041
  <div class="card-top">
1012
- <span class="category-tag">${escapeHtml(p.category)}</span>
1042
+ <span class="category-tag">${escapeHtml(p.category || "General")}</span>
1013
1043
  </div>
1014
1044
  <div class="card-body">
1015
- <p class="card-text">${escapeHtml(p.description)}</p>
1045
+ <p class="card-text">${escapeHtml(p.description || "")}</p>
1016
1046
  </div>
1017
1047
  </div>
1018
1048
  `
@@ -1034,16 +1064,16 @@ function renderUserProfile() {
1034
1064
  .map(
1035
1065
  (w) => `
1036
1066
  <div class="workflow-row">
1037
- <div class="workflow-title">${escapeHtml(w.description)}</div>
1067
+ <div class="workflow-title">${escapeHtml(w.description || "")}</div>
1038
1068
  <div class="workflow-steps-horizontal">
1039
- ${w.steps
1069
+ ${(w.steps || [])
1040
1070
  .map(
1041
1071
  (step, i) => `
1042
1072
  <div class="step-node">
1043
1073
  <span class="step-idx">${i + 1}</span>
1044
1074
  <span class="step-content">${escapeHtml(step)}</span>
1045
1075
  </div>
1046
- ${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>' : ""}
1047
1077
  `
1048
1078
  )
1049
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.1",
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",