@vectorize-io/hindsight-openclaw 0.4.13 → 0.4.15

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/dist/index.js CHANGED
@@ -2,6 +2,12 @@ import { HindsightEmbedManager } from './embed-manager.js';
2
2
  import { HindsightClient } from './client.js';
3
3
  import { dirname } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
+ // Debug logging: silent by default, enable with debug: true in plugin config
6
+ let debugEnabled = false;
7
+ const debug = (...args) => {
8
+ if (debugEnabled)
9
+ console.log(...args);
10
+ };
5
11
  // Module-level state
6
12
  let embedManager = null;
7
13
  let client = null;
@@ -13,6 +19,7 @@ let currentPluginConfig = null;
13
19
  // Track which banks have had their mission set (to avoid re-setting on every request)
14
20
  const banksWithMissionSet = new Set();
15
21
  const inflightRecalls = new Map();
22
+ const turnCountBySession = new Map();
16
23
  const RECALL_TIMEOUT_MS = 10_000;
17
24
  // Cooldown + guard to prevent concurrent reinit attempts
18
25
  let lastReinitAttempt = 0;
@@ -40,9 +47,9 @@ async function lazyReinit() {
40
47
  isReinitInProgress = false;
41
48
  return; // Only external API mode supports lazy reinit
42
49
  }
