From e5fa404640b628bd12a1d562c7199c708d7cd239 Mon Sep 17 00:00:00 2001 From: Alex Newman Date: Wed, 28 Jan 2026 23:45:27 -0500 Subject: [PATCH] feat(memory-claudemem): add workerPath config for manual installs - Add workerPath config option to specify custom worker-service.cjs location - Check custom path first, then fall back to Claude plugin cache auto-discovery - Update docs with simpler manual install instructions (no need to copy to cache) - Update manifest with workerPath schema and UI hints --- docs/plans/claude-mem-moltbot-fix.md | 109 ++++++++++++++++++ docs/plugins/memory-claudemem.md | 58 +++++++--- extensions/memory-claudemem/index.ts | 66 ++++++----- .../memory-claudemem/moltbot.plugin.json | 8 ++ package.json | 5 +- 5 files changed, 200 insertions(+), 46 deletions(-) create mode 100644 docs/plans/claude-mem-moltbot-fix.md diff --git a/docs/plans/claude-mem-moltbot-fix.md b/docs/plans/claude-mem-moltbot-fix.md new file mode 100644 index 000000000..117224beb --- /dev/null +++ b/docs/plans/claude-mem-moltbot-fix.md @@ -0,0 +1,109 @@ +# Claude-Mem Moltbot Plugin Fix Plan + +## Status Assessment + +**Current State:** +- Plugin loads from: `/Users/alexnewman/clawd/plugins/claude-mem/index.ts` +- Observations: Working correctly (recorded via `tool_result_persist` hook) +- Prompts: Being recorded BUT with `[message_id: ...]` suffix appended +- MEMORY.md sync: Working (on `session_start`) +- Context injection: NOT implemented (only MEMORY.md file approach) + +**Root Cause:** +Moltbot's messaging system adds `[message_id: UUID]` to prompts for WhatsApp/Discord reactions and replies (see `src/auto-reply/reply/body.ts:42`). This metadata is being passed through to claude-mem without stripping. + +## Phase 1: Strip message_id from prompts + +### Problem +Line 304 in `/Users/alexnewman/clawd/plugins/claude-mem/index.ts`: +```typescript +const result = await client.initSession(contentSessionId, event.prompt); +``` +Sends the full prompt including `[message_id: UUID]`. + +### Fix +Add a utility function to strip the message_id line before sending: + +**Copy pattern from:** `src/gateway/chat-sanitize.ts:18-38` + +```typescript +const MESSAGE_ID_LINE = /^\s*\[message_id:\s*[^\]]+\]\s*$/im; + +function stripMessageIdFromPrompt(text: string): string { + if (!text.includes("[message_id:")) return text; + const lines = text.split(/\r?\n/); + const filtered = lines.filter((line) => !MESSAGE_ID_LINE.test(line)); + return filtered.join("\n").trim(); +} +``` + +### Implementation +Update `before_agent_start` hook (around line 297-310): + +```typescript +api.on("before_agent_start", async (event, ctx) => { + if (!event.prompt || event.prompt.length < 10) return; + if (!(await client.isHealthy())) return; + + const contentSessionId = getContentSessionId(ctx.sessionKey); + + // Strip [message_id: ...] metadata before recording + const cleanPrompt = stripMessageIdFromPrompt(event.prompt); + + const result = await client.initSession(contentSessionId, cleanPrompt); + if (result) { + api.logger.debug?.( + `claude-mem: session initialized (dbId: ${result.sessionDbId}, prompt#: ${result.promptNumber})` + ); + } +}); +``` + +### Verification +1. Run moltbot with a test message +2. Check `/api/prompts` endpoint - prompts should NOT contain `[message_id: ...]` +3. Check claude-mem UI - prompts should display cleanly + +--- + +## Phase 2: (Optional) Add context injection via prependContext + +The current plugin only syncs to `MEMORY.md` file. For real-time context injection (like the `extensions/memory-claudemem` approach), add: + +```typescript +api.on("before_agent_start", async (event, ctx) => { + // ... existing prompt recording code ... + + // Optional: inject recent context + const observations = await client.getObservations(5); + if (observations.length === 0) return; + + const memoryContext = observations + .map((obs) => `- [#${obs.id}] ${obs.title || obs.type}: ${obs.narrative || obs.text?.slice(0, 100)}`) + .join("\n"); + + return { + prependContext: `\nRecent memories:\n${memoryContext}\n`, + }; +}); +``` + +**Note:** This is optional since MEMORY.md file approach is already working. + +--- + +## Files to Modify + +| File | Change | +|------|--------| +| `/Users/alexnewman/clawd/plugins/claude-mem/index.ts` | Add `stripMessageIdFromPrompt()` utility, update `before_agent_start` hook | + +--- + +## Verification Checklist + +- [ ] Prompts recorded without `[message_id: ...]` suffix +- [ ] Prompts display cleanly in claude-mem UI at localhost:37777 +- [ ] Observations still being recorded correctly +- [ ] MEMORY.md still syncing on session_start +- [ ] No regressions in existing functionality diff --git a/docs/plugins/memory-claudemem.md b/docs/plugins/memory-claudemem.md index 018ee98e6..ce822a99a 100644 --- a/docs/plugins/memory-claudemem.md +++ b/docs/plugins/memory-claudemem.md @@ -60,25 +60,7 @@ bun run build npm run build ``` -#### 4. Create the plugin cache directory - -The Moltbot plugin expects claude-mem to be in the Claude plugins cache: - -```bash -# Create the directory structure -mkdir -p ~/.claude/plugins/cache/thedotmack/claude-mem - -# Copy the built plugin (use actual version number) -cp -r plugin ~/.claude/plugins/cache/thedotmack/claude-mem/9.0.12 -``` - -Alternatively, symlink it: - -```bash -ln -s $(pwd)/plugin ~/.claude/plugins/cache/thedotmack/claude-mem/9.0.12 -``` - -#### 5. Start the worker service +#### 4. Start the worker service Claude-mem runs a background worker service: @@ -100,6 +82,43 @@ The worker runs on `http://localhost:37777` by default and provides: - API endpoints for memory search and retrieval - Background observation processing +#### 5. Configure Moltbot with the worker path + +Tell Moltbot where to find the worker service: + +```json5 +{ + plugins: { + entries: { + "memory-claudemem": { + enabled: true, + config: { + workerPath: "/path/to/claude-mem/plugin/scripts/worker-service.cjs" + } + } + } + } +} +``` + +Or set the slot directly: + +```json5 +{ + plugins: { + slots: { + memory: "memory-claudemem" + }, + entries: { + "memory-claudemem": { + config: { + workerPath: "/path/to/claude-mem/plugin/scripts/worker-service.cjs" + } + } + } + } +} + ## Enabling the Moltbot Plugin Once claude-mem is installed, enable the Moltbot plugin: @@ -147,6 +166,7 @@ Or explicitly in entries: |--------|------|---------|-------------| | `syncMemoryFile` | boolean | `true` | Sync MEMORY.md with claude-mem context on session/gateway start | | `project` | string | workspace dir name | Project name for scoping observations | +| `workerPath` | string | (auto-detect) | Path to `worker-service.cjs` for manual installs | ## How It Works diff --git a/extensions/memory-claudemem/index.ts b/extensions/memory-claudemem/index.ts index 3cb4e74ba..855367f5d 100644 --- a/extensions/memory-claudemem/index.ts +++ b/extensions/memory-claudemem/index.ts @@ -78,6 +78,7 @@ async function getHookCwd(workspaceDir: string, project: string): Promise = { @@ -170,36 +171,50 @@ function callHookFireAndForget(hookName: string, data: Record): } export default function (api: ClawdbotPluginApi) { - // Find the latest claude-mem worker service (sync check) - const cacheDir = join(homedir(), ".claude/plugins/cache/thedotmack/claude-mem"); - if (!existsSync(cacheDir)) { - api.logger.warn?.("claude-mem: plugin cache not found - plugin disabled"); - return; - } + const userConfig = api.pluginConfig as Partial; - // Find latest version synchronously - try { - const entries = require("fs").readdirSync(cacheDir); - let latestVersion: string | null = null; - let latestMtime = 0; + // Check for custom worker path first (for manual installs) + if (userConfig.workerPath) { + if (existsSync(userConfig.workerPath)) { + WORKER_SERVICE_PATH = userConfig.workerPath; + api.logger.debug?.(`claude-mem: using custom worker path: ${WORKER_SERVICE_PATH}`); + } else { + api.logger.warn?.(`claude-mem: custom workerPath not found: ${userConfig.workerPath} - plugin disabled`); + return; + } + } else { + // Auto-discover from Claude plugin cache + const cacheDir = join(homedir(), ".claude/plugins/cache/thedotmack/claude-mem"); + if (!existsSync(cacheDir)) { + api.logger.warn?.("claude-mem: plugin cache not found and no workerPath configured - plugin disabled"); + api.logger.warn?.("claude-mem: install via Claude Code or set plugins.entries.memory-claudemem.config.workerPath"); + return; + } - for (const entry of entries) { - const fullPath = join(cacheDir, entry); - const workerPath = join(fullPath, "scripts/worker-service.cjs"); - if (existsSync(workerPath)) { - const stats = require("fs").statSync(fullPath); - if (stats.mtimeMs > latestMtime) { - latestMtime = stats.mtimeMs; - latestVersion = entry; + // Find latest version synchronously + try { + const entries = require("fs").readdirSync(cacheDir); + let latestVersion: string | null = null; + let latestMtime = 0; + + for (const entry of entries) { + const fullPath = join(cacheDir, entry); + const workerPath = join(fullPath, "scripts/worker-service.cjs"); + if (existsSync(workerPath)) { + const stats = require("fs").statSync(fullPath); + if (stats.mtimeMs > latestMtime) { + latestMtime = stats.mtimeMs; + latestVersion = entry; + } } } + + if (latestVersion) { + WORKER_SERVICE_PATH = join(cacheDir, latestVersion, "scripts/worker-service.cjs"); + } + } catch (err) { + api.logger.warn?.("claude-mem: failed to find worker service", err); } - - if (latestVersion) { - WORKER_SERVICE_PATH = join(cacheDir, latestVersion, "scripts/worker-service.cjs"); - } - } catch (err) { - api.logger.warn?.("claude-mem: failed to find worker service", err); } if (!WORKER_SERVICE_PATH) { @@ -212,7 +227,6 @@ export default function (api: ClawdbotPluginApi) { const workspaceDir = api.workspaceDir || process.cwd(); const defaultProject = basename(workspaceDir); - const userConfig = api.pluginConfig as Partial; const config: ClaudeMemConfig = { ...DEFAULT_CONFIG, project: defaultProject, diff --git a/extensions/memory-claudemem/moltbot.plugin.json b/extensions/memory-claudemem/moltbot.plugin.json index 916741cab..96e5089a4 100644 --- a/extensions/memory-claudemem/moltbot.plugin.json +++ b/extensions/memory-claudemem/moltbot.plugin.json @@ -16,6 +16,10 @@ "type": "boolean", "default": true, "description": "Sync MEMORY.md with claude-mem context on session start" + }, + "workerPath": { + "type": "string", + "description": "Custom path to worker-service.cjs (for manual installs outside Claude plugin cache)" } } }, @@ -27,6 +31,10 @@ "syncMemoryFile": { "label": "Sync MEMORY.md", "help": "Update MEMORY.md with claude-mem context on session/gateway start" + }, + "workerPath": { + "label": "Worker Path", + "help": "Path to claude-mem's worker-service.cjs. Only needed for manual installs." } } } diff --git a/package.json b/package.json index f6d67ab67..562429e48 100644 --- a/package.json +++ b/package.json @@ -194,6 +194,7 @@ "long": "5.3.2", "markdown-it": "^14.1.0", "node-edge-tts": "^1.2.9", + "node-gyp": "^12.2.0", "osc-progress": "^0.3.0", "pdfjs-dist": "^5.4.530", "playwright-core": "1.58.0", @@ -286,7 +287,9 @@ }, "trustedDependencies": [ "@whiskeysockets/baileys", + "esbuild", "node-llama-cpp", - "protobufjs" + "protobufjs", + "sharp" ] }