@bamdra/bamdra-openclaw-memory 0.3.15 → 0.3.17

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.
@@ -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.15",
14
+ "version": "0.3.17",
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.15",
3
+ "version": "0.3.17",
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",
@@ -19,7 +19,8 @@
19
19
  "dist",
20
20
  "openclaw.plugin.json",
21
21
  "README.md",
22
- "skills"
22
+ "skills",
23
+ "scripts"
23
24
  ],
24
25
  "keywords": [
25
26
  "openclaw",
@@ -34,7 +35,8 @@
34
35
  },
35
36
  "scripts": {
36
37
  "bundle": "node ../../scripts/run-local-bin.mjs tsup && cp ../../packages/memory-sqlite/src/schema.sql ./dist/ && node ../../scripts/prepare-plugin-dist.mjs bamdra-openclaw-memory",
37
- "prepublishOnly": "pnpm run bundle"
38
+ "prepublishOnly": "pnpm run bundle",
39
+ "postinstall": "node ./scripts/postinstall.cjs"
38
40
  },
39
41
  "openclaw": {
40
42
  "id": "bamdra-openclaw-memory",
@@ -59,8 +61,8 @@
59
61
  }
60
62
  },
61
63
  "dependencies": {
62
- "@bamdra/bamdra-user-bind": "^0.1.10",
63
- "@bamdra/bamdra-memory-vector": "^0.1.10"
64
+ "@bamdra/bamdra-user-bind": "^0.1.11",
65
+ "@bamdra/bamdra-memory-vector": "^0.1.11"
64
66
  },
65
67
  "devDependencies": {
66
68
  "@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.15",
10
+ "version": "0.3.17",
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.15",
3
+ "version": "0.3.17",
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",
@@ -19,7 +19,8 @@
19
19
  "dist",
20
20
  "openclaw.plugin.json",
21
21
  "README.md",
22
- "skills"
22
+ "skills",
23
+ "scripts"
23
24
  ],
24
25
  "keywords": [
25
26
  "openclaw",
@@ -34,7 +35,8 @@
34
35
  },
35
36
  "scripts": {
36
37
  "bundle": "node ../../scripts/run-local-bin.mjs tsup && cp ../../packages/memory-sqlite/src/schema.sql ./dist/ && node ../../scripts/prepare-plugin-dist.mjs bamdra-openclaw-memory",
37
- "prepublishOnly": "pnpm run bundle"
38
+ "prepublishOnly": "pnpm run bundle",
39
+ "postinstall": "node ./scripts/postinstall.cjs"
38
40
  },
39
41
  "openclaw": {
40
42
  "id": "bamdra-openclaw-memory",
@@ -59,8 +61,8 @@
59
61
  }
60
62
  },
61
63
  "dependencies": {
62
- "@bamdra/bamdra-user-bind": "^0.1.10",
63
- "@bamdra/bamdra-memory-vector": "^0.1.10"
64
+ "@bamdra/bamdra-user-bind": "^0.1.11",
65
+ "@bamdra/bamdra-memory-vector": "^0.1.11"
64
66
  },
