@bamdra/bamdra-openclaw-memory 0.3.0 → 0.3.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.
package/dist/index.js CHANGED
@@ -218,9 +218,9 @@ function stableKeyFragment(text) {
218
218
  // ../../packages/memory-cache-memory/src/index.ts
219
219
  function logCacheEvent(event, details = {}) {
220
220
  try {
221
- console.info("[bamdra-memory-cache]", event, JSON.stringify(details));
221
+ console.info("[bamdra-openclaw-memory-cache]", event, JSON.stringify(details));
222
222
  } catch {
223
- console.info("[bamdra-memory-cache]", event);
223
+ console.info("[bamdra-openclaw-memory-cache]", event);
224
224
  }
225
225
  }
226
226
  var InMemoryCacheStore = class {
@@ -291,7 +291,17 @@ var import_promises = require("node:fs/promises");
291
291
  var import_node_path = require("node:path");
292
292
  var import_node_sqlite = require("node:sqlite");
293
293
  var import_node_url = require("node:url");
294
- var SCHEMA_VERSION = 1;
294
+ var SCHEMA_VERSION = 2;
295
+ var TABLES = {
296
+ schemaMigrations: "bamdra_memory_schema_migrations",
297
+ messages: "bamdra_memory_messages",
298
+ topics: "bamdra_memory_topics",
299
+ topicMembership: "bamdra_memory_topic_membership",
300
+ facts: "bamdra_memory_facts",
301
+ factTags: "bamdra_memory_fact_tags",
302
+ contextSnapshots: "bamdra_memory_context_snapshots",
303
+ sessionState: "bamdra_memory_session_state"
304
+ };
295
305
  var MemorySqliteStore = class {
296
306
  constructor(options) {
297
307
  this.options = options;
@@ -305,15 +315,19 @@ var MemorySqliteStore = class {
305
315
  async applyMigrations() {
306
316
  const schemaSql = await loadSchemaSql();
307
317
  this.db.exec(schemaSql);
318
+ applyUserIdentityMigrations(this.db);
308
319
  }
309
320
  async close() {
310
321
  this.db.close();
311
322
  }
312
323
  async upsertMessage(record) {
313
324
  this.db.prepare(
314
- `INSERT INTO messages (
325
+ `INSERT INTO ${TABLES.messages} (
315
326
  id,
316
327
  session_id,
328
+ user_id,
329
+ channel_type,
330
+ sender_open_id,
317
331
  turn_id,
318
332
  parent_turn_id,
319
333
  role,
@@ -321,9 +335,12 @@ var MemorySqliteStore = class {
321
335
  text,
322
336
  ts,
323
337
  raw_json
324
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
338
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
325
339
  ON CONFLICT(id) DO UPDATE SET
326
340
  session_id = excluded.session_id,
341
+ user_id = excluded.user_id,
342
+ channel_type = excluded.channel_type,
343
+ sender_open_id = excluded.sender_open_id,
327
344
  turn_id = excluded.turn_id,
328
345
  parent_turn_id = excluded.parent_turn_id,
329
346
  role = excluded.role,
@@ -334,6 +351,9 @@ var MemorySqliteStore = class {
334
351
  ).run(
335
352
  record.id,
336
353
  record.sessionId,
354
+ record.userId,
355
+ record.channelType,
356
+ record.senderOpenId,
337
357
  record.turnId,
338
358
  record.parentTurnId,
339
359
  record.role,
@@ -345,28 +365,31 @@ var MemorySqliteStore = class {
345
365
  }
346
366
  async getSessionState(sessionId) {
347
367
  const row = this.db.prepare(
348
- `SELECT session_id, active_topic_id, last_compacted_at, last_turn_id, updated_at
349
- FROM session_state
368
+ `SELECT session_id, user_id, active_topic_id, last_compacted_at, last_turn_id, updated_at
369
+ FROM ${TABLES.sessionState}
350
370
  WHERE session_id = ?`
351
371
  ).get(sessionId);
352
372
  return row ? mapSessionStateRow(row) : null;
353
373
  }
354
374
  async upsertSessionState(record) {
355
375
  this.db.prepare(
356
- `INSERT INTO session_state (
376
+ `INSERT INTO ${TABLES.sessionState} (
357
377
  session_id,
378
+ user_id,
358
379
  active_topic_id,
359
380
  last_compacted_at,
360
381
  last_turn_id,
361
382
  updated_at
362
- ) VALUES (?, ?, ?, ?, ?)
383
+ ) VALUES (?, ?, ?, ?, ?, ?)
363
384
  ON CONFLICT(session_id) DO UPDATE SET
385
+ user_id = excluded.user_id,
364
386
  active_topic_id = excluded.active_topic_id,
365
387
  last_compacted_at = excluded.last_compacted_at,
366
388
  last_turn_id = excluded.last_turn_id,
367
389
  updated_at = excluded.updated_at`
368
390
  ).run(
369
391
  record.sessionId,
392
+ record.userId,
370
393
  record.activeTopicId,
371
394
  record.lastCompactedAt,
372
395
  record.lastTurnId,
@@ -378,6 +401,7 @@ var MemorySqliteStore = class {
378
401
  `SELECT
379
402
  id,
380
403
  session_id,
404
+ user_id,
381
405
  title,
382
406
  status,
383
407
  parent_topic_id,
@@ -387,7 +411,7 @@ var MemorySqliteStore = class {
387
411
  labels_json,
388
412
  created_at,
389
413
  last_active_at
390
- FROM topics
414
+ FROM ${TABLES.topics}
391
415
  WHERE session_id = ?
392
416
  ORDER BY last_active_at DESC`
393
417
  ).all(sessionId);
@@ -398,6 +422,7 @@ var MemorySqliteStore = class {
398
422
  `SELECT
399
423
  id,
400
424
  session_id,
425
+ user_id,
401
426
  title,
402
427
  status,
403
428
  parent_topic_id,
@@ -407,16 +432,17 @@ var MemorySqliteStore = class {
407
432
  labels_json,
408
433
  created_at,
409
434
  last_active_at
410
- FROM topics
435
+ FROM ${TABLES.topics}
411
436
  WHERE id = ?`
412
437
  ).get(topicId);
413
438
  return row ? mapTopicRow(row) : null;
414
439
  }
415
440
  async upsertTopic(record) {
416
441
  this.db.prepare(
417
- `INSERT INTO topics (
442
+ `INSERT INTO ${TABLES.topics} (
418
443
  id,
419
444
  session_id,
445
+ user_id,
420
446
  title,
421
447
  status,
422
448
  parent_topic_id,
@@ -426,9 +452,10 @@ var MemorySqliteStore = class {
426
452
  labels_json,
427
453
  created_at,
428
454
  last_active_at
429
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
455
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
430
456
  ON CONFLICT(id) DO UPDATE SET
431
457
  session_id = excluded.session_id,
458
+ user_id = excluded.user_id,
432
459
  title = excluded.title,
433
460
  status = excluded.status,
434
461
  parent_topic_id = excluded.parent_topic_id,
@@ -441,6 +468,7 @@ var MemorySqliteStore = class {
441
468
  ).run(
442
469
  record.id,
443
470
  record.sessionId,
471
+ record.userId,
444
472
  record.title,
445
473
  record.status,
446
474
  record.parentTopicId,
@@ -454,7 +482,7 @@ var MemorySqliteStore = class {
454
482
  }
455
483
  async upsertTopicMembership(record) {
456
484
  this.db.prepare(
457
- `INSERT INTO topic_membership (
485
+ `INSERT INTO ${TABLES.topicMembership} (
458
486
  message_id,
459
487
  topic_id,
460
488
  score,
@@ -480,7 +508,7 @@ var MemorySqliteStore = class {
480
508
  this.db.exec("BEGIN");
481
509
  try {
482
510
  this.db.prepare(
483
- `INSERT INTO facts (
511
+ `INSERT INTO ${TABLES.facts} (
484
512
  id,
485
513
  scope,
486
514
  category,
@@ -517,9 +545,9 @@ var MemorySqliteStore = class {
517
545
  record.sourceTopicId,
518
546
  record.updatedAt
519
547
  );
520
- this.db.prepare(`DELETE FROM fact_tags WHERE fact_id = ?`).run(record.id);
548
+ this.db.prepare(`DELETE FROM ${TABLES.factTags} WHERE fact_id = ?`).run(record.id);
521
549
  const insertTag = this.db.prepare(
522
- `INSERT INTO fact_tags (fact_id, tag) VALUES (?, ?)`
550
+ `INSERT INTO ${TABLES.factTags} (fact_id, tag) VALUES (?, ?)`
523
551
  );
524
552
  for (const tag of tags) {
525
553
  insertTag.run(record.id, tag);
@@ -545,8 +573,8 @@ var MemorySqliteStore = class {
545
573
  f.source_topic_id,
546
574
  f.updated_at,
547
575
  COALESCE(json_group_array(ft.tag) FILTER (WHERE ft.tag IS NOT NULL), '[]') AS tags_json
548
- FROM facts f
549
- LEFT JOIN fact_tags ft ON ft.fact_id = f.id
576
+ FROM ${TABLES.facts} f
577
+ LEFT JOIN ${TABLES.factTags} ft ON ft.fact_id = f.id
550
578
  WHERE f.scope = ?
551
579
  GROUP BY
552
580
  f.id,
@@ -584,9 +612,9 @@ var MemorySqliteStore = class {
584
612
  f.source_topic_id,
585
613
  f.updated_at,
586
614
  COALESCE(json_group_array(DISTINCT ft_all.tag) FILTER (WHERE ft_all.tag IS NOT NULL), '[]') AS tags_json
587
- FROM facts f
588
- JOIN fact_tags ft_match ON ft_match.fact_id = f.id
589
- LEFT JOIN fact_tags ft_all ON ft_all.fact_id = f.id
615
+ FROM ${TABLES.facts} f
616
+ JOIN ${TABLES.factTags} ft_match ON ft_match.fact_id = f.id
617
+ LEFT JOIN ${TABLES.factTags} ft_all ON ft_all.fact_id = f.id
590
618
  WHERE ft_match.tag IN (${tagPlaceholders})
591
619
  AND f.recall_policy IN (${recallPolicyPlaceholders})
592
620
  GROUP BY
@@ -616,6 +644,9 @@ var MemorySqliteStore = class {
616
644
  tm.created_at,
617
645
  m.id,
618
646
  m.session_id,
647
+ m.user_id,
648
+ m.channel_type,
649
+ m.sender_open_id,
619
650
  m.turn_id,
620
651
  m.parent_turn_id,
621
652
  m.role,
@@ -623,8 +654,8 @@ var MemorySqliteStore = class {
623
654
  m.text,
624
655
  m.ts,
625
656
  m.raw_json
626
- FROM topic_membership tm
627
- JOIN messages m ON m.id = tm.message_id
657
+ FROM ${TABLES.topicMembership} tm
658
+ JOIN ${TABLES.messages} m ON m.id = tm.message_id
628
659
  WHERE tm.topic_id = ?
629
660
  ORDER BY m.ts DESC
630
661
  LIMIT ?`
@@ -650,7 +681,7 @@ var MemorySqliteStore = class {
650
681
  labels_json,
651
682
  created_at,
652
683
  last_active_at
653
- FROM topics
684
+ FROM ${TABLES.topics}
654
685
  WHERE session_id = ?
655
686
  AND (
656
687
  lower(title) LIKE ? ESCAPE '\\'
@@ -671,6 +702,7 @@ var MemorySqliteStore = class {
671
702
  const likeValue = `%${escapeLike(normalizedQuery)}%`;
672
703
  const topicScope = args.topicId ? `topic:${args.topicId}` : null;
673
704
  const sessionScope = `session:${args.sessionId}`;
705
+ const userScope = args.userId ? `user:${args.userId}` : null;
674
706
  const rows = this.db.prepare(
675
707
  `SELECT
676
708
  f.id,
@@ -685,15 +717,16 @@ var MemorySqliteStore = class {
685
717
  f.source_topic_id,
686
718
  f.updated_at,
687
719
  COALESCE(json_group_array(DISTINCT ft_all.tag) FILTER (WHERE ft_all.tag IS NOT NULL), '[]') AS tags_json
688
- FROM facts f
689
- LEFT JOIN fact_tags ft_match ON ft_match.fact_id = f.id
690
- LEFT JOIN fact_tags ft_all ON ft_all.fact_id = f.id
720
+ FROM ${TABLES.facts} f
721
+ LEFT JOIN ${TABLES.factTags} ft_match ON ft_match.fact_id = f.id
722
+ LEFT JOIN ${TABLES.factTags} ft_all ON ft_all.fact_id = f.id
691
723
  WHERE (
692
724
  f.scope IN ('global', 'shared')
693
725
  OR f.scope = ?
694
726
  OR f.scope = ?
727
+ OR f.scope = ?
695
728
  OR f.source_topic_id IN (
696
- SELECT id FROM topics WHERE session_id = ?
729
+ SELECT id FROM ${TABLES.topics} WHERE session_id = ? AND (? IS NULL OR user_id = ?)
697
730
  )
698
731
  )
699
732
  AND (
@@ -716,7 +749,19 @@ var MemorySqliteStore = class {
716
749
  f.updated_at
717
750
  ORDER BY f.updated_at DESC
718
751
  LIMIT ?`
719
- ).all(sessionScope, topicScope, args.sessionId, likeValue, likeValue, likeValue, likeValue, args.limit);
752
+ ).all(
753
+ sessionScope,
754
+ topicScope,
755
+ userScope,
756
+ args.sessionId,
757
+ args.userId ?? null,
758
+ args.userId ?? null,
759
+ likeValue,
760
+ likeValue,
761
+ likeValue,
762
+ likeValue,
763
+ args.limit
764
+ );
720
765
  return rows.map((row) => mapFactSearchResult(row, normalizedQuery)).sort((a, b) => b.score - a.score);
721
766
  }
722
767
  };
@@ -738,6 +783,7 @@ async function loadSchemaSql() {
738
783
  function mapSessionStateRow(row) {
739
784
  return {
740
785
  sessionId: row.session_id,
786
+ userId: row.user_id,
741
787
  activeTopicId: row.active_topic_id,
742
788
  lastCompactedAt: row.last_compacted_at,
743
789
  lastTurnId: row.last_turn_id,
@@ -748,6 +794,7 @@ function mapTopicRow(row) {
748
794
  return {
749
795
  id: row.id,
750
796
  sessionId: row.session_id,
797
+ userId: row.user_id,
751
798
  title: row.title,
752
799
  status: row.status,
753
800
  parentTopicId: row.parent_topic_id,
@@ -787,7 +834,8 @@ function mapTopicSearchResult(row, normalizedQuery) {
787
834
  return {
788
835
  topic,
789
836
  score: scoreTopicSearch(topic, matchReasons),
790
- matchReasons
837
+ matchReasons,
838
+ source: "topic"
791
839
  };
792
840
  }
793
841
  function mapFactSearchResult(row, normalizedQuery) {
@@ -802,7 +850,8 @@ function mapFactSearchResult(row, normalizedQuery) {
802
850
  return {
803
851
  fact,
804
852
  score: scoreFactSearch(fact, matchReasons),
805
- matchReasons
853
+ matchReasons,
854
+ source: "fact"
806
855
  };
807
856
  }
808
857
  function mapRecentTopicMessageRow(row) {
@@ -818,6 +867,9 @@ function mapRecentTopicMessageRow(row) {
818
867
  message: {
819
868
  id: row.id,
820
869
  sessionId: row.session_id,
870
+ userId: row.user_id,
871
+ channelType: row.channel_type,
872
+ senderOpenId: row.sender_open_id,
821
873
  turnId: row.turn_id,
822
874
  parentTurnId: row.parent_turn_id,
823
875
  role: row.role,
@@ -877,6 +929,20 @@ function scoreFactSearch(fact, matchReasons) {
877
929
  }
878
930
  return score;
879
931
  }
932
+ function applyUserIdentityMigrations(db) {
933
+ ensureColumn(db, TABLES.messages, "user_id", "TEXT");
934
+ ensureColumn(db, TABLES.messages, "channel_type", "TEXT");
935
+ ensureColumn(db, TABLES.messages, "sender_open_id", "TEXT");
936
+ ensureColumn(db, TABLES.topics, "user_id", "TEXT");
937
+ ensureColumn(db, TABLES.sessionState, "user_id", "TEXT");
938
+ }
939
+ function ensureColumn(db, tableName, columnName, columnSql) {
940
+ const rows = db.prepare(`PRAGMA table_info(${tableName})`).all();
941
+ if (rows.some((row) => row.name === columnName)) {
942
+ return;
943
+ }
944
+ db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnSql}`);
945
+ }
880
946
 
881
947
  // ../../packages/summary-refresher/src/index.ts
882
948
  var SummaryRefresher = class {
@@ -1117,10 +1183,12 @@ var STOP_TOKENS = /* @__PURE__ */ new Set([
1117
1183
  "topics"
1118
1184
  ]);
1119
1185
 
1120
- // ../bamdra-memory-context-engine/src/index.ts
1186
+ // ../../packages/bamdra-memory-context-engine/src/index.ts
1121
1187
  var import_node_crypto = require("crypto");
1122
1188
  var import_node_os = require("os");
1123
1189
  var import_node_path2 = require("node:path");
1190
+ var USER_BIND_GLOBAL_KEY = "__OPENCLAW_BAMDRA_USER_BIND__";
1191
+ var VECTOR_GLOBAL_KEY = "__OPENCLAW_BAMDRA_MEMORY_VECTOR__";
1124
1192
  var DEFAULT_DB_PATH = (0, import_node_path2.join)(
1125
1193
  (0, import_node_os.homedir)(),
1126
1194
  ".openclaw",
@@ -1129,9 +1197,9 @@ var DEFAULT_DB_PATH = (0, import_node_path2.join)(
1129
1197
  );
1130
1198
  function logMemoryEvent(event, details = {}) {
1131
1199
  try {
1132
- console.info("[bamdra-memory]", event, JSON.stringify(details));
1200
+ console.info("[bamdra-openclaw-memory]", event, JSON.stringify(details));
1133
1201
  } catch {
1134
- console.info("[bamdra-memory]", event);
1202
+ console.info("[bamdra-openclaw-memory]", event);
1135
1203
  }
1136
1204
  }
1137
1205
  function createContextEngineMemoryV2Plugin(inputConfig, api) {
@@ -1156,7 +1224,7 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1156
1224
  }
1157
1225
  let hooksRegistered = false;
1158
1226
  const plugin = {
1159
- name: "bamdra-memory-context-engine",
1227
+ name: "bamdra-openclaw-memory",
1160
1228
  type: "context-engine",
1161
1229
  slot: "memory",
1162
1230
  capabilities: ["memory"],
@@ -1260,6 +1328,7 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1260
1328
  throw new Error(`Topic ${topicId} does not belong to session ${sessionId}`);
1261
1329
  }
1262
1330
  const now = (/* @__PURE__ */ new Date()).toISOString();
1331
+ const userId = getResolvedUserId(sessionId);
1263
1332
  await cache.setSessionState(sessionId, {
1264
1333
  activeTopicId: topicId,
1265
1334
  updatedAt: now
@@ -1273,6 +1342,7 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1273
1342
  await store.upsertTopic(updatedTopic);
1274
1343
  await store.upsertSessionState({
1275
1344
  sessionId,
1345
+ userId,
1276
1346
  activeTopicId: topicId,
1277
1347
  lastCompactedAt: previousState?.lastCompactedAt ?? null,
1278
1348
  lastTurnId: previousState?.lastTurnId ?? null,
@@ -1287,8 +1357,9 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1287
1357
  const sessionState = await resolveSessionState(store, cache, args.sessionId);
1288
1358
  const resolvedTopicId = args.topicId ?? sessionState?.activeTopicId ?? null;
1289
1359
  const resolvedTopic = resolvedTopicId != null ? await store.getTopic(resolvedTopicId) : null;
1360
+ const userId = getResolvedUserId(args.sessionId);
1290
1361
  const normalizedScope = normalizeFactScope(args.scope, args.sessionId);
1291
- const scope = normalizedScope ?? (resolvedTopic != null ? `topic:${resolvedTopic.id}` : "shared");
1362
+ const scope = normalizedScope ?? (resolvedTopic != null ? `topic:${resolvedTopic.id}` : userId != null ? `user:${userId}` : "shared");
1292
1363
  const tags = dedupeTextItems([
1293
1364
  ...resolvedTopic?.labels ?? [],
1294
1365
  ...args.tags ?? [],
@@ -1313,6 +1384,15 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1313
1384
  if (resolvedTopic) {
1314
1385
  await refreshTopicSummary(store, summaryRefresher, config, resolvedTopic.id, now);
1315
1386
  }
1387
+ pushVectorMemoryRecord({
1388
+ userId,
1389
+ sessionId: args.sessionId,
1390
+ topicId: resolvedTopic?.id ?? null,
1391
+ sourcePath: (0, import_node_path2.join)("user", userId ?? "shared", "facts", `${args.key}.md`),
1392
+ title: args.key,
1393
+ text: args.value,
1394
+ tags
1395
+ });
1316
1396
  logMemoryEvent("save-fact", {
1317
1397
  sessionId: args.sessionId,
1318
1398
  topicId: resolvedTopic?.id ?? null,
@@ -1346,6 +1426,7 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1346
1426
  );
1347
1427
  await store.upsertSessionState({
1348
1428
  sessionId: args.sessionId,
1429
+ userId: getResolvedUserId(args.sessionId),
1349
1430
  activeTopicId: sessionState?.activeTopicId ?? topic.id,
1350
1431
  lastCompactedAt: now,
1351
1432
  lastTurnId: sessionState?.lastTurnId ?? null,
@@ -1367,20 +1448,30 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1367
1448
  const sessionState = await resolveSessionState(store, cache, args.sessionId);
1368
1449
  const limit = args.limit ?? 5;
1369
1450
  const resolvedTopicId = args.topicId ?? sessionState?.activeTopicId ?? null;
1451
+ const userId = getResolvedUserId(args.sessionId);
1370
1452
  const [topics, facts] = await Promise.all([
1371
1453
  store.searchTopics(args.sessionId, args.query, limit),
1372
1454
  store.searchFacts({
1373
1455
  sessionId: args.sessionId,
1456
+ userId,
1374
1457
  query: args.query,
1375
1458
  topicId: resolvedTopicId,
1376
1459
  limit
1377
1460
  })
1378
1461
  ]);
1462
+ const vectors = searchVectorMemory({
1463
+ query: args.query,
1464
+ userId,
1465
+ topicId: resolvedTopicId,
1466
+ limit
1467
+ });
1379
1468
  return {
1380
1469
  sessionId: args.sessionId,
1470
+ userId,
1381
1471
  query: args.query,
1382
1472
  topics,
1383
- facts
1473
+ facts,
1474
+ vectors
1384
1475
  };
1385
1476
  },
1386
1477
  async routeTopic(sessionId, text) {
@@ -1398,6 +1489,7 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1398
1489
  await ensureMigrations();
1399
1490
  const sessionState = await resolveSessionState(store, cache, sessionId);
1400
1491
  const topic = sessionState?.activeTopicId != null ? await store.getTopic(sessionState.activeTopicId) : null;
1492
+ const userId = getResolvedUserId(sessionId);
1401
1493
  const recentMessages = topic != null ? await store.listRecentMessagesForTopic(
1402
1494
  topic.id,
1403
1495
  config.contextAssembly?.recentTurns ?? 6
@@ -1405,13 +1497,14 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1405
1497
  const alwaysFacts = await store.listFactsByScope("global");
1406
1498
  const sharedFacts = await store.listFactsByScope("shared");
1407
1499
  const sessionFacts = await store.listFactsByScope(`session:${sessionId}`);
1500
+ const userFacts = userId != null ? await store.listFactsByScope(`user:${userId}`) : [];
1408
1501
  const scopedTopicFacts = topic != null ? await store.listFactsByScope(`topic:${topic.id}`) : [];
1409
1502
  const labelFacts = topic != null ? await store.listFactsByTags(topic.labels, ["always", "topic_bound"]) : [];
1410
1503
  return assembler.assemble({
1411
1504
  sessionId,
1412
1505
  topic,
1413
1506
  recentMessages,
1414
- alwaysFacts: dedupeFacts([...alwaysFacts, ...sharedFacts, ...sessionFacts]),
1507
+ alwaysFacts: dedupeFacts([...alwaysFacts, ...sharedFacts, ...sessionFacts, ...userFacts]),
1415
1508
  topicFacts: dedupeFacts([...scopedTopicFacts, ...labelFacts])
1416
1509
  });
1417
1510
  },
@@ -1427,9 +1520,10 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1427
1520
  recentTopics
1428
1521
  });
1429
1522
  const now = (/* @__PURE__ */ new Date()).toISOString();
1523
+ const userId = getResolvedUserId(sessionId);
1430
1524
  const topicId = decision.action === "spawn" ? createTopicId(sessionId, text) : decision.topicId;
1431
1525
  const messageId = (0, import_node_crypto.randomUUID)();
1432
- const topicRecord = decision.action === "spawn" ? createSpawnedTopic(sessionId, topicId, text, now, persistedState?.activeTopicId ?? null) : await store.getTopic(topicId);
1526
+ const topicRecord = decision.action === "spawn" ? createSpawnedTopic(sessionId, userId, topicId, text, now, persistedState?.activeTopicId ?? null) : await store.getTopic(topicId);
1433
1527
  if (!topicRecord) {
1434
1528
  throw new Error(`Unable to resolve topic ${topicId} after routing`);
1435
1529
  }
@@ -1438,6 +1532,7 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1438
1532
  } else {
1439
1533
  await store.upsertTopic({
1440
1534
  ...topicRecord,
1535
+ userId,
1441
1536
  status: "active",
1442
1537
  openLoops: mergeOpenLoops(topicRecord.openLoops, text),
1443
1538
  lastActiveAt: now
@@ -1446,6 +1541,9 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1446
1541
  const messageRecord = {
1447
1542
  id: messageId,
1448
1543
  sessionId,
1544
+ userId,
1545
+ channelType: getResolvedChannelType(sessionId),
1546
+ senderOpenId: getResolvedSenderOpenId(sessionId),
1449
1547
  turnId: messageId,
1450
1548
  parentTurnId: persistedState?.lastTurnId ?? null,
1451
1549
  role: "user",
@@ -1484,12 +1582,22 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1484
1582
  );
1485
1583
  }
1486
1584
  await refreshTopicSummary(store, summaryRefresher, config, topicId, now);
1585
+ pushVectorMemoryRecord({
1586
+ userId,
1587
+ sessionId,
1588
+ topicId,
1589
+ sourcePath: (0, import_node_path2.join)("user", userId ?? "shared", "topics", topicId, `${messageId}.md`),
1590
+ title: deriveTopicTitle(text),
1591
+ text,
1592
+ tags: dedupeTextItems(topicRecord.labels)
1593
+ });
1487
1594
  await cache.setSessionState(sessionId, {
1488
1595
  activeTopicId: topicId,
1489
1596
  updatedAt: now
1490
1597
  });
1491
1598
  await store.upsertSessionState({
1492
1599
  sessionId,
1600
+ userId,
1493
1601
  activeTopicId: topicId,
1494
1602
  lastCompactedAt: persistedState?.lastCompactedAt ?? null,
1495
1603
  lastTurnId: messageId,
@@ -1497,6 +1605,7 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1497
1605
  });
1498
1606
  logMemoryEvent("route-and-track", {
1499
1607
  sessionId,
1608
+ userId,
1500
1609
  action: decision.action,
1501
1610
  reason: decision.reason,
1502
1611
  topicId,
@@ -1519,6 +1628,7 @@ async function resolveSessionState(store, cache, sessionId) {
1519
1628
  function mapCachedStateToSessionState(sessionId, cached) {
1520
1629
  return {
1521
1630
  sessionId,
1631
+ userId: getResolvedUserId(sessionId),
1522
1632
  activeTopicId: cached.activeTopicId,
1523
1633
  lastCompactedAt: null,
1524
1634
  lastTurnId: null,
@@ -1544,12 +1654,50 @@ function normalizeFactScope(scope, sessionId) {
1544
1654
  if (normalized === "session") {
1545
1655
  return `session:${sessionId}`;
1546
1656
  }
1657
+ if (normalized === "user") {
1658
+ const userId = getResolvedUserId(sessionId);
1659
+ return userId != null ? `user:${userId}` : null;
1660
+ }
1547
1661
  return normalized;
1548
1662
  }
1549
- function createSpawnedTopic(sessionId, topicId, text, now, parentTopicId) {
1663
+ function getResolvedIdentity(sessionId) {
1664
+ const api = globalThis[USER_BIND_GLOBAL_KEY];
1665
+ if (!api?.getIdentityForSession) {
1666
+ return null;
1667
+ }
1668
+ return api.getIdentityForSession(sessionId);
1669
+ }
1670
+ function getResolvedUserId(sessionId) {
1671
+ const identity = getResolvedIdentity(sessionId);
1672
+ const userId = identity?.userId;
1673
+ return typeof userId === "string" && userId.trim() ? userId : null;
1674
+ }
1675
+ function getResolvedChannelType(sessionId) {
1676
+ const identity = getResolvedIdentity(sessionId);
1677
+ const channelType = identity?.channelType;
1678
+ return typeof channelType === "string" && channelType.trim() ? channelType : null;
1679
+ }
1680
+ function getResolvedSenderOpenId(sessionId) {
1681
+ const identity = getResolvedIdentity(sessionId);
1682
+ const senderOpenId = identity?.senderOpenId;
1683
+ return typeof senderOpenId === "string" && senderOpenId.trim() ? senderOpenId : null;
1684
+ }
1685
+ function pushVectorMemoryRecord(args) {
1686
+ const api = globalThis[VECTOR_GLOBAL_KEY];
1687
+ api?.upsertMemoryRecord?.(args);
1688
+ }
1689
+ function searchVectorMemory(args) {
1690
+ const api = globalThis[VECTOR_GLOBAL_KEY];
1691
+ if (!api?.search) {
1692
+ return [];
1693
+ }
1694
+ return api.search(args);
1695
+ }
1696
+ function createSpawnedTopic(sessionId, userId, topicId, text, now, parentTopicId) {
1550
1697
  return {
1551
1698
  id: topicId,
1552
1699
  sessionId,
1700
+ userId,
1553
1701
  title: deriveTopicTitle(text),
1554
1702
  status: "active",
1555
1703
  parentTopicId,
@@ -1772,15 +1920,32 @@ function buildBeforePromptBuildResult(assembled) {
1772
1920
  }
1773
1921
 
1774
1922
  // src/index.ts
1923
+ var import_node_fs2 = require("node:fs");
1924
+ var import_node_module = require("module");
1925
+ var import_node_os2 = require("os");
1926
+ var import_node_path3 = require("node:path");
1775
1927
  var PLUGIN_ID = "bamdra-openclaw-memory";
1776
1928
  var ENGINE_GLOBAL_KEY = "__OPENCLAW_BAMDRA_MEMORY_CONTEXT_ENGINE__";
1777
- var TOOLS_REGISTERED_KEY = /* @__PURE__ */ Symbol.for("bamdra-memory.tools-registered");
1778
- var ENGINE_REGISTERED_KEY = /* @__PURE__ */ Symbol.for("bamdra-memory.context-engine-registered");
1929
+ var TOOLS_REGISTERED_KEY = /* @__PURE__ */ Symbol.for("bamdra-openclaw-memory.tools-registered");
1930
+ var ENGINE_REGISTERED_KEY = /* @__PURE__ */ Symbol.for("bamdra-openclaw-memory.context-engine-registered");
1931
+ var BOOTSTRAP_STARTED_KEY = /* @__PURE__ */ Symbol.for("bamdra-openclaw-memory.host-bootstrap-started");
1932
+ var SKILL_ID = "bamdra-memory-operator";
1933
+ var REQUIRED_TOOL_NAMES = [
1934
+ "memory_list_topics",
1935
+ "memory_switch_topic",
1936
+ "memory_save_fact",
1937
+ "memory_compact_topic",
1938
+ "memory_search"
1939
+ ];
1940
+ var REQUIRED_PLUGIN_IDS = ["bamdra-user-bind"];
1941
+ var OPTIONAL_PLUGIN_IDS = ["bamdra-memory-vector"];
1942
+ var AUTO_PROVISION_PLUGIN_IDS = [...REQUIRED_PLUGIN_IDS, ...OPTIONAL_PLUGIN_IDS];
1943
+ var runtimeRequire = (0, import_node_module.createRequire)(__filename);
1779
1944
  function logUnifiedMemoryEvent(event, details = {}) {
1780
1945
  try {
1781
- console.info("[bamdra-memory]", event, JSON.stringify(details));
1946
+ console.info("[bamdra-openclaw-memory]", event, JSON.stringify(details));
1782
1947
  } catch {
1783
- console.info("[bamdra-memory]", event);
1948
+ console.info("[bamdra-openclaw-memory]", event);
1784
1949
  }
1785
1950
  }
1786
1951
  function register(api) {
@@ -1791,6 +1956,18 @@ async function activate(api) {
1791
1956
  }
1792
1957
  function initializeUnifiedPlugin(api, phase) {
1793
1958
  logUnifiedMemoryEvent(`${phase}-plugin`, { id: PLUGIN_ID });
1959
+ if (!api[BOOTSTRAP_STARTED_KEY]) {
1960
+ api[BOOTSTRAP_STARTED_KEY] = true;
1961
+ queueMicrotask(() => {
1962
+ try {
1963
+ bootstrapOpenClawHost();
1964
+ } catch (error) {
1965
+ logUnifiedMemoryEvent("host-bootstrap-failed", {
1966
+ message: error instanceof Error ? error.message : String(error)
1967
+ });
1968
+ }
1969
+ });
1970
+ }
1794
1971
  const engine = brandContextEngine(createContextEngineMemoryV2Plugin(api.pluginConfig ?? api.config, api));
1795
1972
  exposeContextEngine(engine);
1796
1973
  engine.registerHooks(api);
@@ -1826,8 +2003,7 @@ function exposeContextEngine(engine) {
1826
2003
  function registerUnifiedTools(api, engine) {
1827
2004
  const definitions = [
1828
2005
  createToolDefinitions({
1829
- canonicalName: "memory_list_topics",
1830
- aliasName: "bamdra_memory_list_topics",
2006
+ name: "memory_list_topics",
1831
2007
  description: "List known topics for a session",
1832
2008
  parameters: {
1833
2009
  type: "object",
@@ -1842,8 +2018,7 @@ function registerUnifiedTools(api, engine) {
1842
2018
  }
1843
2019
  }),
1844
2020
  createToolDefinitions({
1845
- canonicalName: "memory_switch_topic",
1846
- aliasName: "bamdra_memory_switch_topic",
2021
+ name: "memory_switch_topic",
1847
2022
  description: "Switch the active topic for a session",
1848
2023
  parameters: {
1849
2024
  type: "object",
@@ -1859,8 +2034,7 @@ function registerUnifiedTools(api, engine) {
1859
2034
  }
1860
2035
  }),
1861
2036
  createToolDefinitions({
1862
- canonicalName: "memory_save_fact",
1863
- aliasName: "bamdra_memory_save_fact",
2037
+ name: "memory_save_fact",
1864
2038
  description: "Persist a pinned memory fact for the current or selected topic",
1865
2039
  parameters: {
1866
2040
  type: "object",
@@ -1886,8 +2060,7 @@ function registerUnifiedTools(api, engine) {
1886
2060
  }
1887
2061
  }),
1888
2062
  createToolDefinitions({
1889
- canonicalName: "memory_compact_topic",
1890
- aliasName: "bamdra_memory_compact_topic",
2063
+ name: "memory_compact_topic",
1891
2064
  description: "Force refresh the summary for the current or selected topic",
1892
2065
  parameters: {
1893
2066
  type: "object",
@@ -1903,8 +2076,7 @@ function registerUnifiedTools(api, engine) {
1903
2076
  }
1904
2077
  }),
1905
2078
  createToolDefinitions({
1906
- canonicalName: "memory_search",
1907
- aliasName: "bamdra_memory_search",
2079
+ name: "memory_search",
1908
2080
  description: "Search topics and durable facts for a session",
1909
2081
  parameters: {
1910
2082
  type: "object",
@@ -1930,20 +2102,20 @@ function registerUnifiedTools(api, engine) {
1930
2102
  }
1931
2103
  }
1932
2104
  function createToolDefinitions(definition) {
1933
- return [definition.canonicalName, definition.aliasName].map((name) => ({
1934
- name,
2105
+ return [{
2106
+ name: definition.name,
1935
2107
  description: definition.description,
1936
2108
  parameters: definition.parameters,
1937
2109
  async execute(invocationId, params) {
1938
2110
  const result = await definition.execute(params);
1939
2111
  logUnifiedMemoryEvent("tool-execute", {
1940
- name,
2112
+ name: definition.name,
1941
2113
  invocationId,
1942
2114
  sessionId: params && typeof params === "object" && "sessionId" in params ? params.sessionId : null
1943
2115
  });
1944
2116
  return asTextResult(result);
1945
2117
  }
1946
- }));
2118
+ }];
1947
2119
  }
1948
2120
  function asTextResult(value) {
1949
2121
  return {
@@ -1955,6 +2127,283 @@ function asTextResult(value) {
1955
2127
  ]
1956
2128
  };
1957
2129
  }
2130
+ function bootstrapOpenClawHost() {
2131
+ const openclawHome = (0, import_node_path3.resolve)((0, import_node_os2.homedir)(), ".openclaw");
2132
+ const extensionRoot = (0, import_node_path3.join)(openclawHome, "extensions");
2133
+ const memoryRoot = (0, import_node_path3.join)(openclawHome, "memory");
2134
+ const pluginRuntimeRoot = (0, import_node_path3.resolve)((0, import_node_path3.dirname)(__dirname));
2135
+ const configPath = (0, import_node_path3.join)(openclawHome, "openclaw.json");
2136
+ const globalSkillsDir = (0, import_node_path3.join)(openclawHome, "skills");
2137
+ const bundledSkillDir = (0, import_node_path3.join)((0, import_node_path3.resolve)((0, import_node_path3.dirname)(__dirname)), "skills", SKILL_ID);
2138
+ const targetSkillDir = (0, import_node_path3.join)(globalSkillsDir, SKILL_ID);
2139
+ const forceBootstrap = process.env.OPENCLAW_BAMDRA_MEMORY_FORCE_BOOTSTRAP === "1";
2140
+ if (!forceBootstrap && !pluginRuntimeRoot.startsWith(extensionRoot)) {
2141
+ logUnifiedMemoryEvent("host-bootstrap-skipped", {
2142
+ reason: "non-installed-runtime",
2143
+ pluginRuntimeRoot
2144
+ });
2145
+ return;
2146
+ }
2147
+ if (!(0, import_node_fs2.existsSync)(configPath)) {
2148
+ logUnifiedMemoryEvent("host-bootstrap-skipped", { reason: "missing-openclaw-config" });
2149
+ return;
2150
+ }
2151
+ (0, import_node_fs2.mkdirSync)(openclawHome, { recursive: true });
2152
+ (0, import_node_fs2.mkdirSync)(extensionRoot, { recursive: true });
2153
+ (0, import_node_fs2.mkdirSync)(memoryRoot, { recursive: true });
2154
+ materializeBundledDependencyPlugins(pluginRuntimeRoot, extensionRoot);
2155
+ materializeBundledSkill(bundledSkillDir, targetSkillDir);
2156
+ const original = (0, import_node_fs2.readFileSync)(configPath, "utf8");
2157
+ const config = JSON.parse(original);
2158
+ const changed = ensureHostConfig(config, targetSkillDir);
2159
+ if (!changed) {
2160
+ logUnifiedMemoryEvent("host-bootstrap-noop", { configPath });
2161
+ return;
2162
+ }
2163
+ (0, import_node_fs2.writeFileSync)(configPath, `${JSON.stringify(config, null, 2)}
2164
+ `, "utf8");
2165
+ logUnifiedMemoryEvent("host-bootstrap-updated", {
2166
+ configPath,
2167
+ targetSkillDir,
2168
+ pluginId: PLUGIN_ID
2169
+ });
2170
+ }
2171
+ function materializeBundledDependencyPlugins(pluginRuntimeRoot, extensionRoot) {
2172
+ for (const pluginId of AUTO_PROVISION_PLUGIN_IDS) {
2173
+ const targetDir = (0, import_node_path3.join)(extensionRoot, pluginId);
2174
+ if ((0, import_node_fs2.existsSync)(targetDir)) {
2175
+ continue;
2176
+ }
2177
+ const sourceDir = resolveBundledDependencySource(pluginRuntimeRoot, pluginId);
2178
+ if (!sourceDir) {
2179
+ logUnifiedMemoryEvent("dependency-plugin-copy-skipped", {
2180
+ pluginId,
2181
+ reason: "source-not-found"
2182
+ });
2183
+ continue;
2184
+ }
2185
+ (0, import_node_fs2.mkdirSync)((0, import_node_path3.dirname)(targetDir), { recursive: true });
2186
+ (0, import_node_fs2.cpSync)(sourceDir, targetDir, { recursive: true });
2187
+ logUnifiedMemoryEvent("dependency-plugin-copied", {
2188
+ pluginId,
2189
+ sourceDir,
2190
+ targetDir
2191
+ });
2192
+ }
2193
+ }
2194
+ function resolveBundledDependencySource(pluginRuntimeRoot, pluginId) {
2195
+ const packageNameByPluginId = {
2196
+ "bamdra-user-bind": "@bamdra/bamdra-user-bind",
2197
+ "bamdra-memory-vector": "@bamdra/bamdra-memory-vector"
2198
+ };
2199
+ const candidateRoots = [];
2200
+ const packageName = packageNameByPluginId[pluginId];
2201
+ if (packageName) {
2202
+ try {
2203
+ const packageJsonPath = runtimeRequire.resolve(`${packageName}/package.json`);
2204
+ candidateRoots.push((0, import_node_path3.dirname)(packageJsonPath));
2205
+ } catch {
2206
+ }
2207
+ }
2208
+ candidateRoots.push((0, import_node_path3.join)(pluginRuntimeRoot, "bundled-plugins", pluginId));
2209
+ candidateRoots.push((0, import_node_path3.resolve)(pluginRuntimeRoot, "..", "..", "..", pluginId));
2210
+ for (const root of candidateRoots) {
2211
+ const distDir = (0, import_node_fs2.existsSync)((0, import_node_path3.join)(root, "dist")) ? (0, import_node_path3.join)(root, "dist") : root;
2212
+ if ((0, import_node_fs2.existsSync)((0, import_node_path3.join)(distDir, "index.js")) && (0, import_node_fs2.existsSync)((0, import_node_path3.join)(distDir, "openclaw.plugin.json")) && (0, import_node_fs2.existsSync)((0, import_node_path3.join)(distDir, "package.json"))) {
2213
+ return distDir;
2214
+ }
2215
+ }
2216
+ return null;
2217
+ }
2218
+ function materializeBundledSkill(sourceDir, targetDir) {
2219
+ if (!(0, import_node_fs2.existsSync)(sourceDir)) {
2220
+ logUnifiedMemoryEvent("skill-copy-skipped", {
2221
+ reason: "missing-bundled-skill",
2222
+ sourceDir
2223
+ });
2224
+ return;
2225
+ }
2226
+ if ((0, import_node_fs2.existsSync)(targetDir)) {
2227
+ logUnifiedMemoryEvent("skill-copy-skipped", {
2228
+ reason: "target-exists",
2229
+ targetDir
2230
+ });
2231
+ return;
2232
+ }
2233
+ (0, import_node_fs2.mkdirSync)((0, import_node_path3.dirname)(targetDir), { recursive: true });
2234
+ (0, import_node_fs2.cpSync)(sourceDir, targetDir, { recursive: true });
2235
+ logUnifiedMemoryEvent("skill-copied", { sourceDir, targetDir });
2236
+ }
2237
+ function ensureHostConfig(config, targetSkillDir) {
2238
+ let changed = false;
2239
+ const plugins = ensureObject(config, "plugins");
2240
+ const tools = ensureObject(config, "tools");
2241
+ const skills = ensureObject(config, "skills");
2242
+ const skillsLoad = ensureObject(skills, "load");
2243
+ const agents = ensureObject(config, "agents");
2244
+ const entries = ensureObject(plugins, "entries");
2245
+ const installs = ensureObject(plugins, "installs");
2246
+ const load = ensureObject(plugins, "load");
2247
+ const slots = ensureObject(plugins, "slots");
2248
+ const pluginEntry = ensureObject(entries, PLUGIN_ID);
2249
+ const userBindEntry = ensureObject(entries, REQUIRED_PLUGIN_IDS[0]);
2250
+ const vectorEntry = ensureObject(entries, OPTIONAL_PLUGIN_IDS[0]);
2251
+ const pluginConfig = ensureObject(pluginEntry, "config");
2252
+ const pluginStore = ensureObject(pluginConfig, "store");
2253
+ const pluginCache = ensureObject(pluginConfig, "cache");
2254
+ changed = ensureArrayIncludes(plugins, "allow", PLUGIN_ID) || changed;
2255
+ for (const dependencyId of REQUIRED_PLUGIN_IDS) {
2256
+ changed = ensureArrayIncludes(plugins, "allow", dependencyId) || changed;
2257
+ const dependencyEntry = dependencyId === REQUIRED_PLUGIN_IDS[0] ? userBindEntry : ensureObject(entries, dependencyId);
2258
+ if (dependencyEntry.enabled !== true) {
2259
+ dependencyEntry.enabled = true;
2260
+ changed = true;
2261
+ }
2262
+ }
2263
+ for (const optionalId of OPTIONAL_PLUGIN_IDS) {
2264
+ changed = ensureArrayIncludes(plugins, "allow", optionalId) || changed;
2265
+ }
2266
+ changed = ensureArrayIncludes(plugins, "deny", "memory-core") || changed;
2267
+ changed = ensureArrayIncludes(load, "paths", (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".openclaw", "extensions")) || changed;
2268
+ changed = ensureArrayIncludes(skillsLoad, "extraDirs", (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".openclaw", "skills")) || changed;
2269
+ if (slots.memory !== PLUGIN_ID) {
2270
+ slots.memory = PLUGIN_ID;
2271
+ changed = true;
2272
+ }
2273
+ if (slots.contextEngine !== PLUGIN_ID) {
2274
+ slots.contextEngine = PLUGIN_ID;
2275
+ changed = true;
2276
+ }
2277
+ if (pluginEntry.enabled !== true) {
2278
+ pluginEntry.enabled = true;
2279
+ changed = true;
2280
+ }
2281
+ if (pluginConfig.enabled !== true) {
2282
+ pluginConfig.enabled = true;
2283
+ changed = true;
2284
+ }
2285
+ if (pluginStore.provider !== "sqlite") {
2286
+ pluginStore.provider = "sqlite";
2287
+ changed = true;
2288
+ }
2289
+ if (typeof pluginStore.path !== "string" || pluginStore.path.length === 0) {
2290
+ pluginStore.path = "~/.openclaw/memory/main.sqlite";
2291
+ changed = true;
2292
+ }
2293
+ if (pluginCache.provider !== "memory") {
2294
+ pluginCache.provider = "memory";
2295
+ changed = true;
2296
+ }
2297
+ if (typeof pluginCache.maxSessions !== "number") {
2298
+ pluginCache.maxSessions = 128;
2299
+ changed = true;
2300
+ }
2301
+ if (typeof userBindEntry.config !== "object" || userBindEntry.config == null) {
2302
+ userBindEntry.config = {
2303
+ enabled: true,
2304
+ adminAgents: []
2305
+ };
2306
+ changed = true;
2307
+ }
2308
+ if (vectorEntry.enabled !== false) {
2309
+ vectorEntry.enabled = false;
2310
+ changed = true;
2311
+ }
2312
+ if (typeof vectorEntry.config !== "object" || vectorEntry.config == null) {
2313
+ vectorEntry.config = {
2314
+ enabled: false,
2315
+ markdownRoot: "~/.openclaw/memory/vector/markdown",
2316
+ privateMarkdownRoot: "~/.openclaw/memory/vector/markdown/private",
2317
+ sharedMarkdownRoot: "~/.openclaw/memory/vector/markdown/shared",
2318
+ indexPath: "~/.openclaw/memory/vector/index.json",
2319
+ topK: 5
2320
+ };
2321
+ changed = true;
2322
+ } else {
2323
+ const vectorConfig = ensureObject(vectorEntry, "config");
2324
+ if (vectorConfig.enabled !== false) {
2325
+ vectorConfig.enabled = false;
2326
+ changed = true;
2327
+ }
2328
+ if (typeof vectorConfig.markdownRoot !== "string" || vectorConfig.markdownRoot.length === 0) {
2329
+ vectorConfig.markdownRoot = "~/.openclaw/memory/vector/markdown";
2330
+ changed = true;
2331
+ }
2332
+ if (typeof vectorConfig.privateMarkdownRoot !== "string" || vectorConfig.privateMarkdownRoot.length === 0) {
2333
+ vectorConfig.privateMarkdownRoot = "~/.openclaw/memory/vector/markdown/private";
2334
+ changed = true;
2335
+ }
2336
+ if (typeof vectorConfig.sharedMarkdownRoot !== "string" || vectorConfig.sharedMarkdownRoot.length === 0) {
2337
+ vectorConfig.sharedMarkdownRoot = "~/.openclaw/memory/vector/markdown/shared";
2338
+ changed = true;
2339
+ }
2340
+ if (typeof vectorConfig.indexPath !== "string" || vectorConfig.indexPath.length === 0) {
2341
+ vectorConfig.indexPath = "~/.openclaw/memory/vector/index.json";
2342
+ changed = true;
2343
+ }
2344
+ if (typeof vectorConfig.topK !== "number") {
2345
+ vectorConfig.topK = 5;
2346
+ changed = true;
2347
+ }
2348
+ }
2349
+ if (PLUGIN_ID in installs) {
2350
+ }
2351
+ changed = ensureToolAllowlist(tools) || changed;
2352
+ changed = ensureAgentSkills(agents, SKILL_ID) || changed;
2353
+ if (!(0, import_node_fs2.existsSync)(targetSkillDir)) {
2354
+ logUnifiedMemoryEvent("host-bootstrap-skill-pending", { targetSkillDir });
2355
+ }
2356
+ return changed;
2357
+ }
2358
+ function ensureToolAllowlist(tools) {
2359
+ let changed = false;
2360
+ for (const toolName of REQUIRED_TOOL_NAMES) {
2361
+ changed = ensureArrayIncludes(tools, "allow", toolName) || changed;
2362
+ }
2363
+ return changed;
2364
+ }
2365
+ function ensureAgentSkills(agents, skillId) {
2366
+ const list = Array.isArray(agents.list) ? agents.list : [];
2367
+ let changed = false;
2368
+ for (const item of list) {
2369
+ if (!item || typeof item !== "object") {
2370
+ continue;
2371
+ }
2372
+ const agent = item;
2373
+ const currentSkills = Array.isArray(agent.skills) ? [...agent.skills] : [];
2374
+ if (!Array.isArray(agent.skills)) {
2375
+ agent.skills = currentSkills;
2376
+ changed = true;
2377
+ }
2378
+ if (!currentSkills.includes(skillId)) {
2379
+ currentSkills.push(skillId);
2380
+ agent.skills = currentSkills;
2381
+ changed = true;
2382
+ }
2383
+ }
2384
+ return changed;
2385
+ }
2386
+ function ensureObject(parent, key) {
2387
+ const current = parent[key];
2388
+ if (current && typeof current === "object" && !Array.isArray(current)) {
2389
+ return current;
2390
+ }
2391
+ const next = {};
2392
+ parent[key] = next;
2393
+ return next;
2394
+ }
2395
+ function ensureArrayIncludes(parent, key, value) {
2396
+ const current = Array.isArray(parent[key]) ? [...parent[key]] : [];
2397
+ if (current.includes(value)) {
2398
+ if (!Array.isArray(parent[key])) {
2399
+ parent[key] = current;
2400
+ }
2401
+ return false;
2402
+ }
2403
+ current.push(value);
2404
+ parent[key] = current;
2405
+ return true;
2406
+ }
1958
2407
  // Annotate the CommonJS export names for ESM import in node:
1959
2408
  module.exports = {activate,
1960
2409
  register};