claude-memory-layer 1.0.0
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/.claude-plugin/commands/memory-forget.md +42 -0
- package/.claude-plugin/commands/memory-history.md +34 -0
- package/.claude-plugin/commands/memory-import.md +56 -0
- package/.claude-plugin/commands/memory-list.md +37 -0
- package/.claude-plugin/commands/memory-search.md +36 -0
- package/.claude-plugin/commands/memory-stats.md +34 -0
- package/.claude-plugin/hooks.json +59 -0
- package/.claude-plugin/plugin.json +24 -0
- package/.history/package_20260201112328.json +45 -0
- package/.history/package_20260201113602.json +45 -0
- package/.history/package_20260201113713.json +45 -0
- package/.history/package_20260201114110.json +45 -0
- package/Memo.txt +558 -0
- package/README.md +520 -0
- package/context.md +636 -0
- package/dist/.claude-plugin/commands/memory-forget.md +42 -0
- package/dist/.claude-plugin/commands/memory-history.md +34 -0
- package/dist/.claude-plugin/commands/memory-import.md +56 -0
- package/dist/.claude-plugin/commands/memory-list.md +37 -0
- package/dist/.claude-plugin/commands/memory-search.md +36 -0
- package/dist/.claude-plugin/commands/memory-stats.md +34 -0
- package/dist/.claude-plugin/hooks.json +59 -0
- package/dist/.claude-plugin/plugin.json +24 -0
- package/dist/cli/index.js +3539 -0
- package/dist/cli/index.js.map +7 -0
- package/dist/core/index.js +4408 -0
- package/dist/core/index.js.map +7 -0
- package/dist/hooks/session-end.js +2971 -0
- package/dist/hooks/session-end.js.map +7 -0
- package/dist/hooks/session-start.js +2969 -0
- package/dist/hooks/session-start.js.map +7 -0
- package/dist/hooks/stop.js +3123 -0
- package/dist/hooks/stop.js.map +7 -0
- package/dist/hooks/user-prompt-submit.js +2960 -0
- package/dist/hooks/user-prompt-submit.js.map +7 -0
- package/dist/services/memory-service.js +2931 -0
- package/dist/services/memory-service.js.map +7 -0
- package/package.json +45 -0
- package/plan.md +1642 -0
- package/scripts/build.ts +102 -0
- package/spec.md +624 -0
- package/specs/citations-system/context.md +243 -0
- package/specs/citations-system/plan.md +495 -0
- package/specs/citations-system/spec.md +371 -0
- package/specs/endless-mode/context.md +305 -0
- package/specs/endless-mode/plan.md +620 -0
- package/specs/endless-mode/spec.md +455 -0
- package/specs/entity-edge-model/context.md +401 -0
- package/specs/entity-edge-model/plan.md +459 -0
- package/specs/entity-edge-model/spec.md +391 -0
- package/specs/evidence-aligner-v2/context.md +401 -0
- package/specs/evidence-aligner-v2/plan.md +303 -0
- package/specs/evidence-aligner-v2/spec.md +312 -0
- package/specs/mcp-desktop-integration/context.md +278 -0
- package/specs/mcp-desktop-integration/plan.md +550 -0
- package/specs/mcp-desktop-integration/spec.md +494 -0
- package/specs/post-tool-use-hook/context.md +319 -0
- package/specs/post-tool-use-hook/plan.md +469 -0
- package/specs/post-tool-use-hook/spec.md +364 -0
- package/specs/private-tags/context.md +288 -0
- package/specs/private-tags/plan.md +412 -0
- package/specs/private-tags/spec.md +345 -0
- package/specs/progressive-disclosure/context.md +346 -0
- package/specs/progressive-disclosure/plan.md +663 -0
- package/specs/progressive-disclosure/spec.md +415 -0
- package/specs/task-entity-system/context.md +297 -0
- package/specs/task-entity-system/plan.md +301 -0
- package/specs/task-entity-system/spec.md +314 -0
- package/specs/vector-outbox-v2/context.md +470 -0
- package/specs/vector-outbox-v2/plan.md +562 -0
- package/specs/vector-outbox-v2/spec.md +466 -0
- package/specs/web-viewer-ui/context.md +384 -0
- package/specs/web-viewer-ui/plan.md +797 -0
- package/specs/web-viewer-ui/spec.md +516 -0
- package/src/cli/index.ts +570 -0
- package/src/core/canonical-key.ts +186 -0
- package/src/core/citation-generator.ts +63 -0
- package/src/core/consolidated-store.ts +279 -0
- package/src/core/consolidation-worker.ts +384 -0
- package/src/core/context-formatter.ts +276 -0
- package/src/core/continuity-manager.ts +336 -0
- package/src/core/edge-repo.ts +324 -0
- package/src/core/embedder.ts +124 -0
- package/src/core/entity-repo.ts +342 -0
- package/src/core/event-store.ts +672 -0
- package/src/core/evidence-aligner.ts +635 -0
- package/src/core/graduation.ts +365 -0
- package/src/core/index.ts +32 -0
- package/src/core/matcher.ts +210 -0
- package/src/core/metadata-extractor.ts +203 -0
- package/src/core/privacy/filter.ts +179 -0
- package/src/core/privacy/index.ts +20 -0
- package/src/core/privacy/tag-parser.ts +145 -0
- package/src/core/progressive-retriever.ts +415 -0
- package/src/core/retriever.ts +235 -0
- package/src/core/task/blocker-resolver.ts +325 -0
- package/src/core/task/index.ts +9 -0
- package/src/core/task/task-matcher.ts +238 -0
- package/src/core/task/task-projector.ts +345 -0
- package/src/core/task/task-resolver.ts +414 -0
- package/src/core/types.ts +841 -0
- package/src/core/vector-outbox.ts +295 -0
- package/src/core/vector-store.ts +182 -0
- package/src/core/vector-worker.ts +488 -0
- package/src/core/working-set-store.ts +244 -0
- package/src/hooks/post-tool-use.ts +127 -0
- package/src/hooks/session-end.ts +78 -0
- package/src/hooks/session-start.ts +57 -0
- package/src/hooks/stop.ts +78 -0
- package/src/hooks/user-prompt-submit.ts +54 -0
- package/src/mcp/handlers.ts +212 -0
- package/src/mcp/index.ts +47 -0
- package/src/mcp/tools.ts +78 -0
- package/src/server/api/citations.ts +101 -0
- package/src/server/api/events.ts +101 -0
- package/src/server/api/index.ts +18 -0
- package/src/server/api/search.ts +98 -0
- package/src/server/api/sessions.ts +111 -0
- package/src/server/api/stats.ts +97 -0
- package/src/server/index.ts +91 -0
- package/src/services/memory-service.ts +626 -0
- package/src/services/session-history-importer.ts +367 -0
- package/tests/canonical-key.test.ts +101 -0
- package/tests/evidence-aligner.test.ts +152 -0
- package/tests/matcher.test.ts +112 -0
- package/tsconfig.json +24 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Service - Main entry point for memory operations
|
|
3
|
+
* Coordinates EventStore, VectorStore, Retriever, and Graduation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as os from 'os';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as crypto from 'crypto';
|
|
10
|
+
|
|
11
|
+
import { EventStore } from '../core/event-store.js';
|
|
12
|
+
import { VectorStore } from '../core/vector-store.js';
|
|
13
|
+
import { Embedder, getDefaultEmbedder } from '../core/embedder.js';
|
|
14
|
+
import { VectorWorker, createVectorWorker } from '../core/vector-worker.js';
|
|
15
|
+
import { Matcher, getDefaultMatcher } from '../core/matcher.js';
|
|
16
|
+
import { Retriever, createRetriever, RetrievalResult } from '../core/retriever.js';
|
|
17
|
+
import { GraduationPipeline, createGraduationPipeline } from '../core/graduation.js';
|
|
18
|
+
import type {
|
|
19
|
+
MemoryEventInput,
|
|
20
|
+
AppendResult,
|
|
21
|
+
MemoryEvent,
|
|
22
|
+
Config,
|
|
23
|
+
ConfigSchema,
|
|
24
|
+
ToolObservationPayload,
|
|
25
|
+
MemoryMode,
|
|
26
|
+
EndlessModeConfig,
|
|
27
|
+
EndlessModeConfigSchema,
|
|
28
|
+
WorkingSet,
|
|
29
|
+
ConsolidatedMemory,
|
|
30
|
+
EndlessModeStatus,
|
|
31
|
+
ContextSnapshot,
|
|
32
|
+
ContinuityScore
|
|
33
|
+
} from '../core/types.js';
|
|
34
|
+
import { createToolObservationEmbedding } from '../core/metadata-extractor.js';
|
|
35
|
+
import { WorkingSetStore, createWorkingSetStore } from '../core/working-set-store.js';
|
|
36
|
+
import { ConsolidatedStore, createConsolidatedStore } from '../core/consolidated-store.js';
|
|
37
|
+
import { ConsolidationWorker, createConsolidationWorker } from '../core/consolidation-worker.js';
|
|
38
|
+
import { ContinuityManager, createContinuityManager } from '../core/continuity-manager.js';
|
|
39
|
+
|
|
40
|
+
export interface MemoryServiceConfig {
|
|
41
|
+
storagePath: string;
|
|
42
|
+
embeddingModel?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class MemoryService {
|
|
46
|
+
private readonly eventStore: EventStore;
|
|
47
|
+
private readonly vectorStore: VectorStore;
|
|
48
|
+
private readonly embedder: Embedder;
|
|
49
|
+
private readonly matcher: Matcher;
|
|
50
|
+
private readonly retriever: Retriever;
|
|
51
|
+
private readonly graduation: GraduationPipeline;
|
|
52
|
+
private vectorWorker: VectorWorker | null = null;
|
|
53
|
+
private initialized = false;
|
|
54
|
+
|
|
55
|
+
// Endless Mode components
|
|
56
|
+
private workingSetStore: WorkingSetStore | null = null;
|
|
57
|
+
private consolidatedStore: ConsolidatedStore | null = null;
|
|
58
|
+
private consolidationWorker: ConsolidationWorker | null = null;
|
|
59
|
+
private continuityManager: ContinuityManager | null = null;
|
|
60
|
+
private endlessMode: MemoryMode = 'session';
|
|
61
|
+
|
|
62
|
+
constructor(config: MemoryServiceConfig) {
|
|
63
|
+
const storagePath = this.expandPath(config.storagePath);
|
|
64
|
+
|
|
65
|
+
// Ensure storage directory exists
|
|
66
|
+
if (!fs.existsSync(storagePath)) {
|
|
67
|
+
fs.mkdirSync(storagePath, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Initialize components
|
|
71
|
+
this.eventStore = new EventStore(path.join(storagePath, 'events.duckdb'));
|
|
72
|
+
this.vectorStore = new VectorStore(path.join(storagePath, 'vectors'));
|
|
73
|
+
this.embedder = config.embeddingModel
|
|
74
|
+
? new Embedder(config.embeddingModel)
|
|
75
|
+
: getDefaultEmbedder();
|
|
76
|
+
this.matcher = getDefaultMatcher();
|
|
77
|
+
this.retriever = createRetriever(
|
|
78
|
+
this.eventStore,
|
|
79
|
+
this.vectorStore,
|
|
80
|
+
this.embedder,
|
|
81
|
+
this.matcher
|
|
82
|
+
);
|
|
83
|
+
this.graduation = createGraduationPipeline(this.eventStore);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Initialize all components
|
|
88
|
+
*/
|
|
89
|
+
async initialize(): Promise<void> {
|
|
90
|
+
if (this.initialized) return;
|
|
91
|
+
|
|
92
|
+
await this.eventStore.initialize();
|
|
93
|
+
await this.vectorStore.initialize();
|
|
94
|
+
await this.embedder.initialize();
|
|
95
|
+
|
|
96
|
+
// Start vector worker
|
|
97
|
+
this.vectorWorker = createVectorWorker(
|
|
98
|
+
this.eventStore,
|
|
99
|
+
this.vectorStore,
|
|
100
|
+
this.embedder
|
|
101
|
+
);
|
|
102
|
+
this.vectorWorker.start();
|
|
103
|
+
|
|
104
|
+
// Load endless mode setting
|
|
105
|
+
const savedMode = await this.eventStore.getEndlessConfig('mode') as MemoryMode | null;
|
|
106
|
+
if (savedMode === 'endless') {
|
|
107
|
+
this.endlessMode = 'endless';
|
|
108
|
+
await this.initializeEndlessMode();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.initialized = true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Start a new session
|
|
116
|
+
*/
|
|
117
|
+
async startSession(sessionId: string, projectPath?: string): Promise<void> {
|
|
118
|
+
await this.initialize();
|
|
119
|
+
|
|
120
|
+
await this.eventStore.upsertSession({
|
|
121
|
+
id: sessionId,
|
|
122
|
+
startedAt: new Date(),
|
|
123
|
+
projectPath
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* End a session
|
|
129
|
+
*/
|
|
130
|
+
async endSession(sessionId: string, summary?: string): Promise<void> {
|
|
131
|
+
await this.initialize();
|
|
132
|
+
|
|
133
|
+
await this.eventStore.upsertSession({
|
|
134
|
+
id: sessionId,
|
|
135
|
+
endedAt: new Date(),
|
|
136
|
+
summary
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Store a user prompt
|
|
142
|
+
*/
|
|
143
|
+
async storeUserPrompt(
|
|
144
|
+
sessionId: string,
|
|
145
|
+
content: string,
|
|
146
|
+
metadata?: Record<string, unknown>
|
|
147
|
+
): Promise<AppendResult> {
|
|
148
|
+
await this.initialize();
|
|
149
|
+
|
|
150
|
+
const result = await this.eventStore.append({
|
|
151
|
+
eventType: 'user_prompt',
|
|
152
|
+
sessionId,
|
|
153
|
+
timestamp: new Date(),
|
|
154
|
+
content,
|
|
155
|
+
metadata
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Enqueue for embedding if new
|
|
159
|
+
if (result.success && !result.isDuplicate) {
|
|
160
|
+
await this.eventStore.enqueueForEmbedding(result.eventId, content);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Store an agent response
|
|
168
|
+
*/
|
|
169
|
+
async storeAgentResponse(
|
|
170
|
+
sessionId: string,
|
|
171
|
+
content: string,
|
|
172
|
+
metadata?: Record<string, unknown>
|
|
173
|
+
): Promise<AppendResult> {
|
|
174
|
+
await this.initialize();
|
|
175
|
+
|
|
176
|
+
const result = await this.eventStore.append({
|
|
177
|
+
eventType: 'agent_response',
|
|
178
|
+
sessionId,
|
|
179
|
+
timestamp: new Date(),
|
|
180
|
+
content,
|
|
181
|
+
metadata
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Enqueue for embedding if new
|
|
185
|
+
if (result.success && !result.isDuplicate) {
|
|
186
|
+
await this.eventStore.enqueueForEmbedding(result.eventId, content);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Store a session summary
|
|
194
|
+
*/
|
|
195
|
+
async storeSessionSummary(
|
|
196
|
+
sessionId: string,
|
|
197
|
+
summary: string
|
|
198
|
+
): Promise<AppendResult> {
|
|
199
|
+
await this.initialize();
|
|
200
|
+
|
|
201
|
+
const result = await this.eventStore.append({
|
|
202
|
+
eventType: 'session_summary',
|
|
203
|
+
sessionId,
|
|
204
|
+
timestamp: new Date(),
|
|
205
|
+
content: summary
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
if (result.success && !result.isDuplicate) {
|
|
209
|
+
await this.eventStore.enqueueForEmbedding(result.eventId, summary);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Store a tool observation
|
|
217
|
+
*/
|
|
218
|
+
async storeToolObservation(
|
|
219
|
+
sessionId: string,
|
|
220
|
+
payload: ToolObservationPayload
|
|
221
|
+
): Promise<AppendResult> {
|
|
222
|
+
await this.initialize();
|
|
223
|
+
|
|
224
|
+
// Create content for storage (JSON stringified payload)
|
|
225
|
+
const content = JSON.stringify(payload);
|
|
226
|
+
|
|
227
|
+
const result = await this.eventStore.append({
|
|
228
|
+
eventType: 'tool_observation',
|
|
229
|
+
sessionId,
|
|
230
|
+
timestamp: new Date(),
|
|
231
|
+
content,
|
|
232
|
+
metadata: {
|
|
233
|
+
toolName: payload.toolName,
|
|
234
|
+
success: payload.success
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Create embedding content (optimized for search)
|
|
239
|
+
if (result.success && !result.isDuplicate) {
|
|
240
|
+
const embeddingContent = createToolObservationEmbedding(
|
|
241
|
+
payload.toolName,
|
|
242
|
+
payload.metadata || {},
|
|
243
|
+
payload.success
|
|
244
|
+
);
|
|
245
|
+
await this.eventStore.enqueueForEmbedding(result.eventId, embeddingContent);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Retrieve relevant memories for a query
|
|
253
|
+
*/
|
|
254
|
+
async retrieveMemories(
|
|
255
|
+
query: string,
|
|
256
|
+
options?: {
|
|
257
|
+
topK?: number;
|
|
258
|
+
minScore?: number;
|
|
259
|
+
sessionId?: string;
|
|
260
|
+
}
|
|
261
|
+
): Promise<RetrievalResult> {
|
|
262
|
+
await this.initialize();
|
|
263
|
+
|
|
264
|
+
// Process any pending embeddings first
|
|
265
|
+
if (this.vectorWorker) {
|
|
266
|
+
await this.vectorWorker.processAll();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return this.retriever.retrieve(query, options);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Get session history
|
|
274
|
+
*/
|
|
275
|
+
async getSessionHistory(sessionId: string): Promise<MemoryEvent[]> {
|
|
276
|
+
await this.initialize();
|
|
277
|
+
return this.eventStore.getSessionEvents(sessionId);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Get recent events
|
|
282
|
+
*/
|
|
283
|
+
async getRecentEvents(limit: number = 100): Promise<MemoryEvent[]> {
|
|
284
|
+
await this.initialize();
|
|
285
|
+
return this.eventStore.getRecentEvents(limit);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get memory statistics
|
|
290
|
+
*/
|
|
291
|
+
async getStats(): Promise<{
|
|
292
|
+
totalEvents: number;
|
|
293
|
+
vectorCount: number;
|
|
294
|
+
levelStats: Array<{ level: string; count: number }>;
|
|
295
|
+
}> {
|
|
296
|
+
await this.initialize();
|
|
297
|
+
|
|
298
|
+
const recentEvents = await this.eventStore.getRecentEvents(10000);
|
|
299
|
+
const vectorCount = await this.vectorStore.count();
|
|
300
|
+
const levelStats = await this.graduation.getStats();
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
totalEvents: recentEvents.length,
|
|
304
|
+
vectorCount,
|
|
305
|
+
levelStats
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Process pending embeddings
|
|
311
|
+
*/
|
|
312
|
+
async processPendingEmbeddings(): Promise<number> {
|
|
313
|
+
if (this.vectorWorker) {
|
|
314
|
+
return this.vectorWorker.processAll();
|
|
315
|
+
}
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Format retrieval results as context for Claude
|
|
321
|
+
*/
|
|
322
|
+
formatAsContext(result: RetrievalResult): string {
|
|
323
|
+
if (!result.context) {
|
|
324
|
+
return '';
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const confidence = result.matchResult.confidence;
|
|
328
|
+
let header = '';
|
|
329
|
+
|
|
330
|
+
if (confidence === 'high') {
|
|
331
|
+
header = '🎯 **High-confidence memory match found:**\n\n';
|
|
332
|
+
} else if (confidence === 'suggested') {
|
|
333
|
+
header = '💡 **Suggested memories (may be relevant):**\n\n';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return header + result.context;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ============================================================
|
|
340
|
+
// Endless Mode Methods
|
|
341
|
+
// ============================================================
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Get the default endless mode config
|
|
345
|
+
*/
|
|
346
|
+
private getDefaultEndlessConfig(): EndlessModeConfig {
|
|
347
|
+
return {
|
|
348
|
+
enabled: true,
|
|
349
|
+
workingSet: {
|
|
350
|
+
maxEvents: 100,
|
|
351
|
+
timeWindowHours: 24,
|
|
352
|
+
minRelevanceScore: 0.5
|
|
353
|
+
},
|
|
354
|
+
consolidation: {
|
|
355
|
+
triggerIntervalMs: 3600000, // 1 hour
|
|
356
|
+
triggerEventCount: 100,
|
|
357
|
+
triggerIdleMs: 1800000, // 30 minutes
|
|
358
|
+
useLLMSummarization: false
|
|
359
|
+
},
|
|
360
|
+
continuity: {
|
|
361
|
+
minScoreForSeamless: 0.7,
|
|
362
|
+
topicDecayHours: 48
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Initialize Endless Mode components
|
|
369
|
+
*/
|
|
370
|
+
async initializeEndlessMode(): Promise<void> {
|
|
371
|
+
const config = await this.getEndlessConfig();
|
|
372
|
+
|
|
373
|
+
this.workingSetStore = createWorkingSetStore(this.eventStore, config);
|
|
374
|
+
this.consolidatedStore = createConsolidatedStore(this.eventStore);
|
|
375
|
+
this.consolidationWorker = createConsolidationWorker(
|
|
376
|
+
this.workingSetStore,
|
|
377
|
+
this.consolidatedStore,
|
|
378
|
+
config
|
|
379
|
+
);
|
|
380
|
+
this.continuityManager = createContinuityManager(this.eventStore, config);
|
|
381
|
+
|
|
382
|
+
// Start consolidation worker
|
|
383
|
+
this.consolidationWorker.start();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Get Endless Mode configuration
|
|
388
|
+
*/
|
|
389
|
+
async getEndlessConfig(): Promise<EndlessModeConfig> {
|
|
390
|
+
const savedConfig = await this.eventStore.getEndlessConfig('config') as EndlessModeConfig | null;
|
|
391
|
+
return savedConfig || this.getDefaultEndlessConfig();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Set Endless Mode configuration
|
|
396
|
+
*/
|
|
397
|
+
async setEndlessConfig(config: Partial<EndlessModeConfig>): Promise<void> {
|
|
398
|
+
const current = await this.getEndlessConfig();
|
|
399
|
+
const merged = { ...current, ...config };
|
|
400
|
+
await this.eventStore.setEndlessConfig('config', merged);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Set memory mode (session or endless)
|
|
405
|
+
*/
|
|
406
|
+
async setMode(mode: MemoryMode): Promise<void> {
|
|
407
|
+
await this.initialize();
|
|
408
|
+
|
|
409
|
+
if (mode === this.endlessMode) return;
|
|
410
|
+
|
|
411
|
+
this.endlessMode = mode;
|
|
412
|
+
await this.eventStore.setEndlessConfig('mode', mode);
|
|
413
|
+
|
|
414
|
+
if (mode === 'endless') {
|
|
415
|
+
await this.initializeEndlessMode();
|
|
416
|
+
} else {
|
|
417
|
+
// Stop endless mode components
|
|
418
|
+
if (this.consolidationWorker) {
|
|
419
|
+
this.consolidationWorker.stop();
|
|
420
|
+
this.consolidationWorker = null;
|
|
421
|
+
}
|
|
422
|
+
this.workingSetStore = null;
|
|
423
|
+
this.consolidatedStore = null;
|
|
424
|
+
this.continuityManager = null;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Get current memory mode
|
|
430
|
+
*/
|
|
431
|
+
getMode(): MemoryMode {
|
|
432
|
+
return this.endlessMode;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Check if endless mode is active
|
|
437
|
+
*/
|
|
438
|
+
isEndlessModeActive(): boolean {
|
|
439
|
+
return this.endlessMode === 'endless';
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Add event to Working Set (Endless Mode)
|
|
444
|
+
*/
|
|
445
|
+
async addToWorkingSet(eventId: string, relevanceScore?: number): Promise<void> {
|
|
446
|
+
if (!this.workingSetStore) return;
|
|
447
|
+
await this.workingSetStore.add(eventId, relevanceScore);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Get the current Working Set
|
|
452
|
+
*/
|
|
453
|
+
async getWorkingSet(): Promise<WorkingSet | null> {
|
|
454
|
+
if (!this.workingSetStore) return null;
|
|
455
|
+
return this.workingSetStore.get();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Search consolidated memories
|
|
460
|
+
*/
|
|
461
|
+
async searchConsolidated(
|
|
462
|
+
query: string,
|
|
463
|
+
options?: { topK?: number }
|
|
464
|
+
): Promise<ConsolidatedMemory[]> {
|
|
465
|
+
if (!this.consolidatedStore) return [];
|
|
466
|
+
return this.consolidatedStore.search(query, options);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Get all consolidated memories
|
|
471
|
+
*/
|
|
472
|
+
async getConsolidatedMemories(limit?: number): Promise<ConsolidatedMemory[]> {
|
|
473
|
+
if (!this.consolidatedStore) return [];
|
|
474
|
+
return this.consolidatedStore.getAll({ limit });
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Calculate continuity score for current context
|
|
479
|
+
*/
|
|
480
|
+
async calculateContinuity(
|
|
481
|
+
content: string,
|
|
482
|
+
metadata?: { files?: string[]; entities?: string[] }
|
|
483
|
+
): Promise<ContinuityScore | null> {
|
|
484
|
+
if (!this.continuityManager) return null;
|
|
485
|
+
|
|
486
|
+
const snapshot = this.continuityManager.createSnapshot(
|
|
487
|
+
crypto.randomUUID(),
|
|
488
|
+
content,
|
|
489
|
+
metadata
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
return this.continuityManager.calculateScore(snapshot);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Record activity (for consolidation idle trigger)
|
|
497
|
+
*/
|
|
498
|
+
recordActivity(): void {
|
|
499
|
+
if (this.consolidationWorker) {
|
|
500
|
+
this.consolidationWorker.recordActivity();
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Force a consolidation run
|
|
506
|
+
*/
|
|
507
|
+
async forceConsolidation(): Promise<number> {
|
|
508
|
+
if (!this.consolidationWorker) return 0;
|
|
509
|
+
return this.consolidationWorker.forceRun();
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Get Endless Mode status
|
|
514
|
+
*/
|
|
515
|
+
async getEndlessModeStatus(): Promise<EndlessModeStatus> {
|
|
516
|
+
await this.initialize();
|
|
517
|
+
|
|
518
|
+
let workingSetSize = 0;
|
|
519
|
+
let continuityScore = 0.5;
|
|
520
|
+
let consolidatedCount = 0;
|
|
521
|
+
let lastConsolidation: Date | null = null;
|
|
522
|
+
|
|
523
|
+
if (this.workingSetStore) {
|
|
524
|
+
workingSetSize = await this.workingSetStore.count();
|
|
525
|
+
const workingSet = await this.workingSetStore.get();
|
|
526
|
+
continuityScore = workingSet.continuityScore;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (this.consolidatedStore) {
|
|
530
|
+
consolidatedCount = await this.consolidatedStore.count();
|
|
531
|
+
lastConsolidation = await this.consolidatedStore.getLastConsolidationTime();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
mode: this.endlessMode,
|
|
536
|
+
workingSetSize,
|
|
537
|
+
continuityScore,
|
|
538
|
+
consolidatedCount,
|
|
539
|
+
lastConsolidation
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Format Endless Mode context for Claude
|
|
545
|
+
*/
|
|
546
|
+
async formatEndlessContext(query: string): Promise<string> {
|
|
547
|
+
if (!this.isEndlessModeActive()) {
|
|
548
|
+
return '';
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const workingSet = await this.getWorkingSet();
|
|
552
|
+
const consolidated = await this.searchConsolidated(query, { topK: 3 });
|
|
553
|
+
const continuity = await this.calculateContinuity(query);
|
|
554
|
+
|
|
555
|
+
const parts: string[] = [];
|
|
556
|
+
|
|
557
|
+
// Continuity status
|
|
558
|
+
if (continuity) {
|
|
559
|
+
const statusEmoji = continuity.transitionType === 'seamless' ? '🔗' :
|
|
560
|
+
continuity.transitionType === 'topic_shift' ? '↪️' : '🆕';
|
|
561
|
+
parts.push(`${statusEmoji} Context: ${continuity.transitionType} (score: ${continuity.score.toFixed(2)})`);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Working set summary
|
|
565
|
+
if (workingSet && workingSet.recentEvents.length > 0) {
|
|
566
|
+
parts.push('\n## Recent Context (Working Set)');
|
|
567
|
+
const recent = workingSet.recentEvents.slice(0, 5);
|
|
568
|
+
for (const event of recent) {
|
|
569
|
+
const preview = event.content.slice(0, 80) + (event.content.length > 80 ? '...' : '');
|
|
570
|
+
const time = event.timestamp.toLocaleTimeString();
|
|
571
|
+
parts.push(`- ${time} [${event.eventType}] ${preview}`);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Consolidated memories
|
|
576
|
+
if (consolidated.length > 0) {
|
|
577
|
+
parts.push('\n## Related Knowledge (Consolidated)');
|
|
578
|
+
for (const memory of consolidated) {
|
|
579
|
+
parts.push(`- ${memory.topics.slice(0, 3).join(', ')}: ${memory.summary.slice(0, 100)}...`);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return parts.join('\n');
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Shutdown service
|
|
588
|
+
*/
|
|
589
|
+
async shutdown(): Promise<void> {
|
|
590
|
+
// Stop endless mode components
|
|
591
|
+
if (this.consolidationWorker) {
|
|
592
|
+
this.consolidationWorker.stop();
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (this.vectorWorker) {
|
|
596
|
+
this.vectorWorker.stop();
|
|
597
|
+
}
|
|
598
|
+
await this.eventStore.close();
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Expand ~ to home directory
|
|
603
|
+
*/
|
|
604
|
+
private expandPath(p: string): string {
|
|
605
|
+
if (p.startsWith('~')) {
|
|
606
|
+
return path.join(os.homedir(), p.slice(1));
|
|
607
|
+
}
|
|
608
|
+
return p;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Default instance
|
|
613
|
+
let defaultService: MemoryService | null = null;
|
|
614
|
+
|
|
615
|
+
export function getDefaultMemoryService(): MemoryService {
|
|
616
|
+
if (!defaultService) {
|
|
617
|
+
defaultService = new MemoryService({
|
|
618
|
+
storagePath: '~/.claude-code/memory'
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
return defaultService;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
export function createMemoryService(config: MemoryServiceConfig): MemoryService {
|
|
625
|
+
return new MemoryService(config);
|
|
626
|
+
}
|