43
- console.log('[Hindsight] Attempting lazy re-initialization...');
50
+ debug('[Hindsight] Attempting lazy re-initialization...');
44
51
  try {
45
- await checkExternalApiHealth(externalApi.apiUrl);
52
+ await checkExternalApiHealth(externalApi.apiUrl, externalApi.apiToken);
46
53
  // Health check passed — set up env vars and create client
47
54
  process.env.HINDSIGHT_EMBED_API_URL = externalApi.apiUrl;
48
55
  if (externalApi.apiToken) {
@@ -59,7 +66,7 @@ async function lazyReinit() {
59
66
  isInitialized = true;
60
67
  // Replace the rejected initPromise with a resolved one
61
68
  initPromise = Promise.resolve();
62
- console.log('[Hindsight] ✓ Lazy re-initialization succeeded');
69
+ debug('[Hindsight] ✓ Lazy re-initialization succeeded');
63
70
  }
64
71
  catch (error) {
65
72
  console.warn(`[Hindsight] Lazy re-initialization failed (will retry in ${REINIT_COOLDOWN_MS / 1000}s):`, error instanceof Error ? error.message : error);
@@ -107,7 +114,7 @@ if (typeof global !== 'undefined') {
107
114
  try {
108
115
  await client.setBankMission(config.bankMission);
109
116
  banksWithMissionSet.add(bankId);
110
- console.log(`[Hindsight] Set mission for new bank: ${bankId}`);
117
+ debug(`[Hindsight] Set mission for new bank: ${bankId}`);
111
118
  }
112
119
  catch (error) {
113
120
  // Log but don't fail - bank mission is not critical
@@ -313,24 +320,28 @@ function buildClientOptions(llmConfig, pluginCfg, externalApi) {
313
320
  * Health check for external Hindsight API.
314
321
  * Retries up to 3 times with 2s delay — container DNS may not be ready on first boot.
315
322
  */
316
- async function checkExternalApiHealth(apiUrl) {
323
+ async function checkExternalApiHealth(apiUrl, apiToken) {
317
324
  const healthUrl = `${apiUrl.replace(/\/$/, '')}/health`;
318
325
  const maxRetries = 3;
319
326
  const retryDelay = 2000;
320
327
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
321
328
  try {
322
- console.log(`[Hindsight] Checking external API health at ${healthUrl}... (attempt ${attempt}/${maxRetries})`);
323
- const response = await fetch(healthUrl, { signal: AbortSignal.timeout(10000) });
329
+ debug(`[Hindsight] Checking external API health at ${healthUrl}... (attempt ${attempt}/${maxRetries})`);
330
+ const headers = {};
331
+ if (apiToken) {
332
+ headers['Authorization'] = `Bearer ${apiToken}`;
333
+ }
334
+ const response = await fetch(healthUrl, { signal: AbortSignal.timeout(10000), headers });
324
335
  if (!response.ok) {
325
336
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
326
337
  }
327
338
  const data = await response.json();
328
- console.log(`[Hindsight] External API health: ${JSON.stringify(data)}`);
339
+ debug(`[Hindsight] External API health: ${JSON.stringify(data)}`);
329
340
  return;
330
341
  }
331
342
  catch (error) {
332
343
  if (attempt < maxRetries) {
333
- console.log(`[Hindsight] Health check attempt ${attempt} failed, retrying in ${retryDelay}ms...`);
344
+ debug(`[Hindsight] Health check attempt ${attempt} failed, retrying in ${retryDelay}ms...`);
334
345
  await new Promise(resolve => setTimeout(resolve, retryDelay));
335
346
  }
336
347
  else {
@@ -358,37 +369,40 @@ function getPluginConfig(api) {
358
369
  dynamicBankId: config.dynamicBankId !== false,
359
370
  bankIdPrefix: config.bankIdPrefix,
360
371
  excludeProviders: Array.isArray(config.excludeProviders) ? config.excludeProviders : [],
372
+ autoRecall: config.autoRecall !== false, // Default: true (on) — backward compatible
373
+ retainEveryNTurns: config.retainEveryNTurns,
374
+ debug: config.debug ?? false,
361
375
  };
362
376
  }
363
377
  export default function (api) {
364
378
  try {
365
- console.log('[Hindsight] Plugin loading...');
366
- // Get plugin config first (needed for LLM detection)
367
- console.log('[Hindsight] Getting plugin config...');
379
+ debug('[Hindsight] Plugin loading...');
380
+ // Get plugin config first (needed for LLM detection and debug flag)
368
381
  const pluginConfig = getPluginConfig(api);
382
+ debugEnabled = pluginConfig.debug ?? false;
369
383
  // Store config globally for bank ID derivation in hooks
370
384
  currentPluginConfig = pluginConfig;
371
385
  // Detect LLM configuration (env vars > plugin config > auto-detect)
372
- console.log('[Hindsight] Detecting LLM config...');
386
+ debug('[Hindsight] Detecting LLM config...');
373
387
  const llmConfig = detectLLMConfig(pluginConfig);
374
388
  const baseUrlInfo = llmConfig.baseUrl ? `, base URL: ${llmConfig.baseUrl}` : '';
375
389
  const modelInfo = llmConfig.model || 'default';
376
390
  if (llmConfig.provider === 'ollama') {
377
- console.log(`[Hindsight] ✓ Using provider: ${llmConfig.provider}, model: ${modelInfo} (${llmConfig.source})`);
391
+ debug(`[Hindsight] ✓ Using provider: ${llmConfig.provider}, model: ${modelInfo} (${llmConfig.source})`);
378
392
  }
379
393
  else {
380
- console.log(`[Hindsight] ✓ Using provider: ${llmConfig.provider}, model: ${modelInfo} (${llmConfig.source}${baseUrlInfo})`);
394
+ debug(`[Hindsight] ✓ Using provider: ${llmConfig.provider}, model: ${modelInfo} (${llmConfig.source}${baseUrlInfo})`);
381
395
  }
382
396
  if (pluginConfig.bankMission) {
383
- console.log(`[Hindsight] Custom bank mission configured: "${pluginConfig.bankMission.substring(0, 50)}..."`);
397
+ debug(`[Hindsight] Custom bank mission configured: "${pluginConfig.bankMission.substring(0, 50)}..."`);
384
398
  }
385
399
  // Log dynamic bank ID mode
386
400
  if (pluginConfig.dynamicBankId) {
387
401
  const prefixInfo = pluginConfig.bankIdPrefix ? ` (prefix: ${pluginConfig.bankIdPrefix})` : '';
388
- console.log(`[Hindsight] ✓ Dynamic bank IDs enabled${prefixInfo} - each channel gets isolated memory`);
402
+ debug(`[Hindsight] ✓ Dynamic bank IDs enabled${prefixInfo} - each channel gets isolated memory`);
389
403
  }
390
404
  else {
391
- console.log(`[Hindsight] Dynamic bank IDs disabled - using static bank: ${DEFAULT_BANK_NAME}`);
405
+ debug(`[Hindsight] Dynamic bank IDs disabled - using static bank: ${DEFAULT_BANK_NAME}`);
392
406
  }
393
407
  // Detect external API mode
394
408
  const externalApi = detectExternalApi(pluginConfig);
@@ -397,64 +411,64 @@ export default function (api) {
397
411
  if (externalApi.apiUrl) {
398
412
  // External API mode - skip local daemon
399
413
  usingExternalApi = true;
400
- console.log(`[Hindsight] ✓ Using external API: ${externalApi.apiUrl}`);
414
+ debug(`[Hindsight] ✓ Using external API: ${externalApi.apiUrl}`);
401
415
  // Set env vars so CLI commands (uvx hindsight-embed) use external API
402
416
  process.env.HINDSIGHT_EMBED_API_URL = externalApi.apiUrl;
403
417
  if (externalApi.apiToken) {
404
418
  process.env.HINDSIGHT_EMBED_API_TOKEN = externalApi.apiToken;
405
- console.log('[Hindsight] API token configured');
419
+ debug('[Hindsight] API token configured');
406
420
  }
407
421
  }
408
422
  else {
409
- console.log(`[Hindsight] Daemon idle timeout: ${pluginConfig.daemonIdleTimeout}s (0 = never timeout)`);
410
- console.log(`[Hindsight] API Port: ${apiPort}`);
423
+ debug(`[Hindsight] Daemon idle timeout: ${pluginConfig.daemonIdleTimeout}s (0 = never timeout)`);
424
+ debug(`[Hindsight] API Port: ${apiPort}`);
411
425
  }
412
426
  // Initialize in background (non-blocking)
413
- console.log('[Hindsight] Starting initialization in background...');
427
+ debug('[Hindsight] Starting initialization in background...');
414
428
  initPromise = (async () => {
415
429
  try {
416
430
  if (usingExternalApi && externalApi.apiUrl) {
417
431
  // External API mode - check health, skip daemon startup
418
- console.log('[Hindsight] External API mode - skipping local daemon...');
419
- await checkExternalApiHealth(externalApi.apiUrl);
432
+ debug('[Hindsight] External API mode - skipping local daemon...');
433
+ await checkExternalApiHealth(externalApi.apiUrl, externalApi.apiToken);
420
434
  // Initialize client with direct HTTP mode
421
- console.log('[Hindsight] Creating HindsightClient (HTTP mode)...');
435
+ debug('[Hindsight] Creating HindsightClient (HTTP mode)...');
422
436
  client = new HindsightClient(buildClientOptions(llmConfig, pluginConfig, externalApi));
423
437
  // Set default bank (will be overridden per-request when dynamic bank IDs are enabled)
424
438
  const defaultBankId = deriveBankId(undefined, pluginConfig);
425
- console.log(`[Hindsight] Default bank: ${defaultBankId}`);
439
+ debug(`[Hindsight] Default bank: ${defaultBankId}`);
426
440
  client.setBankId(defaultBankId);
427
441
  // Note: Bank mission will be set per-bank when dynamic bank IDs are enabled
428
442
  // For now, set it on the default bank
429
443
  if (pluginConfig.bankMission && !pluginConfig.dynamicBankId) {
430
- console.log(`[Hindsight] Setting bank mission...`);
444
+ debug(`[Hindsight] Setting bank mission...`);
431
445
  await client.setBankMission(pluginConfig.bankMission);
432
446
  }
433
447
  isInitialized = true;
434
- console.log('[Hindsight] ✓ Ready (external API mode)');
448
+ debug('[Hindsight] ✓ Ready (external API mode)');
435
449
  }
436
450
  else {
437
451
  // Local daemon mode - start hindsight-embed daemon
438
- console.log('[Hindsight] Creating HindsightEmbedManager...');
452
+ debug('[Hindsight] Creating HindsightEmbedManager...');
439
453
  embedManager = new HindsightEmbedManager(apiPort, llmConfig.provider, llmConfig.apiKey, llmConfig.model, llmConfig.baseUrl, pluginConfig.daemonIdleTimeout, pluginConfig.embedVersion, pluginConfig.embedPackagePath);
440
454
  // Start the embedded server
441
- console.log('[Hindsight] Starting embedded server...');
455
+ debug('[Hindsight] Starting embedded server...');
442
456
  await embedManager.start();
443
457
  // Initialize client (local daemon mode — no apiUrl)
444
- console.log('[Hindsight] Creating HindsightClient (subprocess mode)...');
458
+ debug('[Hindsight] Creating HindsightClient (subprocess mode)...');
445
459
  client = new HindsightClient(buildClientOptions(llmConfig, pluginConfig, { apiUrl: null, apiToken: null }));
446
460
  // Set default bank (will be overridden per-request when dynamic bank IDs are enabled)
447
461
  const defaultBankId = deriveBankId(undefined, pluginConfig);
448
- console.log(`[Hindsight] Default bank: ${defaultBankId}`);
462
+ debug(`[Hindsight] Default bank: ${defaultBankId}`);
449
463
  client.setBankId(defaultBankId);
450
464
  // Note: Bank mission will be set per-bank when dynamic bank IDs are enabled
451
465
  // For now, set it on the default bank
452
466
  if (pluginConfig.bankMission && !pluginConfig.dynamicBankId) {
453
- console.log(`[Hindsight] Setting bank mission...`);
467
+ debug(`[Hindsight] Setting bank mission...`);
454
468
  await client.setBankMission(pluginConfig.bankMission);
455
469
  }
456
470
  isInitialized = true;
457
- console.log('[Hindsight] ✓ Ready');
471
+ debug('[Hindsight] ✓ Ready');
458
472
  }
459
473
  }
460
474
  catch (error) {
@@ -465,11 +479,11 @@ export default function (api) {
465
479
  // Suppress unhandled rejection — service.start() will await and handle errors
466
480
  initPromise.catch(() => { });
467
481
  // Register background service for cleanup
468
- console.log('[Hindsight] Registering service...');
482
+ debug('[Hindsight] Registering service...');
469
483
  api.registerService({
470
484
  id: 'hindsight-memory',
471
485
  async start() {
472
- console.log('[Hindsight] Service start called...');
486
+ debug('[Hindsight] Service start called...');
473
487
  // Wait for background init if still pending
474
488
  if (initPromise) {
475
489
  try {
@@ -485,8 +499,8 @@ export default function (api) {
485
499
  const externalApi = detectExternalApi(pluginConfig);
486
500
  if (externalApi.apiUrl && isInitialized) {
487
501
  try {
488
- await checkExternalApiHealth(externalApi.apiUrl);
489
- console.log('[Hindsight] External API is healthy');
502
+ await checkExternalApiHealth(externalApi.apiUrl, externalApi.apiToken);
503
+ debug('[Hindsight] External API is healthy');
490
504
  return;
491
505
  }
492
506
  catch (error) {
@@ -502,10 +516,10 @@ export default function (api) {
502
516
  if (embedManager && isInitialized) {
503
517
  const healthy = await embedManager.checkHealth();
504
518
  if (healthy) {
505
- console.log('[Hindsight] Daemon is healthy');
519
+ debug('[Hindsight] Daemon is healthy');
506
520
  return;
507
521
  }
508
- console.log('[Hindsight] Daemon is not responding - reinitializing...');
522
+ debug('[Hindsight] Daemon is not responding - reinitializing...');
509
523
  // Reset state for reinitialization
510
524
  embedManager = null;
511
525
  client = null;
@@ -514,7 +528,7 @@ export default function (api) {
514
528
  }
515
529
  // Reinitialize if needed (fresh start or recovery)
516
530
  if (!isInitialized) {
517
- console.log('[Hindsight] Reinitializing...');
531
+ debug('[Hindsight] Reinitializing...');
518
532
  const reinitPluginConfig = getPluginConfig(api);
519
533
  currentPluginConfig = reinitPluginConfig;
520
534
  const llmConfig = detectLLMConfig(reinitPluginConfig);
@@ -527,7 +541,7 @@ export default function (api) {
527
541
  if (externalApi.apiToken) {
528
542
  process.env.HINDSIGHT_EMBED_API_TOKEN = externalApi.apiToken;
529
543
  }
530
- await checkExternalApiHealth(externalApi.apiUrl);
544
+ await checkExternalApiHealth(externalApi.apiUrl, externalApi.apiToken);
531
545
  client = new HindsightClient(buildClientOptions(llmConfig, reinitPluginConfig, externalApi));
532
546
  const defaultBankId = deriveBankId(undefined, reinitPluginConfig);
533
547
  client.setBankId(defaultBankId);
@@ -535,7 +549,7 @@ export default function (api) {
535
549
  await client.setBankMission(reinitPluginConfig.bankMission);
536
550
  }
537
551
  isInitialized = true;
538
- console.log('[Hindsight] Reinitialization complete (external API mode)');
552
+ debug('[Hindsight] Reinitialization complete (external API mode)');
539
553
  }
540
554
  else {
541
555
  // Local daemon mode
@@ -548,13 +562,13 @@ export default function (api) {
548
562
  await client.setBankMission(reinitPluginConfig.bankMission);
549
563
  }
550
564
  isInitialized = true;
551
- console.log('[Hindsight] Reinitialization complete');
565
+ debug('[Hindsight] Reinitialization complete');
552
566
  }
553
567
  }
554
568
  },
555
569
  async stop() {
556
570
  try {
557
- console.log('[Hindsight] Service stopping...');
571
+ debug('[Hindsight] Service stopping...');
558
572
  // Only stop daemon if in local mode
559
573
  if (!usingExternalApi && embedManager) {
560
574
  await embedManager.stop();
@@ -562,7 +576,7 @@ export default function (api) {
562
576
  }
563
577
  client = null;
564
578
  isInitialized = false;
565
- console.log('[Hindsight] Service stopped');
579
+ debug('[Hindsight] Service stopped');
566
580
  }
567
581
  catch (error) {
568
582
  console.error('[Hindsight] Service stop error:', error);
@@ -570,9 +584,9 @@ export default function (api) {
570
584
  }
571
585
  },
572
586
  });
573
- console.log('[Hindsight] Plugin loaded successfully');
587
+ debug('[Hindsight] Plugin loaded successfully');
574
588
  // Register agent hooks for auto-recall and auto-retention
575
- console.log('[Hindsight] Registering agent hooks...');
589
+ debug('[Hindsight] Registering agent hooks...');
576
590
  // Store session key and context for retention
577
591
  let currentSessionKey;
578
592
  let currentAgentContext;
@@ -587,12 +601,17 @@ export default function (api) {
587
601
  currentAgentContext = ctx;
588
602
  // Check if this provider is excluded
589
603
  if (ctx?.messageProvider && pluginConfig.excludeProviders?.includes(ctx.messageProvider)) {
590
- console.log(`[Hindsight] Skipping recall for excluded provider: ${ctx.messageProvider}`);
604
+ debug(`[Hindsight] Skipping recall for excluded provider: ${ctx.messageProvider}`);
605
+ return;
606
+ }
607
+ // Skip auto-recall when disabled (agent has its own recall tool)
608
+ if (!pluginConfig.autoRecall) {
609
+ debug('[Hindsight] Auto-recall disabled via config, skipping');
591
610
  return;
592
611
  }
593
612
  // Derive bank ID from context
594
613
  const bankId = deriveBankId(ctx, pluginConfig);
595
- console.log(`[Hindsight] before_agent_start - bank: ${bankId}, channel: ${ctx?.messageProvider}/${ctx?.channelId}`);
614
+ debug(`[Hindsight] before_agent_start - bank: ${bankId}, channel: ${ctx?.messageProvider}/${ctx?.channelId}`);
596
615
  // Get the user's latest message for recall — only the raw user text, not the full prompt
597
616
  // rawMessage is clean user text; prompt includes envelope, system events, media notes, etc.
598
617
  const extracted = extractRecallQuery(event.rawMessage, event.prompt);
@@ -608,23 +627,23 @@ export default function (api) {
608
627
  // Wait for client to be ready
609
628
  const clientGlobal = global.__hindsightClient;
610
629
  if (!clientGlobal) {
611
- console.log('[Hindsight] Client global not available, skipping auto-recall');
630
+ debug('[Hindsight] Client global not available, skipping auto-recall');
612
631
  return;
613
632
  }
614
633
  await clientGlobal.waitForReady();
615
634
  // Get client configured for this context's bank (async to handle mission setup)
616
635
  const client = await clientGlobal.getClientForContext(ctx);
617
636
  if (!client) {
618
- console.log('[Hindsight] Client not initialized, skipping auto-recall');
637
+ debug('[Hindsight] Client not initialized, skipping auto-recall');
619
638
  return;
620
639
  }
621
- console.log(`[Hindsight] Auto-recall for bank ${bankId}, prompt: ${prompt.substring(0, 50)}`);
640
+ debug(`[Hindsight] Auto-recall for bank ${bankId}, prompt: ${prompt.substring(0, 50)}`);
622
641
  // Recall with deduplication: reuse in-flight request for same bank
623
642
  const recallKey = bankId;
624
643
  const existing = inflightRecalls.get(recallKey);
625
644
  let recallPromise;
626
645
  if (existing) {
627
- console.log(`[Hindsight] Reusing in-flight recall for bank ${bankId}`);
646
+ debug(`[Hindsight] Reusing in-flight recall for bank ${bankId}`);
628
647
  recallPromise = existing;
629
648
  }
630
649
  else {
@@ -634,7 +653,7 @@ export default function (api) {
634
653
  }
635
654
  const response = await recallPromise;
636
655
  if (!response.results || response.results.length === 0) {
637
- console.log('[Hindsight] No memories found for auto-recall');
656
+ debug('[Hindsight] No memories found for auto-recall');
638
657
  return;
639
658
  }
640
659
  // Format memories as JSON with all fields from recall
@@ -645,7 +664,7 @@ ${memoriesJson}
645
664
 
646
665
  User message: ${prompt}
647
666
  </hindsight_memories>`;
648
- console.log(`[Hindsight] Auto-recall: Injecting ${response.results.length} memories from bank ${bankId}`);
667
+ debug(`[Hindsight] Auto-recall: Injecting ${response.results.length} memories from bank ${bankId}`);
649
668
  // Inject context before the user message
650
669
  return { prependContext: contextMessage };
651
670
  }
@@ -669,15 +688,15 @@ User message: ${prompt}
669
688
  const effectiveCtx = ctx || currentAgentContext;
670
689
  // Check if this provider is excluded
671
690
  if (effectiveCtx?.messageProvider && pluginConfig.excludeProviders?.includes(effectiveCtx.messageProvider)) {
672
- console.log(`[Hindsight] Skipping retain for excluded provider: ${effectiveCtx.messageProvider}`);
691
+ debug(`[Hindsight] Skipping retain for excluded provider: ${effectiveCtx.messageProvider}`);
673
692
  return;
674
693
  }
675
694
  // Derive bank ID from context
676
695
  const bankId = deriveBankId(effectiveCtx, pluginConfig);
677
- console.log(`[Hindsight Hook] agent_end triggered - bank: ${bankId}`);
696
+ debug(`[Hindsight Hook] agent_end triggered - bank: ${bankId}`);
678
697
  // Check event success and messages
679
698
  if (!event.success || !Array.isArray(event.messages) || event.messages.length === 0) {
680
- console.log('[Hindsight Hook] Skipping: success:', event.success, 'messages:', event.messages?.length);
699
+ debug('[Hindsight Hook] Skipping: success:', event.success, 'messages:', event.messages?.length);
681
700
  return;
682
701
  }
683
702
  // Wait for client to be ready
@@ -693,8 +712,25 @@ User message: ${prompt}
693
712
  console.warn('[Hindsight] Client not initialized, skipping retain');
694
713
  return;
695
714
  }
715
+ // --- Chunked retention: only retain every Nth turn ---
716
+ const retainEveryN = pluginConfig.retainEveryNTurns ?? 10;
717
+ let messagesToRetain = event.messages;
718
+ if (retainEveryN > 1) {
719
+ const sessionTrackingKey = `${bankId}:${effectiveCtx?.sessionKey || currentSessionKey || 'session'}`;
720
+ const turnCount = (turnCountBySession.get(sessionTrackingKey) || 0) + 1;
721
+ turnCountBySession.set(sessionTrackingKey, turnCount);
722
+ if (turnCount % retainEveryN !== 0) {
723
+ const nextRetain = Math.ceil(turnCount / retainEveryN) * retainEveryN;
724
+ debug(`[Hindsight Hook] Skipping retain (turn ${turnCount}, next at ${nextRetain})`);
725
+ return;
726
+ }
727
+ // Sliding window: N turns of new content + 2-turn overlap for context continuity
728
+ const windowSize = retainEveryN * 2 + 4;
729
+ messagesToRetain = event.messages.slice(-windowSize);
730
+ debug(`[Hindsight Hook] Chunked retain at turn ${turnCount} \u2014 last ${messagesToRetain.length} msgs`);
731
+ }
696
732
  // Format messages into a transcript
697
- const transcript = event.messages
733
+ const transcript = messagesToRetain
698
734
  .map((msg) => {
699
735
  const role = msg.role || 'unknown';
700
736
  let content = '';
@@ -714,7 +750,7 @@ User message: ${prompt}
714
750
  })
715
751
  .join('\n\n');
716
752
  if (!transcript.trim() || transcript.length < 10) {
717
- console.log('[Hindsight Hook] Transcript too short, skipping');
753
+ debug('[Hindsight Hook] Transcript too short, skipping');
718
754
  return;
719
755
  }
720
756
  // Use unique document ID per conversation (sessionKey + timestamp)
@@ -726,19 +762,19 @@ User message: ${prompt}
726
762
  document_id: documentId,
727
763
  metadata: {
728
764
  retained_at: new Date().toISOString(),
729
- message_count: String(event.messages.length),
765
+ message_count: String(messagesToRetain.length),
730
766
  channel_type: effectiveCtx?.messageProvider,
731
767
  channel_id: effectiveCtx?.channelId,
732
768
  sender_id: effectiveCtx?.senderId,
733
769
  },
734
770
  });
735
- console.log(`[Hindsight] Retained ${event.messages.length} messages to bank ${bankId} for session ${documentId}`);
771
+ debug(`[Hindsight] Retained ${messagesToRetain.length} messages to bank ${bankId} for session ${documentId}`);
736
772
  }
737
773
  catch (error) {
738
774
  console.error('[Hindsight] Error retaining messages:', error);
739
775
  }
740
776
  });
741
- console.log('[Hindsight] Hooks registered');
777
+ debug('[Hindsight] Hooks registered');
742
778
  }
743
779
  catch (error) {
744
780
  console.error('[Hindsight] Plugin loading error:', error);
package/dist/types.d.ts CHANGED
@@ -39,6 +39,9 @@ export interface PluginConfig {
39
39
  dynamicBankId?: boolean;
40
40
  bankIdPrefix?: string;
41
41
  excludeProviders?: string[];
42
+ autoRecall?: boolean;
43
+ retainEveryNTurns?: number;
44
+ debug?: boolean;
42
45
  }
43
46
  export interface ServiceConfig {
44
47
  id: string;
@@ -63,6 +63,16 @@
63
63
  "bankIdPrefix": {
64
64
  "type": "string",
65
65
  "description": "Optional prefix for bank IDs (e.g., 'prod' results in 'prod-slack-U123'). Useful for separating environments."
66
+ },
67
+ "autoRecall": {
68
+ "type": "boolean",
69
+ "description": "Automatically recall memories on every prompt and inject them as context. Set to false when agent has its own recall tool.",
70
+ "default": true
71
+ },
72
+ "excludeProviders": {
73
+ "type": "array",
74
+ "items": { "type": "string" },
75
+ "description": "Message providers to exclude from recall and retain (e.g. ['telegram', 'discord'])"
66
76
  }
67
77
  },
68
78
  "additionalProperties": false
@@ -119,6 +129,14 @@
119
129
  "bankIdPrefix": {
120
130
  "label": "Bank ID Prefix",
121
131
  "placeholder": "e.g., prod, staging (optional)"
132
+ },
133
+ "autoRecall": {
134
+ "label": "Auto-Recall",
135
+ "placeholder": "true (inject memories on every prompt)"
136
+ },
137
+ "excludeProviders": {
138
+ "label": "Excluded Providers",
139
+ "placeholder": "e.g. telegram, discord"
122
140
  }
123
141
  }
124
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectorize-io/hindsight-openclaw",
3
- "version": "0.4.13",
3
+ "version": "0.4.15",
4
4
  "description": "Hindsight memory plugin for OpenClaw - biomimetic long-term memory with fact extraction",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",