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.
- package/CHANGELOG.md +433 -0
- package/LICENSE +21 -0
- package/README.md +1051 -0
- package/bin/mcp-memory-keeper +52 -0
- package/dist/__tests__/helpers/database-test-helper.js +160 -0
- package/dist/__tests__/helpers/test-server.js +92 -0
- package/dist/__tests__/integration/advanced-features.test.js +614 -0
- package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
- package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
- package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
- package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
- package/dist/__tests__/integration/channels.test.js +376 -0
- package/dist/__tests__/integration/checkpoint.test.js +251 -0
- package/dist/__tests__/integration/concurrent-access.test.js +190 -0
- package/dist/__tests__/integration/context-operations.test.js +243 -0
- package/dist/__tests__/integration/contextDiff.test.js +852 -0
- package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
- package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
- package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
- package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
- package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
- package/dist/__tests__/integration/contextSearch.test.js +938 -0
- package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
- package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
- package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
- package/dist/__tests__/integration/cross-session-sharing.test.js +302 -0
- package/dist/__tests__/integration/database-initialization.test.js +134 -0
- package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
- package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
- package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
- package/dist/__tests__/integration/error-cases.test.js +407 -0
- package/dist/__tests__/integration/export-import.test.js +367 -0
- package/dist/__tests__/integration/feature-flags.test.js +542 -0
- package/dist/__tests__/integration/file-operations.test.js +264 -0
- package/dist/__tests__/integration/git-integration.test.js +237 -0
- package/dist/__tests__/integration/index-tools.test.js +496 -0
- package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
- package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
- package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
- package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
- package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
- package/dist/__tests__/integration/migrations.test.js +528 -0
- package/dist/__tests__/integration/multi-agent.test.js +546 -0
- package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
- package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
- package/dist/__tests__/integration/project-directory.test.js +283 -0
- package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
- package/dist/__tests__/integration/retention.test.js +513 -0
- package/dist/__tests__/integration/search.test.js +333 -0
- package/dist/__tests__/integration/semantic-search.test.js +266 -0
- package/dist/__tests__/integration/server-initialization.test.js +307 -0
- package/dist/__tests__/integration/session-management.test.js +219 -0
- package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
- package/dist/__tests__/integration/smart-compaction.test.js +230 -0
- package/dist/__tests__/integration/summarization.test.js +308 -0
- package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
- package/dist/__tests__/security/input-validation.test.js +115 -0
- package/dist/__tests__/utils/agents.test.js +473 -0
- package/dist/__tests__/utils/database.test.js +177 -0
- package/dist/__tests__/utils/git.test.js +122 -0
- package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
- package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
- package/dist/__tests__/utils/project-directory-messages.test.js +188 -0
- package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
- package/dist/__tests__/utils/validation.test.js +200 -0
- package/dist/__tests__/utils/vector-store.test.js +231 -0
- package/dist/handlers/contextWatchHandlers.js +206 -0
- package/dist/index.js +4310 -0
- package/dist/index.phase1.backup.js +410 -0
- package/dist/index.phase2.backup.js +704 -0
- package/dist/migrations/003_add_channels.js +174 -0
- package/dist/migrations/004_add_context_watch.js +151 -0
- package/dist/migrations/005_add_context_watch.js +98 -0
- package/dist/migrations/simplify-sharing.js +117 -0
- package/dist/repositories/BaseRepository.js +30 -0
- package/dist/repositories/CheckpointRepository.js +140 -0
- package/dist/repositories/ContextRepository.js +1873 -0
- package/dist/repositories/FileRepository.js +104 -0
- package/dist/repositories/RepositoryManager.js +62 -0
- package/dist/repositories/SessionRepository.js +66 -0
- package/dist/repositories/WatcherRepository.js +252 -0
- package/dist/repositories/index.js +15 -0
- package/dist/server.js +384 -0
- package/dist/test-helpers/database-helper.js +128 -0
- package/dist/types/entities.js +3 -0
- package/dist/utils/agents.js +791 -0
- package/dist/utils/channels.js +150 -0
- package/dist/utils/database.js +731 -0
- package/dist/utils/feature-flags.js +476 -0
- package/dist/utils/git.js +145 -0
- package/dist/utils/knowledge-graph.js +264 -0
- package/dist/utils/migrationHealthCheck.js +373 -0
- package/dist/utils/migrations.js +452 -0
- package/dist/utils/retention.js +460 -0
- package/dist/utils/timestamps.js +112 -0
- package/dist/utils/validation.js +296 -0
- package/dist/utils/vector-store.js +247 -0
- 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; } });
|