openclaw-memory-alibaba-local 0.1.9-beta.7 → 1.0.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/capture-state.ts CHANGED
@@ -166,7 +166,6 @@ function getMessageRoleRaw(msg: unknown): string {
166
166
  export function resolveRoleCountsForSession(
167
167
  entry: CursorFileEntry | undefined,
168
168
  messages: unknown[],
169
- log: { info: (m: string) => void },
170
169
  ): { roleCounts: Record<string, number>; lastMessagesLength: number } {
171
170
  if (isV2Entry(entry)) {
172
171
  return {
@@ -176,7 +175,7 @@ export function resolveRoleCountsForSession(
176
175
  }
177
176
  if (isLegacyEntry(entry)) {
178
177
  const end = Math.min(Math.max(0, entry.lastEndExclusive), messages.length);
179
- log.info("openclaw-memory-alibaba-local: migrated legacy full-context cursor to per-role counts");
178
+ console.log("[openclaw-memory-alibaba-local] migrated legacy full-context cursor to per-role counts");
180
179
  return {
181
180
  roleCounts: countRolesInMessagesPrefix(messages, end),
182
181
  lastMessagesLength: end,
package/categories.ts CHANGED
@@ -8,6 +8,9 @@ export const USER_MEMORY_FACT = "user_memory_fact" as const;
8
8
  export const USER_MEMORY_PREFERENCE = "user_memory_preference" as const;
9
9
  export const USER_MEMORY_DECISION = "user_memory_decision" as const;
10
10
 
11
+ /** World fact: event/entity memories from third-party conversations (LRU-managed). */
12
+ export const WORLD_FACT = "world_fact" as const;
13
+
11
14
  /** All user memory category values (for recall: treat as one logical "user_memory" set) */
12
15
  export const USER_MEMORY_CATEGORIES = [
13
16
  USER_MEMORY_FACT,
@@ -15,11 +18,23 @@ export const USER_MEMORY_CATEGORIES = [
15
18
  USER_MEMORY_DECISION,
16
19
  ] as const;
17
20
 
21
+ /** Categories to search during recall (user memories + world facts). */
22
+ export const RECALL_USER_CATEGORIES = [
23
+ USER_MEMORY_FACT,
24
+ USER_MEMORY_PREFERENCE,
25
+ USER_MEMORY_DECISION,
26
+ WORLD_FACT,
27
+ ] as const;
28
+
18
29
  export type UserMemoryCategory =
19
30
  | typeof USER_MEMORY_FACT
20
31
  | typeof USER_MEMORY_PREFERENCE
21
32
  | typeof USER_MEMORY_DECISION;
22
33
 
34
+ export function isWorldFact(cat: string): boolean {
35
+ return cat === WORLD_FACT;
36
+ }
37
+
23
38
  /** Full context (conversation audit). Legacy single category. */
24
39
  export const FULL_CONTEXT_MEMORY = "full_context_memory" as const;
25
40
 
@@ -62,12 +77,14 @@ export type SelfImprovingCategory = (typeof SELF_IMPROVING_CATEGORIES)[number];
62
77
  /** All allowed category values */
63
78
  export type MemoryCategory =
64
79
  | UserMemoryCategory
80
+ | typeof WORLD_FACT
65
81
  | typeof FULL_CONTEXT_MEMORY
66
82
  | FullContextSourceCategory
67
83
  | SelfImprovingCategory;
68
84
 
69
85
  export const ALL_CATEGORIES: readonly MemoryCategory[] = [
70
86
  ...USER_MEMORY_CATEGORIES,
87
+ WORLD_FACT,
71
88
  FULL_CONTEXT_MEMORY,
72
89
  ...FULL_CONTEXT_SOURCE_CATEGORIES,
73
90
  ...SELF_IMPROVING_CATEGORIES,
@@ -103,6 +120,7 @@ export const MEMORY_CATEGORY_LABEL_ZH: Readonly<Record<MemoryCategory, string>>
103
120
  [USER_MEMORY_FACT]: "用户事实",
104
121
  [USER_MEMORY_PREFERENCE]: "用户偏好",
105
122
  [USER_MEMORY_DECISION]: "用户决策",
123
+ [WORLD_FACT]: "世界事实",
106
124
  [FULL_CONTEXT_MEMORY]: "全文记忆",
107
125
  [FULL_CONTEXT_USER]: "全文 · 用户消息",
108
126
  [FULL_CONTEXT_ASSISTANT]: "全文 · AI助手消息",
package/config.ts CHANGED
@@ -6,9 +6,11 @@ import {
6
6
  ALL_CATEGORIES,
7
7
  FULL_CONTEXT_SOURCE_CATEGORIES,
8
8
  MEMORY_CATEGORY_LABEL_ZH,
9
+ RECALL_USER_CATEGORIES,
9
10
  SELF_IMPROVING_CATEGORIES,
10
11
  USER_MEMORY_CATEGORIES,
11
12
  USER_MEMORY_FACT,
13
+ WORLD_FACT,
12
14
  } from "./categories.js";
13
15
 
14
16
  /** OpenAI-compatible HTTP embeddings; all fields required — invalid/empty values fail on first embed, not at startup. */
@@ -52,7 +54,7 @@ export type AdminPanelMemoryTypeOptionsResolved = {
52
54
 
53
55
  const ADMIN_PANEL_TAB_KEYS = ["user", "self", "full"] as const;
54
56
 
55
- const USER_MEMORY_CAT_SET = new Set<string>(USER_MEMORY_CATEGORIES);
57
+ const USER_MEMORY_CAT_SET = new Set<string>([...USER_MEMORY_CATEGORIES, WORLD_FACT]);
56
58
  const SELF_MEMORY_CAT_SET = new Set<string>(SELF_IMPROVING_CATEGORIES);
57
59
  const FULL_SOURCE_CAT_SET = new Set<string>(FULL_CONTEXT_SOURCE_CATEGORIES);
58
60
 
@@ -569,7 +571,7 @@ export const memoryConfigSchema = {
569
571
  enableFullContextMemory,
570
572
  enableSelfImprovingMemory,
571
573
  memoryExtractionMethod,
572
- autoRecall: cfg.autoRecall === true,
574
+ autoRecall: cfg.autoRecall !== false,
573
575
  autoCapture: cfg.autoCapture !== false,
574
576
  captureMaxChars: captureMaxChars ?? DEFAULT_CAPTURE_MAX_CHARS,
575
577
  enableMemoryDecay,
package/db.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  SELF_IMPROVING_CATEGORIES,
8
8
  USER_MEMORY_FACT,
9
9
  USER_MEMORY_CATEGORIES,
10
+ WORLD_FACT,
10
11
  } from "./categories.js";
11
12
 
12
13
  export type MemoryEntry = {
@@ -235,7 +236,7 @@ export type MemoryDashboardAggregate = {
235
236
  uniqueSessions: number;
236
237
  };
237
238
 
238
- const USER_CAT_SET = new Set<string>(USER_MEMORY_CATEGORIES);
239
+ const USER_CAT_SET = new Set<string>([...USER_MEMORY_CATEGORIES, WORLD_FACT]);
239
240
  const SELF_CAT_SET = new Set<string>(SELF_IMPROVING_CATEGORIES);
240
241
  const FULL_CAT_SET = new Set<string>([FULL_CONTEXT_MEMORY, ...FULL_CONTEXT_SOURCE_CATEGORIES]);
241
242
 
@@ -733,6 +734,7 @@ export class MemoryDB {
733
734
 
734
735
  for (const raw of rows as Array<Record<string, unknown>>) {
735
736
  const cat = String(raw.category ?? "");
737
+ const displayCat = cat === WORLD_FACT ? USER_MEMORY_FACT : cat;
736
738
  const createdAt = Number(raw.createdAt ?? 0);
737
739
  const importance = Number(raw.importance ?? 0);
738
740
  const ag = String(raw.agentId ?? "").trim();
@@ -741,7 +743,7 @@ export class MemoryDB {
741
743
  const k = memoryCategoryKind(cat);
742
744
  byKind[k]++;
743
745
 
744
- byCategory[cat] = (byCategory[cat] ?? 0) + 1;
746
+ byCategory[displayCat] = (byCategory[displayCat] ?? 0) + 1;
745
747
 
746
748
  if (ag) {
747
749
  agentCounts.set(ag, (agentCounts.get(ag) ?? 0) + 1);
@@ -1318,4 +1320,139 @@ export class MemoryDB {
1318
1320
  this.db = null;
1319
1321
  this.initPromise = null;
1320
1322
  }
1323
+
1324
+ /**
1325
+ * Return top N user memory entries sorted by importance DESC.
1326
+ * Used by session_start hook to inject key personal facts into a new session.
1327
+ */
1328
+ async listTopByImportance(
1329
+ agentId: string,
1330
+ categories: MemoryCategory[],
1331
+ limit: number,
1332
+ ): Promise<MemoryEntry[]> {
1333
+ if (categories.length === 0 || limit <= 0) return [];
1334
+ await this.ensureInitialized();
1335
+ await this.refreshToLatest();
1336
+ const a = sqlEscapeLiteral(agentId);
1337
+ const list = categories.map((c) => `'${sqlEscapeLiteral(String(c))}'`).join(", ");
1338
+ const where = `agentId = '${a}' AND isDeleted = 0 AND category IN (${list})`;
1339
+ const rows = await this.table!
1340
+ .query()
1341
+ .where(where)
1342
+ .select(["id", "agentId", "sessionId", "text", "importance", "category", "createdAt", "isDeleted", "batchId", "seqInBatch", "chunkIndex"])
1343
+ .toArray();
1344
+ const entries = (rows as Array<Record<string, unknown>>).map((r) => rowToEntry(r, agentId));
1345
+ entries.sort((a, b) => b.importance - a.importance);
1346
+ return entries.slice(0, limit);
1347
+ }
1348
+
1349
+ /**
1350
+ * GC full_context_* rows for a given agent:
1351
+ * 1. Delete rows older than maxAgeMs.
1352
+ * 2. If remaining count > maxRows, delete oldest until count <= maxRows.
1353
+ */
1354
+ async gcFullContext(
1355
+ agentId: string,
1356
+ maxRows: number,
1357
+ maxAgeMs: number,
1358
+ ): Promise<void> {
1359
+ await this.ensureInitialized();
1360
+ const a = sqlEscapeLiteral(agentId);
1361
+ const cats = FULL_CONTEXT_SOURCE_CATEGORIES.map((c) => `'${sqlEscapeLiteral(String(c))}'`).join(", ");
1362
+ const base = `agentId = '${a}' AND category IN (${cats})`;
1363
+
1364
+ // 1. Delete rows older than maxAgeMs
1365
+ const cutoffMs = Date.now() - maxAgeMs;
1366
+ const agePred = `${base} AND createdAt < ${Math.floor(cutoffMs)}`;
1367
+ await this.table!.delete(agePred);
1368
+
1369
+ // 2. Count remaining; if over maxRows, trim oldest
1370
+ const remaining = await this.table!.countRows(base);
1371
+ if (remaining <= maxRows) {
1372
+ console.log(`[openclaw-memory-alibaba-local] gcFullContext agent=${agentId} remaining=${remaining} (within limit)`);
1373
+ return;
1374
+ }
1375
+ const excess = remaining - maxRows;
1376
+ // Find the createdAt of the oldest rows to delete
1377
+ const oldest: Array<Record<string, unknown>> = await this.table!.query()
1378
+ .where(base)
1379
+ .select(["createdAt"])
1380
+ .toArray();
1381
+ oldest.sort((a, b) => Number(a.createdAt) - Number(b.createdAt));
1382
+ if (oldest.length > maxRows) {
1383
+ const cutoff = Number(oldest[excess - 1]!.createdAt);
1384
+ await this.table!.delete(`${base} AND createdAt <= ${Math.floor(cutoff)}`);
1385
+ }
1386
+ console.log(`[openclaw-memory-alibaba-local] gcFullContext agent=${agentId} deleted ${excess} excess rows (was ${remaining}, cap ${maxRows})`);
1387
+ }
1388
+
1389
+ /**
1390
+ * Touch world_fact entries: update createdAt to current UTC time (LRU refresh).
1391
+ * Fire-and-forget: errors are logged but not thrown.
1392
+ */
1393
+ async touchWorldFactCreatedAt(agentId: string, ids: string[]): Promise<void> {
1394
+ if (ids.length === 0) return;
1395
+ await this.ensureInitialized();
1396
+ const now = Date.now();
1397
+ const a = sqlEscapeLiteral(agentId);
1398
+ const wf = sqlEscapeLiteral(WORLD_FACT);
1399
+ for (const id of ids) {
1400
+ try {
1401
+ const eid = sqlEscapeLiteral(id);
1402
+ await (this.table! as any).update(
1403
+ { createdAt: now },
1404
+ { where: `id = '${eid}' AND agentId = '${a}' AND category = '${wf}'` },
1405
+ );
1406
+ } catch (err) {
1407
+ console.warn(`[openclaw-memory-alibaba-local] touchWorldFactCreatedAt failed id=${id}: ${err}`);
1408
+ }
1409
+ }
1410
+ }
1411
+
1412
+ /**
1413
+ * GC world_fact rows for a given agent (LRU eviction):
1414
+ * Phase 1: If count > softMaxRows, delete rows where createdAt > maxAgeMs old AND importance < 0.7.
1415
+ * Phase 2: If count still > hardMaxRows, delete oldest rows regardless of importance.
1416
+ */
1417
+ async gcWorldFact(
1418
+ agentId: string,
1419
+ softMaxRows: number,
1420
+ maxAgeMs: number,
1421
+ hardMaxRows: number,
1422
+ ): Promise<void> {
1423
+ await this.ensureInitialized();
1424
+ const a = sqlEscapeLiteral(agentId);
1425
+ const wf = sqlEscapeLiteral(WORLD_FACT);
1426
+ const base = `agentId = '${a}' AND category = '${wf}'`;
1427
+
1428
+ let count = await this.table!.countRows(base);
1429
+
1430
+ // Phase 1: soft GC — delete old + low-importance entries when count > softMaxRows
1431
+ if (count > softMaxRows) {
1432
+ const cutoffMs = Date.now() - maxAgeMs;
1433
+ const softPred = `${base} AND createdAt < ${Math.floor(cutoffMs)} AND importance < 0.7`;
1434
+ await this.table!.delete(softPred);
1435
+ const afterSoft = await this.table!.countRows(base);
1436
+ const softDeleted = count - afterSoft;
1437
+ if (softDeleted > 0) {
1438
+ console.log(`[openclaw-memory-alibaba-local] gcWorldFact agent=${agentId} phase1: deleted ${softDeleted} old+low-importance rows (was ${count}, now ${afterSoft})`);
1439
+ }
1440
+ count = afterSoft;
1441
+ }
1442
+
1443
+ // Phase 2: hard cap — delete oldest regardless of importance
1444
+ if (count > hardMaxRows) {
1445
+ const excess = count - hardMaxRows;
1446
+ const oldest: Array<Record<string, unknown>> = await this.table!.query()
1447
+ .where(base)
1448
+ .select(["createdAt"])
1449
+ .toArray();
1450
+ oldest.sort((a, b) => Number(a.createdAt) - Number(b.createdAt));
1451
+ if (oldest.length > hardMaxRows) {
1452
+ const cutoff = Number(oldest[excess - 1]!.createdAt);
1453
+ await this.table!.delete(`${base} AND createdAt <= ${Math.floor(cutoff)}`);
1454
+ }
1455
+ console.log(`[openclaw-memory-alibaba-local] gcWorldFact agent=${agentId} phase2: deleted ${excess} excess rows (was ${count}, cap ${hardMaxRows})`);
1456
+ }
1457
+ }
1321
1458
  }