@bamdra/bamdra-memory-vector 0.1.0 → 0.1.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # bamdra-memory-vector
2
2
 
3
- `bamdra-memory-vector` is the optional semantic retrieval plugin for the Bamdra OpenClaw memory suite.
3
+ `bamdra-memory-vector` is the semantic retrieval plugin for the Bamdra OpenClaw suite.
4
4
 
5
5
  It adds lightweight vector-style recall on top of `bamdra-openclaw-memory` without turning the whole stack into a heavy external vector-database deployment.
6
6
 
@@ -27,11 +27,46 @@ The repository currently looks small because the first public version is intenti
27
27
 
28
28
  ## Current Runtime Model
29
29
 
30
- - Markdown root:
31
- `~/.openclaw/memory/vector/markdown/`
30
+ - private Markdown root:
31
+ `~/.openclaw/memory/vector/markdown/private/`
32
+ - shared Markdown root:
33
+ `~/.openclaw/memory/vector/markdown/shared/`
32
34
  - local index:
33
35
  `~/.openclaw/memory/vector/index.json`
34
36
 
37
+ `markdownRoot` is still supported as a legacy base path, but the recommended production setup is to set `privateMarkdownRoot` and `sharedMarkdownRoot` explicitly.
38
+
39
+ ## Bundled Skill
40
+
41
+ This package now ships its own standalone operator skill:
42
+
43
+ - `bamdra-memory-vector-operator`
44
+
45
+ When OpenClaw bootstraps the installed plugin, that skill can be materialized into `~/.openclaw/skills/` and attached automatically.
46
+
47
+ ## Best Practice: Obsidian-Friendly Layout
48
+
49
+ The plugin now supports separate roots for private memory and shared knowledge. That makes it practical to point Markdown storage at an Obsidian vault, iCloud Drive folder, Git-synced repo, or Syncthing workspace while keeping the local vector index in `~/.openclaw`.
50
+
51
+ Recommended example:
52
+
53
+ ```json
54
+ {
55
+ "enabled": true,
56
+ "privateMarkdownRoot": "~/Documents/Obsidian/MyVault/openclaw/private",
57
+ "sharedMarkdownRoot": "~/Documents/Obsidian/MyVault/openclaw/shared",
58
+ "indexPath": "~/.openclaw/memory/vector/index.json",
59
+ "dimensions": 64
60
+ }
61
+ ```
62
+
63
+ This gives you:
64
+
65
+ - a private vault area for user-specific memory
66
+ - a shared vault area for team or household knowledge
67
+ - Markdown files that can be edited on any synced device
68
+ - a local index that OpenClaw can rebuild without exposing other users' data
69
+
35
70
  This first version exposes a lightweight local vector-style index interface and keeps the integration surface ready for later LanceDB-backed evolution.
36
71
 
37
72
  ## Product Positioning
