@digitalforgestudios/openclaw-sulcus 1.0.2 → 1.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.
Files changed (2) hide show
  1. package/index.ts +69 -2
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -97,6 +97,10 @@ class SulcusClient {
97
97
  body: JSON.stringify(body),
98
98
  });
99
99
 
100
+ if (res.status === 409) {
101
+ // Duplicate memory — silently skip, not an error
102
+ return { id: "", label, memory_type: memoryType, namespace: namespace ?? this.config.namespace ?? "default" } as SulcusNode;
103
+ }
100
104
  if (!res.ok) {
101
105
  const errText = await res.text().catch(() => "");
102
106
  throw new Error(`Sulcus store failed: ${res.status} ${errText}`);
@@ -210,6 +214,23 @@ function shouldCapture(text: string): boolean {
210
214
  // Reject if stripping removed >60% of the content (mostly metadata)
211
215
  if (cleaned.length < text.length * 0.4) return false;
212
216
 
217
+ // Reject system prompts and OpenClaw operational messages that caused 1,000+ dupes
218
+ const rejectPatterns = [
219
+ /^Pre-compaction memory flush/i,
220
+ /^A new session was started via/i,
221
+ /^\[cron:[0-9a-f-]+/i,
222
+ /^To send an image back, prefer the message tool/i,
223
+ /^Heartbeat prompt:/i,
224
+ /^Read HEARTBEAT\.md/i,
225
+ /^Run your Session Startup sequence/i,
226
+ /^You are \w+\. T/i, // cron job identity preambles
227
+ /^Gateway restart/i,
228
+ /^System: \[/,
229
+ /^HEARTBEAT_OK$/i,
230
+ /^NO_REPLY$/i,
231
+ ];
232
+ if (rejectPatterns.some((r) => r.test(cleaned))) return false;
233
+
213
234
  const triggers = [
214
235
  /remember|zapamatuj/i,
215
236
  /prefer|like|love|hate|want/i,
@@ -529,7 +550,7 @@ const sulcusMemoryPlugin = {
529
550
  // ========================================================================
530
551
 
531
552
  if (config.autoRecall) {
532
- api.on("before_agent_start", async (event) => {
553
+ api.on("before_model_resolve", async (event) => {
533
554
  if (!event.prompt || event.prompt.length < 5) return;
534
555
 
535
556
  try {
@@ -539,7 +560,8 @@ const sulcusMemoryPlugin = {
539
560
  const memoryLines = results.map((node, i) => {
540
561
  const label = node.pointer_summary ?? node.label ?? "";
541
562
  const heat = node.current_heat ?? node.heat ?? 0;
542
- return `${i + 1}. [${node.memory_type}] (heat: ${heat.toFixed(2)}) ${escapeForPrompt(label.slice(0, 400))}`;
563
+ const id = node.id ?? "";
564
+ return `${i + 1}. [${node.memory_type}] (heat: ${heat.toFixed(2)}) [id: ${id}] ${escapeForPrompt(label.slice(0, 400))}`;
543
565
  });
544
566
 
545
567
  api.logger.info?.(`openclaw-sulcus: injecting ${results.length} memories into context`);
@@ -553,6 +575,51 @@ const sulcusMemoryPlugin = {
553
575
  });
554
576
  }
555
577
 
578
+ // ========================================================================
579
+ // Lifecycle — Preserve memories before compaction
580
+ // ========================================================================
581
+
582
+ api.on("before_compaction", async (event) => {
583
+ if (!event.messages || event.messages.length === 0) return;
584
+
585
+ try {
586
+ // Scan messages being compacted for important content worth preserving
587
+ const toPreserve: string[] = [];
588
+ for (const msg of event.messages) {
589
+ if (!msg || typeof msg !== "object") continue;
590
+ const msgObj = msg as Record<string, unknown>;
591
+ const content = typeof msgObj.content === "string" ? msgObj.content : "";
592
+ if (!content || content.length < 30) continue;
593
+
594
+ const cleaned = stripMetadataEnvelope(content);
595
+ if (cleaned.length < 30) continue;
596
+ if (!shouldCapture(cleaned)) continue;
597
+
598
+ toPreserve.push(cleaned);
599
+ }
600
+
601
+ if (toPreserve.length === 0) return;
602
+
603
+ // Store up to 5 important memories before compaction discards them
604
+ let stored = 0;
605
+ for (const text of toPreserve.slice(0, 5)) {
606
+ const type = detectMemoryType(text);
607
+ try {
608
+ await client.store(text.slice(0, 2000), type);
609
+ stored++;
610
+ } catch {
611
+ // 409 (dedup) or other — continue
612
+ }
613
+ }
614
+
615
+ if (stored > 0) {
616
+ api.logger.info(`openclaw-sulcus: preserved ${stored} memories before compaction`);
617
+ }
618
+ } catch (err) {
619
+ api.logger.warn(`openclaw-sulcus: before_compaction failed: ${String(err)}`);
620
+ }
621
+ });
622
+
556
623
  // ========================================================================
557
624
  // Lifecycle — Auto-capture
558
625
  // ========================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digitalforgestudios/openclaw-sulcus",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "description": "Sulcus memory plugin for OpenClaw — thermodynamic memory with heat-based decay, reactive triggers, and cross-agent sync.",
5
5
  "keywords": [
6
6
  "openclaw",