@digitalforgestudios/openclaw-sulcus 3.9.0 → 3.11.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 +95 -14
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -104,8 +104,8 @@ const hookHandlers: Record<string, HookHandler> = {
|
|
|
104
104
|
if (!prompt) return;
|
|
105
105
|
try {
|
|
106
106
|
const limit = config.limit ?? 5;
|
|
107
|
-
logger.debug(`sulcus: searching context for prompt: ${prompt.substring(0, 50)}
|
|
108
|
-
const res = await sulcusMem.search_memory(prompt, limit);
|
|
107
|
+
logger.debug(`sulcus: searching context for prompt: ${prompt.substring(0, 50)}... (namespace: ${namespace})`);
|
|
108
|
+
const res = await sulcusMem.search_memory(prompt, limit, namespace);
|
|
109
109
|
const results = res?.results ?? [];
|
|
110
110
|
if (!results || results.length === 0) {
|
|
111
111
|
return { prependSystemContext: FALLBACK_AWARENESS };
|
|
@@ -280,9 +280,10 @@ class SulcusCloudClient {
|
|
|
280
280
|
* search_memory — maps to POST /agent/search
|
|
281
281
|
* Server returns { results: [...] }; we normalise to the results array.
|
|
282
282
|
*/
|
|
283
|
-
async search_memory(query: string, limit?: number): Promise<{ results: any[] }> {
|
|
283
|
+
async search_memory(query: string, limit?: number, namespace?: string): Promise<{ results: any[] }> {
|
|
284
284
|
const body: any = { query };
|
|
285
285
|
if (limit !== undefined) body.limit = limit;
|
|
286
|
+
if (namespace !== undefined) body.namespace = namespace;
|
|
286
287
|
const res = await this.request("POST", "/api/v1/agent/search", body);
|
|
287
288
|
const results = res?.results ?? res?.items ?? res?.nodes ?? (Array.isArray(res) ? res : []);
|
|
288
289
|
return { results };
|
|
@@ -300,12 +301,15 @@ class SulcusCloudClient {
|
|
|
300
301
|
}
|
|
301
302
|
|
|
302
303
|
/**
|
|
303
|
-
* list_hot_nodes — maps to GET /agent/
|
|
304
|
+
* list_hot_nodes — maps to GET /agent/hot_nodes
|
|
304
305
|
* Returns hot_nodes list; normalised for memory_status tool.
|
|
306
|
+
* Note: was incorrectly calling /agent/memory/status which doesn't return hot_nodes.
|
|
305
307
|
*/
|
|
306
|
-
async list_hot_nodes(
|
|
307
|
-
const
|
|
308
|
-
const
|
|
308
|
+
async list_hot_nodes(limit?: number): Promise<{ nodes: any[] }> {
|
|
309
|
+
const q = limit ? `?limit=${limit}` : "";
|
|
310
|
+
const res = await this.request("GET", `/api/v1/agent/hot_nodes${q}`);
|
|
311
|
+
// Server returns an array directly from this endpoint
|
|
312
|
+
const nodes = Array.isArray(res) ? res : (res?.hot_nodes ?? res?.nodes ?? []);
|
|
309
313
|
return { nodes };
|
|
310
314
|
}
|
|
311
315
|
|
|
@@ -318,6 +322,15 @@ class SulcusCloudClient {
|
|
|
318
322
|
return this.request("POST", "/api/v1/agent/consolidate", body);
|
|
319
323
|
}
|
|
320
324
|
|
|
325
|
+
/**
|
|
326
|
+
* delete_memory — maps to DELETE /agent/nodes/:id?train=true|false
|
|
327
|
+
* If train=true, snapshots content before deletion and records a 'reject' training signal for SIVU.
|
|
328
|
+
*/
|
|
329
|
+
async delete_memory(id: string, train?: boolean): Promise<any> {
|
|
330
|
+
const trainParam = train ? "true" : "false";
|
|
331
|
+
return this.request("DELETE", `/api/v1/agent/nodes/${encodeURIComponent(id)}?train=${trainParam}`);
|
|
332
|
+
}
|
|
333
|
+
|
|
321
334
|
/**
|
|
322
335
|
* export_markdown — maps to GET /agent/export?format=markdown
|
|
323
336
|
* Returns raw markdown string.
|
|
@@ -501,6 +514,20 @@ const JUNK_PATTERNS = [
|
|
|
501
514
|
/^<<<EXTERNAL_UNTRUSTED_CONTENT/i,
|
|
502
515
|
/^Runtime:/i,
|
|
503
516
|
/tool_call|function_call|<function_calls>/i,
|
|
517
|
+
// Subagent completion events — internal runtime artifacts, not memories
|
|
518
|
+
/\[Inter-session message\]\s*sourceSession=/i,
|
|
519
|
+
/<<<BEGIN_UNTRUSTED_CHILD_RESULT>>>/,
|
|
520
|
+
/<<<END_UNTRUSTED_CHILD_RESULT>>>/,
|
|
521
|
+
/\[Internal task completion event\]/i,
|
|
522
|
+
/^source:\s*subagent/im,
|
|
523
|
+
/session_key:\s*agent:main:subagent:/i,
|
|
524
|
+
// Cron task payloads — system prompts, not meaningful content
|
|
525
|
+
/^Sulcus validation cycle\./i,
|
|
526
|
+
/^Heartbeat prompt:/i,
|
|
527
|
+
/OpenClaw runtime context \(internal\)/i,
|
|
528
|
+
// Credential patterns — should never be stored
|
|
529
|
+
/\b(sk-[a-f0-9]{40,}|Bearer\s+[A-Za-z0-9._~+/=-]{20,})\b/,
|
|
530
|
+
/\b(api[_-]?key|secret|password|token)\s*[:=]\s*["']?[A-Za-z0-9._~+/=-]{16,}/i,
|
|
504
531
|
];
|
|
505
532
|
|
|
506
533
|
function isJunkMemory(text: string): boolean {
|
|
@@ -601,7 +628,8 @@ const toolDefinitions: Record<string, ToolDefinition> = {
|
|
|
601
628
|
description: "Search Sulcus memory for relevant context",
|
|
602
629
|
parameters: Type.Object({
|
|
603
630
|
query: Type.String({ description: "Search query string." }),
|
|
604
|
-
limit: Type.Optional(Type.Number({ default: 5, description: "Maximum number of results to return (1-10)." }))
|
|
631
|
+
limit: Type.Optional(Type.Number({ default: 5, description: "Maximum number of results to return (1-10)." })),
|
|
632
|
+
namespace: Type.Optional(Type.String({ description: "Namespace to search. Defaults to your own namespace. Specify another to cross-search (ACL enforced)." }))
|
|
605
633
|
}),
|
|
606
634
|
},
|
|
607
635
|
options: { name: "memory_recall" },
|
|
@@ -610,11 +638,14 @@ const toolDefinitions: Record<string, ToolDefinition> = {
|
|
|
610
638
|
if (!isAvailable) {
|
|
611
639
|
throw new Error(`Sulcus unavailable: ${nativeLoader.error || "WASM not loaded"}`);
|
|
612
640
|
}
|
|
613
|
-
|
|
641
|
+
// Default to agent's own namespace to prevent cross-namespace bleed.
|
|
642
|
+
// Agent can explicitly pass namespace to search another (ACL enforced server-side).
|
|
643
|
+
const searchNamespace = params.namespace ?? namespace;
|
|
644
|
+
const res = await sulcusMem.search_memory(params.query, params.limit ?? 5, searchNamespace);
|
|
614
645
|
const results = res?.results ?? res?.items ?? res?.nodes ?? res ?? [];
|
|
615
646
|
return {
|
|
616
647
|
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
617
|
-
details: { results, backend: backendMode, namespace }
|
|
648
|
+
details: { results, backend: backendMode, namespace: searchNamespace }
|
|
618
649
|
};
|
|
619
650
|
},
|
|
620
651
|
},
|
|
@@ -633,6 +664,7 @@ const toolDefinitions: Record<string, ToolDefinition> = {
|
|
|
633
664
|
Type.Literal("procedural"),
|
|
634
665
|
Type.Literal("fact")
|
|
635
666
|
], { description: "Memory type. preference=user preferences, procedural=how-to/processes, fact=stable knowledge, semantic=concepts/relationships, episodic=events/experiences. Default: episodic" })),
|
|
667
|
+
train: Type.Optional(Type.Boolean({ description: "Signal the SIU to learn from this manual store. When true, this memory+type becomes a positive training example for both SIVU (store=yes) and SICU (type=<memory_type>). Default: false" })),
|
|
636
668
|
}),
|
|
637
669
|
},
|
|
638
670
|
options: { name: "memory_store" },
|
|
@@ -652,9 +684,27 @@ const toolDefinitions: Record<string, ToolDefinition> = {
|
|
|
652
684
|
const res = await sulcusMem.add_memory(params.content, params.memory_type ?? null);
|
|
653
685
|
const nodeId = res?.id ?? "unknown";
|
|
654
686
|
const mtype = params.memory_type || "episodic";
|
|
687
|
+
// If train=true, submit a training signal to the SIU
|
|
688
|
+
let trainResult: string | null = null;
|
|
689
|
+
if (params.train && (sulcusMem as any).request) {
|
|
690
|
+
try {
|
|
691
|
+
await (sulcusMem as any).request("POST", "/api/v2/siu/signal", {
|
|
692
|
+
memory_id: nodeId,
|
|
693
|
+
text: params.content,
|
|
694
|
+
sivu_label: "store",
|
|
695
|
+
sicu_label: mtype,
|
|
696
|
+
source: "agent_manual_store",
|
|
697
|
+
});
|
|
698
|
+
trainResult = "training signal submitted";
|
|
699
|
+
logger.info(`sulcus: SIU training signal sent for memory ${nodeId} (store, ${mtype})`);
|
|
700
|
+
} catch (e: any) {
|
|
701
|
+
trainResult = `training signal failed: ${e.message}`;
|
|
702
|
+
logger.warn(`sulcus: SIU training signal failed: ${e.message}`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
655
705
|
return {
|
|
656
|
-
content: [{ type: "text", text: `Stored [${mtype}] memory (id: ${nodeId}) → backend: ${backendMode}, namespace: ${namespace}` }],
|
|
657
|
-
details: { id: nodeId, memory_type: mtype, backend: backendMode, namespace, ...res }
|
|
706
|
+
content: [{ type: "text", text: `Stored [${mtype}] memory (id: ${nodeId}) → backend: ${backendMode}, namespace: ${namespace}${trainResult ? ` | SIU: ${trainResult}` : ""}` }],
|
|
707
|
+
details: { id: nodeId, memory_type: mtype, backend: backendMode, namespace, train: trainResult, ...res }
|
|
658
708
|
};
|
|
659
709
|
},
|
|
660
710
|
},
|
|
@@ -683,7 +733,11 @@ const toolDefinitions: Record<string, ToolDefinition> = {
|
|
|
683
733
|
};
|
|
684
734
|
}
|
|
685
735
|
try {
|
|
686
|
-
|
|
736
|
+
// Fetch both status info and hot nodes in parallel
|
|
737
|
+
const [statusInfo, hotNodes] = await Promise.all([
|
|
738
|
+
sulcusMem.request("GET", "/api/v1/agent/memory/status").catch(() => null),
|
|
739
|
+
sulcusMem.list_hot_nodes(20),
|
|
740
|
+
]);
|
|
687
741
|
const nodeList = hotNodes?.nodes ?? hotNodes ?? [];
|
|
688
742
|
const count = Array.isArray(nodeList) ? nodeList.length : 0;
|
|
689
743
|
return {
|
|
@@ -691,10 +745,12 @@ const toolDefinitions: Record<string, ToolDefinition> = {
|
|
|
691
745
|
status: "ok",
|
|
692
746
|
backend: backendMode,
|
|
693
747
|
namespace,
|
|
748
|
+
...(statusInfo?.capabilities ? { capabilities: statusInfo.capabilities } : {}),
|
|
749
|
+
...(statusInfo?.stats ? { stats: statusInfo.stats } : {}),
|
|
694
750
|
hot_node_count: count,
|
|
695
751
|
hot_nodes: nodeList,
|
|
696
752
|
}, null, 2) }],
|
|
697
|
-
details: { status: "ok", backend: backendMode, namespace, count }
|
|
753
|
+
details: { status: "ok", backend: backendMode, namespace, count, ...(statusInfo?.stats ?? {}) }
|
|
698
754
|
};
|
|
699
755
|
} catch (e: any) {
|
|
700
756
|
return {
|
|
@@ -802,6 +858,31 @@ const toolDefinitions: Record<string, ToolDefinition> = {
|
|
|
802
858
|
},
|
|
803
859
|
},
|
|
804
860
|
|
|
861
|
+
memory_delete: {
|
|
862
|
+
schema: {
|
|
863
|
+
name: "memory_delete",
|
|
864
|
+
label: "Delete Memory",
|
|
865
|
+
description: "Delete a memory node by ID. With train=true (default), the deleted content trains SIVU to reject similar content in the future. Use this to clean up junk, duplicates, or noise memories.",
|
|
866
|
+
parameters: Type.Object({
|
|
867
|
+
id: Type.String({ description: "Memory node ID to delete." }),
|
|
868
|
+
train: Type.Optional(Type.Boolean({ default: true, description: "Train SIVU to reject similar content (default true). Set false to delete without training." })),
|
|
869
|
+
}),
|
|
870
|
+
},
|
|
871
|
+
options: { name: "memory_delete" },
|
|
872
|
+
makeExecute: ({ sulcusMem, backendMode, namespace, nativeLoader, isAvailable }) =>
|
|
873
|
+
async (_id: string, params: any) => {
|
|
874
|
+
if (!isAvailable) {
|
|
875
|
+
throw new Error(`Sulcus unavailable: ${nativeLoader.error || "WASM not loaded"}`);
|
|
876
|
+
}
|
|
877
|
+
const train = params.train !== false; // default true
|
|
878
|
+
const res = await (sulcusMem as SulcusCloudClient).delete_memory(params.id, train);
|
|
879
|
+
return {
|
|
880
|
+
content: [{ type: "text", text: `Deleted memory ${params.id}${train ? " (trained SIVU to reject similar)" : ""}. Backend: ${backendMode}, namespace: ${namespace}` }],
|
|
881
|
+
details: { id: params.id, trained: train, result: res, backend: backendMode, namespace }
|
|
882
|
+
};
|
|
883
|
+
},
|
|
884
|
+
},
|
|
885
|
+
|
|
805
886
|
// ── SIU v2 Tools ───────────────────────────────────────────────────────────
|
|
806
887
|
// These tools call the SIU v2 server endpoints for text classification.
|
|
807
888
|
// Requires cloud backend (serverUrl + apiKey). Uses /api/v2/siu/* endpoints.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@digitalforgestudios/openclaw-sulcus",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.11.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",
|