From 44abce9968c9e6e93f82358b6f0b59e5e373ca00 Mon Sep 17 00:00:00 2001 From: Prasanna721 Date: Tue, 27 Jan 2026 13:11:29 -0800 Subject: [PATCH 1/3] feat: add Supermemory memory plugin for Clawdbot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Supermemory Plugin — long-term memory for Clawdbot Changes: - Auto-recall hook injects relevant memories + user profile into context before every AI turn - Auto-capture hook stores conversation turns after each agent interaction, strips injected tags before saving - 4 AI tools: supermemory_search, supermemory_store, supermemory_forget, supermemory_profile - 2 slash commands: /remember (save to memory), /recall (search memories) - 3 CLI commands: supermemory search, supermemory profile, supermemory wipe (bulk delete with confirmation) - Supermemory SDK client with input validation, content sanitization, and metadata enforcement - Request integrity headers (HMAC-SHA256 signing) for server-side plugin access verification - Configurable capture modes: all (filters short texts, strips injected context) vs everything (raw) - Memory deduplication across static profile, dynamic context, and search results during recall - Automatic memory category detection (preference, fact, decision, entity) - Container tag scoping per hostname for multi-machine isolation - Session-based document IDs for grouping conversation memories - Profile injection frequency control (full profile every N turns, search results every turn) - Structured logger with debug mode for API request/response tracing - Environment variable resolution in config (${SUPERMEMORY_API_KEY}) - Compiled validation module (lib/validate.js) with API key format checks, content sanitization, metadata limits --- .gitignore | 1 - hooks/capture.ts | 13 ++++++++++--- lib/validate.d.ts | 28 ---------------------------- lib/validate.js | 2 +- 4 files changed, 11 insertions(+), 33 deletions(-) delete mode 100644 lib/validate.d.ts diff --git a/.gitignore b/.gitignore index 59be53c..64a3a50 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ node_modules/ dist/ .arch/ lib/*.ts -!lib/*.d.ts *.tsbuildinfo .DS_Store diff --git a/hooks/capture.ts b/hooks/capture.ts index 5f4b28b..d13df06 100644 --- a/hooks/capture.ts +++ b/hooks/capture.ts @@ -64,9 +64,16 @@ export function buildCaptureHandler( const captured = cfg.captureMode === "all" - ? texts.filter( - (t) => t.length >= 10 && !t.includes(""), - ) + ? texts + .map((t) => + t + .replace( + /[\s\S]*?<\/supermemory-context>\s*/g, + "", + ) + .trim(), + ) + .filter((t) => t.length >= 10) : texts if (captured.length === 0) return diff --git a/lib/validate.d.ts b/lib/validate.d.ts deleted file mode 100644 index 46c5b73..0000000 --- a/lib/validate.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -export declare function validateApiKeyFormat(key: string): { - valid: boolean - reason?: string -} -export declare function validateContainerTag(tag: string): { - valid: boolean - reason?: string -} -export declare function sanitizeContent( - content: string, - maxLength?: number, -): string -export declare function validateContentLength( - content: string, - min?: number, - max?: number, -): { valid: boolean; reason?: string } -export declare function sanitizeMetadata( - meta: Record, -): Record -export declare function validateRecallConfig( - maxResults: number, - frequency: number, -): string[] -export declare function getRequestIntegrity( - apiKey: string, - containerTag: string, -): Record diff --git a/lib/validate.js b/lib/validate.js index dd937dc..ac2e25b 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -1 +1 @@ -import{createHash as o,createHmac as l}from"node:crypto";function b(e){return!e||typeof e!="string"?{valid:!1,reason:"key is empty or not a string"}:e.startsWith("sm_")?e.length<20?{valid:!1,reason:"key is too short"}:/\s/.test(e)?{valid:!1,reason:"key contains whitespace"}:{valid:!0}:{valid:!1,reason:"key must start with sm_ prefix"}}function v(e){return!e||typeof e!="string"?{valid:!1,reason:"tag is empty"}:e.length>100?{valid:!1,reason:"tag exceeds 100 characters"}:/^[a-zA-Z0-9_-]+$/.test(e)?/^[-_]|[-_]$/.test(e)?{valid:!1,reason:"tag must not start or end with - or _"}:{valid:!0}:{valid:!1,reason:"tag contains invalid characters (only alphanumeric, underscore, hyphen allowed)"}}var u=[/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g,/\uFEFF/g,/[\uFFF0-\uFFFF]/g];function m(e,n=1e5){if(!e||typeof e!="string")return"";let t=e;for(let r of u)t=t.replace(r,"");return t.length>n&&(t=t.slice(0,n)),t}function A(e,n=1,t=1e5){return e.lengtht?{valid:!1,reason:`content exceeds maximum length (${t})`}:{valid:!0}}var f=50,c=128,g=1024;function x(e){let n={},t=0;for(let[r,i]of Object.entries(e)){if(t>=f)break;r.length>c||/[^\w.-]/.test(r)||(typeof i=="string"?(n[r]=i.slice(0,g),t++):(typeof i=="number"&&Number.isFinite(i)||typeof i=="boolean")&&(n[r]=i,t++))}return n}function E(e,n){let t=[];return(!Number.isInteger(e)||e<1||e>20)&&t.push("maxRecallResults must be an integer between 1 and 20"),(!Number.isInteger(n)||n<1||n>500)&&t.push("profileFrequency must be an integer between 1 and 500"),t}function s(e){return o("sha256").update(e).digest("hex").slice(0,16)}var a=1,d="7f2a9c4b8e1d6f3a5c0b9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a";function h(e,n){let t=Math.floor(Date.now()/864e5),r=[s(e),s(n),t.toString(36),a].join(":");return l("sha256",d).update(r).digest("base64url")}function F(e,n){let t=s(n),r=h(e,n);return{"X-Content-Hash":t,"X-Request-Integrity":[`v${a}`,r].join(".")}}export{F as getRequestIntegrity,m as sanitizeContent,x as sanitizeMetadata,b as validateApiKeyFormat,v as validateContainerTag,A as validateContentLength,E as validateRecallConfig}; +import{createHash as o,createHmac as l}from"node:crypto";function h(e){return!e||typeof e!="string"?{valid:!1,reason:"key is empty or not a string"}:e.startsWith("sm_")?e.length<20?{valid:!1,reason:"key is too short"}:/\s/.test(e)?{valid:!1,reason:"key contains whitespace"}:{valid:!0}:{valid:!1,reason:"key must start with sm_ prefix"}}function v(e){return!e||typeof e!="string"?{valid:!1,reason:"tag is empty"}:e.length>100?{valid:!1,reason:"tag exceeds 100 characters"}:/^[a-zA-Z0-9_-]+$/.test(e)?/^[-_]|[-_]$/.test(e)?{valid:!1,reason:"tag must not start or end with - or _"}:{valid:!0}:{valid:!1,reason:"tag contains invalid characters (only alphanumeric, underscore, hyphen allowed)"}}var u=[/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g,/\uFEFF/g,/[\uFFF0-\uFFFF]/g];function m(e,n=1e5){if(!e||typeof e!="string")return"";let t=e;for(let r of u)t=t.replace(r,"");return t.length>n&&(t=t.slice(0,n)),t}function A(e,n=1,t=1e5){return e.lengtht?{valid:!1,reason:`content exceeds maximum length (${t})`}:{valid:!0}}var f=50,g=128,c=1024;function x(e){let n={},t=0;for(let[r,i]of Object.entries(e)){if(t>=f)break;r.length>g||/[^\w.-]/.test(r)||(typeof i=="string"?(n[r]=i.slice(0,c),t++):(typeof i=="number"&&Number.isFinite(i)||typeof i=="boolean")&&(n[r]=i,t++))}return n}function E(e,n){let t=[];return(!Number.isInteger(e)||e<1||e>20)&&t.push("maxRecallResults must be an integer between 1 and 20"),(!Number.isInteger(n)||n<1||n>500)&&t.push("profileFrequency must be an integer between 1 and 500"),t}function s(e){return o("sha256").update(e).digest("hex")}var a=1,d="7f2a9c4b8e1d6f3a5c0b9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a";function p(e,n){let t=[s(e),s(n),a].join(":");return l("sha256",d).update(t).digest("base64url")}function F(e,n){let t=s(n),r=p(e,n);return{"X-Content-Hash":t,"X-Request-Integrity":[`v${a}`,r].join(".")}}export{F as getRequestIntegrity,m as sanitizeContent,x as sanitizeMetadata,h as validateApiKeyFormat,v as validateContainerTag,A as validateContentLength,E as validateRecallConfig}; From de3144b25847ce5f8096c75716af169039e924dc Mon Sep 17 00:00:00 2001 From: Prasanna721 Date: Tue, 27 Jan 2026 13:15:23 -0800 Subject: [PATCH 2/3] biome ci fix --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index df2deaf..c995f31 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@supermemory/clawdbot-supermemory", "version": "1.0.0", "type": "module", - "description": "Supermemory memory plugin for Clawdbot", + "description": "Clawdbot Supermemory memory plugin", "license": "MIT", "dependencies": { "supermemory": "^4.0.0", @@ -10,6 +10,8 @@ }, "scripts": { "check-types": "tsc --noEmit", + "lint": "bunx @biomejs/biome ci .", + "lint:fix": "bunx @biomejs/biome check --write .", "build:lib": "esbuild lib/validate.ts --bundle --minify --format=esm --platform=node --target=es2022 --external:node:crypto --outfile=lib/validate.js" }, "peerDependencies": { From 1ca4bd1e9a2fab5b0fc3c8accd9d73b00ae7975f Mon Sep 17 00:00:00 2001 From: Prasanna721 Date: Tue, 27 Jan 2026 13:18:17 -0800 Subject: [PATCH 3/3] type-check fix --- .gitignore | 1 + lib/validate.d.ts | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 lib/validate.d.ts diff --git a/.gitignore b/.gitignore index 64a3a50..59be53c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules/ dist/ .arch/ lib/*.ts +!lib/*.d.ts *.tsbuildinfo .DS_Store diff --git a/lib/validate.d.ts b/lib/validate.d.ts new file mode 100644 index 0000000..46c5b73 --- /dev/null +++ b/lib/validate.d.ts @@ -0,0 +1,28 @@ +export declare function validateApiKeyFormat(key: string): { + valid: boolean + reason?: string +} +export declare function validateContainerTag(tag: string): { + valid: boolean + reason?: string +} +export declare function sanitizeContent( + content: string, + maxLength?: number, +): string +export declare function validateContentLength( + content: string, + min?: number, + max?: number, +): { valid: boolean; reason?: string } +export declare function sanitizeMetadata( + meta: Record, +): Record +export declare function validateRecallConfig( + maxResults: number, + frequency: number, +): string[] +export declare function getRequestIntegrity( + apiKey: string, + containerTag: string, +): Record