65
67
  "devDependencies": {
66
68
  "@types/node": "^24.5.2"
@@ -0,0 +1,403 @@
1
+ "use strict";
2
+
3
+ const { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } = require("node:fs");
4
+ const { homedir } = require("node:os");
5
+ const { dirname, join, resolve, sep } = require("node:path");
6
+ const { createRequire } = require("node:module");
7
+
8
+ const PLUGIN_ID = "bamdra-openclaw-memory";
9
+ const SKILL_ID = "bamdra-memory-operator";
10
+ const USER_BIND_PROFILE_SKILL_ID = "bamdra-user-bind-profile";
11
+ const USER_BIND_ADMIN_SKILL_ID = "bamdra-user-bind-admin";
12
+ const VECTOR_SKILL_ID = "bamdra-memory-vector-operator";
13
+ const REQUIRED_TOOL_NAMES = [
14
+ "bamdra_memory_list_topics",
15
+ "bamdra_memory_switch_topic",
16
+ "bamdra_memory_save_fact",
17
+ "bamdra_memory_compact_topic",
18
+ "bamdra_memory_search",
19
+ "bamdra_user_bind_get_my_profile",
20
+ "bamdra_user_bind_update_my_profile",
21
+ "bamdra_user_bind_refresh_my_binding",
22
+ "bamdra_user_bind_admin_query",
23
+ "bamdra_user_bind_admin_edit",
24
+ "bamdra_user_bind_admin_merge",
25
+ "bamdra_user_bind_admin_list_issues",
26
+ "bamdra_user_bind_admin_sync",
27
+ "bamdra_memory_vector_search",
28
+ ] ;
29
+ const LEGACY_TOOL_ALIASES = [
30
+ "memory_list_topics",
31
+ "memory_switch_topic",
32
+ "memory_save_fact",
33
+ "memory_compact_topic",
34
+ "memory_search",
35
+ "user_bind_get_my_profile",
36
+ "user_bind_update_my_profile",
37
+ "user_bind_refresh_my_binding",
38
+ "user_bind_admin_query",
39
+ "user_bind_admin_edit",
40
+ "user_bind_admin_merge",
41
+ "user_bind_admin_list_issues",
42
+ "user_bind_admin_sync",
43
+ "memory_vector_search",
44
+ ];
45
+ const REQUIRED_PLUGIN_IDS = ["bamdra-user-bind"];
46
+ const OPTIONAL_PLUGIN_IDS = ["bamdra-memory-vector"];
47
+ const AUTO_PROVISION_PLUGIN_IDS = [...REQUIRED_PLUGIN_IDS, ...OPTIONAL_PLUGIN_IDS];
48
+ const CONFLICTING_PLUGIN_IDS = ["memory-core", "memory-lancedb"];
49
+ const runtimeRequire = createRequire(__filename);
50
+
51
+ function log(event, details) {
52
+ try {
53
+ console.info("[bamdra-openclaw-memory:postinstall]", event, JSON.stringify(details || {}));
54
+ } catch {
55
+ console.info("[bamdra-openclaw-memory:postinstall]", event);
56
+ }
57
+ }
58
+
59
+ function ensureObject(parent, key) {
60
+ if (!parent[key] || typeof parent[key] !== "object" || Array.isArray(parent[key])) {
61
+ parent[key] = {};
62
+ }
63
+ return parent[key];
64
+ }
65
+
66
+ function ensureArrayIncludes(parent, key, value) {
67
+ const current = Array.isArray(parent[key]) ? [...parent[key]] : [];
68
+ if (current.includes(value)) {
69
+ if (!Array.isArray(parent[key])) {
70
+ parent[key] = current;
71
+ return true;
72
+ }
73
+ return false;
74
+ }
75
+ current.push(value);
76
+ parent[key] = current;
77
+ return true;
78
+ }
79
+
80
+ function dependencySkillIds(pluginId) {
81
+ if (pluginId === "bamdra-user-bind") return [USER_BIND_PROFILE_SKILL_ID, USER_BIND_ADMIN_SKILL_ID];
82
+ if (pluginId === "bamdra-memory-vector") return [VECTOR_SKILL_ID];
83
+ return [];
84
+ }
85
+
86
+ function materializeBundledSkill(sourceDir, targetDir) {
87
+ if (!existsSync(sourceDir) || existsSync(targetDir)) return;
88
+ mkdirSync(dirname(targetDir), { recursive: true });
89
+ cpSync(sourceDir, targetDir, { recursive: true });
90
+ }
91
+
92
+ function resolveBundledDependencySource(pluginId) {
93
+ const packageNameByPluginId = {
94
+ "bamdra-user-bind": "@bamdra/bamdra-user-bind",
95
+ "bamdra-memory-vector": "@bamdra/bamdra-memory-vector",
96
+ };
97
+ const packageName = packageNameByPluginId[pluginId];
98
+ const candidateRoots = [];
99
+
100
+ if (packageName) {
101
+ try {
102
+ const entryPath = runtimeRequire.resolve(packageName);
103
+ candidateRoots.push(dirname(dirname(entryPath)));
104
+ candidateRoots.push(dirname(entryPath));
105
+ } catch {
106
+ // Ignore and continue.
107
+ }
108
+ }
109
+
110
+ candidateRoots.push(resolve(__dirname, "..", "bundled-plugins", pluginId));
111
+ candidateRoots.push(resolve(__dirname, "..", "..", "..", pluginId));
112
+ candidateRoots.push(resolve(__dirname, "..", "..", "..", "..", pluginId));
113
+
114
+ for (const root of candidateRoots) {
115
+ const distDir = existsSync(join(root, "dist")) ? join(root, "dist") : root;
116
+ if (
117
+ existsSync(join(distDir, "index.js")) &&
118
+ existsSync(join(distDir, "openclaw.plugin.json")) &&
119
+ existsSync(join(distDir, "package.json"))
120
+ ) {
121
+ return distDir;
122
+ }
123
+ }
124
+
125
+ return null;
126
+ }
127
+
128
+ function materializeBundledDependencyPlugins(extensionRoot) {
129
+ for (const pluginId of AUTO_PROVISION_PLUGIN_IDS) {
130
+ const targetDir = join(extensionRoot, pluginId);
131
+ if (existsSync(targetDir)) continue;
132
+ const sourceDir = resolveBundledDependencySource(pluginId);
133
+ if (!sourceDir) {
134
+ log("dependency-plugin-copy-skipped", { pluginId, reason: "source-not-found" });
135
+ continue;
136
+ }
137
+ mkdirSync(dirname(targetDir), { recursive: true });
138
+ cpSync(sourceDir, targetDir, { recursive: true });
139
+ log("dependency-plugin-copied", { pluginId, sourceDir, targetDir });
140
+ }
141
+ }
142
+
143
+ function materializeBundledDependencySkills(globalSkillsDir) {
144
+ for (const pluginId of AUTO_PROVISION_PLUGIN_IDS) {
145
+ const sourceDir = resolveBundledDependencySource(pluginId);
146
+ if (!sourceDir) continue;
147
+ const packageRoot = sourceDir.endsWith(`${sep}dist`) ? dirname(sourceDir) : sourceDir;
148
+ const skillsRoot = join(packageRoot, "skills");
149
+ if (!existsSync(skillsRoot)) continue;
150
+ for (const skillId of dependencySkillIds(pluginId)) {
151
+ materializeBundledSkill(join(skillsRoot, skillId), join(globalSkillsDir, skillId));
152
+ }
153
+ }
154
+ }
155
+
156
+ function ensureToolAllowlist(tools) {
157
+ let changed = false;
158
+ const allow = Array.isArray(tools.allow) ? [...tools.allow] : [];
159
+ const filteredAllow = allow.filter((toolName) => !LEGACY_TOOL_ALIASES.includes(toolName));
160
+ if (!Array.isArray(tools.allow) || filteredAllow.length !== allow.length) {
161
+ tools.allow = filteredAllow;
162
+ changed = true;
163
+ }
164
+ for (const toolName of REQUIRED_TOOL_NAMES) {
165
+ changed = ensureArrayIncludes(tools, "allow", toolName) || changed;
166
+ }
167
+ return changed;
168
+ }
169
+
170
+ function ensureAgentSkills(agents, skillId) {
171
+ const list = Array.isArray(agents.list) ? agents.list : [];
172
+ let changed = false;
173
+ for (const item of list) {
174
+ if (!item || typeof item !== "object") continue;
175
+ const currentSkills = Array.isArray(item.skills) ? [...item.skills] : [];
176
+ if (!Array.isArray(item.skills)) {
177
+ item.skills = currentSkills;
178
+ changed = true;
179
+ }
180
+ if (!currentSkills.includes(skillId)) {
181
+ currentSkills.push(skillId);
182
+ item.skills = currentSkills;
183
+ changed = true;
184
+ }
185
+ }
186
+ return changed;
187
+ }
188
+
189
+ function ensureAdminSkills(agents, skillId, adminAgentIds) {
190
+ const list = Array.isArray(agents.list) ? agents.list : [];
191
+ let changed = false;
192
+ for (const item of list) {
193
+ if (!item || typeof item !== "object") continue;
194
+ const agentId = typeof item.id === "string" ? item.id : typeof item.name === "string" ? item.name : null;
195
+ if (!agentId || !adminAgentIds.includes(agentId)) continue;
196
+ const currentSkills = Array.isArray(item.skills) ? [...item.skills] : [];
197
+ if (!Array.isArray(item.skills)) {
198
+ item.skills = currentSkills;
199
+ changed = true;
200
+ }
201
+ if (!currentSkills.includes(skillId)) {
202
+ currentSkills.push(skillId);
203
+ item.skills = currentSkills;
204
+ changed = true;
205
+ }
206
+ }
207
+ return changed;
208
+ }
209
+
210
+ function ensureHostConfig(config) {
211
+ let changed = false;
212
+
213
+ const plugins = ensureObject(config, "plugins");
214
+ const tools = ensureObject(config, "tools");
215
+ const skills = ensureObject(config, "skills");
216
+ const skillsLoad = ensureObject(skills, "load");
217
+ const agents = ensureObject(config, "agents");
218
+ const entries = ensureObject(plugins, "entries");
219
+ const load = ensureObject(plugins, "load");
220
+ const slots = ensureObject(plugins, "slots");
221
+ const pluginEntry = ensureObject(entries, PLUGIN_ID);
222
+ const userBindEntry = ensureObject(entries, REQUIRED_PLUGIN_IDS[0]);
223
+ const vectorEntry = ensureObject(entries, OPTIONAL_PLUGIN_IDS[0]);
224
+ const conflictingEntries = CONFLICTING_PLUGIN_IDS.map((pluginId) => ensureObject(entries, pluginId));
225
+ const pluginConfig = ensureObject(pluginEntry, "config");
226
+ const pluginStore = ensureObject(pluginConfig, "store");
227
+ const pluginCache = ensureObject(pluginConfig, "cache");
228
+
229
+ if (plugins.enabled !== true) {
230
+ plugins.enabled = true;
231
+ changed = true;
232
+ }
233
+ changed = ensureArrayIncludes(plugins, "allow", PLUGIN_ID) || changed;
234
+ for (const dependencyId of REQUIRED_PLUGIN_IDS) {
235
+ changed = ensureArrayIncludes(plugins, "allow", dependencyId) || changed;
236
+ const dependencyEntry = ensureObject(entries, dependencyId);
237
+ if (dependencyEntry.enabled !== true) {
238
+ dependencyEntry.enabled = true;
239
+ changed = true;
240
+ }
241
+ }
242
+ for (const optionalId of OPTIONAL_PLUGIN_IDS) {
243
+ changed = ensureArrayIncludes(plugins, "allow", optionalId) || changed;
244
+ }
245
+ for (const conflictingId of CONFLICTING_PLUGIN_IDS) {
246
+ changed = ensureArrayIncludes(plugins, "deny", conflictingId) || changed;
247
+ }
248
+ changed = ensureArrayIncludes(load, "paths", join(homedir(), ".openclaw", "extensions")) || changed;
249
+ changed = ensureArrayIncludes(skillsLoad, "extraDirs", join(homedir(), ".openclaw", "skills")) || changed;
250
+
251
+ if (slots.memory !== PLUGIN_ID) {
252
+ slots.memory = PLUGIN_ID;
253
+ changed = true;
254
+ }
255
+ if (slots.contextEngine !== PLUGIN_ID) {
256
+ slots.contextEngine = PLUGIN_ID;
257
+ changed = true;
258
+ }
259
+ if (pluginEntry.enabled !== true) {
260
+ pluginEntry.enabled = true;
261
+ changed = true;
262
+ }
263
+ if (pluginConfig.enabled !== true) {
264
+ pluginConfig.enabled = true;
265
+ changed = true;
266
+ }
267
+ if (pluginStore.provider !== "sqlite") {
268
+ pluginStore.provider = "sqlite";
269
+ changed = true;
270
+ }
271
+ if (typeof pluginStore.path !== "string" || pluginStore.path.length === 0) {
272
+ pluginStore.path = "~/.openclaw/memory/main.sqlite";
273
+ changed = true;
274
+ }
275
+ if (pluginCache.provider !== "memory") {
276
+ pluginCache.provider = "memory";
277
+ changed = true;
278
+ }
279
+ if (typeof pluginCache.maxSessions !== "number") {
280
+ pluginCache.maxSessions = 128;
281
+ changed = true;
282
+ }
283
+
284
+ const userBindConfig = ensureObject(userBindEntry, "config");
285
+ if (userBindEntry.enabled !== true) {
286
+ userBindEntry.enabled = true;
287
+ changed = true;
288
+ }
289
+ if (userBindConfig.enabled !== true) {
290
+ userBindConfig.enabled = true;
291
+ changed = true;
292
+ }
293
+ if (typeof userBindConfig.localStorePath !== "string" || userBindConfig.localStorePath.length === 0) {
294
+ userBindConfig.localStorePath = "~/.openclaw/data/bamdra-user-bind";
295
+ changed = true;
296
+ }
297
+ if (typeof userBindConfig.exportPath !== "string" || userBindConfig.exportPath.length === 0) {
298
+ userBindConfig.exportPath = "~/.openclaw/data/bamdra-user-bind/exports";
299
+ changed = true;
300
+ }
301
+ if (typeof userBindConfig.profileMarkdownRoot !== "string" || userBindConfig.profileMarkdownRoot.length === 0) {
302
+ userBindConfig.profileMarkdownRoot = "~/.openclaw/data/bamdra-user-bind/profiles/private";
303
+ changed = true;
304
+ }
305
+ if (typeof userBindConfig.cacheTtlMs !== "number") {
306
+ userBindConfig.cacheTtlMs = 1800000;
307
+ changed = true;
308
+ }
309
+ if (!Array.isArray(userBindConfig.adminAgents) || userBindConfig.adminAgents.length === 0) {
310
+ userBindConfig.adminAgents = ["main"];
311
+ changed = true;
312
+ }
313
+
314
+ const vectorConfig = ensureObject(vectorEntry, "config");
315
+ if (vectorEntry.enabled !== true) {
316
+ vectorEntry.enabled = true;
317
+ changed = true;
318
+ }
319
+ if (vectorConfig.enabled !== true) {
320
+ vectorConfig.enabled = true;
321
+ changed = true;
322
+ }
323
+ if (typeof vectorConfig.markdownRoot !== "string" || vectorConfig.markdownRoot.length === 0) {
324
+ vectorConfig.markdownRoot = "~/.openclaw/memory/vector/markdown";
325
+ changed = true;
326
+ }
327
+ if (typeof vectorConfig.privateMarkdownRoot !== "string" || vectorConfig.privateMarkdownRoot.length === 0) {
328
+ vectorConfig.privateMarkdownRoot = "~/.openclaw/memory/vector/markdown/private";
329
+ changed = true;
330
+ }
331
+ if (typeof vectorConfig.sharedMarkdownRoot !== "string" || vectorConfig.sharedMarkdownRoot.length === 0) {
332
+ vectorConfig.sharedMarkdownRoot = "~/.openclaw/memory/vector/markdown/shared";
333
+ changed = true;
334
+ }
335
+ if (typeof vectorConfig.indexPath !== "string" || vectorConfig.indexPath.length === 0) {
336
+ vectorConfig.indexPath = "~/.openclaw/memory/vector/index.json";
337
+ changed = true;
338
+ }
339
+ if (typeof vectorConfig.dimensions !== "number") {
340
+ vectorConfig.dimensions = 64;
341
+ changed = true;
342
+ }
343
+ if (typeof vectorConfig.topK !== "number") {
344
+ vectorConfig.topK = 5;
345
+ changed = true;
346
+ }
347
+
348
+ for (const conflictingEntry of conflictingEntries) {
349
+ if (conflictingEntry.enabled !== false) {
350
+ conflictingEntry.enabled = false;
351
+ changed = true;
352
+ }
353
+ }
354
+
355
+ changed = ensureToolAllowlist(tools) || changed;
356
+ changed = ensureAgentSkills(agents, SKILL_ID) || changed;
357
+ changed = ensureAgentSkills(agents, USER_BIND_PROFILE_SKILL_ID) || changed;
358
+ changed = ensureAgentSkills(agents, VECTOR_SKILL_ID) || changed;
359
+ changed = ensureAdminSkills(agents, USER_BIND_ADMIN_SKILL_ID, ["main"]) || changed;
360
+
361
+ return changed;
362
+ }
363
+
364
+ function main() {
365
+ const openclawHome = resolve(homedir(), ".openclaw");
366
+ const extensionRoot = join(openclawHome, "extensions");
367
+ const memoryRoot = join(openclawHome, "memory");
368
+ const globalSkillsDir = join(openclawHome, "skills");
369
+ const bundledSkillDir = join(resolve(__dirname, "..", "skills", SKILL_ID));
370
+ const targetSkillDir = join(globalSkillsDir, SKILL_ID);
371
+ const configPath = join(openclawHome, "openclaw.json");
372
+
373
+ if (!existsSync(configPath)) {
374
+ log("bootstrap-skipped", { reason: "missing-openclaw-config" });
375
+ return;
376
+ }
377
+
378
+ mkdirSync(openclawHome, { recursive: true });
379
+ mkdirSync(extensionRoot, { recursive: true });
380
+ mkdirSync(memoryRoot, { recursive: true });
381
+
382
+ materializeBundledDependencyPlugins(extensionRoot);
383
+ materializeBundledSkill(bundledSkillDir, targetSkillDir);
384
+ materializeBundledDependencySkills(globalSkillsDir);
385
+
386
+ const original = readFileSync(configPath, "utf8");
387
+ const config = JSON.parse(original);
388
+ const changed = ensureHostConfig(config);
389
+
390
+ if (!changed) {
391
+ log("bootstrap-noop", { configPath });
392
+ return;
393
+ }
394
+
395
+ writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
396
+ log("bootstrap-updated", { configPath, pluginId: PLUGIN_ID });
397
+ }
398
+
399
+ try {
400
+ main();
401
+ } catch (error) {
402
+ log("bootstrap-failed", { message: error instanceof Error ? error.message : String(error) });
403
+ }
@@ -11,6 +11,8 @@ This skill is an optional behavior layer. It improves tool judgment, but the run
11
11
 
12
12
  Treat `bamdra-user-bind` as the primary user personalization layer. In practice it should cover most of what a per-user `USER.md` file would normally do: preferred address, long-lived preferences, role, personality notes, and other stable profile traits for the current bound user.
13
13
 
14
+ Workspace-level `USER.md` files should stay thin and avoid repeating per-user address preferences. Use the bound profile as the source of truth for greeting style and default address.
15
+
14
16
  ## Operating Goal
15
17
 
16
18
  Make memory feel natural:
@@ -21,6 +23,7 @@ Make memory feel natural:
21
23
  - avoid leaking unrelated memory into the current answer
22
24
  - avoid narrating internal storage mechanics unless the user asks
23
25
  - personalize tone and defaults from the bound user profile when that improves the response
26
+ - prefer the bound profile over workspace notes for address, nickname, and stable personal style
24
27
 
25
28
  ## Decision Policy
26
29