mcp-memory-keeper 0.10.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.
Files changed (98) hide show
  1. package/CHANGELOG.md +433 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1051 -0
  4. package/bin/mcp-memory-keeper +52 -0
  5. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  6. package/dist/__tests__/helpers/test-server.js +92 -0
  7. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  8. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  9. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  10. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  11. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  12. package/dist/__tests__/integration/channels.test.js +376 -0
  13. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  14. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  15. package/dist/__tests__/integration/context-operations.test.js +243 -0
  16. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  17. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  18. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  19. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  20. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  21. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  22. package/dist/__tests__/integration/contextSearch.test.js +938 -0
  23. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  24. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  25. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  26. package/dist/__tests__/integration/cross-session-sharing.test.js +302 -0
  27. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  28. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  29. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  30. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  31. package/dist/__tests__/integration/error-cases.test.js +407 -0
  32. package/dist/__tests__/integration/export-import.test.js +367 -0
  33. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  34. package/dist/__tests__/integration/file-operations.test.js +264 -0
  35. package/dist/__tests__/integration/git-integration.test.js +237 -0
  36. package/dist/__tests__/integration/index-tools.test.js +496 -0
  37. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  38. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  39. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  40. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  41. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  42. package/dist/__tests__/integration/migrations.test.js +528 -0
  43. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  44. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  45. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  46. package/dist/__tests__/integration/project-directory.test.js +283 -0
  47. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  48. package/dist/__tests__/integration/retention.test.js +513 -0
  49. package/dist/__tests__/integration/search.test.js +333 -0
  50. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  51. package/dist/__tests__/integration/server-initialization.test.js +307 -0
  52. package/dist/__tests__/integration/session-management.test.js +219 -0
  53. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  54. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  55. package/dist/__tests__/integration/summarization.test.js +308 -0
  56. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  57. package/dist/__tests__/security/input-validation.test.js +115 -0
  58. package/dist/__tests__/utils/agents.test.js +473 -0
  59. package/dist/__tests__/utils/database.test.js +177 -0
  60. package/dist/__tests__/utils/git.test.js +122 -0
  61. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  62. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  63. package/dist/__tests__/utils/project-directory-messages.test.js +188 -0
  64. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  65. package/dist/__tests__/utils/validation.test.js +200 -0
  66. package/dist/__tests__/utils/vector-store.test.js +231 -0
  67. package/dist/handlers/contextWatchHandlers.js +206 -0
  68. package/dist/index.js +4310 -0
  69. package/dist/index.phase1.backup.js +410 -0
  70. package/dist/index.phase2.backup.js +704 -0
  71. package/dist/migrations/003_add_channels.js +174 -0
  72. package/dist/migrations/004_add_context_watch.js +151 -0
  73. package/dist/migrations/005_add_context_watch.js +98 -0
  74. package/dist/migrations/simplify-sharing.js +117 -0
  75. package/dist/repositories/BaseRepository.js +30 -0
  76. package/dist/repositories/CheckpointRepository.js +140 -0
  77. package/dist/repositories/ContextRepository.js +1873 -0
  78. package/dist/repositories/FileRepository.js +104 -0
  79. package/dist/repositories/RepositoryManager.js +62 -0
  80. package/dist/repositories/SessionRepository.js +66 -0
  81. package/dist/repositories/WatcherRepository.js +252 -0
  82. package/dist/repositories/index.js +15 -0
  83. package/dist/server.js +384 -0
  84. package/dist/test-helpers/database-helper.js +128 -0
  85. package/dist/types/entities.js +3 -0
  86. package/dist/utils/agents.js +791 -0
  87. package/dist/utils/channels.js +150 -0
  88. package/dist/utils/database.js +731 -0
  89. package/dist/utils/feature-flags.js +476 -0
  90. package/dist/utils/git.js +145 -0
  91. package/dist/utils/knowledge-graph.js +264 -0
  92. package/dist/utils/migrationHealthCheck.js +373 -0
  93. package/dist/utils/migrations.js +452 -0
  94. package/dist/utils/retention.js +460 -0
  95. package/dist/utils/timestamps.js +112 -0
  96. package/dist/utils/validation.js +296 -0
  97. package/dist/utils/vector-store.js +247 -0
  98. package/package.json +84 -0
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FileRepository = void 0;
4
+ const BaseRepository_js_1 = require("./BaseRepository.js");
5
+ class FileRepository extends BaseRepository_js_1.BaseRepository {
6
+ cache(sessionId, input) {
7
+ const id = this.generateId();
8
+ const size = this.calculateSize(input.content);
9
+ const timestamp = this.getCurrentTimestamp();
10
+ const stmt = this.db.prepare(`
11
+ INSERT OR REPLACE INTO file_cache
12
+ (id, session_id, file_path, content, hash, size, last_read, updated_at)
13
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
14
+ `);
15
+ stmt.run(id, sessionId, input.file_path, input.content, input.hash || null, size, timestamp, timestamp);
16
+ return this.getById(id);
17
+ }
18
+ getById(id) {
19
+ const stmt = this.db.prepare('SELECT * FROM file_cache WHERE id = ?');
20
+ return stmt.get(id);
21
+ }
22
+ getBySessionId(sessionId) {
23
+ const stmt = this.db.prepare(`
24
+ SELECT * FROM file_cache
25
+ WHERE session_id = ?
26
+ ORDER BY last_read DESC
27
+ `);
28
+ return stmt.all(sessionId);
29
+ }
30
+ getByFilePath(sessionId, filePath) {
31
+ const stmt = this.db.prepare(`
32
+ SELECT * FROM file_cache
33
+ WHERE session_id = ? AND file_path = ?
34
+ `);
35
+ return stmt.get(sessionId, filePath);
36
+ }
37
+ hasChanged(sessionId, filePath, currentContent) {
38
+ const cached = this.getByFilePath(sessionId, filePath);
39
+ if (!cached) {
40
+ return true; // No cached version, so it's "changed"
41
+ }
42
+ return cached.content !== currentContent;
43
+ }
44
+ updateContent(sessionId, filePath, content, hash) {
45
+ const size = this.calculateSize(content);
46
+ const timestamp = this.getCurrentTimestamp();
47
+ const stmt = this.db.prepare(`
48
+ UPDATE file_cache
49
+ SET content = ?, hash = ?, size = ?, last_read = ?, updated_at = ?
50
+ WHERE session_id = ? AND file_path = ?
51
+ `);
52
+ stmt.run(content, hash || null, size, timestamp, timestamp, sessionId, filePath);
53
+ }
54
+ delete(id) {
55
+ const stmt = this.db.prepare('DELETE FROM file_cache WHERE id = ?');
56
+ stmt.run(id);
57
+ }
58
+ deleteBySessionId(sessionId) {
59
+ const stmt = this.db.prepare('DELETE FROM file_cache WHERE session_id = ?');
60
+ stmt.run(sessionId);
61
+ }
62
+ deleteByFilePath(sessionId, filePath) {
63
+ const stmt = this.db.prepare('DELETE FROM file_cache WHERE session_id = ? AND file_path = ?');
64
+ stmt.run(sessionId, filePath);
65
+ }
66
+ copyBetweenSessions(fromSessionId, toSessionId) {
67
+ const stmt = this.db.prepare(`
68
+ INSERT INTO file_cache (id, session_id, file_path, content, hash, size, last_read, updated_at)
69
+ SELECT ?, ?, file_path, content, hash, size, last_read, ?
70
+ FROM file_cache
71
+ WHERE session_id = ?
72
+ `);
73
+ const files = this.getBySessionId(fromSessionId);
74
+ let copied = 0;
75
+ for (const _file of files) {
76
+ stmt.run(this.generateId(), toSessionId, this.getCurrentTimestamp(), fromSessionId);
77
+ copied++;
78
+ }
79
+ return copied;
80
+ }
81
+ getStatsBySession(sessionId) {
82
+ const stmt = this.db.prepare(`
83
+ SELECT COUNT(*) as count, SUM(size) as totalSize
84
+ FROM file_cache
85
+ WHERE session_id = ?
86
+ `);
87
+ const result = stmt.get(sessionId);
88
+ return {
89
+ count: result.count || 0,
90
+ totalSize: result.totalSize || 0,
91
+ };
92
+ }
93
+ cleanup(olderThanDays) {
94
+ const cutoffDate = new Date();
95
+ cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);
96
+ const stmt = this.db.prepare(`
97
+ DELETE FROM file_cache
98
+ WHERE last_read < ?
99
+ `);
100
+ const result = stmt.run(cutoffDate.toISOString());
101
+ return result.changes;
102
+ }
103
+ }
104
+ exports.FileRepository = FileRepository;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RepositoryManager = void 0;
4
+ const SessionRepository_js_1 = require("./SessionRepository.js");
5
+ const ContextRepository_js_1 = require("./ContextRepository.js");
6
+ const FileRepository_js_1 = require("./FileRepository.js");
7
+ const CheckpointRepository_js_1 = require("./CheckpointRepository.js");
8
+ const WatcherRepository_js_1 = require("./WatcherRepository.js");
9
+ class RepositoryManager {
10
+ dbManager;
11
+ sessions;
12
+ contexts;
13
+ files;
14
+ checkpoints;
15
+ watchers;
16
+ constructor(dbManager) {
17
+ this.dbManager = dbManager;
18
+ // Initialize all repositories
19
+ this.sessions = new SessionRepository_js_1.SessionRepository(dbManager);
20
+ this.contexts = new ContextRepository_js_1.ContextRepository(dbManager);
21
+ this.files = new FileRepository_js_1.FileRepository(dbManager);
22
+ this.checkpoints = new CheckpointRepository_js_1.CheckpointRepository(dbManager);
23
+ this.watchers = new WatcherRepository_js_1.WatcherRepository(dbManager);
24
+ }
25
+ /**
26
+ * Get the underlying database manager
27
+ */
28
+ getDatabaseManager() {
29
+ return this.dbManager;
30
+ }
31
+ /**
32
+ * Close all database connections
33
+ */
34
+ close() {
35
+ this.dbManager.close();
36
+ }
37
+ /**
38
+ * Get session statistics across all repositories
39
+ */
40
+ getSessionStats(sessionId) {
41
+ const contextStats = this.contexts.getStatsBySession(sessionId);
42
+ const fileStats = this.files.getStatsBySession(sessionId);
43
+ const checkpointStats = this.checkpoints.getStatsBySession(sessionId);
44
+ return {
45
+ session: this.sessions.getById(sessionId),
46
+ contexts: contextStats,
47
+ files: fileStats,
48
+ checkpoints: checkpointStats,
49
+ totalSize: contextStats.totalSize + fileStats.totalSize,
50
+ };
51
+ }
52
+ /**
53
+ * Clean up old data across all repositories
54
+ */
55
+ cleanup(olderThanDays) {
56
+ const filesDeleted = this.files.cleanup(olderThanDays);
57
+ return {
58
+ filesDeleted,
59
+ };
60
+ }
61
+ }
62
+ exports.RepositoryManager = RepositoryManager;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SessionRepository = void 0;
4
+ const BaseRepository_js_1 = require("./BaseRepository.js");
5
+ class SessionRepository extends BaseRepository_js_1.BaseRepository {
6
+ create(input) {
7
+ const id = this.generateId();
8
+ const timestamp = this.getCurrentTimestamp();
9
+ const stmt = this.db.prepare(`
10
+ INSERT INTO sessions (id, name, description, branch, working_directory, parent_id, default_channel, created_at, updated_at)
11
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
12
+ `);
13
+ stmt.run(id, input.name || `Session ${timestamp}`, input.description || '', input.branch || null, input.working_directory || null, input.parent_id || null, input.defaultChannel || 'general', timestamp, timestamp);
14
+ return this.getById(id);
15
+ }
16
+ getById(id) {
17
+ const stmt = this.db.prepare('SELECT * FROM sessions WHERE id = ?');
18
+ return stmt.get(id);
19
+ }
20
+ getAll(limit = 50) {
21
+ const stmt = this.db.prepare('SELECT * FROM sessions ORDER BY created_at DESC LIMIT ?');
22
+ return stmt.all(limit);
23
+ }
24
+ getLatest() {
25
+ const stmt = this.db.prepare('SELECT * FROM sessions ORDER BY created_at DESC LIMIT 1');
26
+ return stmt.get();
27
+ }
28
+ update(id, updates) {
29
+ const setClause = Object.keys(updates)
30
+ .filter(key => key !== 'id' && key !== 'created_at')
31
+ .map(key => `${key} = ?`)
32
+ .join(', ');
33
+ if (setClause) {
34
+ const values = Object.keys(updates)
35
+ .filter(key => key !== 'id' && key !== 'created_at')
36
+ .map(key => updates[key]);
37
+ const stmt = this.db.prepare(`
38
+ UPDATE sessions
39
+ SET ${setClause}, updated_at = CURRENT_TIMESTAMP
40
+ WHERE id = ?
41
+ `);
42
+ stmt.run(...values, id);
43
+ }
44
+ }
45
+ delete(id) {
46
+ const stmt = this.db.prepare('DELETE FROM sessions WHERE id = ?');
47
+ stmt.run(id);
48
+ }
49
+ findByBranch(branch) {
50
+ const stmt = this.db.prepare('SELECT * FROM sessions WHERE branch = ? ORDER BY created_at DESC');
51
+ return stmt.all(branch);
52
+ }
53
+ findByWorkingDirectory(workingDirectory) {
54
+ const stmt = this.db.prepare('SELECT * FROM sessions WHERE working_directory = ? ORDER BY created_at DESC');
55
+ return stmt.all(workingDirectory);
56
+ }
57
+ getChildren(parentId) {
58
+ const stmt = this.db.prepare('SELECT * FROM sessions WHERE parent_id = ? ORDER BY created_at DESC');
59
+ return stmt.all(parentId);
60
+ }
61
+ getRecent(limit = 10) {
62
+ const stmt = this.db.prepare('SELECT * FROM sessions ORDER BY created_at DESC LIMIT ?');
63
+ return stmt.all(limit);
64
+ }
65
+ }
66
+ exports.SessionRepository = SessionRepository;
@@ -0,0 +1,252 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WatcherRepository = void 0;
4
+ const uuid_1 = require("uuid");
5
+ const timestamps_1 = require("../utils/timestamps");
6
+ class WatcherRepository {
7
+ db;
8
+ constructor(db) {
9
+ this.db = db;
10
+ }
11
+ createWatcher(params) {
12
+ const id = `watch_${(0, uuid_1.v4)().substring(0, 8)}`;
13
+ const now = new Date();
14
+ const ttlSeconds = params.ttl || 3600; // Default 1 hour
15
+ const expiresAt = new Date(now.getTime() + ttlSeconds * 1000);
16
+ // Get current max sequence number
17
+ const maxSeqQuery = params.sessionId
18
+ ? 'SELECT MAX(sequence_id) as max_seq FROM context_changes WHERE session_id = ?'
19
+ : 'SELECT MAX(sequence_id) as max_seq FROM context_changes';
20
+ const maxSeqResult = params.sessionId
21
+ ? this.db.getDatabase().prepare(maxSeqQuery).get(params.sessionId)
22
+ : this.db.getDatabase().prepare(maxSeqQuery).get();
23
+ const currentSequence = maxSeqResult?.max_seq || 0;
24
+ const stmt = this.db.getDatabase().prepare(`
25
+ INSERT INTO context_watchers (
26
+ id, session_id, filter_keys, filter_categories, filter_channels, filter_priorities,
27
+ last_sequence, created_at, expires_at, is_active
28
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
29
+ `);
30
+ stmt.run(id, params.sessionId || null, JSON.stringify(params.filters?.keys || []), JSON.stringify(params.filters?.categories || []), JSON.stringify(params.filters?.channels || []), JSON.stringify(params.filters?.priorities || []), currentSequence, (0, timestamps_1.ensureSQLiteFormat)(now.toISOString()), (0, timestamps_1.ensureSQLiteFormat)(expiresAt.toISOString()), 1);
31
+ return {
32
+ id,
33
+ sessionId: params.sessionId,
34
+ filters: params.filters || {},
35
+ lastSequence: currentSequence,
36
+ createdAt: (0, timestamps_1.ensureSQLiteFormat)(now.toISOString()),
37
+ expiresAt: (0, timestamps_1.ensureSQLiteFormat)(expiresAt.toISOString()),
38
+ isActive: true,
39
+ };
40
+ }
41
+ getWatcher(watcherId) {
42
+ const watcher = this.db
43
+ .getDatabase()
44
+ .prepare(`
45
+ SELECT * FROM context_watchers WHERE id = ?
46
+ `)
47
+ .get(watcherId);
48
+ if (!watcher) {
49
+ return null;
50
+ }
51
+ return this.mapWatcherFromDb(watcher);
52
+ }
53
+ pollChanges(watcherId, limit = 100) {
54
+ const watcher = this.getWatcher(watcherId);
55
+ if (!watcher) {
56
+ return {
57
+ changes: [],
58
+ lastSequence: 0,
59
+ hasMore: false,
60
+ watcherStatus: 'deleted',
61
+ };
62
+ }
63
+ // Check if stopped
64
+ if (!watcher.isActive) {
65
+ return {
66
+ changes: [],
67
+ lastSequence: watcher.lastSequence,
68
+ hasMore: false,
69
+ watcherStatus: 'expired', // This is what the handler expects for stopped watchers
70
+ };
71
+ }
72
+ // Check if expired
73
+ const now = new Date();
74
+ const expiresAt = new Date(watcher.expiresAt);
75
+ if (now > expiresAt) {
76
+ // Mark as inactive if expired
77
+ this.db
78
+ .getDatabase()
79
+ .prepare('UPDATE context_watchers SET is_active = 0 WHERE id = ?')
80
+ .run(watcherId);
81
+ return {
82
+ changes: [],
83
+ lastSequence: watcher.lastSequence,
84
+ hasMore: false,
85
+ watcherStatus: 'expired',
86
+ };
87
+ }
88
+ // Build query for changes
89
+ let query = `
90
+ SELECT * FROM context_changes
91
+ WHERE sequence_id > ?
92
+ `;
93
+ const params = [watcher.lastSequence];
94
+ // Apply session filter
95
+ if (watcher.sessionId) {
96
+ query += ' AND session_id = ?';
97
+ params.push(watcher.sessionId);
98
+ }
99
+ // Apply filters
100
+ const filterConditions = [];
101
+ // Key filters with wildcard support
102
+ if (watcher.filters.keys && watcher.filters.keys.length > 0) {
103
+ const keyConditions = watcher.filters.keys
104
+ .map((pattern) => {
105
+ const sqlPattern = pattern.replace(/\*/g, '%').replace(/\?/g, '_');
106
+ params.push(sqlPattern);
107
+ return 'key LIKE ?';
108
+ })
109
+ .join(' OR ');
110
+ filterConditions.push(`(${keyConditions})`);
111
+ }
112
+ // Category filters
113
+ if (watcher.filters.categories && watcher.filters.categories.length > 0) {
114
+ const placeholders = watcher.filters.categories.map(() => '?').join(',');
115
+ filterConditions.push(`category IN (${placeholders})`);
116
+ params.push(...watcher.filters.categories);
117
+ }
118
+ // Channel filters
119
+ if (watcher.filters.channels && watcher.filters.channels.length > 0) {
120
+ const placeholders = watcher.filters.channels.map(() => '?').join(',');
121
+ filterConditions.push(`channel IN (${placeholders})`);
122
+ params.push(...watcher.filters.channels);
123
+ }
124
+ // Priority filters
125
+ if (watcher.filters.priorities && watcher.filters.priorities.length > 0) {
126
+ const placeholders = watcher.filters.priorities.map(() => '?').join(',');
127
+ filterConditions.push(`priority IN (${placeholders})`);
128
+ params.push(...watcher.filters.priorities);
129
+ }
130
+ if (filterConditions.length > 0) {
131
+ query += ' AND (' + filterConditions.join(' AND ') + ')';
132
+ }
133
+ // Add privacy filter if watching across sessions
134
+ if (!watcher.sessionId) {
135
+ query += ` AND EXISTS (
136
+ SELECT 1 FROM context_items ci
137
+ WHERE ci.id = context_changes.item_id
138
+ AND ci.is_private = 0
139
+ )`;
140
+ }
141
+ query += ' ORDER BY sequence_id ASC LIMIT ?';
142
+ params.push(limit + 1); // Get one extra to check hasMore
143
+ const changes = this.db
144
+ .getDatabase()
145
+ .prepare(query)
146
+ .all(...params);
147
+ const hasMore = changes.length > limit;
148
+ if (hasMore) {
149
+ changes.pop(); // Remove the extra one
150
+ }
151
+ let maxSequence = watcher.lastSequence;
152
+ const mappedChanges = changes.map(change => {
153
+ maxSequence = Math.max(maxSequence, change.sequence_id);
154
+ return this.mapChangeFromDb(change);
155
+ });
156
+ // Update watcher's last sequence and extend expiration
157
+ // Always update on poll to extend expiration
158
+ const newExpiry = new Date(Date.now() + 30 * 60 * 1000); // Extend by 30 minutes
159
+ this.db
160
+ .getDatabase()
161
+ .prepare(`
162
+ UPDATE context_watchers
163
+ SET last_sequence = ?, last_poll_at = ?, expires_at = ?
164
+ WHERE id = ?
165
+ `)
166
+ .run(maxSequence, (0, timestamps_1.ensureSQLiteFormat)(new Date().toISOString()), (0, timestamps_1.ensureSQLiteFormat)(newExpiry.toISOString()), watcherId);
167
+ return {
168
+ changes: mappedChanges,
169
+ lastSequence: maxSequence,
170
+ hasMore,
171
+ watcherStatus: 'active',
172
+ };
173
+ }
174
+ stopWatcher(watcherId) {
175
+ const result = this.db
176
+ .getDatabase()
177
+ .prepare(`
178
+ UPDATE context_watchers SET is_active = 0 WHERE id = ?
179
+ `)
180
+ .run(watcherId);
181
+ return result.changes > 0;
182
+ }
183
+ listWatchers(sessionId, includeExpired = false) {
184
+ let query = 'SELECT * FROM context_watchers';
185
+ const conditions = [];
186
+ const params = [];
187
+ if (sessionId) {
188
+ conditions.push('session_id = ?');
189
+ params.push(sessionId);
190
+ }
191
+ if (!includeExpired) {
192
+ conditions.push('is_active = 1');
193
+ }
194
+ if (conditions.length > 0) {
195
+ query += ' WHERE ' + conditions.join(' AND ');
196
+ }
197
+ query += ' ORDER BY created_at DESC';
198
+ const watchers = this.db
199
+ .getDatabase()
200
+ .prepare(query)
201
+ .all(...params);
202
+ return watchers.map(w => this.mapWatcherFromDb(w));
203
+ }
204
+ cleanupExpiredWatchers() {
205
+ const result = this.db
206
+ .getDatabase()
207
+ .prepare(`
208
+ DELETE FROM context_watchers
209
+ WHERE expires_at < datetime('now') AND is_active = 1
210
+ `)
211
+ .run();
212
+ return result.changes;
213
+ }
214
+ mapWatcherFromDb(row) {
215
+ return {
216
+ id: row.id,
217
+ sessionId: row.session_id || undefined,
218
+ filters: {
219
+ keys: JSON.parse(row.filter_keys || '[]'),
220
+ categories: JSON.parse(row.filter_categories || '[]'),
221
+ channels: JSON.parse(row.filter_channels || '[]'),
222
+ priorities: JSON.parse(row.filter_priorities || '[]'),
223
+ },
224
+ lastSequence: row.last_sequence,
225
+ createdAt: row.created_at,
226
+ lastPollAt: row.last_poll_at || undefined,
227
+ expiresAt: row.expires_at,
228
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
229
+ isActive: row.is_active === 1,
230
+ };
231
+ }
232
+ mapChangeFromDb(row) {
233
+ return {
234
+ sequenceId: row.sequence_id,
235
+ sessionId: row.session_id,
236
+ itemId: row.item_id,
237
+ key: row.key,
238
+ operation: row.operation,
239
+ oldValue: row.old_value || undefined,
240
+ newValue: row.new_value || undefined,
241
+ oldMetadata: row.old_metadata || undefined,
242
+ newMetadata: row.new_metadata || undefined,
243
+ category: row.category || undefined,
244
+ priority: row.priority || undefined,
245
+ channel: row.channel || undefined,
246
+ sizeDelta: row.size_delta || 0,
247
+ createdAt: row.created_at,
248
+ createdBy: row.created_by || undefined,
249
+ };
250
+ }
251
+ }
252
+ exports.WatcherRepository = WatcherRepository;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RepositoryManager = exports.CheckpointRepository = exports.FileRepository = exports.ContextRepository = exports.SessionRepository = exports.BaseRepository = void 0;
4
+ var BaseRepository_js_1 = require("./BaseRepository.js");
5
+ Object.defineProperty(exports, "BaseRepository", { enumerable: true, get: function () { return BaseRepository_js_1.BaseRepository; } });
6
+ var SessionRepository_js_1 = require("./SessionRepository.js");
7
+ Object.defineProperty(exports, "SessionRepository", { enumerable: true, get: function () { return SessionRepository_js_1.SessionRepository; } });
8
+ var ContextRepository_js_1 = require("./ContextRepository.js");
9
+ Object.defineProperty(exports, "ContextRepository", { enumerable: true, get: function () { return ContextRepository_js_1.ContextRepository; } });
10
+ var FileRepository_js_1 = require("./FileRepository.js");
11
+ Object.defineProperty(exports, "FileRepository", { enumerable: true, get: function () { return FileRepository_js_1.FileRepository; } });
12
+ var CheckpointRepository_js_1 = require("./CheckpointRepository.js");
13
+ Object.defineProperty(exports, "CheckpointRepository", { enumerable: true, get: function () { return CheckpointRepository_js_1.CheckpointRepository; } });
14
+ var RepositoryManager_js_1 = require("./RepositoryManager.js");
15
+ Object.defineProperty(exports, "RepositoryManager", { enumerable: true, get: function () { return RepositoryManager_js_1.RepositoryManager; } });