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
package/dist/index.js
ADDED
|
@@ -0,0 +1,4310 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.dbManager = exports._featureFlagManager = void 0;
|
|
37
|
+
exports.debugLog = debugLog;
|
|
38
|
+
exports.validatePaginationParams = validatePaginationParams;
|
|
39
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
40
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
41
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
42
|
+
const uuid_1 = require("uuid");
|
|
43
|
+
const crypto = __importStar(require("crypto"));
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
47
|
+
const database_js_1 = require("./utils/database.js");
|
|
48
|
+
const knowledge_graph_js_1 = require("./utils/knowledge-graph.js");
|
|
49
|
+
const vector_store_js_1 = require("./utils/vector-store.js");
|
|
50
|
+
const timestamps_js_1 = require("./utils/timestamps.js");
|
|
51
|
+
const agents_js_1 = require("./utils/agents.js");
|
|
52
|
+
const retention_js_1 = require("./utils/retention.js");
|
|
53
|
+
const feature_flags_js_1 = require("./utils/feature-flags.js");
|
|
54
|
+
const RepositoryManager_js_1 = require("./repositories/RepositoryManager.js");
|
|
55
|
+
const simple_git_1 = require("simple-git");
|
|
56
|
+
const channels_js_1 = require("./utils/channels.js");
|
|
57
|
+
const contextWatchHandlers_js_1 = require("./handlers/contextWatchHandlers.js");
|
|
58
|
+
// Initialize database with migrations
|
|
59
|
+
const dbManager = new database_js_1.DatabaseManager({ filename: 'context.db' });
|
|
60
|
+
exports.dbManager = dbManager;
|
|
61
|
+
const db = dbManager.getDatabase();
|
|
62
|
+
// Initialize repository manager
|
|
63
|
+
const repositories = new RepositoryManager_js_1.RepositoryManager(dbManager);
|
|
64
|
+
// Initialize git - will be created per session as needed
|
|
65
|
+
// REMOVED: Global project directory was causing conflicts between sessions
|
|
66
|
+
// Initialize knowledge graph manager
|
|
67
|
+
const knowledgeGraph = new knowledge_graph_js_1.KnowledgeGraphManager(db);
|
|
68
|
+
// Initialize vector store
|
|
69
|
+
const vectorStore = new vector_store_js_1.VectorStore(db);
|
|
70
|
+
// Initialize multi-agent system
|
|
71
|
+
const agentCoordinator = new agents_js_1.AgentCoordinator();
|
|
72
|
+
const analyzerAgent = new agents_js_1.AnalyzerAgent(db, knowledgeGraph, vectorStore);
|
|
73
|
+
const synthesizerAgent = new agents_js_1.SynthesizerAgent(db, vectorStore);
|
|
74
|
+
agentCoordinator.registerAgent(analyzerAgent);
|
|
75
|
+
agentCoordinator.registerAgent(synthesizerAgent);
|
|
76
|
+
// Initialize retention manager
|
|
77
|
+
const _retentionManager = new retention_js_1.RetentionManager(dbManager);
|
|
78
|
+
// Initialize feature flag manager
|
|
79
|
+
const _featureFlagManager = new feature_flags_js_1.FeatureFlagManager(dbManager);
|
|
80
|
+
exports._featureFlagManager = _featureFlagManager;
|
|
81
|
+
// Initialize debug logging flag if it doesn't exist
|
|
82
|
+
try {
|
|
83
|
+
if (!_featureFlagManager.getFlagByKey('debug_logging')) {
|
|
84
|
+
_featureFlagManager.createFlag({
|
|
85
|
+
name: 'Debug Logging',
|
|
86
|
+
key: 'debug_logging',
|
|
87
|
+
enabled: Boolean(process.env.MCP_DEBUG_LOGGING),
|
|
88
|
+
description: 'Enable debug logging for development and troubleshooting',
|
|
89
|
+
category: 'debug',
|
|
90
|
+
tags: ['debug', 'logging'],
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (_error) {
|
|
95
|
+
// Silently continue if flag creation fails (migrations might not be complete)
|
|
96
|
+
}
|
|
97
|
+
// Migration manager is no longer needed - watcher migrations are now applied by DatabaseManager
|
|
98
|
+
// Tables are now created by DatabaseManager in utils/database.ts
|
|
99
|
+
// Track current session
|
|
100
|
+
let currentSessionId = null;
|
|
101
|
+
// Debug logging utility
|
|
102
|
+
function debugLog(message, ...args) {
|
|
103
|
+
try {
|
|
104
|
+
if (_featureFlagManager.isEnabled('debug_logging') || process.env.MCP_DEBUG_LOGGING) {
|
|
105
|
+
// eslint-disable-next-line no-console
|
|
106
|
+
console.log(`[MCP-Memory-Keeper DEBUG] ${message}`, ...args);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (_error) {
|
|
110
|
+
// Silently fail if feature flags aren't available yet
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function validatePaginationParams(params) {
|
|
114
|
+
const errors = [];
|
|
115
|
+
let limit = 25; // default
|
|
116
|
+
let offset = 0; // default
|
|
117
|
+
// Validate limit
|
|
118
|
+
if (params.limit !== undefined && params.limit !== null) {
|
|
119
|
+
const rawLimit = params.limit;
|
|
120
|
+
if (!Number.isInteger(rawLimit) || rawLimit <= 0) {
|
|
121
|
+
errors.push(`Invalid limit: expected positive integer, got ${typeof rawLimit} '${rawLimit}'`);
|
|
122
|
+
debugLog(`Pagination validation: Invalid limit ${rawLimit}, using default ${limit}`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
limit = Math.min(Math.max(1, rawLimit), 100); // clamp between 1-100
|
|
126
|
+
if (limit !== rawLimit) {
|
|
127
|
+
debugLog(`Pagination validation: Clamped limit from ${rawLimit} to ${limit}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Validate offset
|
|
132
|
+
if (params.offset !== undefined && params.offset !== null) {
|
|
133
|
+
const rawOffset = params.offset;
|
|
134
|
+
if (!Number.isInteger(rawOffset) || rawOffset < 0) {
|
|
135
|
+
errors.push(`Invalid offset: expected non-negative integer, got ${typeof rawOffset} '${rawOffset}'`);
|
|
136
|
+
debugLog(`Pagination validation: Invalid offset ${rawOffset}, using default ${offset}`);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
offset = rawOffset;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return { limit, offset, errors };
|
|
143
|
+
}
|
|
144
|
+
// Helper function to get or create default session
|
|
145
|
+
function ensureSession() {
|
|
146
|
+
if (!currentSessionId) {
|
|
147
|
+
const session = repositories.sessions.getLatest();
|
|
148
|
+
if (session) {
|
|
149
|
+
currentSessionId = session.id;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Create default session
|
|
153
|
+
const newSession = repositories.sessions.create({
|
|
154
|
+
name: 'Default Session',
|
|
155
|
+
description: 'Auto-created default session',
|
|
156
|
+
});
|
|
157
|
+
currentSessionId = newSession.id;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return currentSessionId;
|
|
161
|
+
}
|
|
162
|
+
// Helper to calculate file hash
|
|
163
|
+
function calculateFileHash(content) {
|
|
164
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
165
|
+
}
|
|
166
|
+
// Helper to calculate byte size of string
|
|
167
|
+
function calculateSize(value) {
|
|
168
|
+
return Buffer.byteLength(value, 'utf8');
|
|
169
|
+
}
|
|
170
|
+
// Helper to estimate token count from text
|
|
171
|
+
// Rough estimate: 1 token ≈ 4 characters
|
|
172
|
+
function estimateTokens(text) {
|
|
173
|
+
return Math.ceil(text.length / 4);
|
|
174
|
+
}
|
|
175
|
+
// Helper to calculate total response size and token estimate
|
|
176
|
+
function calculateResponseMetrics(items) {
|
|
177
|
+
let totalSize = 0;
|
|
178
|
+
for (const item of items) {
|
|
179
|
+
const itemSize = item.size || calculateSize(item.value);
|
|
180
|
+
totalSize += itemSize;
|
|
181
|
+
}
|
|
182
|
+
// Convert to JSON string to get actual response size
|
|
183
|
+
const jsonString = JSON.stringify(items);
|
|
184
|
+
const estimatedTokens = estimateTokens(jsonString);
|
|
185
|
+
const averageSize = items.length > 0 ? Math.round(totalSize / items.length) : 0;
|
|
186
|
+
return { totalSize, estimatedTokens, averageSize };
|
|
187
|
+
}
|
|
188
|
+
// Helper to parse relative time strings
|
|
189
|
+
function parseRelativeTime(relativeTime) {
|
|
190
|
+
const now = new Date();
|
|
191
|
+
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
192
|
+
if (relativeTime === 'today') {
|
|
193
|
+
return today.toISOString();
|
|
194
|
+
}
|
|
195
|
+
else if (relativeTime === 'yesterday') {
|
|
196
|
+
return new Date(today.getTime() - 24 * 60 * 60 * 1000).toISOString();
|
|
197
|
+
}
|
|
198
|
+
else if (relativeTime.match(/^(\d+) hours? ago$/)) {
|
|
199
|
+
const hours = parseInt(relativeTime.match(/^(\d+)/)[1]);
|
|
200
|
+
return new Date(now.getTime() - hours * 60 * 60 * 1000).toISOString();
|
|
201
|
+
}
|
|
202
|
+
else if (relativeTime.match(/^(\d+) days? ago$/)) {
|
|
203
|
+
const days = parseInt(relativeTime.match(/^(\d+)/)[1]);
|
|
204
|
+
return new Date(now.getTime() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
205
|
+
}
|
|
206
|
+
else if (relativeTime === 'this week') {
|
|
207
|
+
const startOfWeek = new Date(today);
|
|
208
|
+
startOfWeek.setDate(today.getDate() - today.getDay());
|
|
209
|
+
return startOfWeek.toISOString();
|
|
210
|
+
}
|
|
211
|
+
else if (relativeTime === 'last week') {
|
|
212
|
+
const startOfLastWeek = new Date(today);
|
|
213
|
+
startOfLastWeek.setDate(today.getDate() - today.getDay() - 7);
|
|
214
|
+
return startOfLastWeek.toISOString();
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
// Helper to get project directory setup message
|
|
219
|
+
function getProjectDirectorySetupMessage() {
|
|
220
|
+
return `⚠️ No project directory set for git tracking!
|
|
221
|
+
|
|
222
|
+
To enable git tracking for your project, use one of these methods:
|
|
223
|
+
|
|
224
|
+
1. For the current session:
|
|
225
|
+
context_set_project_dir({ projectDir: "/path/to/your/project" })
|
|
226
|
+
|
|
227
|
+
2. When starting a new session:
|
|
228
|
+
context_session_start({ name: "My Session", projectDir: "/path/to/your/project" })
|
|
229
|
+
|
|
230
|
+
This allows the MCP server to track git changes in your actual project directory.`;
|
|
231
|
+
}
|
|
232
|
+
// Helper to get git status for a session
|
|
233
|
+
async function getGitStatus(sessionId) {
|
|
234
|
+
// Get the current session's working directory
|
|
235
|
+
const session = sessionId
|
|
236
|
+
? repositories.sessions.getById(sessionId)
|
|
237
|
+
: repositories.sessions.getById(currentSessionId || '');
|
|
238
|
+
if (!session || !session.working_directory) {
|
|
239
|
+
return { status: 'No project directory set', branch: 'none' };
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
const git = (0, simple_git_1.simpleGit)(session.working_directory);
|
|
243
|
+
const status = await git.status();
|
|
244
|
+
const branch = await git.branch();
|
|
245
|
+
return {
|
|
246
|
+
status: JSON.stringify({
|
|
247
|
+
modified: status.modified,
|
|
248
|
+
created: status.created,
|
|
249
|
+
deleted: status.deleted,
|
|
250
|
+
staged: status.staged,
|
|
251
|
+
ahead: status.ahead,
|
|
252
|
+
behind: status.behind,
|
|
253
|
+
}),
|
|
254
|
+
branch: branch.current,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
catch (_e) {
|
|
258
|
+
return { status: 'No git repository', branch: 'none' };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// Helper to create summary
|
|
262
|
+
function createSummary(items, options) {
|
|
263
|
+
const { categories, maxLength = 1000 } = options;
|
|
264
|
+
let filteredItems = items;
|
|
265
|
+
if (categories && categories.length > 0) {
|
|
266
|
+
filteredItems = items.filter(item => categories.includes(item.category));
|
|
267
|
+
}
|
|
268
|
+
// Group by category
|
|
269
|
+
const grouped = filteredItems.reduce((acc, item) => {
|
|
270
|
+
const cat = item.category || 'uncategorized';
|
|
271
|
+
if (!acc[cat])
|
|
272
|
+
acc[cat] = [];
|
|
273
|
+
acc[cat].push(item);
|
|
274
|
+
return acc;
|
|
275
|
+
}, {});
|
|
276
|
+
// Build summary
|
|
277
|
+
let summary = '# Context Summary\n\n';
|
|
278
|
+
// High priority items first
|
|
279
|
+
const highPriorityItems = filteredItems.filter(item => item.priority === 'high');
|
|
280
|
+
if (highPriorityItems.length > 0) {
|
|
281
|
+
summary += '## High Priority Items\n';
|
|
282
|
+
highPriorityItems.forEach(item => {
|
|
283
|
+
summary += `- **${item.key}**: ${item.value.substring(0, 200)}${item.value.length > 200 ? '...' : ''}\n`;
|
|
284
|
+
});
|
|
285
|
+
summary += '\n';
|
|
286
|
+
}
|
|
287
|
+
// Then by category
|
|
288
|
+
Object.entries(grouped).forEach(([category, categoryItems]) => {
|
|
289
|
+
if (category !== 'uncategorized') {
|
|
290
|
+
summary += `## ${category.charAt(0).toUpperCase() + category.slice(1)}\n`;
|
|
291
|
+
categoryItems.forEach((item) => {
|
|
292
|
+
if (item.priority !== 'high') {
|
|
293
|
+
// Already shown above
|
|
294
|
+
summary += `- ${item.key}: ${item.value.substring(0, 100)}${item.value.length > 100 ? '...' : ''}\n`;
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
summary += '\n';
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
// Truncate if needed
|
|
301
|
+
if (summary.length > maxLength) {
|
|
302
|
+
summary = summary.substring(0, maxLength - 3) + '...';
|
|
303
|
+
}
|
|
304
|
+
return summary;
|
|
305
|
+
}
|
|
306
|
+
// Create MCP server
|
|
307
|
+
const server = new index_js_1.Server({
|
|
308
|
+
name: 'memory-keeper',
|
|
309
|
+
version: '0.10.0',
|
|
310
|
+
}, {
|
|
311
|
+
capabilities: {
|
|
312
|
+
tools: {},
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
// Main request handler
|
|
316
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
317
|
+
const toolName = request.params.name;
|
|
318
|
+
const args = request.params.arguments;
|
|
319
|
+
switch (toolName) {
|
|
320
|
+
// Session Management
|
|
321
|
+
case 'context_session_start': {
|
|
322
|
+
const { name, description, continueFrom, projectDir, defaultChannel } = args;
|
|
323
|
+
// Project directory will be saved with the session if provided
|
|
324
|
+
// Get current git branch if available
|
|
325
|
+
let branch = null;
|
|
326
|
+
let gitDetected = false;
|
|
327
|
+
try {
|
|
328
|
+
const checkPath = projectDir || process.cwd();
|
|
329
|
+
// Try to detect if directory has git
|
|
330
|
+
const gitHeadPath = path.join(checkPath, '.git', 'HEAD');
|
|
331
|
+
if (fs.existsSync(gitHeadPath)) {
|
|
332
|
+
// Use simple-git to get proper branch info
|
|
333
|
+
const tempGit = (0, simple_git_1.simpleGit)(checkPath);
|
|
334
|
+
const branchInfo = await tempGit.branch();
|
|
335
|
+
branch = branchInfo.current;
|
|
336
|
+
gitDetected = true;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch (_e) {
|
|
340
|
+
// Ignore git errors
|
|
341
|
+
}
|
|
342
|
+
// Derive default channel if not provided
|
|
343
|
+
let channel = defaultChannel;
|
|
344
|
+
if (!channel) {
|
|
345
|
+
channel = (0, channels_js_1.deriveDefaultChannel)(branch || undefined, name || undefined);
|
|
346
|
+
}
|
|
347
|
+
// Create new session using repository
|
|
348
|
+
const session = repositories.sessions.create({
|
|
349
|
+
name: name || `Session ${new Date().toISOString()}`,
|
|
350
|
+
description: description || '',
|
|
351
|
+
branch: branch || undefined,
|
|
352
|
+
working_directory: projectDir || undefined,
|
|
353
|
+
defaultChannel: channel,
|
|
354
|
+
});
|
|
355
|
+
// Copy context from previous session if specified
|
|
356
|
+
if (continueFrom) {
|
|
357
|
+
repositories.contexts.copyBetweenSessions(continueFrom, session.id);
|
|
358
|
+
}
|
|
359
|
+
currentSessionId = session.id;
|
|
360
|
+
let statusMessage = `Started new session: ${session.id}\nName: ${name || 'Unnamed'}\nChannel: ${channel}`;
|
|
361
|
+
if (projectDir) {
|
|
362
|
+
statusMessage += `\nProject directory: ${projectDir}`;
|
|
363
|
+
if (gitDetected) {
|
|
364
|
+
statusMessage += `\nGit branch: ${branch || 'unknown'}`;
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
statusMessage += `\nGit: No repository found in project directory`;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
statusMessage += `\nGit branch: ${branch || 'unknown'}`;
|
|
372
|
+
// Provide helpful guidance about setting project directory
|
|
373
|
+
const cwdHasGit = fs.existsSync(path.join(process.cwd(), '.git'));
|
|
374
|
+
if (cwdHasGit) {
|
|
375
|
+
statusMessage += `\n\n💡 Tip: Your current directory has a git repository. To enable full git tracking, start a session with:\ncontext_session_start({ name: "${name || 'My Session'}", projectDir: "${process.cwd()}" })`;
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
// Check for git repos in immediate subdirectories
|
|
379
|
+
const subdirs = fs
|
|
380
|
+
.readdirSync(process.cwd(), { withFileTypes: true })
|
|
381
|
+
.filter(dirent => dirent.isDirectory())
|
|
382
|
+
.map(dirent => dirent.name)
|
|
383
|
+
.filter(name => !name.startsWith('.'));
|
|
384
|
+
const gitSubdirs = subdirs.filter(dir => {
|
|
385
|
+
try {
|
|
386
|
+
return fs.existsSync(path.join(process.cwd(), dir, '.git'));
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
if (gitSubdirs.length > 0) {
|
|
393
|
+
statusMessage += `\n\n💡 Found git repositories in: ${gitSubdirs.join(', ')}`;
|
|
394
|
+
statusMessage += `\nTo enable git tracking, start a session with your project directory:`;
|
|
395
|
+
statusMessage += `\ncontext_session_start({ name: "${name || 'My Session'}", projectDir: "${path.join(process.cwd(), gitSubdirs[0])}" })`;
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
statusMessage += `\n\n💡 To enable git tracking, start a session with your project directory:`;
|
|
399
|
+
statusMessage += `\ncontext_session_start({ name: "${name || 'My Session'}", projectDir: "/path/to/your/project" })`;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
content: [
|
|
405
|
+
{
|
|
406
|
+
type: 'text',
|
|
407
|
+
text: statusMessage,
|
|
408
|
+
},
|
|
409
|
+
],
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
case 'context_set_project_dir': {
|
|
413
|
+
const { projectDir } = args;
|
|
414
|
+
const sessionId = ensureSession();
|
|
415
|
+
if (!projectDir) {
|
|
416
|
+
throw new Error('Project directory path is required');
|
|
417
|
+
}
|
|
418
|
+
// Verify the directory exists
|
|
419
|
+
if (!fs.existsSync(projectDir)) {
|
|
420
|
+
return {
|
|
421
|
+
content: [
|
|
422
|
+
{
|
|
423
|
+
type: 'text',
|
|
424
|
+
text: `Error: Directory not found: ${projectDir}`,
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
// Update the current session's working directory
|
|
430
|
+
repositories.sessions.update(sessionId, { working_directory: projectDir });
|
|
431
|
+
// Try to get git info to verify it's a git repo
|
|
432
|
+
let gitInfo = 'No git repository found';
|
|
433
|
+
try {
|
|
434
|
+
const git = (0, simple_git_1.simpleGit)(projectDir);
|
|
435
|
+
const branchInfo = await git.branch();
|
|
436
|
+
const status = await git.status();
|
|
437
|
+
gitInfo = `Git repository detected\nBranch: ${branchInfo.current}\nStatus: ${status.modified.length} modified, ${status.created.length} new, ${status.deleted.length} deleted`;
|
|
438
|
+
}
|
|
439
|
+
catch (_e) {
|
|
440
|
+
// Not a git repo, that's okay
|
|
441
|
+
}
|
|
442
|
+
return {
|
|
443
|
+
content: [
|
|
444
|
+
{
|
|
445
|
+
type: 'text',
|
|
446
|
+
text: `Project directory set for session ${sessionId.substring(0, 8)}: ${projectDir}\n\n${gitInfo}`,
|
|
447
|
+
},
|
|
448
|
+
],
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
case 'context_session_list': {
|
|
452
|
+
const { limit = 10 } = args;
|
|
453
|
+
const sessions = db
|
|
454
|
+
.prepare(`
|
|
455
|
+
SELECT id, name, description, branch, created_at,
|
|
456
|
+
(SELECT COUNT(*) FROM context_items WHERE session_id = sessions.id) as item_count
|
|
457
|
+
FROM sessions
|
|
458
|
+
ORDER BY created_at DESC
|
|
459
|
+
LIMIT ?
|
|
460
|
+
`)
|
|
461
|
+
.all(limit);
|
|
462
|
+
const sessionList = sessions
|
|
463
|
+
.map((s) => `• ${s.name} (${s.id.substring(0, 8)})\n Created: ${s.created_at}\n Items: ${s.item_count}\n Branch: ${s.branch || 'unknown'}`)
|
|
464
|
+
.join('\n\n');
|
|
465
|
+
return {
|
|
466
|
+
content: [
|
|
467
|
+
{
|
|
468
|
+
type: 'text',
|
|
469
|
+
text: `Recent sessions:\n\n${sessionList}`,
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
// Enhanced Context Storage
|
|
475
|
+
case 'context_save': {
|
|
476
|
+
const { key, value, category, priority = 'normal', private: isPrivate = false, channel, } = args;
|
|
477
|
+
try {
|
|
478
|
+
const sessionId = ensureSession();
|
|
479
|
+
// Verify session exists before saving context
|
|
480
|
+
const session = repositories.sessions.getById(sessionId);
|
|
481
|
+
if (!session) {
|
|
482
|
+
// Session was deleted or corrupted, create a new one
|
|
483
|
+
console.warn(`Session ${sessionId} not found, creating new session`);
|
|
484
|
+
const newSession = repositories.sessions.create({
|
|
485
|
+
name: 'Recovery Session',
|
|
486
|
+
description: 'Auto-created after session corruption',
|
|
487
|
+
});
|
|
488
|
+
currentSessionId = newSession.id;
|
|
489
|
+
const _contextItem = repositories.contexts.save(newSession.id, {
|
|
490
|
+
key,
|
|
491
|
+
value,
|
|
492
|
+
category,
|
|
493
|
+
priority: priority,
|
|
494
|
+
isPrivate,
|
|
495
|
+
channel,
|
|
496
|
+
});
|
|
497
|
+
return {
|
|
498
|
+
content: [
|
|
499
|
+
{
|
|
500
|
+
type: 'text',
|
|
501
|
+
text: `Saved: ${key}\nCategory: ${category || 'none'}\nPriority: ${priority}\nSession: ${newSession.id.substring(0, 8)} (recovered)`,
|
|
502
|
+
},
|
|
503
|
+
],
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
const contextItem = repositories.contexts.save(sessionId, {
|
|
507
|
+
key,
|
|
508
|
+
value,
|
|
509
|
+
category,
|
|
510
|
+
priority: priority,
|
|
511
|
+
isPrivate,
|
|
512
|
+
channel,
|
|
513
|
+
});
|
|
514
|
+
// Create embedding for semantic search
|
|
515
|
+
try {
|
|
516
|
+
const content = `${key}: ${value}`;
|
|
517
|
+
const metadata = { key, category, priority };
|
|
518
|
+
await vectorStore.storeDocument(contextItem.id, content, metadata);
|
|
519
|
+
}
|
|
520
|
+
catch (error) {
|
|
521
|
+
// Log but don't fail the save operation
|
|
522
|
+
console.error('Failed to create embedding:', error);
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
content: [
|
|
526
|
+
{
|
|
527
|
+
type: 'text',
|
|
528
|
+
text: `Saved: ${key}\nCategory: ${category || 'none'}\nPriority: ${priority}\nChannel: ${contextItem.channel || 'general'}\nSession: ${sessionId.substring(0, 8)}`,
|
|
529
|
+
},
|
|
530
|
+
],
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
console.error('Context save error:', error);
|
|
535
|
+
// If it's a foreign key constraint error, try recovery
|
|
536
|
+
if (error.message?.includes('FOREIGN KEY constraint failed')) {
|
|
537
|
+
try {
|
|
538
|
+
console.warn('Foreign key constraint failed, attempting recovery...');
|
|
539
|
+
const newSession = repositories.sessions.create({
|
|
540
|
+
name: 'Emergency Recovery Session',
|
|
541
|
+
description: 'Created due to foreign key constraint failure',
|
|
542
|
+
});
|
|
543
|
+
currentSessionId = newSession.id;
|
|
544
|
+
const _contextItem = repositories.contexts.save(newSession.id, {
|
|
545
|
+
key,
|
|
546
|
+
value,
|
|
547
|
+
category,
|
|
548
|
+
priority: priority,
|
|
549
|
+
isPrivate,
|
|
550
|
+
channel,
|
|
551
|
+
});
|
|
552
|
+
return {
|
|
553
|
+
content: [
|
|
554
|
+
{
|
|
555
|
+
type: 'text',
|
|
556
|
+
text: `Saved: ${key}\nCategory: ${category || 'none'}\nPriority: ${priority}\nSession: ${newSession.id.substring(0, 8)} (emergency recovery)`,
|
|
557
|
+
},
|
|
558
|
+
],
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
catch (recoveryError) {
|
|
562
|
+
return {
|
|
563
|
+
content: [
|
|
564
|
+
{
|
|
565
|
+
type: 'text',
|
|
566
|
+
text: `Failed to save context item: ${recoveryError.message}`,
|
|
567
|
+
},
|
|
568
|
+
],
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
content: [
|
|
574
|
+
{
|
|
575
|
+
type: 'text',
|
|
576
|
+
text: `Failed to save context item: ${error.message}`,
|
|
577
|
+
},
|
|
578
|
+
],
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
case 'context_get': {
|
|
583
|
+
const { key, category, channel, channels, sessionId: specificSessionId, includeMetadata, sort, limit: rawLimit, offset: rawOffset, createdAfter, createdBefore, keyPattern, priorities, } = args;
|
|
584
|
+
const targetSessionId = specificSessionId || currentSessionId || ensureSession();
|
|
585
|
+
// Validate pagination parameters with proper defaults
|
|
586
|
+
// Default limit for context_get should be 100 to match repository defaults
|
|
587
|
+
const paginationValidation = validatePaginationParams({
|
|
588
|
+
limit: rawLimit !== undefined ? rawLimit : 100,
|
|
589
|
+
offset: rawOffset,
|
|
590
|
+
});
|
|
591
|
+
const { limit, offset, errors: paginationErrors } = paginationValidation;
|
|
592
|
+
// Log pagination validation errors for debugging
|
|
593
|
+
if (paginationErrors.length > 0) {
|
|
594
|
+
debugLog('context_get pagination validation errors:', paginationErrors);
|
|
595
|
+
}
|
|
596
|
+
// Always use enhanced query to ensure consistent pagination
|
|
597
|
+
// This prevents token limit issues when querying large datasets
|
|
598
|
+
// Removed the conditional check since we always want to use this path
|
|
599
|
+
{
|
|
600
|
+
const result = repositories.contexts.queryEnhanced({
|
|
601
|
+
sessionId: targetSessionId,
|
|
602
|
+
key,
|
|
603
|
+
category,
|
|
604
|
+
channel,
|
|
605
|
+
channels,
|
|
606
|
+
sort,
|
|
607
|
+
limit,
|
|
608
|
+
offset,
|
|
609
|
+
createdAfter,
|
|
610
|
+
createdBefore,
|
|
611
|
+
keyPattern,
|
|
612
|
+
priorities,
|
|
613
|
+
includeMetadata,
|
|
614
|
+
});
|
|
615
|
+
if (result.items.length === 0) {
|
|
616
|
+
return {
|
|
617
|
+
content: [
|
|
618
|
+
{
|
|
619
|
+
type: 'text',
|
|
620
|
+
text: 'No matching context found',
|
|
621
|
+
},
|
|
622
|
+
],
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
// Calculate response metrics
|
|
626
|
+
const metrics = calculateResponseMetrics(result.items);
|
|
627
|
+
const TOKEN_LIMIT = 20000; // Conservative limit to stay well under MCP's 25k limit
|
|
628
|
+
// Check if we're approaching token limits
|
|
629
|
+
const isApproachingLimit = metrics.estimatedTokens > TOKEN_LIMIT;
|
|
630
|
+
// Calculate pagination metadata
|
|
631
|
+
// Use the validated limit and offset from paginationValidation
|
|
632
|
+
const effectiveLimit = limit; // Already validated and defaulted
|
|
633
|
+
const effectiveOffset = offset; // Already validated and defaulted
|
|
634
|
+
const currentPage = effectiveLimit > 0 ? Math.floor(effectiveOffset / effectiveLimit) + 1 : 1;
|
|
635
|
+
const totalPages = effectiveLimit > 0 ? Math.ceil(result.totalCount / effectiveLimit) : 1;
|
|
636
|
+
const hasNextPage = currentPage < totalPages;
|
|
637
|
+
const hasPreviousPage = currentPage > 1;
|
|
638
|
+
// Track whether defaults were applied
|
|
639
|
+
const defaultsApplied = {
|
|
640
|
+
limit: rawLimit === undefined,
|
|
641
|
+
sort: sort === undefined,
|
|
642
|
+
};
|
|
643
|
+
// Enhanced response format
|
|
644
|
+
if (includeMetadata) {
|
|
645
|
+
const itemsWithMetadata = result.items.map(item => ({
|
|
646
|
+
key: item.key,
|
|
647
|
+
value: item.value,
|
|
648
|
+
category: item.category,
|
|
649
|
+
priority: item.priority,
|
|
650
|
+
channel: item.channel,
|
|
651
|
+
metadata: item.metadata ? JSON.parse(item.metadata) : null,
|
|
652
|
+
size: item.size || calculateSize(item.value),
|
|
653
|
+
created_at: item.created_at,
|
|
654
|
+
updated_at: item.updated_at,
|
|
655
|
+
}));
|
|
656
|
+
const response = {
|
|
657
|
+
items: itemsWithMetadata,
|
|
658
|
+
pagination: {
|
|
659
|
+
total: result.totalCount,
|
|
660
|
+
returned: result.items.length,
|
|
661
|
+
offset: effectiveOffset,
|
|
662
|
+
hasMore: hasNextPage,
|
|
663
|
+
nextOffset: hasNextPage ? effectiveOffset + effectiveLimit : null,
|
|
664
|
+
// Extended pagination metadata
|
|
665
|
+
totalCount: result.totalCount,
|
|
666
|
+
page: currentPage,
|
|
667
|
+
pageSize: effectiveLimit,
|
|
668
|
+
totalPages: totalPages,
|
|
669
|
+
hasNextPage: hasNextPage,
|
|
670
|
+
hasPreviousPage: hasPreviousPage,
|
|
671
|
+
previousOffset: hasPreviousPage
|
|
672
|
+
? Math.max(0, effectiveOffset - effectiveLimit)
|
|
673
|
+
: null,
|
|
674
|
+
// Size information
|
|
675
|
+
totalSize: metrics.totalSize,
|
|
676
|
+
averageSize: metrics.averageSize,
|
|
677
|
+
// Defaults applied
|
|
678
|
+
defaultsApplied: defaultsApplied,
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
// Add warning if approaching token limits
|
|
682
|
+
if (isApproachingLimit) {
|
|
683
|
+
response.pagination.warning =
|
|
684
|
+
'Large result set. Consider using smaller limit or more specific filters.';
|
|
685
|
+
}
|
|
686
|
+
return {
|
|
687
|
+
content: [
|
|
688
|
+
{
|
|
689
|
+
type: 'text',
|
|
690
|
+
text: JSON.stringify(response, null, 2),
|
|
691
|
+
},
|
|
692
|
+
],
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
// Return enhanced format for all queries to support pagination
|
|
696
|
+
const response = {
|
|
697
|
+
items: result.items,
|
|
698
|
+
pagination: {
|
|
699
|
+
total: result.totalCount,
|
|
700
|
+
returned: result.items.length,
|
|
701
|
+
offset: effectiveOffset,
|
|
702
|
+
hasMore: hasNextPage,
|
|
703
|
+
nextOffset: hasNextPage ? effectiveOffset + effectiveLimit : null,
|
|
704
|
+
},
|
|
705
|
+
};
|
|
706
|
+
// Add warning if approaching token limits
|
|
707
|
+
if (isApproachingLimit) {
|
|
708
|
+
response.pagination.warning =
|
|
709
|
+
'Large result set. Consider using smaller limit or more specific filters.';
|
|
710
|
+
}
|
|
711
|
+
return {
|
|
712
|
+
content: [
|
|
713
|
+
{
|
|
714
|
+
type: 'text',
|
|
715
|
+
text: JSON.stringify(response, null, 2),
|
|
716
|
+
},
|
|
717
|
+
],
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
// File Caching
|
|
722
|
+
case 'context_cache_file': {
|
|
723
|
+
const { filePath, content } = args;
|
|
724
|
+
const sessionId = ensureSession();
|
|
725
|
+
const hash = calculateFileHash(content);
|
|
726
|
+
const stmt = db.prepare(`
|
|
727
|
+
INSERT OR REPLACE INTO file_cache (id, session_id, file_path, content, hash)
|
|
728
|
+
VALUES (?, ?, ?, ?, ?)
|
|
729
|
+
`);
|
|
730
|
+
stmt.run((0, uuid_1.v4)(), sessionId, filePath, content, hash);
|
|
731
|
+
return {
|
|
732
|
+
content: [
|
|
733
|
+
{
|
|
734
|
+
type: 'text',
|
|
735
|
+
text: `Cached file: ${filePath}\nHash: ${hash.substring(0, 16)}...\nSize: ${content.length} bytes`,
|
|
736
|
+
},
|
|
737
|
+
],
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
case 'context_file_changed': {
|
|
741
|
+
const { filePath, currentContent } = args;
|
|
742
|
+
const sessionId = ensureSession();
|
|
743
|
+
const cached = db
|
|
744
|
+
.prepare('SELECT hash, content FROM file_cache WHERE session_id = ? AND file_path = ?')
|
|
745
|
+
.get(sessionId, filePath);
|
|
746
|
+
if (!cached) {
|
|
747
|
+
return {
|
|
748
|
+
content: [
|
|
749
|
+
{
|
|
750
|
+
type: 'text',
|
|
751
|
+
text: `No cached version found for: ${filePath}`,
|
|
752
|
+
},
|
|
753
|
+
],
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
const currentHash = currentContent ? calculateFileHash(currentContent) : null;
|
|
757
|
+
const hasChanged = currentHash !== cached.hash;
|
|
758
|
+
return {
|
|
759
|
+
content: [
|
|
760
|
+
{
|
|
761
|
+
type: 'text',
|
|
762
|
+
text: `File: ${filePath}\nChanged: ${hasChanged}\nCached hash: ${cached.hash.substring(0, 16)}...\nCurrent hash: ${currentHash ? currentHash.substring(0, 16) + '...' : 'N/A'}`,
|
|
763
|
+
},
|
|
764
|
+
],
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
case 'context_status': {
|
|
768
|
+
const sessionId = currentSessionId || ensureSession();
|
|
769
|
+
const stats = db
|
|
770
|
+
.prepare(`
|
|
771
|
+
SELECT
|
|
772
|
+
(SELECT COUNT(*) FROM context_items WHERE session_id = ?) as item_count,
|
|
773
|
+
(SELECT COUNT(*) FROM file_cache WHERE session_id = ?) as file_count,
|
|
774
|
+
(SELECT created_at FROM sessions WHERE id = ?) as session_created,
|
|
775
|
+
(SELECT name FROM sessions WHERE id = ?) as session_name
|
|
776
|
+
`)
|
|
777
|
+
.get(sessionId, sessionId, sessionId, sessionId);
|
|
778
|
+
const recentItems = db
|
|
779
|
+
.prepare(`
|
|
780
|
+
SELECT key, category, priority FROM context_items
|
|
781
|
+
WHERE session_id = ?
|
|
782
|
+
ORDER BY created_at DESC
|
|
783
|
+
LIMIT 5
|
|
784
|
+
`)
|
|
785
|
+
.all(sessionId);
|
|
786
|
+
const recentList = recentItems
|
|
787
|
+
.map((item) => ` • [${item.priority}] ${item.key} (${item.category || 'uncategorized'})`)
|
|
788
|
+
.join('\n');
|
|
789
|
+
return {
|
|
790
|
+
content: [
|
|
791
|
+
{
|
|
792
|
+
type: 'text',
|
|
793
|
+
text: `Current Session: ${stats.session_name}
|
|
794
|
+
Session ID: ${sessionId.substring(0, 8)}
|
|
795
|
+
Created: ${stats.session_created}
|
|
796
|
+
Context Items: ${stats.item_count}
|
|
797
|
+
Cached Files: ${stats.file_count}
|
|
798
|
+
|
|
799
|
+
Recent Items:
|
|
800
|
+
${recentList || ' None'}`,
|
|
801
|
+
},
|
|
802
|
+
],
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
// Phase 2: Checkpoint System
|
|
806
|
+
case 'context_checkpoint': {
|
|
807
|
+
const { name, description, includeFiles = true, includeGitStatus = true } = args;
|
|
808
|
+
const sessionId = ensureSession();
|
|
809
|
+
const checkpointId = (0, uuid_1.v4)();
|
|
810
|
+
// Get git status if requested
|
|
811
|
+
let gitStatus = null;
|
|
812
|
+
let gitBranch = null;
|
|
813
|
+
if (includeGitStatus) {
|
|
814
|
+
const gitInfo = await getGitStatus();
|
|
815
|
+
gitStatus = gitInfo.status;
|
|
816
|
+
gitBranch = gitInfo.branch;
|
|
817
|
+
}
|
|
818
|
+
// Create checkpoint
|
|
819
|
+
db.prepare(`
|
|
820
|
+
INSERT INTO checkpoints (id, session_id, name, description, git_status, git_branch)
|
|
821
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
822
|
+
`).run(checkpointId, sessionId, name, description || '', gitStatus, gitBranch);
|
|
823
|
+
// Save context items
|
|
824
|
+
const contextItems = db
|
|
825
|
+
.prepare('SELECT id FROM context_items WHERE session_id = ?')
|
|
826
|
+
.all(sessionId);
|
|
827
|
+
const itemStmt = db.prepare('INSERT INTO checkpoint_items (id, checkpoint_id, context_item_id) VALUES (?, ?, ?)');
|
|
828
|
+
for (const item of contextItems) {
|
|
829
|
+
itemStmt.run((0, uuid_1.v4)(), checkpointId, item.id);
|
|
830
|
+
}
|
|
831
|
+
// Save file cache if requested
|
|
832
|
+
let fileCount = 0;
|
|
833
|
+
if (includeFiles) {
|
|
834
|
+
const files = db.prepare('SELECT id FROM file_cache WHERE session_id = ?').all(sessionId);
|
|
835
|
+
const fileStmt = db.prepare('INSERT INTO checkpoint_files (id, checkpoint_id, file_cache_id) VALUES (?, ?, ?)');
|
|
836
|
+
for (const file of files) {
|
|
837
|
+
fileStmt.run((0, uuid_1.v4)(), checkpointId, file.id);
|
|
838
|
+
fileCount++;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
let statusText = `Created checkpoint: ${name}
|
|
842
|
+
ID: ${checkpointId.substring(0, 8)}
|
|
843
|
+
Context items: ${contextItems.length}
|
|
844
|
+
Cached files: ${fileCount}
|
|
845
|
+
Git branch: ${gitBranch || 'none'}
|
|
846
|
+
Git status: ${gitStatus ? 'captured' : 'not captured'}`;
|
|
847
|
+
// Add helpful message if git status was requested but no project directory is set
|
|
848
|
+
const currentSession = repositories.sessions.getById(sessionId);
|
|
849
|
+
if (includeGitStatus && (!currentSession || !currentSession.working_directory)) {
|
|
850
|
+
statusText += `\n\n💡 Note: Git status was requested but no project directory is set.
|
|
851
|
+
To enable git tracking, use context_set_project_dir with your project path.`;
|
|
852
|
+
}
|
|
853
|
+
return {
|
|
854
|
+
content: [
|
|
855
|
+
{
|
|
856
|
+
type: 'text',
|
|
857
|
+
text: statusText,
|
|
858
|
+
},
|
|
859
|
+
],
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
case 'context_restore_checkpoint': {
|
|
863
|
+
const { name, checkpointId, restoreFiles = true } = args;
|
|
864
|
+
// Find checkpoint
|
|
865
|
+
let checkpoint;
|
|
866
|
+
if (checkpointId) {
|
|
867
|
+
checkpoint = db.prepare('SELECT * FROM checkpoints WHERE id = ?').get(checkpointId);
|
|
868
|
+
}
|
|
869
|
+
else if (name) {
|
|
870
|
+
checkpoint = db
|
|
871
|
+
.prepare('SELECT * FROM checkpoints ORDER BY created_at DESC')
|
|
872
|
+
.all()
|
|
873
|
+
.find((cp) => cp.name === name);
|
|
874
|
+
}
|
|
875
|
+
else {
|
|
876
|
+
// Get latest checkpoint
|
|
877
|
+
checkpoint = db.prepare('SELECT * FROM checkpoints ORDER BY created_at DESC LIMIT 1').get();
|
|
878
|
+
}
|
|
879
|
+
if (!checkpoint) {
|
|
880
|
+
return {
|
|
881
|
+
content: [
|
|
882
|
+
{
|
|
883
|
+
type: 'text',
|
|
884
|
+
text: 'No checkpoint found',
|
|
885
|
+
},
|
|
886
|
+
],
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
const cp = checkpoint;
|
|
890
|
+
// Start new session from checkpoint
|
|
891
|
+
const newSessionId = (0, uuid_1.v4)();
|
|
892
|
+
db.prepare(`
|
|
893
|
+
INSERT INTO sessions (id, name, description, branch, working_directory)
|
|
894
|
+
VALUES (?, ?, ?, ?, ?)
|
|
895
|
+
`).run(newSessionId, `Restored from: ${cp.name}`, `Checkpoint ${cp.id.substring(0, 8)} created at ${cp.created_at}`, cp.git_branch, null);
|
|
896
|
+
// Restore context items
|
|
897
|
+
const contextItems = db
|
|
898
|
+
.prepare(`
|
|
899
|
+
SELECT ci.* FROM context_items ci
|
|
900
|
+
JOIN checkpoint_items cpi ON ci.id = cpi.context_item_id
|
|
901
|
+
WHERE cpi.checkpoint_id = ?
|
|
902
|
+
`)
|
|
903
|
+
.all(cp.id);
|
|
904
|
+
const itemStmt = db.prepare(`
|
|
905
|
+
INSERT INTO context_items (id, session_id, key, value, category, priority, size, created_at, updated_at)
|
|
906
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
907
|
+
`);
|
|
908
|
+
for (const item of contextItems) {
|
|
909
|
+
const itemData = item;
|
|
910
|
+
itemStmt.run((0, uuid_1.v4)(), newSessionId, itemData.key, itemData.value, itemData.category, itemData.priority, itemData.size || calculateSize(itemData.value), itemData.created_at);
|
|
911
|
+
}
|
|
912
|
+
// Restore file cache if requested
|
|
913
|
+
let fileCount = 0;
|
|
914
|
+
if (restoreFiles) {
|
|
915
|
+
const files = db
|
|
916
|
+
.prepare(`
|
|
917
|
+
SELECT fc.* FROM file_cache fc
|
|
918
|
+
JOIN checkpoint_files cpf ON fc.id = cpf.file_cache_id
|
|
919
|
+
WHERE cpf.checkpoint_id = ?
|
|
920
|
+
`)
|
|
921
|
+
.all(cp.id);
|
|
922
|
+
const fileStmt = db.prepare(`
|
|
923
|
+
INSERT INTO file_cache (id, session_id, file_path, content, hash, last_read)
|
|
924
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
925
|
+
`);
|
|
926
|
+
for (const file of files) {
|
|
927
|
+
fileStmt.run((0, uuid_1.v4)(), newSessionId, file.file_path, file.content, file.hash, file.last_read);
|
|
928
|
+
fileCount++;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
currentSessionId = newSessionId;
|
|
932
|
+
// Get session information for enhanced messaging
|
|
933
|
+
const sessionCount = db.prepare('SELECT COUNT(*) as count FROM sessions').get();
|
|
934
|
+
const originalSession = db
|
|
935
|
+
.prepare('SELECT name FROM sessions WHERE id = ?')
|
|
936
|
+
.get(cp.session_id);
|
|
937
|
+
return {
|
|
938
|
+
content: [
|
|
939
|
+
{
|
|
940
|
+
type: 'text',
|
|
941
|
+
text: `✅ Successfully restored from checkpoint: ${cp.name}
|
|
942
|
+
|
|
943
|
+
🔄 Data Safety: A new session was created to preserve your current work
|
|
944
|
+
📋 New Session: ${newSessionId.substring(0, 8)} ("${`Restored from: ${cp.name}`}")
|
|
945
|
+
🔙 Original Session: ${originalSession?.name || 'Unknown'} remains accessible
|
|
946
|
+
|
|
947
|
+
📊 Restored Data:
|
|
948
|
+
- Context items: ${contextItems.length}
|
|
949
|
+
- Files: ${fileCount}
|
|
950
|
+
- Git branch: ${cp.git_branch || 'none'}
|
|
951
|
+
- Checkpoint created: ${cp.created_at}
|
|
952
|
+
|
|
953
|
+
💡 Next Steps:
|
|
954
|
+
- You are now working in the restored session
|
|
955
|
+
- Your previous work is safely preserved in session ${sessionCount.count - 1}
|
|
956
|
+
- Use context_session_list to see all sessions
|
|
957
|
+
- Switch sessions anytime without losing data
|
|
958
|
+
|
|
959
|
+
🆘 Need your previous work? Use context_search_all to find items across sessions`,
|
|
960
|
+
},
|
|
961
|
+
],
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
// Phase 2: Summarization
|
|
965
|
+
case 'context_summarize': {
|
|
966
|
+
const { sessionId: specificSessionId, categories, maxLength } = args;
|
|
967
|
+
const targetSessionId = specificSessionId || currentSessionId || ensureSession();
|
|
968
|
+
const items = db
|
|
969
|
+
.prepare(`
|
|
970
|
+
SELECT * FROM context_items
|
|
971
|
+
WHERE session_id = ?
|
|
972
|
+
ORDER BY priority DESC, created_at DESC
|
|
973
|
+
`)
|
|
974
|
+
.all(targetSessionId);
|
|
975
|
+
const summary = createSummary(items, { categories, maxLength });
|
|
976
|
+
return {
|
|
977
|
+
content: [
|
|
978
|
+
{
|
|
979
|
+
type: 'text',
|
|
980
|
+
text: summary,
|
|
981
|
+
},
|
|
982
|
+
],
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
// Phase 3: Smart Compaction Helper
|
|
986
|
+
case 'context_prepare_compaction': {
|
|
987
|
+
const sessionId = ensureSession();
|
|
988
|
+
// Get all high priority items
|
|
989
|
+
const highPriorityItems = db
|
|
990
|
+
.prepare(`
|
|
991
|
+
SELECT * FROM context_items
|
|
992
|
+
WHERE session_id = ? AND priority = 'high'
|
|
993
|
+
ORDER BY created_at DESC
|
|
994
|
+
`)
|
|
995
|
+
.all(sessionId);
|
|
996
|
+
// Get recent tasks
|
|
997
|
+
const recentTasks = db
|
|
998
|
+
.prepare(`
|
|
999
|
+
SELECT * FROM context_items
|
|
1000
|
+
WHERE session_id = ? AND category = 'task'
|
|
1001
|
+
ORDER BY created_at DESC LIMIT 10
|
|
1002
|
+
`)
|
|
1003
|
+
.all(sessionId);
|
|
1004
|
+
// Get all decisions
|
|
1005
|
+
const decisions = db
|
|
1006
|
+
.prepare(`
|
|
1007
|
+
SELECT * FROM context_items
|
|
1008
|
+
WHERE session_id = ? AND category = 'decision'
|
|
1009
|
+
ORDER BY created_at DESC
|
|
1010
|
+
`)
|
|
1011
|
+
.all(sessionId);
|
|
1012
|
+
// Get files that changed
|
|
1013
|
+
const changedFiles = db
|
|
1014
|
+
.prepare(`
|
|
1015
|
+
SELECT file_path, hash FROM file_cache
|
|
1016
|
+
WHERE session_id = ?
|
|
1017
|
+
`)
|
|
1018
|
+
.all(sessionId);
|
|
1019
|
+
// Auto-create checkpoint
|
|
1020
|
+
const checkpointId = (0, uuid_1.v4)();
|
|
1021
|
+
const checkpointName = `auto-compaction-${new Date().toISOString()}`;
|
|
1022
|
+
const gitInfo = await getGitStatus();
|
|
1023
|
+
db.prepare(`
|
|
1024
|
+
INSERT INTO checkpoints (id, session_id, name, description, git_status, git_branch)
|
|
1025
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1026
|
+
`).run(checkpointId, sessionId, checkpointName, 'Automatic checkpoint before compaction', gitInfo.status, gitInfo.branch);
|
|
1027
|
+
// Save all context items to checkpoint
|
|
1028
|
+
const allItems = db
|
|
1029
|
+
.prepare('SELECT id FROM context_items WHERE session_id = ?')
|
|
1030
|
+
.all(sessionId);
|
|
1031
|
+
const itemStmt = db.prepare('INSERT INTO checkpoint_items (id, checkpoint_id, context_item_id) VALUES (?, ?, ?)');
|
|
1032
|
+
for (const item of allItems) {
|
|
1033
|
+
itemStmt.run((0, uuid_1.v4)(), checkpointId, item.id);
|
|
1034
|
+
}
|
|
1035
|
+
// Generate summary for next session
|
|
1036
|
+
const summary = createSummary([...highPriorityItems, ...recentTasks, ...decisions], {
|
|
1037
|
+
maxLength: 2000,
|
|
1038
|
+
});
|
|
1039
|
+
// Determine next steps
|
|
1040
|
+
const nextSteps = [];
|
|
1041
|
+
const unfinishedTasks = recentTasks.filter((t) => !t.value.toLowerCase().includes('completed') && !t.value.toLowerCase().includes('done'));
|
|
1042
|
+
unfinishedTasks.forEach((task) => {
|
|
1043
|
+
nextSteps.push(`Continue: ${task.key}`);
|
|
1044
|
+
});
|
|
1045
|
+
// Save prepared context
|
|
1046
|
+
const preparedContext = {
|
|
1047
|
+
checkpoint: checkpointName,
|
|
1048
|
+
summary,
|
|
1049
|
+
nextSteps,
|
|
1050
|
+
criticalItems: highPriorityItems.map((i) => ({ key: i.key, value: i.value })),
|
|
1051
|
+
decisions: decisions.map((d) => ({ key: d.key, value: d.value })),
|
|
1052
|
+
filesModified: changedFiles.length,
|
|
1053
|
+
gitBranch: gitInfo.branch,
|
|
1054
|
+
};
|
|
1055
|
+
// Save as special context item
|
|
1056
|
+
const preparedValue = JSON.stringify(preparedContext);
|
|
1057
|
+
db.prepare(`
|
|
1058
|
+
INSERT OR REPLACE INTO context_items (id, session_id, key, value, category, priority, size, updated_at)
|
|
1059
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
1060
|
+
`).run((0, uuid_1.v4)(), sessionId, '_prepared_compaction', preparedValue, 'system', 'high', calculateSize(preparedValue));
|
|
1061
|
+
return {
|
|
1062
|
+
content: [
|
|
1063
|
+
{
|
|
1064
|
+
type: 'text',
|
|
1065
|
+
text: `Prepared for compaction:
|
|
1066
|
+
|
|
1067
|
+
Checkpoint: ${checkpointName}
|
|
1068
|
+
Critical items saved: ${highPriorityItems.length}
|
|
1069
|
+
Decisions preserved: ${decisions.length}
|
|
1070
|
+
Next steps identified: ${nextSteps.length}
|
|
1071
|
+
Files tracked: ${changedFiles.length}
|
|
1072
|
+
|
|
1073
|
+
Summary:
|
|
1074
|
+
${summary.substring(0, 500)}${summary.length > 500 ? '...' : ''}
|
|
1075
|
+
|
|
1076
|
+
Next Steps:
|
|
1077
|
+
${nextSteps.join('\n')}
|
|
1078
|
+
|
|
1079
|
+
To restore after compaction:
|
|
1080
|
+
mcp_context_restore_checkpoint({ name: "${checkpointName}" })`,
|
|
1081
|
+
},
|
|
1082
|
+
],
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
// Phase 3: Git Integration
|
|
1086
|
+
case 'context_git_commit': {
|
|
1087
|
+
const { message, autoSave = true } = args;
|
|
1088
|
+
const sessionId = ensureSession();
|
|
1089
|
+
// Check if project directory is set for this session
|
|
1090
|
+
const session = repositories.sessions.getById(sessionId);
|
|
1091
|
+
if (!session || !session.working_directory) {
|
|
1092
|
+
return {
|
|
1093
|
+
content: [
|
|
1094
|
+
{
|
|
1095
|
+
type: 'text',
|
|
1096
|
+
text: getProjectDirectorySetupMessage(),
|
|
1097
|
+
},
|
|
1098
|
+
],
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
if (autoSave) {
|
|
1102
|
+
// Save current context state
|
|
1103
|
+
const timestamp = new Date().toISOString();
|
|
1104
|
+
const commitValue = message || 'No commit message';
|
|
1105
|
+
db.prepare(`
|
|
1106
|
+
INSERT OR REPLACE INTO context_items (id, session_id, key, value, category, priority, size, updated_at)
|
|
1107
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
1108
|
+
`).run((0, uuid_1.v4)(), sessionId, `commit_${timestamp}`, commitValue, 'git', 'normal', calculateSize(commitValue));
|
|
1109
|
+
// Create checkpoint
|
|
1110
|
+
const checkpointId = (0, uuid_1.v4)();
|
|
1111
|
+
const checkpointName = `git-commit-${timestamp}`;
|
|
1112
|
+
const gitInfo = await getGitStatus();
|
|
1113
|
+
db.prepare(`
|
|
1114
|
+
INSERT INTO checkpoints (id, session_id, name, description, git_status, git_branch)
|
|
1115
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1116
|
+
`).run(checkpointId, sessionId, checkpointName, `Git commit: ${message || 'No message'}`, gitInfo.status, gitInfo.branch);
|
|
1117
|
+
// Link current context to checkpoint
|
|
1118
|
+
const items = db
|
|
1119
|
+
.prepare('SELECT id FROM context_items WHERE session_id = ?')
|
|
1120
|
+
.all(sessionId);
|
|
1121
|
+
const itemStmt = db.prepare('INSERT INTO checkpoint_items (id, checkpoint_id, context_item_id) VALUES (?, ?, ?)');
|
|
1122
|
+
for (const item of items) {
|
|
1123
|
+
itemStmt.run((0, uuid_1.v4)(), checkpointId, item.id);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
// Execute git commit
|
|
1127
|
+
try {
|
|
1128
|
+
const git = (0, simple_git_1.simpleGit)(session.working_directory);
|
|
1129
|
+
await git.add('.');
|
|
1130
|
+
const commitResult = await git.commit(message || 'Commit via Memory Keeper');
|
|
1131
|
+
return {
|
|
1132
|
+
content: [
|
|
1133
|
+
{
|
|
1134
|
+
type: 'text',
|
|
1135
|
+
text: `Git commit successful!
|
|
1136
|
+
Commit: ${commitResult.commit}
|
|
1137
|
+
Context saved: ${autoSave ? 'Yes' : 'No'}
|
|
1138
|
+
Checkpoint: ${autoSave ? `git-commit-${new Date().toISOString()}` : 'None'}`,
|
|
1139
|
+
},
|
|
1140
|
+
],
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
catch (error) {
|
|
1144
|
+
return {
|
|
1145
|
+
content: [
|
|
1146
|
+
{
|
|
1147
|
+
type: 'text',
|
|
1148
|
+
text: `Git commit failed: ${error.message}`,
|
|
1149
|
+
},
|
|
1150
|
+
],
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
// Phase 3: Context Search
|
|
1155
|
+
case 'context_search': {
|
|
1156
|
+
const { query, searchIn = ['key', 'value'], sessionId: specificSessionId, category, channel, channels, sort, limit, offset, createdAfter, createdBefore, keyPattern, priorities, includeMetadata, } = args;
|
|
1157
|
+
const targetSessionId = specificSessionId || currentSessionId || ensureSession();
|
|
1158
|
+
// Use enhanced search for all cases
|
|
1159
|
+
const result = repositories.contexts.searchEnhanced({
|
|
1160
|
+
query,
|
|
1161
|
+
sessionId: targetSessionId,
|
|
1162
|
+
searchIn,
|
|
1163
|
+
category,
|
|
1164
|
+
channel,
|
|
1165
|
+
channels,
|
|
1166
|
+
sort,
|
|
1167
|
+
limit,
|
|
1168
|
+
offset,
|
|
1169
|
+
createdAfter,
|
|
1170
|
+
createdBefore,
|
|
1171
|
+
keyPattern,
|
|
1172
|
+
priorities,
|
|
1173
|
+
includeMetadata,
|
|
1174
|
+
});
|
|
1175
|
+
if (result.items.length === 0) {
|
|
1176
|
+
return {
|
|
1177
|
+
content: [
|
|
1178
|
+
{
|
|
1179
|
+
type: 'text',
|
|
1180
|
+
text: `No results found for: "${query}"`,
|
|
1181
|
+
},
|
|
1182
|
+
],
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
// Enhanced response format with metadata
|
|
1186
|
+
if (includeMetadata) {
|
|
1187
|
+
const itemsWithMetadata = result.items.map(item => ({
|
|
1188
|
+
key: item.key,
|
|
1189
|
+
value: item.value,
|
|
1190
|
+
category: item.category,
|
|
1191
|
+
priority: item.priority,
|
|
1192
|
+
channel: item.channel,
|
|
1193
|
+
metadata: item.metadata ? JSON.parse(item.metadata) : null,
|
|
1194
|
+
size: item.size || calculateSize(item.value),
|
|
1195
|
+
created_at: item.created_at,
|
|
1196
|
+
updated_at: item.updated_at,
|
|
1197
|
+
}));
|
|
1198
|
+
return {
|
|
1199
|
+
content: [
|
|
1200
|
+
{
|
|
1201
|
+
type: 'text',
|
|
1202
|
+
text: JSON.stringify({
|
|
1203
|
+
items: itemsWithMetadata,
|
|
1204
|
+
totalCount: result.totalCount,
|
|
1205
|
+
page: offset && limit ? Math.floor(offset / limit) + 1 : 1,
|
|
1206
|
+
pageSize: limit || result.items.length,
|
|
1207
|
+
query,
|
|
1208
|
+
}, null, 2),
|
|
1209
|
+
},
|
|
1210
|
+
],
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
// Backward compatible format
|
|
1214
|
+
const resultText = result.items
|
|
1215
|
+
.map((r) => `• [${r.priority}] ${r.key} (${r.category || 'none'})\n ${r.value.substring(0, 100)}${r.value.length > 100 ? '...' : ''}`)
|
|
1216
|
+
.join('\n\n');
|
|
1217
|
+
return {
|
|
1218
|
+
content: [
|
|
1219
|
+
{
|
|
1220
|
+
type: 'text',
|
|
1221
|
+
text: `Found ${result.items.length} results for "${query}":\n\n${resultText}`,
|
|
1222
|
+
},
|
|
1223
|
+
],
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
// Phase 3: Export/Import
|
|
1227
|
+
case 'context_export': {
|
|
1228
|
+
const { sessionId: specificSessionId, format = 'json', includeStats = false, confirmEmpty = false, } = args;
|
|
1229
|
+
const targetSessionId = specificSessionId || currentSessionId;
|
|
1230
|
+
// Phase 1: Validation
|
|
1231
|
+
if (!targetSessionId) {
|
|
1232
|
+
throw new Error('No session ID provided and no current session active');
|
|
1233
|
+
}
|
|
1234
|
+
// Check if session exists
|
|
1235
|
+
const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(targetSessionId);
|
|
1236
|
+
if (!session) {
|
|
1237
|
+
throw new Error(`Session not found: ${targetSessionId}`);
|
|
1238
|
+
}
|
|
1239
|
+
// Get session data
|
|
1240
|
+
const contextItems = db
|
|
1241
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ?')
|
|
1242
|
+
.all(targetSessionId);
|
|
1243
|
+
const fileCache = db
|
|
1244
|
+
.prepare('SELECT * FROM file_cache WHERE session_id = ?')
|
|
1245
|
+
.all(targetSessionId);
|
|
1246
|
+
const checkpoints = db
|
|
1247
|
+
.prepare('SELECT * FROM checkpoints WHERE session_id = ?')
|
|
1248
|
+
.all(targetSessionId);
|
|
1249
|
+
// Check if session is empty
|
|
1250
|
+
const isEmpty = contextItems.length === 0 && fileCache.length === 0 && checkpoints.length === 0;
|
|
1251
|
+
if (isEmpty && !confirmEmpty) {
|
|
1252
|
+
return {
|
|
1253
|
+
content: [
|
|
1254
|
+
{
|
|
1255
|
+
type: 'text',
|
|
1256
|
+
text: 'Warning: Session appears to be empty. No context items, files, or checkpoints found.\n\nTo export anyway, use confirmEmpty: true',
|
|
1257
|
+
},
|
|
1258
|
+
],
|
|
1259
|
+
isEmpty: true,
|
|
1260
|
+
requiresConfirmation: true,
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
const exportData = {
|
|
1264
|
+
version: '0.4.0',
|
|
1265
|
+
exported: new Date().toISOString(),
|
|
1266
|
+
session,
|
|
1267
|
+
contextItems,
|
|
1268
|
+
fileCache,
|
|
1269
|
+
checkpoints,
|
|
1270
|
+
metadata: {
|
|
1271
|
+
itemCount: contextItems.length,
|
|
1272
|
+
fileCount: fileCache.length,
|
|
1273
|
+
checkpointCount: checkpoints.length,
|
|
1274
|
+
totalSize: JSON.stringify({ contextItems, fileCache, checkpoints }).length,
|
|
1275
|
+
},
|
|
1276
|
+
};
|
|
1277
|
+
if (format === 'json') {
|
|
1278
|
+
const exportPath = path.join(os.tmpdir(), `memory-keeper-export-${targetSessionId.substring(0, 8)}.json`);
|
|
1279
|
+
// Check write permissions
|
|
1280
|
+
try {
|
|
1281
|
+
fs.writeFileSync(exportPath, JSON.stringify(exportData, null, 2));
|
|
1282
|
+
}
|
|
1283
|
+
catch (error) {
|
|
1284
|
+
if (error.code === 'EACCES') {
|
|
1285
|
+
throw new Error(`Permission denied: Cannot write to ${exportPath}`);
|
|
1286
|
+
}
|
|
1287
|
+
throw error;
|
|
1288
|
+
}
|
|
1289
|
+
const stats = {
|
|
1290
|
+
items: contextItems.length,
|
|
1291
|
+
files: fileCache.length,
|
|
1292
|
+
checkpoints: checkpoints.length,
|
|
1293
|
+
size: fs.statSync(exportPath).size,
|
|
1294
|
+
};
|
|
1295
|
+
return {
|
|
1296
|
+
content: [
|
|
1297
|
+
{
|
|
1298
|
+
type: 'text',
|
|
1299
|
+
text: includeStats
|
|
1300
|
+
? `✅ Successfully exported session "${session.name}" to: ${exportPath}
|
|
1301
|
+
|
|
1302
|
+
📊 Export Statistics:
|
|
1303
|
+
- Context Items: ${stats.items}
|
|
1304
|
+
- Cached Files: ${stats.files}
|
|
1305
|
+
- Checkpoints: ${stats.checkpoints}
|
|
1306
|
+
- Export Size: ${(stats.size / 1024).toFixed(2)} KB
|
|
1307
|
+
|
|
1308
|
+
Session ID: ${targetSessionId}`
|
|
1309
|
+
: `Exported session to: ${exportPath}
|
|
1310
|
+
Items: ${stats.items}
|
|
1311
|
+
Files: ${stats.files}`,
|
|
1312
|
+
},
|
|
1313
|
+
],
|
|
1314
|
+
exportPath,
|
|
1315
|
+
statistics: stats,
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
// Inline format
|
|
1319
|
+
return {
|
|
1320
|
+
content: [
|
|
1321
|
+
{
|
|
1322
|
+
type: 'text',
|
|
1323
|
+
text: JSON.stringify(exportData, null, 2),
|
|
1324
|
+
},
|
|
1325
|
+
],
|
|
1326
|
+
statistics: {
|
|
1327
|
+
items: contextItems.length,
|
|
1328
|
+
files: fileCache.length,
|
|
1329
|
+
checkpoints: checkpoints.length,
|
|
1330
|
+
},
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
case 'context_import': {
|
|
1334
|
+
const { filePath, merge = false } = args;
|
|
1335
|
+
try {
|
|
1336
|
+
const importData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
1337
|
+
// Create new session or merge
|
|
1338
|
+
let targetSessionId;
|
|
1339
|
+
if (merge && currentSessionId) {
|
|
1340
|
+
targetSessionId = currentSessionId;
|
|
1341
|
+
}
|
|
1342
|
+
else {
|
|
1343
|
+
targetSessionId = (0, uuid_1.v4)();
|
|
1344
|
+
const importedSession = importData.session;
|
|
1345
|
+
db.prepare(`
|
|
1346
|
+
INSERT INTO sessions (id, name, description, branch, working_directory, created_at)
|
|
1347
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1348
|
+
`).run(targetSessionId, `Imported: ${importedSession.name}`, `Imported from ${filePath} on ${new Date().toISOString()}`, importedSession.branch, null, new Date().toISOString());
|
|
1349
|
+
currentSessionId = targetSessionId;
|
|
1350
|
+
}
|
|
1351
|
+
// Import context items
|
|
1352
|
+
const itemStmt = db.prepare(`
|
|
1353
|
+
INSERT OR REPLACE INTO context_items (id, session_id, key, value, category, priority, size, created_at, updated_at)
|
|
1354
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
1355
|
+
`);
|
|
1356
|
+
let itemCount = 0;
|
|
1357
|
+
for (const item of importData.contextItems) {
|
|
1358
|
+
itemStmt.run((0, uuid_1.v4)(), targetSessionId, item.key, item.value, item.category, item.priority, item.size || calculateSize(item.value), item.created_at);
|
|
1359
|
+
itemCount++;
|
|
1360
|
+
}
|
|
1361
|
+
// Import file cache
|
|
1362
|
+
const fileStmt = db.prepare(`
|
|
1363
|
+
INSERT OR REPLACE INTO file_cache (id, session_id, file_path, content, hash, last_read)
|
|
1364
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1365
|
+
`);
|
|
1366
|
+
let fileCount = 0;
|
|
1367
|
+
for (const file of importData.fileCache || []) {
|
|
1368
|
+
fileStmt.run((0, uuid_1.v4)(), targetSessionId, file.file_path, file.content, file.hash, file.last_read);
|
|
1369
|
+
fileCount++;
|
|
1370
|
+
}
|
|
1371
|
+
return {
|
|
1372
|
+
content: [
|
|
1373
|
+
{
|
|
1374
|
+
type: 'text',
|
|
1375
|
+
text: `Import successful!
|
|
1376
|
+
Session: ${targetSessionId.substring(0, 8)}
|
|
1377
|
+
Context items: ${itemCount}
|
|
1378
|
+
Files: ${fileCount}
|
|
1379
|
+
Mode: ${merge ? 'Merged' : 'New session'}`,
|
|
1380
|
+
},
|
|
1381
|
+
],
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
catch (error) {
|
|
1385
|
+
return {
|
|
1386
|
+
content: [
|
|
1387
|
+
{
|
|
1388
|
+
type: 'text',
|
|
1389
|
+
text: `Import failed: ${error.message}`,
|
|
1390
|
+
},
|
|
1391
|
+
],
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
// Phase 4.1: Knowledge Graph Tools
|
|
1396
|
+
case 'context_analyze': {
|
|
1397
|
+
const { sessionId, categories } = args;
|
|
1398
|
+
const targetSessionId = sessionId || ensureSession();
|
|
1399
|
+
try {
|
|
1400
|
+
// Get context items to analyze
|
|
1401
|
+
let query = 'SELECT * FROM context_items WHERE session_id = ?';
|
|
1402
|
+
const params = [targetSessionId];
|
|
1403
|
+
if (categories && categories.length > 0) {
|
|
1404
|
+
query += ` AND category IN (${categories.map(() => '?').join(',')})`;
|
|
1405
|
+
params.push(...categories);
|
|
1406
|
+
}
|
|
1407
|
+
const items = db.prepare(query).all(...params);
|
|
1408
|
+
let entitiesCreated = 0;
|
|
1409
|
+
let relationsCreated = 0;
|
|
1410
|
+
// Analyze each context item
|
|
1411
|
+
for (const item of items) {
|
|
1412
|
+
const analysis = knowledgeGraph.analyzeContext(targetSessionId, item.value);
|
|
1413
|
+
// Create entities
|
|
1414
|
+
for (const entityData of analysis.entities) {
|
|
1415
|
+
const existing = knowledgeGraph.findEntity(targetSessionId, entityData.name, entityData.type);
|
|
1416
|
+
if (!existing) {
|
|
1417
|
+
knowledgeGraph.createEntity(targetSessionId, entityData.type, entityData.name, {
|
|
1418
|
+
confidence: entityData.confidence,
|
|
1419
|
+
source: item.key,
|
|
1420
|
+
});
|
|
1421
|
+
entitiesCreated++;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
// Create relations
|
|
1425
|
+
for (const relationData of analysis.relations) {
|
|
1426
|
+
const subject = knowledgeGraph.findEntity(targetSessionId, relationData.subject);
|
|
1427
|
+
const object = knowledgeGraph.findEntity(targetSessionId, relationData.object);
|
|
1428
|
+
if (subject && object) {
|
|
1429
|
+
knowledgeGraph.createRelation(targetSessionId, subject.id, relationData.predicate, object.id, relationData.confidence);
|
|
1430
|
+
relationsCreated++;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
// Get summary statistics
|
|
1435
|
+
const entityStats = db
|
|
1436
|
+
.prepare(`
|
|
1437
|
+
SELECT type, COUNT(*) as count
|
|
1438
|
+
FROM entities
|
|
1439
|
+
WHERE session_id = ?
|
|
1440
|
+
GROUP BY type
|
|
1441
|
+
`)
|
|
1442
|
+
.all(targetSessionId);
|
|
1443
|
+
return {
|
|
1444
|
+
content: [
|
|
1445
|
+
{
|
|
1446
|
+
type: 'text',
|
|
1447
|
+
text: `Analysis complete!
|
|
1448
|
+
Items analyzed: ${items.length}
|
|
1449
|
+
Entities created: ${entitiesCreated}
|
|
1450
|
+
Relations created: ${relationsCreated}
|
|
1451
|
+
|
|
1452
|
+
Entity breakdown:
|
|
1453
|
+
${entityStats.map(s => `- ${s.type}: ${s.count}`).join('\n')}`,
|
|
1454
|
+
},
|
|
1455
|
+
],
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
catch (error) {
|
|
1459
|
+
return {
|
|
1460
|
+
content: [
|
|
1461
|
+
{
|
|
1462
|
+
type: 'text',
|
|
1463
|
+
text: `Analysis failed: ${error.message}`,
|
|
1464
|
+
},
|
|
1465
|
+
],
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
case 'context_find_related': {
|
|
1470
|
+
const { key, relationTypes, maxDepth = 2 } = args;
|
|
1471
|
+
const sessionId = ensureSession();
|
|
1472
|
+
try {
|
|
1473
|
+
// First try to find as entity
|
|
1474
|
+
let entity = knowledgeGraph.findEntity(sessionId, key);
|
|
1475
|
+
// If not found as entity, check if it's a context key
|
|
1476
|
+
if (!entity) {
|
|
1477
|
+
const contextItem = db
|
|
1478
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
|
|
1479
|
+
.get(sessionId, key);
|
|
1480
|
+
if (contextItem) {
|
|
1481
|
+
// Try to extract entities from the context value
|
|
1482
|
+
const analysis = knowledgeGraph.analyzeContext(sessionId, contextItem.value);
|
|
1483
|
+
if (analysis.entities.length > 0) {
|
|
1484
|
+
entity = knowledgeGraph.findEntity(sessionId, analysis.entities[0].name);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
if (!entity) {
|
|
1489
|
+
return {
|
|
1490
|
+
content: [
|
|
1491
|
+
{
|
|
1492
|
+
type: 'text',
|
|
1493
|
+
text: `No entity found for key: ${key}`,
|
|
1494
|
+
},
|
|
1495
|
+
],
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
// Get connected entities
|
|
1499
|
+
const connectedIds = knowledgeGraph.getConnectedEntities(entity.id, maxDepth);
|
|
1500
|
+
// Get details for connected entities
|
|
1501
|
+
const entities = Array.from(connectedIds).map(id => {
|
|
1502
|
+
const entityData = db.prepare('SELECT * FROM entities WHERE id = ?').get(id);
|
|
1503
|
+
const relations = knowledgeGraph.getRelations(id);
|
|
1504
|
+
const observations = knowledgeGraph.getObservations(id);
|
|
1505
|
+
return {
|
|
1506
|
+
...entityData,
|
|
1507
|
+
attributes: entityData.attributes ? JSON.parse(entityData.attributes) : {},
|
|
1508
|
+
relations: relations.length,
|
|
1509
|
+
observations: observations.length,
|
|
1510
|
+
};
|
|
1511
|
+
});
|
|
1512
|
+
// Filter by relation types if specified
|
|
1513
|
+
let relevantRelations = knowledgeGraph.getRelations(entity.id);
|
|
1514
|
+
if (relationTypes && relationTypes.length > 0) {
|
|
1515
|
+
relevantRelations = relevantRelations.filter(r => relationTypes.includes(r.predicate));
|
|
1516
|
+
}
|
|
1517
|
+
return {
|
|
1518
|
+
content: [
|
|
1519
|
+
{
|
|
1520
|
+
type: 'text',
|
|
1521
|
+
text: `Related entities for "${key}":
|
|
1522
|
+
|
|
1523
|
+
Found ${entities.length} connected entities (max depth: ${maxDepth})
|
|
1524
|
+
|
|
1525
|
+
Main entity:
|
|
1526
|
+
- Type: ${entity.type}
|
|
1527
|
+
- Name: ${entity.name}
|
|
1528
|
+
- Direct relations: ${relevantRelations.length}
|
|
1529
|
+
|
|
1530
|
+
Connected entities:
|
|
1531
|
+
${entities
|
|
1532
|
+
.slice(0, 20)
|
|
1533
|
+
.map(e => `- ${e.type}: ${e.name} (${e.relations} relations, ${e.observations} observations)`)
|
|
1534
|
+
.join('\n')}
|
|
1535
|
+
${entities.length > 20 ? `\n... and ${entities.length - 20} more` : ''}`,
|
|
1536
|
+
},
|
|
1537
|
+
],
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
catch (error) {
|
|
1541
|
+
return {
|
|
1542
|
+
content: [
|
|
1543
|
+
{
|
|
1544
|
+
type: 'text',
|
|
1545
|
+
text: `Find related failed: ${error.message}`,
|
|
1546
|
+
},
|
|
1547
|
+
],
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
case 'context_visualize': {
|
|
1552
|
+
const { type = 'graph', entityTypes, sessionId } = args;
|
|
1553
|
+
const targetSessionId = sessionId || ensureSession();
|
|
1554
|
+
try {
|
|
1555
|
+
if (type === 'graph') {
|
|
1556
|
+
const graphData = knowledgeGraph.getGraphData(targetSessionId, entityTypes);
|
|
1557
|
+
return {
|
|
1558
|
+
content: [
|
|
1559
|
+
{
|
|
1560
|
+
type: 'text',
|
|
1561
|
+
text: JSON.stringify(graphData, null, 2),
|
|
1562
|
+
},
|
|
1563
|
+
],
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
else if (type === 'timeline') {
|
|
1567
|
+
// Get time-based data
|
|
1568
|
+
const timeline = db
|
|
1569
|
+
.prepare(`
|
|
1570
|
+
SELECT
|
|
1571
|
+
strftime('%Y-%m-%d %H:00', created_at) as hour,
|
|
1572
|
+
COUNT(*) as events,
|
|
1573
|
+
GROUP_CONCAT(DISTINCT category) as categories
|
|
1574
|
+
FROM context_items
|
|
1575
|
+
WHERE session_id = ?
|
|
1576
|
+
GROUP BY hour
|
|
1577
|
+
ORDER BY hour DESC
|
|
1578
|
+
LIMIT 24
|
|
1579
|
+
`)
|
|
1580
|
+
.all(targetSessionId);
|
|
1581
|
+
return {
|
|
1582
|
+
content: [
|
|
1583
|
+
{
|
|
1584
|
+
type: 'text',
|
|
1585
|
+
text: JSON.stringify({
|
|
1586
|
+
type: 'timeline',
|
|
1587
|
+
data: timeline,
|
|
1588
|
+
}, null, 2),
|
|
1589
|
+
},
|
|
1590
|
+
],
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
else if (type === 'heatmap') {
|
|
1594
|
+
// Get category/priority heatmap data
|
|
1595
|
+
const heatmap = db
|
|
1596
|
+
.prepare(`
|
|
1597
|
+
SELECT
|
|
1598
|
+
category,
|
|
1599
|
+
priority,
|
|
1600
|
+
COUNT(*) as count
|
|
1601
|
+
FROM context_items
|
|
1602
|
+
WHERE session_id = ?
|
|
1603
|
+
GROUP BY category, priority
|
|
1604
|
+
`)
|
|
1605
|
+
.all(targetSessionId);
|
|
1606
|
+
return {
|
|
1607
|
+
content: [
|
|
1608
|
+
{
|
|
1609
|
+
type: 'text',
|
|
1610
|
+
text: JSON.stringify({
|
|
1611
|
+
type: 'heatmap',
|
|
1612
|
+
data: heatmap,
|
|
1613
|
+
}, null, 2),
|
|
1614
|
+
},
|
|
1615
|
+
],
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
return {
|
|
1619
|
+
content: [
|
|
1620
|
+
{
|
|
1621
|
+
type: 'text',
|
|
1622
|
+
text: `Unknown visualization type: ${type}`,
|
|
1623
|
+
},
|
|
1624
|
+
],
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
catch (error) {
|
|
1628
|
+
return {
|
|
1629
|
+
content: [
|
|
1630
|
+
{
|
|
1631
|
+
type: 'text',
|
|
1632
|
+
text: `Visualization failed: ${error.message}`,
|
|
1633
|
+
},
|
|
1634
|
+
],
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
// Phase 4.2: Semantic Search
|
|
1639
|
+
case 'context_semantic_search': {
|
|
1640
|
+
const { query, topK = 10, minSimilarity = 0.3, sessionId } = args;
|
|
1641
|
+
const targetSessionId = sessionId || ensureSession();
|
|
1642
|
+
try {
|
|
1643
|
+
// Ensure embeddings are up to date for the session
|
|
1644
|
+
const _embeddingCount = await vectorStore.updateSessionEmbeddings(targetSessionId);
|
|
1645
|
+
// Perform semantic search
|
|
1646
|
+
const results = await vectorStore.searchInSession(targetSessionId, query, topK, minSimilarity);
|
|
1647
|
+
if (results.length === 0) {
|
|
1648
|
+
return {
|
|
1649
|
+
content: [
|
|
1650
|
+
{
|
|
1651
|
+
type: 'text',
|
|
1652
|
+
text: `No results found for query: "${query}"`,
|
|
1653
|
+
},
|
|
1654
|
+
],
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
// Format results
|
|
1658
|
+
let response = `Found ${results.length} results for: "${query}"\n\n`;
|
|
1659
|
+
results.forEach((result, index) => {
|
|
1660
|
+
const similarity = (result.similarity * 100).toFixed(1);
|
|
1661
|
+
response += `${index + 1}. [${similarity}% match]\n`;
|
|
1662
|
+
// Extract key and value from content
|
|
1663
|
+
const colonIndex = result.content.indexOf(':');
|
|
1664
|
+
if (colonIndex > -1) {
|
|
1665
|
+
const key = result.content.substring(0, colonIndex);
|
|
1666
|
+
const value = result.content.substring(colonIndex + 1).trim();
|
|
1667
|
+
response += ` Key: ${key}\n`;
|
|
1668
|
+
response += ` Value: ${value.substring(0, 200)}${value.length > 200 ? '...' : ''}\n`;
|
|
1669
|
+
}
|
|
1670
|
+
else {
|
|
1671
|
+
response += ` ${result.content.substring(0, 200)}${result.content.length > 200 ? '...' : ''}\n`;
|
|
1672
|
+
}
|
|
1673
|
+
if (result.metadata) {
|
|
1674
|
+
if (result.metadata.category) {
|
|
1675
|
+
response += ` Category: ${result.metadata.category}`;
|
|
1676
|
+
}
|
|
1677
|
+
if (result.metadata.priority) {
|
|
1678
|
+
response += `, Priority: ${result.metadata.priority}`;
|
|
1679
|
+
}
|
|
1680
|
+
response += '\n';
|
|
1681
|
+
}
|
|
1682
|
+
response += '\n';
|
|
1683
|
+
});
|
|
1684
|
+
return {
|
|
1685
|
+
content: [
|
|
1686
|
+
{
|
|
1687
|
+
type: 'text',
|
|
1688
|
+
text: response,
|
|
1689
|
+
},
|
|
1690
|
+
],
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
catch (error) {
|
|
1694
|
+
return {
|
|
1695
|
+
content: [
|
|
1696
|
+
{
|
|
1697
|
+
type: 'text',
|
|
1698
|
+
text: `Semantic search failed: ${error.message}`,
|
|
1699
|
+
},
|
|
1700
|
+
],
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
// Phase 4.3: Multi-Agent System
|
|
1705
|
+
case 'context_delegate': {
|
|
1706
|
+
const { taskType, input, sessionId, chain = false } = args;
|
|
1707
|
+
const targetSessionId = sessionId || ensureSession();
|
|
1708
|
+
try {
|
|
1709
|
+
// Create agent task
|
|
1710
|
+
const task = {
|
|
1711
|
+
id: (0, uuid_1.v4)(),
|
|
1712
|
+
type: taskType,
|
|
1713
|
+
input: {
|
|
1714
|
+
...input,
|
|
1715
|
+
sessionId: targetSessionId,
|
|
1716
|
+
},
|
|
1717
|
+
};
|
|
1718
|
+
// Process with agents
|
|
1719
|
+
let results;
|
|
1720
|
+
if (chain && Array.isArray(input)) {
|
|
1721
|
+
// Process as a chain of tasks
|
|
1722
|
+
const tasks = input.map((inp, index) => ({
|
|
1723
|
+
id: (0, uuid_1.v4)(),
|
|
1724
|
+
type: Array.isArray(taskType) ? taskType[index] : taskType,
|
|
1725
|
+
input: { ...inp, sessionId: targetSessionId },
|
|
1726
|
+
}));
|
|
1727
|
+
results = await agentCoordinator.processChain(tasks);
|
|
1728
|
+
}
|
|
1729
|
+
else {
|
|
1730
|
+
// Single task delegation
|
|
1731
|
+
results = await agentCoordinator.delegate(task);
|
|
1732
|
+
}
|
|
1733
|
+
// Format response
|
|
1734
|
+
let response = `Agent Processing Results:\n\n`;
|
|
1735
|
+
for (const result of results) {
|
|
1736
|
+
response += `## ${result.agentType.toUpperCase()} Agent\n`;
|
|
1737
|
+
response += `Confidence: ${(result.confidence * 100).toFixed(0)}%\n`;
|
|
1738
|
+
response += `Processing Time: ${result.processingTime}ms\n`;
|
|
1739
|
+
if (result.reasoning) {
|
|
1740
|
+
response += `Reasoning: ${result.reasoning}\n`;
|
|
1741
|
+
}
|
|
1742
|
+
response += `\nOutput:\n`;
|
|
1743
|
+
response += JSON.stringify(result.output, null, 2);
|
|
1744
|
+
response += '\n\n---\n\n';
|
|
1745
|
+
}
|
|
1746
|
+
// Get best result if multiple agents processed
|
|
1747
|
+
if (results.length > 1) {
|
|
1748
|
+
const best = agentCoordinator.getBestResult(task.id);
|
|
1749
|
+
if (best) {
|
|
1750
|
+
response += `\n## Best Result (${best.agentType}, ${(best.confidence * 100).toFixed(0)}% confidence):\n`;
|
|
1751
|
+
response += JSON.stringify(best.output, null, 2);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
return {
|
|
1755
|
+
content: [
|
|
1756
|
+
{
|
|
1757
|
+
type: 'text',
|
|
1758
|
+
text: response,
|
|
1759
|
+
},
|
|
1760
|
+
],
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
catch (error) {
|
|
1764
|
+
return {
|
|
1765
|
+
content: [
|
|
1766
|
+
{
|
|
1767
|
+
type: 'text',
|
|
1768
|
+
text: `Agent delegation failed: ${error.message}`,
|
|
1769
|
+
},
|
|
1770
|
+
],
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
// Phase 4.4: Session Branching
|
|
1775
|
+
case 'context_branch_session': {
|
|
1776
|
+
const { branchName, copyDepth = 'shallow' } = args;
|
|
1777
|
+
const sourceSessionId = ensureSession();
|
|
1778
|
+
try {
|
|
1779
|
+
// Get source session info
|
|
1780
|
+
const sourceSession = db
|
|
1781
|
+
.prepare('SELECT * FROM sessions WHERE id = ?')
|
|
1782
|
+
.get(sourceSessionId);
|
|
1783
|
+
if (!sourceSession) {
|
|
1784
|
+
throw new Error('Source session not found');
|
|
1785
|
+
}
|
|
1786
|
+
// Create new branch session
|
|
1787
|
+
const branchId = (0, uuid_1.v4)();
|
|
1788
|
+
db.prepare(`
|
|
1789
|
+
INSERT INTO sessions (id, name, description, branch, working_directory, parent_id)
|
|
1790
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1791
|
+
`).run(branchId, branchName, `Branch of ${sourceSession.name} created at ${new Date().toISOString()}`, sourceSession.branch, null, sourceSessionId);
|
|
1792
|
+
if (copyDepth === 'deep') {
|
|
1793
|
+
// Copy all context items
|
|
1794
|
+
const items = db
|
|
1795
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ?')
|
|
1796
|
+
.all(sourceSessionId);
|
|
1797
|
+
const stmt = db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)');
|
|
1798
|
+
for (const item of items) {
|
|
1799
|
+
stmt.run((0, uuid_1.v4)(), branchId, item.key, item.value, item.category, item.priority, item.created_at);
|
|
1800
|
+
}
|
|
1801
|
+
// Copy file cache
|
|
1802
|
+
const files = db
|
|
1803
|
+
.prepare('SELECT * FROM file_cache WHERE session_id = ?')
|
|
1804
|
+
.all(sourceSessionId);
|
|
1805
|
+
const fileStmt = db.prepare('INSERT INTO file_cache (id, session_id, file_path, content, hash, last_read) VALUES (?, ?, ?, ?, ?, ?)');
|
|
1806
|
+
for (const file of files) {
|
|
1807
|
+
fileStmt.run((0, uuid_1.v4)(), branchId, file.file_path, file.content, file.hash, file.last_read);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
else {
|
|
1811
|
+
// Shallow copy - only copy high priority items
|
|
1812
|
+
const items = db
|
|
1813
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND priority = ?')
|
|
1814
|
+
.all(sourceSessionId, 'high');
|
|
1815
|
+
const stmt = db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)');
|
|
1816
|
+
for (const item of items) {
|
|
1817
|
+
stmt.run((0, uuid_1.v4)(), branchId, item.key, item.value, item.category, item.priority, item.created_at);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
// Switch to the new branch
|
|
1821
|
+
currentSessionId = branchId;
|
|
1822
|
+
return {
|
|
1823
|
+
content: [
|
|
1824
|
+
{
|
|
1825
|
+
type: 'text',
|
|
1826
|
+
text: `Created branch session: ${branchName}
|
|
1827
|
+
ID: ${branchId}
|
|
1828
|
+
Parent: ${sourceSession.name} (${sourceSessionId.substring(0, 8)})
|
|
1829
|
+
Copy depth: ${copyDepth}
|
|
1830
|
+
Items copied: ${copyDepth === 'deep' ? 'All' : 'High priority only'}
|
|
1831
|
+
|
|
1832
|
+
Now working in branch: ${branchName}`,
|
|
1833
|
+
},
|
|
1834
|
+
],
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
catch (error) {
|
|
1838
|
+
return {
|
|
1839
|
+
content: [
|
|
1840
|
+
{
|
|
1841
|
+
type: 'text',
|
|
1842
|
+
text: `Branch creation failed: ${error.message}`,
|
|
1843
|
+
},
|
|
1844
|
+
],
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
// Phase 4.4: Session Merging
|
|
1849
|
+
case 'context_merge_sessions': {
|
|
1850
|
+
const { sourceSessionId, conflictResolution = 'keep_current' } = args;
|
|
1851
|
+
const targetSessionId = ensureSession();
|
|
1852
|
+
try {
|
|
1853
|
+
// Get both sessions
|
|
1854
|
+
const sourceSession = db
|
|
1855
|
+
.prepare('SELECT * FROM sessions WHERE id = ?')
|
|
1856
|
+
.get(sourceSessionId);
|
|
1857
|
+
const targetSession = db
|
|
1858
|
+
.prepare('SELECT * FROM sessions WHERE id = ?')
|
|
1859
|
+
.get(targetSessionId);
|
|
1860
|
+
if (!sourceSession) {
|
|
1861
|
+
throw new Error('Source session not found');
|
|
1862
|
+
}
|
|
1863
|
+
// Get items from source session
|
|
1864
|
+
const sourceItems = db
|
|
1865
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ?')
|
|
1866
|
+
.all(sourceSessionId);
|
|
1867
|
+
let merged = 0;
|
|
1868
|
+
let skipped = 0;
|
|
1869
|
+
for (const item of sourceItems) {
|
|
1870
|
+
// Check if item exists in target
|
|
1871
|
+
const existing = db
|
|
1872
|
+
.prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
|
|
1873
|
+
.get(targetSessionId, item.key);
|
|
1874
|
+
if (existing) {
|
|
1875
|
+
// Handle conflict
|
|
1876
|
+
if (conflictResolution === 'keep_source' ||
|
|
1877
|
+
(conflictResolution === 'keep_newest' &&
|
|
1878
|
+
new Date(item.created_at) > new Date(existing.created_at))) {
|
|
1879
|
+
db.prepare('UPDATE context_items SET value = ?, category = ?, priority = ? WHERE session_id = ? AND key = ?').run(item.value, item.category, item.priority, targetSessionId, item.key);
|
|
1880
|
+
merged++;
|
|
1881
|
+
}
|
|
1882
|
+
else {
|
|
1883
|
+
skipped++;
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
else {
|
|
1887
|
+
// No conflict, insert item
|
|
1888
|
+
db.prepare('INSERT INTO context_items (id, session_id, key, value, category, priority, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)').run((0, uuid_1.v4)(), targetSessionId, item.key, item.value, item.category, item.priority, item.created_at);
|
|
1889
|
+
merged++;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
return {
|
|
1893
|
+
content: [
|
|
1894
|
+
{
|
|
1895
|
+
type: 'text',
|
|
1896
|
+
text: `Merge completed!
|
|
1897
|
+
Source: ${sourceSession.name} (${sourceSessionId.substring(0, 8)})
|
|
1898
|
+
Target: ${targetSession.name} (${targetSessionId.substring(0, 8)})
|
|
1899
|
+
Items merged: ${merged}
|
|
1900
|
+
Items skipped: ${skipped}
|
|
1901
|
+
Conflict resolution: ${conflictResolution}`,
|
|
1902
|
+
},
|
|
1903
|
+
],
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
catch (error) {
|
|
1907
|
+
return {
|
|
1908
|
+
content: [
|
|
1909
|
+
{
|
|
1910
|
+
type: 'text',
|
|
1911
|
+
text: `Session merge failed: ${error.message}`,
|
|
1912
|
+
},
|
|
1913
|
+
],
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
// Phase 4.4: Journal Entry
|
|
1918
|
+
case 'context_journal_entry': {
|
|
1919
|
+
const { entry, tags = [], mood } = args;
|
|
1920
|
+
const sessionId = ensureSession();
|
|
1921
|
+
try {
|
|
1922
|
+
const id = (0, uuid_1.v4)();
|
|
1923
|
+
db.prepare(`
|
|
1924
|
+
INSERT INTO journal_entries (id, session_id, entry, tags, mood)
|
|
1925
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1926
|
+
`).run(id, sessionId, entry, JSON.stringify(tags), mood);
|
|
1927
|
+
return {
|
|
1928
|
+
content: [
|
|
1929
|
+
{
|
|
1930
|
+
type: 'text',
|
|
1931
|
+
text: `Journal entry added!
|
|
1932
|
+
Time: ${new Date().toISOString()}
|
|
1933
|
+
Mood: ${mood || 'not specified'}
|
|
1934
|
+
Tags: ${tags.join(', ') || 'none'}
|
|
1935
|
+
Entry saved with ID: ${id.substring(0, 8)}`,
|
|
1936
|
+
},
|
|
1937
|
+
],
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
catch (error) {
|
|
1941
|
+
return {
|
|
1942
|
+
content: [
|
|
1943
|
+
{
|
|
1944
|
+
type: 'text',
|
|
1945
|
+
text: `Journal entry failed: ${error.message}`,
|
|
1946
|
+
},
|
|
1947
|
+
],
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
// Phase 4.4: Timeline
|
|
1952
|
+
case 'context_timeline': {
|
|
1953
|
+
const { startDate, endDate, groupBy = 'day', sessionId, categories, relativeTime, itemsPerPeriod, includeItems, minItemsPerPeriod, showEmpty, } = args;
|
|
1954
|
+
const targetSessionId = sessionId || ensureSession();
|
|
1955
|
+
try {
|
|
1956
|
+
// Use the enhanced timeline method
|
|
1957
|
+
const timeline = repositories.contexts.getTimelineData({
|
|
1958
|
+
sessionId: targetSessionId,
|
|
1959
|
+
startDate,
|
|
1960
|
+
endDate,
|
|
1961
|
+
categories,
|
|
1962
|
+
relativeTime,
|
|
1963
|
+
itemsPerPeriod,
|
|
1964
|
+
includeItems,
|
|
1965
|
+
groupBy,
|
|
1966
|
+
minItemsPerPeriod,
|
|
1967
|
+
showEmpty,
|
|
1968
|
+
});
|
|
1969
|
+
// Get journal entries for the same period
|
|
1970
|
+
let journalQuery = 'SELECT * FROM journal_entries WHERE session_id = ?';
|
|
1971
|
+
const journalParams = [targetSessionId];
|
|
1972
|
+
// Calculate effective dates based on relativeTime if needed
|
|
1973
|
+
let effectiveStartDate = startDate;
|
|
1974
|
+
let effectiveEndDate = endDate;
|
|
1975
|
+
if (relativeTime) {
|
|
1976
|
+
const now = new Date();
|
|
1977
|
+
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
1978
|
+
if (relativeTime === 'today') {
|
|
1979
|
+
effectiveStartDate = today.toISOString();
|
|
1980
|
+
effectiveEndDate = new Date(today.getTime() + 24 * 60 * 60 * 1000).toISOString();
|
|
1981
|
+
}
|
|
1982
|
+
else if (relativeTime === 'yesterday') {
|
|
1983
|
+
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
|
|
1984
|
+
effectiveStartDate = yesterday.toISOString();
|
|
1985
|
+
effectiveEndDate = today.toISOString();
|
|
1986
|
+
}
|
|
1987
|
+
else if (relativeTime.match(/^(\d+) hours? ago$/)) {
|
|
1988
|
+
const hours = parseInt(relativeTime.match(/^(\d+)/)[1]);
|
|
1989
|
+
effectiveStartDate = new Date(now.getTime() - hours * 60 * 60 * 1000).toISOString();
|
|
1990
|
+
}
|
|
1991
|
+
else if (relativeTime.match(/^(\d+) days? ago$/)) {
|
|
1992
|
+
const days = parseInt(relativeTime.match(/^(\d+)/)[1]);
|
|
1993
|
+
effectiveStartDate = new Date(now.getTime() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
1994
|
+
}
|
|
1995
|
+
else if (relativeTime === 'this week') {
|
|
1996
|
+
const startOfWeek = new Date(today);
|
|
1997
|
+
startOfWeek.setDate(today.getDate() - today.getDay());
|
|
1998
|
+
effectiveStartDate = startOfWeek.toISOString();
|
|
1999
|
+
}
|
|
2000
|
+
else if (relativeTime === 'last week') {
|
|
2001
|
+
const startOfLastWeek = new Date(today);
|
|
2002
|
+
startOfLastWeek.setDate(today.getDate() - today.getDay() - 7);
|
|
2003
|
+
const endOfLastWeek = new Date(startOfLastWeek);
|
|
2004
|
+
endOfLastWeek.setDate(startOfLastWeek.getDate() + 7);
|
|
2005
|
+
effectiveStartDate = startOfLastWeek.toISOString();
|
|
2006
|
+
effectiveEndDate = endOfLastWeek.toISOString();
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
if (effectiveStartDate) {
|
|
2010
|
+
journalQuery += ' AND created_at >= ?';
|
|
2011
|
+
journalParams.push(effectiveStartDate);
|
|
2012
|
+
}
|
|
2013
|
+
if (effectiveEndDate) {
|
|
2014
|
+
journalQuery += ' AND created_at <= ?';
|
|
2015
|
+
journalParams.push(effectiveEndDate);
|
|
2016
|
+
}
|
|
2017
|
+
const journals = db
|
|
2018
|
+
.prepare(journalQuery + ' ORDER BY created_at')
|
|
2019
|
+
.all(...journalParams);
|
|
2020
|
+
// Format enhanced timeline response
|
|
2021
|
+
const timelineData = {
|
|
2022
|
+
session_id: targetSessionId,
|
|
2023
|
+
period: {
|
|
2024
|
+
start: effectiveStartDate || startDate || 'beginning',
|
|
2025
|
+
end: effectiveEndDate || endDate || 'now',
|
|
2026
|
+
relative: relativeTime || null,
|
|
2027
|
+
},
|
|
2028
|
+
groupBy,
|
|
2029
|
+
filters: {
|
|
2030
|
+
categories: categories || null,
|
|
2031
|
+
},
|
|
2032
|
+
timeline: timeline.map(period => {
|
|
2033
|
+
const result = {
|
|
2034
|
+
period: period.period,
|
|
2035
|
+
count: period.count,
|
|
2036
|
+
};
|
|
2037
|
+
if (includeItems && period.items) {
|
|
2038
|
+
result.items = period.items.map((item) => ({
|
|
2039
|
+
key: item.key,
|
|
2040
|
+
value: item.value,
|
|
2041
|
+
category: item.category,
|
|
2042
|
+
priority: item.priority,
|
|
2043
|
+
created_at: item.created_at,
|
|
2044
|
+
}));
|
|
2045
|
+
if (period.hasMore) {
|
|
2046
|
+
result.hasMore = true;
|
|
2047
|
+
result.totalCount = period.totalCount;
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
return result;
|
|
2051
|
+
}),
|
|
2052
|
+
journal_entries: journals.map((journal) => ({
|
|
2053
|
+
entry: journal.entry,
|
|
2054
|
+
tags: JSON.parse(journal.tags || '[]'),
|
|
2055
|
+
mood: journal.mood,
|
|
2056
|
+
created_at: journal.created_at,
|
|
2057
|
+
})),
|
|
2058
|
+
};
|
|
2059
|
+
return {
|
|
2060
|
+
content: [
|
|
2061
|
+
{
|
|
2062
|
+
type: 'text',
|
|
2063
|
+
text: JSON.stringify(timelineData, null, 2),
|
|
2064
|
+
},
|
|
2065
|
+
],
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
catch (error) {
|
|
2069
|
+
return {
|
|
2070
|
+
content: [
|
|
2071
|
+
{
|
|
2072
|
+
type: 'text',
|
|
2073
|
+
text: `Timeline generation failed: ${error.message}`,
|
|
2074
|
+
},
|
|
2075
|
+
],
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
// Phase 4.4: Progressive Compression
|
|
2080
|
+
case 'context_compress': {
|
|
2081
|
+
const { olderThan, preserveCategories = [], targetSize: _targetSize, sessionId } = args;
|
|
2082
|
+
const targetSessionId = sessionId || ensureSession();
|
|
2083
|
+
try {
|
|
2084
|
+
// Build query for items to compress
|
|
2085
|
+
let query = 'SELECT * FROM context_items WHERE session_id = ?';
|
|
2086
|
+
const params = [targetSessionId];
|
|
2087
|
+
if (olderThan) {
|
|
2088
|
+
query += ' AND created_at < ?';
|
|
2089
|
+
params.push(olderThan);
|
|
2090
|
+
}
|
|
2091
|
+
if (preserveCategories.length > 0) {
|
|
2092
|
+
query += ` AND category NOT IN (${preserveCategories.map(() => '?').join(',')})`;
|
|
2093
|
+
params.push(...preserveCategories);
|
|
2094
|
+
}
|
|
2095
|
+
const itemsToCompress = db.prepare(query).all(...params);
|
|
2096
|
+
if (itemsToCompress.length === 0) {
|
|
2097
|
+
return {
|
|
2098
|
+
content: [
|
|
2099
|
+
{
|
|
2100
|
+
type: 'text',
|
|
2101
|
+
text: 'No items found to compress with given criteria.',
|
|
2102
|
+
},
|
|
2103
|
+
],
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
// Group items by category for compression
|
|
2107
|
+
const categoryGroups = {};
|
|
2108
|
+
for (const item of itemsToCompress) {
|
|
2109
|
+
const category = item.category || 'uncategorized';
|
|
2110
|
+
if (!categoryGroups[category]) {
|
|
2111
|
+
categoryGroups[category] = [];
|
|
2112
|
+
}
|
|
2113
|
+
categoryGroups[category].push(item);
|
|
2114
|
+
}
|
|
2115
|
+
// Compress each category group
|
|
2116
|
+
const compressed = [];
|
|
2117
|
+
for (const [category, items] of Object.entries(categoryGroups)) {
|
|
2118
|
+
const summary = {
|
|
2119
|
+
category,
|
|
2120
|
+
count: items.length,
|
|
2121
|
+
priorities: { high: 0, normal: 0, low: 0 },
|
|
2122
|
+
keys: items.map((i) => i.key),
|
|
2123
|
+
samples: items
|
|
2124
|
+
.slice(0, 3)
|
|
2125
|
+
.map((i) => ({ key: i.key, value: i.value.substring(0, 100) })),
|
|
2126
|
+
};
|
|
2127
|
+
for (const item of items) {
|
|
2128
|
+
const priority = (item.priority || 'normal');
|
|
2129
|
+
summary.priorities[priority]++;
|
|
2130
|
+
}
|
|
2131
|
+
compressed.push(summary);
|
|
2132
|
+
}
|
|
2133
|
+
// Calculate compression
|
|
2134
|
+
const originalSize = JSON.stringify(itemsToCompress).length;
|
|
2135
|
+
const compressedData = JSON.stringify(compressed);
|
|
2136
|
+
const compressedSize = compressedData.length;
|
|
2137
|
+
const compressionRatio = 1 - compressedSize / originalSize;
|
|
2138
|
+
// Store compressed data
|
|
2139
|
+
const compressedId = (0, uuid_1.v4)();
|
|
2140
|
+
const dateRange = itemsToCompress.reduce((acc, item) => {
|
|
2141
|
+
const date = new Date(item.created_at);
|
|
2142
|
+
if (!acc.start || date < acc.start)
|
|
2143
|
+
acc.start = date;
|
|
2144
|
+
if (!acc.end || date > acc.end)
|
|
2145
|
+
acc.end = date;
|
|
2146
|
+
return acc;
|
|
2147
|
+
}, { start: null, end: null });
|
|
2148
|
+
db.prepare(`
|
|
2149
|
+
INSERT INTO compressed_context (id, session_id, original_count, compressed_data, compression_ratio, date_range_start, date_range_end)
|
|
2150
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
2151
|
+
`).run(compressedId, targetSessionId, itemsToCompress.length, compressedData, compressionRatio, dateRange.start?.toISOString(), dateRange.end?.toISOString());
|
|
2152
|
+
// Delete original items
|
|
2153
|
+
const deleteStmt = db.prepare('DELETE FROM context_items WHERE id = ?');
|
|
2154
|
+
for (const item of itemsToCompress) {
|
|
2155
|
+
deleteStmt.run(item.id);
|
|
2156
|
+
}
|
|
2157
|
+
return {
|
|
2158
|
+
content: [
|
|
2159
|
+
{
|
|
2160
|
+
type: 'text',
|
|
2161
|
+
text: `Compression completed!
|
|
2162
|
+
Items compressed: ${itemsToCompress.length}
|
|
2163
|
+
Original size: ${(originalSize / 1024).toFixed(2)} KB
|
|
2164
|
+
Compressed size: ${(compressedSize / 1024).toFixed(2)} KB
|
|
2165
|
+
Compression ratio: ${(compressionRatio * 100).toFixed(1)}%
|
|
2166
|
+
Date range: ${dateRange.start?.toISOString().substring(0, 10)} to ${dateRange.end?.toISOString().substring(0, 10)}
|
|
2167
|
+
|
|
2168
|
+
Categories compressed:
|
|
2169
|
+
${Object.entries(categoryGroups)
|
|
2170
|
+
.map(([cat, items]) => `- ${cat}: ${items.length} items`)
|
|
2171
|
+
.join('\n')}
|
|
2172
|
+
|
|
2173
|
+
Compressed data ID: ${compressedId.substring(0, 8)}`,
|
|
2174
|
+
},
|
|
2175
|
+
],
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
catch (error) {
|
|
2179
|
+
return {
|
|
2180
|
+
content: [
|
|
2181
|
+
{
|
|
2182
|
+
type: 'text',
|
|
2183
|
+
text: `Compression failed: ${error.message}`,
|
|
2184
|
+
},
|
|
2185
|
+
],
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
// Phase 4.4: Cross-Tool Integration
|
|
2190
|
+
case 'context_integrate_tool': {
|
|
2191
|
+
const { toolName, eventType, data } = args;
|
|
2192
|
+
const sessionId = ensureSession();
|
|
2193
|
+
try {
|
|
2194
|
+
const id = (0, uuid_1.v4)();
|
|
2195
|
+
db.prepare(`
|
|
2196
|
+
INSERT INTO tool_events (id, session_id, tool_name, event_type, data)
|
|
2197
|
+
VALUES (?, ?, ?, ?, ?)
|
|
2198
|
+
`).run(id, sessionId, toolName, eventType, JSON.stringify(data));
|
|
2199
|
+
// Optionally create a context item for important events
|
|
2200
|
+
if (data.important || eventType === 'error' || eventType === 'milestone') {
|
|
2201
|
+
db.prepare(`
|
|
2202
|
+
INSERT INTO context_items (id, session_id, key, value, category, priority)
|
|
2203
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
2204
|
+
`).run((0, uuid_1.v4)(), sessionId, `${toolName}_${eventType}_${Date.now()}`, `Tool event: ${toolName} - ${eventType}: ${JSON.stringify(data)}`, 'tool_event', data.important ? 'high' : 'normal');
|
|
2205
|
+
}
|
|
2206
|
+
return {
|
|
2207
|
+
content: [
|
|
2208
|
+
{
|
|
2209
|
+
type: 'text',
|
|
2210
|
+
text: `Tool event recorded!
|
|
2211
|
+
Tool: ${toolName}
|
|
2212
|
+
Event: ${eventType}
|
|
2213
|
+
Data recorded: ${JSON.stringify(data).length} bytes
|
|
2214
|
+
Event ID: ${id.substring(0, 8)}`,
|
|
2215
|
+
},
|
|
2216
|
+
],
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
catch (error) {
|
|
2220
|
+
return {
|
|
2221
|
+
content: [
|
|
2222
|
+
{
|
|
2223
|
+
type: 'text',
|
|
2224
|
+
text: `Tool integration failed: ${error.message}`,
|
|
2225
|
+
},
|
|
2226
|
+
],
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
// Cross-Session Collaboration Tools
|
|
2231
|
+
// REMOVED: Sharing is now automatic (public by default)
|
|
2232
|
+
/*
|
|
2233
|
+
case 'context_share': {
|
|
2234
|
+
const { key, targetSessions, makePublic = false } = args;
|
|
2235
|
+
const sessionId = ensureSession();
|
|
2236
|
+
|
|
2237
|
+
try {
|
|
2238
|
+
// Get the item to share
|
|
2239
|
+
const item = repositories.contexts.getByKey(sessionId, key);
|
|
2240
|
+
if (!item) {
|
|
2241
|
+
return {
|
|
2242
|
+
content: [{
|
|
2243
|
+
type: 'text',
|
|
2244
|
+
text: `Item not found: ${key}`,
|
|
2245
|
+
}],
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
// Share with specific sessions or make public
|
|
2250
|
+
const targetSessionIds = makePublic ? [] : (targetSessions || []);
|
|
2251
|
+
repositories.contexts.shareByKey(sessionId, key, targetSessionIds);
|
|
2252
|
+
|
|
2253
|
+
return {
|
|
2254
|
+
content: [{
|
|
2255
|
+
type: 'text',
|
|
2256
|
+
text: `Shared "${key}" ${makePublic ? 'publicly' : `with ${targetSessionIds.length} session(s)`}`,
|
|
2257
|
+
}],
|
|
2258
|
+
};
|
|
2259
|
+
} catch (error: any) {
|
|
2260
|
+
return {
|
|
2261
|
+
content: [{
|
|
2262
|
+
type: 'text',
|
|
2263
|
+
text: `Failed to share context: ${error.message}`,
|
|
2264
|
+
}],
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
*/
|
|
2269
|
+
// REMOVED: All accessible items are retrieved via context_get
|
|
2270
|
+
/*
|
|
2271
|
+
case 'context_get_shared': {
|
|
2272
|
+
const { includeAll = false } = args;
|
|
2273
|
+
const sessionId = ensureSession();
|
|
2274
|
+
|
|
2275
|
+
try {
|
|
2276
|
+
const items = includeAll
|
|
2277
|
+
? repositories.contexts.getAllSharedItems()
|
|
2278
|
+
: repositories.contexts.getSharedItems(sessionId);
|
|
2279
|
+
|
|
2280
|
+
if (items.length === 0) {
|
|
2281
|
+
return {
|
|
2282
|
+
content: [{
|
|
2283
|
+
type: 'text',
|
|
2284
|
+
text: 'No shared context items found',
|
|
2285
|
+
}],
|
|
2286
|
+
};
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
const itemsList = items.map((item: any) => {
|
|
2290
|
+
const sharedWith = item.shared_with_sessions
|
|
2291
|
+
? JSON.parse(item.shared_with_sessions).length
|
|
2292
|
+
: 'all';
|
|
2293
|
+
return `• [${item.priority}] ${item.key} (from session: ${item.session_id.substring(0, 8)}, shared with: ${sharedWith})\n ${item.value.substring(0, 100)}${item.value.length > 100 ? '...' : ''}`;
|
|
2294
|
+
}).join('\n\n');
|
|
2295
|
+
|
|
2296
|
+
return {
|
|
2297
|
+
content: [{
|
|
2298
|
+
type: 'text',
|
|
2299
|
+
text: `Found ${items.length} shared items:\n\n${itemsList}`,
|
|
2300
|
+
}],
|
|
2301
|
+
};
|
|
2302
|
+
} catch (error: any) {
|
|
2303
|
+
return {
|
|
2304
|
+
content: [{
|
|
2305
|
+
type: 'text',
|
|
2306
|
+
text: `Failed to get shared context: ${error.message}`,
|
|
2307
|
+
}],
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
*/
|
|
2312
|
+
case 'context_search_all': {
|
|
2313
|
+
const { query, sessions, includeShared = true, limit: rawLimit = 25, offset: rawOffset = 0, sort = 'created_desc', category, channel, channels, priorities, createdAfter, createdBefore, keyPattern, searchIn = ['key', 'value'], includeMetadata = false, } = args;
|
|
2314
|
+
// Enhanced pagination validation with proper error handling
|
|
2315
|
+
const paginationValidation = validatePaginationParams({ limit: rawLimit, offset: rawOffset });
|
|
2316
|
+
const { limit, offset, errors: paginationErrors } = paginationValidation;
|
|
2317
|
+
const currentSession = currentSessionId || ensureSession();
|
|
2318
|
+
// Log pagination validation errors for debugging
|
|
2319
|
+
if (paginationErrors.length > 0) {
|
|
2320
|
+
debugLog('Pagination validation errors:', paginationErrors);
|
|
2321
|
+
}
|
|
2322
|
+
try {
|
|
2323
|
+
// Use enhanced search across sessions with pagination
|
|
2324
|
+
const result = repositories.contexts.searchAcrossSessionsEnhanced({
|
|
2325
|
+
query,
|
|
2326
|
+
currentSessionId: currentSession,
|
|
2327
|
+
sessions,
|
|
2328
|
+
includeShared,
|
|
2329
|
+
searchIn,
|
|
2330
|
+
limit,
|
|
2331
|
+
offset,
|
|
2332
|
+
sort,
|
|
2333
|
+
category,
|
|
2334
|
+
channel,
|
|
2335
|
+
channels,
|
|
2336
|
+
priorities,
|
|
2337
|
+
createdAfter,
|
|
2338
|
+
createdBefore,
|
|
2339
|
+
keyPattern,
|
|
2340
|
+
includeMetadata,
|
|
2341
|
+
});
|
|
2342
|
+
// PAGINATION VALIDATION: Ensure pagination is working as expected
|
|
2343
|
+
if (result.items.length > limit && limit < result.totalCount) {
|
|
2344
|
+
debugLog(`Pagination warning: Expected max ${limit} items, got ${result.items.length}. This may indicate a pagination implementation issue.`);
|
|
2345
|
+
}
|
|
2346
|
+
if (result.items.length === 0) {
|
|
2347
|
+
return {
|
|
2348
|
+
content: [
|
|
2349
|
+
{
|
|
2350
|
+
type: 'text',
|
|
2351
|
+
text: `No results found for: "${query}"${result.totalCount > 0 ? ` (showing page ${result.pagination.currentPage} of ${result.pagination.totalPages})` : ''}`,
|
|
2352
|
+
},
|
|
2353
|
+
],
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
const resultsList = result.items
|
|
2357
|
+
.map((item) => `• [${item.session_id.substring(0, 8)}] ${item.key}: ${item.value.substring(0, 100)}${item.value.length > 100 ? '...' : ''}`)
|
|
2358
|
+
.join('\n');
|
|
2359
|
+
// Build pagination info
|
|
2360
|
+
const paginationInfo = result.pagination.totalPages > 1
|
|
2361
|
+
? `\n\nPagination: Page ${result.pagination.currentPage} of ${result.pagination.totalPages} (${result.pagination.totalItems} total items)${result.pagination.hasNextPage
|
|
2362
|
+
? `\nNext page: offset=${result.pagination.nextOffset}, limit=${result.pagination.itemsPerPage}`
|
|
2363
|
+
: ''}${result.pagination.hasPreviousPage
|
|
2364
|
+
? `\nPrevious page: offset=${result.pagination.previousOffset}, limit=${result.pagination.itemsPerPage}`
|
|
2365
|
+
: ''}`
|
|
2366
|
+
: '';
|
|
2367
|
+
return {
|
|
2368
|
+
content: [
|
|
2369
|
+
{
|
|
2370
|
+
type: 'text',
|
|
2371
|
+
text: `Found ${result.items.length} results on this page (${result.totalCount} total across sessions):\n\n${resultsList}${paginationInfo}`,
|
|
2372
|
+
},
|
|
2373
|
+
],
|
|
2374
|
+
};
|
|
2375
|
+
}
|
|
2376
|
+
catch (error) {
|
|
2377
|
+
// Enhanced error handling to distinguish pagination errors from search errors
|
|
2378
|
+
let errorMessage = 'Search failed';
|
|
2379
|
+
if (paginationErrors.length > 0) {
|
|
2380
|
+
errorMessage = `Search failed due to pagination validation errors: ${paginationErrors.join(', ')}. ${error.message}`;
|
|
2381
|
+
}
|
|
2382
|
+
else if (error.message.includes('pagination') ||
|
|
2383
|
+
error.message.includes('limit') ||
|
|
2384
|
+
error.message.includes('offset')) {
|
|
2385
|
+
errorMessage = `Search failed due to pagination parameter issue: ${error.message}`;
|
|
2386
|
+
}
|
|
2387
|
+
else {
|
|
2388
|
+
errorMessage = `Search failed: ${error.message}`;
|
|
2389
|
+
}
|
|
2390
|
+
debugLog('Search error:', { error: error.message, paginationErrors, limit, offset });
|
|
2391
|
+
return {
|
|
2392
|
+
content: [
|
|
2393
|
+
{
|
|
2394
|
+
type: 'text',
|
|
2395
|
+
text: errorMessage,
|
|
2396
|
+
},
|
|
2397
|
+
],
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
// Context Diff - Track changes since a specific point in time
|
|
2402
|
+
case 'context_diff': {
|
|
2403
|
+
const { since, sessionId: specificSessionId, category, channel, channels, includeValues = true, limit, offset, } = args;
|
|
2404
|
+
const targetSessionId = specificSessionId || currentSessionId || ensureSession();
|
|
2405
|
+
try {
|
|
2406
|
+
// Parse the 'since' parameter
|
|
2407
|
+
let sinceTimestamp = null;
|
|
2408
|
+
let checkpointId = null;
|
|
2409
|
+
if (since) {
|
|
2410
|
+
// Check if it's a checkpoint name or ID
|
|
2411
|
+
const checkpointByName = db
|
|
2412
|
+
.prepare('SELECT * FROM checkpoints WHERE name = ? ORDER BY created_at DESC LIMIT 1')
|
|
2413
|
+
.get(since);
|
|
2414
|
+
const checkpointById = !checkpointByName
|
|
2415
|
+
? db.prepare('SELECT * FROM checkpoints WHERE id = ?').get(since)
|
|
2416
|
+
: null;
|
|
2417
|
+
const checkpoint = checkpointByName || checkpointById;
|
|
2418
|
+
if (checkpoint) {
|
|
2419
|
+
checkpointId = checkpoint.id;
|
|
2420
|
+
sinceTimestamp = checkpoint.created_at;
|
|
2421
|
+
}
|
|
2422
|
+
else {
|
|
2423
|
+
// Try to parse as relative time
|
|
2424
|
+
const parsedTime = parseRelativeTime(since);
|
|
2425
|
+
if (parsedTime) {
|
|
2426
|
+
sinceTimestamp = parsedTime;
|
|
2427
|
+
}
|
|
2428
|
+
else {
|
|
2429
|
+
// Assume it's an ISO timestamp
|
|
2430
|
+
sinceTimestamp = since;
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
else {
|
|
2435
|
+
// Default to 1 hour ago if no 'since' provided
|
|
2436
|
+
sinceTimestamp = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
|
2437
|
+
}
|
|
2438
|
+
// Convert ISO timestamp to SQLite format for repository compatibility
|
|
2439
|
+
const sqliteTimestamp = (0, timestamps_js_1.ensureSQLiteFormat)(sinceTimestamp);
|
|
2440
|
+
// Use repository method to get diff data
|
|
2441
|
+
const diffData = repositories.contexts.getDiff({
|
|
2442
|
+
sessionId: targetSessionId,
|
|
2443
|
+
sinceTimestamp: sqliteTimestamp,
|
|
2444
|
+
category,
|
|
2445
|
+
channel,
|
|
2446
|
+
channels,
|
|
2447
|
+
limit,
|
|
2448
|
+
offset,
|
|
2449
|
+
includeValues,
|
|
2450
|
+
});
|
|
2451
|
+
// Handle deleted items if we have a checkpoint
|
|
2452
|
+
let deletedKeys = [];
|
|
2453
|
+
if (checkpointId) {
|
|
2454
|
+
deletedKeys = repositories.contexts.getDeletedKeysFromCheckpoint(targetSessionId, checkpointId);
|
|
2455
|
+
}
|
|
2456
|
+
// Format response
|
|
2457
|
+
const toDate = new Date().toISOString();
|
|
2458
|
+
const response = {
|
|
2459
|
+
added: includeValues
|
|
2460
|
+
? diffData.added
|
|
2461
|
+
: diffData.added.map(i => ({ key: i.key, category: i.category })),
|
|
2462
|
+
modified: includeValues
|
|
2463
|
+
? diffData.modified
|
|
2464
|
+
: diffData.modified.map(i => ({ key: i.key, category: i.category })),
|
|
2465
|
+
deleted: deletedKeys,
|
|
2466
|
+
summary: `${diffData.added.length} added, ${diffData.modified.length} modified, ${deletedKeys.length} deleted`,
|
|
2467
|
+
period: {
|
|
2468
|
+
from: sinceTimestamp,
|
|
2469
|
+
to: toDate,
|
|
2470
|
+
},
|
|
2471
|
+
};
|
|
2472
|
+
return {
|
|
2473
|
+
content: [
|
|
2474
|
+
{
|
|
2475
|
+
type: 'text',
|
|
2476
|
+
text: JSON.stringify(response, null, 2),
|
|
2477
|
+
},
|
|
2478
|
+
],
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2481
|
+
catch (error) {
|
|
2482
|
+
return {
|
|
2483
|
+
content: [
|
|
2484
|
+
{
|
|
2485
|
+
type: 'text',
|
|
2486
|
+
text: `Failed to get context diff: ${error.message}`,
|
|
2487
|
+
},
|
|
2488
|
+
],
|
|
2489
|
+
};
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
// Channel Management
|
|
2493
|
+
case 'context_list_channels': {
|
|
2494
|
+
const { sessionId, sessionIds, sort, includeEmpty } = args;
|
|
2495
|
+
try {
|
|
2496
|
+
const channels = repositories.contexts.listChannels({
|
|
2497
|
+
sessionId: sessionId || currentSessionId,
|
|
2498
|
+
sessionIds,
|
|
2499
|
+
sort,
|
|
2500
|
+
includeEmpty,
|
|
2501
|
+
});
|
|
2502
|
+
if (channels.length === 0) {
|
|
2503
|
+
return {
|
|
2504
|
+
content: [
|
|
2505
|
+
{
|
|
2506
|
+
type: 'text',
|
|
2507
|
+
text: 'No channels found.',
|
|
2508
|
+
},
|
|
2509
|
+
],
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
// Format the response
|
|
2513
|
+
const channelList = channels
|
|
2514
|
+
.map((ch) => `• ${ch.channel}: ${ch.total_count} items (${ch.public_count} public, ${ch.private_count} private)\n Last activity: ${new Date(ch.last_activity).toLocaleString()}\n Categories: ${ch.categories.join(', ') || 'none'}\n Sessions: ${ch.session_count}`)
|
|
2515
|
+
.join('\n\n');
|
|
2516
|
+
return {
|
|
2517
|
+
content: [
|
|
2518
|
+
{
|
|
2519
|
+
type: 'text',
|
|
2520
|
+
text: `Found ${channels.length} channels:\n\n${channelList}`,
|
|
2521
|
+
},
|
|
2522
|
+
],
|
|
2523
|
+
};
|
|
2524
|
+
}
|
|
2525
|
+
catch (error) {
|
|
2526
|
+
return {
|
|
2527
|
+
content: [
|
|
2528
|
+
{
|
|
2529
|
+
type: 'text',
|
|
2530
|
+
text: `Failed to list channels: ${error.message}`,
|
|
2531
|
+
},
|
|
2532
|
+
],
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
case 'context_channel_stats': {
|
|
2537
|
+
const { channel, sessionId, includeTimeSeries, includeInsights } = args;
|
|
2538
|
+
try {
|
|
2539
|
+
const stats = repositories.contexts.getChannelStats({
|
|
2540
|
+
channel,
|
|
2541
|
+
sessionId: sessionId || currentSessionId,
|
|
2542
|
+
includeTimeSeries,
|
|
2543
|
+
includeInsights,
|
|
2544
|
+
});
|
|
2545
|
+
return {
|
|
2546
|
+
content: [
|
|
2547
|
+
{
|
|
2548
|
+
type: 'text',
|
|
2549
|
+
text: JSON.stringify(stats, null, 2),
|
|
2550
|
+
},
|
|
2551
|
+
],
|
|
2552
|
+
};
|
|
2553
|
+
}
|
|
2554
|
+
catch (error) {
|
|
2555
|
+
return {
|
|
2556
|
+
content: [
|
|
2557
|
+
{
|
|
2558
|
+
type: 'text',
|
|
2559
|
+
text: `Failed to get channel stats: ${error.message}`,
|
|
2560
|
+
},
|
|
2561
|
+
],
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
// Context Watch functionality
|
|
2566
|
+
case 'context_watch': {
|
|
2567
|
+
return await (0, contextWatchHandlers_js_1.handleContextWatch)(args, repositories, ensureSession());
|
|
2568
|
+
}
|
|
2569
|
+
// Context Reassign Channel
|
|
2570
|
+
case 'context_reassign_channel': {
|
|
2571
|
+
const { keys, keyPattern, fromChannel, toChannel, sessionId, category, priorities, dryRun = false, } = args;
|
|
2572
|
+
try {
|
|
2573
|
+
// Validate input
|
|
2574
|
+
if (!toChannel || !toChannel.trim()) {
|
|
2575
|
+
throw new Error('Target channel name cannot be empty');
|
|
2576
|
+
}
|
|
2577
|
+
if (!keys && !keyPattern && !fromChannel) {
|
|
2578
|
+
throw new Error('Must provide either keys array, keyPattern, or fromChannel');
|
|
2579
|
+
}
|
|
2580
|
+
if (fromChannel && fromChannel === toChannel) {
|
|
2581
|
+
throw new Error('Source and destination channels cannot be the same');
|
|
2582
|
+
}
|
|
2583
|
+
const targetSessionId = sessionId || ensureSession();
|
|
2584
|
+
// Call repository method
|
|
2585
|
+
const result = await repositories.contexts.reassignChannel({
|
|
2586
|
+
keys,
|
|
2587
|
+
keyPattern,
|
|
2588
|
+
fromChannel,
|
|
2589
|
+
toChannel,
|
|
2590
|
+
sessionId: targetSessionId,
|
|
2591
|
+
category,
|
|
2592
|
+
priorities,
|
|
2593
|
+
dryRun,
|
|
2594
|
+
});
|
|
2595
|
+
return {
|
|
2596
|
+
content: [
|
|
2597
|
+
{
|
|
2598
|
+
type: 'text',
|
|
2599
|
+
text: JSON.stringify(result, null, 2),
|
|
2600
|
+
},
|
|
2601
|
+
],
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
catch (error) {
|
|
2605
|
+
return {
|
|
2606
|
+
content: [
|
|
2607
|
+
{
|
|
2608
|
+
type: 'text',
|
|
2609
|
+
text: `Failed to reassign channel: ${error.message}`,
|
|
2610
|
+
},
|
|
2611
|
+
],
|
|
2612
|
+
};
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
// Batch Operations
|
|
2616
|
+
case 'context_batch_save': {
|
|
2617
|
+
const { items, updateExisting = true } = args;
|
|
2618
|
+
const sessionId = ensureSession();
|
|
2619
|
+
// Validate items
|
|
2620
|
+
if (!items || !Array.isArray(items) || items.length === 0) {
|
|
2621
|
+
return {
|
|
2622
|
+
content: [
|
|
2623
|
+
{
|
|
2624
|
+
type: 'text',
|
|
2625
|
+
text: 'No items provided for batch save',
|
|
2626
|
+
},
|
|
2627
|
+
],
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
// Enforce batch size limit
|
|
2631
|
+
const maxBatchSize = 100;
|
|
2632
|
+
if (items.length > maxBatchSize) {
|
|
2633
|
+
return {
|
|
2634
|
+
content: [
|
|
2635
|
+
{
|
|
2636
|
+
type: 'text',
|
|
2637
|
+
text: `Batch size ${items.length} exceeds maximum allowed size of ${maxBatchSize}`,
|
|
2638
|
+
},
|
|
2639
|
+
],
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
// Validate items
|
|
2643
|
+
const validationErrors = [];
|
|
2644
|
+
items.forEach((item, index) => {
|
|
2645
|
+
try {
|
|
2646
|
+
// Validate item
|
|
2647
|
+
if (!item.key || !item.key.trim()) {
|
|
2648
|
+
throw new Error('Key is required and cannot be empty');
|
|
2649
|
+
}
|
|
2650
|
+
if (!item.value) {
|
|
2651
|
+
throw new Error('Value is required');
|
|
2652
|
+
}
|
|
2653
|
+
// Validate category
|
|
2654
|
+
if (item.category) {
|
|
2655
|
+
const validCategories = ['task', 'decision', 'progress', 'note', 'error', 'warning'];
|
|
2656
|
+
if (!validCategories.includes(item.category)) {
|
|
2657
|
+
throw new Error(`Invalid category: ${item.category}`);
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
// Validate priority
|
|
2661
|
+
if (item.priority) {
|
|
2662
|
+
const validPriorities = ['high', 'normal', 'low'];
|
|
2663
|
+
if (!validPriorities.includes(item.priority)) {
|
|
2664
|
+
throw new Error(`Invalid priority: ${item.priority}`);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
catch (error) {
|
|
2669
|
+
validationErrors.push({
|
|
2670
|
+
index,
|
|
2671
|
+
key: item.key || 'undefined',
|
|
2672
|
+
error: error.message,
|
|
2673
|
+
});
|
|
2674
|
+
}
|
|
2675
|
+
});
|
|
2676
|
+
// If all items have validation errors, return early
|
|
2677
|
+
if (validationErrors.length === items.length) {
|
|
2678
|
+
return {
|
|
2679
|
+
content: [
|
|
2680
|
+
{
|
|
2681
|
+
type: 'text',
|
|
2682
|
+
text: JSON.stringify({
|
|
2683
|
+
operation: 'batch_save',
|
|
2684
|
+
totalItems: items.length,
|
|
2685
|
+
succeeded: 0,
|
|
2686
|
+
failed: validationErrors.length,
|
|
2687
|
+
totalSize: 0,
|
|
2688
|
+
results: [],
|
|
2689
|
+
errors: validationErrors,
|
|
2690
|
+
timestamp: new Date().toISOString(),
|
|
2691
|
+
}, null, 2),
|
|
2692
|
+
},
|
|
2693
|
+
],
|
|
2694
|
+
};
|
|
2695
|
+
}
|
|
2696
|
+
let results = [];
|
|
2697
|
+
let errors = [];
|
|
2698
|
+
let totalSize = 0;
|
|
2699
|
+
// Begin transaction
|
|
2700
|
+
db.prepare('BEGIN TRANSACTION').run();
|
|
2701
|
+
try {
|
|
2702
|
+
// Use repository method
|
|
2703
|
+
const batchResult = repositories.contexts.batchSave(sessionId, items, { updateExisting });
|
|
2704
|
+
totalSize = batchResult.totalSize;
|
|
2705
|
+
// Merge validation errors with operation results
|
|
2706
|
+
const allResults = batchResult.results.filter(r => r.success);
|
|
2707
|
+
const allErrors = [
|
|
2708
|
+
...validationErrors,
|
|
2709
|
+
...batchResult.results
|
|
2710
|
+
.filter(r => !r.success)
|
|
2711
|
+
.map(r => ({
|
|
2712
|
+
index: r.index,
|
|
2713
|
+
key: r.key,
|
|
2714
|
+
error: r.error,
|
|
2715
|
+
})),
|
|
2716
|
+
];
|
|
2717
|
+
// Commit transaction
|
|
2718
|
+
db.prepare('COMMIT').run();
|
|
2719
|
+
// Create embeddings for successful saves (async, don't wait)
|
|
2720
|
+
allResults.forEach(async (result) => {
|
|
2721
|
+
if (result.success && result.action === 'created') {
|
|
2722
|
+
try {
|
|
2723
|
+
const item = items[result.index];
|
|
2724
|
+
const content = `${item.key}: ${item.value}`;
|
|
2725
|
+
const metadata = { key: item.key, category: item.category, priority: item.priority };
|
|
2726
|
+
await vectorStore.storeDocument(result.id, content, metadata);
|
|
2727
|
+
}
|
|
2728
|
+
catch (error) {
|
|
2729
|
+
// Log but don't fail
|
|
2730
|
+
console.error('Failed to create embedding:', error);
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
});
|
|
2734
|
+
results = allResults;
|
|
2735
|
+
errors = allErrors;
|
|
2736
|
+
}
|
|
2737
|
+
catch (error) {
|
|
2738
|
+
// Rollback transaction
|
|
2739
|
+
db.prepare('ROLLBACK').run();
|
|
2740
|
+
return {
|
|
2741
|
+
content: [
|
|
2742
|
+
{
|
|
2743
|
+
type: 'text',
|
|
2744
|
+
text: `Batch save failed: ${error.message}`,
|
|
2745
|
+
},
|
|
2746
|
+
],
|
|
2747
|
+
};
|
|
2748
|
+
}
|
|
2749
|
+
// Prepare response
|
|
2750
|
+
const response = {
|
|
2751
|
+
operation: 'batch_save',
|
|
2752
|
+
totalItems: items.length,
|
|
2753
|
+
succeeded: results.length,
|
|
2754
|
+
failed: errors.length,
|
|
2755
|
+
totalSize: totalSize,
|
|
2756
|
+
averageSize: results.length > 0 ? Math.round(totalSize / results.length) : 0,
|
|
2757
|
+
results: results,
|
|
2758
|
+
errors: errors,
|
|
2759
|
+
timestamp: new Date().toISOString(),
|
|
2760
|
+
};
|
|
2761
|
+
return {
|
|
2762
|
+
content: [
|
|
2763
|
+
{
|
|
2764
|
+
type: 'text',
|
|
2765
|
+
text: JSON.stringify(response, null, 2),
|
|
2766
|
+
},
|
|
2767
|
+
],
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2770
|
+
case 'context_batch_delete': {
|
|
2771
|
+
const { keys, keyPattern, sessionId: specificSessionId, dryRun = false } = args;
|
|
2772
|
+
const targetSessionId = specificSessionId || currentSessionId || ensureSession();
|
|
2773
|
+
// Validate input
|
|
2774
|
+
if (!keys && !keyPattern) {
|
|
2775
|
+
return {
|
|
2776
|
+
content: [
|
|
2777
|
+
{
|
|
2778
|
+
type: 'text',
|
|
2779
|
+
text: 'Either keys array or keyPattern must be provided',
|
|
2780
|
+
},
|
|
2781
|
+
],
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
if (keys && (!Array.isArray(keys) || keys.length === 0)) {
|
|
2785
|
+
return {
|
|
2786
|
+
content: [
|
|
2787
|
+
{
|
|
2788
|
+
type: 'text',
|
|
2789
|
+
text: 'Keys must be a non-empty array',
|
|
2790
|
+
},
|
|
2791
|
+
],
|
|
2792
|
+
};
|
|
2793
|
+
}
|
|
2794
|
+
let results = [];
|
|
2795
|
+
let totalDeleted = 0;
|
|
2796
|
+
try {
|
|
2797
|
+
if (dryRun) {
|
|
2798
|
+
// Dry run - just show what would be deleted
|
|
2799
|
+
const itemsToDelete = repositories.contexts.getDryRunItems(targetSessionId, {
|
|
2800
|
+
keys,
|
|
2801
|
+
keyPattern,
|
|
2802
|
+
});
|
|
2803
|
+
return {
|
|
2804
|
+
content: [
|
|
2805
|
+
{
|
|
2806
|
+
type: 'text',
|
|
2807
|
+
text: JSON.stringify({
|
|
2808
|
+
operation: 'batch_delete',
|
|
2809
|
+
dryRun: true,
|
|
2810
|
+
keys: keys,
|
|
2811
|
+
pattern: keyPattern,
|
|
2812
|
+
itemsToDelete: itemsToDelete,
|
|
2813
|
+
totalItems: itemsToDelete.length,
|
|
2814
|
+
}, null, 2),
|
|
2815
|
+
},
|
|
2816
|
+
],
|
|
2817
|
+
};
|
|
2818
|
+
}
|
|
2819
|
+
// Actual deletion
|
|
2820
|
+
db.prepare('BEGIN TRANSACTION').run();
|
|
2821
|
+
const deleteResult = repositories.contexts.batchDelete(targetSessionId, {
|
|
2822
|
+
keys,
|
|
2823
|
+
keyPattern,
|
|
2824
|
+
});
|
|
2825
|
+
results = deleteResult.results || [];
|
|
2826
|
+
totalDeleted = deleteResult.totalDeleted;
|
|
2827
|
+
db.prepare('COMMIT').run();
|
|
2828
|
+
}
|
|
2829
|
+
catch (error) {
|
|
2830
|
+
db.prepare('ROLLBACK').run();
|
|
2831
|
+
return {
|
|
2832
|
+
content: [
|
|
2833
|
+
{
|
|
2834
|
+
type: 'text',
|
|
2835
|
+
text: `Batch delete failed: ${error.message}`,
|
|
2836
|
+
},
|
|
2837
|
+
],
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
// Prepare response
|
|
2841
|
+
const response = keys
|
|
2842
|
+
? {
|
|
2843
|
+
operation: 'batch_delete',
|
|
2844
|
+
keys: keys,
|
|
2845
|
+
totalRequested: keys.length,
|
|
2846
|
+
totalDeleted: totalDeleted,
|
|
2847
|
+
notFound: results.filter(r => !r.deleted).map(r => r.key),
|
|
2848
|
+
results: results,
|
|
2849
|
+
}
|
|
2850
|
+
: {
|
|
2851
|
+
operation: 'batch_delete',
|
|
2852
|
+
pattern: keyPattern,
|
|
2853
|
+
totalDeleted: totalDeleted,
|
|
2854
|
+
};
|
|
2855
|
+
return {
|
|
2856
|
+
content: [
|
|
2857
|
+
{
|
|
2858
|
+
type: 'text',
|
|
2859
|
+
text: JSON.stringify(response, null, 2),
|
|
2860
|
+
},
|
|
2861
|
+
],
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2864
|
+
case 'context_batch_update': {
|
|
2865
|
+
const { updates, sessionId: specificSessionId } = args;
|
|
2866
|
+
const targetSessionId = specificSessionId || currentSessionId || ensureSession();
|
|
2867
|
+
// Validate input
|
|
2868
|
+
if (!updates || !Array.isArray(updates) || updates.length === 0) {
|
|
2869
|
+
return {
|
|
2870
|
+
content: [
|
|
2871
|
+
{
|
|
2872
|
+
type: 'text',
|
|
2873
|
+
text: 'Updates array must be provided and non-empty',
|
|
2874
|
+
},
|
|
2875
|
+
],
|
|
2876
|
+
};
|
|
2877
|
+
}
|
|
2878
|
+
// Validate updates
|
|
2879
|
+
const validationErrors = [];
|
|
2880
|
+
updates.forEach((update, index) => {
|
|
2881
|
+
try {
|
|
2882
|
+
// Validate update
|
|
2883
|
+
if (!update.key || !update.key.trim()) {
|
|
2884
|
+
throw new Error('Key is required and cannot be empty');
|
|
2885
|
+
}
|
|
2886
|
+
// Check if any updates are provided
|
|
2887
|
+
const hasUpdates = update.value !== undefined ||
|
|
2888
|
+
update.category !== undefined ||
|
|
2889
|
+
update.priority !== undefined ||
|
|
2890
|
+
update.channel !== undefined;
|
|
2891
|
+
if (!hasUpdates) {
|
|
2892
|
+
throw new Error('No updates provided');
|
|
2893
|
+
}
|
|
2894
|
+
// Validate fields if provided
|
|
2895
|
+
if (update.category !== undefined) {
|
|
2896
|
+
const validCategories = ['task', 'decision', 'progress', 'note', 'error', 'warning'];
|
|
2897
|
+
if (!validCategories.includes(update.category)) {
|
|
2898
|
+
throw new Error(`Invalid category: ${update.category}`);
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
if (update.priority !== undefined) {
|
|
2902
|
+
const validPriorities = ['high', 'normal', 'low'];
|
|
2903
|
+
if (!validPriorities.includes(update.priority)) {
|
|
2904
|
+
throw new Error(`Invalid priority: ${update.priority}`);
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
if (update.value !== undefined && update.value === '') {
|
|
2908
|
+
throw new Error('Value cannot be empty');
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
catch (error) {
|
|
2912
|
+
validationErrors.push({
|
|
2913
|
+
index,
|
|
2914
|
+
key: update.key || 'undefined',
|
|
2915
|
+
error: error.message,
|
|
2916
|
+
});
|
|
2917
|
+
}
|
|
2918
|
+
});
|
|
2919
|
+
// If all updates have validation errors, return early
|
|
2920
|
+
if (validationErrors.length === updates.length) {
|
|
2921
|
+
return {
|
|
2922
|
+
content: [
|
|
2923
|
+
{
|
|
2924
|
+
type: 'text',
|
|
2925
|
+
text: JSON.stringify({
|
|
2926
|
+
operation: 'batch_update',
|
|
2927
|
+
totalItems: updates.length,
|
|
2928
|
+
succeeded: 0,
|
|
2929
|
+
failed: validationErrors.length,
|
|
2930
|
+
results: [],
|
|
2931
|
+
errors: validationErrors,
|
|
2932
|
+
}, null, 2),
|
|
2933
|
+
},
|
|
2934
|
+
],
|
|
2935
|
+
};
|
|
2936
|
+
}
|
|
2937
|
+
let results = [];
|
|
2938
|
+
let errors = [];
|
|
2939
|
+
// Begin transaction
|
|
2940
|
+
db.prepare('BEGIN TRANSACTION').run();
|
|
2941
|
+
try {
|
|
2942
|
+
// Use repository method
|
|
2943
|
+
const updateResult = repositories.contexts.batchUpdate(targetSessionId, updates);
|
|
2944
|
+
// Merge validation errors with operation results
|
|
2945
|
+
results = updateResult.results.filter(r => r.updated);
|
|
2946
|
+
errors = [
|
|
2947
|
+
...validationErrors,
|
|
2948
|
+
...updateResult.results
|
|
2949
|
+
.filter(r => !r.updated)
|
|
2950
|
+
.map(r => ({
|
|
2951
|
+
index: r.index,
|
|
2952
|
+
key: r.key,
|
|
2953
|
+
error: r.error,
|
|
2954
|
+
})),
|
|
2955
|
+
];
|
|
2956
|
+
// Commit transaction
|
|
2957
|
+
db.prepare('COMMIT').run();
|
|
2958
|
+
}
|
|
2959
|
+
catch (error) {
|
|
2960
|
+
// Rollback transaction
|
|
2961
|
+
db.prepare('ROLLBACK').run();
|
|
2962
|
+
return {
|
|
2963
|
+
content: [
|
|
2964
|
+
{
|
|
2965
|
+
type: 'text',
|
|
2966
|
+
text: `Batch update failed: ${error.message}`,
|
|
2967
|
+
},
|
|
2968
|
+
],
|
|
2969
|
+
};
|
|
2970
|
+
}
|
|
2971
|
+
// Prepare response
|
|
2972
|
+
const response = {
|
|
2973
|
+
operation: 'batch_update',
|
|
2974
|
+
totalItems: updates.length,
|
|
2975
|
+
succeeded: results.length,
|
|
2976
|
+
failed: errors.length,
|
|
2977
|
+
results: results,
|
|
2978
|
+
errors: errors,
|
|
2979
|
+
};
|
|
2980
|
+
return {
|
|
2981
|
+
content: [
|
|
2982
|
+
{
|
|
2983
|
+
type: 'text',
|
|
2984
|
+
text: JSON.stringify(response, null, 2),
|
|
2985
|
+
},
|
|
2986
|
+
],
|
|
2987
|
+
};
|
|
2988
|
+
}
|
|
2989
|
+
// Context Relationships
|
|
2990
|
+
case 'context_link': {
|
|
2991
|
+
const { sourceKey, targetKey, relationship, metadata } = args;
|
|
2992
|
+
const sessionId = currentSessionId || ensureSession();
|
|
2993
|
+
// Validate inputs
|
|
2994
|
+
if (!sourceKey || !sourceKey.trim()) {
|
|
2995
|
+
return {
|
|
2996
|
+
content: [
|
|
2997
|
+
{
|
|
2998
|
+
type: 'text',
|
|
2999
|
+
text: 'Error: sourceKey cannot be empty',
|
|
3000
|
+
},
|
|
3001
|
+
],
|
|
3002
|
+
};
|
|
3003
|
+
}
|
|
3004
|
+
if (!targetKey || !targetKey.trim()) {
|
|
3005
|
+
return {
|
|
3006
|
+
content: [
|
|
3007
|
+
{
|
|
3008
|
+
type: 'text',
|
|
3009
|
+
text: 'Error: targetKey cannot be empty',
|
|
3010
|
+
},
|
|
3011
|
+
],
|
|
3012
|
+
};
|
|
3013
|
+
}
|
|
3014
|
+
if (!relationship || !relationship.trim()) {
|
|
3015
|
+
return {
|
|
3016
|
+
content: [
|
|
3017
|
+
{
|
|
3018
|
+
type: 'text',
|
|
3019
|
+
text: 'Error: relationship cannot be empty',
|
|
3020
|
+
},
|
|
3021
|
+
],
|
|
3022
|
+
};
|
|
3023
|
+
}
|
|
3024
|
+
// Create relationship
|
|
3025
|
+
const result = repositories.contexts.createRelationship({
|
|
3026
|
+
sessionId,
|
|
3027
|
+
sourceKey,
|
|
3028
|
+
targetKey,
|
|
3029
|
+
relationship,
|
|
3030
|
+
metadata,
|
|
3031
|
+
});
|
|
3032
|
+
if (!result.created) {
|
|
3033
|
+
return {
|
|
3034
|
+
content: [
|
|
3035
|
+
{
|
|
3036
|
+
type: 'text',
|
|
3037
|
+
text: `Error: ${result.error}`,
|
|
3038
|
+
},
|
|
3039
|
+
],
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
return {
|
|
3043
|
+
content: [
|
|
3044
|
+
{
|
|
3045
|
+
type: 'text',
|
|
3046
|
+
text: JSON.stringify({
|
|
3047
|
+
operation: 'context_link',
|
|
3048
|
+
relationshipId: result.id,
|
|
3049
|
+
sourceKey,
|
|
3050
|
+
targetKey,
|
|
3051
|
+
relationship,
|
|
3052
|
+
metadata,
|
|
3053
|
+
created: true,
|
|
3054
|
+
timestamp: new Date().toISOString(),
|
|
3055
|
+
}, null, 2),
|
|
3056
|
+
},
|
|
3057
|
+
],
|
|
3058
|
+
};
|
|
3059
|
+
}
|
|
3060
|
+
case 'context_get_related': {
|
|
3061
|
+
const { key, relationship, depth = 1, direction = 'both' } = args;
|
|
3062
|
+
const sessionId = currentSessionId || ensureSession();
|
|
3063
|
+
if (!key || !key.trim()) {
|
|
3064
|
+
return {
|
|
3065
|
+
content: [
|
|
3066
|
+
{
|
|
3067
|
+
type: 'text',
|
|
3068
|
+
text: 'Error: key cannot be empty',
|
|
3069
|
+
},
|
|
3070
|
+
],
|
|
3071
|
+
};
|
|
3072
|
+
}
|
|
3073
|
+
// Get related items
|
|
3074
|
+
const result = repositories.contexts.getRelatedItems({
|
|
3075
|
+
sessionId,
|
|
3076
|
+
key,
|
|
3077
|
+
relationship,
|
|
3078
|
+
depth,
|
|
3079
|
+
direction,
|
|
3080
|
+
});
|
|
3081
|
+
const totalRelated = result.outgoing.length + result.incoming.length;
|
|
3082
|
+
// Prepare response
|
|
3083
|
+
let response = {
|
|
3084
|
+
operation: 'context_get_related',
|
|
3085
|
+
key,
|
|
3086
|
+
related: {
|
|
3087
|
+
outgoing: result.outgoing,
|
|
3088
|
+
incoming: result.incoming,
|
|
3089
|
+
},
|
|
3090
|
+
totalRelated,
|
|
3091
|
+
};
|
|
3092
|
+
// Add graph data if depth > 1
|
|
3093
|
+
if (depth > 1 && result.graph) {
|
|
3094
|
+
response.visualization = {
|
|
3095
|
+
format: 'graph',
|
|
3096
|
+
nodes: result.graph.nodes,
|
|
3097
|
+
edges: result.graph.edges,
|
|
3098
|
+
};
|
|
3099
|
+
response.summary = {
|
|
3100
|
+
totalNodes: result.graph.nodes.length,
|
|
3101
|
+
totalEdges: result.graph.edges.length,
|
|
3102
|
+
relationshipTypes: [...new Set(result.graph.edges.map((e) => e.type))],
|
|
3103
|
+
};
|
|
3104
|
+
}
|
|
3105
|
+
// Add message if no relationships found
|
|
3106
|
+
if (totalRelated === 0) {
|
|
3107
|
+
response.message = 'No relationships found for this item';
|
|
3108
|
+
}
|
|
3109
|
+
return {
|
|
3110
|
+
content: [
|
|
3111
|
+
{
|
|
3112
|
+
type: 'text',
|
|
3113
|
+
text: JSON.stringify(response, null, 2),
|
|
3114
|
+
},
|
|
3115
|
+
],
|
|
3116
|
+
};
|
|
3117
|
+
}
|
|
3118
|
+
default:
|
|
3119
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
3120
|
+
}
|
|
3121
|
+
});
|
|
3122
|
+
// List available tools
|
|
3123
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
3124
|
+
return {
|
|
3125
|
+
tools: [
|
|
3126
|
+
// Session Management
|
|
3127
|
+
{
|
|
3128
|
+
name: 'context_session_start',
|
|
3129
|
+
description: 'Start a new context session with optional project directory for git tracking',
|
|
3130
|
+
inputSchema: {
|
|
3131
|
+
type: 'object',
|
|
3132
|
+
properties: {
|
|
3133
|
+
name: { type: 'string', description: 'Session name' },
|
|
3134
|
+
description: { type: 'string', description: 'Session description' },
|
|
3135
|
+
continueFrom: { type: 'string', description: 'Session ID to continue from' },
|
|
3136
|
+
projectDir: {
|
|
3137
|
+
type: 'string',
|
|
3138
|
+
description: 'Project directory path for git tracking (e.g., "/path/to/your/project")',
|
|
3139
|
+
},
|
|
3140
|
+
defaultChannel: {
|
|
3141
|
+
type: 'string',
|
|
3142
|
+
description: 'Default channel for context items (auto-derived from git branch if not provided)',
|
|
3143
|
+
},
|
|
3144
|
+
},
|
|
3145
|
+
},
|
|
3146
|
+
},
|
|
3147
|
+
{
|
|
3148
|
+
name: 'context_session_list',
|
|
3149
|
+
description: 'List recent sessions',
|
|
3150
|
+
inputSchema: {
|
|
3151
|
+
type: 'object',
|
|
3152
|
+
properties: {
|
|
3153
|
+
limit: {
|
|
3154
|
+
type: 'number',
|
|
3155
|
+
description: 'Maximum number of sessions to return',
|
|
3156
|
+
default: 10,
|
|
3157
|
+
},
|
|
3158
|
+
},
|
|
3159
|
+
},
|
|
3160
|
+
},
|
|
3161
|
+
{
|
|
3162
|
+
name: 'context_set_project_dir',
|
|
3163
|
+
description: 'Set the project directory for git tracking in the current session',
|
|
3164
|
+
inputSchema: {
|
|
3165
|
+
type: 'object',
|
|
3166
|
+
properties: {
|
|
3167
|
+
projectDir: {
|
|
3168
|
+
type: 'string',
|
|
3169
|
+
description: 'Project directory path for git tracking (e.g., "/path/to/your/project")',
|
|
3170
|
+
},
|
|
3171
|
+
},
|
|
3172
|
+
required: ['projectDir'],
|
|
3173
|
+
},
|
|
3174
|
+
},
|
|
3175
|
+
// Enhanced Context Storage
|
|
3176
|
+
{
|
|
3177
|
+
name: 'context_save',
|
|
3178
|
+
description: 'Save a context item with optional category, priority, and privacy setting',
|
|
3179
|
+
inputSchema: {
|
|
3180
|
+
type: 'object',
|
|
3181
|
+
properties: {
|
|
3182
|
+
key: { type: 'string', description: 'Unique key for the context item' },
|
|
3183
|
+
value: { type: 'string', description: 'Context value to save' },
|
|
3184
|
+
category: {
|
|
3185
|
+
type: 'string',
|
|
3186
|
+
description: 'Category (e.g., task, decision, progress)',
|
|
3187
|
+
enum: ['task', 'decision', 'progress', 'note', 'error', 'warning'],
|
|
3188
|
+
},
|
|
3189
|
+
priority: {
|
|
3190
|
+
type: 'string',
|
|
3191
|
+
description: 'Priority level',
|
|
3192
|
+
enum: ['high', 'normal', 'low'],
|
|
3193
|
+
default: 'normal',
|
|
3194
|
+
},
|
|
3195
|
+
private: {
|
|
3196
|
+
type: 'boolean',
|
|
3197
|
+
description: 'If true, item is only accessible from the current session. Default: false (accessible from all sessions)',
|
|
3198
|
+
default: false,
|
|
3199
|
+
},
|
|
3200
|
+
channel: {
|
|
3201
|
+
type: 'string',
|
|
3202
|
+
description: 'Channel to organize this item (uses session default if not provided)',
|
|
3203
|
+
},
|
|
3204
|
+
},
|
|
3205
|
+
required: ['key', 'value'],
|
|
3206
|
+
},
|
|
3207
|
+
},
|
|
3208
|
+
{
|
|
3209
|
+
name: 'context_get',
|
|
3210
|
+
description: 'Retrieve saved context by key, category, or session with enhanced filtering. Returns all accessible items (public items + own private items)',
|
|
3211
|
+
inputSchema: {
|
|
3212
|
+
type: 'object',
|
|
3213
|
+
properties: {
|
|
3214
|
+
key: { type: 'string', description: 'Specific key to retrieve' },
|
|
3215
|
+
category: { type: 'string', description: 'Filter by category' },
|
|
3216
|
+
sessionId: { type: 'string', description: 'Specific session ID (defaults to current)' },
|
|
3217
|
+
channel: { type: 'string', description: 'Filter by single channel' },
|
|
3218
|
+
channels: {
|
|
3219
|
+
type: 'array',
|
|
3220
|
+
items: { type: 'string' },
|
|
3221
|
+
description: 'Filter by multiple channels',
|
|
3222
|
+
},
|
|
3223
|
+
includeMetadata: {
|
|
3224
|
+
type: 'boolean',
|
|
3225
|
+
description: 'Include timestamps and size info',
|
|
3226
|
+
},
|
|
3227
|
+
sort: {
|
|
3228
|
+
type: 'string',
|
|
3229
|
+
enum: ['created_desc', 'created_asc', 'updated_desc', 'key_asc', 'key_desc'],
|
|
3230
|
+
description: 'Sort order for results',
|
|
3231
|
+
},
|
|
3232
|
+
limit: {
|
|
3233
|
+
type: 'number',
|
|
3234
|
+
description: 'Maximum items to return. Must be a positive integer. Invalid values will cause validation error. (default: auto-derived)',
|
|
3235
|
+
},
|
|
3236
|
+
offset: {
|
|
3237
|
+
type: 'number',
|
|
3238
|
+
description: 'Pagination offset. Must be a non-negative integer. Invalid values will cause validation error. (default: 0)',
|
|
3239
|
+
},
|
|
3240
|
+
createdAfter: {
|
|
3241
|
+
type: 'string',
|
|
3242
|
+
description: 'ISO date - items created after this time',
|
|
3243
|
+
},
|
|
3244
|
+
createdBefore: {
|
|
3245
|
+
type: 'string',
|
|
3246
|
+
description: 'ISO date - items created before this time',
|
|
3247
|
+
},
|
|
3248
|
+
keyPattern: {
|
|
3249
|
+
type: 'string',
|
|
3250
|
+
description: 'Regex pattern for key matching',
|
|
3251
|
+
},
|
|
3252
|
+
priorities: {
|
|
3253
|
+
type: 'array',
|
|
3254
|
+
items: { type: 'string', enum: ['high', 'normal', 'low'] },
|
|
3255
|
+
description: 'Filter by priority levels',
|
|
3256
|
+
},
|
|
3257
|
+
},
|
|
3258
|
+
},
|
|
3259
|
+
},
|
|
3260
|
+
// File Caching
|
|
3261
|
+
{
|
|
3262
|
+
name: 'context_cache_file',
|
|
3263
|
+
description: 'Cache file content with hash for change detection',
|
|
3264
|
+
inputSchema: {
|
|
3265
|
+
type: 'object',
|
|
3266
|
+
properties: {
|
|
3267
|
+
filePath: { type: 'string', description: 'Path to the file' },
|
|
3268
|
+
content: { type: 'string', description: 'File content to cache' },
|
|
3269
|
+
},
|
|
3270
|
+
required: ['filePath', 'content'],
|
|
3271
|
+
},
|
|
3272
|
+
},
|
|
3273
|
+
{
|
|
3274
|
+
name: 'context_file_changed',
|
|
3275
|
+
description: 'Check if a file has changed since it was cached',
|
|
3276
|
+
inputSchema: {
|
|
3277
|
+
type: 'object',
|
|
3278
|
+
properties: {
|
|
3279
|
+
filePath: { type: 'string', description: 'Path to the file' },
|
|
3280
|
+
currentContent: { type: 'string', description: 'Current file content to compare' },
|
|
3281
|
+
},
|
|
3282
|
+
required: ['filePath'],
|
|
3283
|
+
},
|
|
3284
|
+
},
|
|
3285
|
+
// Status
|
|
3286
|
+
{
|
|
3287
|
+
name: 'context_status',
|
|
3288
|
+
description: 'Get current context status and statistics',
|
|
3289
|
+
inputSchema: {
|
|
3290
|
+
type: 'object',
|
|
3291
|
+
properties: {},
|
|
3292
|
+
},
|
|
3293
|
+
},
|
|
3294
|
+
// Phase 2: Checkpoint System
|
|
3295
|
+
{
|
|
3296
|
+
name: 'context_checkpoint',
|
|
3297
|
+
description: 'Create a named checkpoint of current context',
|
|
3298
|
+
inputSchema: {
|
|
3299
|
+
type: 'object',
|
|
3300
|
+
properties: {
|
|
3301
|
+
name: { type: 'string', description: 'Checkpoint name' },
|
|
3302
|
+
description: { type: 'string', description: 'Checkpoint description' },
|
|
3303
|
+
includeFiles: {
|
|
3304
|
+
type: 'boolean',
|
|
3305
|
+
description: 'Include cached files in checkpoint',
|
|
3306
|
+
default: true,
|
|
3307
|
+
},
|
|
3308
|
+
includeGitStatus: {
|
|
3309
|
+
type: 'boolean',
|
|
3310
|
+
description: 'Capture current git status',
|
|
3311
|
+
default: true,
|
|
3312
|
+
},
|
|
3313
|
+
},
|
|
3314
|
+
required: ['name'],
|
|
3315
|
+
},
|
|
3316
|
+
},
|
|
3317
|
+
{
|
|
3318
|
+
name: 'context_restore_checkpoint',
|
|
3319
|
+
description: 'Restore context from a checkpoint',
|
|
3320
|
+
inputSchema: {
|
|
3321
|
+
type: 'object',
|
|
3322
|
+
properties: {
|
|
3323
|
+
name: { type: 'string', description: 'Checkpoint name to restore' },
|
|
3324
|
+
checkpointId: { type: 'string', description: 'Specific checkpoint ID' },
|
|
3325
|
+
restoreFiles: {
|
|
3326
|
+
type: 'boolean',
|
|
3327
|
+
description: 'Restore cached files',
|
|
3328
|
+
default: true,
|
|
3329
|
+
},
|
|
3330
|
+
},
|
|
3331
|
+
},
|
|
3332
|
+
},
|
|
3333
|
+
// Phase 2: Summarization
|
|
3334
|
+
{
|
|
3335
|
+
name: 'context_summarize',
|
|
3336
|
+
description: 'Get AI-friendly summary of session context',
|
|
3337
|
+
inputSchema: {
|
|
3338
|
+
type: 'object',
|
|
3339
|
+
properties: {
|
|
3340
|
+
sessionId: {
|
|
3341
|
+
type: 'string',
|
|
3342
|
+
description: 'Session to summarize (defaults to current)',
|
|
3343
|
+
},
|
|
3344
|
+
categories: {
|
|
3345
|
+
type: 'array',
|
|
3346
|
+
items: { type: 'string' },
|
|
3347
|
+
description: 'Filter by specific categories',
|
|
3348
|
+
},
|
|
3349
|
+
maxLength: {
|
|
3350
|
+
type: 'number',
|
|
3351
|
+
description: 'Maximum summary length',
|
|
3352
|
+
default: 1000,
|
|
3353
|
+
},
|
|
3354
|
+
},
|
|
3355
|
+
},
|
|
3356
|
+
},
|
|
3357
|
+
// Phase 3: Smart Compaction
|
|
3358
|
+
{
|
|
3359
|
+
name: 'context_prepare_compaction',
|
|
3360
|
+
description: 'Automatically save critical context before compaction',
|
|
3361
|
+
inputSchema: {
|
|
3362
|
+
type: 'object',
|
|
3363
|
+
properties: {},
|
|
3364
|
+
},
|
|
3365
|
+
},
|
|
3366
|
+
// Phase 3: Git Integration
|
|
3367
|
+
{
|
|
3368
|
+
name: 'context_git_commit',
|
|
3369
|
+
description: 'Create git commit with automatic context save',
|
|
3370
|
+
inputSchema: {
|
|
3371
|
+
type: 'object',
|
|
3372
|
+
properties: {
|
|
3373
|
+
message: { type: 'string', description: 'Commit message' },
|
|
3374
|
+
autoSave: {
|
|
3375
|
+
type: 'boolean',
|
|
3376
|
+
description: 'Automatically save context state',
|
|
3377
|
+
default: true,
|
|
3378
|
+
},
|
|
3379
|
+
},
|
|
3380
|
+
required: ['message'],
|
|
3381
|
+
},
|
|
3382
|
+
},
|
|
3383
|
+
// Phase 3: Search
|
|
3384
|
+
{
|
|
3385
|
+
name: 'context_search',
|
|
3386
|
+
description: 'Search through saved context items with advanced filtering',
|
|
3387
|
+
inputSchema: {
|
|
3388
|
+
type: 'object',
|
|
3389
|
+
properties: {
|
|
3390
|
+
query: { type: 'string', description: 'Search query' },
|
|
3391
|
+
searchIn: {
|
|
3392
|
+
type: 'array',
|
|
3393
|
+
items: { type: 'string', enum: ['key', 'value'] },
|
|
3394
|
+
description: 'Fields to search in',
|
|
3395
|
+
default: ['key', 'value'],
|
|
3396
|
+
},
|
|
3397
|
+
sessionId: { type: 'string', description: 'Session to search (defaults to current)' },
|
|
3398
|
+
category: { type: 'string', description: 'Filter by category' },
|
|
3399
|
+
channel: { type: 'string', description: 'Filter by single channel' },
|
|
3400
|
+
channels: {
|
|
3401
|
+
type: 'array',
|
|
3402
|
+
items: { type: 'string' },
|
|
3403
|
+
description: 'Filter by multiple channels',
|
|
3404
|
+
},
|
|
3405
|
+
createdAfter: {
|
|
3406
|
+
type: 'string',
|
|
3407
|
+
description: 'ISO date - items created after this time',
|
|
3408
|
+
},
|
|
3409
|
+
createdBefore: {
|
|
3410
|
+
type: 'string',
|
|
3411
|
+
description: 'ISO date - items created before this time',
|
|
3412
|
+
},
|
|
3413
|
+
relativeTime: {
|
|
3414
|
+
type: 'string',
|
|
3415
|
+
description: 'Natural language time (e.g., "2 hours ago", "yesterday")',
|
|
3416
|
+
},
|
|
3417
|
+
keyPattern: {
|
|
3418
|
+
type: 'string',
|
|
3419
|
+
description: 'Pattern for key matching (uses GLOB syntax)',
|
|
3420
|
+
},
|
|
3421
|
+
priorities: {
|
|
3422
|
+
type: 'array',
|
|
3423
|
+
items: { type: 'string', enum: ['high', 'normal', 'low'] },
|
|
3424
|
+
description: 'Filter by priority levels',
|
|
3425
|
+
},
|
|
3426
|
+
sort: {
|
|
3427
|
+
type: 'string',
|
|
3428
|
+
enum: ['created_desc', 'created_asc', 'updated_desc', 'key_asc', 'key_desc'],
|
|
3429
|
+
description: 'Sort order for results',
|
|
3430
|
+
},
|
|
3431
|
+
limit: {
|
|
3432
|
+
type: 'number',
|
|
3433
|
+
description: 'Maximum items to return. Must be a positive integer. Invalid values will cause validation error. (default: auto-derived)',
|
|
3434
|
+
},
|
|
3435
|
+
offset: {
|
|
3436
|
+
type: 'number',
|
|
3437
|
+
description: 'Pagination offset. Must be a non-negative integer. Invalid values will cause validation error. (default: 0)',
|
|
3438
|
+
},
|
|
3439
|
+
includeMetadata: {
|
|
3440
|
+
type: 'boolean',
|
|
3441
|
+
description: 'Include timestamps and size info',
|
|
3442
|
+
},
|
|
3443
|
+
},
|
|
3444
|
+
required: ['query'],
|
|
3445
|
+
},
|
|
3446
|
+
},
|
|
3447
|
+
// Cross-Session Collaboration
|
|
3448
|
+
// REMOVED: Sharing is now automatic (public by default)
|
|
3449
|
+
/*
|
|
3450
|
+
{
|
|
3451
|
+
name: 'context_share',
|
|
3452
|
+
description: 'Share a context item with other sessions for cross-session collaboration',
|
|
3453
|
+
inputSchema: {
|
|
3454
|
+
type: 'object',
|
|
3455
|
+
properties: {
|
|
3456
|
+
key: { type: 'string', description: 'Key of the item to share' },
|
|
3457
|
+
targetSessions: {
|
|
3458
|
+
type: 'array',
|
|
3459
|
+
items: { type: 'string' },
|
|
3460
|
+
description: 'Session IDs to share with (empty for public sharing)'
|
|
3461
|
+
},
|
|
3462
|
+
makePublic: {
|
|
3463
|
+
type: 'boolean',
|
|
3464
|
+
description: 'Share with all sessions',
|
|
3465
|
+
default: false
|
|
3466
|
+
},
|
|
3467
|
+
},
|
|
3468
|
+
required: ['key'],
|
|
3469
|
+
},
|
|
3470
|
+
},
|
|
3471
|
+
*/
|
|
3472
|
+
// REMOVED: All accessible items are retrieved via context_get
|
|
3473
|
+
/*
|
|
3474
|
+
{
|
|
3475
|
+
name: 'context_get_shared',
|
|
3476
|
+
description: 'Get shared context items from other sessions',
|
|
3477
|
+
inputSchema: {
|
|
3478
|
+
type: 'object',
|
|
3479
|
+
properties: {
|
|
3480
|
+
includeAll: {
|
|
3481
|
+
type: 'boolean',
|
|
3482
|
+
description: 'Include all shared items from all sessions',
|
|
3483
|
+
default: false
|
|
3484
|
+
},
|
|
3485
|
+
},
|
|
3486
|
+
},
|
|
3487
|
+
},
|
|
3488
|
+
*/
|
|
3489
|
+
{
|
|
3490
|
+
name: 'context_search_all',
|
|
3491
|
+
description: 'Search across multiple or all sessions with pagination support',
|
|
3492
|
+
inputSchema: {
|
|
3493
|
+
type: 'object',
|
|
3494
|
+
properties: {
|
|
3495
|
+
query: { type: 'string', description: 'Search query' },
|
|
3496
|
+
sessions: {
|
|
3497
|
+
type: 'array',
|
|
3498
|
+
items: { type: 'string' },
|
|
3499
|
+
description: 'Session IDs to search (empty for all sessions)',
|
|
3500
|
+
},
|
|
3501
|
+
includeShared: {
|
|
3502
|
+
type: 'boolean',
|
|
3503
|
+
description: 'Include shared items in search',
|
|
3504
|
+
default: true,
|
|
3505
|
+
},
|
|
3506
|
+
limit: {
|
|
3507
|
+
type: 'number',
|
|
3508
|
+
description: 'Maximum number of items to return. Must be a positive integer between 1-100. Non-integer values will be rejected with validation error. (default: 25)',
|
|
3509
|
+
minimum: 1,
|
|
3510
|
+
maximum: 100,
|
|
3511
|
+
default: 25,
|
|
3512
|
+
},
|
|
3513
|
+
offset: {
|
|
3514
|
+
type: 'number',
|
|
3515
|
+
description: 'Number of items to skip for pagination. Must be a non-negative integer (0 or higher). Non-integer values will be rejected with validation error. (default: 0)',
|
|
3516
|
+
minimum: 0,
|
|
3517
|
+
default: 0,
|
|
3518
|
+
},
|
|
3519
|
+
sort: {
|
|
3520
|
+
type: 'string',
|
|
3521
|
+
description: 'Sort order for results',
|
|
3522
|
+
enum: ['created_desc', 'created_asc', 'updated_desc', 'key_asc', 'key_desc'],
|
|
3523
|
+
default: 'created_desc',
|
|
3524
|
+
},
|
|
3525
|
+
category: {
|
|
3526
|
+
type: 'string',
|
|
3527
|
+
description: 'Filter by category',
|
|
3528
|
+
enum: ['task', 'decision', 'progress', 'note', 'error', 'warning'],
|
|
3529
|
+
},
|
|
3530
|
+
channel: {
|
|
3531
|
+
type: 'string',
|
|
3532
|
+
description: 'Filter by single channel',
|
|
3533
|
+
},
|
|
3534
|
+
channels: {
|
|
3535
|
+
type: 'array',
|
|
3536
|
+
items: { type: 'string' },
|
|
3537
|
+
description: 'Filter by multiple channels',
|
|
3538
|
+
},
|
|
3539
|
+
priorities: {
|
|
3540
|
+
type: 'array',
|
|
3541
|
+
items: { type: 'string', enum: ['high', 'normal', 'low'] },
|
|
3542
|
+
description: 'Filter by priority levels',
|
|
3543
|
+
},
|
|
3544
|
+
createdAfter: {
|
|
3545
|
+
type: 'string',
|
|
3546
|
+
description: 'Filter items created after this date (ISO format or relative time)',
|
|
3547
|
+
},
|
|
3548
|
+
createdBefore: {
|
|
3549
|
+
type: 'string',
|
|
3550
|
+
description: 'Filter items created before this date (ISO format or relative time)',
|
|
3551
|
+
},
|
|
3552
|
+
keyPattern: {
|
|
3553
|
+
type: 'string',
|
|
3554
|
+
description: 'Pattern to match keys (supports wildcards: *, ?)',
|
|
3555
|
+
},
|
|
3556
|
+
searchIn: {
|
|
3557
|
+
type: 'array',
|
|
3558
|
+
items: { type: 'string', enum: ['key', 'value'] },
|
|
3559
|
+
description: 'Fields to search in',
|
|
3560
|
+
default: ['key', 'value'],
|
|
3561
|
+
},
|
|
3562
|
+
includeMetadata: {
|
|
3563
|
+
type: 'boolean',
|
|
3564
|
+
description: 'Include timestamps and size info',
|
|
3565
|
+
default: false,
|
|
3566
|
+
},
|
|
3567
|
+
},
|
|
3568
|
+
required: ['query'],
|
|
3569
|
+
},
|
|
3570
|
+
},
|
|
3571
|
+
// Phase 3: Export/Import
|
|
3572
|
+
{
|
|
3573
|
+
name: 'context_export',
|
|
3574
|
+
description: 'Export session data for backup or sharing',
|
|
3575
|
+
inputSchema: {
|
|
3576
|
+
type: 'object',
|
|
3577
|
+
properties: {
|
|
3578
|
+
sessionId: { type: 'string', description: 'Session to export (defaults to current)' },
|
|
3579
|
+
format: {
|
|
3580
|
+
type: 'string',
|
|
3581
|
+
enum: ['json', 'inline'],
|
|
3582
|
+
description: 'Export format',
|
|
3583
|
+
default: 'json',
|
|
3584
|
+
},
|
|
3585
|
+
},
|
|
3586
|
+
},
|
|
3587
|
+
},
|
|
3588
|
+
{
|
|
3589
|
+
name: 'context_import',
|
|
3590
|
+
description: 'Import previously exported session data',
|
|
3591
|
+
inputSchema: {
|
|
3592
|
+
type: 'object',
|
|
3593
|
+
properties: {
|
|
3594
|
+
filePath: { type: 'string', description: 'Path to import file' },
|
|
3595
|
+
merge: {
|
|
3596
|
+
type: 'boolean',
|
|
3597
|
+
description: 'Merge with current session instead of creating new',
|
|
3598
|
+
default: false,
|
|
3599
|
+
},
|
|
3600
|
+
},
|
|
3601
|
+
required: ['filePath'],
|
|
3602
|
+
},
|
|
3603
|
+
},
|
|
3604
|
+
// Phase 4.1: Knowledge Graph
|
|
3605
|
+
{
|
|
3606
|
+
name: 'context_analyze',
|
|
3607
|
+
description: 'Analyze context to extract entities and relationships',
|
|
3608
|
+
inputSchema: {
|
|
3609
|
+
type: 'object',
|
|
3610
|
+
properties: {
|
|
3611
|
+
sessionId: {
|
|
3612
|
+
type: 'string',
|
|
3613
|
+
description: 'Session ID to analyze (defaults to current)',
|
|
3614
|
+
},
|
|
3615
|
+
categories: {
|
|
3616
|
+
type: 'array',
|
|
3617
|
+
items: { type: 'string' },
|
|
3618
|
+
description: 'Categories to analyze',
|
|
3619
|
+
},
|
|
3620
|
+
},
|
|
3621
|
+
},
|
|
3622
|
+
},
|
|
3623
|
+
{
|
|
3624
|
+
name: 'context_find_related',
|
|
3625
|
+
description: 'Find entities related to a key or entity',
|
|
3626
|
+
inputSchema: {
|
|
3627
|
+
type: 'object',
|
|
3628
|
+
properties: {
|
|
3629
|
+
key: { type: 'string', description: 'Context key or entity name' },
|
|
3630
|
+
relationTypes: {
|
|
3631
|
+
type: 'array',
|
|
3632
|
+
items: { type: 'string' },
|
|
3633
|
+
description: 'Types of relations to include',
|
|
3634
|
+
},
|
|
3635
|
+
maxDepth: {
|
|
3636
|
+
type: 'number',
|
|
3637
|
+
description: 'Maximum graph traversal depth',
|
|
3638
|
+
default: 2,
|
|
3639
|
+
},
|
|
3640
|
+
},
|
|
3641
|
+
required: ['key'],
|
|
3642
|
+
},
|
|
3643
|
+
},
|
|
3644
|
+
{
|
|
3645
|
+
name: 'context_visualize',
|
|
3646
|
+
description: 'Generate visualization data for the knowledge graph',
|
|
3647
|
+
inputSchema: {
|
|
3648
|
+
type: 'object',
|
|
3649
|
+
properties: {
|
|
3650
|
+
type: {
|
|
3651
|
+
type: 'string',
|
|
3652
|
+
enum: ['graph', 'timeline', 'heatmap'],
|
|
3653
|
+
description: 'Visualization type',
|
|
3654
|
+
default: 'graph',
|
|
3655
|
+
},
|
|
3656
|
+
entityTypes: {
|
|
3657
|
+
type: 'array',
|
|
3658
|
+
items: { type: 'string' },
|
|
3659
|
+
description: 'Entity types to include',
|
|
3660
|
+
},
|
|
3661
|
+
sessionId: {
|
|
3662
|
+
type: 'string',
|
|
3663
|
+
description: 'Session to visualize (defaults to current)',
|
|
3664
|
+
},
|
|
3665
|
+
},
|
|
3666
|
+
},
|
|
3667
|
+
},
|
|
3668
|
+
// Phase 4.2: Semantic Search
|
|
3669
|
+
{
|
|
3670
|
+
name: 'context_semantic_search',
|
|
3671
|
+
description: 'Search context using natural language queries',
|
|
3672
|
+
inputSchema: {
|
|
3673
|
+
type: 'object',
|
|
3674
|
+
properties: {
|
|
3675
|
+
query: { type: 'string', description: 'Natural language search query' },
|
|
3676
|
+
topK: {
|
|
3677
|
+
type: 'number',
|
|
3678
|
+
description: 'Number of results to return',
|
|
3679
|
+
default: 10,
|
|
3680
|
+
},
|
|
3681
|
+
minSimilarity: {
|
|
3682
|
+
type: 'number',
|
|
3683
|
+
description: 'Minimum similarity score (0-1)',
|
|
3684
|
+
default: 0.3,
|
|
3685
|
+
},
|
|
3686
|
+
sessionId: {
|
|
3687
|
+
type: 'string',
|
|
3688
|
+
description: 'Search within specific session (defaults to current)',
|
|
3689
|
+
},
|
|
3690
|
+
},
|
|
3691
|
+
required: ['query'],
|
|
3692
|
+
},
|
|
3693
|
+
},
|
|
3694
|
+
// Phase 4.3: Multi-Agent System
|
|
3695
|
+
{
|
|
3696
|
+
name: 'context_delegate',
|
|
3697
|
+
description: 'Delegate complex analysis tasks to specialized agents',
|
|
3698
|
+
inputSchema: {
|
|
3699
|
+
type: 'object',
|
|
3700
|
+
properties: {
|
|
3701
|
+
taskType: {
|
|
3702
|
+
type: 'string',
|
|
3703
|
+
enum: ['analyze', 'synthesize'],
|
|
3704
|
+
description: 'Type of task to delegate',
|
|
3705
|
+
},
|
|
3706
|
+
input: {
|
|
3707
|
+
type: 'object',
|
|
3708
|
+
properties: {
|
|
3709
|
+
analysisType: {
|
|
3710
|
+
type: 'string',
|
|
3711
|
+
enum: ['patterns', 'relationships', 'trends', 'comprehensive'],
|
|
3712
|
+
description: 'For analyze tasks: type of analysis',
|
|
3713
|
+
},
|
|
3714
|
+
synthesisType: {
|
|
3715
|
+
type: 'string',
|
|
3716
|
+
enum: ['summary', 'merge', 'recommendations'],
|
|
3717
|
+
description: 'For synthesize tasks: type of synthesis',
|
|
3718
|
+
},
|
|
3719
|
+
categories: {
|
|
3720
|
+
type: 'array',
|
|
3721
|
+
items: { type: 'string' },
|
|
3722
|
+
description: 'Categories to include in analysis',
|
|
3723
|
+
},
|
|
3724
|
+
timeframe: {
|
|
3725
|
+
type: 'string',
|
|
3726
|
+
description: 'Time period for analysis (e.g., "-7 days")',
|
|
3727
|
+
},
|
|
3728
|
+
maxLength: {
|
|
3729
|
+
type: 'number',
|
|
3730
|
+
description: 'Maximum length for summaries',
|
|
3731
|
+
},
|
|
3732
|
+
insights: {
|
|
3733
|
+
type: 'array',
|
|
3734
|
+
description: 'For merge synthesis: array of insights to merge',
|
|
3735
|
+
},
|
|
3736
|
+
},
|
|
3737
|
+
},
|
|
3738
|
+
chain: {
|
|
3739
|
+
type: 'boolean',
|
|
3740
|
+
description: 'Process multiple tasks in sequence',
|
|
3741
|
+
default: false,
|
|
3742
|
+
},
|
|
3743
|
+
sessionId: { type: 'string', description: 'Session to analyze (defaults to current)' },
|
|
3744
|
+
},
|
|
3745
|
+
required: ['taskType', 'input'],
|
|
3746
|
+
},
|
|
3747
|
+
},
|
|
3748
|
+
// Phase 4.4: Advanced Features
|
|
3749
|
+
{
|
|
3750
|
+
name: 'context_branch_session',
|
|
3751
|
+
description: 'Create a branch from current session for exploring alternatives',
|
|
3752
|
+
inputSchema: {
|
|
3753
|
+
type: 'object',
|
|
3754
|
+
properties: {
|
|
3755
|
+
branchName: {
|
|
3756
|
+
type: 'string',
|
|
3757
|
+
description: 'Name for the new branch',
|
|
3758
|
+
},
|
|
3759
|
+
copyDepth: {
|
|
3760
|
+
type: 'string',
|
|
3761
|
+
enum: ['shallow', 'deep'],
|
|
3762
|
+
description: 'How much to copy: shallow (high priority only) or deep (everything)',
|
|
3763
|
+
default: 'shallow',
|
|
3764
|
+
},
|
|
3765
|
+
},
|
|
3766
|
+
required: ['branchName'],
|
|
3767
|
+
},
|
|
3768
|
+
},
|
|
3769
|
+
{
|
|
3770
|
+
name: 'context_merge_sessions',
|
|
3771
|
+
description: 'Merge another session into the current one',
|
|
3772
|
+
inputSchema: {
|
|
3773
|
+
type: 'object',
|
|
3774
|
+
properties: {
|
|
3775
|
+
sourceSessionId: {
|
|
3776
|
+
type: 'string',
|
|
3777
|
+
description: 'ID of the session to merge from',
|
|
3778
|
+
},
|
|
3779
|
+
conflictResolution: {
|
|
3780
|
+
type: 'string',
|
|
3781
|
+
enum: ['keep_current', 'keep_source', 'keep_newest'],
|
|
3782
|
+
description: 'How to resolve conflicts',
|
|
3783
|
+
default: 'keep_current',
|
|
3784
|
+
},
|
|
3785
|
+
},
|
|
3786
|
+
required: ['sourceSessionId'],
|
|
3787
|
+
},
|
|
3788
|
+
},
|
|
3789
|
+
{
|
|
3790
|
+
name: 'context_journal_entry',
|
|
3791
|
+
description: 'Add a timestamped journal entry with optional tags and mood',
|
|
3792
|
+
inputSchema: {
|
|
3793
|
+
type: 'object',
|
|
3794
|
+
properties: {
|
|
3795
|
+
entry: {
|
|
3796
|
+
type: 'string',
|
|
3797
|
+
description: 'Journal entry text',
|
|
3798
|
+
},
|
|
3799
|
+
tags: {
|
|
3800
|
+
type: 'array',
|
|
3801
|
+
items: { type: 'string' },
|
|
3802
|
+
description: 'Tags for categorization',
|
|
3803
|
+
},
|
|
3804
|
+
mood: {
|
|
3805
|
+
type: 'string',
|
|
3806
|
+
description: 'Current mood/feeling',
|
|
3807
|
+
},
|
|
3808
|
+
},
|
|
3809
|
+
required: ['entry'],
|
|
3810
|
+
},
|
|
3811
|
+
},
|
|
3812
|
+
{
|
|
3813
|
+
name: 'context_timeline',
|
|
3814
|
+
description: 'Get timeline of activities with optional grouping',
|
|
3815
|
+
inputSchema: {
|
|
3816
|
+
type: 'object',
|
|
3817
|
+
properties: {
|
|
3818
|
+
startDate: {
|
|
3819
|
+
type: 'string',
|
|
3820
|
+
description: 'Start date (ISO format)',
|
|
3821
|
+
},
|
|
3822
|
+
endDate: {
|
|
3823
|
+
type: 'string',
|
|
3824
|
+
description: 'End date (ISO format)',
|
|
3825
|
+
},
|
|
3826
|
+
groupBy: {
|
|
3827
|
+
type: 'string',
|
|
3828
|
+
enum: ['hour', 'day', 'week'],
|
|
3829
|
+
description: 'How to group timeline data',
|
|
3830
|
+
default: 'day',
|
|
3831
|
+
},
|
|
3832
|
+
sessionId: {
|
|
3833
|
+
type: 'string',
|
|
3834
|
+
description: 'Session to analyze (defaults to current)',
|
|
3835
|
+
},
|
|
3836
|
+
includeItems: {
|
|
3837
|
+
type: 'boolean',
|
|
3838
|
+
description: 'Include item details in timeline',
|
|
3839
|
+
},
|
|
3840
|
+
categories: {
|
|
3841
|
+
type: 'array',
|
|
3842
|
+
items: { type: 'string' },
|
|
3843
|
+
description: 'Filter by categories',
|
|
3844
|
+
},
|
|
3845
|
+
relativeTime: {
|
|
3846
|
+
type: 'string',
|
|
3847
|
+
description: 'Natural language time (e.g., "2 hours ago", "today")',
|
|
3848
|
+
},
|
|
3849
|
+
itemsPerPeriod: {
|
|
3850
|
+
type: 'number',
|
|
3851
|
+
description: 'Max items per time period',
|
|
3852
|
+
},
|
|
3853
|
+
minItemsPerPeriod: {
|
|
3854
|
+
type: 'number',
|
|
3855
|
+
description: 'Only include periods with at least N items',
|
|
3856
|
+
},
|
|
3857
|
+
showEmpty: {
|
|
3858
|
+
type: 'boolean',
|
|
3859
|
+
description: 'Include periods with 0 items (default: false)',
|
|
3860
|
+
},
|
|
3861
|
+
},
|
|
3862
|
+
},
|
|
3863
|
+
},
|
|
3864
|
+
{
|
|
3865
|
+
name: 'context_compress',
|
|
3866
|
+
description: 'Intelligently compress old context to save space',
|
|
3867
|
+
inputSchema: {
|
|
3868
|
+
type: 'object',
|
|
3869
|
+
properties: {
|
|
3870
|
+
olderThan: {
|
|
3871
|
+
type: 'string',
|
|
3872
|
+
description: 'Compress items older than this date (ISO format)',
|
|
3873
|
+
},
|
|
3874
|
+
preserveCategories: {
|
|
3875
|
+
type: 'array',
|
|
3876
|
+
items: { type: 'string' },
|
|
3877
|
+
description: 'Categories to preserve (not compress)',
|
|
3878
|
+
},
|
|
3879
|
+
targetSize: {
|
|
3880
|
+
type: 'number',
|
|
3881
|
+
description: 'Target size in KB (optional)',
|
|
3882
|
+
},
|
|
3883
|
+
sessionId: {
|
|
3884
|
+
type: 'string',
|
|
3885
|
+
description: 'Session to compress (defaults to current)',
|
|
3886
|
+
},
|
|
3887
|
+
},
|
|
3888
|
+
},
|
|
3889
|
+
},
|
|
3890
|
+
{
|
|
3891
|
+
name: 'context_integrate_tool',
|
|
3892
|
+
description: 'Track events from other MCP tools',
|
|
3893
|
+
inputSchema: {
|
|
3894
|
+
type: 'object',
|
|
3895
|
+
properties: {
|
|
3896
|
+
toolName: {
|
|
3897
|
+
type: 'string',
|
|
3898
|
+
description: 'Name of the tool',
|
|
3899
|
+
},
|
|
3900
|
+
eventType: {
|
|
3901
|
+
type: 'string',
|
|
3902
|
+
description: 'Type of event',
|
|
3903
|
+
},
|
|
3904
|
+
data: {
|
|
3905
|
+
type: 'object',
|
|
3906
|
+
description: 'Event data',
|
|
3907
|
+
properties: {
|
|
3908
|
+
important: {
|
|
3909
|
+
type: 'boolean',
|
|
3910
|
+
description: 'Mark as important to save as context item',
|
|
3911
|
+
},
|
|
3912
|
+
},
|
|
3913
|
+
},
|
|
3914
|
+
},
|
|
3915
|
+
required: ['toolName', 'eventType', 'data'],
|
|
3916
|
+
},
|
|
3917
|
+
},
|
|
3918
|
+
{
|
|
3919
|
+
name: 'context_diff',
|
|
3920
|
+
description: 'Get changes to context items since a specific point in time (timestamp, checkpoint, or relative time)',
|
|
3921
|
+
inputSchema: {
|
|
3922
|
+
type: 'object',
|
|
3923
|
+
properties: {
|
|
3924
|
+
since: {
|
|
3925
|
+
type: 'string',
|
|
3926
|
+
description: 'Point in time to compare against (ISO timestamp, checkpoint name/ID, or relative time like "2 hours ago")',
|
|
3927
|
+
},
|
|
3928
|
+
sessionId: {
|
|
3929
|
+
type: 'string',
|
|
3930
|
+
description: 'Session ID to analyze (defaults to current)',
|
|
3931
|
+
},
|
|
3932
|
+
category: {
|
|
3933
|
+
type: 'string',
|
|
3934
|
+
description: 'Filter by category',
|
|
3935
|
+
},
|
|
3936
|
+
channel: {
|
|
3937
|
+
type: 'string',
|
|
3938
|
+
description: 'Filter by single channel',
|
|
3939
|
+
},
|
|
3940
|
+
channels: {
|
|
3941
|
+
type: 'array',
|
|
3942
|
+
items: { type: 'string' },
|
|
3943
|
+
description: 'Filter by multiple channels',
|
|
3944
|
+
},
|
|
3945
|
+
includeValues: {
|
|
3946
|
+
type: 'boolean',
|
|
3947
|
+
description: 'Include full item values in response',
|
|
3948
|
+
default: true,
|
|
3949
|
+
},
|
|
3950
|
+
limit: {
|
|
3951
|
+
type: 'number',
|
|
3952
|
+
description: 'Maximum items per category (added/modified)',
|
|
3953
|
+
},
|
|
3954
|
+
offset: {
|
|
3955
|
+
type: 'number',
|
|
3956
|
+
description: 'Pagination offset',
|
|
3957
|
+
},
|
|
3958
|
+
},
|
|
3959
|
+
},
|
|
3960
|
+
},
|
|
3961
|
+
{
|
|
3962
|
+
name: 'context_list_channels',
|
|
3963
|
+
description: 'List all channels with metadata (counts, activity, categories)',
|
|
3964
|
+
inputSchema: {
|
|
3965
|
+
type: 'object',
|
|
3966
|
+
properties: {
|
|
3967
|
+
sessionId: {
|
|
3968
|
+
type: 'string',
|
|
3969
|
+
description: 'Filter by specific session (shows accessible items)',
|
|
3970
|
+
},
|
|
3971
|
+
sessionIds: {
|
|
3972
|
+
type: 'array',
|
|
3973
|
+
items: { type: 'string' },
|
|
3974
|
+
description: 'Filter by multiple sessions',
|
|
3975
|
+
},
|
|
3976
|
+
sort: {
|
|
3977
|
+
type: 'string',
|
|
3978
|
+
enum: ['name', 'count', 'activity'],
|
|
3979
|
+
description: 'Sort order for results',
|
|
3980
|
+
default: 'name',
|
|
3981
|
+
},
|
|
3982
|
+
includeEmpty: {
|
|
3983
|
+
type: 'boolean',
|
|
3984
|
+
description: 'Include channels with no items',
|
|
3985
|
+
default: false,
|
|
3986
|
+
},
|
|
3987
|
+
},
|
|
3988
|
+
},
|
|
3989
|
+
},
|
|
3990
|
+
{
|
|
3991
|
+
name: 'context_channel_stats',
|
|
3992
|
+
description: 'Get detailed statistics for a specific channel or all channels',
|
|
3993
|
+
inputSchema: {
|
|
3994
|
+
type: 'object',
|
|
3995
|
+
properties: {
|
|
3996
|
+
channel: {
|
|
3997
|
+
type: 'string',
|
|
3998
|
+
description: 'Specific channel name (omit for all channels overview)',
|
|
3999
|
+
},
|
|
4000
|
+
sessionId: {
|
|
4001
|
+
type: 'string',
|
|
4002
|
+
description: 'Session context for privacy filtering',
|
|
4003
|
+
},
|
|
4004
|
+
includeTimeSeries: {
|
|
4005
|
+
type: 'boolean',
|
|
4006
|
+
description: 'Include hourly/daily activity data',
|
|
4007
|
+
default: false,
|
|
4008
|
+
},
|
|
4009
|
+
includeInsights: {
|
|
4010
|
+
type: 'boolean',
|
|
4011
|
+
description: 'Include AI-generated insights',
|
|
4012
|
+
default: false,
|
|
4013
|
+
},
|
|
4014
|
+
},
|
|
4015
|
+
},
|
|
4016
|
+
},
|
|
4017
|
+
// Context Watch - Real-time monitoring
|
|
4018
|
+
{
|
|
4019
|
+
name: 'context_watch',
|
|
4020
|
+
description: 'Create and manage watchers for real-time context change monitoring',
|
|
4021
|
+
inputSchema: {
|
|
4022
|
+
type: 'object',
|
|
4023
|
+
properties: {
|
|
4024
|
+
action: {
|
|
4025
|
+
type: 'string',
|
|
4026
|
+
enum: ['create', 'poll', 'stop', 'list'],
|
|
4027
|
+
description: 'Action to perform',
|
|
4028
|
+
},
|
|
4029
|
+
watcherId: {
|
|
4030
|
+
type: 'string',
|
|
4031
|
+
description: 'Watcher ID (required for poll/stop actions)',
|
|
4032
|
+
},
|
|
4033
|
+
filters: {
|
|
4034
|
+
type: 'object',
|
|
4035
|
+
description: 'Filters for watching specific changes (for create action)',
|
|
4036
|
+
properties: {
|
|
4037
|
+
keys: {
|
|
4038
|
+
type: 'array',
|
|
4039
|
+
items: { type: 'string' },
|
|
4040
|
+
description: 'Key patterns to watch (supports wildcards: *, ?)',
|
|
4041
|
+
},
|
|
4042
|
+
categories: {
|
|
4043
|
+
type: 'array',
|
|
4044
|
+
items: {
|
|
4045
|
+
type: 'string',
|
|
4046
|
+
enum: ['task', 'decision', 'progress', 'note', 'error', 'warning'],
|
|
4047
|
+
},
|
|
4048
|
+
description: 'Categories to watch',
|
|
4049
|
+
},
|
|
4050
|
+
channels: {
|
|
4051
|
+
type: 'array',
|
|
4052
|
+
items: { type: 'string' },
|
|
4053
|
+
description: 'Channels to watch',
|
|
4054
|
+
},
|
|
4055
|
+
priorities: {
|
|
4056
|
+
type: 'array',
|
|
4057
|
+
items: {
|
|
4058
|
+
type: 'string',
|
|
4059
|
+
enum: ['high', 'normal', 'low'],
|
|
4060
|
+
},
|
|
4061
|
+
description: 'Priority levels to watch',
|
|
4062
|
+
},
|
|
4063
|
+
},
|
|
4064
|
+
},
|
|
4065
|
+
pollTimeout: {
|
|
4066
|
+
type: 'number',
|
|
4067
|
+
description: 'Polling timeout in seconds (default: 30)',
|
|
4068
|
+
default: 30,
|
|
4069
|
+
},
|
|
4070
|
+
},
|
|
4071
|
+
required: ['action'],
|
|
4072
|
+
},
|
|
4073
|
+
},
|
|
4074
|
+
// Context Reassign Channel
|
|
4075
|
+
{
|
|
4076
|
+
name: 'context_reassign_channel',
|
|
4077
|
+
description: 'Move context items between channels based on keys, patterns, or entire channel',
|
|
4078
|
+
inputSchema: {
|
|
4079
|
+
type: 'object',
|
|
4080
|
+
properties: {
|
|
4081
|
+
keys: {
|
|
4082
|
+
type: 'array',
|
|
4083
|
+
items: { type: 'string' },
|
|
4084
|
+
description: 'Specific keys to reassign',
|
|
4085
|
+
},
|
|
4086
|
+
keyPattern: {
|
|
4087
|
+
type: 'string',
|
|
4088
|
+
description: 'Pattern to match keys (supports wildcards: *, ?)',
|
|
4089
|
+
},
|
|
4090
|
+
fromChannel: {
|
|
4091
|
+
type: 'string',
|
|
4092
|
+
description: 'Source channel to move all items from',
|
|
4093
|
+
},
|
|
4094
|
+
toChannel: {
|
|
4095
|
+
type: 'string',
|
|
4096
|
+
description: 'Target channel to move items to',
|
|
4097
|
+
},
|
|
4098
|
+
sessionId: {
|
|
4099
|
+
type: 'string',
|
|
4100
|
+
description: 'Session ID (defaults to current)',
|
|
4101
|
+
},
|
|
4102
|
+
category: {
|
|
4103
|
+
type: 'string',
|
|
4104
|
+
enum: ['task', 'decision', 'progress', 'note', 'error', 'warning'],
|
|
4105
|
+
description: 'Filter by category',
|
|
4106
|
+
},
|
|
4107
|
+
priorities: {
|
|
4108
|
+
type: 'array',
|
|
4109
|
+
items: {
|
|
4110
|
+
type: 'string',
|
|
4111
|
+
enum: ['high', 'normal', 'low'],
|
|
4112
|
+
},
|
|
4113
|
+
description: 'Filter by priority levels',
|
|
4114
|
+
},
|
|
4115
|
+
dryRun: {
|
|
4116
|
+
type: 'boolean',
|
|
4117
|
+
description: 'Preview changes without applying them',
|
|
4118
|
+
default: false,
|
|
4119
|
+
},
|
|
4120
|
+
},
|
|
4121
|
+
required: ['toChannel'],
|
|
4122
|
+
},
|
|
4123
|
+
},
|
|
4124
|
+
// Batch Operations
|
|
4125
|
+
{
|
|
4126
|
+
name: 'context_batch_save',
|
|
4127
|
+
description: 'Save multiple context items in a single atomic operation',
|
|
4128
|
+
inputSchema: {
|
|
4129
|
+
type: 'object',
|
|
4130
|
+
properties: {
|
|
4131
|
+
items: {
|
|
4132
|
+
type: 'array',
|
|
4133
|
+
description: 'Array of items to save',
|
|
4134
|
+
items: {
|
|
4135
|
+
type: 'object',
|
|
4136
|
+
properties: {
|
|
4137
|
+
key: { type: 'string', description: 'Unique key for the context item' },
|
|
4138
|
+
value: { type: 'string', description: 'Context value to save' },
|
|
4139
|
+
category: {
|
|
4140
|
+
type: 'string',
|
|
4141
|
+
description: 'Category (e.g., task, decision, progress)',
|
|
4142
|
+
enum: ['task', 'decision', 'progress', 'note', 'error', 'warning'],
|
|
4143
|
+
},
|
|
4144
|
+
priority: {
|
|
4145
|
+
type: 'string',
|
|
4146
|
+
description: 'Priority level',
|
|
4147
|
+
enum: ['high', 'normal', 'low'],
|
|
4148
|
+
},
|
|
4149
|
+
channel: {
|
|
4150
|
+
type: 'string',
|
|
4151
|
+
description: 'Channel to organize this item',
|
|
4152
|
+
},
|
|
4153
|
+
},
|
|
4154
|
+
required: ['key', 'value'],
|
|
4155
|
+
},
|
|
4156
|
+
},
|
|
4157
|
+
updateExisting: {
|
|
4158
|
+
type: 'boolean',
|
|
4159
|
+
description: 'Update existing items with same key (default: true)',
|
|
4160
|
+
default: true,
|
|
4161
|
+
},
|
|
4162
|
+
},
|
|
4163
|
+
required: ['items'],
|
|
4164
|
+
},
|
|
4165
|
+
},
|
|
4166
|
+
{
|
|
4167
|
+
name: 'context_batch_delete',
|
|
4168
|
+
description: 'Delete multiple context items by keys or pattern in a single atomic operation',
|
|
4169
|
+
inputSchema: {
|
|
4170
|
+
type: 'object',
|
|
4171
|
+
properties: {
|
|
4172
|
+
keys: {
|
|
4173
|
+
type: 'array',
|
|
4174
|
+
items: { type: 'string' },
|
|
4175
|
+
description: 'Array of specific keys to delete',
|
|
4176
|
+
},
|
|
4177
|
+
keyPattern: {
|
|
4178
|
+
type: 'string',
|
|
4179
|
+
description: 'Pattern to match keys for deletion (supports wildcards: *, ?)',
|
|
4180
|
+
},
|
|
4181
|
+
sessionId: {
|
|
4182
|
+
type: 'string',
|
|
4183
|
+
description: 'Session ID (defaults to current)',
|
|
4184
|
+
},
|
|
4185
|
+
dryRun: {
|
|
4186
|
+
type: 'boolean',
|
|
4187
|
+
description: 'Preview items to be deleted without actually deleting',
|
|
4188
|
+
default: false,
|
|
4189
|
+
},
|
|
4190
|
+
},
|
|
4191
|
+
},
|
|
4192
|
+
},
|
|
4193
|
+
{
|
|
4194
|
+
name: 'context_batch_update',
|
|
4195
|
+
description: 'Update multiple context items with partial updates in a single atomic operation',
|
|
4196
|
+
inputSchema: {
|
|
4197
|
+
type: 'object',
|
|
4198
|
+
properties: {
|
|
4199
|
+
updates: {
|
|
4200
|
+
type: 'array',
|
|
4201
|
+
description: 'Array of updates to apply',
|
|
4202
|
+
items: {
|
|
4203
|
+
type: 'object',
|
|
4204
|
+
properties: {
|
|
4205
|
+
key: { type: 'string', description: 'Key of the item to update' },
|
|
4206
|
+
value: { type: 'string', description: 'New value (optional)' },
|
|
4207
|
+
category: {
|
|
4208
|
+
type: 'string',
|
|
4209
|
+
description: 'New category (optional)',
|
|
4210
|
+
enum: ['task', 'decision', 'progress', 'note', 'error', 'warning'],
|
|
4211
|
+
},
|
|
4212
|
+
priority: {
|
|
4213
|
+
type: 'string',
|
|
4214
|
+
description: 'New priority (optional)',
|
|
4215
|
+
enum: ['high', 'normal', 'low'],
|
|
4216
|
+
},
|
|
4217
|
+
channel: {
|
|
4218
|
+
type: 'string',
|
|
4219
|
+
description: 'New channel (optional)',
|
|
4220
|
+
},
|
|
4221
|
+
},
|
|
4222
|
+
required: ['key'],
|
|
4223
|
+
},
|
|
4224
|
+
},
|
|
4225
|
+
sessionId: {
|
|
4226
|
+
type: 'string',
|
|
4227
|
+
description: 'Session ID (defaults to current)',
|
|
4228
|
+
},
|
|
4229
|
+
},
|
|
4230
|
+
required: ['updates'],
|
|
4231
|
+
},
|
|
4232
|
+
},
|
|
4233
|
+
// Context Relationships
|
|
4234
|
+
{
|
|
4235
|
+
name: 'context_link',
|
|
4236
|
+
description: 'Create a relationship between two context items',
|
|
4237
|
+
inputSchema: {
|
|
4238
|
+
type: 'object',
|
|
4239
|
+
properties: {
|
|
4240
|
+
sourceKey: {
|
|
4241
|
+
type: 'string',
|
|
4242
|
+
description: 'Key of the source context item',
|
|
4243
|
+
},
|
|
4244
|
+
targetKey: {
|
|
4245
|
+
type: 'string',
|
|
4246
|
+
description: 'Key of the target context item',
|
|
4247
|
+
},
|
|
4248
|
+
relationship: {
|
|
4249
|
+
type: 'string',
|
|
4250
|
+
description: 'Type of relationship',
|
|
4251
|
+
enum: [
|
|
4252
|
+
'contains',
|
|
4253
|
+
'depends_on',
|
|
4254
|
+
'references',
|
|
4255
|
+
'implements',
|
|
4256
|
+
'extends',
|
|
4257
|
+
'related_to',
|
|
4258
|
+
'blocks',
|
|
4259
|
+
'blocked_by',
|
|
4260
|
+
'parent_of',
|
|
4261
|
+
'child_of',
|
|
4262
|
+
'has_task',
|
|
4263
|
+
'documented_in',
|
|
4264
|
+
'serves',
|
|
4265
|
+
'leads_to',
|
|
4266
|
+
],
|
|
4267
|
+
},
|
|
4268
|
+
metadata: {
|
|
4269
|
+
type: 'object',
|
|
4270
|
+
description: 'Optional metadata for the relationship',
|
|
4271
|
+
},
|
|
4272
|
+
},
|
|
4273
|
+
required: ['sourceKey', 'targetKey', 'relationship'],
|
|
4274
|
+
},
|
|
4275
|
+
},
|
|
4276
|
+
{
|
|
4277
|
+
name: 'context_get_related',
|
|
4278
|
+
description: 'Get items related to a given context item',
|
|
4279
|
+
inputSchema: {
|
|
4280
|
+
type: 'object',
|
|
4281
|
+
properties: {
|
|
4282
|
+
key: {
|
|
4283
|
+
type: 'string',
|
|
4284
|
+
description: 'Key of the context item to find relationships for',
|
|
4285
|
+
},
|
|
4286
|
+
relationship: {
|
|
4287
|
+
type: 'string',
|
|
4288
|
+
description: 'Filter by specific relationship type',
|
|
4289
|
+
},
|
|
4290
|
+
depth: {
|
|
4291
|
+
type: 'number',
|
|
4292
|
+
description: 'Traversal depth for multi-level relationships (default: 1)',
|
|
4293
|
+
default: 1,
|
|
4294
|
+
},
|
|
4295
|
+
direction: {
|
|
4296
|
+
type: 'string',
|
|
4297
|
+
description: 'Direction of relationships to retrieve',
|
|
4298
|
+
enum: ['outgoing', 'incoming', 'both'],
|
|
4299
|
+
default: 'both',
|
|
4300
|
+
},
|
|
4301
|
+
},
|
|
4302
|
+
required: ['key'],
|
|
4303
|
+
},
|
|
4304
|
+
},
|
|
4305
|
+
],
|
|
4306
|
+
};
|
|
4307
|
+
});
|
|
4308
|
+
// Start server
|
|
4309
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
4310
|
+
server.connect(transport);
|