diff --git a/package.json b/package.json index 5d88329f..30960cda 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ }, "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.1.76", + "@chroma-core/default-embed": "^0.1.9", "@modelcontextprotocol/sdk": "^1.25.1", "ansi-to-html": "^0.7.2", "chromadb": "^3.2.2", diff --git a/plugin/package.json b/plugin/package.json index 5c03d7af..ddb941df 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -4,7 +4,9 @@ "private": true, "description": "Runtime dependencies for claude-mem bundled hooks", "type": "module", - "dependencies": {}, + "dependencies": { + "@chroma-core/default-embed": "^0.1.9" + }, "engines": { "node": ">=18.0.0", "bun": ">=1.0.0" diff --git a/scripts/build-hooks.js b/scripts/build-hooks.js index 49bcbaa4..6ffdca57 100644 --- a/scripts/build-hooks.js +++ b/scripts/build-hooks.js @@ -58,7 +58,10 @@ async function buildHooks() { private: true, description: 'Runtime dependencies for claude-mem bundled hooks', type: 'module', - dependencies: {}, + dependencies: { + // Chroma embedding function with native ONNX binaries (can't be bundled) + '@chroma-core/default-embed': '^0.1.9' + }, engines: { node: '>=18.0.0', bun: '>=1.0.0' @@ -94,9 +97,12 @@ async function buildHooks() { logLevel: 'error', // Suppress warnings (import.meta warning is benign) external: [ 'bun:sqlite', - // Optional chromadb embedding providers (not needed for HTTP client mode) + // Optional chromadb embedding providers 'cohere-ai', - 'ollama' + 'ollama', + // Default embedding function with native binaries + '@chroma-core/default-embed', + 'onnxruntime-node' ], define: { '__DEFAULT_PACKAGE_VERSION__': `"${version}"` diff --git a/src/services/sync/ChromaServerManager.ts b/src/services/sync/ChromaServerManager.ts index 8864dbac..4b9d7460 100644 --- a/src/services/sync/ChromaServerManager.ts +++ b/src/services/sync/ChromaServerManager.ts @@ -48,6 +48,7 @@ export class ChromaServerManager { /** * Start the Chroma HTTP server * Spawns `npx chroma run` as a background process + * If a server is already running (from previous worker), reuses it */ async start(): Promise { if (this.ready || this.starting) { @@ -57,6 +58,24 @@ export class ChromaServerManager { }); return; } + + // Check if a server is already running (from previous worker or manual start) + try { + const response = await fetch( + `http://${this.config.host}:${this.config.port}/api/v2/heartbeat` + ); + if (response.ok) { + logger.info('CHROMA_SERVER', 'Existing server detected, reusing', { + host: this.config.host, + port: this.config.port + }); + this.ready = true; + return; + } + } catch { + // No server running, proceed to start one + } + this.starting = true; // Cross-platform: use npx.cmd on Windows @@ -159,9 +178,29 @@ export class ChromaServerManager { /** * Check if the server is running and ready + * Returns true if we manage the process OR if a server is responding */ isRunning(): boolean { - return this.ready && this.serverProcess !== null; + return this.ready; + } + + /** + * Async check if server is running by pinging heartbeat + * Use this when you need to verify server is actually reachable + */ + async isServerReachable(): Promise { + try { + const response = await fetch( + `http://${this.config.host}:${this.config.port}/api/v2/heartbeat` + ); + if (response.ok) { + this.ready = true; + return true; + } + } catch { + // Server not reachable + } + return false; } /** diff --git a/src/services/sync/ChromaSync.ts b/src/services/sync/ChromaSync.ts index 3fcbe41c..e9d11e59 100644 --- a/src/services/sync/ChromaSync.ts +++ b/src/services/sync/ChromaSync.ts @@ -13,6 +13,7 @@ */ import { ChromaClient, Collection } from 'chromadb'; +import { DefaultEmbeddingFunction } from '@chroma-core/default-embed'; import { ParsedObservation, ParsedSummary } from '../../sdk/parser.js'; import { SessionStore } from '../sqlite/SessionStore.js'; import { logger } from '../../utils/logger.js'; @@ -108,11 +109,12 @@ export class ChromaSync { const port = parseInt(settings.CLAUDE_MEM_CHROMA_PORT || '8000', 10); const ssl = settings.CLAUDE_MEM_CHROMA_SSL === 'true'; - // In local mode, verify server is running + // In local mode, verify server is reachable if (mode === 'local') { const serverManager = ChromaServerManager.getInstance(); - if (!serverManager.isRunning()) { - throw new Error('Chroma server not running. Ensure worker started correctly.'); + const reachable = await serverManager.isServerReachable(); + if (!reachable) { + throw new Error('Chroma server not reachable. Ensure worker started correctly.'); } } @@ -159,8 +161,11 @@ export class ChromaSync { try { // getOrCreateCollection handles both cases + // Use DefaultEmbeddingFunction for local embeddings (all-MiniLM-L6-v2) + const embeddingFunction = new DefaultEmbeddingFunction(); this.collection = await this.chromaClient.getOrCreateCollection({ - name: this.collectionName + name: this.collectionName, + embeddingFunction }); logger.debug('CHROMA_SYNC', 'Collection ready', { collection: this.collectionName });