diff --git a/hindsight-docs/docs-integrations/openclaw.md b/hindsight-docs/docs-integrations/openclaw.md index 7409e57c..5613a693 100644 --- a/hindsight-docs/docs-integrations/openclaw.md +++ b/hindsight-docs/docs-integrations/openclaw.md @@ -96,7 +96,8 @@ Optional settings in `~/.openclaw/openclaw.json`: - `embedVersion` - hindsight-embed version (default: `"latest"`) - `bankMission` - Agent identity/purpose stored on the memory bank. Helps the memory engine understand context for better fact extraction during retain. Set once per bank on first use — not a recall prompt. - `dynamicBankId` - Enable per-context memory banks (default: `true`) -- `bankIdPrefix` - Optional prefix for bank IDs (e.g. `"prod"` → `"prod-slack-C123"`) +- `bankId` - Static bank ID used when `dynamicBankId` is `false`. Can also be set with `HINDSIGHT_BANK_ID`. +- `bankIdPrefix` - Optional prefix for bank IDs (e.g. `"prod"` → `"prod-slack-C123"` or `"prod-shared-bank"`) - `dynamicBankGranularity` - Fields used to derive bank ID: `agent`, `channel`, `user`, `provider` (default: `["agent", "channel", "user"]`) - `excludeProviders` - Message providers to skip for recall/retain (e.g. `["slack"]`, `["telegram"]`, `["discord"]`) - `autoRecall` - Auto-inject memories before each turn (default: `true`). Set to `false` when the agent has its own recall tool. @@ -144,7 +145,7 @@ Available isolation fields: - `user` - The user interacting with the agent - `provider` - The message provider (e.g. Slack, Discord) -Use `bankIdPrefix` to namespace bank IDs across environments (e.g. `"prod"`, `"staging"`). Set `dynamicBankId` to `false` to use a single shared bank for all conversations. +Use `bankIdPrefix` to namespace bank IDs across environments (e.g. `"prod"`, `"staging"`). Set `dynamicBankId` to `false` to use a single shared bank for all conversations. In static mode, the plugin uses `bankId`, then `HINDSIGHT_BANK_ID`, then the default `openclaw` bank name. ### Retention Controls diff --git a/hindsight-integrations/openclaw/README.md b/hindsight-integrations/openclaw/README.md index dc9e59c4..b3121674 100644 --- a/hindsight-integrations/openclaw/README.md +++ b/hindsight-integrations/openclaw/README.md @@ -46,6 +46,7 @@ Optional settings in `~/.openclaw/openclaw.json` under `plugins.entries.hindsigh | `llmModel` | provider default | LLM model override used with `llmProvider` | | `llmApiKeyEnv` | provider standard env var | Custom env var name for the provider API key | | `dynamicBankId` | `true` | Enable per-context memory banks | +| `bankId` | — | Static bank ID used when `dynamicBankId` is `false`. Can also be set with `HINDSIGHT_BANK_ID`. | | `bankIdPrefix` | — | Prefix for bank IDs (e.g. `"prod"`) | | `dynamicBankGranularity` | `["agent", "channel", "user"]` | Fields used to derive bank ID. Options: `agent`, `channel`, `user`, `provider` | | `excludeProviders` | `[]` | Message providers to skip for recall/retain (e.g. `slack`, `telegram`, `discord`) | diff --git a/hindsight-integrations/openclaw/openclaw.plugin.json b/hindsight-integrations/openclaw/openclaw.plugin.json index 3c65579e..5d0d705e 100644 --- a/hindsight-integrations/openclaw/openclaw.plugin.json +++ b/hindsight-integrations/openclaw/openclaw.plugin.json @@ -68,6 +68,10 @@ "description": "Enable per-user memory banks. When true, memories are isolated by user per channel (e.g., slack-U123, telegram-456789). When false, all users share a single 'openclaw' bank.", "default": true }, + "bankId": { + "type": "string", + "description": "Static bank ID used when dynamicBankId is false. Can also be provided via HINDSIGHT_BANK_ID." + }, "bankIdPrefix": { "type": "string", "description": "Optional prefix for bank IDs (e.g., 'prod' results in 'prod-slack-U123'). Useful for separating environments." @@ -282,6 +286,10 @@ "label": "Dynamic Bank IDs", "placeholder": "true (isolate memories per channel)" }, + "bankId": { + "label": "Static Bank ID", + "placeholder": "e.g. openclaw, shared-bank (used when dynamicBankId is false)" + }, "bankIdPrefix": { "label": "Bank ID Prefix", "placeholder": "e.g., prod, staging (optional)" diff --git a/hindsight-integrations/openclaw/src/derive-bank-id.test.ts b/hindsight-integrations/openclaw/src/derive-bank-id.test.ts index 9cbf3d98..2c43c633 100644 --- a/hindsight-integrations/openclaw/src/derive-bank-id.test.ts +++ b/hindsight-integrations/openclaw/src/derive-bank-id.test.ts @@ -91,6 +91,17 @@ describe('deriveBankId', () => { expect(bankId).toBe('openclaw'); }); + it('should return configured bankId when dynamicBankId is false', () => { + const config: PluginConfig = { + dynamicBankId: false, + bankId: 'shared-bank', + bankIdPrefix: 'prod', + dynamicBankGranularity: ['provider', 'user'], + }; + const bankId = deriveBankId(ctx, config); + expect(bankId).toBe('prod-shared-bank'); + }); + it('should encode segments to prevent separator collisions', () => { const ctxWithSeparator: PluginHookAgentContext = { agentId: 'a::b', diff --git a/hindsight-integrations/openclaw/src/index.ts b/hindsight-integrations/openclaw/src/index.ts index e2b48bab..63774ba2 100644 --- a/hindsight-integrations/openclaw/src/index.ts +++ b/hindsight-integrations/openclaw/src/index.ts @@ -175,7 +175,7 @@ async function lazyReinit(): Promise { const defaultBankId = deriveBankId(undefined, config); client.setBankId(defaultBankId); - if (config.bankMission && !config.dynamicBankId) { + if (config.bankMission && usesStaticBank(config)) { await client.setBankMission(config.bankMission); } @@ -222,7 +222,7 @@ if (typeof global !== 'undefined') { getClientForContext: async (ctx: PluginHookAgentContext | undefined) => { if (!client) {return null;} const config = currentPluginConfig || {}; - if (config.dynamicBankId === false) { + if (usesStaticBank(config)) { return client; } const bankId = deriveBankId(ctx, config); @@ -244,7 +244,7 @@ if (typeof global !== 'undefined') { } // Set bank mission on first use of this bank (if configured) - if (config.bankMission && config.dynamicBankId && !banksWithMissionSet.has(bankId)) { + if (config.bankMission && !usesStaticBank(config) && !banksWithMissionSet.has(bankId)) { try { await bankClient.setBankMission(config.bankMission); banksWithMissionSet.add(bankId); @@ -268,6 +268,29 @@ const __dirname = dirname(__filename); // Default bank name (fallback when channel context not available) const DEFAULT_BANK_NAME = 'openclaw'; +function getConfiguredBankId(pluginConfig: PluginConfig): string | undefined { + if (typeof pluginConfig.bankId !== 'string') { + return undefined; + } + + const trimmed = pluginConfig.bankId.trim(); + return trimmed.length > 0 ? trimmed : undefined; +} + +function usesStaticBank(pluginConfig: PluginConfig): boolean { + return pluginConfig.dynamicBankId === false; +} + +function getDefaultBankId(pluginConfig: PluginConfig): string { + return pluginConfig.bankIdPrefix ? `${pluginConfig.bankIdPrefix}-${DEFAULT_BANK_NAME}` : DEFAULT_BANK_NAME; +} + +function getStaticBankId(pluginConfig: PluginConfig): string { + const configuredBankId = getConfiguredBankId(pluginConfig); + const baseBankId = configuredBankId || DEFAULT_BANK_NAME; + return pluginConfig.bankIdPrefix ? `${pluginConfig.bankIdPrefix}-${baseBankId}` : baseBankId; +} + /** * Strip plugin-injected memory tags from content to prevent retain feedback loop. * Removes and blocks that were injected @@ -515,12 +538,12 @@ function parseSessionKey(sessionKey: string): { agentId?: string; provider?: str export function deriveBankId(ctx: PluginHookAgentContext | undefined, pluginConfig: PluginConfig): string { if (pluginConfig.dynamicBankId === false) { - return pluginConfig.bankIdPrefix ? `${pluginConfig.bankIdPrefix}-openclaw` : 'openclaw'; + return getStaticBankId(pluginConfig); } // When no context is available, fall back to the static default bank. if (!ctx) { - return pluginConfig.bankIdPrefix ? `${pluginConfig.bankIdPrefix}-openclaw` : 'openclaw'; + return getDefaultBankId(pluginConfig); } const fields = pluginConfig.dynamicBankGranularity?.length ? pluginConfig.dynamicBankGranularity : ['agent', 'channel', 'user']; @@ -768,6 +791,9 @@ async function checkExternalApiHealth(apiUrl: string, apiToken?: string | null): function getPluginConfig(api: MoltbotPluginAPI): PluginConfig { const config = api.config.plugins?.entries?.['hindsight-openclaw']?.config || {}; const defaultMission = 'You are an AI assistant helping users across multiple communication channels (Telegram, Slack, Discord, etc.). Remember user preferences, instructions, and important context from conversations to provide personalized assistance.'; + const envBankId = typeof process.env.HINDSIGHT_BANK_ID === 'string' && process.env.HINDSIGHT_BANK_ID.trim().length > 0 + ? process.env.HINDSIGHT_BANK_ID.trim() + : undefined; return { bankMission: config.bankMission || defaultMission, @@ -783,6 +809,7 @@ function getPluginConfig(api: MoltbotPluginAPI): PluginConfig { apiPort: config.apiPort || 9077, // Dynamic bank ID options (default: enabled) dynamicBankId: config.dynamicBankId !== false, + bankId: envBankId || (typeof config.bankId === 'string' && config.bankId.trim().length > 0 ? config.bankId.trim() : undefined), bankIdPrefix: config.bankIdPrefix, excludeProviders: Array.isArray(config.excludeProviders) ? config.excludeProviders : [], autoRecall: config.autoRecall !== false, // Default: true (on) — backward compatible @@ -855,12 +882,13 @@ export default function (api: MoltbotPluginAPI) { debug(`[Hindsight] Custom bank mission configured: "${pluginConfig.bankMission.substring(0, 50)}..."`); } - // Log dynamic bank ID mode + // Log bank ID mode if (pluginConfig.dynamicBankId) { const prefixInfo = pluginConfig.bankIdPrefix ? ` (prefix: ${pluginConfig.bankIdPrefix})` : ''; debug(`[Hindsight] ✓ Dynamic bank IDs enabled${prefixInfo} - each channel gets isolated memory`); } else { - debug(`[Hindsight] Dynamic bank IDs disabled - using static bank: ${DEFAULT_BANK_NAME}`); + const sourceInfo = getConfiguredBankId(pluginConfig) ? 'configured' : 'default'; + debug(`[Hindsight] Dynamic bank IDs disabled - using ${sourceInfo} static bank: ${getStaticBankId(pluginConfig)}`); } // Detect external API mode @@ -933,7 +961,7 @@ export default function (api: MoltbotPluginAPI) { // Note: Bank mission will be set per-bank when dynamic bank IDs are enabled // For now, set it on the default bank - if (pluginConfig.bankMission && !pluginConfig.dynamicBankId) { + if (pluginConfig.bankMission && usesStaticBank(pluginConfig)) { debug(`[Hindsight] Setting bank mission...`); await client.setBankMission(pluginConfig.bankMission); } @@ -978,7 +1006,7 @@ export default function (api: MoltbotPluginAPI) { // Note: Bank mission will be set per-bank when dynamic bank IDs are enabled // For now, set it on the default bank - if (pluginConfig.bankMission && !pluginConfig.dynamicBankId) { + if (pluginConfig.bankMission && usesStaticBank(pluginConfig)) { debug(`[Hindsight] Setting bank mission...`); await client.setBankMission(pluginConfig.bankMission); } @@ -1070,7 +1098,7 @@ export default function (api: MoltbotPluginAPI) { const defaultBankId = deriveBankId(undefined, reinitPluginConfig); client.setBankId(defaultBankId); - if (reinitPluginConfig.bankMission && !reinitPluginConfig.dynamicBankId) { + if (reinitPluginConfig.bankMission && usesStaticBank(reinitPluginConfig)) { await client.setBankMission(reinitPluginConfig.bankMission); } @@ -1098,7 +1126,7 @@ export default function (api: MoltbotPluginAPI) { const defaultBankId = deriveBankId(undefined, reinitPluginConfig); client.setBankId(defaultBankId); - if (reinitPluginConfig.bankMission && !reinitPluginConfig.dynamicBankId) { + if (reinitPluginConfig.bankMission && usesStaticBank(reinitPluginConfig)) { await client.setBankMission(reinitPluginConfig.bankMission); } diff --git a/hindsight-integrations/openclaw/src/types.ts b/hindsight-integrations/openclaw/src/types.ts index 9ee903fb..8e6924c0 100644 --- a/hindsight-integrations/openclaw/src/types.ts +++ b/hindsight-integrations/openclaw/src/types.ts @@ -62,6 +62,7 @@ export interface PluginConfig { hindsightApiUrl?: string; // External Hindsight API URL (skips local daemon when set) hindsightApiToken?: string; // API token for external Hindsight API authentication dynamicBankId?: boolean; // Enable per-channel memory banks (default: true) + bankId?: string; // Static bank ID used when dynamicBankId is false. Can also be set via HINDSIGHT_BANK_ID. bankIdPrefix?: string; // Prefix for bank IDs (e.g. 'prod' -> 'prod-slack-C123') excludeProviders?: string[]; // Message providers to exclude from recall/retain (e.g. ['telegram', 'discord']) autoRecall?: boolean; // Auto-recall memories on every prompt (default: true). Set to false when agent has its own recall tool.