@@ -0,0 +1,87 @@
1
+ # bamdra-memory-vector
2
+
3
+ `bamdra-memory-vector` 是 Bamdra OpenClaw 套件中的语义召回插件,而且它也可以独立运行。
4
+
5
+ ## 它做什么
6
+
7
+ - 把记忆内容以 Markdown 可读形式写入本地
8
+ - 建立轻量的本地语义索引
9
+ - 按当前用户边界限制检索范围
10
+ - 为长程记忆回捞提供 Top-K 语义召回
11
+ - 保持私有记忆与共享知识分离
12
+
13
+ ## 开源内容说明
14
+
15
+ 当前开源版的实际源码已经包含在这个仓库里。
16
+
17
+ - 源码入口:
18
+ [src/index.ts](/Users/wood/workspace/macmini-openclaw/openclaw-enhanced/bamdra-memory-vector/src/index.ts)
19
+ - 插件清单:
20
+ [openclaw.plugin.json](/Users/wood/workspace/macmini-openclaw/openclaw-enhanced/bamdra-memory-vector/openclaw.plugin.json)
21
+ - 包元数据:
22
+ [package.json](/Users/wood/workspace/macmini-openclaw/openclaw-enhanced/bamdra-memory-vector/package.json)
23
+
24
+ 仓库目前看起来比较轻,是因为首个公开版本刻意保持为紧凑、单入口插件。
25
+
26
+ ## 当前运行模型
27
+
28
+ - 私有 Markdown 根目录:
29
+ `~/.openclaw/memory/vector/markdown/private/`
30
+ - 共享 Markdown 根目录:
31
+ `~/.openclaw/memory/vector/markdown/shared/`
32
+ - 本地索引:
33
+ `~/.openclaw/memory/vector/index.json`
34
+
35
+ 这一版提供的是轻量本地向量式索引接口,同时为后续 LanceDB 演进保留接入面。
36
+
37
+ `markdownRoot` 仍然保留兼容,但更推荐在正式环境里显式设置 `privateMarkdownRoot` 和 `sharedMarkdownRoot`。
38
+
39
+ ## 随包 Skill
40
+
41
+ 这个包现在会附带独立的 operator skill:
42
+
43
+ - `bamdra-memory-vector-operator`
44
+
45
+ 安装到 OpenClaw 后,bootstrap 可以把它复制到 `~/.openclaw/skills/` 并自动挂到 agent。
46
+
47
+ ## 最佳实践:兼容 Obsidian 的双库布局
48
+
49
+ 插件现在支持把私有记忆和共享知识分别落到两个 Markdown 根目录。这意味着你可以把 Markdown 指向 Obsidian 仓库、iCloud Drive、Git 同步目录或者 Syncthing 目录,而把本地索引仍然保留在 `~/.openclaw` 下面。
50
+
51
+ 推荐配置示例:
52
+
53
+ ```json
54
+ {
55
+ "enabled": true,
56
+ "privateMarkdownRoot": "~/Documents/Obsidian/MyVault/openclaw/private",
57
+ "sharedMarkdownRoot": "~/Documents/Obsidian/MyVault/openclaw/shared",
58
+ "indexPath": "~/.openclaw/memory/vector/index.json",
59
+ "dimensions": 64
60
+ }
61
+ ```
62
+
63
+ 这样做的好处是:
64
+
65
+ - 私有记忆和共享知识库天然分开
66
+ - Markdown 文件可以在任何同步设备上直接编辑
67
+ - OpenClaw 继续使用本地索引,不依赖远端向量服务
68
+ - 更适合把 Obsidian 作为长期知识库编辑入口
69
+
70
+ ## 产品定位
71
+
72
+ `bamdra-memory-vector` 不是用来替代 `bamdra-openclaw-memory` 的。
73
+
74
+ - `bamdra-openclaw-memory` 更强调连续性
75
+ - `bamdra-memory-vector` 更强调召回增强
76
+
77
+ 两者组合后的价值是:
78
+
79
+ - 更强的长会话连续性
80
+ - Markdown 可读、可维护的记忆载体
81
+ - 不依赖重型基础设施的轻量语义召回
82
+
83
+ ## 构建
84
+
85
+ ```bash
86
+ pnpm run bundle
87
+ ```
package/dist/index.js CHANGED
@@ -24,18 +24,29 @@ __export(index_exports, {
24
24
  register: () => register
25
25
  });
26
26
  module.exports = __toCommonJS(index_exports);
27
+
28
+ // ../node_modules/.pnpm/tsup@8.5.1_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
29
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
30
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
31
+
32
+ // src/index.ts
27
33
  var import_node_crypto = require("crypto");
28
34
  var import_node_fs = require("fs");
29
35
  var import_node_os = require("os");
30
36
  var import_node_path = require("path");
37
+ var import_node_url = require("url");
31
38
  var GLOBAL_VECTOR_API_KEY = "__OPENCLAW_BAMDRA_MEMORY_VECTOR__";
