@digitalforgestudios/openclaw-sulcus 2.0.0 → 3.1.0

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 CHANGED
@@ -1,6 +1,5 @@
1
- import { spawn, ChildProcess } from "node:child_process";
2
- import { createInterface } from "node:readline";
3
1
  import { resolve } from "node:path";
2
+ import { existsSync } from "node:fs";
4
3
  import { Type } from "@sinclair/typebox";
5
4
 
6
5
  // ─── STATIC AWARENESS ───────────────────────────────────────────────────────
@@ -8,154 +7,174 @@ import { Type } from "@sinclair/typebox";
8
7
  // This is the absolute minimum the LLM needs to know Sulcus exists.
9
8
  // It fires even if build_context crashes, times out, or returns empty.
10
9
  // Build static awareness with runtime backend info
11
- function buildStaticAwareness(backendMode: string, namespace: string, serverUrl: string) {
10
+ function buildStaticAwareness(backendMode: string, namespace: string) {
12
11
  return `## Persistent Memory (Sulcus)
13
12
  You have Sulcus — a persistent, reactive, thermodynamic memory system with reactive triggers.
14
13
  Memories survive across sessions. They have heat (0.0–1.0) that decays over time.
15
14
 
16
- **Connection:** Backend: ${backendMode} | Namespace: ${namespace} | Server: ${serverUrl}
15
+ **Connection:** Backend: ${backendMode} | Namespace: ${namespace}
17
16
 
18
17
  **Your memory tools:**
19
18
  - \`memory_store\` — Save important information (preferences, facts, procedures, decisions, lessons)
20
- Parameters: content, memory_type (episodic|semantic|preference|procedural|fact), decay_class (volatile|normal|stable|permanent), is_pinned, min_heat, key_points
19
+ Parameters: content, memory_type (episodic|semantic|preference|procedural|fact)
21
20
  - \`memory_recall\` — Search memories semantically. Use before answering about past work, decisions, or people.
22
21
  Parameters: query, limit
23
22
 
24
23
  **When to store:** User states a preference, important decision made, correction given, lesson learned, anything worth surviving this session.
25
24
  **When to search:** Questions about prior work/decisions, context seems incomplete, user references past conversations.
26
25
 
27
- **Memory types:** episodic (events, fast decay) · semantic (knowledge, slow) · preference (opinions, slower) · procedural (how-tos, slowest) · fact (data, slow)
28
- **Decay classes:** volatile (hours) · normal (days) · stable (weeks) · permanent (never)
29
- **Pinning:** is_pinned=true prevents decay. Use for critical knowledge.
30
- **Triggers:** Reactive rules on memory events. Active triggers and recent fires appear in your context below.`;
26
+ **Memory types:** episodic (events, fast decay) · semantic (knowledge, slow) · preference (opinions, slower) · procedural (how-tos, slowest) · fact (data, slow)`;
31
27
  }
32
28
 
33
29
  // Legacy static string for backward compat (overwritten at register time)
34
- let STATIC_AWARENESS = buildStaticAwareness("local", "default", "local");
30
+ let STATIC_AWARENESS = buildStaticAwareness("local", "default");
35
31
 
36
32
  // Fallback context when build_context fails — includes the cheatsheet
37
33
  // but warns that dynamic context is unavailable.
38
34
  const FALLBACK_AWARENESS = `<sulcus_context token_budget="500">
39
35
  <cheatsheet>
40
36
  You have Sulcus — persistent memory with reactive triggers.
41
- STORE: memory_store (content, memory_type, decay_class, is_pinned, key_points)
37
+ STORE: memory_store (content, memory_type)
42
38
  FIND: memory_recall (query, limit)
43
- MANAGE: memory_boost / memory_deprecate / memory_relate / memory_reclassify
44
- PIN: Set is_pinned=true to make a memory permanent (immune to decay).
45
- TRIGGERS: create_trigger to set reactive rules on your memory graph
46
39
  TYPES: episodic (fast fade), semantic (slow), preference, procedural (slowest), fact
47
40
  ⚠️ Context build failed this turn — use memory_recall to search manually.
48
41
  Below is your active context. Search for deeper recall. Unlimited storage.
49
42
  </cheatsheet>
50
43
  </sulcus_context>`;
51
44
 
52
- // MCP-over-stdio client for sulcus-local
53
- // This is the ONLY communication path — no REST/network calls in this plugin.
54
- // If serverUrl/apiKey are configured, they are passed as env vars to the spawn()
55
- // call below so that sulcus-sync (a dylib loaded by sulcus-local) can pick them
56
- // up for cloud replication. The plugin itself never makes any HTTP calls.
57
- class SulcusClient {
58
- private child: ChildProcess | null = null;
59
- private nextId = 1;
60
- private pending = new Map<string | number, (res: any) => void>();
61
- private configPath: string | undefined;
62
- private spawnEnv: Record<string, string>;
45
+ // ─── NATIVE LIB LOADER ──────────────────────────────────────────────────────
46
+ // Loads libsulcus_store.dylib (embedded PG) and libsulcus_vectors.dylib (embeddings)
47
+ // via koffi FFI. Provides queryFn and embedFn callbacks for SulcusMem.create().
48
+
49
+ class NativeLibLoader {
50
+ private koffi: any = null;
51
+ private storeLib: any = null;
52
+ private vectorsLib: any = null;
53
+ private vectorsHandle: any = null;
54
+
55
+ // koffi function handles
56
+ private fn_store_init: any = null;
57
+ private fn_store_query: any = null;
58
+ private fn_store_free: any = null;
59
+ private fn_vectors_create: any = null;
60
+ private fn_vectors_text: any = null;
61
+ private fn_vectors_free: any = null;
62
+
63
+ public loaded = false;
64
+ public error: string | null = null;
63
65
 
64
66
  constructor(
65
- private binaryPath: string,
66
- configPath?: string,
67
- spawnEnv: Record<string, string> = {}
68
- ) {
69
- this.configPath = configPath;
70
- this.spawnEnv = spawnEnv;
71
- }
67
+ private storeLibPath: string,
68
+ private vectorsLibPath: string
69
+ ) {}
72
70
 
73
- // Launches sulcus-local binary as an MCP stdio sidecar.
74
- // serverUrl/apiKey (if any) are passed via environment variables so that
75
- // sulcus-sync (dylib) can perform cloud replication without this plugin
76
- // making any network calls itself.
77
- async start(configPath?: string) {
78
- const cfgPath = configPath || this.configPath;
79
- const args = cfgPath ? ["--config", cfgPath, "stdio"] : ["stdio"];
80
- this.child = spawn(this.binaryPath, args, {
81
- stdio: ["pipe", "pipe", "inherit"],
82
- env: {
83
- ...process.env,
84
- RUST_LOG: "info",
85
- ...this.spawnEnv, // SULCUS_SERVER_URL and SULCUS_API_KEY forwarded here
86
- }
87
- });
71
+ init(logger: any): void {
72
+ try {
73
+ // koffi is a pure-JS FFI library no native compilation needed
74
+ this.koffi = require("koffi");
75
+ } catch (e: any) {
76
+ this.error = `koffi not available: ${e.message}`;
77
+ logger.warn(`memory-sulcus: ${this.error}`);
78
+ return;
79
+ }
88
80
 
89
- this.child.on("error", (err) => {
90
- // Reject all pending calls if the process dies
91
- for (const [_id, resolve] of this.pending) {
92
- resolve({ error: { code: -1, message: `Sulcus process error: ${err.message}` } });
93
- }
94
- this.pending.clear();
95
- this.child = null;
96
- });
81
+ // ── Load libsulcus_store.dylib ──
82
+ if (!existsSync(this.storeLibPath)) {
83
+ this.error = `libsulcus_store not found at ${this.storeLibPath}`;
84
+ logger.warn(`memory-sulcus: ${this.error}`);
85
+ return;
86
+ }
87
+ if (!existsSync(this.vectorsLibPath)) {
88
+ this.error = `libsulcus_vectors not found at ${this.vectorsLibPath}`;
89
+ logger.warn(`memory-sulcus: ${this.error}`);
90
+ return;
91
+ }
92
+
93
+ try {
94
+ this.storeLib = this.koffi.load(this.storeLibPath);
95
+ this.fn_store_init = this.storeLib.func("sulcus_store_init", "int", ["str", "uint16"]);
96
+ this.fn_store_query = this.storeLib.func("sulcus_store_query", "char*", ["str"]);
97
+ this.fn_store_free = this.storeLib.func("sulcus_store_free_string", "void", ["char*"]);
98
+ } catch (e: any) {
99
+ this.error = `Failed to load libsulcus_store: ${e.message}`;
100
+ logger.warn(`memory-sulcus: ${this.error}`);
101
+ return;
102
+ }
97
103
 
98
- this.child.on("exit", (code) => {
99
- for (const [_id, resolve] of this.pending) {
100
- resolve({ error: { code: -1, message: `Sulcus process exited with code ${code}` } });
104
+ try {
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
+ } catch (e: any) {
110
+ this.error = `Failed to load libsulcus_vectors: ${e.message}`;
111
+ logger.warn(`memory-sulcus: ${this.error}`);
112
+ return;
113
+ }
114
+
115
+ // ── Initialise embedded PG store ──
116
+ try {
117
+ const dataDir = resolve(process.env.HOME || "~", ".sulcus/data");
118
+ const port = 15432;
119
+ const rc = this.fn_store_init(dataDir, port);
120
+ if (rc !== 0) {
121
+ this.error = `sulcus_store_init returned ${rc}`;
122
+ logger.warn(`memory-sulcus: ${this.error}`);
123
+ return;
101
124
  }
102
- this.pending.clear();
103
- this.child = null;
104
- });
125
+ } catch (e: any) {
126
+ this.error = `sulcus_store_init failed: ${e.message}`;
127
+ logger.warn(`memory-sulcus: ${this.error}`);
128
+ return;
129
+ }
105
130
 
106
- const rl = createInterface({ input: this.child.stdout! });
107
- rl.on("line", (line) => {
108
- try {
109
- const msg = JSON.parse(line);
110
- if (msg.id && this.pending.has(msg.id)) {
111
- const resolve = this.pending.get(msg.id)!;
112
- this.pending.delete(msg.id);
113
- resolve(msg);
114
- }
115
- } catch (e) {}
116
- });
131
+ // ── Create embed handle ──
132
+ try {
133
+ this.vectorsHandle = this.fn_vectors_create();
134
+ } catch (e: any) {
135
+ this.error = `sulcus_vectors_create failed: ${e.message}`;
136
+ logger.warn(`memory-sulcus: ${this.error}`);
137
+ return;
138
+ }
139
+
140
+ this.loaded = true;
141
+ logger.info(`memory-sulcus: native libs loaded (store: ${this.storeLibPath}, vectors: ${this.vectorsLibPath})`);
117
142
  }
118
143
 
119
- async call(method: string, params: any = {}): Promise<any> {
120
- if (!this.child) await this.start();
121
- const id = this.nextId++;
122
- const request = { jsonrpc: "2.0", id, method: "tools/call", params: { name: method, arguments: params } };
123
-
124
- return new Promise((resolve, reject) => {
125
- const timeout = setTimeout(() => reject(new Error(`Sulcus timeout: ${method}`)), 30000);
126
- this.pending.set(id, (res) => {
127
- clearTimeout(timeout);
128
- if (res.error) reject(new Error(res.error.message));
129
- else {
130
- // MCP result format
131
- try {
132
- const content = JSON.parse(res.result.content[0].text);
133
- resolve(content);
134
- } catch (e) {
135
- resolve(res.result);
136
- }
137
- }
138
- });
139
- this.child!.stdin!.write(JSON.stringify(request) + "\n");
140
- });
144
+ // queryFn: async (sql: string, params: any[]) => any[]
145
+ // Calls sulcus_store_query which accepts plain SQL (params inlined by caller or
146
+ // passed as a JSON payload — the store lib handles parameterisation internally).
147
+ makeQueryFn(): (sql: string, params: any[]) => Promise<any[]> {
148
+ return async (sql: string, _params: any[]): Promise<any[]> => {
149
+ if (!this.loaded) throw new Error("Sulcus store not available");
150
+ // The store lib's query function takes a single JSON-encoded request
151
+ const payload = JSON.stringify({ sql, params: _params });
152
+ const raw: string = this.fn_store_query(payload);
153
+ if (!raw) return [];
154
+ try {
155
+ const parsed = JSON.parse(raw);
156
+ return Array.isArray(parsed) ? parsed : (parsed?.rows ?? [parsed]);
157
+ } finally {
158
+ // NOTE: koffi manages string memory automatically for char* returns;
159
+ // no manual free needed with koffi's default charset handling.
160
+ // If the ABI requires explicit free, call fn_store_free here.
161
+ }
162
+ };
141
163
  }
142
164
 
143
- stop() {
144
- if (this.child) this.child.kill();
165
+ // embedFn: async (text: string) => Float32Array
166
+ // Calls sulcus_vectors_text which returns a JSON float array string.
167
+ makeEmbedFn(): (text: string) => Promise<Float32Array> {
168
+ return async (text: string): Promise<Float32Array> => {
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
+ const arr: number[] = JSON.parse(raw);
173
+ return new Float32Array(arr);
174
+ };
145
175
  }
146
176
  }
147
177
 
148
- // NOTE: SulcusRestClient was removed — the plugin no longer makes any HTTP/REST
149
- // calls. All communication with sulcus-local goes through MCP over stdio via
150
- // SulcusClient above. If serverUrl/apiKey are provided in config, they are
151
- // forwarded as SULCUS_SERVER_URL / SULCUS_API_KEY environment variables to the
152
- // sulcus-local spawn so that sulcus-sync (dylib) can replicate to the cloud
153
- // without this plugin touching the network.
154
-
155
- // NOTE: SiuClassifier / ClientSiu / SiuModel were removed — sulcus-local
156
- // already has fastembed + ONNX for embeddings and handles classification
157
- // internally. A future PR may expose classification via an MCP tool if needed.
158
-
159
178
  // ─── PRE-SEND FILTER ─────────────────────────────────────────────────────────
160
179
  // Rule-based junk filter. Catches obvious noise before it hits the API.
161
180
 
@@ -176,65 +195,83 @@ const JUNK_PATTERNS = [
176
195
 
177
196
  function isJunkMemory(text: string): boolean {
178
197
  if (!text || text.length < 10) return true;
179
- if (text.length > 10000) return true; // probably a raw dump
198
+ if (text.length > 10000) return true;
180
199
  for (const pattern of JUNK_PATTERNS) {
181
200
  if (pattern.test(text.trim())) return true;
182
201
  }
183
202
  return false;
184
203
  }
185
204
 
205
+ // ─── PLUGIN ──────────────────────────────────────────────────────────────────
206
+
186
207
  const sulcusPlugin = {
187
208
  id: "memory-sulcus",
188
209
  name: "Sulcus vMMU",
189
- description: "Sulcus-backed vMMU memory for OpenClaw — thermodynamic decay, reactive triggers, local-first with cloud sync",
210
+ description: "Sulcus-backed vMMU memory for OpenClaw — thermodynamic decay, reactive triggers, local-first",
190
211
  kind: "memory" as const,
191
212
 
192
213
  register(api: any) {
193
- const binaryPath = api.config?.binaryPath || "/Users/dv00003-00/dev/sulcus/target/release/sulcus-local";
194
- const iniPath = api.config?.iniPath || resolve(process.env.HOME || "~", ".config/sulcus/sulcus.ini");
214
+ // ── Configuration ──
215
+ const libDir = api.config?.libDir
216
+ ? resolve(api.config.libDir)
217
+ : resolve(process.env.HOME || "~", ".sulcus/lib");
218
+
219
+ const storeLibPath = api.config?.storeLibPath
220
+ ? resolve(api.config.storeLibPath)
221
+ : resolve(libDir, process.platform === "darwin" ? "libsulcus_store.dylib" : "libsulcus_store.so");
222
+
223
+ const vectorsLibPath = api.config?.vectorsLibPath
224
+ ? resolve(api.config.vectorsLibPath)
225
+ : resolve(libDir, process.platform === "darwin" ? "libsulcus_vectors.dylib" : "libsulcus_vectors.so");
226
+
227
+ const wasmDir = api.config?.wasmDir
228
+ ? resolve(api.config.wasmDir)
229
+ : resolve(__dirname, "wasm");
230
+
195
231
  // Default namespace = agent name (prevents everything landing in "default")
196
- // Priority: explicit namespace config > agentId config > pluginConfig.agentId > "default"
197
232
  const agentId = api.config?.agentId || api.pluginConfig?.agentId;
198
233
  const namespace = api.config?.namespace === "default" && agentId
199
234
  ? agentId
200
235
  : (api.config?.namespace || agentId || "default");
201
236
 
202
- // serverUrl/apiKey are NOT used by this plugin for HTTP calls.
203
- // They are forwarded as env vars to sulcus-local so sulcus-sync (dylib)
204
- // can replicate memories to the cloud without any network calls here.
205
- const serverUrl = api.config?.serverUrl || "";
206
- const apiKey = api.config?.apiKey || "";
207
-
208
- // Build spawn env: pass cloud config to sulcus-local via environment
209
- const spawnEnv: Record<string, string> = {};
210
- if (serverUrl) spawnEnv["SULCUS_SERVER_URL"] = serverUrl;
211
- if (apiKey) spawnEnv["SULCUS_API_KEY"] = apiKey;
237
+ // ── Load native dylibs ──
238
+ const nativeLoader = new NativeLibLoader(storeLibPath, vectorsLibPath);
239
+ nativeLoader.init(api.logger);
212
240
 
213
- const client = new SulcusClient(binaryPath, iniPath, spawnEnv);
241
+ // ── Load WASM module ──
242
+ let sulcusMem: any = null;
243
+ let backendMode = "unavailable";
214
244
 
215
- // Check binary availability
216
- let hasBinary = false;
217
- try {
218
- const { existsSync } = require("node:fs");
219
- hasBinary = existsSync(binaryPath);
220
- } catch { }
221
-
222
- const backendMode = hasBinary ? "local" : "unavailable";
223
-
224
- // All memory calls go exclusively through MCP — no REST fallback.
225
- // If the MCP client isn't connected (binary missing or crashed), we return
226
- // an error rather than falling back to HTTP.
227
- async function memoryCall(method: string, params: any): Promise<any> {
228
- if (!hasBinary) {
229
- throw new Error(`Sulcus unavailable: binary not found at ${binaryPath}`);
245
+ if (nativeLoader.loaded) {
246
+ const wasmJsPath = resolve(wasmDir, "sulcus_wasm.js");
247
+ if (existsSync(wasmJsPath)) {
248
+ try {
249
+ const { SulcusMem, on_init } = require(wasmJsPath);
250
+ // on_init sets up WASM internals (panic hooks etc.)
251
+ if (typeof on_init === "function") on_init();
252
+
253
+ const queryFn = nativeLoader.makeQueryFn();
254
+ const embedFn = nativeLoader.makeEmbedFn();
255
+ sulcusMem = SulcusMem.create(queryFn, embedFn);
256
+ backendMode = "wasm";
257
+ api.logger.info(`memory-sulcus: SulcusMem created via WASM (wasm: ${wasmJsPath})`);
258
+ } catch (e: any) {
259
+ api.logger.warn(`memory-sulcus: WASM load failed: ${e.message}`);
260
+ backendMode = "unavailable";
261
+ }
262
+ } else {
263
+ api.logger.warn(`memory-sulcus: WASM module not found at ${wasmJsPath}`);
230
264
  }
231
- return client.call(method, params);
265
+ } else {
266
+ api.logger.warn(`memory-sulcus: native libs unavailable — ${nativeLoader.error}`);
232
267
  }
233
268
 
269
+ const isAvailable = sulcusMem !== null;
270
+
234
271
  // Update static awareness with runtime info
235
- STATIC_AWARENESS = buildStaticAwareness(backendMode, namespace, serverUrl || "local");
272
+ STATIC_AWARENESS = buildStaticAwareness(backendMode, namespace);
236
273
 
237
- api.logger.info(`memory-sulcus: registered (binary: ${binaryPath}, hasBinary: ${hasBinary}, namespace: ${namespace}, backend: ${backendMode})`);
274
+ api.logger.info(`memory-sulcus: registered (backend: ${backendMode}, namespace: ${namespace}, available: ${isAvailable})`);
238
275
 
239
276
  // ── Core memory tools ──
240
277
 
@@ -247,12 +284,14 @@ const sulcusPlugin = {
247
284
  limit: Type.Optional(Type.Number({ default: 5, description: "Maximum number of results to return (1-10)." }))
248
285
  }),
249
286
  async execute(_id: string, params: any) {
250
- const res = await memoryCall("search_memory", { query: params.query, limit: params.limit });
251
- const results = res?.results || res;
252
- const provenance = res?.provenance || { backend: backendMode, namespace };
287
+ if (!isAvailable) {
288
+ throw new Error(`Sulcus unavailable: ${nativeLoader.error || "WASM not loaded"}`);
289
+ }
290
+ const res = await sulcusMem.search_memory(params.query, params.limit ?? 5);
291
+ const results = res?.results ?? res;
253
292
  return {
254
293
  content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
255
- details: { ...res, provenance }
294
+ details: { results, backend: backendMode, namespace }
256
295
  };
257
296
  }
258
297
  }, { name: "memory_recall" });
@@ -260,10 +299,9 @@ const sulcusPlugin = {
260
299
  api.registerTool({
261
300
  name: "memory_store",
262
301
  label: "Memory Store",
263
- description: "Record information in Sulcus memory. Supports Markdown formatting. You control the memory type, decay rate, importance, and key details at creation time.",
302
+ description: "Record information in Sulcus memory. Supports Markdown formatting. You control the memory type at creation time.",
264
303
  parameters: Type.Object({
265
304
  content: Type.String({ description: "Memory content. Supports Markdown formatting for structured content." }),
266
- fold_name: Type.Optional(Type.String({ description: `Memory namespace/fold. Defaults to "${namespace}" (agent namespace).` })),
267
305
  memory_type: Type.Optional(Type.Union([
268
306
  Type.Literal("episodic"),
269
307
  Type.Literal("semantic"),
@@ -271,15 +309,6 @@ const sulcusPlugin = {
271
309
  Type.Literal("procedural"),
272
310
  Type.Literal("fact")
273
311
  ], { description: "Memory type. preference=user preferences, procedural=how-to/processes, fact=stable knowledge, semantic=concepts/relationships, episodic=events/experiences. Default: episodic" })),
274
- decay_class: Type.Optional(Type.Union([
275
- Type.Literal("volatile"),
276
- Type.Literal("normal"),
277
- Type.Literal("stable"),
278
- Type.Literal("permanent")
279
- ], { description: "Decay rate. volatile=fast decay, normal=default, stable=slow decay, permanent=never decays" })),
280
- is_pinned: Type.Optional(Type.Boolean({ description: "Pin memory to freeze heat at current value, preventing ALL decay. Pinned memories never lose heat." })),
281
- min_heat: Type.Optional(Type.Number({ description: "Minimum heat floor (0.0-1.0). Memory will never decay below this value." })),
282
- key_points: Type.Optional(Type.Array(Type.String(), { description: "Key points to index for search. Extracted highlights." }))
283
312
  }),
284
313
  async execute(_id: string, params: any) {
285
314
  // Pre-send junk filter
@@ -291,25 +320,14 @@ const sulcusPlugin = {
291
320
  };
292
321
  }
293
322
 
294
- const res = await memoryCall("record_memory", { ...params, namespace, fold_name: params.fold_name || namespace });
295
- // Check for storage limit error
296
- if (res?.error === "storage_limit_reached") {
297
- return {
298
- content: [{ type: "text", text: `⚠️ Storage limit reached: ${res.message}` }],
299
- details: res
300
- };
323
+ if (!isAvailable) {
324
+ throw new Error(`Sulcus unavailable: ${nativeLoader.error || "WASM not loaded"}`);
301
325
  }
302
- const provenance = res?.provenance || {
303
- backend: backendMode,
304
- namespace,
305
- server: serverUrl || "local",
306
- sync_available: !!serverUrl,
307
- siu_classified: false,
308
- };
309
- const provenanceStr = `[${provenance.backend}] namespace: ${provenance.namespace || namespace}, server: ${provenance.server || "local"}`;
326
+
327
+ const res = await sulcusMem.add_memory(params.content, params.memory_type ?? null);
310
328
  return {
311
- content: [{ type: "text", text: `Stored [${params.memory_type || "episodic"}] memory: "${(params.content || "").substring(0, 80)}..." → ${provenanceStr}` }],
312
- details: { ...res, provenance }
329
+ content: [{ type: "text", text: `Stored [${params.memory_type || "episodic"}] memory: "${(params.content || "").substring(0, 80)}..." → [${backendMode}] namespace: ${namespace}` }],
330
+ details: { ...res, backend: backendMode, namespace }
313
331
  };
314
332
  }
315
333
  }, { name: "memory_store" });
@@ -317,26 +335,35 @@ const sulcusPlugin = {
317
335
  api.registerTool({
318
336
  name: "memory_status",
319
337
  label: "Memory Status",
320
- description: "Check Sulcus memory backend status: connection, namespace, capabilities, and memory count.",
338
+ description: "Check Sulcus memory backend status: connection, namespace, capabilities, and hot nodes.",
321
339
  parameters: Type.Object({}),
322
340
  async execute(_id: string, _params: any) {
323
- if (!hasBinary) {
341
+ if (!isAvailable) {
324
342
  return {
325
343
  content: [{ type: "text", text: JSON.stringify({
326
344
  status: "unavailable",
327
345
  backend: backendMode,
328
346
  namespace,
329
- binary: binaryPath,
330
- server: serverUrl || "none (env forwarding only)",
347
+ error: nativeLoader.error || "WASM not loaded",
348
+ storeLib: storeLibPath,
349
+ vectorsLib: vectorsLibPath,
350
+ wasmDir,
331
351
  }, null, 2) }],
332
352
  };
333
353
  }
334
354
  try {
335
- // Ask sulcus-local for status via MCP
336
- const status = await memoryCall("memory_status", {});
355
+ const hotNodes = await sulcusMem.list_hot_nodes(20);
356
+ const nodeList = hotNodes?.nodes ?? hotNodes ?? [];
357
+ const count = Array.isArray(nodeList) ? nodeList.length : 0;
337
358
  return {
338
- content: [{ type: "text", text: JSON.stringify(status, null, 2) }],
339
- details: status
359
+ content: [{ type: "text", text: JSON.stringify({
360
+ status: "ok",
361
+ backend: backendMode,
362
+ namespace,
363
+ hot_node_count: count,
364
+ hot_nodes: nodeList,
365
+ }, null, 2) }],
366
+ details: { status: "ok", backend: backendMode, namespace, count }
340
367
  };
341
368
  } catch (e: any) {
342
369
  return {
@@ -344,7 +371,6 @@ const sulcusPlugin = {
344
371
  status: "error",
345
372
  backend: backendMode,
346
373
  namespace,
347
- server: serverUrl || "none (env forwarding only)",
348
374
  error: e.message,
349
375
  }, null, 2) }],
350
376
  };
@@ -371,32 +397,23 @@ const sulcusPlugin = {
371
397
  api.logger.debug(`memory-sulcus: autoRecall is disabled, skipping context build`);
372
398
  return;
373
399
  }
400
+ if (!isAvailable) return;
374
401
  api.logger.info(`memory-sulcus: before_agent_start hook triggered for agent ${event.agentId}`);
375
402
  if (!event.prompt) return;
376
403
  try {
377
- api.logger.debug(`memory-sulcus: building context for prompt: ${event.prompt.substring(0, 50)}...`);
378
- // include_recent: false OpenClaw already has conversation context.
379
- // Only inject curated preferences, facts, and procedures from Sulcus.
380
- const res = await memoryCall("build_context", { prompt: event.prompt, token_budget: 2000, include_recent: false });
381
- // build_context returns either:
382
- // - plain XML string (new format, post-4dca467)
383
- // - { context: "...", token_estimate: N } (old format)
384
- // The MCP client resolves to the parsed JSON if valid, or the raw MCP result object.
385
- let context: string | undefined;
386
- if (typeof res === "string") {
387
- context = res;
388
- } else if (res?.context) {
389
- context = res.context;
390
- } else if (res?.content?.[0]?.text) {
391
- context = res.content[0].text;
404
+ api.logger.debug(`memory-sulcus: searching context for prompt: ${event.prompt.substring(0, 50)}...`);
405
+ const res = await sulcusMem.search_memory(event.prompt, 5);
406
+ const results = res?.results ?? [];
407
+ if (!results || results.length === 0) {
408
+ return { prependSystemContext: FALLBACK_AWARENESS };
392
409
  }
393
- if (context) {
394
- api.logger.info(`memory-sulcus: context build successful, injecting ${context.length} chars`);
395
- return { prependSystemContext: context };
396
- }
397
- // Context was empty inject fallback so LLM still knows about Sulcus
398
- api.logger.warn(`memory-sulcus: build_context returned empty, injecting fallback awareness`);
399
- return { prependSystemContext: FALLBACK_AWARENESS };
410
+ // Format results as a concise XML context block
411
+ const items = results.map((r: any) =>
412
+ ` <memory id="${r.id}" heat="${(r.current_heat ?? r.score ?? 0).toFixed(2)}" type="${r.memory_type ?? "unknown"}">${r.label ?? r.pointer_summary ?? ""}</memory>`
413
+ ).join("\n");
414
+ const context = `<sulcus_context token_budget="500" namespace="${namespace}">\n${items}\n</sulcus_context>`;
415
+ api.logger.info(`memory-sulcus: injecting ${results.length} recalled memories (${context.length} chars)`);
416
+ return { prependSystemContext: context };
400
417
  } catch (e) {
401
418
  // build_context failed — inject fallback so the LLM isn't flying blind
402
419
  api.logger.warn(`memory-sulcus: context build failed: ${e} — injecting fallback awareness`);
@@ -405,18 +422,12 @@ const sulcusPlugin = {
405
422
  });
406
423
 
407
424
  // agent_end: Do NOT auto-record raw conversation turns.
408
- // The LLM has record_memory as an MCP tool — it decides what's worth remembering.
409
- // Auto-recording every turn flooded the store with 2000+ junk episodic nodes
410
- // containing placeholder vectors and raw JSON conversation payloads.
425
+ // The LLM has memory_store as a tool — it decides what's worth remembering.
411
426
  api.on("agent_end", async (event: any) => {
412
427
  api.logger.debug(`memory-sulcus: agent_end hook triggered for agent ${event.agentId} (no auto-record)`);
413
428
  });
414
429
 
415
- api.registerService({
416
- id: "memory-sulcus",
417
- start: () => client.start(),
418
- stop: () => client.stop()
419
- });
430
+ // No service registration needed — no background process to manage
420
431
  }
421
432
  };
422
433
 
@@ -2,23 +2,23 @@
2
2
  "id": "openclaw-sulcus",
3
3
  "kind": "memory",
4
4
  "name": "Sulcus",
5
- "description": "Reactive, thermodynamic memory for AI agents. Persistent memory with heat-based decay, semantic search, triggers, and cross-agent sync. Connects to a user-configured Sulcus server (cloud or self-hosted). No data is sent unless the user provides an API key. Auto-recall and auto-capture are disabled by default; enable them explicitly in config.",
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
- "consentModel": "explicit-config",
8
- "consentNote": "Plugin requires a user-provided API key to function. Auto-recall and auto-capture are off by default. All automatic data transmission requires explicit opt-in via config fields.",
9
- "crossNamespaceAccess": "tenant-scoped",
10
- "crossNamespaceNote": "Agents within the same tenant can share memories across namespaces. Cross-namespace access is controlled by server-side ACL rules, not the plugin. Users configure ACL via the Sulcus dashboard.",
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-local.",
9
+ "crossNamespaceAccess": "local-only",
10
+ "crossNamespaceNote": "All memory operations are local. Cross-namespace access only applies if cloud sync is configured separately.",
11
11
  "dataFlows": [
12
12
  {
13
- "direction": "outbound",
14
- "destination": "User-configured Sulcus server (default: api.sulcus.ca, Canada)",
15
- "data": "Memory text content, search queries",
16
- "trigger": "User invokes memory_store/memory_search tools explicitly. Auto-recall and auto-capture are disabled by default and must be opted into via config.",
17
- "auth": "Bearer API key (user-provided, stored in plugin config)"
13
+ "direction": "local-only",
14
+ "destination": "Local embedded PostgreSQL (~/.sulcus/data/)",
15
+ "data": "Memory text content, embeddings, search queries",
16
+ "trigger": "User invokes memory_store/memory_recall tools. Auto-recall opt-in via config.",
17
+ "auth": "None all local"
18
18
  }
19
19
  ],
20
- "storage": "Memories stored on configured server, isolated by tenant and namespace. Self-hosted option available via sulcus-local.",
21
- "optOut": "autoRecall and autoCapture default to false. No automatic data transmission occurs unless the user explicitly enables these features. To disable after enabling, set them back to false."
20
+ "storage": "Memories stored locally in embedded PostgreSQL at ~/.sulcus/data/. Zero network by default.",
21
+ "optOut": "autoRecall defaults to false. No automatic context injection unless explicitly enabled."
22
22
  },
23
23
  "configSchema": {
24
24
  "type": "object",
@@ -26,12 +26,20 @@
26
26
  "properties": {
27
27
  "serverUrl": {
28
28
  "type": "string",
29
- "description": "Sulcus server URL",
30
- "default": "https://api.sulcus.ca"
29
+ "description": "Sulcus cloud server URL (for sulcus-sync replication). Passed as SULCUS_SERVER_URL env var. Leave empty for local-only."
31
30
  },
32
31
  "apiKey": {
33
32
  "type": "string",
34
- "description": "Sulcus API key (Bearer token). Required for any data transmission."
33
+ "description": "Sulcus API key (for sulcus-sync replication). Passed as SULCUS_API_KEY env var. Leave empty for local-only."
34
+ },
35
+ "libDir": {
36
+ "type": "string",
37
+ "description": "Directory containing native dylibs (libsulcus_store, libsulcus_vectors)",
38
+ "default": "~/.sulcus/lib"
39
+ },
40
+ "wasmDir": {
41
+ "type": "string",
42
+ "description": "Directory containing WASM module (sulcus_wasm.js, sulcus_wasm_bg.wasm). Defaults to plugin's wasm/ subdirectory."
35
43
  },
36
44
  "agentId": {
37
45
  "type": "string",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digitalforgestudios/openclaw-sulcus",
3
- "version": "2.0.0",
3
+ "version": "3.1.0",
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",
@@ -39,11 +39,13 @@
39
39
  },
40
40
  "files": [
41
41
  "index.ts",
42
+ "wasm/",
42
43
  "openclaw.plugin.json",
43
44
  "README.md"
44
45
  ],
45
46
  "dependencies": {
46
- "@sinclair/typebox": "^0.34.0"
47
+ "@sinclair/typebox": "^0.34.0",
48
+ "koffi": "^2.9.0"
47
49
  },
48
50
  "peerDependencies": {
49
51
  "openclaw": ">=2026.3.0"
@@ -0,0 +1,607 @@
1
+ /* @ts-self-types="./sulcus_wasm.d.ts" */
2
+
3
+ /**
4
+ * The main SULCUS memory handle. Create once; keep alive for the session.
5
+ */
6
+ class SulcusMem {
7
+ static __wrap(ptr) {
8
+ ptr = ptr >>> 0;
9
+ const obj = Object.create(SulcusMem.prototype);
10
+ obj.__wbg_ptr = ptr;
11
+ SulcusMemFinalization.register(obj, obj.__wbg_ptr, obj);
12
+ return obj;
13
+ }
14
+ __destroy_into_raw() {
15
+ const ptr = this.__wbg_ptr;
16
+ this.__wbg_ptr = 0;
17
+ SulcusMemFinalization.unregister(this);
18
+ return ptr;
19
+ }
20
+ free() {
21
+ const ptr = this.__destroy_into_raw();
22
+ wasm.__wbg_sulcusmem_free(ptr, 0);
23
+ }
24
+ /**
25
+ * Record a new image memory.
26
+ *
27
+ * @param label Human-readable label for the image (optional).
28
+ * @param bitmap The raw image bytes (Uint8Array).
29
+ * @param mime MIME type (e.g., "image/png").
30
+ * @param namespace Optional: partition memory by namespace.
31
+ * @returns `{ id: string, status: "added" }`
32
+ * @param {string | null | undefined} label
33
+ * @param {Uint8Array} bitmap
34
+ * @param {string} mime
35
+ * @param {string | null} [namespace]
36
+ * @returns {Promise<any>}
37
+ */
38
+ add_image_memory(label, bitmap, mime, namespace) {
39
+ var ptr0 = isLikeNone(label) ? 0 : passStringToWasm0(label, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
40
+ var len0 = WASM_VECTOR_LEN;
41
+ const ptr1 = passArray8ToWasm0(bitmap, wasm.__wbindgen_malloc);
42
+ const len1 = WASM_VECTOR_LEN;
43
+ const ptr2 = passStringToWasm0(mime, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
44
+ const len2 = WASM_VECTOR_LEN;
45
+ var ptr3 = isLikeNone(namespace) ? 0 : passStringToWasm0(namespace, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
46
+ var len3 = WASM_VECTOR_LEN;
47
+ const ret = wasm.sulcusmem_add_image_memory(this.__wbg_ptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3);
48
+ return ret;
49
+ }
50
+ /**
51
+ * Record a new memory.
52
+ *
53
+ * @param text The raw text to remember.
54
+ * @param memory_type Optional: "episodic" | "semantic" | "preference" | "procedural".
55
+ * @returns `{ id: string, status: "added" }`
56
+ * @param {string} text
57
+ * @param {string | null} [memory_type]
58
+ * @returns {Promise<any>}
59
+ */
60
+ add_memory(text, memory_type) {
61
+ const ptr0 = passStringToWasm0(text, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
62
+ const len0 = WASM_VECTOR_LEN;
63
+ var ptr1 = isLikeNone(memory_type) ? 0 : passStringToWasm0(memory_type, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
64
+ var len1 = WASM_VECTOR_LEN;
65
+ const ret = wasm.sulcusmem_add_memory(this.__wbg_ptr, ptr0, len0, ptr1, len1);
66
+ return ret;
67
+ }
68
+ /**
69
+ * Create a new `SulcusMem` instance.
70
+ *
71
+ * @param query_fn `async (sql: string, params: any[]) => any[]`
72
+ * @param embed_fn `async (text: string) => Float32Array`
73
+ * @param embed_image_fn Optional: `async (bitmap: Uint8Array) => Float32Array`
74
+ * @param {Function} query_fn
75
+ * @param {Function} embed_fn
76
+ * @param {Function | null} [embed_image_fn]
77
+ * @returns {SulcusMem}
78
+ */
79
+ static create(query_fn, embed_fn, embed_image_fn) {
80
+ const ret = wasm.sulcusmem_create(query_fn, embed_fn, isLikeNone(embed_image_fn) ? 0 : addToExternrefTable0(embed_image_fn));
81
+ return SulcusMem.__wrap(ret);
82
+ }
83
+ /**
84
+ * List nodes ordered by current_heat DESC.
85
+ *
86
+ * @param limit Max nodes to return (default 20).
87
+ * @returns `{ nodes: Array<{ id, label, pointer_summary, current_heat, memory_type }> }`
88
+ * @param {number | null} [limit]
89
+ * @returns {Promise<any>}
90
+ */
91
+ list_hot_nodes(limit) {
92
+ const ret = wasm.sulcusmem_list_hot_nodes(this.__wbg_ptr, isLikeNone(limit) ? 0x100000001 : (limit) >>> 0);
93
+ return ret;
94
+ }
95
+ /**
96
+ * Search for similar memories using an image as query (CLIP).
97
+ *
98
+ * @param bitmap The raw image bytes (Uint8Array).
99
+ * @param limit Max results (default 10).
100
+ * @returns `{ results: Array<{ id, label, pointer_summary, score }> }`
101
+ * @param {Uint8Array} bitmap
102
+ * @param {number | null} [limit]
103
+ * @returns {Promise<any>}
104
+ */
105
+ search_by_image(bitmap, limit) {
106
+ const ptr0 = passArray8ToWasm0(bitmap, wasm.__wbindgen_malloc);
107
+ const len0 = WASM_VECTOR_LEN;
108
+ const ret = wasm.sulcusmem_search_by_image(this.__wbg_ptr, ptr0, len0, isLikeNone(limit) ? 0x100000001 : (limit) >>> 0);
109
+ return ret;
110
+ }
111
+ /**
112
+ * Hybrid FTS + cosine similarity search using native pgvector operators.
113
+ *
114
+ * @param query Natural language query.
115
+ * @param limit Max results (default 10).
116
+ * @returns `{ results: Array<{ id, label, pointer_summary, score }> }`
117
+ * @param {string} query
118
+ * @param {number | null} [limit]
119
+ * @returns {Promise<any>}
120
+ */
121
+ search_memory(query, limit) {
122
+ const ptr0 = passStringToWasm0(query, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
123
+ const len0 = WASM_VECTOR_LEN;
124
+ const ret = wasm.sulcusmem_search_memory(this.__wbg_ptr, ptr0, len0, isLikeNone(limit) ? 0x100000001 : (limit) >>> 0);
125
+ return ret;
126
+ }
127
+ /**
128
+ * Run one thermodynamics cycle: decay all nodes, spread heat along edges,
129
+ * rebuild `active_index`.
130
+ *
131
+ * @param decay Heat decay factor per tick (default 0.85).
132
+ * @param spread Spreading activation weight (default 0.5).
133
+ * @param limit Max nodes kept in `active_index` (default 20).
134
+ * @returns `{ status: "tick_complete" }`
135
+ * @param {number} decay
136
+ * @param {number} spread
137
+ * @param {number} limit
138
+ * @returns {Promise<any>}
139
+ */
140
+ tick(decay, spread, limit) {
141
+ const ret = wasm.sulcusmem_tick(this.__wbg_ptr, decay, spread, limit);
142
+ return ret;
143
+ }
144
+ /**
145
+ * Run one thermodynamics cycle using the configurable ThermoConfig engine.
146
+ *
147
+ * @param config_json JSON string of ThermoConfig (or `null` for defaults).
148
+ * @returns `{ status: "tick_complete", engine: "thermo_v2", ... }`
149
+ * @param {string | null} [config_json]
150
+ * @returns {Promise<any>}
151
+ */
152
+ tick_v2(config_json) {
153
+ var ptr0 = isLikeNone(config_json) ? 0 : passStringToWasm0(config_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
154
+ var len0 = WASM_VECTOR_LEN;
155
+ const ret = wasm.sulcusmem_tick_v2(this.__wbg_ptr, ptr0, len0);
156
+ return ret;
157
+ }
158
+ }
159
+ if (Symbol.dispose) SulcusMem.prototype[Symbol.dispose] = SulcusMem.prototype.free;
160
+ exports.SulcusMem = SulcusMem;
161
+
162
+ function on_init() {
163
+ wasm.on_init();
164
+ }
165
+ exports.on_init = on_init;
166
+
167
+ function __wbg_get_imports() {
168
+ const import0 = {
169
+ __proto__: null,
170
+ __wbg___wbindgen_debug_string_0bc8482c6e3508ae: function(arg0, arg1) {
171
+ const ret = debugString(arg1);
172
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
173
+ const len1 = WASM_VECTOR_LEN;
174
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
175
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
176
+ },
177
+ __wbg___wbindgen_is_function_0095a73b8b156f76: function(arg0) {
178
+ const ret = typeof(arg0) === 'function';
179
+ return ret;
180
+ },
181
+ __wbg___wbindgen_is_undefined_9e4d92534c42d778: function(arg0) {
182
+ const ret = arg0 === undefined;
183
+ return ret;
184
+ },
185
+ __wbg___wbindgen_string_get_72fb696202c56729: function(arg0, arg1) {
186
+ const obj = arg1;
187
+ const ret = typeof(obj) === 'string' ? obj : undefined;
188
+ var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
189
+ var len1 = WASM_VECTOR_LEN;
190
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
191
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
192
+ },
193
+ __wbg___wbindgen_throw_be289d5034ed271b: function(arg0, arg1) {
194
+ throw new Error(getStringFromWasm0(arg0, arg1));
195
+ },
196
+ __wbg__wbg_cb_unref_d9b87ff7982e3b21: function(arg0) {
197
+ arg0._wbg_cb_unref();
198
+ },
199
+ __wbg_call_389efe28435a9388: function() { return handleError(function (arg0, arg1) {
200
+ const ret = arg0.call(arg1);
201
+ return ret;
202
+ }, arguments); },
203
+ __wbg_call_4708e0c13bdc8e95: function() { return handleError(function (arg0, arg1, arg2) {
204
+ const ret = arg0.call(arg1, arg2);
205
+ return ret;
206
+ }, arguments); },
207
+ __wbg_call_812d25f1510c13c8: function() { return handleError(function (arg0, arg1, arg2, arg3) {
208
+ const ret = arg0.call(arg1, arg2, arg3);
209
+ return ret;
210
+ }, arguments); },
211
+ __wbg_error_7534b8e9a36f1ab4: function(arg0, arg1) {
212
+ let deferred0_0;
213
+ let deferred0_1;
214
+ try {
215
+ deferred0_0 = arg0;
216
+ deferred0_1 = arg1;
217
+ console.error(getStringFromWasm0(arg0, arg1));
218
+ } finally {
219
+ wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
220
+ }
221
+ },
222
+ __wbg_getRandomValues_71d446877d8b0ad4: function() { return handleError(function (arg0, arg1) {
223
+ globalThis.crypto.getRandomValues(getArrayU8FromWasm0(arg0, arg1));
224
+ }, arguments); },
225
+ __wbg_instanceof_Float32Array_c882a172bf41d92a: function(arg0) {
226
+ let result;
227
+ try {
228
+ result = arg0 instanceof Float32Array;
229
+ } catch (_) {
230
+ result = false;
231
+ }
232
+ const ret = result;
233
+ return ret;
234
+ },
235
+ __wbg_instanceof_Promise_0094681e3519d6ec: function(arg0) {
236
+ let result;
237
+ try {
238
+ result = arg0 instanceof Promise;
239
+ } catch (_) {
240
+ result = false;
241
+ }
242
+ const ret = result;
243
+ return ret;
244
+ },
245
+ __wbg_length_9a7876c9728a0979: function(arg0) {
246
+ const ret = arg0.length;
247
+ return ret;
248
+ },
249
+ __wbg_new_3eb36ae241fe6f44: function() {
250
+ const ret = new Array();
251
+ return ret;
252
+ },
253
+ __wbg_new_8a6f238a6ece86ea: function() {
254
+ const ret = new Error();
255
+ return ret;
256
+ },
257
+ __wbg_new_b5d9e2fb389fef91: function(arg0, arg1) {
258
+ try {
259
+ var state0 = {a: arg0, b: arg1};
260
+ var cb0 = (arg0, arg1) => {
261
+ const a = state0.a;
262
+ state0.a = 0;
263
+ try {
264
+ return wasm_bindgen__convert__closures_____invoke__h8e726e14fdbe3765(a, state0.b, arg0, arg1);
265
+ } finally {
266
+ state0.a = a;
267
+ }
268
+ };
269
+ const ret = new Promise(cb0);
270
+ return ret;
271
+ } finally {
272
+ state0.a = state0.b = 0;
273
+ }
274
+ },
275
+ __wbg_new_from_slice_a3d2629dc1826784: function(arg0, arg1) {
276
+ const ret = new Uint8Array(getArrayU8FromWasm0(arg0, arg1));
277
+ return ret;
278
+ },
279
+ __wbg_new_no_args_1c7c842f08d00ebb: function(arg0, arg1) {
280
+ const ret = new Function(getStringFromWasm0(arg0, arg1));
281
+ return ret;
282
+ },
283
+ __wbg_parse_708461a1feddfb38: function() { return handleError(function (arg0, arg1) {
284
+ const ret = JSON.parse(getStringFromWasm0(arg0, arg1));
285
+ return ret;
286
+ }, arguments); },
287
+ __wbg_prototypesetcall_c7e6a26aeade796d: function(arg0, arg1, arg2) {
288
+ Float32Array.prototype.set.call(getArrayF32FromWasm0(arg0, arg1), arg2);
289
+ },
290
+ __wbg_push_8ffdcb2063340ba5: function(arg0, arg1) {
291
+ const ret = arg0.push(arg1);
292
+ return ret;
293
+ },
294
+ __wbg_queueMicrotask_0aa0a927f78f5d98: function(arg0) {
295
+ const ret = arg0.queueMicrotask;
296
+ return ret;
297
+ },
298
+ __wbg_queueMicrotask_5bb536982f78a56f: function(arg0) {
299
+ queueMicrotask(arg0);
300
+ },
301
+ __wbg_resolve_002c4b7d9d8f6b64: function(arg0) {
302
+ const ret = Promise.resolve(arg0);
303
+ return ret;
304
+ },
305
+ __wbg_stack_0ed75d68575b0f3c: function(arg0, arg1) {
306
+ const ret = arg1.stack;
307
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
308
+ const len1 = WASM_VECTOR_LEN;
309
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
310
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
311
+ },
312
+ __wbg_static_accessor_GLOBAL_12837167ad935116: function() {
313
+ const ret = typeof global === 'undefined' ? null : global;
314
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
315
+ },
316
+ __wbg_static_accessor_GLOBAL_THIS_e628e89ab3b1c95f: function() {
317
+ const ret = typeof globalThis === 'undefined' ? null : globalThis;
318
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
319
+ },
320
+ __wbg_static_accessor_SELF_a621d3dfbb60d0ce: function() {
321
+ const ret = typeof self === 'undefined' ? null : self;
322
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
323
+ },
324
+ __wbg_static_accessor_WINDOW_f8727f0cf888e0bd: function() {
325
+ const ret = typeof window === 'undefined' ? null : window;
326
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
327
+ },
328
+ __wbg_stringify_8d1cc6ff383e8bae: function() { return handleError(function (arg0) {
329
+ const ret = JSON.stringify(arg0);
330
+ return ret;
331
+ }, arguments); },
332
+ __wbg_then_0d9fe2c7b1857d32: function(arg0, arg1, arg2) {
333
+ const ret = arg0.then(arg1, arg2);
334
+ return ret;
335
+ },
336
+ __wbg_then_b9e7b3b5f1a9e1b5: function(arg0, arg1) {
337
+ const ret = arg0.then(arg1);
338
+ return ret;
339
+ },
340
+ __wbindgen_cast_0000000000000001: function(arg0, arg1) {
341
+ // Cast intrinsic for `Closure(Closure { dtor_idx: 170, function: Function { arguments: [Externref], shim_idx: 171, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
342
+ const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__he93a1b3377269182, wasm_bindgen__convert__closures_____invoke__h668261b123d2ac6c);
343
+ return ret;
344
+ },
345
+ __wbindgen_cast_0000000000000002: function(arg0) {
346
+ // Cast intrinsic for `F64 -> Externref`.
347
+ const ret = arg0;
348
+ return ret;
349
+ },
350
+ __wbindgen_cast_0000000000000003: function(arg0, arg1) {
351
+ // Cast intrinsic for `Ref(String) -> Externref`.
352
+ const ret = getStringFromWasm0(arg0, arg1);
353
+ return ret;
354
+ },
355
+ __wbindgen_init_externref_table: function() {
356
+ const table = wasm.__wbindgen_externrefs;
357
+ const offset = table.grow(4);
358
+ table.set(0, undefined);
359
+ table.set(offset + 0, undefined);
360
+ table.set(offset + 1, null);
361
+ table.set(offset + 2, true);
362
+ table.set(offset + 3, false);
363
+ },
364
+ };
365
+ return {
366
+ __proto__: null,
367
+ "./sulcus_wasm_bg.js": import0,
368
+ };
369
+ }
370
+
371
+ function wasm_bindgen__convert__closures_____invoke__h668261b123d2ac6c(arg0, arg1, arg2) {
372
+ wasm.wasm_bindgen__convert__closures_____invoke__h668261b123d2ac6c(arg0, arg1, arg2);
373
+ }
374
+
375
+ function wasm_bindgen__convert__closures_____invoke__h8e726e14fdbe3765(arg0, arg1, arg2, arg3) {
376
+ wasm.wasm_bindgen__convert__closures_____invoke__h8e726e14fdbe3765(arg0, arg1, arg2, arg3);
377
+ }
378
+
379
+ const SulcusMemFinalization = (typeof FinalizationRegistry === 'undefined')
380
+ ? { register: () => {}, unregister: () => {} }
381
+ : new FinalizationRegistry(ptr => wasm.__wbg_sulcusmem_free(ptr >>> 0, 1));
382
+
383
+ function addToExternrefTable0(obj) {
384
+ const idx = wasm.__externref_table_alloc();
385
+ wasm.__wbindgen_externrefs.set(idx, obj);
386
+ return idx;
387
+ }
388
+
389
+ const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined')
390
+ ? { register: () => {}, unregister: () => {} }
391
+ : new FinalizationRegistry(state => state.dtor(state.a, state.b));
392
+
393
+ function debugString(val) {
394
+ // primitive types
395
+ const type = typeof val;
396
+ if (type == 'number' || type == 'boolean' || val == null) {
397
+ return `${val}`;
398
+ }
399
+ if (type == 'string') {
400
+ return `"${val}"`;
401
+ }
402
+ if (type == 'symbol') {
403
+ const description = val.description;
404
+ if (description == null) {
405
+ return 'Symbol';
406
+ } else {
407
+ return `Symbol(${description})`;
408
+ }
409
+ }
410
+ if (type == 'function') {
411
+ const name = val.name;
412
+ if (typeof name == 'string' && name.length > 0) {
413
+ return `Function(${name})`;
414
+ } else {
415
+ return 'Function';
416
+ }
417
+ }
418
+ // objects
419
+ if (Array.isArray(val)) {
420
+ const length = val.length;
421
+ let debug = '[';
422
+ if (length > 0) {
423
+ debug += debugString(val[0]);
424
+ }
425
+ for(let i = 1; i < length; i++) {
426
+ debug += ', ' + debugString(val[i]);
427
+ }
428
+ debug += ']';
429
+ return debug;
430
+ }
431
+ // Test for built-in
432
+ const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
433
+ let className;
434
+ if (builtInMatches && builtInMatches.length > 1) {
435
+ className = builtInMatches[1];
436
+ } else {
437
+ // Failed to match the standard '[object ClassName]'
438
+ return toString.call(val);
439
+ }
440
+ if (className == 'Object') {
441
+ // we're a user defined class or Object
442
+ // JSON.stringify avoids problems with cycles, and is generally much
443
+ // easier than looping through ownProperties of `val`.
444
+ try {
445
+ return 'Object(' + JSON.stringify(val) + ')';
446
+ } catch (_) {
447
+ return 'Object';
448
+ }
449
+ }
450
+ // errors
451
+ if (val instanceof Error) {
452
+ return `${val.name}: ${val.message}\n${val.stack}`;
453
+ }
454
+ // TODO we could test for more things here, like `Set`s and `Map`s.
455
+ return className;
456
+ }
457
+
458
+ function getArrayF32FromWasm0(ptr, len) {
459
+ ptr = ptr >>> 0;
460
+ return getFloat32ArrayMemory0().subarray(ptr / 4, ptr / 4 + len);
461
+ }
462
+
463
+ function getArrayU8FromWasm0(ptr, len) {
464
+ ptr = ptr >>> 0;
465
+ return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
466
+ }
467
+
468
+ let cachedDataViewMemory0 = null;
469
+ function getDataViewMemory0() {
470
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
471
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
472
+ }
473
+ return cachedDataViewMemory0;
474
+ }
475
+
476
+ let cachedFloat32ArrayMemory0 = null;
477
+ function getFloat32ArrayMemory0() {
478
+ if (cachedFloat32ArrayMemory0 === null || cachedFloat32ArrayMemory0.byteLength === 0) {
479
+ cachedFloat32ArrayMemory0 = new Float32Array(wasm.memory.buffer);
480
+ }
481
+ return cachedFloat32ArrayMemory0;
482
+ }
483
+
484
+ function getStringFromWasm0(ptr, len) {
485
+ ptr = ptr >>> 0;
486
+ return decodeText(ptr, len);
487
+ }
488
+
489
+ let cachedUint8ArrayMemory0 = null;
490
+ function getUint8ArrayMemory0() {
491
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
492
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
493
+ }
494
+ return cachedUint8ArrayMemory0;
495
+ }
496
+
497
+ function handleError(f, args) {
498
+ try {
499
+ return f.apply(this, args);
500
+ } catch (e) {
501
+ const idx = addToExternrefTable0(e);
502
+ wasm.__wbindgen_exn_store(idx);
503
+ }
504
+ }
505
+
506
+ function isLikeNone(x) {
507
+ return x === undefined || x === null;
508
+ }
509
+
510
+ function makeMutClosure(arg0, arg1, dtor, f) {
511
+ const state = { a: arg0, b: arg1, cnt: 1, dtor };
512
+ const real = (...args) => {
513
+
514
+ // First up with a closure we increment the internal reference
515
+ // count. This ensures that the Rust closure environment won't
516
+ // be deallocated while we're invoking it.
517
+ state.cnt++;
518
+ const a = state.a;
519
+ state.a = 0;
520
+ try {
521
+ return f(a, state.b, ...args);
522
+ } finally {
523
+ state.a = a;
524
+ real._wbg_cb_unref();
525
+ }
526
+ };
527
+ real._wbg_cb_unref = () => {
528
+ if (--state.cnt === 0) {
529
+ state.dtor(state.a, state.b);
530
+ state.a = 0;
531
+ CLOSURE_DTORS.unregister(state);
532
+ }
533
+ };
534
+ CLOSURE_DTORS.register(real, state, state);
535
+ return real;
536
+ }
537
+
538
+ function passArray8ToWasm0(arg, malloc) {
539
+ const ptr = malloc(arg.length * 1, 1) >>> 0;
540
+ getUint8ArrayMemory0().set(arg, ptr / 1);
541
+ WASM_VECTOR_LEN = arg.length;
542
+ return ptr;
543
+ }
544
+
545
+ function passStringToWasm0(arg, malloc, realloc) {
546
+ if (realloc === undefined) {
547
+ const buf = cachedTextEncoder.encode(arg);
548
+ const ptr = malloc(buf.length, 1) >>> 0;
549
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
550
+ WASM_VECTOR_LEN = buf.length;
551
+ return ptr;
552
+ }
553
+
554
+ let len = arg.length;
555
+ let ptr = malloc(len, 1) >>> 0;
556
+
557
+ const mem = getUint8ArrayMemory0();
558
+
559
+ let offset = 0;
560
+
561
+ for (; offset < len; offset++) {
562
+ const code = arg.charCodeAt(offset);
563
+ if (code > 0x7F) break;
564
+ mem[ptr + offset] = code;
565
+ }
566
+ if (offset !== len) {
567
+ if (offset !== 0) {
568
+ arg = arg.slice(offset);
569
+ }
570
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
571
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
572
+ const ret = cachedTextEncoder.encodeInto(arg, view);
573
+
574
+ offset += ret.written;
575
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
576
+ }
577
+
578
+ WASM_VECTOR_LEN = offset;
579
+ return ptr;
580
+ }
581
+
582
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
583
+ cachedTextDecoder.decode();
584
+ function decodeText(ptr, len) {
585
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
586
+ }
587
+
588
+ const cachedTextEncoder = new TextEncoder();
589
+
590
+ if (!('encodeInto' in cachedTextEncoder)) {
591
+ cachedTextEncoder.encodeInto = function (arg, view) {
592
+ const buf = cachedTextEncoder.encode(arg);
593
+ view.set(buf);
594
+ return {
595
+ read: arg.length,
596
+ written: buf.length
597
+ };
598
+ };
599
+ }
600
+
601
+ let WASM_VECTOR_LEN = 0;
602
+
603
+ const wasmPath = `${__dirname}/sulcus_wasm_bg.wasm`;
604
+ const wasmBytes = require('fs').readFileSync(wasmPath);
605
+ const wasmModule = new WebAssembly.Module(wasmBytes);
606
+ const wasm = new WebAssembly.Instance(wasmModule, __wbg_get_imports()).exports;
607
+ wasm.__wbindgen_start();
Binary file