claude-memory-layer 1.0.29 → 1.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-memory-layer",
3
- "version": "1.0.29",
3
+ "version": "1.0.30",
4
4
  "description": "Claude Code plugin that learns from conversations to provide personalized assistance",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -60,10 +60,9 @@ function isEmbeddingBackendAvailable(rootDir = process.cwd()) {
60
60
  }
61
61
  }
62
62
 
63
- function shouldAttemptAutoInstall({ platform, arch, cudaMajor, transformersAvailable, skipRequested }) {
63
+ function shouldAttemptAutoInstall({ platform, arch, transformersAvailable, skipRequested }) {
64
64
  return platform === 'linux' &&
65
65
  arch === 'x64' &&
66
- cudaMajor === 11 &&
67
66
  !transformersAvailable &&
68
67
  !skipRequested;
69
68
  }
@@ -104,7 +103,7 @@ function runPostinstall({
104
103
  return { attempted: false, cudaMajor, transformersAvailable, skipRequested };
105
104
  }
106
105
 
107
- log('[claude-memory-layer] CUDA 11 detected and optional embedding backend is missing. Installing CPU-only embedding backend...');
106
+ log('[claude-memory-layer] Optional embedding backend is missing on Linux x64. Installing CPU-only embedding backend...');
108
107
 
109
108
  const npmCommand = platform === 'win32' ? 'npm.cmd' : 'npm';
110
109
  const result = spawnSyncImpl(npmCommand, createNpmInstallArgs(), {
@@ -59,6 +59,10 @@ import {
59
59
  renderExternalMarketContextReport,
60
60
  type ExternalMarketProvider
61
61
  } from '../../core/external-market-context.js';
62
+ import {
63
+ DEFAULT_EMBEDDING_FALLBACK_MODEL,
64
+ DEFAULT_EMBEDDING_MODEL
65
+ } from '../../extensions/vector/embedder.js';
62
66
 
63
67
  // ============================================================
64
68
  // Hook Installation Utilities
@@ -1025,7 +1029,7 @@ program
1025
1029
  .option('-l, --limit <number>', 'Limit messages per session')
1026
1030
  .option('--session-limit <number>', 'Limit recent matching sessions to import')
1027
1031
  .option('-f, --force', 'Force reimport: delete existing events and reimport with turn_id grouping')
1028
- .option('--embedding-model <name>', 'Embedding model override (default: jinaai/jina-embeddings-v5-text-nano-text-matching, or env CLAUDE_MEMORY_EMBEDDING_MODEL; fallback env: CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL)')
1032
+ .option('--embedding-model <name>', `Embedding model override (default: ${DEFAULT_EMBEDDING_MODEL}, or env CLAUDE_MEMORY_EMBEDDING_MODEL; fallback: ${DEFAULT_EMBEDDING_FALLBACK_MODEL} or env CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL)`)
1029
1033
  .option('-v, --verbose', 'Show detailed progress')
1030
1034
  .action(async (options) => {
1031
1035
  const startTime = Date.now();
package/src/core/types.ts CHANGED
@@ -152,8 +152,8 @@ export const ConfigSchema = z.object({
152
152
  }).default({}),
153
153
  embedding: z.object({
154
154
  provider: z.enum(['local', 'openai']).default('local'),
155
- model: z.string().default('jinaai/jina-embeddings-v5-text-nano-text-matching'),
156
- openaiModel: z.string().default('jinaai/jina-embeddings-v5-text-nano-text-matching'),
155
+ model: z.string().default('Xenova/multilingual-e5-small'),
156
+ openaiModel: z.string().default('Xenova/multilingual-e5-small'),
157
157
  batchSize: z.number().default(32)
158
158
  }).default({}),
159
159
  retrieval: z.object({
@@ -14,13 +14,16 @@ type FeatureExtractionPipelineFactory = (
14
14
  model: string
15
15
  ) => Promise<NonNullable<Embedder['pipeline']>>;
16
16
 
17
+ export const DEFAULT_EMBEDDING_MODEL = 'Xenova/multilingual-e5-small';
18
+ export const DEFAULT_EMBEDDING_FALLBACK_MODEL = 'intfloat/multilingual-e5-small';
19
+
17
20
  export class Embedder {
18
21
  private pipeline: ((input: string, options?: Record<string, unknown>) => Promise<{ data: Float32Array }>) | null = null;
19
22
  private readonly modelName: string;
20
23
  private activeModelName: string;
21
24
  private initialized = false;
22
25
 
23
- constructor(modelName: string = 'jinaai/jina-embeddings-v5-text-nano-text-matching') {
26
+ constructor(modelName: string = DEFAULT_EMBEDDING_MODEL) {
24
27
  this.modelName = modelName;
25
28
  this.activeModelName = modelName;
26
29
  }
@@ -31,7 +34,16 @@ export class Embedder {
31
34
  async initialize(): Promise<void> {
32
35
  if (this.initialized) return;
33
36
 
34
- const pipeline = await withSuppressedKnownTransformersWarnings(() => loadTransformersPipeline());
37
+ const pipeline = await withSuppressedKnownTransformersWarnings(async () => {
38
+ try {
39
+ return await loadTransformersPipeline();
40
+ } catch (error) {
41
+ if (isMissingTransformersDependencyError(error)) {
42
+ throw createEmbeddingBackendUnavailableError(error);
43
+ }
44
+ throw error;
45
+ }
46
+ });
35
47
 
36
48
  try {
37
49
  this.pipeline = await withSuppressedKnownTransformersWarnings(() => pipeline('feature-extraction', this.modelName));
@@ -39,7 +51,7 @@ export class Embedder {
39
51
  this.initialized = true;
40
52
  return;
41
53
  } catch (primaryError) {
42
- const fallbackModel = process.env.CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL || 'onnx-community/embeddinggemma-300m-ONNX';
54
+ const fallbackModel = process.env.CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL || DEFAULT_EMBEDDING_FALLBACK_MODEL;
43
55
  if (fallbackModel === this.modelName) {
44
56
  throw primaryError;
45
57
  }
@@ -186,6 +198,30 @@ export function isKnownBenignTransformersWarning(message: string): boolean {
186
198
  message.includes('dtype not specified for "model"');
187
199
  }
188
200
 
201
+ export function isMissingTransformersDependencyError(error: unknown): boolean {
202
+ const maybeError = error as { code?: unknown; message?: unknown } | null;
203
+ const message = typeof maybeError?.message === 'string' ? maybeError.message : '';
204
+ return maybeError?.code === 'ERR_MODULE_NOT_FOUND' &&
205
+ message.includes("@huggingface/transformers");
206
+ }
207
+
208
+ export function createEmbeddingBackendUnavailableError(cause: unknown): Error & { cause?: unknown } {
209
+ const error = new Error(
210
+ [
211
+ 'Optional embedding backend is not installed.',
212
+ '',
213
+ 'Claude Memory Layer can run embeddings on CPU-only ONNX Runtime; CUDA is not required.',
214
+ 'Reinstall globally with:',
215
+ ' ONNXRUNTIME_NODE_INSTALL_CUDA=skip npm install -g claude-memory-layer@latest',
216
+ '',
217
+ 'If you are inside a local checkout or package directory, repair only the backend with:',
218
+ ' ONNXRUNTIME_NODE_INSTALL_CUDA=skip npm install --no-save --no-package-lock --omit=dev @huggingface/transformers@3.8.1'
219
+ ].join('\n')
220
+ ) as Error & { cause?: unknown };
221
+ error.cause = cause;
222
+ return error;
223
+ }
224
+
189
225
  async function loadTransformersPipeline(): Promise<FeatureExtractionPipelineFactory> {
190
226
  // Keep @huggingface/transformers lazy so importing MemoryService or pure
191
227
  // adapter helpers does not eagerly dlopen onnxruntime native bindings.
@@ -64,7 +64,7 @@ describe('embedding backend postinstall repair', () => {
64
64
  expect(postinstall.parseCudaMajor('nvcc: NVIDIA (R) Cuda compiler driver')).toBeNull();
65
65
  });
66
66
 
67
- it('only auto-installs the embedding backend for Linux x64 CUDA 11 when it is missing', () => {
67
+ it('auto-installs a missing embedding backend for Linux x64 even when CUDA cannot be detected', () => {
68
68
  const postinstall = loadPostinstallModule();
69
69
 
70
70
  expect(postinstall.shouldAttemptAutoInstall({
@@ -78,31 +78,39 @@ describe('embedding backend postinstall repair', () => {
78
78
  expect(postinstall.shouldAttemptAutoInstall({
79
79
  platform: 'linux',
80
80
  arch: 'x64',
81
- cudaMajor: 11,
82
- transformersAvailable: true,
81
+ cudaMajor: null,
82
+ transformersAvailable: false,
83
83
  skipRequested: false
84
- })).toBe(false);
84
+ })).toBe(true);
85
85
 
86
86
  expect(postinstall.shouldAttemptAutoInstall({
87
87
  platform: 'linux',
88
- arch: 'arm64',
89
- cudaMajor: 11,
88
+ arch: 'x64',
89
+ cudaMajor: 12,
90
90
  transformersAvailable: false,
91
91
  skipRequested: false
92
- })).toBe(false);
92
+ })).toBe(true);
93
93
 
94
94
  expect(postinstall.shouldAttemptAutoInstall({
95
- platform: 'darwin',
95
+ platform: 'linux',
96
96
  arch: 'x64',
97
- cudaMajor: 11,
98
- transformersAvailable: false,
97
+ cudaMajor: null,
98
+ transformersAvailable: true,
99
99
  skipRequested: false
100
100
  })).toBe(false);
101
101
 
102
102
  expect(postinstall.shouldAttemptAutoInstall({
103
103
  platform: 'linux',
104
+ arch: 'arm64',
105
+ cudaMajor: null,
106
+ transformersAvailable: false,
107
+ skipRequested: false
108
+ })).toBe(false);
109
+
110
+ expect(postinstall.shouldAttemptAutoInstall({
111
+ platform: 'darwin',
104
112
  arch: 'x64',
105
- cudaMajor: 12,
113
+ cudaMajor: null,
106
114
  transformersAvailable: false,
107
115
  skipRequested: false
108
116
  })).toBe(false);
@@ -110,7 +118,7 @@ describe('embedding backend postinstall repair', () => {
110
118
  expect(postinstall.shouldAttemptAutoInstall({
111
119
  platform: 'linux',
112
120
  arch: 'x64',
113
- cudaMajor: 11,
121
+ cudaMajor: null,
114
122
  transformersAvailable: false,
115
123
  skipRequested: true
116
124
  })).toBe(false);
@@ -132,7 +140,7 @@ describe('embedding backend postinstall repair', () => {
132
140
  ]);
133
141
  });
134
142
 
135
- it('runs the automatic repair command when Linux x64 CUDA 11 skipped the optional backend', () => {
143
+ it('runs the automatic repair command when Linux x64 skipped the optional backend without detectable CUDA', () => {
136
144
  const postinstall = loadPostinstallModule();
137
145
  const rootDir = mkdtempSync(join(tmpdir(), 'cml-postinstall-test-'));
138
146
  const calls: SpawnCall[] = [];
@@ -145,7 +153,7 @@ describe('embedding backend postinstall repair', () => {
145
153
  env: {},
146
154
  platform: 'linux',
147
155
  arch: 'x64',
148
- execFileSyncImpl: () => 'Cuda compilation tools, release 11.8, V11.8.89',
156
+ execFileSyncImpl: () => '',
149
157
  spawnSyncImpl: (cmd, args, options) => {
150
158
  calls.push({ cmd, args, env: options.env });
151
159
  return { status: 0 };
@@ -154,7 +162,7 @@ describe('embedding backend postinstall repair', () => {
154
162
  warn: () => undefined
155
163
  });
156
164
 
157
- expect(result).toMatchObject({ attempted: true, success: true, cudaMajor: 11, transformersAvailable: false });
165
+ expect(result).toMatchObject({ attempted: true, success: true, cudaMajor: null, transformersAvailable: false });
158
166
  expect(calls).toHaveLength(1);
159
167
  expect(calls[0]?.cmd).toBe('npm');
160
168
  expect(calls[0]?.args).toEqual(postinstall.createNpmInstallArgs());
@@ -1,11 +1,41 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
2
 
3
3
  import {
4
+ DEFAULT_EMBEDDING_MODEL,
5
+ Embedder,
6
+ createEmbeddingBackendUnavailableError,
4
7
  isKnownBenignTransformersWarning,
8
+ isMissingTransformersDependencyError,
5
9
  withSuppressedKnownTransformersWarnings
6
10
  } from '../../src/extensions/vector/embedder.js';
11
+ import { ConfigSchema } from '../../src/core/types.js';
7
12
 
8
13
  describe('Embedder warning suppression', () => {
14
+ it('uses a CPU-friendly multilingual Korean-capable default embedding model', () => {
15
+ expect(DEFAULT_EMBEDDING_MODEL).toBe('Xenova/multilingual-e5-small');
16
+ expect(new Embedder().getModelName()).toBe(DEFAULT_EMBEDDING_MODEL);
17
+
18
+ const parsedConfig = ConfigSchema.parse({});
19
+ expect(parsedConfig.embedding.model).toBe(DEFAULT_EMBEDDING_MODEL);
20
+ expect(parsedConfig.embedding.openaiModel).toBe(DEFAULT_EMBEDDING_MODEL);
21
+ });
22
+
23
+ it('turns missing optional @huggingface/transformers errors into actionable install guidance', () => {
24
+ const missingBackendError = Object.assign(
25
+ new Error("Cannot find package '@huggingface/transformers' imported from /tmp/dist/cli/index.js"),
26
+ { code: 'ERR_MODULE_NOT_FOUND' }
27
+ );
28
+
29
+ expect(isMissingTransformersDependencyError(missingBackendError)).toBe(true);
30
+ expect(isMissingTransformersDependencyError(new Error('network failure'))).toBe(false);
31
+
32
+ const friendly = createEmbeddingBackendUnavailableError(missingBackendError);
33
+ expect(friendly.message).toContain('Optional embedding backend is not installed');
34
+ expect(friendly.message).toContain('ONNXRUNTIME_NODE_INSTALL_CUDA=skip npm install -g claude-memory-layer@latest');
35
+ expect(friendly.message).toContain('ONNXRUNTIME_NODE_INSTALL_CUDA=skip npm install --no-save --no-package-lock --omit=dev @huggingface/transformers@3.8.1');
36
+ expect(friendly.cause).toBe(missingBackendError);
37
+ });
38
+
9
39
  it('recognizes known benign transformer warnings', () => {
10
40
  expect(isKnownBenignTransformersWarning('Unknown model class "eurobert", attempting to construct from base class.')).toBe(true);
11
41
  expect(isKnownBenignTransformersWarning('dtype not specified for "model". Using the default dtype (fp32).')).toBe(true);