@digitalforgestudios/openclaw-sulcus 3.0.0 → 3.1.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.
- package/index.ts +31 -29
- package/openclaw.plugin.json +3 -3
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -43,29 +43,29 @@ const FALLBACK_AWARENESS = `<sulcus_context token_budget="500">
|
|
|
43
43
|
</sulcus_context>`;
|
|
44
44
|
|
|
45
45
|
// ─── NATIVE LIB LOADER ──────────────────────────────────────────────────────
|
|
46
|
-
// Loads libsulcus_store.dylib (embedded PG) and
|
|
46
|
+
// Loads libsulcus_store.dylib (embedded PG) and libsulcus_vectors.dylib (embeddings)
|
|
47
47
|
// via koffi FFI. Provides queryFn and embedFn callbacks for SulcusMem.create().
|
|
48
48
|
|
|
49
49
|
class NativeLibLoader {
|
|
50
50
|
private koffi: any = null;
|
|
51
51
|
private storeLib: any = null;
|
|
52
|
-
private
|
|
53
|
-
private
|
|
52
|
+
private vectorsLib: any = null;
|
|
53
|
+
private vectorsHandle: any = null;
|
|
54
54
|
|
|
55
55
|
// koffi function handles
|
|
56
56
|
private fn_store_init: any = null;
|
|
57
57
|
private fn_store_query: any = null;
|
|
58
58
|
private fn_store_free: any = null;
|
|
59
|
-
private
|
|
60
|
-
private
|
|
61
|
-
private
|
|
59
|
+
private fn_vectors_create: any = null;
|
|
60
|
+
private fn_vectors_text: any = null;
|
|
61
|
+
private fn_vectors_free: any = null;
|
|
62
62
|
|
|
63
63
|
public loaded = false;
|
|
64
64
|
public error: string | null = null;
|
|
65
65
|
|
|
66
66
|
constructor(
|
|
67
67
|
private storeLibPath: string,
|
|
68
|
-
private
|
|
68
|
+
private vectorsLibPath: string
|
|
69
69
|
) {}
|
|
70
70
|
|
|
71
71
|
init(logger: any): void {
|
|
@@ -84,8 +84,8 @@ class NativeLibLoader {
|
|
|
84
84
|
logger.warn(`memory-sulcus: ${this.error}`);
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
87
|
-
if (!existsSync(this.
|
|
88
|
-
this.error = `
|
|
87
|
+
if (!existsSync(this.vectorsLibPath)) {
|
|
88
|
+
this.error = `libsulcus_vectors not found at ${this.vectorsLibPath}`;
|
|
89
89
|
logger.warn(`memory-sulcus: ${this.error}`);
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
@@ -102,12 +102,12 @@ class NativeLibLoader {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
try {
|
|
105
|
-
this.
|
|
106
|
-
this.
|
|
107
|
-
this.
|
|
108
|
-
this.
|
|
105
|
+
this.vectorsLib = this.koffi.load(this.vectorsLibPath);
|
|
106
|
+
this.fn_vectors_create = this.vectorsLib.func("sulcus_vectors_create", "void*", []);
|
|
107
|
+
this.fn_vectors_text = this.vectorsLib.func("sulcus_vectors_text", "char*", ["void*", "str"]);
|
|
108
|
+
this.fn_vectors_free = this.vectorsLib.func("sulcus_vectors_free_string", "void", ["char*"]);
|
|
109
109
|
} catch (e: any) {
|
|
110
|
-
this.error = `Failed to load
|
|
110
|
+
this.error = `Failed to load libsulcus_vectors: ${e.message}`;
|
|
111
111
|
logger.warn(`memory-sulcus: ${this.error}`);
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
@@ -130,15 +130,15 @@ class NativeLibLoader {
|
|
|
130
130
|
|
|
131
131
|
// ── Create embed handle ──
|
|
132
132
|
try {
|
|
133
|
-
this.
|
|
133
|
+
this.vectorsHandle = this.fn_vectors_create();
|
|
134
134
|
} catch (e: any) {
|
|
135
|
-
this.error = `
|
|
135
|
+
this.error = `sulcus_vectors_create failed: ${e.message}`;
|
|
136
136
|
logger.warn(`memory-sulcus: ${this.error}`);
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
this.loaded = true;
|
|
141
|
-
logger.info(`memory-sulcus: native libs loaded (store: ${this.storeLibPath},
|
|
141
|
+
logger.info(`memory-sulcus: native libs loaded (store: ${this.storeLibPath}, vectors: ${this.vectorsLibPath})`);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
// queryFn: async (sql: string, params: any[]) => any[]
|
|
@@ -163,12 +163,12 @@ class NativeLibLoader {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
// embedFn: async (text: string) => Float32Array
|
|
166
|
-
// Calls
|
|
166
|
+
// Calls sulcus_vectors_text which returns a JSON float array string.
|
|
167
167
|
makeEmbedFn(): (text: string) => Promise<Float32Array> {
|
|
168
168
|
return async (text: string): Promise<Float32Array> => {
|
|
169
|
-
if (!this.loaded) throw new Error("Sulcus
|
|
170
|
-
const raw: string = this.
|
|
171
|
-
if (!raw) throw new Error("
|
|
169
|
+
if (!this.loaded) throw new Error("Sulcus vectors not available");
|
|
170
|
+
const raw: string = this.fn_vectors_text(this.vectorsHandle, text);
|
|
171
|
+
if (!raw) throw new Error("sulcus_vectors_text returned null");
|
|
172
172
|
const arr: number[] = JSON.parse(raw);
|
|
173
173
|
return new Float32Array(arr);
|
|
174
174
|
};
|
|
@@ -220,9 +220,9 @@ const sulcusPlugin = {
|
|
|
220
220
|
? resolve(api.config.storeLibPath)
|
|
221
221
|
: resolve(libDir, process.platform === "darwin" ? "libsulcus_store.dylib" : "libsulcus_store.so");
|
|
222
222
|
|
|
223
|
-
const
|
|
224
|
-
? resolve(api.config.
|
|
225
|
-
: resolve(libDir, process.platform === "darwin" ? "
|
|
223
|
+
const vectorsLibPath = api.config?.vectorsLibPath
|
|
224
|
+
? resolve(api.config.vectorsLibPath)
|
|
225
|
+
: resolve(libDir, process.platform === "darwin" ? "libsulcus_vectors.dylib" : "libsulcus_vectors.so");
|
|
226
226
|
|
|
227
227
|
const wasmDir = api.config?.wasmDir
|
|
228
228
|
? resolve(api.config.wasmDir)
|
|
@@ -235,7 +235,7 @@ const sulcusPlugin = {
|
|
|
235
235
|
: (api.config?.namespace || agentId || "default");
|
|
236
236
|
|
|
237
237
|
// ── Load native dylibs ──
|
|
238
|
-
const nativeLoader = new NativeLibLoader(storeLibPath,
|
|
238
|
+
const nativeLoader = new NativeLibLoader(storeLibPath, vectorsLibPath);
|
|
239
239
|
nativeLoader.init(api.logger);
|
|
240
240
|
|
|
241
241
|
// ── Load WASM module ──
|
|
@@ -288,7 +288,7 @@ const sulcusPlugin = {
|
|
|
288
288
|
throw new Error(`Sulcus unavailable: ${nativeLoader.error || "WASM not loaded"}`);
|
|
289
289
|
}
|
|
290
290
|
const res = await sulcusMem.search_memory(params.query, params.limit ?? 5);
|
|
291
|
-
const results = res?.results ?? res;
|
|
291
|
+
const results = res?.results ?? res?.items ?? res?.nodes ?? res ?? [];
|
|
292
292
|
return {
|
|
293
293
|
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
294
294
|
details: { results, backend: backendMode, namespace }
|
|
@@ -325,9 +325,11 @@ const sulcusPlugin = {
|
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
const res = await sulcusMem.add_memory(params.content, params.memory_type ?? null);
|
|
328
|
+
const nodeId = res?.id ?? "unknown";
|
|
329
|
+
const mtype = params.memory_type || "episodic";
|
|
328
330
|
return {
|
|
329
|
-
content: [{ type: "text", text: `Stored [${
|
|
330
|
-
details: {
|
|
331
|
+
content: [{ type: "text", text: `Stored [${mtype}] memory (id: ${nodeId}) → backend: ${backendMode}, namespace: ${namespace}` }],
|
|
332
|
+
details: { id: nodeId, memory_type: mtype, backend: backendMode, namespace, ...res }
|
|
331
333
|
};
|
|
332
334
|
}
|
|
333
335
|
}, { name: "memory_store" });
|
|
@@ -346,7 +348,7 @@ const sulcusPlugin = {
|
|
|
346
348
|
namespace,
|
|
347
349
|
error: nativeLoader.error || "WASM not loaded",
|
|
348
350
|
storeLib: storeLibPath,
|
|
349
|
-
|
|
351
|
+
vectorsLib: vectorsLibPath,
|
|
350
352
|
wasmDir,
|
|
351
353
|
}, null, 2) }],
|
|
352
354
|
};
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"id": "openclaw-sulcus",
|
|
3
3
|
"kind": "memory",
|
|
4
4
|
"name": "Sulcus",
|
|
5
|
-
"description": "Reactive, thermodynamic memory for AI agents. Runs entirely local via WASM + native dylibs (embedded PostgreSQL,
|
|
5
|
+
"description": "Reactive, thermodynamic memory for AI agents. Runs entirely local via WASM + native dylibs (embedded PostgreSQL, sulcus-vectors). No network calls. Optional cloud sync via sulcus-sync dylib when serverUrl/apiKey are configured.",
|
|
6
6
|
"privacy": {
|
|
7
7
|
"consentModel": "local-first",
|
|
8
|
-
"consentNote": "Plugin runs entirely in-process. No network calls. All data stays local in ~/.sulcus/. Cloud sync is opt-in via serverUrl/apiKey config, handled by sulcus-sync dylib loaded by sulcus
|
|
8
|
+
"consentNote": "Plugin runs entirely in-process. No network calls. All data stays local in ~/.sulcus/. Cloud sync is opt-in via serverUrl/apiKey config, handled by sulcus-sync dylib loaded by sulcus.",
|
|
9
9
|
"crossNamespaceAccess": "local-only",
|
|
10
10
|
"crossNamespaceNote": "All memory operations are local. Cross-namespace access only applies if cloud sync is configured separately.",
|
|
11
11
|
"dataFlows": [
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"libDir": {
|
|
36
36
|
"type": "string",
|
|
37
|
-
"description": "Directory containing native dylibs (libsulcus_store,
|
|
37
|
+
"description": "Directory containing native dylibs (libsulcus_store, libsulcus_vectors)",
|
|
38
38
|
"default": "~/.sulcus/lib"
|
|
39
39
|
},
|
|
40
40
|
"wasmDir": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@digitalforgestudios/openclaw-sulcus",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Sulcus — reactive, thermodynamic memory plugin for OpenClaw. Opt-in persistent memory with heat-based decay, semantic search, and cross-agent sync. Auto-recall and auto-capture disabled by default.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openclaw",
|