@bamdra/bamdra-openclaw-memory 0.3.8 → 0.3.10

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
@@ -107,6 +107,7 @@ var FactExtractor = class {
107
107
  candidates.push(...extractAccountLikeFacts(input));
108
108
  candidates.push(...extractConstraintFacts(input));
109
109
  candidates.push(...extractPreferenceFacts(input));
110
+ candidates.push(...extractPersonalWorkFacts(input));
110
111
  return dedupeCandidates(candidates);
111
112
  }
112
113
  };
@@ -134,7 +135,7 @@ function extractAccountLikeFacts(input) {
134
135
  if (!/(账号|账户|account|appid|appsecret|token|apikey|api key)/i.test(input.text)) {
135
136
  return candidates;
136
137
  }
137
- const scope = input.topic ? `topic:${input.topic.id}` : "shared";
138
+ const scope = input.topic ? `topic:${input.topic.id}` : fallbackSharedScope(input);
138
139
  const labels = input.topic?.labels ?? [];
139
140
  if (/(appid|appsecret|token|apikey|api key)/i.test(input.text)) {
140
141
  candidates.push({
@@ -173,7 +174,7 @@ function extractConstraintFacts(input) {
173
174
  value: abbreviate(input.text),
174
175
  sensitivity: "normal",
175
176
  recallPolicy: "topic_bound",
176
- scope: input.topic ? `topic:${input.topic.id}` : "shared",
177
+ scope: input.topic ? `topic:${input.topic.id}` : fallbackSharedScope(input),
177
178
  confidence: 0.82,
178
179
  tags: [...input.topic?.labels ?? [], "constraint"]
179
180
  }
@@ -190,12 +191,49 @@ function extractPreferenceFacts(input) {
190
191
  value: abbreviate(input.text),
191
192
  sensitivity: "normal",
192
193
  recallPolicy: "always",
193
- scope: "shared",
194
+ scope: choosePersonalOrSharedScope(input),
194
195
  confidence: 0.76,
195
196
  tags: [...input.topic?.labels ?? [], "preference"]
196
197
  }
197
198
  ];
198
199
  }
200
+ function extractPersonalWorkFacts(input) {
201
+ const explicitCurrentWork = input.text.match(/(?:记住[::]?\s*)?我(?:现在)?主要在做(.+?)[。.!!\n]?$/) ?? input.text.match(/(?:记住[::]?\s*)?我最近主要在做(.+?)[。.!!\n]?$/) ?? input.text.match(/(?:记住[::]?\s*)?我当前主要在做(.+?)[。.!!\n]?$/);
202
+ if (!explicitCurrentWork) {
203
+ return [];
204
+ }
205
+ const value = explicitCurrentWork[1]?.trim();
206
+ if (!value) {
207
+ return [];
208
+ }
209
+ return [
210
+ {
211
+ category: "project",
212
+ key: "\u5F53\u524D\u4E3B\u8981\u5DE5\u4F5C",
213
+ value,
214
+ sensitivity: "normal",
215
+ recallPolicy: "always",
216
+ scope: choosePersonalOrSharedScope(input),
217
+ confidence: 0.92,
218
+ tags: [...input.topic?.labels ?? [], "project", "current-focus"]
219
+ }
220
+ ];
221
+ }
222
+ function choosePersonalOrSharedScope(input) {
223
+ if (shouldForceShared(input.text)) {
224
+ return "shared";
225
+ }
226
+ if (input.userId) {
227
+ return `user:${input.userId}`;
228
+ }
229
+ return fallbackSharedScope(input);
230
+ }
231
+ function fallbackSharedScope(input) {
232
+ return input.topic ? `topic:${input.topic.id}` : "shared";
233
+ }
234
+ function shouldForceShared(text) {
235
+ return /(共享|shared|公共|团队|所有人|everyone|team|public)/i.test(text);
236
+ }
199
237
  function dedupeCandidates(candidates) {
200
238
  const seen = /* @__PURE__ */ new Set();
201
239
  return candidates.filter((candidate) => {
@@ -363,6 +401,32 @@ var MemorySqliteStore = class {
363
401
  record.rawJson
364
402
  );
365
403
  }
404
+ async backfillSessionIdentity(args) {
405
+ this.db.exec("BEGIN");
406
+ try {
407
+ this.db.prepare(
408
+ `UPDATE ${TABLES.messages}
409
+ SET user_id = COALESCE(user_id, ?),
410
+ channel_type = COALESCE(channel_type, ?),
411
+ sender_open_id = COALESCE(sender_open_id, ?)
412
+ WHERE session_id = ?`
413
+ ).run(args.userId, args.channelType ?? null, args.senderOpenId ?? null, args.sessionId);
414
+ this.db.prepare(
415
+ `UPDATE ${TABLES.topics}
416
+ SET user_id = COALESCE(user_id, ?)
417
+ WHERE session_id = ?`
418
+ ).run(args.userId, args.sessionId);
419
+ this.db.prepare(
420
+ `UPDATE ${TABLES.sessionState}
421
+ SET user_id = COALESCE(user_id, ?)
422
+ WHERE session_id = ?`
423
+ ).run(args.userId, args.sessionId);
424
+ this.db.exec("COMMIT");
425
+ } catch (error) {
426
+ this.db.exec("ROLLBACK");
427
+ throw error;
428
+ }
429
+ }
366
430
  async getSessionState(sessionId) {
367
431
  const row = this.db.prepare(
368
432
  `SELECT session_id, user_id, active_topic_id, last_compacted_at, last_turn_id, updated_at
@@ -1241,6 +1305,7 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1241
1305
  async (event) => {
1242
1306
  await ensureResolvedIdentity(event);
1243
1307
  const sessionId = getSessionIdFromHookContext(event);
1308
+ await backfillResolvedIdentity(store, sessionId);
1244
1309
  const text = getTextFromHookContext(event);
1245
1310
  logMemoryEvent("hook-ingest-received", {
1246
1311
  hasSessionId: Boolean(sessionId),
@@ -1270,6 +1335,7 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1270
1335
  registerTypedHook("before_prompt_build", async (event, hookContext) => {
1271
1336
  await ensureResolvedIdentity(hookContext);
1272
1337
  const sessionId = getSessionIdFromHookContext(hookContext);
1338
+ await backfillResolvedIdentity(store, sessionId);
1273
1339
  const text = getTextFromHookContext(event);
1274
1340
  logMemoryEvent("hook-assemble-received", {
1275
1341
  hasSessionId: Boolean(sessionId),
@@ -1566,6 +1632,7 @@ function createContextEngineMemoryV2Plugin(inputConfig, api) {
1566
1632
  await store.upsertTopicMembership(membership);
1567
1633
  const extractedFacts = factExtractor.extract({
1568
1634
  sessionId,
1635
+ userId,
1569
1636
  text,
1570
1637
  topic: decision.action === "spawn" ? topicRecord : {
1571
1638
  ...topicRecord,
@@ -1682,6 +1749,21 @@ async function ensureResolvedIdentity(context) {
1682
1749
  });
1683
1750
  }
1684
1751
  }
1752
+ async function backfillResolvedIdentity(store, sessionId) {
1753
+ if (!sessionId || typeof store.backfillSessionIdentity !== "function") {
1754
+ return;
1755
+ }
1756
+ const userId = getResolvedUserId(sessionId);
1757
+ if (!userId) {
1758
+ return;
1759
+ }
1760
+ await store.backfillSessionIdentity({
1761
+ sessionId,
1762
+ userId,
1763
+ channelType: getResolvedChannelType(sessionId),
1764
+ senderOpenId: getResolvedSenderOpenId(sessionId)
1765
+ });
1766
+ }
1685
1767
  function getResolvedUserId(sessionId) {
1686
1768
  const identity = getResolvedIdentity(sessionId);
1687
1769
  const userId = identity?.userId;
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "name": "Bamdra OpenClaw Memory",
13
13
  "description": "Unified topic-aware memory plugin for OpenClaw",
14
- "version": "0.3.1",
14
+ "version": "0.3.10",
15
15
  "main": "./index.js",
16
16
  "configSchema": {
17
17
  "type": "object",
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamdra/bamdra-openclaw-memory",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "description": "Unified topic-aware memory plugin for OpenClaw with durable SQLite recall and bounded context growth.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.bamdra.com",
@@ -59,8 +59,8 @@
59
59
  }
60
60
  },
61
61
  "dependencies": {
62
- "@bamdra/bamdra-user-bind": "^0.1.5",
63
- "@bamdra/bamdra-memory-vector": "^0.1.5"
62
+ "@bamdra/bamdra-user-bind": "^0.1.7",
63
+ "@bamdra/bamdra-memory-vector": "^0.1.7"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/node": "^24.5.2"
@@ -7,7 +7,7 @@
7
7
  "skills": ["./skills"],
8
8
  "name": "Bamdra OpenClaw Memory",
9
9
  "description": "Unified topic-aware memory plugin for OpenClaw",
10
- "version": "0.3.1",
10
+ "version": "0.3.10",
11
11
  "main": "./dist/index.js",
12
12
  "configSchema": {
13
13
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamdra/bamdra-openclaw-memory",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "description": "Unified topic-aware memory plugin for OpenClaw with durable SQLite recall and bounded context growth.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.bamdra.com",
@@ -59,8 +59,8 @@
59
59
  }
60
60
  },
61
61
  "dependencies": {
62
- "@bamdra/bamdra-user-bind": "^0.1.5",
63
- "@bamdra/bamdra-memory-vector": "^0.1.5"
62
+ "@bamdra/bamdra-user-bind": "^0.1.7",
63
+ "@bamdra/bamdra-memory-vector": "^0.1.7"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/node": "^24.5.2"
@@ -107,6 +107,28 @@ If `bamdra-memory-vector` is enabled, treat `bamdra_memory_search` as a hybrid r
107
107
  - If memory retrieval feels privacy-sensitive or contextually risky, narrow the search or answer conservatively.
108
108
  - Treat `bamdra-user-bind` profile data as private-by-default user context, not as a global team directory.
109
109
 
110
+ ## Scope Rules
111
+
112
+ Use `shared` only for genuinely reusable public knowledge.
113
+
114
+ Good `shared` examples:
115
+
116
+ - team conventions
117
+ - reusable SOPs
118
+ - public project templates
119
+ - shared knowledge base entries
120
+ - information the user explicitly says should be shared
121
+
122
+ Keep these private by default and prefer `user:` scope or `bamdra-user-bind` profile updates:
123
+
124
+ - preferred form of address
125
+ - timezone
126
+ - tone and formatting preferences
127
+ - pets, family, role, and personal background
128
+ - the user's current focus, ongoing work, and long-lived personal goals
129
+
130
+ When a fact is about “me”, “my”, or the current user's own stable way of working, do not store it as `shared` unless the user explicitly says it is a shared rule for everyone.
131
+
110
132
  ## Working Style
111
133
 
112
134
  - Keep memory behavior mostly invisible.