@icex-labs/openclaw-memory-engine 5.0.3 → 5.1.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.
@@ -140,9 +140,26 @@ for (const { path, tag } of files) {
140
140
  continue;
141
141
  }
142
142
 
143
+ // Infer date from filename (e.g., 2026-03-28.md → 2026-03-28, 2026-W13.md → week date)
144
+ const dateMatch = basename(path).match(/^(\d{4}-\d{2}-\d{2})/);
145
+ const weekMatch = basename(path).match(/^(\d{4})-W(\d{2})/);
146
+ let inferredTs;
147
+ if (dateMatch) {
148
+ inferredTs = dateMatch[1] + "T12:00:00Z";
149
+ } else if (weekMatch) {
150
+ // Approximate: week number × 7 days from Jan 1
151
+ const year = parseInt(weekMatch[1]);
152
+ const week = parseInt(weekMatch[2]);
153
+ const jan1 = new Date(year, 0, 1);
154
+ const weekDate = new Date(jan1.getTime() + (week - 1) * 7 * 86400000);
155
+ inferredTs = weekDate.toISOString();
156
+ } else {
157
+ inferredTs = new Date().toISOString();
158
+ }
159
+
143
160
  const record = {
144
161
  id: `arch-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
145
- ts: new Date().toISOString(),
162
+ ts: inferredTs,
146
163
  last_accessed: null,
147
164
  access_count: 0,
148
165
  importance: 5,
package/index.js CHANGED
@@ -702,7 +702,7 @@ export default definePluginEntry({
702
702
  async execute(_id, params) {
703
703
  const wsp = ws(agentId, params);
704
704
  try {
705
- const result = runQualityPass(wsp, {
705
+ const result = await runQualityPass(wsp, {
706
706
  skipGraph: params.skip_graph || false,
707
707
  skipEpisodes: params.skip_episodes || false,
708
708
  });
package/lib/quality.js CHANGED
@@ -67,12 +67,18 @@ export async function runQualityPass(ws, options = {}) {
67
67
  }
68
68
 
69
69
  /**
70
- * Generate episode summaries from clusters of records on the same day.
70
+ * Generate episode summaries from records.
71
+ * Strategy 1: group by date (works when ts reflects original dates)
72
+ * Strategy 2: group by entity/topic (fallback when all ts are same day, e.g., after migration)
71
73
  */
72
74
  function generateEpisodesFromRecords(ws, records) {
73
75
  const episodes = loadEpisodes(ws);
76
+ const existingTopics = new Set(episodes.flatMap((e) => e.topics || []));
74
77
  const existingDates = new Set(episodes.map((e) => e.ts?.slice(0, 10)));
75
78
 
79
+ let generated = 0;
80
+
81
+ // Strategy 1: by date
76
82
  const byDate = {};
77
83
  for (const r of records) {
78
84
  if (!r.ts) continue;
@@ -81,14 +87,15 @@ function generateEpisodesFromRecords(ws, records) {
81
87
  byDate[date].push(r);
82
88
  }
83
89
 
84
- let generated = 0;
85
90
  for (const [date, dayRecords] of Object.entries(byDate)) {
86
91
  if (existingDates.has(date) || dayRecords.length < 3) continue;
92
+ // Skip if most records share the same date (migration artifact)
93
+ if (dayRecords.length > records.length * 0.5) continue;
87
94
 
88
95
  const topics = [...new Set(dayRecords.map((r) => r.entity).filter((e) => e && e !== "general"))];
89
96
  const topContent = dayRecords
90
97
  .sort((a, b) => (b.importance || 5) - (a.importance || 5))
91
- .slice(0, 5)
98
+ .slice(0, 3)
92
99
  .map((r) => r.content.slice(0, 80))
93
100
  .join("; ");
94
101
 
@@ -103,6 +110,35 @@ function generateEpisodesFromRecords(ws, records) {
103
110
  generated++;
104
111
  }
105
112
 
113
+ // Strategy 2: by entity (fallback for migration data with same-day ts)
114
+ const byEntity = {};
115
+ for (const r of records) {
116
+ const e = r.entity || "general";
117
+ if (e === "general") continue;
118
+ if (!byEntity[e]) byEntity[e] = [];
119
+ byEntity[e].push(r);
120
+ }
121
+
122
+ for (const [entity, recs] of Object.entries(byEntity)) {
123
+ if (recs.length < 5) continue;
124
+ if (existingTopics.has(entity)) continue;
125
+
126
+ const top = recs
127
+ .sort((a, b) => (b.importance || 5) - (a.importance || 5))
128
+ .slice(0, 3)
129
+ .map((r) => r.content.slice(0, 80));
130
+
131
+ saveEpisode(ws, {
132
+ summary: `[${entity}] ${top.join("; ")}`,
133
+ decisions: [],
134
+ mood: "",
135
+ topics: [entity],
136
+ participants: [],
137
+ source: "topic-summary",
138
+ });
139
+ generated++;
140
+ }
141
+
106
142
  return generated;
107
143
  }
108
144
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icex-labs/openclaw-memory-engine",
3
- "version": "5.0.3",
3
+ "version": "5.1.0",
4
4
  "description": "MemGPT-style hierarchical memory plugin for OpenClaw — core memory block + archival storage with semantic search",
5
5
  "type": "module",
6
6
  "main": "index.js",