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/README.md +16 -2
- package/dist/cli/index.js +37 -5
- package/dist/cli/index.js.map +2 -2
- package/dist/core/index.js +41 -5
- package/dist/core/index.js.map +2 -2
- package/dist/hooks/post-tool-use.js +35 -3
- package/dist/hooks/post-tool-use.js.map +2 -2
- package/dist/hooks/semantic-daemon.js +35 -3
- package/dist/hooks/semantic-daemon.js.map +2 -2
- package/dist/hooks/session-end.js +35 -3
- package/dist/hooks/session-end.js.map +2 -2
- package/dist/hooks/session-start.js +35 -3
- package/dist/hooks/session-start.js.map +2 -2
- package/dist/hooks/stop.js +35 -3
- package/dist/hooks/stop.js.map +2 -2
- package/dist/hooks/user-prompt-submit.js +35 -3
- package/dist/hooks/user-prompt-submit.js.map +2 -2
- package/dist/index.js +41 -5
- package/dist/index.js.map +2 -2
- package/dist/mcp/index.js +35 -3
- package/dist/mcp/index.js.map +2 -2
- package/dist/server/api/index.js +35 -3
- package/dist/server/api/index.js.map +2 -2
- package/dist/server/index.js +35 -3
- package/dist/server/index.js.map +2 -2
- package/dist/services/memory-service.js +35 -3
- package/dist/services/memory-service.js.map +2 -2
- package/package.json +1 -1
- package/scripts/postinstall-embedding-backend.cjs +2 -3
- package/src/apps/cli/index.ts +5 -1
- package/src/core/types.ts +2 -2
- package/src/extensions/vector/embedder.ts +39 -3
- package/tests/apps/postinstall-embedding-backend.test.ts +23 -15
- package/tests/extensions/embedder-warning-suppression.test.ts +30 -0
package/package.json
CHANGED
|
@@ -60,10 +60,9 @@ function isEmbeddingBackendAvailable(rootDir = process.cwd()) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
function shouldAttemptAutoInstall({ platform, arch,
|
|
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]
|
|
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(), {
|
package/src/apps/cli/index.ts
CHANGED
|
@@ -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>',
|
|
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('
|
|
156
|
-
openaiModel: z.string().default('
|
|
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 =
|
|
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(() =>
|
|
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 ||
|
|
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('
|
|
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:
|
|
82
|
-
transformersAvailable:
|
|
81
|
+
cudaMajor: null,
|
|
82
|
+
transformersAvailable: false,
|
|
83
83
|
skipRequested: false
|
|
84
|
-
})).toBe(
|
|
84
|
+
})).toBe(true);
|
|
85
85
|
|
|
86
86
|
expect(postinstall.shouldAttemptAutoInstall({
|
|
87
87
|
platform: 'linux',
|
|
88
|
-
arch: '
|
|
89
|
-
cudaMajor:
|
|
88
|
+
arch: 'x64',
|
|
89
|
+
cudaMajor: 12,
|
|
90
90
|
transformersAvailable: false,
|
|
91
91
|
skipRequested: false
|
|
92
|
-
})).toBe(
|
|
92
|
+
})).toBe(true);
|
|
93
93
|
|
|
94
94
|
expect(postinstall.shouldAttemptAutoInstall({
|
|
95
|
-
platform: '
|
|
95
|
+
platform: 'linux',
|
|
96
96
|
arch: 'x64',
|
|
97
|
-
cudaMajor:
|
|
98
|
-
transformersAvailable:
|
|
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:
|
|
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:
|
|
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
|
|
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: () => '
|
|
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:
|
|
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);
|