@digitalforgestudios/openclaw-sulcus 6.6.2 → 6.6.4

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 (3) hide show
  1. package/index.js +2912 -136
  2. package/index.ts +202 -3
  3. package/package.json +2 -2
package/index.ts CHANGED
@@ -5295,16 +5295,215 @@ const sulcusPlugin = {
5295
5295
  }
5296
5296
  }
5297
5297
 
5298
- // 3. registerMemoryFlushPlan — no custom compaction flush
5298
+ // 3. registerMemoryFlushPlan — Sulcus-aware pre-compaction flush
5299
5299
  if (typeof (api.registerMemoryFlushPlan as unknown) === "function") {
5300
5300
  try {
5301
- (api.registerMemoryFlushPlan as (r: unknown) => void)(() => null);
5302
- logger.info("sulcus: registered memory flush plan (no-op)");
5301
+ (api.registerMemoryFlushPlan as (r: unknown) => void)(() => {
5302
+ if (!isAvailable || !sulcusMem) return null;
5303
+ return {
5304
+ softThresholdTokens: 15000,
5305
+ forceFlushTranscriptBytes: "2mb",
5306
+ reserveTokensFloor: 30000,
5307
+ prompt: [
5308
+ "Your session is approaching context limits. Before compaction, extract and save the most important information from this conversation using memory_store.",
5309
+ "",
5310
+ "Focus on:",
5311
+ "- Decisions made and their reasoning",
5312
+ "- Facts learned or confirmed",
5313
+ "- User preferences stated or implied",
5314
+ "- Procedures or workflows discussed",
5315
+ "- Errors encountered and their resolutions",
5316
+ "",
5317
+ "Use the appropriate memory_type for each:",
5318
+ "- preference: user preferences, opinions, style choices",
5319
+ "- fact: data points, configurations, names, values",
5320
+ "- semantic: knowledge, explanations, conclusions",
5321
+ "- procedural: how-tos, workflows, step-by-step processes",
5322
+ "- episodic: events, conversations, time-specific context",
5323
+ "",
5324
+ "Store 3-8 memories. Be selective — quality over quantity. Skip trivial exchanges.",
5325
+ ].join("\n"),
5326
+ systemPrompt: "You are a memory extraction agent. Your job is to identify and store the most valuable information from the current conversation before it gets compacted. Use memory_store with precise memory_type classification. Do not store system noise, tool outputs, or trivial exchanges.",
5327
+ };
5328
+ });
5329
+ logger.info("sulcus: registered memory flush plan (Sulcus-aware)");
5303
5330
  } catch (e: unknown) {
5304
5331
  logger.warn(`sulcus: registerMemoryFlushPlan failed: ${e instanceof Error ? e.message : e}`);
5305
5332
  }
5306
5333
  }
5307
5334
 
