@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 +101 -65
- package/dist/types.d.ts +3 -0
- package/openclaw.plugin.json +18 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
323
|
-
const
|
|
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
|
-
|
|
339
|
+
debug(`[Hindsight] External API health: ${JSON.stringify(data)}`);
|
|
329
340
|
return;
|
|
330
341
|
}
|
|
331
342
|
catch (error) {
|
|
332
343
|
if (attempt < maxRetries) {
|
|
333
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
391
|
+
debug(`[Hindsight] ✓ Using provider: ${llmConfig.provider}, model: ${modelInfo} (${llmConfig.source})`);
|
|
378
392
|
}
|
|
379
393
|
else {
|
|
380
|
-
|
|
394
|
+
debug(`[Hindsight] ✓ Using provider: ${llmConfig.provider}, model: ${modelInfo} (${llmConfig.source}${baseUrlInfo})`);
|
|
381
395
|
}
|
|
382
396
|
if (pluginConfig.bankMission) {
|
|
383
|
-
|
|
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
|
-
|
|
402
|
+
debug(`[Hindsight] ✓ Dynamic bank IDs enabled${prefixInfo} - each channel gets isolated memory`);
|
|
389
403
|
}
|
|
390
404
|
else {
|
|
391
|
-
|
|
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
|
-
|
|
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
|
-
|
|
419
|
+
debug('[Hindsight] API token configured');
|
|
406
420
|
}
|
|
407
421
|
}
|
|
408
422
|
else {
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
444
|
+
debug(`[Hindsight] Setting bank mission...`);
|
|
431
445
|
await client.setBankMission(pluginConfig.bankMission);
|
|
432
446
|
}
|
|
433
447
|
isInitialized = true;
|
|
434
|
-
|
|
448
|
+
debug('[Hindsight] ✓ Ready (external API mode)');
|
|
435
449
|
}
|
|
436
450
|
else {
|
|
437
451
|
// Local daemon mode - start hindsight-embed daemon
|
|
438
|
-
|
|
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
|
-
|
|
455
|
+
debug('[Hindsight] Starting embedded server...');
|
|
442
456
|
await embedManager.start();
|
|
443
457
|
// Initialize client (local daemon mode — no apiUrl)
|
|
444
|
-
|
|
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
|
-
|
|
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
|
-
|
|
467
|
+
debug(`[Hindsight] Setting bank mission...`);
|
|
454
468
|
await client.setBankMission(pluginConfig.bankMission);
|
|
455
469
|
}
|
|
456
470
|
isInitialized = true;
|
|
457
|
-
|
|
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
|
-
|
|
482
|
+
debug('[Hindsight] Registering service...');
|
|
469
483
|
api.registerService({
|
|
470
484
|
id: 'hindsight-memory',
|
|
471
485
|
async start() {
|
|
472
|
-
|
|
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
|
-
|
|
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
|
-
|
|
519
|
+
debug('[Hindsight] Daemon is healthy');
|
|
506
520
|
return;
|
|
507
521
|
}
|
|
508
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
565
|
+
debug('[Hindsight] Reinitialization complete');
|
|
552
566
|
}
|
|
553
567
|
}
|
|
554
568
|
},
|
|
555
569
|
async stop() {
|
|
556
570
|
try {
|
|
557
|
-
|
|
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
|
-
|
|
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
|
-
|
|
587
|
+
debug('[Hindsight] Plugin loaded successfully');
|
|
574
588
|
// Register agent hooks for auto-recall and auto-retention
|
|
575
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
637
|
+
debug('[Hindsight] Client not initialized, skipping auto-recall');
|
|
619
638
|
return;
|
|
620
639
|
}
|
|
621
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
package/openclaw.plugin.json
CHANGED
|
@@ -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