39
+ var PLUGIN_ID = "bamdra-memory-vector";
40
+ var SKILL_ID = "bamdra-memory-vector-operator";
41
+ var TOOL_NAME = "memory_vector_search";
32
42
  var LocalVectorIndex = class {
33
43
  config;
34
44
  records = /* @__PURE__ */ new Map();
35
45
  constructor(inputConfig) {
36
46
  this.config = normalizeConfig(inputConfig);
37
47
  (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(this.config.indexPath), { recursive: true });
38
- (0, import_node_fs.mkdirSync)(this.config.markdownRoot, { recursive: true });
48
+ (0, import_node_fs.mkdirSync)(this.config.privateMarkdownRoot, { recursive: true });
49
+ (0, import_node_fs.mkdirSync)(this.config.sharedMarkdownRoot, { recursive: true });
39
50
  this.load();
40
51
  }
41
52
  upsert(args) {
@@ -54,7 +65,8 @@ ${args.text}`, this.config.dimensions),
54
65
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
55
66
  };
56
67
  this.records.set(id, record);
57
- const markdownPath = (0, import_node_path.join)(this.config.markdownRoot, args.sourcePath);
68
+ const markdownRoot = args.userId == null ? this.config.sharedMarkdownRoot : this.config.privateMarkdownRoot;
69
+ const markdownPath = (0, import_node_path.join)(markdownRoot, args.sourcePath);
58
70
  (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(markdownPath), { recursive: true });
59
71
  (0, import_node_fs.writeFileSync)(markdownPath, `# ${args.title}
60
72
 
@@ -69,7 +81,7 @@ ${args.text}
69
81
  if (args.userId == null) {
70
82
  return record.userId == null;
71
83
  }
72
- return record.userId === args.userId;
84
+ return record.userId === args.userId || record.userId == null;
73
85
  }).map((record) => ({
74
86
  id: record.id,
75
87
  userId: record.userId,
@@ -100,10 +112,16 @@ ${args.text}
100
112
  }
101
113
  };
102
114
  function register(api) {
115
+ queueMicrotask(() => {
116
+ try {
117
+ bootstrapOpenClawHost();
118
+ } catch {
119
+ }
120
+ });
103
121
  const runtime = new LocalVectorIndex(api.pluginConfig ?? api.config ?? api.plugin?.config);
104
122
  exposeVectorApi(runtime);
105
123
  api.registerTool?.({
106
- name: "memory_vector_search",
124
+ name: TOOL_NAME,
107
125
  description: "Search the current user's vector memory index",
108
126
  parameters: {
109
127
  type: "object",
@@ -148,13 +166,118 @@ function exposeVectorApi(runtime) {
148
166
  }
149
167
  function normalizeConfig(input) {
150
168
  const root = (0, import_node_path.join)((0, import_node_os.homedir)(), ".openclaw", "memory", "vector");
169
+ const markdownRoot = input?.markdownRoot ?? (0, import_node_path.join)(root, "markdown");
151
170
  return {
152
171
  enabled: input?.enabled ?? true,
153
- markdownRoot: input?.markdownRoot ?? (0, import_node_path.join)(root, "markdown"),
172
+ markdownRoot,
173
+ privateMarkdownRoot: input?.privateMarkdownRoot ?? (0, import_node_path.join)(markdownRoot, "private"),
174
+ sharedMarkdownRoot: input?.sharedMarkdownRoot ?? (0, import_node_path.join)(markdownRoot, "shared"),
154
175
  indexPath: input?.indexPath ?? (0, import_node_path.join)(root, "index.json"),
155
176
  dimensions: input?.dimensions ?? 64
156
177
  };
157
178
  }
179
+ function bootstrapOpenClawHost() {
180
+ const currentFile = (0, import_node_url.fileURLToPath)(importMetaUrl);
181
+ const runtimeDir = (0, import_node_path.dirname)(currentFile);
182
+ const packageRoot = (0, import_node_path.resolve)(runtimeDir, "..");
183
+ const openclawHome = (0, import_node_path.resolve)((0, import_node_os.homedir)(), ".openclaw");
184
+ const configPath = (0, import_node_path.join)(openclawHome, "openclaw.json");
185
+ const extensionRoot = (0, import_node_path.join)(openclawHome, "extensions");
186
+ const globalSkillsDir = (0, import_node_path.join)(openclawHome, "skills");
187
+ const skillSource = (0, import_node_path.join)(packageRoot, "skills", SKILL_ID);
188
+ const skillTarget = (0, import_node_path.join)(globalSkillsDir, SKILL_ID);
189
+ if (!runtimeDir.startsWith(extensionRoot) || !(0, import_node_fs.existsSync)(configPath)) {
190
+ return;
191
+ }
192
+ if ((0, import_node_fs.existsSync)(skillSource) && !(0, import_node_fs.existsSync)(skillTarget)) {
193
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(skillTarget), { recursive: true });
194
+ (0, import_node_fs.cpSync)(skillSource, skillTarget, { recursive: true });
195
+ }
196
+ const original = (0, import_node_fs.readFileSync)(configPath, "utf8");
197
+ const config = JSON.parse(original);
198
+ const changed = ensureHostConfig(config);
199
+ if (!changed) {
200
+ return;
201
+ }
202
+ (0, import_node_fs.writeFileSync)(configPath, `${JSON.stringify(config, null, 2)}
203
+ `, "utf8");
204
+ }
205
+ function ensureHostConfig(config) {
206
+ let changed = false;
207
+ const plugins = ensureObject(config, "plugins");
208
+ const entries = ensureObject(plugins, "entries");
209
+ const load = ensureObject(plugins, "load");
210
+ const tools = ensureObject(config, "tools");
211
+ const skills = ensureObject(config, "skills");
212
+ const skillsLoad = ensureObject(skills, "load");
213
+ const agents = ensureObject(config, "agents");
214
+ const entry = ensureObject(entries, PLUGIN_ID);
215
+ const entryConfig = ensureObject(entry, "config");
216
+ changed = ensureArrayIncludes(plugins, "allow", PLUGIN_ID) || changed;
217
+ changed = ensureArrayIncludes(load, "paths", (0, import_node_path.join)((0, import_node_os.homedir)(), ".openclaw", "extensions")) || changed;
218
+ changed = ensureArrayIncludes(skillsLoad, "extraDirs", (0, import_node_path.join)((0, import_node_os.homedir)(), ".openclaw", "skills")) || changed;
219
+ changed = ensureArrayIncludes(tools, "allow", TOOL_NAME) || changed;
220
+ if (entry.enabled !== false) {
221
+ entry.enabled = false;
222
+ changed = true;
223
+ }
224
+ if (entryConfig.enabled !== false) {
225
+ entryConfig.enabled = false;
226
+ changed = true;
227
+ }
228
+ if (typeof entryConfig.privateMarkdownRoot !== "string" || entryConfig.privateMarkdownRoot.length === 0) {
229
+ entryConfig.privateMarkdownRoot = "~/.openclaw/memory/vector/markdown/private";
230
+ changed = true;
231
+ }
232
+ if (typeof entryConfig.sharedMarkdownRoot !== "string" || entryConfig.sharedMarkdownRoot.length === 0) {
233
+ entryConfig.sharedMarkdownRoot = "~/.openclaw/memory/vector/markdown/shared";
234
+ changed = true;
235
+ }
236
+ if (typeof entryConfig.indexPath !== "string" || entryConfig.indexPath.length === 0) {
237
+ entryConfig.indexPath = "~/.openclaw/memory/vector/index.json";
238
+ changed = true;
239
+ }
240
+ changed = ensureAgentSkills(agents, SKILL_ID) || changed;
241
+ return changed;
242
+ }
243
+ function ensureObject(parent, key) {
244
+ const current = parent[key];
245
+ if (current && typeof current === "object" && !Array.isArray(current)) {
246
+ return current;
247
+ }
248
+ const next = {};
249
+ parent[key] = next;
250
+ return next;
251
+ }
252
+ function ensureArrayIncludes(parent, key, value) {
253
+ const current = Array.isArray(parent[key]) ? [...parent[key]] : [];
254
+ if (current.includes(value)) {
255
+ if (!Array.isArray(parent[key])) {
256
+ parent[key] = current;
257
+ }
258
+ return false;
259
+ }
260
+ current.push(value);
261
+ parent[key] = current;
262
+ return true;
263
+ }
264
+ function ensureAgentSkills(agents, skillId) {
265
+ const list = Array.isArray(agents.list) ? agents.list : [];
266
+ let changed = false;
267
+ for (const item of list) {
268
+ if (!item || typeof item !== "object") {
269
+ continue;
270
+ }
271
+ const agent = item;
272
+ const current = Array.isArray(agent.skills) ? [...agent.skills] : [];
273
+ if (!current.includes(skillId)) {
274
+ current.push(skillId);
275
+ agent.skills = current;
276
+ changed = true;
277
+ }
278
+ }
279
+ return changed;
280
+ }
158
281
  function embed(text, dimensions) {
159
282
  const vector = Array.from({ length: dimensions }, () => 0);
160
283
  const tokens = text.toLowerCase().split(/[^a-z0-9_\u4e00-\u9fff]+/i).filter(Boolean);
@@ -5,6 +5,7 @@
5
5
  "description": "Local vector-style semantic retrieval enhancement for Bamdra memory.",
6
6
  "version": "0.1.0",
7
7
  "main": "./dist/index.js",
8
+ "skills": ["./skills"],
8
9
  "configSchema": {
9
10
  "type": "object",
10
11
  "additionalProperties": true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamdra/bamdra-memory-vector",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Lightweight local semantic retrieval enhancement for the Bamdra OpenClaw memory suite.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.bamdra.com",
@@ -16,6 +16,7 @@
16
16
  "types": "./dist/index.d.ts",
17
17
  "files": [
18
18
  "dist",
19
+ "skills",
19
20
  "openclaw.plugin.json",
20
21
  "README.md",
21
22
  "LICENSE"
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: bamdra-memory-vector-operator
3
+ description: Use vector-enhanced Markdown recall to find semantically related private or shared knowledge without overfilling context.
4
+ ---
5
+
6
+ # Bamdra Memory Vector Operator
7
+
8
+ Treat `bamdra-memory-vector` as the semantic recall layer for memory and knowledge files.
9
+
10
+ It complements topic memory. Use it when the user remembers something fuzzily, when keyword matching may fail, or when Markdown knowledge files are the likely source.
11
+
12
+ ## Good Triggers
13
+
14
+ - “你还记得吗”
15
+ - “之前提过那个”
16
+ - “知识库里有没有”
17
+ - “我好像写过一份相关说明”
18
+ - the wording is approximate rather than exact
19
+
20
+ ## Retrieval Policy
21
+
22
+ - prefer vector recall when the user is referencing older or fuzzily remembered content
23
+ - use shared knowledge and private knowledge deliberately
24
+ - keep cross-user boundaries intact
25
+ - do not flood the prompt with low-signal chunks
26
+ - prefer a few strong recalls over many weak ones
27
+
28
+ ## Markdown Knowledge Model
29
+
30
+ - private Markdown is for one user's durable notes and memory fragments
31
+ - shared Markdown is for team or reusable knowledge
32
+ - both are editable by humans outside the runtime
33
+
34
+ ## Safety Rules
35
+
36
+ - never treat another user's private Markdown as searchable context
37
+ - do not reveal storage paths unless the user asks
38
+ - if retrieval confidence is weak, answer cautiously instead of pretending certainty