5335
+ // 3b. registerCompactionProvider — Sulcus-native compaction summarization
5336
+ if (isCloudBackend && sulcusMem && typeof (api.registerCompactionProvider as unknown) === "function") {
5337
+ try {
5338
+ const compactionSulcusMem = sulcusMem as SulcusCloudClient;
5339
+ (api.registerCompactionProvider as (p: unknown) => void)({
5340
+ id: "sulcus",
5341
+ async summarize(params: {
5342
+ messages: Record<string, unknown>[];
5343
+ signal?: AbortSignal;
5344
+ customInstructions?: string;
5345
+ summarizationInstructions?: string;
5346
+ previousSummary?: string;
5347
+ }): Promise<string> {
5348
+ const msgs = params.messages ?? [];
5349
+ logger.info(`sulcus: compaction provider summarize() called with ${msgs.length} messages`);
5350
+ try {
5351
+ // 1. Extract substantive content from messages being compacted
5352
+ const decisions: string[] = [];
5353
+ const filesModified: string[] = [];
5354
+ const toolsUsed: string[] = [];
5355
+ const errorsHit: string[] = [];
5356
+ const userIntents: string[] = [];
5357
+ const assistantWork: string[] = [];
5358
+
5359
+ const DECISION_MARKERS = ["decided", "will use", "going to", "plan is", "the fix", "conclusion", "recommend", "approach"];
5360
+
5361
+ for (const msg of msgs) {
5362
+ const role = (msg.role ?? msg.type) as string | undefined;
5363
+ const text = typeof msg.content === "string" ? msg.content
5364
+ : typeof msg.text === "string" ? msg.text as string
5365
+ : Array.isArray(msg.content)
5366
+ ? (msg.content as Record<string, unknown>[]).filter((c) => c.type === "text").map((c) => c.text as string).join("\n")
5367
+ : "";
5368
+ if (!text) continue;
5369
+
5370
+ if ((role === "user" || role === "human") && text.length > 10) {
5371
+ userIntents.push(text.substring(0, 200));
5372
+ }
5373
+ if ((role === "assistant" || role === "ai") && text.length > 50) {
5374
+ const lc = text.toLowerCase();
5375
+ if (DECISION_MARKERS.some((m) => lc.includes(m))) {
5376
+ const sentences = text.split(/[.!?\n]/).filter((s) => s.trim().length > 15);
5377
+ for (const s of sentences) {
5378
+ if (DECISION_MARKERS.some((m) => s.toLowerCase().includes(m))) {
5379
+ decisions.push(s.trim().substring(0, 300));
5380
+ if (decisions.length >= 8) break;
5381
+ }
5382
+ }
5383
+ }
5384
+ if (text.length > 100) {
5385
+ assistantWork.push(text.substring(0, 500));
5386
+ }
5387
+ }
5388
+
5389
+ // Extract tool usage
5390
+ const toolCalls = Array.isArray(msg.tool_calls) ? msg.tool_calls as Record<string, unknown>[] : [];
5391
+ for (const tc of toolCalls) {
5392
+ const name = ((tc.name ?? tc.function) as string) ?? "";
5393
+ if (name && !toolsUsed.includes(name)) toolsUsed.push(name);
5394
+ if (/^(write|edit)$/i.test(name)) {
5395
+ const input = (tc.input ?? tc.arguments ?? {}) as Record<string, unknown>;
5396
+ const fp = (input?.file_path ?? input?.path) as string | undefined;
5397
+ if (fp && !filesModified.includes(fp)) filesModified.push(fp);
5398
+ }
5399
+ }
5400
+
5401
+ // Track errors
5402
+ if (role === "tool" && (msg.is_error === true || (typeof text === "string" && /error|failed|exception/i.test(text.substring(0, 100))))) {
5403
+ errorsHit.push(text.substring(0, 200));
5404
+ }
5405
+ }
5406
+
5407
+ // 2. Store key items as Sulcus memories via SIU classification
5408
+ let stored = 0;
5409
+ const itemsToStore: { text: string; suggestedType: string }[] = [];
5410
+
5411
+ for (const d of decisions.slice(0, 5)) {
5412
+ itemsToStore.push({ text: d, suggestedType: "semantic" });
5413
+ }
5414
+ for (const u of userIntents.slice(0, 5)) {
5415
+ if (u.length > 50 && /\b(prefer|always|never|don't|stop|use|switch|remember)\b/i.test(u)) {
5416
+ itemsToStore.push({ text: u, suggestedType: "preference" });
5417
+ }
5418
+ }
5419
+ for (const a of assistantWork.slice(0, 3)) {
5420
+ if (/\b(step \d|procedure|workflow|how to|instructions)\b/i.test(a)) {
5421
+ itemsToStore.push({ text: a, suggestedType: "procedural" });
5422
+ } else {
5423
+ itemsToStore.push({ text: a, suggestedType: "semantic" });
5424
+ }
5425
+ }
5426
+
5427
+ for (const item of itemsToStore) {
5428
+ if (isJunkMemory(item.text)) continue;
5429
+ if (!shouldCapture(item.text)) continue;
5430
+ try {
5431
+ let memType = item.suggestedType;
5432
+ try {
5433
+ const siuResult = await compactionSulcusMem.request(
5434
+ "POST", "/api/v2/siu/label", { text: item.text }
5435
+ ) as Record<string, unknown>;
5436
+ if (siuResult?.store === false && ((siuResult?.store_confidence as number) ?? 0) < 0.3) continue;
5437
+ if (siuResult?.memory_type) memType = siuResult.memory_type as string;
5438
+ } catch { /* SIU unavailable — use heuristic */ }
5439
+ const hints = buildExtractionHints(memType, namespace, "compaction_provider", item.text.substring(0, 200));
5440
+ await compactionSulcusMem.add_memory(item.text, memType, hints);
5441
+ stored++;
5442
+ } catch { /* best-effort */ }
5443
+ }
5444
+ logger.info(`sulcus: compaction provider — stored ${stored} memories`);
5445
+
5446
+ // 3. Query Sulcus for relevant context (including freshly stored)
5447
+ const topicQuery = userIntents.slice(0, 3).join(" ").substring(0, 500) || "session summary";
5448
+ let relevantMemories: Record<string, unknown>[] = [];
5449
+ try {
5450
+ const searchRes = await compactionSulcusMem.search_memory(topicQuery, 15, namespace);
5451
+ relevantMemories = searchRes?.results ?? [];
5452
+ } catch (e) {
5453
+ logger.warn(`sulcus: compaction provider — memory search failed: ${e}`);
5454
+ }
5455
+
5456
+ // 4. Build structured summary
5457
+ const sections: string[] = [];
5458
+
5459
+ if (params.previousSummary?.trim()) {
5460
+ sections.push(`## Prior Context\n${params.previousSummary.trim()}`);
5461
+ }
5462
+
5463
+ // Memory-backed context
5464
+ const memLines: string[] = [];
5465
+ const seenIds = new Set<string>();
5466
+ for (const mem of relevantMemories) {
5467
+ const id = mem.id as string;
5468
+ if (seenIds.has(id)) continue;
5469
+ seenIds.add(id);
5470
+ const mtype = (mem.memory_type as string) ?? "unknown";
5471
+ const label = ((mem.label ?? mem.pointer_summary ?? "") as string).trim();
5472
+ if (!label || label.length < 20) continue;
5473
+ memLines.push(`- [${mtype}] ${label.length > 400 ? label.substring(0, 400) + "..." : label}`);
5474
+ }
5475
+ if (memLines.length > 0) {
5476
+ sections.push(`## Key Context (from Sulcus memory)\n${memLines.join("\n")}`);
5477
+ }
5478
+
5479
+ // Decisions
5480
+ if (decisions.length > 0) {
5481
+ sections.push(`## Decisions Made\n${decisions.map((d) => "- " + d).join("\n")}`);
5482
+ }
5483
+
5484
+ // Session activity
5485
+ const activity: string[] = [`${msgs.length} messages in this session segment`];
5486
+ if (filesModified.length > 0) activity.push(`Files modified: ${filesModified.join(", ")}`);
5487
+ if (toolsUsed.length > 0) activity.push(`Tools used: ${toolsUsed.join(", ")}`);
5488
+ if (errorsHit.length > 0) activity.push(`Errors: ${errorsHit.length}`);
5489
+ sections.push(`## Session Activity\n${activity.join("\n")}`);
5490
+
5491
+ const summary = sections.join("\n\n");
5492
+ if (!summary.trim()) throw new Error("Sulcus compaction produced empty summary");
5493
+ logger.info(`sulcus: compaction provider produced summary (${summary.length} chars)`);
5494
+ return summary;
5495
+ } catch (err) {
5496
+ logger.warn(`sulcus: compaction provider failed: ${err instanceof Error ? err.message : String(err)}`);
5497
+ throw err; // Fall back to built-in LLM summarization
5498
+ }
5499
+ },
5500
+ });
5501
+ logger.info("sulcus: registered compaction provider \"sulcus\"");
5502
+ } catch (e: unknown) {
5503
+ logger.warn(`sulcus: registerCompactionProvider failed: ${e instanceof Error ? e.message : e}`);
5504
+ }
5505
+ }
5506
+
5308
5507
  // 4. registerService — lifecycle management
5309
5508
  if (typeof (api.registerService as unknown) === "function") {
5310
5509
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digitalforgestudios/openclaw-sulcus",
3
- "version": "6.6.2",
3
+ "version": "6.6.4",
4
4
  "description": "Sulcus \u2014 thermodynamic memory + Apache AGE knowledge graph for OpenClaw agents. v6.0: Multi-signal recall (semantic + hot-context + entity-graph + profile), configurable guardrails (outputGuard + toolGuard), token budget enforcement, context rebuild post-compaction, sulcus.toml config layer, SIRU training data logging, session-scoped memory, batch heat-boost. SIU v2 pipeline (SIVU/SICU/SILU/SITU/SIRU). Interaction-based decay. Curator sleep-cycle. Cross-agent sync.",
5
5
  "keywords": [
6
6
  "openclaw",
@@ -58,7 +58,7 @@
58
58
  },
59
59
  "main": "index.js",
60
60
  "scripts": {
61
- "build": "npx esbuild index.ts --bundle --platform=node --target=node18 --outfile=index.js --external:openclaw --external:koffi --external:@sinclair/typebox --format=cjs",
61
+ "build": "npx esbuild index.ts --bundle --platform=node --target=node18 --outfile=index.js --external:openclaw --external:koffi --format=cjs",
62
62
  "prepublishOnly": "npm run build"
63
63
  }
64
64
  }