From 3619fe49c463ee95b14c5881127c022ce737dfea Mon Sep 17 00:00:00 2001 From: Prasanna721 Date: Mon, 16 Feb 2026 19:03:31 -0800 Subject: [PATCH 1/3] feat: v2.0 - Custom container tags with AI-driven routing v2.0: Custom Container Tags & Advanced Setup Features - Custom container tags with AI-driven routing - containerTag parameter on all tools (store, search, forget, profile) - enableCustomContainerTags config option - customContainers array with tag + description - customContainerInstructions for AI routing guidance - setup-advanced CLI command for full configuration - status CLI command - Current channel injection for context --- .gitignore | 1 + README.md | 141 ++++++++++++------- client.ts | 43 ++++-- commands/cli.ts | 329 ++++++++++++++++++++++++++++++++++++++++++- commands/slash.ts | 28 +++- config.ts | 78 ++++++++-- hooks/capture.ts | 4 + hooks/recall.ts | 59 +++++++- index.ts | 19 ++- openclaw.plugin.json | 30 +++- tools/forget.ts | 23 ++- tools/profile.ts | 20 ++- tools/search.ts | 18 ++- tools/store.ts | 13 +- 14 files changed, 704 insertions(+), 102 deletions(-) diff --git a/.gitignore b/.gitignore index 59be53c..9763a2b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ dist/ .arch/ lib/*.ts !lib/*.d.ts +temp/ *.tsbuildinfo .DS_Store diff --git a/README.md b/README.md index cb9910f..b25ff44 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,10 @@ -# Supermemory Plugin for OpenClaw (previously Clawdbot) +# OpenClaw Supermemory Plugin Announcement-3 (2) - - Long-term memory for OpenClaw. Automatically remembers conversations, recalls relevant context, and builds a persistent user profile — all powered by [Supermemory](https://supermemory.ai) cloud. No local infrastructure required. -> **✨ Requires [Supermemory Pro or above](https://console.supermemory.ai/billing)** - Unlock the state of the art memory for your OpenClaw bot. +> **Requires [Supermemory Pro or above](https://console.supermemory.ai/billing)** - Unlock the state of the art memory for your OpenClaw bot. ## Install @@ -16,76 +14,113 @@ openclaw plugins install @supermemory/openclaw-supermemory Restart OpenClaw after installing. -## Configuration - -The only required value is your Supermemory API key. Get one at [console.supermemory.ai](https://console.supermemory.ai). - -Set it as an environment variable: +## Setup ```bash -export SUPERMEMORY_OPENCLAW_API_KEY="sm_..." +openclaw supermemory setup ``` -Or configure it directly in `openclaw.json`: +Enter your API key from [console.supermemory.ai](https://console.supermemory.ai). That's it. -```json5 -{ - "plugins": { - "entries": { - "openclaw-supermemory": { - "enabled": true, - "config": { - "apiKey": "${SUPERMEMORY_OPENCLAW_API_KEY}" - } - } - } - } -} -``` +### Advanced Setup -### Advanced options +```bash +openclaw supermemory setup-advanced +``` -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `containerTag` | `string` | `openclaw_{hostname}` | Memory namespace. All channels share this tag. | -| `autoRecall` | `boolean` | `true` | Inject relevant memories before every AI turn. | -| `autoCapture` | `boolean` | `true` | Automatically store conversation content after every turn. | -| `maxRecallResults` | `number` | `10` | Max memories injected into context per turn. | -| `profileFrequency` | `number` | `50` | Inject full user profile every N turns. Search results are injected every turn. | -| `captureMode` | `string` | `"all"` | `"all"` filters short texts and injected context. `"everything"` captures all messages. | -| `debug` | `boolean` | `false` | Verbose debug logs for API calls and responses. | +Configure all options interactively: container tag, auto-recall, auto-capture, capture mode, custom container tags, and more. ## How it works -Once installed, the plugin works automatically with zero interaction: +Once installed, the plugin works automatically: -- **Auto-Recall** — Before every AI turn, the plugin queries Supermemory for relevant memories and injects them as context. The AI sees your user profile (preferences, facts) and semantically similar past conversations. -- **Auto-Capture** — After every AI turn, the last user/assistant exchange is sent to Supermemory for extraction and long-term storage. +- **Auto-Recall** — Before every AI turn, queries Supermemory for relevant memories and injects them as context. The AI sees your user profile and semantically similar past conversations. +- **Auto-Capture** — After every AI turn, the conversation is sent to Supermemory for extraction and long-term storage. +- **Custom Container Tags** — Define custom memory containers (e.g., `work`, `personal`, `bookmarks`). The AI automatically picks the right container based on your instructions when using memory tools. -Everything runs in the cloud. Supermemory handles extraction, deduplication, and profile building on its end. +Everything runs in the cloud. Supermemory handles extraction, deduplication, and profile building. ## Slash Commands -| Command | Description | -|---------|-------------| -| `/remember ` | Manually save something to memory. | -| `/recall ` | Search your memories and see results with similarity scores. | +| Command | Description | +| ------------------ | --------------------------------------- | +| `/remember ` | Manually save something to memory. | +| `/recall ` | Search memories with similarity scores. | ## AI Tools -The AI can use these tools autonomously during conversations: +The AI uses these tools autonomously. With custom container tags enabled, all tools support a `containerTag` parameter for routing to specific containers. -| Tool | Description | -|------|-------------| -| `supermemory_store` | Save information to long-term memory. | -| `supermemory_search` | Search memories by query. | -| `supermemory_forget` | Delete a memory by query. | -| `supermemory_profile` | View the user profile (persistent facts + recent context). | +| Tool | Description | +| --------------------- | ------------------------------------------------------ | +| `supermemory_store` | Save information to memory. | +| `supermemory_search` | Search memories by query. | +| `supermemory_forget` | Delete a memory by query or ID. | +| `supermemory_profile` | View user profile (persistent facts + recent context). | ## CLI Commands ```bash -openclaw supermemory search # Search memories -openclaw supermemory profile # View user profile -openclaw supermemory wipe # Delete all memories (destructive, requires confirmation) +openclaw supermemory setup # Configure API key +openclaw supermemory setup-advanced # Configure all options +openclaw supermemory status # View current configuration +openclaw supermemory search # Search memories +openclaw supermemory profile # View user profile +openclaw supermemory wipe # Delete all memories (requires confirmation) +``` + +## Configuration + +Set API key via environment variable: + +```bash +export SUPERMEMORY_OPENCLAW_API_KEY="sm_..." +``` + +Or configure in `~/.openclaw/openclaw.json`: + +### Options + +| Key | Type | Default | Description | +| ----------------------------- | --------- | --------------------- | --------------------------------------------------------- | +| `apiKey` | `string` | — | Supermemory API key. | +| `containerTag` | `string` | `openclaw_{hostname}` | Root memory namespace. | +| `autoRecall` | `boolean` | `true` | Inject relevant memories before every AI turn. | +| `autoCapture` | `boolean` | `true` | Store conversations after every turn. | +| `maxRecallResults` | `number` | `10` | Max memories injected per turn. | +| `profileFrequency` | `number` | `50` | Inject full profile every N turns. | +| `captureMode` | `string` | `"all"` | `"all"` filters short texts, `"everything"` captures all. | +| `debug` | `boolean` | `false` | Verbose debug logs. | +| `enableCustomContainerTags` | `boolean` | `false` | Enable custom container routing. | +| `customContainers` | `array` | `[]` | Custom containers with `tag` and `description`. | +| `customContainerInstructions` | `string` | `""` | Instructions for AI on container routing. | + +### Full Example + +```json +{ + "plugins": { + "entries": { + "openclaw-supermemory": { + "enabled": true, + "config": { + "apiKey": "${SUPERMEMORY_OPENCLAW_API_KEY}", + "containerTag": "my_memory", + "autoRecall": true, + "autoCapture": true, + "maxRecallResults": 10, + "profileFrequency": 50, + "captureMode": "all", + "debug": false, + "enableCustomContainerTags": true, + "customContainers": [ + { "tag": "work", "description": "Work-related memories" }, + { "tag": "personal", "description": "Personal notes" } + ], + "customContainerInstructions": "Store work tasks in 'work', personal stuff in 'personal'" + } + } + } + } +} ``` diff --git a/client.ts b/client.ts index afee359..fc7a0ae 100644 --- a/client.ts +++ b/client.ts @@ -61,18 +61,21 @@ export class SupermemoryClient { content: string, metadata?: Record, customId?: string, + containerTag?: string, ): Promise<{ id: string }> { const cleaned = sanitizeContent(content) + const tag = containerTag ?? this.containerTag log.debugRequest("add", { contentLength: cleaned.length, customId, metadata, + containerTag: tag, }) const result = await this.client.add({ content: cleaned, - containerTag: this.containerTag, + containerTag: tag, ...(metadata && { metadata }), ...(customId && { customId }), }) @@ -81,16 +84,22 @@ export class SupermemoryClient { return { id: result.id } } - async search(query: string, limit = 5): Promise { + async search( + query: string, + limit = 5, + containerTag?: string, + ): Promise { + const tag = containerTag ?? this.containerTag + log.debugRequest("search.memories", { query, limit, - containerTag: this.containerTag, + containerTag: tag, }) const response = await this.client.search.memories({ q: query, - containerTag: this.containerTag, + containerTag: tag, limit, }) @@ -106,11 +115,13 @@ export class SupermemoryClient { return results } - async getProfile(query?: string): Promise { - log.debugRequest("profile", { containerTag: this.containerTag, query }) + async getProfile(query?: string, containerTag?: string): Promise { + const tag = containerTag ?? this.containerTag + + log.debugRequest("profile", { containerTag: tag, query }) const response = await this.client.profile({ - containerTag: this.containerTag, + containerTag: tag, ...(query && { q: query }), }) @@ -131,13 +142,18 @@ export class SupermemoryClient { return result } - async deleteMemory(id: string): Promise<{ id: string; forgotten: boolean }> { + async deleteMemory( + id: string, + containerTag?: string, + ): Promise<{ id: string; forgotten: boolean }> { + const tag = containerTag ?? this.containerTag + log.debugRequest("memories.delete", { id, - containerTag: this.containerTag, + containerTag: tag, }) const result = await this.client.memories.forget({ - containerTag: this.containerTag, + containerTag: tag, id, }) log.debugResponse("memories.delete", result) @@ -146,16 +162,17 @@ export class SupermemoryClient { async forgetByQuery( query: string, + containerTag?: string, ): Promise<{ success: boolean; message: string }> { - log.debugRequest("forgetByQuery", { query }) + log.debugRequest("forgetByQuery", { query, containerTag }) - const results = await this.search(query, 5) + const results = await this.search(query, 5, containerTag) if (results.length === 0) { return { success: false, message: "No matching memory found to forget." } } const target = results[0] - await this.deleteMemory(target.id) + await this.deleteMemory(target.id, containerTag) const preview = limitText(target.content || target.memory || "", 100) return { success: true, message: `Forgot: "${preview}"` } diff --git a/commands/cli.ts b/commands/cli.ts index 09df7d7..8184c22 100644 --- a/commands/cli.ts +++ b/commands/cli.ts @@ -1,8 +1,328 @@ +import * as fs from "node:fs" +import * as os from "node:os" +import * as path from "node:path" +import * as readline from "node:readline" import type { OpenClawPluginApi } from "openclaw/plugin-sdk" import type { SupermemoryClient } from "../client.ts" import type { SupermemoryConfig } from "../config.ts" import { log } from "../logger.ts" +export function registerCliSetup(api: OpenClawPluginApi): void { + api.registerCli( + // biome-ignore lint/suspicious/noExplicitAny: openclaw SDK does not ship types + ({ program }: { program: any }) => { + const cmd = program + .command("supermemory") + .description("Supermemory long-term memory commands") + + cmd + .command("setup") + .description("Configure Supermemory API key") + .action(async () => { + const configDir = path.join(os.homedir(), ".openclaw") + const configPath = path.join(configDir, "openclaw.json") + + console.log("\n🧠 Supermemory Setup\n") + console.log("Get your API key from: https://console.supermemory.ai\n") + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }) + + const apiKey = await new Promise((resolve) => { + rl.question("Enter your Supermemory API key: ", resolve) + }) + rl.close() + + if (!apiKey.trim()) { + console.log("\nNo API key provided. Setup cancelled.") + return + } + + if (!apiKey.startsWith("sm_")) { + console.log("\nWarning: API key should start with 'sm_'") + } + + let config: Record = {} + if (fs.existsSync(configPath)) { + try { + config = JSON.parse(fs.readFileSync(configPath, "utf-8")) + } catch { + config = {} + } + } + + if (!config.plugins) config.plugins = {} + const plugins = config.plugins as Record + if (!plugins.entries) plugins.entries = {} + const entries = plugins.entries as Record + + entries["openclaw-supermemory"] = { + enabled: true, + config: { + apiKey: apiKey.trim(), + }, + } + + if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir, { recursive: true }) + } + + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)) + + console.log("\n✓ API key saved to ~/.openclaw/openclaw.json") + console.log(" Restart OpenClaw to apply changes: openclaw gateway --force\n") + }) + + cmd + .command("setup-advanced") + .description("Configure Supermemory with all options") + .action(async () => { + const configDir = path.join(os.homedir(), ".openclaw") + const configPath = path.join(configDir, "openclaw.json") + const defaultTag = os.hostname().replace(/[^a-zA-Z0-9_]/g, "_") + + console.log("\n🧠 Supermemory Advanced Setup\n") + console.log("Press Enter to use default values shown in [brackets]\n") + console.log("Get your API key from: https://console.supermemory.ai\n") + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }) + + const ask = (question: string): Promise => + new Promise((resolve) => rl.question(question, resolve)) + + const apiKey = await ask("API key (required): ") + if (!apiKey.trim()) { + console.log("\nNo API key provided. Setup cancelled.") + rl.close() + return + } + + if (!apiKey.startsWith("sm_")) { + console.log("Warning: API key should start with 'sm_'\n") + } + + const containerTag = await ask(`Container tag [openclaw_${defaultTag}]: `) + + console.log("\nAuto-recall:") + console.log(" true - Inject relevant memories before each AI response (recommended)") + console.log(" false - Disable automatic memory recall") + const autoRecallInput = await ask("Auto-recall (true/false) [true]: ") + let autoRecall = true + if (autoRecallInput.trim().toLowerCase() === "false") { + autoRecall = false + } else if (autoRecallInput.trim() && autoRecallInput.trim().toLowerCase() !== "true") { + console.log(" Invalid value, using default: true") + } + + console.log("\nAuto-capture:") + console.log(" true - Save conversations to memory after each AI response (recommended)") + console.log(" false - Disable automatic conversation capture") + const autoCaptureInput = await ask("Auto-capture (true/false) [true]: ") + let autoCapture = true + if (autoCaptureInput.trim().toLowerCase() === "false") { + autoCapture = false + } else if (autoCaptureInput.trim() && autoCaptureInput.trim().toLowerCase() !== "true") { + console.log(" Invalid value, using default: true") + } + + const maxResultsInput = await ask("Max memories to recall per turn (1-20) [10]: ") + let maxRecallResults = 10 + const parsedMax = Number.parseInt(maxResultsInput.trim(), 10) + if (maxResultsInput.trim()) { + if (parsedMax >= 1 && parsedMax <= 20) { + maxRecallResults = parsedMax + } else { + console.log(" Invalid value, using default: 10") + } + } + + const profileFreqInput = await ask("Inject full profile every N turns (1-500) [50]: ") + let profileFrequency = 50 + const parsedFreq = Number.parseInt(profileFreqInput.trim(), 10) + if (profileFreqInput.trim()) { + if (parsedFreq >= 1 && parsedFreq <= 500) { + profileFrequency = parsedFreq + } else { + console.log(" Invalid value, using default: 50") + } + } + + console.log("\nCapture mode:") + console.log(" all - Filter short texts and context blocks (recommended)") + console.log(" everything - Capture all messages without filtering") + const captureModeInput = await ask("Capture mode (all/everything) [all]: ") + let captureMode: "all" | "everything" = "all" + if (captureModeInput.trim().toLowerCase() === "everything") { + captureMode = "everything" + } else if (captureModeInput.trim() && captureModeInput.trim().toLowerCase() !== "all") { + console.log(" Invalid value, using default: all") + } + + console.log("\n--- Custom Container Tags (Advanced) ---") + console.log("Define custom containers for AI-driven memory routing.") + const enableCustomContainerTagsInput = await ask("Enable custom container tags? (true/false) [false]: ") + let enableCustomContainerTags = false + if (enableCustomContainerTagsInput.trim().toLowerCase() === "true") { + enableCustomContainerTags = true + } else if (enableCustomContainerTagsInput.trim() && enableCustomContainerTagsInput.trim().toLowerCase() !== "false") { + console.log(" Invalid value, using default: false") + } + + console.log("\nAdd custom containers (tag:description). Leave blank when done.") + const customContainers: Array<{ tag: string; description: string }> = [] + while (true) { + const containerInput = await ask("Container (e.g. work:Work projects): ") + if (!containerInput.trim()) break + const [tag, ...descParts] = containerInput.split(":") + const description = descParts.join(":").trim() + if (tag && description) { + customContainers.push({ + tag: tag.trim().replace(/[^a-zA-Z0-9_]/g, "_"), + description, + }) + console.log(` Added: ${tag.trim()} → ${description}`) + } else { + console.log(" Invalid format. Use tag:description") + } + } + + const customContainerInstructions = await ask("Custom container tag instructions (optional): ") + + rl.close() + + let config: Record = {} + if (fs.existsSync(configPath)) { + try { + config = JSON.parse(fs.readFileSync(configPath, "utf-8")) + } catch { + config = {} + } + } + + if (!config.plugins) config.plugins = {} + const plugins = config.plugins as Record + if (!plugins.entries) plugins.entries = {} + const entries = plugins.entries as Record + + const pluginConfig: Record = { + apiKey: apiKey.trim(), + } + + if (containerTag.trim()) { + pluginConfig.containerTag = containerTag.trim().replace(/[^a-zA-Z0-9_]/g, "_") + } + if (!autoRecall) pluginConfig.autoRecall = false + if (!autoCapture) pluginConfig.autoCapture = false + if (maxRecallResults !== 10) pluginConfig.maxRecallResults = maxRecallResults + if (profileFrequency !== 50) pluginConfig.profileFrequency = profileFrequency + if (captureMode !== "all") pluginConfig.captureMode = captureMode + if (enableCustomContainerTags) pluginConfig.enableCustomContainerTags = true + if (customContainerInstructions.trim()) { + pluginConfig.customContainerInstructions = customContainerInstructions.trim() + } + if (customContainers.length > 0) { + pluginConfig.customContainers = customContainers + } + + entries["openclaw-supermemory"] = { + enabled: true, + config: pluginConfig, + } + + if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir, { recursive: true }) + } + + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)) + + console.log("\n✓ Configuration saved to ~/.openclaw/openclaw.json") + console.log("\nSettings:") + console.log(` API key: ${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`) + console.log(` Container tag: ${containerTag.trim() || `openclaw_${defaultTag}`}`) + console.log(` Auto-recall: ${autoRecall}`) + console.log(` Auto-capture: ${autoCapture}`) + console.log(` Max results: ${maxRecallResults}`) + console.log(` Profile freq: ${profileFrequency}`) + console.log(` Capture mode: ${captureMode}`) + console.log(` Custom containers: ${enableCustomContainerTags ? "enabled" : "disabled"}`) + console.log(` Custom containers: ${customContainers.length}`) + if (customContainerInstructions.trim()) { + console.log(` Routing instructions: "${customContainerInstructions.trim().slice(0, 50)}${customContainerInstructions.length > 50 ? "..." : ""}"`) + } + console.log("\nRestart OpenClaw to apply: openclaw gateway --force\n") + }) + + cmd + .command("status") + .description("Check Supermemory configuration status") + .action(async () => { + const configPath = path.join(os.homedir(), ".openclaw", "openclaw.json") + const envKey = process.env.SUPERMEMORY_OPENCLAW_API_KEY + const defaultTag = `openclaw_${os.hostname().replace(/[^a-zA-Z0-9_]/g, "_")}` + + console.log("\n🧠 Supermemory Status\n") + + let apiKeySource = "" + let apiKeyDisplay = "" + let pluginConfig: Record = {} + let enabled = true + + if (envKey) { + apiKeySource = "environment" + apiKeyDisplay = `${envKey.slice(0, 8)}...${envKey.slice(-4)}` + } + + if (fs.existsSync(configPath)) { + try { + const config = JSON.parse(fs.readFileSync(configPath, "utf-8")) + const entry = config?.plugins?.entries?.["openclaw-supermemory"] + if (entry) { + enabled = entry.enabled ?? true + pluginConfig = entry.config ?? {} + if (pluginConfig.apiKey && !envKey) { + const key = pluginConfig.apiKey as string + apiKeySource = "config" + apiKeyDisplay = `${key.slice(0, 8)}...${key.slice(-4)}` + } + } + } catch { + console.log("✗ Could not read config file\n") + return + } + } + + if (!apiKeyDisplay) { + console.log("✗ No API key configured") + console.log(" Run: openclaw supermemory setup\n") + return + } + + const customContainers = Array.isArray(pluginConfig.customContainers) + ? pluginConfig.customContainers + : [] + + console.log(`✓ API key: ${apiKeyDisplay} (from ${apiKeySource})`) + console.log(` Enabled: ${enabled}`) + console.log(` Container tag: ${pluginConfig.containerTag ?? defaultTag}`) + console.log(` Auto-recall: ${pluginConfig.autoRecall ?? true}`) + console.log(` Auto-capture: ${pluginConfig.autoCapture ?? true}`) + console.log(` Max results: ${pluginConfig.maxRecallResults ?? 10}`) + console.log(` Profile freq: ${pluginConfig.profileFrequency ?? 50}`) + console.log(` Capture mode: ${pluginConfig.captureMode ?? "all"}`) + console.log(` Custom containers: ${pluginConfig.enableCustomContainerTags ? "enabled" : "disabled"}`) + console.log(` Custom containers: ${customContainers.length}`) + console.log("") + }) + }, + { commands: ["supermemory"] }, + ) +} + export function registerCli( api: OpenClawPluginApi, client: SupermemoryClient, @@ -11,9 +331,11 @@ export function registerCli( api.registerCli( // biome-ignore lint/suspicious/noExplicitAny: openclaw SDK does not ship types ({ program }: { program: any }) => { - const cmd = program - .command("supermemory") - .description("Supermemory long-term memory commands") + const cmd = program.commands.find( + // biome-ignore lint/suspicious/noExplicitAny: openclaw SDK does not ship types + (c: any) => c.name() === "supermemory", + ) + if (!cmd) return cmd .command("search") @@ -67,7 +389,6 @@ export function registerCli( .description("Delete ALL memories for this container tag") .action(async () => { const tag = client.getContainerTag() - const readline = await import("node:readline") const rl = readline.createInterface({ input: process.stdin, output: process.stdout, diff --git a/commands/slash.ts b/commands/slash.ts index f9dc795..48f437a 100644 --- a/commands/slash.ts +++ b/commands/slash.ts @@ -4,6 +4,32 @@ import type { SupermemoryConfig } from "../config.ts" import { log } from "../logger.ts" import { buildDocumentId, detectCategory } from "../memory.ts" +export function registerStubCommands(api: OpenClawPluginApi): void { + api.registerCommand({ + name: "remember", + description: "Save something to memory", + acceptsArgs: true, + requireAuth: true, + handler: async () => { + return { + text: "Supermemory not configured. Run 'openclaw supermemory setup' first.", + } + }, + }) + + api.registerCommand({ + name: "recall", + description: "Search your memories", + acceptsArgs: true, + requireAuth: true, + handler: async () => { + return { + text: "Supermemory not configured. Run 'openclaw supermemory setup' first.", + } + }, + }) +} + export function registerCommands( api: OpenClawPluginApi, client: SupermemoryClient, @@ -55,7 +81,7 @@ export function registerCommands( log.debug(`/recall command: "${query}"`) try { - const results = await client.search(query, 5) + const results = await client.search(query, _cfg.maxRecallResults) if (results.length === 0) { return { text: `No memories found for: "${query}"` } diff --git a/config.ts b/config.ts index a687aa4..313a204 100644 --- a/config.ts +++ b/config.ts @@ -2,8 +2,13 @@ import { hostname } from "node:os" export type CaptureMode = "everything" | "all" +export type CustomContainer = { + tag: string + description: string +} + export type SupermemoryConfig = { - apiKey: string + apiKey: string | undefined containerTag: string autoRecall: boolean autoCapture: boolean @@ -11,6 +16,9 @@ export type SupermemoryConfig = { profileFrequency: number captureMode: CaptureMode debug: boolean + enableCustomContainerTags: boolean + customContainers: CustomContainer[] + customContainerInstructions: string } const ALLOWED_KEYS = [ @@ -22,6 +30,9 @@ const ALLOWED_KEYS = [ "profileFrequency", "captureMode", "debug", + "enableCustomContainerTags", + "customContainers", + "customContainerInstructions", ] function assertAllowedKeys( @@ -66,15 +77,31 @@ export function parseConfig(raw: unknown): SupermemoryConfig { assertAllowedKeys(cfg, ALLOWED_KEYS, "supermemory config") } - const apiKey = - typeof cfg.apiKey === "string" && cfg.apiKey.length > 0 - ? resolveEnvVars(cfg.apiKey) - : process.env.SUPERMEMORY_OPENCLAW_API_KEY + let apiKey: string | undefined + try { + apiKey = + typeof cfg.apiKey === "string" && cfg.apiKey.length > 0 + ? resolveEnvVars(cfg.apiKey) + : process.env.SUPERMEMORY_OPENCLAW_API_KEY + } catch { + apiKey = undefined + } - if (!apiKey) { - throw new Error( - "supermemory: apiKey is required (set in plugin config or SUPERMEMORY_OPENCLAW_API_KEY env var)", - ) + const customContainers: CustomContainer[] = [] + if (Array.isArray(cfg.customContainers)) { + for (const c of cfg.customContainers) { + if ( + c && + typeof c === "object" && + typeof (c as Record).tag === "string" && + typeof (c as Record).description === "string" + ) { + customContainers.push({ + tag: sanitizeTag((c as Record).tag as string), + description: (c as Record).description as string, + }) + } + } } return { @@ -91,9 +118,42 @@ export function parseConfig(raw: unknown): SupermemoryConfig { ? ("everything" as const) : ("all" as const), debug: (cfg.debug as boolean) ?? false, + enableCustomContainerTags: (cfg.enableCustomContainerTags as boolean) ?? false, + customContainers, + customContainerInstructions: + typeof cfg.customContainerInstructions === "string" + ? cfg.customContainerInstructions + : "", } } export const supermemoryConfigSchema = { + jsonSchema: { + type: "object", + additionalProperties: false, + properties: { + apiKey: { type: "string" }, + containerTag: { type: "string" }, + autoRecall: { type: "boolean" }, + autoCapture: { type: "boolean" }, + maxRecallResults: { type: "number" }, + profileFrequency: { type: "number" }, + captureMode: { type: "string", enum: ["all", "everything"] }, + debug: { type: "boolean" }, + enableCustomContainerTags: { type: "boolean" }, + customContainers: { + type: "array", + items: { + type: "object", + properties: { + tag: { type: "string" }, + description: { type: "string" }, + }, + required: ["tag", "description"], + }, + }, + customContainerInstructions: { type: "string" }, + }, + }, parse: parseConfig, } diff --git a/hooks/capture.ts b/hooks/capture.ts index befbae2..a0c63dc 100644 --- a/hooks/capture.ts +++ b/hooks/capture.ts @@ -71,6 +71,10 @@ export function buildCaptureHandler( /[\s\S]*?<\/supermemory-context>\s*/g, "", ) + .replace( + /[\s\S]*?<\/supermemory-containers>\s*/g, + "", + ) .trim(), ) .filter((t) => t.length >= 10) diff --git a/hooks/recall.ts b/hooks/recall.ts index 2c172d7..1de8673 100644 --- a/hooks/recall.ts +++ b/hooks/recall.ts @@ -128,36 +128,85 @@ function countUserTurns(messages: unknown[]): number { return count } +function formatContainerMetadata( + cfg: SupermemoryConfig, + messageProvider?: string, +): string | null { + if (!cfg.enableCustomContainerTags || cfg.customContainers.length === 0) + return null + + const lines: string[] = [] + + lines.push(`Root container: \`${cfg.containerTag}\``) + lines.push("") + lines.push("Custom memory containers:") + for (const c of cfg.customContainers) { + lines.push(`- \`${c.tag}\`: ${c.description}`) + } + + if (messageProvider) { + lines.push("") + lines.push(`Current channel: ${messageProvider}`) + } + + if (cfg.customContainerInstructions) { + lines.push("") + lines.push(cfg.customContainerInstructions) + } + + lines.push("") + lines.push( + "Use containerTag parameter to store in a specific container, otherwise stores to root.", + ) + + return lines.join("\n") +} + export function buildRecallHandler( client: SupermemoryClient, cfg: SupermemoryConfig, ) { - return async (event: Record) => { + return async ( + event: Record, + ctx?: Record, + ) => { const prompt = event.prompt as string | undefined if (!prompt || prompt.length < 5) return const messages = Array.isArray(event.messages) ? event.messages : [] const turn = countUserTurns(messages) const includeProfile = turn <= 1 || turn % cfg.profileFrequency === 0 + const messageProvider = ctx?.messageProvider as string | undefined log.debug(`recalling for turn ${turn} (profile: ${includeProfile})`) try { const profile = await client.getProfile(prompt) - const context = formatContext( + const memoryContext = formatContext( includeProfile ? profile.static : [], includeProfile ? profile.dynamic : [], profile.searchResults, cfg.maxRecallResults, ) - if (!context) { + const containerContext = formatContainerMetadata(cfg, messageProvider) + + const contextParts: string[] = [] + if (memoryContext) contextParts.push(memoryContext) + if (containerContext) { + contextParts.push( + `\n${containerContext}\n`, + ) + } + + if (contextParts.length === 0) { log.debug("no profile data to inject") return } - log.debug(`injecting context (${context.length} chars, turn ${turn})`) - return { prependContext: context } + const finalContext = contextParts.join("\n\n") + log.debug(`injecting context (${finalContext.length} chars, turn ${turn})`) + return { prependContext: finalContext } } catch (err) { log.error("recall failed", err) return diff --git a/index.ts b/index.ts index 4b261b3..8a76443 100644 --- a/index.ts +++ b/index.ts @@ -1,7 +1,7 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk" import { SupermemoryClient } from "./client.ts" -import { registerCli } from "./commands/cli.ts" -import { registerCommands } from "./commands/slash.ts" +import { registerCli, registerCliSetup } from "./commands/cli.ts" +import { registerCommands, registerStubCommands } from "./commands/slash.ts" import { parseConfig, supermemoryConfigSchema } from "./config.ts" import { buildCaptureHandler } from "./hooks/capture.ts" import { buildRecallHandler } from "./hooks/recall.ts" @@ -23,9 +23,20 @@ export default { initLogger(api.logger, cfg.debug) + registerCliSetup(api) + + if (!cfg.apiKey) { + api.logger.info( + "supermemory: not configured - run 'openclaw supermemory setup'", + ) + registerStubCommands(api) + return + } + const client = new SupermemoryClient(cfg.apiKey, cfg.containerTag) let sessionKey: string | undefined + let messageProvider: string | undefined const getSessionKey = () => sessionKey registerSearchTool(api, client, cfg) @@ -39,7 +50,9 @@ export default { "before_agent_start", (event: Record, ctx: Record) => { if (ctx.sessionKey) sessionKey = ctx.sessionKey as string - return recallHandler(event) + if (ctx.messageProvider) + messageProvider = ctx.messageProvider as string + return recallHandler(event, ctx) }, ) } diff --git a/openclaw.plugin.json b/openclaw.plugin.json index 1ce5a54..df11b93 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -43,6 +43,21 @@ "label": "Debug Logging", "help": "Enable verbose debug logs for API calls and responses", "advanced": true + }, + "enableCustomContainerTags": { + "label": "Enable Custom Container Tags", + "help": "Enable AI-driven routing to custom containers", + "advanced": true + }, + "customContainers": { + "label": "Custom Containers", + "help": "Define custom containers with tags and descriptions for AI routing", + "advanced": true + }, + "customContainerInstructions": { + "label": "Container Instructions", + "help": "Instructions for AI on how to route memories to custom containers", + "advanced": true } }, "configSchema": { @@ -56,7 +71,20 @@ "maxRecallResults": { "type": "number", "minimum": 1, "maximum": 20 }, "profileFrequency": { "type": "number", "minimum": 1, "maximum": 500 }, "captureMode": { "type": "string", "enum": ["everything", "all"] }, - "debug": { "type": "boolean" } + "debug": { "type": "boolean" }, + "enableCustomContainerTags": { "type": "boolean" }, + "customContainers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tag": { "type": "string" }, + "description": { "type": "string" } + }, + "required": ["tag", "description"] + } + }, + "customContainerInstructions": { "type": "string" } }, "required": [] } diff --git a/tools/forget.ts b/tools/forget.ts index 342838b..6225bc1 100644 --- a/tools/forget.ts +++ b/tools/forget.ts @@ -22,22 +22,35 @@ export function registerForgetTool( memoryId: Type.Optional( Type.String({ description: "Direct memory ID to delete" }), ), + containerTag: Type.Optional( + Type.String({ + description: + "Optional container tag to delete from a specific container", + }), + ), }), async execute( _toolCallId: string, - params: { query?: string; memoryId?: string }, + params: { query?: string; memoryId?: string; containerTag?: string }, ) { if (params.memoryId) { - log.debug(`forget tool: direct delete id="${params.memoryId}"`) - await client.deleteMemory(params.memoryId) + log.debug( + `forget tool: direct delete id="${params.memoryId}" containerTag="${params.containerTag ?? "default"}"`, + ) + await client.deleteMemory(params.memoryId, params.containerTag) return { content: [{ type: "text" as const, text: "Memory forgotten." }], } } if (params.query) { - log.debug(`forget tool: search-then-delete query="${params.query}"`) - const result = await client.forgetByQuery(params.query) + log.debug( + `forget tool: search-then-delete query="${params.query}" containerTag="${params.containerTag ?? "default"}"`, + ) + const result = await client.forgetByQuery( + params.query, + params.containerTag, + ) return { content: [{ type: "text" as const, text: result.message }], } diff --git a/tools/profile.ts b/tools/profile.ts index d3d626c..8d5f00d 100644 --- a/tools/profile.ts +++ b/tools/profile.ts @@ -21,11 +21,25 @@ export function registerProfileTool( description: "Optional query to focus the profile", }), ), + containerTag: Type.Optional( + Type.String({ + description: + "Optional container tag to get profile from a specific container", + }), + ), }), - async execute(_toolCallId: string, params: { query?: string }) { - log.debug(`profile tool: query="${params.query ?? "(none)"}"`) + async execute( + _toolCallId: string, + params: { query?: string; containerTag?: string }, + ) { + log.debug( + `profile tool: query="${params.query ?? "(none)"}" containerTag="${params.containerTag ?? "default"}"`, + ) - const profile = await client.getProfile(params.query) + const profile = await client.getProfile( + params.query, + params.containerTag, + ) if (profile.static.length === 0 && profile.dynamic.length === 0) { return { diff --git a/tools/search.ts b/tools/search.ts index 41b326e..5cdfce3 100644 --- a/tools/search.ts +++ b/tools/search.ts @@ -20,15 +20,27 @@ export function registerSearchTool( limit: Type.Optional( Type.Number({ description: "Max results (default: 5)" }), ), + containerTag: Type.Optional( + Type.String({ + description: + "Optional container tag to search in a specific container", + }), + ), }), async execute( _toolCallId: string, - params: { query: string; limit?: number }, + params: { query: string; limit?: number; containerTag?: string }, ) { const limit = params.limit ?? 5 - log.debug(`search tool: query="${params.query}" limit=${limit}`) + log.debug( + `search tool: query="${params.query}" limit=${limit} containerTag="${params.containerTag ?? "default"}"`, + ) - const results = await client.search(params.query, limit) + const results = await client.search( + params.query, + limit, + params.containerTag, + ) if (results.length === 0) { return { diff --git a/tools/store.ts b/tools/store.ts index 4ca2aa4..5cfaf02 100644 --- a/tools/store.ts +++ b/tools/store.ts @@ -24,21 +24,30 @@ export function registerStoreTool( parameters: Type.Object({ text: Type.String({ description: "Information to remember" }), category: Type.Optional(stringEnum(MEMORY_CATEGORIES)), + containerTag: Type.Optional( + Type.String({ + description: + "Optional container tag to store the memory in a specific container", + }), + ), }), async execute( _toolCallId: string, - params: { text: string; category?: string }, + params: { text: string; category?: string; containerTag?: string }, ) { const category = params.category ?? detectCategory(params.text) const sk = getSessionKey() const customId = sk ? buildDocumentId(sk) : undefined - log.debug(`store tool: category="${category}" customId="${customId}"`) + log.debug( + `store tool: category="${category}" customId="${customId}" containerTag="${params.containerTag ?? "default"}"`, + ) await client.addMemory( params.text, { type: category, source: "openclaw_tool" }, customId, + params.containerTag, ) const preview = From 0f33cd01401be6a770e2cf512c2c01c73827e94d Mon Sep 17 00:00:00 2001 From: Prasanna721 Date: Mon, 16 Feb 2026 19:07:26 -0800 Subject: [PATCH 2/3] biome fix --- client.ts | 5 +- commands/cli.ts | 137 ++++++++++++++++++++++++++++++++++++------------ config.ts | 3 +- hooks/recall.ts | 4 +- index.ts | 3 -- 5 files changed, 112 insertions(+), 40 deletions(-) diff --git a/client.ts b/client.ts index fc7a0ae..df5cac2 100644 --- a/client.ts +++ b/client.ts @@ -115,7 +115,10 @@ export class SupermemoryClient { return results } - async getProfile(query?: string, containerTag?: string): Promise { + async getProfile( + query?: string, + containerTag?: string, + ): Promise { const tag = containerTag ?? this.containerTag log.debugRequest("profile", { containerTag: tag, query }) diff --git a/commands/cli.ts b/commands/cli.ts index 8184c22..7cfef62 100644 --- a/commands/cli.ts +++ b/commands/cli.ts @@ -72,7 +72,9 @@ export function registerCliSetup(api: OpenClawPluginApi): void { fs.writeFileSync(configPath, JSON.stringify(config, null, 2)) console.log("\n✓ API key saved to ~/.openclaw/openclaw.json") - console.log(" Restart OpenClaw to apply changes: openclaw gateway --force\n") + console.log( + " Restart OpenClaw to apply changes: openclaw gateway --force\n", + ) }) cmd @@ -106,31 +108,47 @@ export function registerCliSetup(api: OpenClawPluginApi): void { console.log("Warning: API key should start with 'sm_'\n") } - const containerTag = await ask(`Container tag [openclaw_${defaultTag}]: `) + const containerTag = await ask( + `Container tag [openclaw_${defaultTag}]: `, + ) console.log("\nAuto-recall:") - console.log(" true - Inject relevant memories before each AI response (recommended)") + console.log( + " true - Inject relevant memories before each AI response (recommended)", + ) console.log(" false - Disable automatic memory recall") const autoRecallInput = await ask("Auto-recall (true/false) [true]: ") let autoRecall = true if (autoRecallInput.trim().toLowerCase() === "false") { autoRecall = false - } else if (autoRecallInput.trim() && autoRecallInput.trim().toLowerCase() !== "true") { + } else if ( + autoRecallInput.trim() && + autoRecallInput.trim().toLowerCase() !== "true" + ) { console.log(" Invalid value, using default: true") } console.log("\nAuto-capture:") - console.log(" true - Save conversations to memory after each AI response (recommended)") + console.log( + " true - Save conversations to memory after each AI response (recommended)", + ) console.log(" false - Disable automatic conversation capture") - const autoCaptureInput = await ask("Auto-capture (true/false) [true]: ") + const autoCaptureInput = await ask( + "Auto-capture (true/false) [true]: ", + ) let autoCapture = true if (autoCaptureInput.trim().toLowerCase() === "false") { autoCapture = false - } else if (autoCaptureInput.trim() && autoCaptureInput.trim().toLowerCase() !== "true") { + } else if ( + autoCaptureInput.trim() && + autoCaptureInput.trim().toLowerCase() !== "true" + ) { console.log(" Invalid value, using default: true") } - const maxResultsInput = await ask("Max memories to recall per turn (1-20) [10]: ") + const maxResultsInput = await ask( + "Max memories to recall per turn (1-20) [10]: ", + ) let maxRecallResults = 10 const parsedMax = Number.parseInt(maxResultsInput.trim(), 10) if (maxResultsInput.trim()) { @@ -141,7 +159,9 @@ export function registerCliSetup(api: OpenClawPluginApi): void { } } - const profileFreqInput = await ask("Inject full profile every N turns (1-500) [50]: ") + const profileFreqInput = await ask( + "Inject full profile every N turns (1-500) [50]: ", + ) let profileFrequency = 50 const parsedFreq = Number.parseInt(profileFreqInput.trim(), 10) if (profileFreqInput.trim()) { @@ -153,30 +173,47 @@ export function registerCliSetup(api: OpenClawPluginApi): void { } console.log("\nCapture mode:") - console.log(" all - Filter short texts and context blocks (recommended)") + console.log( + " all - Filter short texts and context blocks (recommended)", + ) console.log(" everything - Capture all messages without filtering") - const captureModeInput = await ask("Capture mode (all/everything) [all]: ") + const captureModeInput = await ask( + "Capture mode (all/everything) [all]: ", + ) let captureMode: "all" | "everything" = "all" if (captureModeInput.trim().toLowerCase() === "everything") { captureMode = "everything" - } else if (captureModeInput.trim() && captureModeInput.trim().toLowerCase() !== "all") { + } else if ( + captureModeInput.trim() && + captureModeInput.trim().toLowerCase() !== "all" + ) { console.log(" Invalid value, using default: all") } console.log("\n--- Custom Container Tags (Advanced) ---") console.log("Define custom containers for AI-driven memory routing.") - const enableCustomContainerTagsInput = await ask("Enable custom container tags? (true/false) [false]: ") + const enableCustomContainerTagsInput = await ask( + "Enable custom container tags? (true/false) [false]: ", + ) let enableCustomContainerTags = false if (enableCustomContainerTagsInput.trim().toLowerCase() === "true") { enableCustomContainerTags = true - } else if (enableCustomContainerTagsInput.trim() && enableCustomContainerTagsInput.trim().toLowerCase() !== "false") { + } else if ( + enableCustomContainerTagsInput.trim() && + enableCustomContainerTagsInput.trim().toLowerCase() !== "false" + ) { console.log(" Invalid value, using default: false") } - console.log("\nAdd custom containers (tag:description). Leave blank when done.") - const customContainers: Array<{ tag: string; description: string }> = [] + console.log( + "\nAdd custom containers (tag:description). Leave blank when done.", + ) + const customContainers: Array<{ tag: string; description: string }> = + [] while (true) { - const containerInput = await ask("Container (e.g. work:Work projects): ") + const containerInput = await ask( + "Container (e.g. work:Work projects): ", + ) if (!containerInput.trim()) break const [tag, ...descParts] = containerInput.split(":") const description = descParts.join(":").trim() @@ -191,7 +228,9 @@ export function registerCliSetup(api: OpenClawPluginApi): void { } } - const customContainerInstructions = await ask("Custom container tag instructions (optional): ") + const customContainerInstructions = await ask( + "Custom container tag instructions (optional): ", + ) rl.close() @@ -214,16 +253,22 @@ export function registerCliSetup(api: OpenClawPluginApi): void { } if (containerTag.trim()) { - pluginConfig.containerTag = containerTag.trim().replace(/[^a-zA-Z0-9_]/g, "_") + pluginConfig.containerTag = containerTag + .trim() + .replace(/[^a-zA-Z0-9_]/g, "_") } if (!autoRecall) pluginConfig.autoRecall = false if (!autoCapture) pluginConfig.autoCapture = false - if (maxRecallResults !== 10) pluginConfig.maxRecallResults = maxRecallResults - if (profileFrequency !== 50) pluginConfig.profileFrequency = profileFrequency + if (maxRecallResults !== 10) + pluginConfig.maxRecallResults = maxRecallResults + if (profileFrequency !== 50) + pluginConfig.profileFrequency = profileFrequency if (captureMode !== "all") pluginConfig.captureMode = captureMode - if (enableCustomContainerTags) pluginConfig.enableCustomContainerTags = true + if (enableCustomContainerTags) + pluginConfig.enableCustomContainerTags = true if (customContainerInstructions.trim()) { - pluginConfig.customContainerInstructions = customContainerInstructions.trim() + pluginConfig.customContainerInstructions = + customContainerInstructions.trim() } if (customContainers.length > 0) { pluginConfig.customContainers = customContainers @@ -242,17 +287,25 @@ export function registerCliSetup(api: OpenClawPluginApi): void { console.log("\n✓ Configuration saved to ~/.openclaw/openclaw.json") console.log("\nSettings:") - console.log(` API key: ${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`) - console.log(` Container tag: ${containerTag.trim() || `openclaw_${defaultTag}`}`) + console.log( + ` API key: ${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`, + ) + console.log( + ` Container tag: ${containerTag.trim() || `openclaw_${defaultTag}`}`, + ) console.log(` Auto-recall: ${autoRecall}`) console.log(` Auto-capture: ${autoCapture}`) console.log(` Max results: ${maxRecallResults}`) console.log(` Profile freq: ${profileFrequency}`) console.log(` Capture mode: ${captureMode}`) - console.log(` Custom containers: ${enableCustomContainerTags ? "enabled" : "disabled"}`) + console.log( + ` Custom containers: ${enableCustomContainerTags ? "enabled" : "disabled"}`, + ) console.log(` Custom containers: ${customContainers.length}`) if (customContainerInstructions.trim()) { - console.log(` Routing instructions: "${customContainerInstructions.trim().slice(0, 50)}${customContainerInstructions.length > 50 ? "..." : ""}"`) + console.log( + ` Routing instructions: "${customContainerInstructions.trim().slice(0, 50)}${customContainerInstructions.length > 50 ? "..." : ""}"`, + ) } console.log("\nRestart OpenClaw to apply: openclaw gateway --force\n") }) @@ -261,7 +314,11 @@ export function registerCliSetup(api: OpenClawPluginApi): void { .command("status") .description("Check Supermemory configuration status") .action(async () => { - const configPath = path.join(os.homedir(), ".openclaw", "openclaw.json") + const configPath = path.join( + os.homedir(), + ".openclaw", + "openclaw.json", + ) const envKey = process.env.SUPERMEMORY_OPENCLAW_API_KEY const defaultTag = `openclaw_${os.hostname().replace(/[^a-zA-Z0-9_]/g, "_")}` @@ -306,15 +363,27 @@ export function registerCliSetup(api: OpenClawPluginApi): void { ? pluginConfig.customContainers : [] - console.log(`✓ API key: ${apiKeyDisplay} (from ${apiKeySource})`) + console.log( + `✓ API key: ${apiKeyDisplay} (from ${apiKeySource})`, + ) console.log(` Enabled: ${enabled}`) - console.log(` Container tag: ${pluginConfig.containerTag ?? defaultTag}`) + console.log( + ` Container tag: ${pluginConfig.containerTag ?? defaultTag}`, + ) console.log(` Auto-recall: ${pluginConfig.autoRecall ?? true}`) console.log(` Auto-capture: ${pluginConfig.autoCapture ?? true}`) - console.log(` Max results: ${pluginConfig.maxRecallResults ?? 10}`) - console.log(` Profile freq: ${pluginConfig.profileFrequency ?? 50}`) - console.log(` Capture mode: ${pluginConfig.captureMode ?? "all"}`) - console.log(` Custom containers: ${pluginConfig.enableCustomContainerTags ? "enabled" : "disabled"}`) + console.log( + ` Max results: ${pluginConfig.maxRecallResults ?? 10}`, + ) + console.log( + ` Profile freq: ${pluginConfig.profileFrequency ?? 50}`, + ) + console.log( + ` Capture mode: ${pluginConfig.captureMode ?? "all"}`, + ) + console.log( + ` Custom containers: ${pluginConfig.enableCustomContainerTags ? "enabled" : "disabled"}`, + ) console.log(` Custom containers: ${customContainers.length}`) console.log("") }) diff --git a/config.ts b/config.ts index 313a204..f592ecb 100644 --- a/config.ts +++ b/config.ts @@ -118,7 +118,8 @@ export function parseConfig(raw: unknown): SupermemoryConfig { ? ("everything" as const) : ("all" as const), debug: (cfg.debug as boolean) ?? false, - enableCustomContainerTags: (cfg.enableCustomContainerTags as boolean) ?? false, + enableCustomContainerTags: + (cfg.enableCustomContainerTags as boolean) ?? false, customContainers, customContainerInstructions: typeof cfg.customContainerInstructions === "string" diff --git a/hooks/recall.ts b/hooks/recall.ts index 1de8673..ddd2d4e 100644 --- a/hooks/recall.ts +++ b/hooks/recall.ts @@ -205,7 +205,9 @@ export function buildRecallHandler( } const finalContext = contextParts.join("\n\n") - log.debug(`injecting context (${finalContext.length} chars, turn ${turn})`) + log.debug( + `injecting context (${finalContext.length} chars, turn ${turn})`, + ) return { prependContext: finalContext } } catch (err) { log.error("recall failed", err) diff --git a/index.ts b/index.ts index 8a76443..9634d45 100644 --- a/index.ts +++ b/index.ts @@ -36,7 +36,6 @@ export default { const client = new SupermemoryClient(cfg.apiKey, cfg.containerTag) let sessionKey: string | undefined - let messageProvider: string | undefined const getSessionKey = () => sessionKey registerSearchTool(api, client, cfg) @@ -50,8 +49,6 @@ export default { "before_agent_start", (event: Record, ctx: Record) => { if (ctx.sessionKey) sessionKey = ctx.sessionKey as string - if (ctx.messageProvider) - messageProvider = ctx.messageProvider as string return recallHandler(event, ctx) }, ) From 420823ce3aba8f9ed41926f34d4d4592910bf09b Mon Sep 17 00:00:00 2001 From: Prasanna <106952318+Prasanna721@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:16:16 -0800 Subject: [PATCH 3/3] Update README Updated the image in the README and added a new image. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b25ff44..dd419cf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # OpenClaw Supermemory Plugin -Announcement-3 (2) +Untitled_Artwork 3 + Long-term memory for OpenClaw. Automatically remembers conversations, recalls relevant context, and builds a persistent user profile — all powered by [Supermemory](https://supermemory.ai) cloud. No local infrastructure required.