@@ -2,26 +2,27 @@ import type { Plugin, PluginInput, Hooks } from '@opencode-ai/plugin'
22import { tool } from '@opencode-ai/plugin'
33import { agents } from './agents'
44import { createConfigHandler } from './config'
5+ import { VERSION } from './version'
56import {
67 createSessionHooks ,
78 createKeywordHooks ,
89 createParamsHooks ,
910 ACTIVATION_CONTEXT ,
1011} from './hooks'
1112import { join } from 'path'
12- import { initializeDatabase , resolveDataDir , closeDatabase , createMetadataQuery } from './storage'
13+ import { initializeDatabase , resolveDataDir , closeDatabase , createMetadataQuery , getTableDimensions , recreateVecTable } from './storage'
1314import type { MemoryService } from './services/memory'
1415import { createVecService } from './storage/vec'
15- import { createEmbeddingProvider , checkServerHealth , isServerRunning } from './embedding'
16+ import { createEmbeddingProvider , checkServerHealth , isServerRunning , killEmbeddingServer } from './embedding'
1617import { createMemoryService } from './services/memory'
1718import { createSessionStateService } from './services/session-state'
1819import { createEmbeddingSyncService } from './services/embedding-sync'
1920import { loadPluginConfig } from './setup'
2021import { resolveLogPath } from './storage'
2122import { createLogger } from './utils/logger'
23+ import type { Database } from 'bun:sqlite'
2224import type { PluginConfig , CompactionConfig , HealthStatus , Logger , PlanningState } from './types'
2325import type { EmbeddingProvider } from './embedding'
24- import type { Database } from 'bun:sqlite'
2526import { createNoopVecService } from './storage/vec'
2627
2728
@@ -108,7 +109,8 @@ function formatHealthStatus(status: HealthStatus, provider: EmbeddingProvider):
108109 const embeddingStatus : 'ok' | 'error' = operational ? 'ok' : 'error'
109110
110111 const lines : string [ ] = [
111- `Memory Plugin Health: ${ overallStatus . toUpperCase ( ) } ` ,
112+ `Memory Plugin v${ VERSION } ` ,
113+ `Status: ${ overallStatus . toUpperCase ( ) } ` ,
112114 '' ,
113115 `Embedding: ${ embeddingStatus } ` ,
114116 ` Provider: ${ provider . name } (${ provider . dimensions } d)` ,
@@ -150,11 +152,19 @@ async function executeHealthCheck(
150152 return formatHealthStatus ( status , provider )
151153}
152154
155+ interface DimensionMismatchState {
156+ detected : boolean
157+ expected : number | null
158+ actual : number | null
159+ }
160+
153161async function executeReindex (
154162 memoryService : MemoryService ,
155163 db : Database ,
156164 config : PluginConfig ,
157165 provider : EmbeddingProvider ,
166+ dataDir : string ,
167+ mismatchState : DimensionMismatchState ,
158168) : Promise < string > {
159169 const configuredModel = config . embedding . model
160170 const configuredDimensions = config . embedding . dimensions ?? provider . dimensions
@@ -170,13 +180,26 @@ async function executeReindex(
170180 return 'Reindex failed: embedding provider is not operational. Check your API key and model configuration.'
171181 }
172182
183+ const tableResult = getTableDimensions ( db )
184+ if ( tableResult . exists && tableResult . dimensions !== configuredDimensions ) {
185+ recreateVecTable ( db , configuredDimensions )
186+ const newVec = await createVecService ( db , dataDir , configuredDimensions )
187+ memoryService . setVecService ( newVec )
188+ }
189+
173190 const result = await memoryService . reindex ( )
174191
175192 if ( result . success > 0 || result . total === 0 ) {
176193 const metadata = createMetadataQuery ( db )
177194 metadata . setEmbeddingModel ( configuredModel , configuredDimensions )
178195 }
179196
197+ if ( result . failed === 0 ) {
198+ mismatchState . detected = false
199+ mismatchState . expected = null
200+ mismatchState . actual = null
201+ }
202+
180203 const lines : string [ ] = [
181204 'Reindex complete' ,
182205 '' ,
@@ -248,6 +271,11 @@ export function createMemoryPlugin(config: PluginConfig): Plugin {
248271 provider . warmup ( )
249272
250273 const dataDir = config . dataDir ?? resolveDataDir ( )
274+
275+ if ( config . embedding . provider !== 'local' ) {
276+ killEmbeddingServer ( dataDir ) . catch ( ( ) => { } )
277+ }
278+
251279 const db = initializeDatabase ( dataDir )
252280 const dimensions = config . embedding . dimensions ?? provider . dimensions
253281
@@ -267,6 +295,12 @@ export function createMemoryPlugin(config: PluginConfig): Plugin {
267295 memoryService . setDedupThreshold ( config . dedupThreshold )
268296 }
269297
298+ const mismatchState : DimensionMismatchState = {
299+ detected : false ,
300+ expected : null ,
301+ actual : null ,
302+ }
303+
270304 const initPromise = createVecService ( db , dataDir , dimensions )
271305 . then ( async ( vec ) => {
272306 memoryService . setVecService ( vec )
@@ -278,6 +312,16 @@ export function createMemoryPlugin(config: PluginConfig): Plugin {
278312
279313 logger . log ( 'Vec service initialized' )
280314
315+ const tableResult = getTableDimensions ( db )
316+ if ( tableResult . exists && tableResult . dimensions !== dimensions ) {
317+ mismatchState . detected = true
318+ mismatchState . expected = dimensions
319+ mismatchState . actual = tableResult . dimensions
320+ memoryService . setVecService ( createNoopVecService ( ) )
321+ logger . log ( `Dimension mismatch detected: config=${ dimensions } , table=${ tableResult . dimensions ?? 'unknown' } ` )
322+ return
323+ }
324+
281325 const embeddingSync = createEmbeddingSyncService ( memoryService , logger )
282326 await embeddingSync . start ( ) . catch ( ( err : unknown ) => {
283327 logger . error ( 'Embedding sync failed' , err )
@@ -296,6 +340,11 @@ export function createMemoryPlugin(config: PluginConfig): Plugin {
296340
297341 const scopeEnum = z . enum ( [ 'convention' , 'decision' , 'context' ] )
298342
343+ function withDimensionWarning ( result : string ) : string {
344+ if ( ! mismatchState . detected ) return result
345+ return `${ result } \n\n---\nWarning: Embedding dimension mismatch detected (config: ${ mismatchState . expected } d, database: ${ mismatchState . actual } d). Semantic search is disabled.\n- If you changed your embedding model intentionally, run memory-health with action "reindex" to rebuild embeddings.\n- If this was accidental, revert your embedding config to match the existing model.`
346+ }
347+
299348 let cleaned = false
300349 const cleanup = async ( ) => {
301350 if ( cleaned ) return
@@ -343,13 +392,13 @@ export function createMemoryPlugin(config: PluginConfig): Plugin {
343392
344393 logger . log ( `memory-read: returned ${ results . length } results` )
345394 if ( results . length === 0 ) {
346- return 'No memories found.'
395+ return withDimensionWarning ( 'No memories found.' )
347396 }
348397
349398 const formatted = results . map (
350399 ( m : any ) => `[${ m . id } ] (${ m . scope } ) - Created ${ new Date ( m . createdAt ) . toISOString ( ) . split ( 'T' ) [ 0 ] } \n${ m . content } `
351400 )
352- return `Found ${ results . length } memories:\n\n${ formatted . join ( '\n\n' ) } `
401+ return withDimensionWarning ( `Found ${ results . length } memories:\n\n${ formatted . join ( '\n\n' ) } ` )
353402 } ,
354403 } ) ,
355404 'memory-write' : tool ( {
@@ -369,7 +418,7 @@ export function createMemoryPlugin(config: PluginConfig): Plugin {
369418 } )
370419
371420 logger . log ( `memory-write: created id=${ result . id } , deduplicated=${ result . deduplicated } ` )
372- return `Memory stored (ID: #${ result . id } , scope: ${ args . scope } ).${ result . deduplicated ? ' (matched existing memory)' : '' } `
421+ return withDimensionWarning ( `Memory stored (ID: #${ result . id } , scope: ${ args . scope } ).${ result . deduplicated ? ' (matched existing memory)' : '' } ` )
373422 } ,
374423 } ) ,
375424 'memory-edit' : tool ( {
@@ -386,7 +435,7 @@ export function createMemoryPlugin(config: PluginConfig): Plugin {
386435 const memory = memoryService . getById ( args . id )
387436 if ( ! memory ) {
388437 logger . log ( `memory-edit: id=${ args . id } not found` )
389- return `Memory #${ args . id } not found.`
438+ return withDimensionWarning ( `Memory #${ args . id } not found.` )
390439 }
391440
392441 await memoryService . update ( args . id , {
@@ -395,7 +444,7 @@ export function createMemoryPlugin(config: PluginConfig): Plugin {
395444 } )
396445
397446 logger . log ( `memory-edit: updated id=${ args . id } ` )
398- return `Updated memory #${ args . id } (scope: ${ args . scope ?? memory . scope } ).`
447+ return withDimensionWarning ( `Updated memory #${ args . id } (scope: ${ args . scope ?? memory . scope } ).` )
399448 } ,
400449 } ) ,
401450 'memory-delete' : tool ( {
@@ -404,31 +453,32 @@ export function createMemoryPlugin(config: PluginConfig): Plugin {
404453 id : z . number ( ) . describe ( 'The memory ID to delete' ) ,
405454 } ,
406455 execute : async ( args ) => {
456+ await initPromise
407457 const id = args . id
408458 logger . log ( `memory-delete: id=${ id } ` )
409459
410460 const memory = memoryService . getById ( id )
411461 if ( ! memory ) {
412462 logger . log ( `memory-delete: id=${ id } not found` )
413- return `Memory #${ id } not found.`
463+ return withDimensionWarning ( `Memory #${ id } not found.` )
414464 }
415465
416466 await memoryService . delete ( id )
417467 logger . log ( `memory-delete: deleted id=${ id } ` )
418- return `Deleted memory #${ id } : "${ memory . content . substring ( 0 , 50 ) } ..." (${ memory . scope } )`
468+ return withDimensionWarning ( `Deleted memory #${ id } : "${ memory . content . substring ( 0 , 50 ) } ..." (${ memory . scope } )` )
419469 } ,
420470 } ) ,
421471 'memory-health' : tool ( {
422- description : 'Check memory plugin health or trigger a reindex of all embeddings. Use action "check" (default) to view status, or "reindex" to regenerate all embeddings when model has changed or embeddings are missing.' ,
472+ description : 'Check memory plugin health or trigger a reindex of all embeddings. Use action "check" (default) to view status, or "reindex" to regenerate all embeddings when model has changed or embeddings are missing. Always report the plugin version from the output. Never run reindex unless the user explicitly asks for it. ' ,
423473 args : {
424474 action : z . enum ( [ 'check' , 'reindex' ] ) . optional ( ) . default ( 'check' ) . describe ( 'Action to perform: "check" for health status, "reindex" to regenerate embeddings' ) ,
425475 } ,
426476 execute : async ( args ) => {
427477 await initPromise
428478 if ( args . action === 'reindex' ) {
429- return executeReindex ( memoryService , db , config , provider )
479+ return executeReindex ( memoryService , db , config , provider , dataDir , mismatchState )
430480 }
431- return executeHealthCheck ( db , config , provider , dataDir )
481+ return withDimensionWarning ( await executeHealthCheck ( db , config , provider , dataDir ) )
432482 } ,
433483 } ) ,
434484 'memory-planning-update' : tool ( {
0 commit comments