/** * LLMemory Plugin for OpenCode * * Provides a persistent memory/journal system for AI agents. * Memories are stored in SQLite and searchable across sessions. */ import { tool } from "@opencode-ai/plugin"; import { spawn } from "child_process"; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const MEMORY_CLI = join(__dirname, '../llmemory/bin/llmemory'); function runMemoryCommand(args) { return new Promise((resolve, reject) => { const child = spawn('node', [MEMORY_CLI, ...args], { env: { ...process.env } }); let stdout = ''; let stderr = ''; child.stdout.on('data', (data) => { stdout += data.toString(); }); child.stderr.on('data', (data) => { stderr += data.toString(); }); child.on('close', (code) => { if (code !== 0) { reject(new Error(stderr || `Command failed with code ${code}`)); } else { resolve(stdout); } }); }); } export const LLMemoryPlugin = async (ctx) => { const tools = { memory_store: tool({ description: `Store a memory for future reference. Use this to remember important information across sessions. Examples: - Store implementation decisions: "Decided to use JWT for auth instead of sessions" - Record completed work: "Implemented user authentication with email/password" - Save debugging insights: "Bug was caused by race condition in async handler" - Document project context: "Client prefers Material-UI over Tailwind" Memories are searchable by content and tags.`, args: { content: tool.schema.string() .describe("The memory content to store (required)"), tags: tool.schema.string() .optional() .describe("Comma-separated tags for categorization (e.g., 'backend,auth,security')"), expires: tool.schema.string() .optional() .describe("Optional expiration date (ISO format, e.g., '2026-12-31')"), by: tool.schema.string() .optional() .describe("Agent/user identifier (defaults to 'agent')") }, async execute(args) { const cmdArgs = ['store', args.content]; if (args.tags) cmdArgs.push('--tags', args.tags); if (args.expires) cmdArgs.push('--expires', args.expires); if (args.by) cmdArgs.push('--by', args.by); try { const result = await runMemoryCommand(cmdArgs); return result; } catch (error) { return `Error storing memory: ${error.message}`; } } }), memory_search: tool({ description: `Search stored memories by content and/or tags. Returns relevant memories from past sessions. Use cases: - Find past decisions: "authentication" - Recall debugging insights: "race condition" - Look up project context: "client preferences" - Review completed work: "implemented" Supports filtering by tags, date ranges, and limiting results.`, args: { query: tool.schema.string() .describe("Search query (case-insensitive substring match)"), tags: tool.schema.string() .optional() .describe("Filter by tags (AND logic, comma-separated)"), any_tag: tool.schema.string() .optional() .describe("Filter by tags (OR logic, comma-separated)"), limit: tool.schema.number() .optional() .describe("Maximum results to return (default: 10)") }, async execute(args) { const cmdArgs = ['search', args.query, '--json']; if (args.tags) cmdArgs.push('--tags', args.tags); if (args.any_tag) cmdArgs.push('--any-tag', args.any_tag); if (args.limit) cmdArgs.push('--limit', String(args.limit)); try { const result = await runMemoryCommand(cmdArgs); return result; } catch (error) { return `Error searching memories: ${error.message}`; } } }), memory_list: tool({ description: `List recent memories, optionally filtered by tags. Useful for reviewing recent work or exploring stored context.`, args: { limit: tool.schema.number() .optional() .describe("Maximum results to return (default: 20)"), tags: tool.schema.string() .optional() .describe("Filter by tags (comma-separated)") }, async execute(args) { const cmdArgs = ['list', '--json']; if (args.limit) cmdArgs.push('--limit', String(args.limit)); if (args.tags) cmdArgs.push('--tags', args.tags); try { const result = await runMemoryCommand(cmdArgs); return result; } catch (error) { return `Error listing memories: ${error.message}`; } } }) }; return { tool: tools }; };