diff --git a/docs/public/usage/claude-desktop.mdx b/docs/public/usage/claude-desktop.mdx index f04160bb..708fdfb2 100644 --- a/docs/public/usage/claude-desktop.mdx +++ b/docs/public/usage/claude-desktop.mdx @@ -28,29 +28,7 @@ curl http://localhost:37777/api/health ## Installation -### Step 1: Download the Skill - -Download the skill package from the repository: - - - Download the mem-search skill for Claude Desktop - - -Or build from source: - -```bash -npm run build # Generates plugin/skills/mem-search.zip -``` - -### Step 2: Install in Claude Desktop - -1. Open **Claude Desktop** -2. Go to **Settings** (gear icon) -3. Navigate to **Skills** -4. Click **Install Skill** or drag the `mem-search.zip` file -5. Confirm installation - -### Step 3: Configure MCP Server +### Step 1: Configure MCP Server The skill requires the `mcp-search` MCP server. Add this to your Claude Desktop configuration: @@ -93,7 +71,7 @@ The skill requires the `mcp-search` MCP server. Add this to your Claude Desktop Replace `YOUR_USERNAME` with your actual username. Restart Claude Desktop after editing the configuration. -### Step 4: Restart Claude Desktop +### Step 2: Restart Claude Desktop Close and reopen Claude Desktop for the MCP server configuration to take effect. @@ -111,19 +89,21 @@ Once installed, the skill auto-activates when you ask about past work: ## Available MCP Tools -The skill provides access to these MCP tools: +The skill provides three core MCP tools following a 3-layer workflow pattern: | Tool | Description | |------|-------------| -| `search` | Unified search across observations, sessions, and prompts | +| `search` | Search memory index. Returns compact results with IDs for filtering | | `timeline` | Get chronological context around a query or observation ID | -| `get_observation` | Fetch a single observation by ID | -| `get_observations` | Fetch multiple observations efficiently | -| `get_session` | Fetch session summary by ID | -| `get_prompt` | Fetch user prompt by ID | -| `get_recent_context` | Get recent timeline items | -| `get_context_timeline` | Get timeline around a specific observation | -| `help` | Load detailed usage instructions | +| `get_observations` | Fetch full observation details by ID (use after filtering with search/timeline) | + +### Token-Efficient Workflow + +1. **Search** → Get index with IDs (~50-100 tokens/result) +2. **Timeline** → Get context around interesting results +3. **Get Observations** → Fetch full details ONLY for filtered IDs + +This 3-layer approach provides ~10x token savings compared to fetching full details upfront. ## Troubleshooting diff --git a/plugin/scripts/context-generator.cjs b/plugin/scripts/context-generator.cjs index f7e1e66e..2c5afd2b 100644 --- a/plugin/scripts/context-generator.cjs +++ b/plugin/scripts/context-generator.cjs @@ -192,7 +192,7 @@ ${i.stack}`:` ${i.message}`:this.getLevel()===0&&typeof i=="object"?l=` completed_at_epoch INTEGER, FOREIGN KEY (session_db_id) REFERENCES sdk_sessions(id) ON DELETE CASCADE ) - `),this.db.run("CREATE INDEX IF NOT EXISTS idx_pending_messages_session ON pending_messages(session_db_id)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_pending_messages_status ON pending_messages(status)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_pending_messages_claude_session ON pending_messages(content_session_id)"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(16,new Date().toISOString()),p.info("DB","pending_messages table created successfully")}catch(e){throw p.error("DB","Pending messages table migration error",void 0,e),e}}renameSessionIdColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(17))return;if(p.info("DB","Renaming session ID columns for semantic clarity"),this.db.query("PRAGMA table_info(sdk_sessions)").all().some(r=>r.name==="content_session_id")){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(17,new Date().toISOString());return}this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN claude_session_id TO content_session_id"),this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN sdk_session_id TO memory_session_id"),this.db.run("ALTER TABLE pending_messages RENAME COLUMN claude_session_id TO content_session_id"),this.db.run("ALTER TABLE observations RENAME COLUMN sdk_session_id TO memory_session_id"),this.db.run("ALTER TABLE session_summaries RENAME COLUMN sdk_session_id TO memory_session_id"),this.db.run("ALTER TABLE user_prompts RENAME COLUMN claude_session_id TO content_session_id"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(17,new Date().toISOString()),p.info("DB","Successfully renamed session ID columns")}catch(e){throw p.error("DB","Session ID column rename migration error",void 0,e),e}}repairSessionIdColumnRename(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(19))return;p.info("DB","Checking session ID column renames (repair migration)");let t=!1;this.db.query("PRAGMA table_info(sdk_sessions)").all().some(d=>d.name==="claude_session_id")&&(p.info("DB","Repairing sdk_sessions columns"),this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN claude_session_id TO content_session_id"),this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN sdk_session_id TO memory_session_id"),t=!0),this.db.query("PRAGMA table_info(pending_messages)").all().some(d=>d.name==="claude_session_id")&&(p.info("DB","Repairing pending_messages columns"),this.db.run("ALTER TABLE pending_messages RENAME COLUMN claude_session_id TO content_session_id"),t=!0),this.db.query("PRAGMA table_info(observations)").all().some(d=>d.name==="sdk_session_id")&&(p.info("DB","Repairing observations columns"),this.db.run("ALTER TABLE observations RENAME COLUMN sdk_session_id TO memory_session_id"),t=!0),this.db.query("PRAGMA table_info(session_summaries)").all().some(d=>d.name==="sdk_session_id")&&(p.info("DB","Repairing session_summaries columns"),this.db.run("ALTER TABLE session_summaries RENAME COLUMN sdk_session_id TO memory_session_id"),t=!0),this.db.query("PRAGMA table_info(user_prompts)").all().some(d=>d.name==="claude_session_id")&&(p.info("DB","Repairing user_prompts columns"),this.db.run("ALTER TABLE user_prompts RENAME COLUMN claude_session_id TO content_session_id"),t=!0),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(19,new Date().toISOString()),t?p.info("DB","Session ID column rename repairs completed"):p.info("DB","No session ID column repairs needed")}catch(e){throw p.error("DB","Session ID column rename repair error",void 0,e),e}}updateMemorySessionId(e,t){this.db.prepare(` + `),this.db.run("CREATE INDEX IF NOT EXISTS idx_pending_messages_session ON pending_messages(session_db_id)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_pending_messages_status ON pending_messages(status)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_pending_messages_claude_session ON pending_messages(content_session_id)"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(16,new Date().toISOString()),p.info("DB","pending_messages table created successfully")}catch(e){throw p.error("DB","Pending messages table migration error",void 0,e),e}}renameSessionIdColumns(){if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(17))return;if(this.db.query("PRAGMA table_info(sdk_sessions)").all().some(r=>r.name==="content_session_id")){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(17,new Date().toISOString());return}p.info("DB","Renaming session ID columns for semantic clarity"),this.db.run("BEGIN TRANSACTION");try{this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN claude_session_id TO content_session_id"),this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN sdk_session_id TO memory_session_id"),this.db.run("ALTER TABLE pending_messages RENAME COLUMN claude_session_id TO content_session_id"),this.db.run("ALTER TABLE observations RENAME COLUMN sdk_session_id TO memory_session_id"),this.db.run("ALTER TABLE session_summaries RENAME COLUMN sdk_session_id TO memory_session_id"),this.db.run("ALTER TABLE user_prompts RENAME COLUMN claude_session_id TO content_session_id"),this.db.run("COMMIT"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(17,new Date().toISOString()),p.info("DB","Successfully renamed session ID columns")}catch(r){throw this.db.run("ROLLBACK"),p.error("DB","Session ID column rename migration error",void 0,r),r}}repairSessionIdColumnRename(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(19))return;p.info("DB","Checking session ID column renames (repair migration)");let t=!1;this.db.query("PRAGMA table_info(sdk_sessions)").all().some(d=>d.name==="claude_session_id")&&(p.info("DB","Repairing sdk_sessions columns"),this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN claude_session_id TO content_session_id"),this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN sdk_session_id TO memory_session_id"),t=!0),this.db.query("PRAGMA table_info(pending_messages)").all().some(d=>d.name==="claude_session_id")&&(p.info("DB","Repairing pending_messages columns"),this.db.run("ALTER TABLE pending_messages RENAME COLUMN claude_session_id TO content_session_id"),t=!0),this.db.query("PRAGMA table_info(observations)").all().some(d=>d.name==="sdk_session_id")&&(p.info("DB","Repairing observations columns"),this.db.run("ALTER TABLE observations RENAME COLUMN sdk_session_id TO memory_session_id"),t=!0),this.db.query("PRAGMA table_info(session_summaries)").all().some(d=>d.name==="sdk_session_id")&&(p.info("DB","Repairing session_summaries columns"),this.db.run("ALTER TABLE session_summaries RENAME COLUMN sdk_session_id TO memory_session_id"),t=!0),this.db.query("PRAGMA table_info(user_prompts)").all().some(d=>d.name==="claude_session_id")&&(p.info("DB","Repairing user_prompts columns"),this.db.run("ALTER TABLE user_prompts RENAME COLUMN claude_session_id TO content_session_id"),t=!0),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(19,new Date().toISOString()),t?p.info("DB","Session ID column rename repairs completed"):p.info("DB","No session ID column repairs needed")}catch(e){throw p.error("DB","Session ID column rename repair error",void 0,e),e}}updateMemorySessionId(e,t){this.db.prepare(` UPDATE sdk_sessions SET memory_session_id = ? WHERE id = ? diff --git a/plugin/scripts/worker-service.cjs b/plugin/scripts/worker-service.cjs index b3a2981f..7236c42d 100755 --- a/plugin/scripts/worker-service.cjs +++ b/plugin/scripts/worker-service.cjs @@ -243,7 +243,7 @@ ${s.stack}`:` ${s.message}`:this.getLevel()===0&&typeof s=="object"?l=` completed_at_epoch INTEGER, FOREIGN KEY (session_db_id) REFERENCES sdk_sessions(id) ON DELETE CASCADE ) - `),this.db.run("CREATE INDEX IF NOT EXISTS idx_pending_messages_session ON pending_messages(session_db_id)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_pending_messages_status ON pending_messages(status)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_pending_messages_claude_session ON pending_messages(content_session_id)"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(16,new Date().toISOString()),P.info("DB","pending_messages table created successfully")}catch(e){throw P.error("DB","Pending messages table migration error",void 0,e),e}}renameSessionIdColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(17))return;if(P.info("DB","Renaming session ID columns for semantic clarity"),this.db.query("PRAGMA table_info(sdk_sessions)").all().some(n=>n.name==="content_session_id")){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(17,new Date().toISOString());return}this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN claude_session_id TO content_session_id"),this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN sdk_session_id TO memory_session_id"),this.db.run("ALTER TABLE pending_messages RENAME COLUMN claude_session_id TO content_session_id"),this.db.run("ALTER TABLE observations RENAME COLUMN sdk_session_id TO memory_session_id"),this.db.run("ALTER TABLE session_summaries RENAME COLUMN sdk_session_id TO memory_session_id"),this.db.run("ALTER TABLE user_prompts RENAME COLUMN claude_session_id TO content_session_id"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(17,new Date().toISOString()),P.info("DB","Successfully renamed session ID columns")}catch(e){throw P.error("DB","Session ID column rename migration error",void 0,e),e}}repairSessionIdColumnRename(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(19))return;P.info("DB","Checking session ID column renames (repair migration)");let r=!1;this.db.query("PRAGMA table_info(sdk_sessions)").all().some(c=>c.name==="claude_session_id")&&(P.info("DB","Repairing sdk_sessions columns"),this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN claude_session_id TO content_session_id"),this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN sdk_session_id TO memory_session_id"),r=!0),this.db.query("PRAGMA table_info(pending_messages)").all().some(c=>c.name==="claude_session_id")&&(P.info("DB","Repairing pending_messages columns"),this.db.run("ALTER TABLE pending_messages RENAME COLUMN claude_session_id TO content_session_id"),r=!0),this.db.query("PRAGMA table_info(observations)").all().some(c=>c.name==="sdk_session_id")&&(P.info("DB","Repairing observations columns"),this.db.run("ALTER TABLE observations RENAME COLUMN sdk_session_id TO memory_session_id"),r=!0),this.db.query("PRAGMA table_info(session_summaries)").all().some(c=>c.name==="sdk_session_id")&&(P.info("DB","Repairing session_summaries columns"),this.db.run("ALTER TABLE session_summaries RENAME COLUMN sdk_session_id TO memory_session_id"),r=!0),this.db.query("PRAGMA table_info(user_prompts)").all().some(c=>c.name==="claude_session_id")&&(P.info("DB","Repairing user_prompts columns"),this.db.run("ALTER TABLE user_prompts RENAME COLUMN claude_session_id TO content_session_id"),r=!0),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(19,new Date().toISOString()),r?P.info("DB","Session ID column rename repairs completed"):P.info("DB","No session ID column repairs needed")}catch(e){throw P.error("DB","Session ID column rename repair error",void 0,e),e}}updateMemorySessionId(e,r){this.db.prepare(` + `),this.db.run("CREATE INDEX IF NOT EXISTS idx_pending_messages_session ON pending_messages(session_db_id)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_pending_messages_status ON pending_messages(status)"),this.db.run("CREATE INDEX IF NOT EXISTS idx_pending_messages_claude_session ON pending_messages(content_session_id)"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(16,new Date().toISOString()),P.info("DB","pending_messages table created successfully")}catch(e){throw P.error("DB","Pending messages table migration error",void 0,e),e}}renameSessionIdColumns(){if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(17))return;if(this.db.query("PRAGMA table_info(sdk_sessions)").all().some(n=>n.name==="content_session_id")){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(17,new Date().toISOString());return}P.info("DB","Renaming session ID columns for semantic clarity"),this.db.run("BEGIN TRANSACTION");try{this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN claude_session_id TO content_session_id"),this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN sdk_session_id TO memory_session_id"),this.db.run("ALTER TABLE pending_messages RENAME COLUMN claude_session_id TO content_session_id"),this.db.run("ALTER TABLE observations RENAME COLUMN sdk_session_id TO memory_session_id"),this.db.run("ALTER TABLE session_summaries RENAME COLUMN sdk_session_id TO memory_session_id"),this.db.run("ALTER TABLE user_prompts RENAME COLUMN claude_session_id TO content_session_id"),this.db.run("COMMIT"),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(17,new Date().toISOString()),P.info("DB","Successfully renamed session ID columns")}catch(n){throw this.db.run("ROLLBACK"),P.error("DB","Session ID column rename migration error",void 0,n),n}}repairSessionIdColumnRename(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(19))return;P.info("DB","Checking session ID column renames (repair migration)");let r=!1;this.db.query("PRAGMA table_info(sdk_sessions)").all().some(c=>c.name==="claude_session_id")&&(P.info("DB","Repairing sdk_sessions columns"),this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN claude_session_id TO content_session_id"),this.db.run("ALTER TABLE sdk_sessions RENAME COLUMN sdk_session_id TO memory_session_id"),r=!0),this.db.query("PRAGMA table_info(pending_messages)").all().some(c=>c.name==="claude_session_id")&&(P.info("DB","Repairing pending_messages columns"),this.db.run("ALTER TABLE pending_messages RENAME COLUMN claude_session_id TO content_session_id"),r=!0),this.db.query("PRAGMA table_info(observations)").all().some(c=>c.name==="sdk_session_id")&&(P.info("DB","Repairing observations columns"),this.db.run("ALTER TABLE observations RENAME COLUMN sdk_session_id TO memory_session_id"),r=!0),this.db.query("PRAGMA table_info(session_summaries)").all().some(c=>c.name==="sdk_session_id")&&(P.info("DB","Repairing session_summaries columns"),this.db.run("ALTER TABLE session_summaries RENAME COLUMN sdk_session_id TO memory_session_id"),r=!0),this.db.query("PRAGMA table_info(user_prompts)").all().some(c=>c.name==="claude_session_id")&&(P.info("DB","Repairing user_prompts columns"),this.db.run("ALTER TABLE user_prompts RENAME COLUMN claude_session_id TO content_session_id"),r=!0),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(19,new Date().toISOString()),r?P.info("DB","Session ID column rename repairs completed"):P.info("DB","No session ID column repairs needed")}catch(e){throw P.error("DB","Session ID column rename repair error",void 0,e),e}}updateMemorySessionId(e,r){this.db.prepare(` UPDATE sdk_sessions SET memory_session_id = ? WHERE id = ? diff --git a/src/services/sqlite/SessionStore.ts b/src/services/sqlite/SessionStore.ts index cc7c3065..f9b7a170 100644 --- a/src/services/sqlite/SessionStore.ts +++ b/src/services/sqlite/SessionStore.ts @@ -584,22 +584,25 @@ export class SessionStore { * - sdk_session_id → memory_session_id (memory agent's session for resume) */ private renameSessionIdColumns(): void { + const applied = this.db.prepare('SELECT version FROM schema_versions WHERE version = ?').get(17) as SchemaVersion | undefined; + if (applied) return; + + // Check if columns are already renamed (idempotent check) + const sessionsInfo = this.db.query('PRAGMA table_info(sdk_sessions)').all() as TableColumnInfo[]; + const hasContentSessionId = sessionsInfo.some(col => col.name === 'content_session_id'); + + if (hasContentSessionId) { + // Already renamed, just record migration + this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(17, new Date().toISOString()); + return; + } + + logger.info('DB', 'Renaming session ID columns for semantic clarity'); + + // Begin transaction for atomic rename + this.db.run('BEGIN TRANSACTION'); + try { - const applied = this.db.prepare('SELECT version FROM schema_versions WHERE version = ?').get(17) as SchemaVersion | undefined; - if (applied) return; - - logger.info('DB', 'Renaming session ID columns for semantic clarity'); - - // Check if columns are already renamed (idempotent check) - const sessionsInfo = this.db.query('PRAGMA table_info(sdk_sessions)').all() as TableColumnInfo[]; - const hasContentSessionId = sessionsInfo.some(col => col.name === 'content_session_id'); - - if (hasContentSessionId) { - // Already renamed, just record migration - this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(17, new Date().toISOString()); - return; - } - // SQLite 3.25+ supports ALTER TABLE RENAME COLUMN // Rename in sdk_sessions table this.db.run('ALTER TABLE sdk_sessions RENAME COLUMN claude_session_id TO content_session_id'); @@ -617,11 +620,16 @@ export class SessionStore { // Rename in user_prompts table this.db.run('ALTER TABLE user_prompts RENAME COLUMN claude_session_id TO content_session_id'); + // Commit transaction + this.db.run('COMMIT'); + // Record migration this.db.prepare('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)').run(17, new Date().toISOString()); logger.info('DB', 'Successfully renamed session ID columns'); } catch (error: any) { + // Rollback on error + this.db.run('ROLLBACK'); logger.error('DB', 'Session ID column rename migration error', undefined, error); throw error; }