@rlabs-inc/memory 0.1.0 ā 0.2.1
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/dist/index.js +130 -18
- package/dist/index.mjs +130 -18
- package/dist/server/index.js +141 -18
- package/dist/server/index.mjs +141 -18
- package/hooks/curation.ts +74 -0
- package/hooks/session-start.ts +82 -0
- package/hooks/user-prompt.ts +81 -0
- package/package.json +14 -8
- package/src/cli/colors.ts +174 -0
- package/src/cli/commands/doctor.ts +143 -0
- package/src/cli/commands/install.ts +153 -0
- package/src/cli/commands/serve.ts +76 -0
- package/src/cli/commands/stats.ts +64 -0
- package/src/cli/index.ts +128 -0
- package/src/core/curator.ts +7 -48
- package/src/core/engine.test.ts +321 -0
- package/src/core/engine.ts +45 -8
- package/src/core/retrieval.ts +1 -1
- package/src/core/store.ts +109 -98
- package/src/server/index.ts +15 -40
- package/src/types/schema.ts +1 -1
- package/src/utils/logger.ts +158 -107
- package/bun.lock +0 -102
- package/test-retrieval.ts +0 -91
- package/tsconfig.json +0 -16
package/src/core/engine.ts
CHANGED
|
@@ -69,6 +69,17 @@ export interface ContextRequest {
|
|
|
69
69
|
projectPath?: string // Required for 'local' storage mode
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Session metadata for deduplication
|
|
74
|
+
* Tracks which memories have been injected in each session
|
|
75
|
+
*/
|
|
76
|
+
interface SessionMetadata {
|
|
77
|
+
message_count: number
|
|
78
|
+
started_at: number
|
|
79
|
+
project_id: string
|
|
80
|
+
injected_memories: Set<string> // Memory IDs already shown in this session
|
|
81
|
+
}
|
|
82
|
+
|
|
72
83
|
/**
|
|
73
84
|
* Memory Engine - The main orchestrator
|
|
74
85
|
*/
|
|
@@ -76,6 +87,7 @@ export class MemoryEngine {
|
|
|
76
87
|
private _config: Required<Omit<EngineConfig, 'embedder'>> & { embedder?: EngineConfig['embedder'] }
|
|
77
88
|
private _stores = new Map<string, MemoryStore>() // projectPath -> store
|
|
78
89
|
private _retrieval: SmartVectorRetrieval
|
|
90
|
+
private _sessionMetadata = new Map<string, SessionMetadata>() // sessionId -> metadata
|
|
79
91
|
|
|
80
92
|
constructor(config: EngineConfig = {}) {
|
|
81
93
|
this._config = {
|
|
@@ -89,6 +101,21 @@ export class MemoryEngine {
|
|
|
89
101
|
this._retrieval = createRetrieval()
|
|
90
102
|
}
|
|
91
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Get or create session metadata for deduplication
|
|
106
|
+
*/
|
|
107
|
+
private _getSessionMetadata(sessionId: string, projectId: string): SessionMetadata {
|
|
108
|
+
if (!this._sessionMetadata.has(sessionId)) {
|
|
109
|
+
this._sessionMetadata.set(sessionId, {
|
|
110
|
+
message_count: 0,
|
|
111
|
+
started_at: Date.now(),
|
|
112
|
+
project_id: projectId,
|
|
113
|
+
injected_memories: new Set(),
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
return this._sessionMetadata.get(sessionId)!
|
|
117
|
+
}
|
|
118
|
+
|
|
92
119
|
/**
|
|
93
120
|
* Get the appropriate store for a project
|
|
94
121
|
*/
|
|
@@ -97,13 +124,6 @@ export class MemoryEngine {
|
|
|
97
124
|
? projectPath
|
|
98
125
|
: projectId
|
|
99
126
|
|
|
100
|
-
console.log(`šŖ [DEBUG] _getStore called:`)
|
|
101
|
-
console.log(` projectId: ${projectId}`)
|
|
102
|
-
console.log(` projectPath: ${projectPath}`)
|
|
103
|
-
console.log(` storageMode: ${this._config.storageMode}`)
|
|
104
|
-
console.log(` cache key: ${key}`)
|
|
105
|
-
console.log(` cached: ${this._stores.has(key)}`)
|
|
106
|
-
|
|
107
127
|
if (this._stores.has(key)) {
|
|
108
128
|
return this._stores.get(key)!
|
|
109
129
|
}
|
|
@@ -166,6 +186,10 @@ export class MemoryEngine {
|
|
|
166
186
|
return { memories: [], formatted: '' }
|
|
167
187
|
}
|
|
168
188
|
|
|
189
|
+
// Get session metadata for deduplication
|
|
190
|
+
const sessionMeta = this._getSessionMetadata(sessionId, projectId)
|
|
191
|
+
const injectedIds = sessionMeta.injected_memories
|
|
192
|
+
|
|
169
193
|
// Get all memories for this project
|
|
170
194
|
const allMemories = await store.getAllMemories(projectId)
|
|
171
195
|
|
|
@@ -173,6 +197,13 @@ export class MemoryEngine {
|
|
|
173
197
|
return { memories: [], formatted: '' }
|
|
174
198
|
}
|
|
175
199
|
|
|
200
|
+
// Filter out already-injected memories (deduplication)
|
|
201
|
+
const candidateMemories = allMemories.filter(m => !injectedIds.has(m.id))
|
|
202
|
+
|
|
203
|
+
if (!candidateMemories.length) {
|
|
204
|
+
return { memories: [], formatted: '' }
|
|
205
|
+
}
|
|
206
|
+
|
|
176
207
|
// Generate embedding for query if embedder is available
|
|
177
208
|
let queryEmbedding: Float32Array | undefined
|
|
178
209
|
if (this._config.embedder) {
|
|
@@ -187,14 +218,20 @@ export class MemoryEngine {
|
|
|
187
218
|
}
|
|
188
219
|
|
|
189
220
|
// Retrieve relevant memories using 10-dimensional scoring
|
|
221
|
+
// Use candidateMemories (already filtered for deduplication)
|
|
190
222
|
const relevantMemories = this._retrieval.retrieveRelevantMemories(
|
|
191
|
-
|
|
223
|
+
candidateMemories,
|
|
192
224
|
currentMessage,
|
|
193
225
|
queryEmbedding ?? new Float32Array(384), // Empty embedding if no embedder
|
|
194
226
|
sessionContext,
|
|
195
227
|
maxMemories
|
|
196
228
|
)
|
|
197
229
|
|
|
230
|
+
// Update injected memories for deduplication
|
|
231
|
+
for (const memory of relevantMemories) {
|
|
232
|
+
injectedIds.add(memory.id)
|
|
233
|
+
}
|
|
234
|
+
|
|
198
235
|
return {
|
|
199
236
|
memories: relevantMemories,
|
|
200
237
|
formatted: this._formatMemories(relevantMemories),
|
package/src/core/retrieval.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// ============================================================================
|
|
6
6
|
|
|
7
7
|
import type { StoredMemory, RetrievalResult } from '../types/memory.ts'
|
|
8
|
-
import { cosineSimilarity } from '
|
|
8
|
+
import { cosineSimilarity } from '@rlabs-inc/fsdb'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Session context for retrieval
|
package/src/core/store.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
|
-
// MEMORY STORE -
|
|
2
|
+
// MEMORY STORE - fsDB-powered storage
|
|
3
3
|
// Per-project database management with reactive parallel arrays
|
|
4
4
|
// ============================================================================
|
|
5
5
|
|
|
6
|
-
import { createDatabase, type Database } from '
|
|
6
|
+
import { createDatabase, type Database, type PersistentCollection } from '@rlabs-inc/fsdb'
|
|
7
7
|
import { homedir } from 'os'
|
|
8
8
|
import { join } from 'path'
|
|
9
9
|
import type {
|
|
@@ -42,21 +42,22 @@ export interface StoreConfig {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Project database
|
|
45
|
+
* Project database with collections
|
|
46
46
|
*/
|
|
47
|
-
interface
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
interface ProjectDB {
|
|
48
|
+
db: Database
|
|
49
|
+
memories: PersistentCollection<typeof memorySchema>
|
|
50
|
+
summaries: PersistentCollection<typeof sessionSummarySchema>
|
|
51
|
+
snapshots: PersistentCollection<typeof projectSnapshotSchema>
|
|
52
|
+
sessions: PersistentCollection<typeof sessionSchema>
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
|
-
* MemoryStore - Manages per-project
|
|
56
|
+
* MemoryStore - Manages per-project fsDB instances
|
|
56
57
|
*/
|
|
57
58
|
export class MemoryStore {
|
|
58
59
|
private _config: Required<StoreConfig>
|
|
59
|
-
private _projects = new Map<string,
|
|
60
|
+
private _projects = new Map<string, ProjectDB>()
|
|
60
61
|
|
|
61
62
|
constructor(config: StoreConfig = {}) {
|
|
62
63
|
this._config = {
|
|
@@ -66,9 +67,9 @@ export class MemoryStore {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
/**
|
|
69
|
-
* Get or create
|
|
70
|
+
* Get or create database for a project
|
|
70
71
|
*/
|
|
71
|
-
async getProject(projectId: string): Promise<
|
|
72
|
+
async getProject(projectId: string): Promise<ProjectDB> {
|
|
72
73
|
if (this._projects.has(projectId)) {
|
|
73
74
|
console.log(`š [DEBUG] Returning cached databases for ${projectId}`)
|
|
74
75
|
return this._projects.get(projectId)!
|
|
@@ -78,40 +79,51 @@ export class MemoryStore {
|
|
|
78
79
|
const projectPath = join(this._config.basePath, projectId)
|
|
79
80
|
console.log(` Path: ${projectPath}`)
|
|
80
81
|
|
|
81
|
-
// Create
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
82
|
+
// Create the database for this project
|
|
83
|
+
const db = createDatabase({
|
|
84
|
+
name: projectId,
|
|
85
|
+
basePath: projectPath,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Create all collections for this project
|
|
89
|
+
const memories = db.collection('memories', {
|
|
90
|
+
schema: memorySchema,
|
|
91
|
+
contentColumn: 'content',
|
|
92
|
+
autoSave: true,
|
|
93
|
+
watchFiles: this._config.watchFiles,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const summaries = db.collection('summaries', {
|
|
97
|
+
schema: sessionSummarySchema,
|
|
98
|
+
contentColumn: 'summary',
|
|
99
|
+
autoSave: true,
|
|
100
|
+
watchFiles: this._config.watchFiles,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const snapshots = db.collection('snapshots', {
|
|
104
|
+
schema: projectSnapshotSchema,
|
|
105
|
+
autoSave: true,
|
|
106
|
+
watchFiles: this._config.watchFiles,
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const sessions = db.collection('sessions', {
|
|
110
|
+
schema: sessionSchema,
|
|
111
|
+
autoSave: true,
|
|
112
|
+
watchFiles: this._config.watchFiles,
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// Load existing data
|
|
116
|
+
await Promise.all([
|
|
117
|
+
memories.load(),
|
|
118
|
+
summaries.load(),
|
|
119
|
+
snapshots.load(),
|
|
120
|
+
sessions.load(),
|
|
109
121
|
])
|
|
110
122
|
|
|
111
|
-
const
|
|
112
|
-
this._projects.set(projectId,
|
|
123
|
+
const projectDB: ProjectDB = { db, memories, summaries, snapshots, sessions }
|
|
124
|
+
this._projects.set(projectId, projectDB)
|
|
113
125
|
|
|
114
|
-
return
|
|
126
|
+
return projectDB
|
|
115
127
|
}
|
|
116
128
|
|
|
117
129
|
// ================================================================
|
|
@@ -129,7 +141,7 @@ export class MemoryStore {
|
|
|
129
141
|
): Promise<string> {
|
|
130
142
|
const { memories } = await this.getProject(projectId)
|
|
131
143
|
|
|
132
|
-
const id =
|
|
144
|
+
const id = memories.insert({
|
|
133
145
|
content: memory.content,
|
|
134
146
|
reasoning: memory.reasoning,
|
|
135
147
|
importance_weight: memory.importance_weight,
|
|
@@ -147,7 +159,7 @@ export class MemoryStore {
|
|
|
147
159
|
project_id: projectId,
|
|
148
160
|
embedding: embedding
|
|
149
161
|
? (embedding instanceof Float32Array ? embedding : new Float32Array(embedding))
|
|
150
|
-
:
|
|
162
|
+
: null,
|
|
151
163
|
})
|
|
152
164
|
|
|
153
165
|
return id
|
|
@@ -165,10 +177,10 @@ export class MemoryStore {
|
|
|
165
177
|
reasoning: record.reasoning,
|
|
166
178
|
importance_weight: record.importance_weight,
|
|
167
179
|
confidence_score: record.confidence_score,
|
|
168
|
-
context_type: record.context_type,
|
|
169
|
-
temporal_relevance: record.temporal_relevance,
|
|
170
|
-
knowledge_domain: record.knowledge_domain,
|
|
171
|
-
emotional_resonance: record.emotional_resonance,
|
|
180
|
+
context_type: record.context_type as StoredMemory['context_type'],
|
|
181
|
+
temporal_relevance: record.temporal_relevance as StoredMemory['temporal_relevance'],
|
|
182
|
+
knowledge_domain: record.knowledge_domain as StoredMemory['knowledge_domain'],
|
|
183
|
+
emotional_resonance: record.emotional_resonance as StoredMemory['emotional_resonance'],
|
|
172
184
|
action_required: record.action_required,
|
|
173
185
|
problem_solution_pair: record.problem_solution_pair,
|
|
174
186
|
semantic_tags: record.semantic_tags,
|
|
@@ -176,7 +188,7 @@ export class MemoryStore {
|
|
|
176
188
|
question_types: record.question_types,
|
|
177
189
|
session_id: record.session_id,
|
|
178
190
|
project_id: record.project_id,
|
|
179
|
-
embedding: record.embedding,
|
|
191
|
+
embedding: record.embedding ?? undefined,
|
|
180
192
|
created_at: record.created,
|
|
181
193
|
updated_at: record.updated,
|
|
182
194
|
stale: record.stale,
|
|
@@ -196,18 +208,19 @@ export class MemoryStore {
|
|
|
196
208
|
|
|
197
209
|
const results = memories.search('embedding', queryEmbedding, {
|
|
198
210
|
topK,
|
|
199
|
-
filter: filter ? (record) => {
|
|
200
|
-
//
|
|
211
|
+
filter: filter ? (record, _idx) => {
|
|
212
|
+
// Filter receives raw schema record - we need to adapt it
|
|
213
|
+
// Note: filter doesn't have access to id/created/updated (those are in RecordWithMeta)
|
|
201
214
|
const mem: StoredMemory = {
|
|
202
|
-
id:
|
|
215
|
+
id: '', // Not available in filter
|
|
203
216
|
content: record.content,
|
|
204
217
|
reasoning: record.reasoning,
|
|
205
218
|
importance_weight: record.importance_weight,
|
|
206
219
|
confidence_score: record.confidence_score,
|
|
207
|
-
context_type: record.context_type,
|
|
208
|
-
temporal_relevance: record.temporal_relevance,
|
|
209
|
-
knowledge_domain: record.knowledge_domain,
|
|
210
|
-
emotional_resonance: record.emotional_resonance,
|
|
220
|
+
context_type: record.context_type as StoredMemory['context_type'],
|
|
221
|
+
temporal_relevance: record.temporal_relevance as StoredMemory['temporal_relevance'],
|
|
222
|
+
knowledge_domain: record.knowledge_domain as StoredMemory['knowledge_domain'],
|
|
223
|
+
emotional_resonance: record.emotional_resonance as StoredMemory['emotional_resonance'],
|
|
211
224
|
action_required: record.action_required,
|
|
212
225
|
problem_solution_pair: record.problem_solution_pair,
|
|
213
226
|
semantic_tags: record.semantic_tags,
|
|
@@ -222,27 +235,27 @@ export class MemoryStore {
|
|
|
222
235
|
} : undefined,
|
|
223
236
|
})
|
|
224
237
|
|
|
225
|
-
return results.map(
|
|
226
|
-
id: record.id,
|
|
227
|
-
content: record.content,
|
|
228
|
-
reasoning: record.reasoning,
|
|
229
|
-
importance_weight: record.importance_weight,
|
|
230
|
-
confidence_score: record.confidence_score,
|
|
231
|
-
context_type: record.context_type,
|
|
232
|
-
temporal_relevance: record.temporal_relevance,
|
|
233
|
-
knowledge_domain: record.knowledge_domain,
|
|
234
|
-
emotional_resonance: record.emotional_resonance,
|
|
235
|
-
action_required: record.action_required,
|
|
236
|
-
problem_solution_pair: record.problem_solution_pair,
|
|
237
|
-
semantic_tags: record.semantic_tags,
|
|
238
|
-
trigger_phrases: record.trigger_phrases,
|
|
239
|
-
question_types: record.question_types,
|
|
240
|
-
session_id: record.session_id,
|
|
241
|
-
project_id: record.project_id,
|
|
242
|
-
embedding: record.embedding,
|
|
243
|
-
created_at: record.created,
|
|
244
|
-
updated_at: record.updated,
|
|
245
|
-
stale:
|
|
238
|
+
return results.map(result => ({
|
|
239
|
+
id: result.record.id,
|
|
240
|
+
content: result.record.content,
|
|
241
|
+
reasoning: result.record.reasoning,
|
|
242
|
+
importance_weight: result.record.importance_weight,
|
|
243
|
+
confidence_score: result.record.confidence_score,
|
|
244
|
+
context_type: result.record.context_type as StoredMemory['context_type'],
|
|
245
|
+
temporal_relevance: result.record.temporal_relevance as StoredMemory['temporal_relevance'],
|
|
246
|
+
knowledge_domain: result.record.knowledge_domain as StoredMemory['knowledge_domain'],
|
|
247
|
+
emotional_resonance: result.record.emotional_resonance as StoredMemory['emotional_resonance'],
|
|
248
|
+
action_required: result.record.action_required,
|
|
249
|
+
problem_solution_pair: result.record.problem_solution_pair,
|
|
250
|
+
semantic_tags: result.record.semantic_tags,
|
|
251
|
+
trigger_phrases: result.record.trigger_phrases,
|
|
252
|
+
question_types: result.record.question_types,
|
|
253
|
+
session_id: result.record.session_id,
|
|
254
|
+
project_id: result.record.project_id,
|
|
255
|
+
embedding: result.record.embedding ?? undefined,
|
|
256
|
+
created_at: result.record.created,
|
|
257
|
+
updated_at: result.record.updated,
|
|
258
|
+
stale: result.stale,
|
|
246
259
|
}))
|
|
247
260
|
}
|
|
248
261
|
|
|
@@ -254,9 +267,10 @@ export class MemoryStore {
|
|
|
254
267
|
memoryId: string,
|
|
255
268
|
embedding: Float32Array | number[],
|
|
256
269
|
content: string
|
|
257
|
-
): Promise<
|
|
270
|
+
): Promise<void> {
|
|
258
271
|
const { memories } = await this.getProject(projectId)
|
|
259
|
-
|
|
272
|
+
const vec = embedding instanceof Float32Array ? embedding : new Float32Array(embedding)
|
|
273
|
+
memories.setEmbedding(memoryId, 'embedding', vec, content)
|
|
260
274
|
}
|
|
261
275
|
|
|
262
276
|
/**
|
|
@@ -264,7 +278,7 @@ export class MemoryStore {
|
|
|
264
278
|
*/
|
|
265
279
|
async getStaleMemoryIds(projectId: string): Promise<string[]> {
|
|
266
280
|
const { memories } = await this.getProject(projectId)
|
|
267
|
-
return memories.
|
|
281
|
+
return memories.all().filter(r => r.stale).map(r => r.id)
|
|
268
282
|
}
|
|
269
283
|
|
|
270
284
|
// ================================================================
|
|
@@ -293,7 +307,7 @@ export class MemoryStore {
|
|
|
293
307
|
const allSessions = sessions.all()
|
|
294
308
|
const firstSessionCompleted = allSessions.some(s => s.first_session_completed)
|
|
295
309
|
|
|
296
|
-
|
|
310
|
+
sessions.insert({
|
|
297
311
|
id: sessionId,
|
|
298
312
|
project_id: projectId,
|
|
299
313
|
message_count: 0,
|
|
@@ -321,7 +335,7 @@ export class MemoryStore {
|
|
|
321
335
|
}
|
|
322
336
|
|
|
323
337
|
const newCount = session.message_count + 1
|
|
324
|
-
|
|
338
|
+
sessions.update(sessionId, {
|
|
325
339
|
message_count: newCount,
|
|
326
340
|
last_active: Date.now(),
|
|
327
341
|
})
|
|
@@ -334,7 +348,7 @@ export class MemoryStore {
|
|
|
334
348
|
*/
|
|
335
349
|
async markFirstSessionCompleted(projectId: string, sessionId: string): Promise<void> {
|
|
336
350
|
const { sessions } = await this.getProject(projectId)
|
|
337
|
-
|
|
351
|
+
sessions.update(sessionId, { first_session_completed: true })
|
|
338
352
|
}
|
|
339
353
|
|
|
340
354
|
// ================================================================
|
|
@@ -356,7 +370,7 @@ export class MemoryStore {
|
|
|
356
370
|
console.log(` Summary length: ${summary.length} chars`)
|
|
357
371
|
console.log(` Summaries count before: ${summaries.all().length}`)
|
|
358
372
|
|
|
359
|
-
const id =
|
|
373
|
+
const id = summaries.insert({
|
|
360
374
|
session_id: sessionId,
|
|
361
375
|
project_id: projectId,
|
|
362
376
|
summary,
|
|
@@ -385,9 +399,9 @@ export class MemoryStore {
|
|
|
385
399
|
}
|
|
386
400
|
|
|
387
401
|
// Sort by created timestamp (most recent first)
|
|
388
|
-
all.sort((a, b) => b.created - a.created)
|
|
402
|
+
const sorted = [...all].sort((a, b) => b.created - a.created)
|
|
389
403
|
|
|
390
|
-
const latest =
|
|
404
|
+
const latest = sorted[0]!
|
|
391
405
|
console.log(` Latest summary: ${latest.summary.slice(0, 50)}...`)
|
|
392
406
|
|
|
393
407
|
return {
|
|
@@ -434,9 +448,9 @@ export class MemoryStore {
|
|
|
434
448
|
if (!all.length) return null
|
|
435
449
|
|
|
436
450
|
// Sort by created timestamp (most recent first)
|
|
437
|
-
all.sort((a, b) => b.created - a.created)
|
|
451
|
+
const sorted = [...all].sort((a, b) => b.created - a.created)
|
|
438
452
|
|
|
439
|
-
const latest =
|
|
453
|
+
const latest = sorted[0]!
|
|
440
454
|
return {
|
|
441
455
|
id: latest.id,
|
|
442
456
|
session_id: latest.session_id,
|
|
@@ -466,19 +480,19 @@ export class MemoryStore {
|
|
|
466
480
|
|
|
467
481
|
const allMemories = memories.all()
|
|
468
482
|
const allSessions = sessions.all()
|
|
469
|
-
const
|
|
483
|
+
const staleCount = allMemories.filter(r => r.stale).length
|
|
470
484
|
|
|
471
485
|
// Find latest session
|
|
472
486
|
let latestSession: string | null = null
|
|
473
487
|
if (allSessions.length) {
|
|
474
|
-
allSessions.sort((a, b) => b.last_active - a.last_active)
|
|
475
|
-
latestSession =
|
|
488
|
+
const sorted = [...allSessions].sort((a, b) => b.last_active - a.last_active)
|
|
489
|
+
latestSession = sorted[0]!.id
|
|
476
490
|
}
|
|
477
491
|
|
|
478
492
|
return {
|
|
479
493
|
totalMemories: allMemories.length,
|
|
480
494
|
totalSessions: allSessions.length,
|
|
481
|
-
staleMemories:
|
|
495
|
+
staleMemories: staleCount,
|
|
482
496
|
latestSession,
|
|
483
497
|
}
|
|
484
498
|
}
|
|
@@ -487,11 +501,8 @@ export class MemoryStore {
|
|
|
487
501
|
* Close all project databases
|
|
488
502
|
*/
|
|
489
503
|
close(): void {
|
|
490
|
-
for (const
|
|
491
|
-
|
|
492
|
-
dbs.summaries.close()
|
|
493
|
-
dbs.snapshots.close()
|
|
494
|
-
dbs.sessions.close()
|
|
504
|
+
for (const projectDB of this._projects.values()) {
|
|
505
|
+
projectDB.db.close()
|
|
495
506
|
}
|
|
496
507
|
this._projects.clear()
|
|
497
508
|
}
|
package/src/server/index.ts
CHANGED
|
@@ -12,21 +12,8 @@ import { logger } from '../utils/logger.ts'
|
|
|
12
12
|
* Server configuration
|
|
13
13
|
*/
|
|
14
14
|
export interface ServerConfig extends EngineConfig {
|
|
15
|
-
/**
|
|
16
|
-
* Port to listen on
|
|
17
|
-
* Default: 8765 (same as Python)
|
|
18
|
-
*/
|
|
19
15
|
port?: number
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Host to bind to
|
|
23
|
-
* Default: 'localhost'
|
|
24
|
-
*/
|
|
25
16
|
host?: string
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Curator configuration
|
|
29
|
-
*/
|
|
30
17
|
curator?: CuratorConfig
|
|
31
18
|
}
|
|
32
19
|
|
|
@@ -61,7 +48,6 @@ interface CheckpointRequest {
|
|
|
61
48
|
|
|
62
49
|
/**
|
|
63
50
|
* Create and start the memory server
|
|
64
|
-
* Uses Bun.serve() for high performance
|
|
65
51
|
*/
|
|
66
52
|
export function createServer(config: ServerConfig = {}) {
|
|
67
53
|
const {
|
|
@@ -102,7 +88,9 @@ export function createServer(config: ServerConfig = {}) {
|
|
|
102
88
|
|
|
103
89
|
// Get memory context for a message
|
|
104
90
|
if (path === '/memory/context' && req.method === 'POST') {
|
|
105
|
-
const body
|
|
91
|
+
const body = await req.json() as ContextRequest
|
|
92
|
+
|
|
93
|
+
logger.request('POST', '/memory/context', body.project_id)
|
|
106
94
|
|
|
107
95
|
const result = await engine.getContext({
|
|
108
96
|
sessionId: body.session_id,
|
|
@@ -126,20 +114,13 @@ export function createServer(config: ServerConfig = {}) {
|
|
|
126
114
|
)
|
|
127
115
|
}
|
|
128
116
|
|
|
129
|
-
// Debug: show what's actually being returned
|
|
130
|
-
console.log(`\nš¤ [DEBUG] Response to hook:`)
|
|
131
|
-
console.log(` memories_count: ${result.memories.length}`)
|
|
132
|
-
console.log(` has_primer: ${!!result.primer}`)
|
|
133
|
-
console.log(` formatted length: ${result.formatted.length} chars`)
|
|
134
|
-
if (result.formatted) {
|
|
135
|
-
console.log(` formatted preview:\n${result.formatted.slice(0, 300)}...`)
|
|
136
|
-
} else {
|
|
137
|
-
console.log(` ā ļø formatted is EMPTY!`)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
117
|
return Response.json({
|
|
141
118
|
success: true,
|
|
142
|
-
|
|
119
|
+
session_id: body.session_id,
|
|
120
|
+
message_count: 0,
|
|
121
|
+
context_text: result.formatted,
|
|
122
|
+
has_memories: result.memories.length > 0,
|
|
123
|
+
curator_enabled: true,
|
|
143
124
|
memories_count: result.memories.length,
|
|
144
125
|
has_primer: !!result.primer,
|
|
145
126
|
}, { headers: corsHeaders })
|
|
@@ -147,7 +128,7 @@ export function createServer(config: ServerConfig = {}) {
|
|
|
147
128
|
|
|
148
129
|
// Process/track a message exchange
|
|
149
130
|
if (path === '/memory/process' && req.method === 'POST') {
|
|
150
|
-
const body
|
|
131
|
+
const body = await req.json() as ProcessRequest
|
|
151
132
|
|
|
152
133
|
const messageCount = await engine.trackMessage(
|
|
153
134
|
body.project_id,
|
|
@@ -155,7 +136,7 @@ export function createServer(config: ServerConfig = {}) {
|
|
|
155
136
|
body.project_path
|
|
156
137
|
)
|
|
157
138
|
|
|
158
|
-
logger.session(`Message #${messageCount} tracked
|
|
139
|
+
logger.session(`Message #${messageCount} tracked [${body.project_id}]`)
|
|
159
140
|
|
|
160
141
|
return Response.json({
|
|
161
142
|
success: true,
|
|
@@ -165,15 +146,13 @@ export function createServer(config: ServerConfig = {}) {
|
|
|
165
146
|
|
|
166
147
|
// Checkpoint - trigger curation
|
|
167
148
|
if (path === '/memory/checkpoint' && req.method === 'POST') {
|
|
168
|
-
const body
|
|
149
|
+
const body = await req.json() as CheckpointRequest
|
|
169
150
|
|
|
170
|
-
logger.
|
|
151
|
+
logger.logCurationStart(body.claude_session_id, body.trigger)
|
|
171
152
|
|
|
172
153
|
// Fire and forget - don't block the response
|
|
173
154
|
setImmediate(async () => {
|
|
174
155
|
try {
|
|
175
|
-
logger.info(`Resuming session ${body.claude_session_id.slice(0, 8)}... for curation`)
|
|
176
|
-
|
|
177
156
|
const result = await curator.curateWithCLI(
|
|
178
157
|
body.claude_session_id,
|
|
179
158
|
body.trigger,
|
|
@@ -188,14 +167,10 @@ export function createServer(config: ServerConfig = {}) {
|
|
|
188
167
|
body.project_path
|
|
189
168
|
)
|
|
190
169
|
|
|
170
|
+
logger.logCurationComplete(result.memories.length, result.session_summary)
|
|
191
171
|
logger.logCuratedMemories(result.memories)
|
|
192
|
-
logger.success(`Stored ${result.memories.length} memories for ${body.project_id}`)
|
|
193
|
-
|
|
194
|
-
if (result.session_summary) {
|
|
195
|
-
logger.info(`Summary: ${result.session_summary.slice(0, 100)}...`)
|
|
196
|
-
}
|
|
197
172
|
} else {
|
|
198
|
-
logger.
|
|
173
|
+
logger.logCurationComplete(0)
|
|
199
174
|
}
|
|
200
175
|
} catch (error) {
|
|
201
176
|
logger.error(`Curation failed: ${error}`)
|
|
@@ -227,7 +202,7 @@ export function createServer(config: ServerConfig = {}) {
|
|
|
227
202
|
{ status: 404, headers: corsHeaders }
|
|
228
203
|
)
|
|
229
204
|
} catch (error) {
|
|
230
|
-
|
|
205
|
+
logger.error(`Server error: ${error}`)
|
|
231
206
|
return Response.json(
|
|
232
207
|
{ error: String(error) },
|
|
233
208
|
{ status: 500, headers: corsHeaders }
|
package/src/types/schema.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Maps CuratedMemory to reactive parallel arrays
|
|
4
4
|
// ============================================================================
|
|
5
5
|
|
|
6
|
-
import type { SchemaDefinition } from '
|
|
6
|
+
import type { SchemaDefinition } from '@rlabs-inc/fsdb'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Memory storage schema
|