@psiclawops/hypermem-memory 0.5.1

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.
@@ -0,0 +1,24 @@
1
+ /**
2
+ * HyperMem Memory Plugin
3
+ *
4
+ * Thin adapter that bridges HyperMem's retrieval capabilities into
5
+ * OpenClaw's memory slot contract (`kind: "memory"`).
6
+ *
7
+ * The context engine plugin (hypercompositor) owns the full lifecycle:
8
+ * ingest, assemble, compact, afterTurn, bootstrap, dispose.
9
+ *
10
+ * This plugin owns the memory slot contract:
11
+ * - registerMemoryCapability() with runtime + publicArtifacts
12
+ * - memory_search tool backing via MemorySearchManager
13
+ * - Public artifacts for memory-wiki bridge
14
+ *
15
+ * Both plugins share the same HyperMem singleton (loaded from repo dist).
16
+ */
17
+ declare const _default: {
18
+ id: string;
19
+ name: string;
20
+ description: string;
21
+ configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
22
+ register: NonNullable<import("openclaw/plugin-sdk/plugin-entry").OpenClawPluginDefinition["register"]>;
23
+ } & Pick<import("openclaw/plugin-sdk/plugin-entry").OpenClawPluginDefinition, "kind" | "reload" | "nodeHostCommands" | "securityAuditCollectors">;
24
+ export default _default;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;;;;;;;;AA0VH,wBAmBG"}
package/dist/index.js ADDED
@@ -0,0 +1,287 @@
1
+ /**
2
+ * HyperMem Memory Plugin
3
+ *
4
+ * Thin adapter that bridges HyperMem's retrieval capabilities into
5
+ * OpenClaw's memory slot contract (`kind: "memory"`).
6
+ *
7
+ * The context engine plugin (hypercompositor) owns the full lifecycle:
8
+ * ingest, assemble, compact, afterTurn, bootstrap, dispose.
9
+ *
10
+ * This plugin owns the memory slot contract:
11
+ * - registerMemoryCapability() with runtime + publicArtifacts
12
+ * - memory_search tool backing via MemorySearchManager
13
+ * - Public artifacts for memory-wiki bridge
14
+ *
15
+ * Both plugins share the same HyperMem singleton (loaded from repo dist).
16
+ */
17
+ import { definePluginEntry, emptyPluginConfigSchema } from 'openclaw/plugin-sdk/plugin-entry';
18
+ import os from 'os';
19
+ import path from 'path';
20
+ import fs from 'fs/promises';
21
+ // ─── HyperMem singleton ────────────────────────────────────────
22
+ // Reuses the same singleton pattern as the context engine plugin.
23
+ // Both plugins load from the same repo dist path and share the instance.
24
+ const HYPERMEM_PATH = path.join(os.homedir(), '.openclaw/workspace/repo/hypermem/dist/index.js');
25
+ let _hm = null;
26
+ let _hmInitPromise = null;
27
+ async function getHyperMem() {
28
+ if (_hm)
29
+ return _hm;
30
+ if (_hmInitPromise)
31
+ return _hmInitPromise;
32
+ _hmInitPromise = (async () => {
33
+ const mod = await import(HYPERMEM_PATH);
34
+ const HyperMem = mod.HyperMem;
35
+ const instance = await HyperMem.create({
36
+ dataDir: path.join(os.homedir(), '.openclaw/hypermem'),
37
+ cache: {
38
+ keyPrefix: 'hm:',
39
+ sessionTTL: 14400,
40
+ historyTTL: 86400,
41
+ },
42
+ });
43
+ _hm = instance;
44
+ return instance;
45
+ })();
46
+ return _hmInitPromise;
47
+ }
48
+ /**
49
+ * Create a MemorySearchManager backed by HyperMem's retrieval pipeline.
50
+ *
51
+ * Uses HyperMem's:
52
+ * - library.db fact search (FTS5 + BM25)
53
+ * - vector store semantic search (when available)
54
+ * - message search (full-text across conversations)
55
+ */
56
+ function createMemorySearchManager(hm, agentId, workspaceDir) {
57
+ return {
58
+ async search(query, opts) {
59
+ const maxResults = opts?.maxResults ?? 10;
60
+ const minScore = opts?.minScore ?? 0;
61
+ const results = [];
62
+ // 1. Fact search (FTS5 + BM25 from library.db)
63
+ try {
64
+ const facts = hm.getActiveFacts(agentId, { limit: maxResults * 2 });
65
+ // Simple keyword matching for facts (FTS5 handles this in the DB layer)
66
+ const queryLower = query.toLowerCase();
67
+ const queryTerms = queryLower.split(/\s+/).filter(t => t.length > 2);
68
+ for (const fact of facts) {
69
+ const contentLower = fact.content.toLowerCase();
70
+ const matchCount = queryTerms.filter(t => contentLower.includes(t)).length;
71
+ if (matchCount === 0)
72
+ continue;
73
+ const score = matchCount / queryTerms.length;
74
+ if (score < minScore)
75
+ continue;
76
+ results.push({
77
+ path: `library://facts/${fact.id}`,
78
+ startLine: 0,
79
+ endLine: 0,
80
+ score,
81
+ snippet: fact.content.slice(0, 300),
82
+ source: 'memory',
83
+ citation: fact.domain ? `[fact:${fact.domain}]` : '[fact]',
84
+ });
85
+ }
86
+ }
87
+ catch {
88
+ // Fact search non-fatal
89
+ }
90
+ // 2. Vector/semantic search (when available)
91
+ try {
92
+ const vectorStore = hm.getVectorStore();
93
+ if (vectorStore) {
94
+ const vectorResults = await hm.semanticSearch(agentId, query, {
95
+ limit: maxResults,
96
+ maxDistance: 1.5,
97
+ });
98
+ for (const vr of vectorResults) {
99
+ const score = 1.0 - (vr.distance / 2.0); // normalize distance to 0-1 score
100
+ if (score < minScore)
101
+ continue;
102
+ results.push({
103
+ path: `vector://${vr.sourceTable}/${vr.sourceId}`,
104
+ startLine: 0,
105
+ endLine: 0,
106
+ score,
107
+ snippet: vr.content.slice(0, 300),
108
+ source: 'memory',
109
+ citation: `[${vr.sourceTable}:${vr.sourceId}]`,
110
+ });
111
+ }
112
+ }
113
+ }
114
+ catch {
115
+ // Vector search non-fatal
116
+ }
117
+ // 3. Message search (FTS5 across conversations)
118
+ try {
119
+ const messageResults = hm.search(agentId, query, maxResults);
120
+ for (const msg of messageResults) {
121
+ const content = msg.textContent ?? '';
122
+ results.push({
123
+ path: `messages://${msg.conversationId ?? 'unknown'}/${msg.id}`,
124
+ startLine: 0,
125
+ endLine: 0,
126
+ score: 0.5, // message search doesn't return scores, use mid-range
127
+ snippet: content.slice(0, 300),
128
+ source: 'sessions',
129
+ citation: `[message:${msg.id}]`,
130
+ });
131
+ }
132
+ }
133
+ catch {
134
+ // Message search non-fatal
135
+ }
136
+ // Deduplicate by content similarity, sort by score, limit
137
+ results.sort((a, b) => b.score - a.score);
138
+ return results.slice(0, maxResults);
139
+ },
140
+ async readFile(params) {
141
+ const absPath = path.resolve(workspaceDir, params.relPath);
142
+ try {
143
+ const content = await fs.readFile(absPath, 'utf-8');
144
+ const lines = content.split('\n');
145
+ const from = params.from ?? 0;
146
+ const count = params.lines ?? lines.length;
147
+ const slice = lines.slice(from, from + count);
148
+ return { text: slice.join('\n'), path: absPath };
149
+ }
150
+ catch (err) {
151
+ return { text: `Error reading ${absPath}: ${err.message}`, path: absPath };
152
+ }
153
+ },
154
+ status() {
155
+ const vectorStore = hm.getVectorStore();
156
+ const vectorStats = vectorStore ? hm.getVectorStats(agentId) : null;
157
+ return {
158
+ backend: 'builtin',
159
+ provider: 'hypermem',
160
+ model: 'hypermem-fts5+vector',
161
+ workspaceDir,
162
+ dbPath: path.join(os.homedir(), '.openclaw/hypermem'),
163
+ sources: ['memory', 'sessions'],
164
+ fts: {
165
+ enabled: true,
166
+ available: true,
167
+ },
168
+ vector: {
169
+ enabled: !!vectorStore,
170
+ available: !!vectorStore,
171
+ dims: vectorStats ? 768 : undefined,
172
+ },
173
+ custom: {
174
+ vectorStats: vectorStats ?? undefined,
175
+ factCount: hm.getActiveFacts(agentId, { limit: 1 }).length > 0 ? 'available' : 'empty',
176
+ },
177
+ };
178
+ },
179
+ async probeEmbeddingAvailability() {
180
+ try {
181
+ const vectorStore = hm.getVectorStore();
182
+ if (!vectorStore)
183
+ return { ok: false, error: 'Vector store not initialized' };
184
+ return { ok: true };
185
+ }
186
+ catch (err) {
187
+ return { ok: false, error: err.message };
188
+ }
189
+ },
190
+ async probeVectorAvailability() {
191
+ return !!hm.getVectorStore();
192
+ },
193
+ };
194
+ }
195
+ // ─── Manager cache ──────────────────────────────────────────────
196
+ // One manager per agentId; closed on plugin dispose.
197
+ const _managers = new Map();
198
+ // ─── Plugin Entry ───────────────────────────────────────────────
199
+ export default definePluginEntry({
200
+ id: 'hypermem',
201
+ name: 'HyperMem Memory',
202
+ description: 'Bridges HyperMem retrieval (facts, vectors, messages) into the OpenClaw memory slot for memory_search and memory-wiki.',
203
+ kind: 'memory',
204
+ configSchema: emptyPluginConfigSchema(),
205
+ register(api) {
206
+ api.registerMemoryCapability({
207
+ runtime: {
208
+ async getMemorySearchManager(params) {
209
+ try {
210
+ const hm = await getHyperMem();
211
+ const agentId = params.agentId || 'main';
212
+ // Cache managers per agent
213
+ if (!_managers.has(agentId)) {
214
+ // Resolve workspace dir from agent config
215
+ const agents = params.cfg?.agents?.list ?? [];
216
+ const agentCfg = agents.find((a) => a.id === agentId);
217
+ const workspaceDir = agentCfg?.workspace
218
+ ?? path.join(os.homedir(), '.openclaw/workspace');
219
+ _managers.set(agentId, createMemorySearchManager(hm, agentId, workspaceDir));
220
+ }
221
+ return { manager: _managers.get(agentId) };
222
+ }
223
+ catch (err) {
224
+ return { manager: null, error: err.message };
225
+ }
226
+ },
227
+ resolveMemoryBackendConfig(_params) {
228
+ return { backend: 'builtin' };
229
+ },
230
+ async closeAllMemorySearchManagers() {
231
+ _managers.clear();
232
+ },
233
+ },
234
+ publicArtifacts: {
235
+ async listArtifacts(params) {
236
+ const artifacts = [];
237
+ // List memory files for each agent
238
+ const agents = params.cfg?.agents?.list ?? [];
239
+ for (const agent of agents) {
240
+ const agentId = agent.id;
241
+ if (!agentId)
242
+ continue;
243
+ const workspace = agent.workspace;
244
+ if (!workspace)
245
+ continue;
246
+ const memoryDir = path.join(workspace, 'memory');
247
+ try {
248
+ const files = await fs.readdir(memoryDir);
249
+ for (const file of files) {
250
+ if (!file.endsWith('.md'))
251
+ continue;
252
+ artifacts.push({
253
+ kind: 'memory-daily',
254
+ workspaceDir: workspace,
255
+ relativePath: `memory/${file}`,
256
+ absolutePath: path.join(memoryDir, file),
257
+ agentIds: [agentId],
258
+ contentType: 'markdown',
259
+ });
260
+ }
261
+ }
262
+ catch {
263
+ // No memory dir for this agent — skip
264
+ }
265
+ // Also expose MEMORY.md index
266
+ const memoryIndex = path.join(workspace, 'MEMORY.md');
267
+ try {
268
+ await fs.access(memoryIndex);
269
+ artifacts.push({
270
+ kind: 'memory-index',
271
+ workspaceDir: workspace,
272
+ relativePath: 'MEMORY.md',
273
+ absolutePath: memoryIndex,
274
+ agentIds: [agentId],
275
+ contentType: 'markdown',
276
+ });
277
+ }
278
+ catch {
279
+ // No MEMORY.md — skip
280
+ }
281
+ }
282
+ return artifacts;
283
+ },
284
+ },
285
+ });
286
+ },
287
+ });
@@ -0,0 +1,10 @@
1
+ {
2
+ "id": "hypermem",
3
+ "enabledByDefault": false,
4
+ "kind": "memory",
5
+ "configSchema": {
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {}
9
+ }
10
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@psiclawops/hypermem-memory",
3
+ "version": "0.5.1",
4
+ "description": "HyperMem memory plugin for OpenClaw \u2014 bridges HyperMem retrieval into the memory slot",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "license": "Apache-2.0",
8
+ "author": "PsiClawOps",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/PsiClawOps/hypermem-internal.git",
12
+ "directory": "memory-plugin"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public",
16
+ "registry": "https://registry.npmjs.org"
17
+ },
18
+ "openclaw": {
19
+ "plugin": {
20
+ "id": "hypermem",
21
+ "name": "HyperMem Memory",
22
+ "kind": "memory"
23
+ },
24
+ "extensions": [
25
+ "./dist/index.js"
26
+ ]
27
+ },
28
+ "scripts": {
29
+ "build": "tsc",
30
+ "typecheck": "tsc --noEmit"
31
+ },
32
+ "devDependencies": {
33
+ "@psiclawops/hypermem": "file:..",
34
+ "openclaw": "*",
35
+ "typescript": "^5.4.0"
36
+ },
37
+ "peerDependencies": {
38
+ "openclaw": "*"
39
+ },
40
+ "keywords": [
41
+ "openclaw",
42
+ "openclaw-plugin",
43
+ "memory",
44
+ "hypermem"
45
+ ],
46
+ "files": [
47
+ "dist/**/*.js",
48
+ "dist/**/*.d.ts",
49
+ "dist/**/*.d.ts.map",
50
+ "openclaw.plugin.json",
51
+ "README.md",
52
+ "LICENSE"
53
+ ]
54
+ }