claude-memory-layer 1.0.0 → 1.0.2
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/settings.local.json +15 -0
- package/.history/package_20260201114632.json +46 -0
- package/dist/cli/index.js +360 -154
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +337 -161
- package/dist/core/index.js.map +3 -3
- package/dist/hooks/session-end.js +320 -130
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +331 -138
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +320 -130
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +320 -130
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/services/memory-service.js +349 -128
- package/dist/services/memory-service.js.map +4 -4
- package/package.json +1 -1
- package/src/cli/index.ts +84 -23
- package/src/core/consolidated-store.ts +33 -18
- package/src/core/continuity-manager.ts +12 -7
- package/src/core/db-wrapper.ts +112 -0
- package/src/core/edge-repo.ts +22 -13
- package/src/core/entity-repo.ts +23 -14
- package/src/core/event-store.ts +98 -72
- package/src/core/task/blocker-resolver.ts +17 -9
- package/src/core/task/task-matcher.ts +8 -6
- package/src/core/task/task-projector.ts +29 -16
- package/src/core/task/task-resolver.ts +17 -9
- package/src/core/vector-outbox.ts +29 -16
- package/src/core/vector-store.ts +23 -12
- package/src/core/vector-worker.ts +7 -4
- package/src/core/working-set-store.ts +31 -18
- package/src/hooks/session-end.ts +3 -2
- package/src/hooks/session-start.ts +12 -8
- package/src/hooks/stop.ts +3 -2
- package/src/hooks/user-prompt-submit.ts +3 -2
- package/src/services/memory-service.ts +158 -6
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* AXIOMMIND: Incremental processing with offset tracking
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { Database } from '
|
|
6
|
+
import { dbRun, dbAll, toDate, type Database } from '../db-wrapper.js';
|
|
7
7
|
import { randomUUID } from 'crypto';
|
|
8
8
|
import type { BlockerMode, BlockerRef } from '../types.js';
|
|
9
9
|
|
|
@@ -37,7 +37,8 @@ export class TaskProjector {
|
|
|
37
37
|
* Get current projection offset
|
|
38
38
|
*/
|
|
39
39
|
async getOffset(): Promise<ProjectionOffset> {
|
|
40
|
-
const rows = await
|
|
40
|
+
const rows = await dbAll<Record<string, unknown>>(
|
|
41
|
+
this.db,
|
|
41
42
|
`SELECT last_event_id, last_timestamp
|
|
42
43
|
FROM projection_offsets
|
|
43
44
|
WHERE projection_name = ?`,
|
|
@@ -60,7 +61,8 @@ export class TaskProjector {
|
|
|
60
61
|
* Update projection offset
|
|
61
62
|
*/
|
|
62
63
|
private async updateOffset(eventId: string, timestamp: Date): Promise<void> {
|
|
63
|
-
await
|
|
64
|
+
await dbRun(
|
|
65
|
+
this.db,
|
|
64
66
|
`INSERT INTO projection_offsets (projection_name, last_event_id, last_timestamp, updated_at)
|
|
65
67
|
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
|
66
68
|
ON CONFLICT (projection_name) DO UPDATE SET
|
|
@@ -97,13 +99,13 @@ export class TaskProjector {
|
|
|
97
99
|
query += ` ORDER BY timestamp ASC, id ASC LIMIT ?`;
|
|
98
100
|
params.push(limit);
|
|
99
101
|
|
|
100
|
-
const rows = await
|
|
102
|
+
const rows = await dbAll<Record<string, unknown>>(this.db, query, params);
|
|
101
103
|
|
|
102
104
|
return rows.map(row => ({
|
|
103
105
|
id: row.id as string,
|
|
104
106
|
eventType: row.event_type as string,
|
|
105
107
|
sessionId: row.session_id as string,
|
|
106
|
-
timestamp:
|
|
108
|
+
timestamp: toDate(row.timestamp),
|
|
107
109
|
content: typeof row.content === 'string'
|
|
108
110
|
? JSON.parse(row.content)
|
|
109
111
|
: row.content as Record<string, unknown>
|
|
@@ -184,14 +186,16 @@ export class TaskProjector {
|
|
|
184
186
|
|
|
185
187
|
// If status changed to 'done', remove all blocked_by edges
|
|
186
188
|
if (toStatus === 'done') {
|
|
187
|
-
await
|
|
189
|
+
await dbRun(
|
|
190
|
+
this.db,
|
|
188
191
|
`DELETE FROM edges
|
|
189
192
|
WHERE src_id = ? AND rel_type IN ('blocked_by', 'blocked_by_suggested')`,
|
|
190
193
|
[taskId]
|
|
191
194
|
);
|
|
192
195
|
|
|
193
196
|
// Clear blockers cache in entity
|
|
194
|
-
await
|
|
197
|
+
await dbRun(
|
|
198
|
+
this.db,
|
|
195
199
|
`UPDATE entities
|
|
196
200
|
SET current_json = json_remove(json_remove(current_json, '$.blockers'), '$.blockerSuggestions'),
|
|
197
201
|
updated_at = CURRENT_TIMESTAMP
|
|
@@ -213,7 +217,8 @@ export class TaskProjector {
|
|
|
213
217
|
|
|
214
218
|
if (mode === 'replace') {
|
|
215
219
|
// Delete existing blocked_by edges
|
|
216
|
-
await
|
|
220
|
+
await dbRun(
|
|
221
|
+
this.db,
|
|
217
222
|
`DELETE FROM edges WHERE src_id = ? AND rel_type = 'blocked_by'`,
|
|
218
223
|
[taskId]
|
|
219
224
|
);
|
|
@@ -225,7 +230,8 @@ export class TaskProjector {
|
|
|
225
230
|
|
|
226
231
|
// Update entity cache
|
|
227
232
|
const blockerIds = blockers.map(b => b.entityId);
|
|
228
|
-
await
|
|
233
|
+
await dbRun(
|
|
234
|
+
this.db,
|
|
229
235
|
`UPDATE entities
|
|
230
236
|
SET current_json = json_set(current_json, '$.blockers', ?),
|
|
231
237
|
updated_at = CURRENT_TIMESTAMP
|
|
@@ -236,7 +242,8 @@ export class TaskProjector {
|
|
|
236
242
|
} else {
|
|
237
243
|
// mode === 'suggest'
|
|
238
244
|
// Delete existing suggested edges
|
|
239
|
-
await
|
|
245
|
+
await dbRun(
|
|
246
|
+
this.db,
|
|
240
247
|
`DELETE FROM edges WHERE src_id = ? AND rel_type = 'blocked_by_suggested'`,
|
|
241
248
|
[taskId]
|
|
242
249
|
);
|
|
@@ -248,7 +255,8 @@ export class TaskProjector {
|
|
|
248
255
|
|
|
249
256
|
// Update entity cache (suggestions)
|
|
250
257
|
const suggestionIds = blockers.map(b => b.entityId);
|
|
251
|
-
await
|
|
258
|
+
await dbRun(
|
|
259
|
+
this.db,
|
|
252
260
|
`UPDATE entities
|
|
253
261
|
SET current_json = json_set(current_json, '$.blockerSuggestions', ?),
|
|
254
262
|
updated_at = CURRENT_TIMESTAMP
|
|
@@ -268,7 +276,8 @@ export class TaskProjector {
|
|
|
268
276
|
): Promise<void> {
|
|
269
277
|
const edgeId = randomUUID();
|
|
270
278
|
|
|
271
|
-
await
|
|
279
|
+
await dbRun(
|
|
280
|
+
this.db,
|
|
272
281
|
`INSERT INTO edges (edge_id, src_type, src_id, rel_type, dst_type, dst_id, meta_json, created_at)
|
|
273
282
|
VALUES (?, 'entity', ?, ?, 'entity', ?, ?, CURRENT_TIMESTAMP)
|
|
274
283
|
ON CONFLICT DO NOTHING`,
|
|
@@ -297,7 +306,8 @@ export class TaskProjector {
|
|
|
297
306
|
};
|
|
298
307
|
|
|
299
308
|
// Update condition entity
|
|
300
|
-
await
|
|
309
|
+
await dbRun(
|
|
310
|
+
this.db,
|
|
301
311
|
`UPDATE entities
|
|
302
312
|
SET current_json = json_set(json_set(current_json, '$.resolved', true), '$.resolvedTo', ?),
|
|
303
313
|
updated_at = CURRENT_TIMESTAMP
|
|
@@ -315,7 +325,8 @@ export class TaskProjector {
|
|
|
315
325
|
const jobId = randomUUID();
|
|
316
326
|
const embeddingVersion = 'v1'; // Should come from config
|
|
317
327
|
|
|
318
|
-
await
|
|
328
|
+
await dbRun(
|
|
329
|
+
this.db,
|
|
319
330
|
`INSERT INTO vector_outbox (job_id, item_kind, item_id, embedding_version, status, retry_count)
|
|
320
331
|
VALUES (?, ?, ?, ?, 'pending', 0)
|
|
321
332
|
ON CONFLICT (item_kind, item_id, embedding_version) DO NOTHING`,
|
|
@@ -329,12 +340,14 @@ export class TaskProjector {
|
|
|
329
340
|
*/
|
|
330
341
|
async rebuild(): Promise<number> {
|
|
331
342
|
// Clear task-related edges
|
|
332
|
-
await
|
|
343
|
+
await dbRun(
|
|
344
|
+
this.db,
|
|
333
345
|
`DELETE FROM edges WHERE rel_type IN ('blocked_by', 'blocked_by_suggested', 'resolves_to')`
|
|
334
346
|
);
|
|
335
347
|
|
|
336
348
|
// Reset offset
|
|
337
|
-
await
|
|
349
|
+
await dbRun(
|
|
350
|
+
this.db,
|
|
338
351
|
`DELETE FROM projection_offsets WHERE projection_name = ?`,
|
|
339
352
|
[PROJECTOR_NAME]
|
|
340
353
|
);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* AXIOMMIND: Task state via event fold, no direct updates
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { Database } from '
|
|
6
|
+
import { dbRun, dbAll, type Database } from '../db-wrapper.js';
|
|
7
7
|
import { randomUUID } from 'crypto';
|
|
8
8
|
import type {
|
|
9
9
|
Entity,
|
|
@@ -154,7 +154,8 @@ export class TaskResolver {
|
|
|
154
154
|
};
|
|
155
155
|
|
|
156
156
|
// Insert entity
|
|
157
|
-
await
|
|
157
|
+
await dbRun(
|
|
158
|
+
this.db,
|
|
158
159
|
`INSERT INTO entities (
|
|
159
160
|
entity_id, entity_type, canonical_key, title, stage, status,
|
|
160
161
|
current_json, title_norm, search_text, created_at, updated_at
|
|
@@ -175,7 +176,8 @@ export class TaskResolver {
|
|
|
175
176
|
);
|
|
176
177
|
|
|
177
178
|
// Create alias
|
|
178
|
-
await
|
|
179
|
+
await dbRun(
|
|
180
|
+
this.db,
|
|
179
181
|
`INSERT INTO entity_aliases (entity_type, canonical_key, entity_id, is_primary)
|
|
180
182
|
VALUES (?, ?, ?, TRUE)
|
|
181
183
|
ON CONFLICT (entity_type, canonical_key) DO NOTHING`,
|
|
@@ -245,7 +247,8 @@ export class TaskResolver {
|
|
|
245
247
|
});
|
|
246
248
|
|
|
247
249
|
// Update entity (projector will do this, but we update for immediate consistency)
|
|
248
|
-
await
|
|
250
|
+
await dbRun(
|
|
251
|
+
this.db,
|
|
249
252
|
`UPDATE entities
|
|
250
253
|
SET current_json = json_set(current_json, '$.status', ?),
|
|
251
254
|
updated_at = ?
|
|
@@ -278,7 +281,8 @@ export class TaskResolver {
|
|
|
278
281
|
});
|
|
279
282
|
|
|
280
283
|
// Update entity
|
|
281
|
-
await
|
|
284
|
+
await dbRun(
|
|
285
|
+
this.db,
|
|
282
286
|
`UPDATE entities
|
|
283
287
|
SET current_json = json_set(current_json, '$.priority', ?),
|
|
284
288
|
updated_at = ?
|
|
@@ -348,7 +352,8 @@ export class TaskResolver {
|
|
|
348
352
|
);
|
|
349
353
|
|
|
350
354
|
// Check for duplicate
|
|
351
|
-
const existing = await
|
|
355
|
+
const existing = await dbAll<{ event_id: string }>(
|
|
356
|
+
this.db,
|
|
352
357
|
`SELECT event_id FROM event_dedup WHERE dedupe_key = ?`,
|
|
353
358
|
[dedupeKey]
|
|
354
359
|
);
|
|
@@ -358,7 +363,8 @@ export class TaskResolver {
|
|
|
358
363
|
}
|
|
359
364
|
|
|
360
365
|
// Insert event
|
|
361
|
-
await
|
|
366
|
+
await dbRun(
|
|
367
|
+
this.db,
|
|
362
368
|
`INSERT INTO events (
|
|
363
369
|
id, event_type, session_id, timestamp, content,
|
|
364
370
|
canonical_key, dedupe_key, metadata
|
|
@@ -376,7 +382,8 @@ export class TaskResolver {
|
|
|
376
382
|
);
|
|
377
383
|
|
|
378
384
|
// Insert dedup record
|
|
379
|
-
await
|
|
385
|
+
await dbRun(
|
|
386
|
+
this.db,
|
|
380
387
|
`INSERT INTO event_dedup (dedupe_key, event_id)
|
|
381
388
|
VALUES (?, ?)
|
|
382
389
|
ON CONFLICT DO NOTHING`,
|
|
@@ -402,7 +409,8 @@ export class TaskResolver {
|
|
|
402
409
|
});
|
|
403
410
|
|
|
404
411
|
// Create resolves_to edge
|
|
405
|
-
await
|
|
412
|
+
await dbRun(
|
|
413
|
+
this.db,
|
|
406
414
|
`INSERT INTO edges (edge_id, src_type, src_id, rel_type, dst_type, dst_id, meta_json)
|
|
407
415
|
VALUES (?, 'entity', ?, 'resolves_to', 'entity', ?, ?)
|
|
408
416
|
ON CONFLICT DO NOTHING`,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* AXIOMMIND Principle 6: DuckDB → outbox → LanceDB unidirectional flow
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { Database } from '
|
|
6
|
+
import { dbRun, dbAll, toDate, type Database } from './db-wrapper.js';
|
|
7
7
|
import { randomUUID } from 'crypto';
|
|
8
8
|
import type {
|
|
9
9
|
OutboxJob,
|
|
@@ -56,7 +56,8 @@ export class VectorOutbox {
|
|
|
56
56
|
const jobId = randomUUID();
|
|
57
57
|
const now = new Date().toISOString();
|
|
58
58
|
|
|
59
|
-
await
|
|
59
|
+
await dbRun(
|
|
60
|
+
this.db,
|
|
60
61
|
`INSERT INTO vector_outbox (
|
|
61
62
|
job_id, item_kind, item_id, embedding_version, status, retry_count, created_at, updated_at
|
|
62
63
|
) VALUES (?, ?, ?, ?, 'pending', 0, ?, ?)
|
|
@@ -74,7 +75,8 @@ export class VectorOutbox {
|
|
|
74
75
|
const now = new Date().toISOString();
|
|
75
76
|
|
|
76
77
|
// Atomic claim using UPDATE RETURNING
|
|
77
|
-
const rows = await
|
|
78
|
+
const rows = await dbAll<Record<string, unknown>>(
|
|
79
|
+
this.db,
|
|
78
80
|
`UPDATE vector_outbox
|
|
79
81
|
SET status = 'processing', updated_at = ?
|
|
80
82
|
WHERE job_id IN (
|
|
@@ -94,7 +96,8 @@ export class VectorOutbox {
|
|
|
94
96
|
* Mark job as done
|
|
95
97
|
*/
|
|
96
98
|
async markDone(jobId: string): Promise<void> {
|
|
97
|
-
await
|
|
99
|
+
await dbRun(
|
|
100
|
+
this.db,
|
|
98
101
|
`UPDATE vector_outbox
|
|
99
102
|
SET status = 'done', updated_at = ?
|
|
100
103
|
WHERE job_id = ?`,
|
|
@@ -109,7 +112,8 @@ export class VectorOutbox {
|
|
|
109
112
|
const now = new Date().toISOString();
|
|
110
113
|
|
|
111
114
|
// Check retry count
|
|
112
|
-
const rows = await
|
|
115
|
+
const rows = await dbAll<{ retry_count: number }>(
|
|
116
|
+
this.db,
|
|
113
117
|
`SELECT retry_count FROM vector_outbox WHERE job_id = ?`,
|
|
114
118
|
[jobId]
|
|
115
119
|
);
|
|
@@ -121,7 +125,8 @@ export class VectorOutbox {
|
|
|
121
125
|
? 'failed'
|
|
122
126
|
: 'pending'; // Will retry
|
|
123
127
|
|
|
124
|
-
await
|
|
128
|
+
await dbRun(
|
|
129
|
+
this.db,
|
|
125
130
|
`UPDATE vector_outbox
|
|
126
131
|
SET status = ?, error = ?, retry_count = retry_count + 1, updated_at = ?
|
|
127
132
|
WHERE job_id = ?`,
|
|
@@ -133,7 +138,8 @@ export class VectorOutbox {
|
|
|
133
138
|
* Get job by ID
|
|
134
139
|
*/
|
|
135
140
|
async getJob(jobId: string): Promise<OutboxJob | null> {
|
|
136
|
-
const rows = await
|
|
141
|
+
const rows = await dbAll<Record<string, unknown>>(
|
|
142
|
+
this.db,
|
|
137
143
|
`SELECT * FROM vector_outbox WHERE job_id = ?`,
|
|
138
144
|
[jobId]
|
|
139
145
|
);
|
|
@@ -146,7 +152,8 @@ export class VectorOutbox {
|
|
|
146
152
|
* Get jobs by status
|
|
147
153
|
*/
|
|
148
154
|
async getJobsByStatus(status: OutboxStatus, limit: number = 100): Promise<OutboxJob[]> {
|
|
149
|
-
const rows = await
|
|
155
|
+
const rows = await dbAll<Record<string, unknown>>(
|
|
156
|
+
this.db,
|
|
150
157
|
`SELECT * FROM vector_outbox
|
|
151
158
|
WHERE status = ?
|
|
152
159
|
ORDER BY created_at ASC
|
|
@@ -165,7 +172,8 @@ export class VectorOutbox {
|
|
|
165
172
|
const stuckThreshold = new Date(now.getTime() - this.config.stuckThresholdMs);
|
|
166
173
|
|
|
167
174
|
// Recover stuck processing jobs
|
|
168
|
-
|
|
175
|
+
await dbRun(
|
|
176
|
+
this.db,
|
|
169
177
|
`UPDATE vector_outbox
|
|
170
178
|
SET status = 'pending', updated_at = ?
|
|
171
179
|
WHERE status = 'processing'
|
|
@@ -174,7 +182,8 @@ export class VectorOutbox {
|
|
|
174
182
|
);
|
|
175
183
|
|
|
176
184
|
// Retry failed jobs that haven't exceeded max retries
|
|
177
|
-
|
|
185
|
+
await dbRun(
|
|
186
|
+
this.db,
|
|
178
187
|
`UPDATE vector_outbox
|
|
179
188
|
SET status = 'pending', updated_at = ?
|
|
180
189
|
WHERE status = 'failed'
|
|
@@ -183,7 +192,8 @@ export class VectorOutbox {
|
|
|
183
192
|
);
|
|
184
193
|
|
|
185
194
|
// Get counts (DuckDB doesn't return affected rows easily)
|
|
186
|
-
const recoveredRows = await
|
|
195
|
+
const recoveredRows = await dbAll<{ count: number }>(
|
|
196
|
+
this.db,
|
|
187
197
|
`SELECT COUNT(*) as count FROM vector_outbox
|
|
188
198
|
WHERE status = 'pending' AND updated_at = ?`,
|
|
189
199
|
[now.toISOString()]
|
|
@@ -202,7 +212,8 @@ export class VectorOutbox {
|
|
|
202
212
|
const threshold = new Date();
|
|
203
213
|
threshold.setDate(threshold.getDate() - this.config.cleanupDays);
|
|
204
214
|
|
|
205
|
-
await
|
|
215
|
+
await dbRun(
|
|
216
|
+
this.db,
|
|
206
217
|
`DELETE FROM vector_outbox
|
|
207
218
|
WHERE status = 'done'
|
|
208
219
|
AND updated_at < ?`,
|
|
@@ -216,13 +227,15 @@ export class VectorOutbox {
|
|
|
216
227
|
* Get metrics
|
|
217
228
|
*/
|
|
218
229
|
async getMetrics(): Promise<OutboxMetrics> {
|
|
219
|
-
const statusCounts = await
|
|
230
|
+
const statusCounts = await dbAll<{ status: string; count: number }>(
|
|
231
|
+
this.db,
|
|
220
232
|
`SELECT status, COUNT(*) as count
|
|
221
233
|
FROM vector_outbox
|
|
222
234
|
GROUP BY status`
|
|
223
235
|
);
|
|
224
236
|
|
|
225
|
-
const oldestPending = await
|
|
237
|
+
const oldestPending = await dbAll<{ created_at: string }>(
|
|
238
|
+
this.db,
|
|
226
239
|
`SELECT created_at FROM vector_outbox
|
|
227
240
|
WHERE status = 'pending'
|
|
228
241
|
ORDER BY created_at ASC
|
|
@@ -288,8 +301,8 @@ export class VectorOutbox {
|
|
|
288
301
|
status: row.status as OutboxStatus,
|
|
289
302
|
retryCount: row.retry_count as number,
|
|
290
303
|
error: row.error as string | undefined,
|
|
291
|
-
createdAt:
|
|
292
|
-
updatedAt:
|
|
304
|
+
createdAt: toDate(row.created_at),
|
|
305
|
+
updatedAt: toDate(row.updated_at)
|
|
293
306
|
};
|
|
294
307
|
}
|
|
295
308
|
}
|
package/src/core/vector-store.ts
CHANGED
|
@@ -121,7 +121,11 @@ export class VectorStore {
|
|
|
121
121
|
|
|
122
122
|
const { limit = 5, minScore = 0.7, sessionId } = options;
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
// Use cosine distance for semantic similarity
|
|
125
|
+
let query = this.table
|
|
126
|
+
.search(queryVector)
|
|
127
|
+
.distanceType('cosine')
|
|
128
|
+
.limit(limit * 2); // Get more for filtering
|
|
125
129
|
|
|
126
130
|
// Apply session filter if specified
|
|
127
131
|
if (sessionId) {
|
|
@@ -132,20 +136,27 @@ export class VectorStore {
|
|
|
132
136
|
|
|
133
137
|
return results
|
|
134
138
|
.filter(r => {
|
|
135
|
-
// Convert distance to score
|
|
136
|
-
|
|
139
|
+
// Convert cosine distance to similarity score
|
|
140
|
+
// Cosine distance ranges from 0 (identical) to 2 (opposite)
|
|
141
|
+
// Score = 1 - (distance / 2) gives range [0, 1]
|
|
142
|
+
const distance = r._distance || 0;
|
|
143
|
+
const score = 1 - (distance / 2);
|
|
137
144
|
return score >= minScore;
|
|
138
145
|
})
|
|
139
146
|
.slice(0, limit)
|
|
140
|
-
.map(r =>
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
.map(r => {
|
|
148
|
+
const distance = r._distance || 0;
|
|
149
|
+
const score = 1 - (distance / 2);
|
|
150
|
+
return {
|
|
151
|
+
id: r.id as string,
|
|
152
|
+
eventId: r.eventId as string,
|
|
153
|
+
content: r.content as string,
|
|
154
|
+
score,
|
|
155
|
+
sessionId: r.sessionId as string,
|
|
156
|
+
eventType: r.eventType as string,
|
|
157
|
+
timestamp: r.timestamp as string
|
|
158
|
+
};
|
|
159
|
+
});
|
|
149
160
|
}
|
|
150
161
|
|
|
151
162
|
/**
|
|
@@ -186,7 +186,7 @@ export function createVectorWorker(
|
|
|
186
186
|
// Vector Worker V2 - Extended for Task Entity System
|
|
187
187
|
// ============================================================
|
|
188
188
|
|
|
189
|
-
import { Database } from '
|
|
189
|
+
import { dbAll, type Database } from './db-wrapper.js';
|
|
190
190
|
import { VectorOutbox } from './vector-outbox.js';
|
|
191
191
|
import type { OutboxJob, OutboxItemKind } from './types.js';
|
|
192
192
|
|
|
@@ -240,7 +240,8 @@ export class DefaultContentProvider implements ContentProvider {
|
|
|
240
240
|
content: string;
|
|
241
241
|
metadata: Record<string, unknown>;
|
|
242
242
|
} | null> {
|
|
243
|
-
const rows = await
|
|
243
|
+
const rows = await dbAll<Record<string, unknown>>(
|
|
244
|
+
this.db,
|
|
244
245
|
`SELECT title, content_json, entry_type FROM entries WHERE entry_id = ?`,
|
|
245
246
|
[entryId]
|
|
246
247
|
);
|
|
@@ -265,7 +266,8 @@ export class DefaultContentProvider implements ContentProvider {
|
|
|
265
266
|
content: string;
|
|
266
267
|
metadata: Record<string, unknown>;
|
|
267
268
|
} | null> {
|
|
268
|
-
const rows = await
|
|
269
|
+
const rows = await dbAll<Record<string, unknown>>(
|
|
270
|
+
this.db,
|
|
269
271
|
`SELECT title, search_text, current_json FROM entities
|
|
270
272
|
WHERE entity_id = ? AND entity_type = 'task'`,
|
|
271
273
|
[taskId]
|
|
@@ -287,7 +289,8 @@ export class DefaultContentProvider implements ContentProvider {
|
|
|
287
289
|
content: string;
|
|
288
290
|
metadata: Record<string, unknown>;
|
|
289
291
|
} | null> {
|
|
290
|
-
const rows = await
|
|
292
|
+
const rows = await dbAll<Record<string, unknown>>(
|
|
293
|
+
this.db,
|
|
291
294
|
`SELECT content, event_type, session_id FROM events WHERE id = ?`,
|
|
292
295
|
[eventId]
|
|
293
296
|
);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { randomUUID } from 'crypto';
|
|
8
|
-
import { Database } from '
|
|
8
|
+
import { dbRun, dbAll, toDate, type Database } from './db-wrapper.js';
|
|
9
9
|
import type {
|
|
10
10
|
MemoryEvent,
|
|
11
11
|
EndlessModeConfig,
|
|
@@ -32,7 +32,8 @@ export class WorkingSetStore {
|
|
|
32
32
|
Date.now() + this.config.workingSet.timeWindowHours * 60 * 60 * 1000
|
|
33
33
|
);
|
|
34
34
|
|
|
35
|
-
await
|
|
35
|
+
await dbRun(
|
|
36
|
+
this.db,
|
|
36
37
|
`INSERT OR REPLACE INTO working_set (id, event_id, added_at, relevance_score, topics, expires_at)
|
|
37
38
|
VALUES (?, ?, CURRENT_TIMESTAMP, ?, ?, ?)`,
|
|
38
39
|
[
|
|
@@ -56,7 +57,8 @@ export class WorkingSetStore {
|
|
|
56
57
|
await this.cleanup();
|
|
57
58
|
|
|
58
59
|
// Get working set items with their events
|
|
59
|
-
const rows = await
|
|
60
|
+
const rows = await dbAll<Record<string, unknown>>(
|
|
61
|
+
this.db,
|
|
60
62
|
`SELECT ws.*, e.*
|
|
61
63
|
FROM working_set ws
|
|
62
64
|
JOIN events e ON ws.event_id = e.id
|
|
@@ -69,7 +71,7 @@ export class WorkingSetStore {
|
|
|
69
71
|
id: row.id as string,
|
|
70
72
|
eventType: row.event_type as 'user_prompt' | 'agent_response' | 'session_summary' | 'tool_observation',
|
|
71
73
|
sessionId: row.session_id as string,
|
|
72
|
-
timestamp:
|
|
74
|
+
timestamp: toDate(row.timestamp),
|
|
73
75
|
content: row.content as string,
|
|
74
76
|
canonicalKey: row.canonical_key as string,
|
|
75
77
|
dedupeKey: row.dedupe_key as string,
|
|
@@ -87,17 +89,18 @@ export class WorkingSetStore {
|
|
|
87
89
|
* Get working set items (metadata only)
|
|
88
90
|
*/
|
|
89
91
|
async getItems(): Promise<WorkingSetItem[]> {
|
|
90
|
-
const rows = await
|
|
92
|
+
const rows = await dbAll<Record<string, unknown>>(
|
|
93
|
+
this.db,
|
|
91
94
|
`SELECT * FROM working_set ORDER BY relevance_score DESC, added_at DESC`
|
|
92
95
|
);
|
|
93
96
|
|
|
94
97
|
return rows.map(row => ({
|
|
95
98
|
id: row.id as string,
|
|
96
99
|
eventId: row.event_id as string,
|
|
97
|
-
addedAt:
|
|
100
|
+
addedAt: toDate(row.added_at),
|
|
98
101
|
relevanceScore: row.relevance_score as number,
|
|
99
102
|
topics: row.topics ? JSON.parse(row.topics as string) : undefined,
|
|
100
|
-
expiresAt:
|
|
103
|
+
expiresAt: toDate(row.expires_at)
|
|
101
104
|
}));
|
|
102
105
|
}
|
|
103
106
|
|
|
@@ -105,7 +108,8 @@ export class WorkingSetStore {
|
|
|
105
108
|
* Update relevance score for an event
|
|
106
109
|
*/
|
|
107
110
|
async updateRelevance(eventId: string, score: number): Promise<void> {
|
|
108
|
-
await
|
|
111
|
+
await dbRun(
|
|
112
|
+
this.db,
|
|
109
113
|
`UPDATE working_set SET relevance_score = ? WHERE event_id = ?`,
|
|
110
114
|
[score, eventId]
|
|
111
115
|
);
|
|
@@ -118,7 +122,8 @@ export class WorkingSetStore {
|
|
|
118
122
|
if (eventIds.length === 0) return;
|
|
119
123
|
|
|
120
124
|
const placeholders = eventIds.map(() => '?').join(',');
|
|
121
|
-
await
|
|
125
|
+
await dbRun(
|
|
126
|
+
this.db,
|
|
122
127
|
`DELETE FROM working_set WHERE event_id IN (${placeholders})`,
|
|
123
128
|
eventIds
|
|
124
129
|
);
|
|
@@ -128,7 +133,8 @@ export class WorkingSetStore {
|
|
|
128
133
|
* Get the count of items in working set
|
|
129
134
|
*/
|
|
130
135
|
async count(): Promise<number> {
|
|
131
|
-
const result = await
|
|
136
|
+
const result = await dbAll<{ count: number }>(
|
|
137
|
+
this.db,
|
|
132
138
|
`SELECT COUNT(*) as count FROM working_set`
|
|
133
139
|
);
|
|
134
140
|
return result[0]?.count || 0;
|
|
@@ -138,14 +144,15 @@ export class WorkingSetStore {
|
|
|
138
144
|
* Clear the entire working set
|
|
139
145
|
*/
|
|
140
146
|
async clear(): Promise<void> {
|
|
141
|
-
await this.db
|
|
147
|
+
await dbRun(this.db, `DELETE FROM working_set`);
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
/**
|
|
145
151
|
* Check if an event is in the working set
|
|
146
152
|
*/
|
|
147
153
|
async contains(eventId: string): Promise<boolean> {
|
|
148
|
-
const result = await
|
|
154
|
+
const result = await dbAll<{ count: number }>(
|
|
155
|
+
this.db,
|
|
149
156
|
`SELECT COUNT(*) as count FROM working_set WHERE event_id = ?`,
|
|
150
157
|
[eventId]
|
|
151
158
|
);
|
|
@@ -160,7 +167,8 @@ export class WorkingSetStore {
|
|
|
160
167
|
Date.now() + this.config.workingSet.timeWindowHours * 60 * 60 * 1000
|
|
161
168
|
);
|
|
162
169
|
|
|
163
|
-
await
|
|
170
|
+
await dbRun(
|
|
171
|
+
this.db,
|
|
164
172
|
`UPDATE working_set SET expires_at = ? WHERE event_id = ?`,
|
|
165
173
|
[newExpiresAt.toISOString(), eventId]
|
|
166
174
|
);
|
|
@@ -170,7 +178,8 @@ export class WorkingSetStore {
|
|
|
170
178
|
* Clean up expired items
|
|
171
179
|
*/
|
|
172
180
|
private async cleanup(): Promise<void> {
|
|
173
|
-
await
|
|
181
|
+
await dbRun(
|
|
182
|
+
this.db,
|
|
174
183
|
`DELETE FROM working_set WHERE expires_at < datetime('now')`
|
|
175
184
|
);
|
|
176
185
|
}
|
|
@@ -183,7 +192,8 @@ export class WorkingSetStore {
|
|
|
183
192
|
const maxEvents = this.config.workingSet.maxEvents;
|
|
184
193
|
|
|
185
194
|
// Get IDs to keep (highest relevance, most recent)
|
|
186
|
-
const keepIds = await
|
|
195
|
+
const keepIds = await dbAll<{ id: string }>(
|
|
196
|
+
this.db,
|
|
187
197
|
`SELECT id FROM working_set
|
|
188
198
|
ORDER BY relevance_score DESC, added_at DESC
|
|
189
199
|
LIMIT ?`,
|
|
@@ -196,7 +206,8 @@ export class WorkingSetStore {
|
|
|
196
206
|
const placeholders = keepIdList.map(() => '?').join(',');
|
|
197
207
|
|
|
198
208
|
// Delete everything not in the keep list
|
|
199
|
-
await
|
|
209
|
+
await dbRun(
|
|
210
|
+
this.db,
|
|
200
211
|
`DELETE FROM working_set WHERE id NOT IN (${placeholders})`,
|
|
201
212
|
keepIdList
|
|
202
213
|
);
|
|
@@ -206,7 +217,8 @@ export class WorkingSetStore {
|
|
|
206
217
|
* Calculate continuity score based on recent context transitions
|
|
207
218
|
*/
|
|
208
219
|
private async calculateContinuityScore(): Promise<number> {
|
|
209
|
-
const result = await
|
|
220
|
+
const result = await dbAll<{ avg_score: number | null }>(
|
|
221
|
+
this.db,
|
|
210
222
|
`SELECT AVG(continuity_score) as avg_score
|
|
211
223
|
FROM continuity_log
|
|
212
224
|
WHERE created_at > datetime('now', '-1 hour')`
|
|
@@ -219,7 +231,8 @@ export class WorkingSetStore {
|
|
|
219
231
|
* Get topics from current working set for context matching
|
|
220
232
|
*/
|
|
221
233
|
async getActiveTopics(): Promise<string[]> {
|
|
222
|
-
const rows = await
|
|
234
|
+
const rows = await dbAll<{ topics: string }>(
|
|
235
|
+
this.db,
|
|
223
236
|
`SELECT topics FROM working_set WHERE topics IS NOT NULL`
|
|
224
237
|
);
|
|
225
238
|
|
package/src/hooks/session-end.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Called when session ends - generates and stores session summary
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { getMemoryServiceForSession } from '../services/memory-service.js';
|
|
8
8
|
import type { SessionEndInput } from '../core/types.js';
|
|
9
9
|
|
|
10
10
|
async function main(): Promise<void> {
|
|
@@ -12,7 +12,8 @@ async function main(): Promise<void> {
|
|
|
12
12
|
const inputData = await readStdin();
|
|
13
13
|
const input: SessionEndInput = JSON.parse(inputData);
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// Get project-specific memory service via session lookup
|
|
16
|
+
const memoryService = getMemoryServiceForSession(input.session_id);
|
|
16
17
|
|
|
17
18
|
try {
|
|
18
19
|
// Get session history
|