@smallironman/mcp-memory-keeper 0.12.2-fork1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/CHANGELOG.md +542 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1281 -0
  4. package/bin/mcp-memory-keeper +54 -0
  5. package/dist/__tests__/e2e/issue33-reproduce.test.js +234 -0
  6. package/dist/__tests__/e2e/server-e2e.test.js +341 -0
  7. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  8. package/dist/__tests__/helpers/test-server.js +92 -0
  9. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  10. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  11. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  12. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  13. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  14. package/dist/__tests__/integration/channels.test.js +376 -0
  15. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  16. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  17. package/dist/__tests__/integration/context-operations.test.js +243 -0
  18. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  19. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  20. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  21. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  22. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  23. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  24. package/dist/__tests__/integration/contextSearch.test.js +1054 -0
  25. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  26. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  27. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  28. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  29. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  30. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  31. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  32. package/dist/__tests__/integration/error-cases.test.js +411 -0
  33. package/dist/__tests__/integration/export-import.test.js +367 -0
  34. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  35. package/dist/__tests__/integration/file-operations.test.js +264 -0
  36. package/dist/__tests__/integration/filterBySessionId.test.js +251 -0
  37. package/dist/__tests__/integration/git-integration.test.js +241 -0
  38. package/dist/__tests__/integration/index-tools.test.js +496 -0
  39. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  40. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  41. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  42. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  43. package/dist/__tests__/integration/issue24-final-fix.test.js +241 -0
  44. package/dist/__tests__/integration/issue24-fix-validation.test.js +158 -0
  45. package/dist/__tests__/integration/issue24-reproduce.test.js +225 -0
  46. package/dist/__tests__/integration/issue24-token-limit.test.js +199 -0
  47. package/dist/__tests__/integration/issue33-array-items-schema.test.js +165 -0
  48. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  49. package/dist/__tests__/integration/migrations.test.js +528 -0
  50. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  51. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  52. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  53. package/dist/__tests__/integration/project-directory.test.js +291 -0
  54. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  55. package/dist/__tests__/integration/retention.test.js +513 -0
  56. package/dist/__tests__/integration/search.test.js +333 -0
  57. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  58. package/dist/__tests__/integration/server-initialization.test.js +305 -0
  59. package/dist/__tests__/integration/session-management.test.js +219 -0
  60. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  61. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  62. package/dist/__tests__/integration/summarization.test.js +308 -0
  63. package/dist/__tests__/integration/tokenLimitEnforcement.test.js +134 -0
  64. package/dist/__tests__/integration/tool-profiles-integration.test.js +150 -0
  65. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  66. package/dist/__tests__/security/input-validation.test.js +115 -0
  67. package/dist/__tests__/utils/agents.test.js +473 -0
  68. package/dist/__tests__/utils/database.test.js +177 -0
  69. package/dist/__tests__/utils/git.test.js +122 -0
  70. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  71. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  72. package/dist/__tests__/utils/project-directory-messages.test.js +192 -0
  73. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  74. package/dist/__tests__/utils/token-limits.test.js +225 -0
  75. package/dist/__tests__/utils/tool-profiles.test.js +374 -0
  76. package/dist/__tests__/utils/validation.test.js +200 -0
  77. package/dist/__tests__/utils/vector-store.test.js +231 -0
  78. package/dist/handlers/contextWatchHandlers.js +206 -0
  79. package/dist/index.js +4425 -0
  80. package/dist/migrations/003_add_channels.js +174 -0
  81. package/dist/migrations/004_add_context_watch.js +151 -0
  82. package/dist/migrations/005_add_context_watch.js +98 -0
  83. package/dist/migrations/simplify-sharing.js +117 -0
  84. package/dist/repositories/BaseRepository.js +30 -0
  85. package/dist/repositories/CheckpointRepository.js +140 -0
  86. package/dist/repositories/ContextRepository.js +2017 -0
  87. package/dist/repositories/FileRepository.js +104 -0
  88. package/dist/repositories/RepositoryManager.js +62 -0
  89. package/dist/repositories/SessionRepository.js +66 -0
  90. package/dist/repositories/WatcherRepository.js +252 -0
  91. package/dist/repositories/index.js +15 -0
  92. package/dist/test-helpers/database-helper.js +128 -0
  93. package/dist/types/entities.js +3 -0
  94. package/dist/utils/agents.js +791 -0
  95. package/dist/utils/channels.js +150 -0
  96. package/dist/utils/database.js +780 -0
  97. package/dist/utils/feature-flags.js +476 -0
  98. package/dist/utils/git.js +145 -0
  99. package/dist/utils/knowledge-graph.js +264 -0
  100. package/dist/utils/migrationHealthCheck.js +373 -0
  101. package/dist/utils/migrations.js +452 -0
  102. package/dist/utils/retention.js +460 -0
  103. package/dist/utils/timestamps.js +112 -0
  104. package/dist/utils/token-limits.js +350 -0
  105. package/dist/utils/tool-profiles.js +242 -0
  106. package/dist/utils/validation.js +296 -0
  107. package/dist/utils/vector-store.js +247 -0
  108. package/examples/config.json +31 -0
  109. package/examples/project-directory-setup.md +114 -0
  110. package/package.json +85 -0
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ // Test to validate the fix for issue #24
3
+ // Tests the actual flow through index.ts handler
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ const globals_1 = require("@jest/globals");
6
+ // Mock the functions used in index.ts for context_get
7
+ function estimateTokens(text) {
8
+ return Math.ceil(text.length / 4);
9
+ }
10
+ function calculateSize(value) {
11
+ return Buffer.byteLength(value, 'utf8');
12
+ }
13
+ function calculateResponseMetrics(items) {
14
+ let totalSize = 0;
15
+ for (const item of items) {
16
+ const itemSize = item.size || calculateSize(item.value);
17
+ totalSize += itemSize;
18
+ }
19
+ // Convert to JSON string to get actual response size
20
+ const jsonString = JSON.stringify(items);
21
+ const estimatedTokens = estimateTokens(jsonString);
22
+ const averageSize = items.length > 0 ? Math.round(totalSize / items.length) : 0;
23
+ return { totalSize, estimatedTokens, averageSize };
24
+ }
25
+ (0, globals_1.describe)('Issue #24 Fix Validation', () => {
26
+ (0, globals_1.it)('should correctly calculate tokens for response with metadata', () => {
27
+ // Create sample items as they would come from the database
28
+ const dbItems = [];
29
+ for (let i = 0; i < 100; i++) {
30
+ dbItems.push({
31
+ id: `id-${i}`,
32
+ session_id: 'test-session',
33
+ key: `test_item_${i}`,
34
+ value: `This is test content that is moderately long to simulate real data. `.repeat(3),
35
+ category: 'task',
36
+ priority: 'high',
37
+ channel: 'test',
38
+ metadata: JSON.stringify({ index: i }),
39
+ size: 200,
40
+ created_at: '2025-01-20T10:00:00Z',
41
+ updated_at: '2025-01-20T10:00:00Z',
42
+ });
43
+ }
44
+ // Test without metadata (original calculation)
45
+ const metricsWithoutMetadata = calculateResponseMetrics(dbItems);
46
+ console.log('Without metadata - Items:', dbItems.length, 'Tokens:', metricsWithoutMetadata.estimatedTokens);
47
+ // Test with metadata (as per the fix)
48
+ const itemsWithMetadata = dbItems.map(item => ({
49
+ key: item.key,
50
+ value: item.value,
51
+ category: item.category,
52
+ priority: item.priority,
53
+ channel: item.channel,
54
+ metadata: item.metadata ? JSON.parse(item.metadata) : null,
55
+ size: item.size || calculateSize(item.value),
56
+ created_at: item.created_at,
57
+ updated_at: item.updated_at,
58
+ }));
59
+ const metricsWithMetadata = calculateResponseMetrics(itemsWithMetadata);
60
+ console.log('With metadata - Items:', itemsWithMetadata.length, 'Tokens:', metricsWithMetadata.estimatedTokens);
61
+ // The metadata version might have fewer tokens due to JSON parsing
62
+ // but the overall response structure adds overhead
63
+ // Build full response structure as the handler does
64
+ const fullResponse = {
65
+ items: itemsWithMetadata,
66
+ pagination: {
67
+ total: 100,
68
+ returned: 100,
69
+ offset: 0,
70
+ hasMore: false,
71
+ nextOffset: null,
72
+ totalCount: 100,
73
+ page: 1,
74
+ pageSize: 100,
75
+ totalPages: 1,
76
+ hasNextPage: false,
77
+ hasPreviousPage: false,
78
+ previousOffset: null,
79
+ totalSize: metricsWithMetadata.totalSize,
80
+ averageSize: metricsWithMetadata.averageSize,
81
+ defaultsApplied: { limit: true, sort: true },
82
+ truncated: false,
83
+ truncatedCount: 0,
84
+ },
85
+ };
86
+ const finalResponseJson = JSON.stringify(fullResponse, null, 2);
87
+ const finalTokens = estimateTokens(finalResponseJson);
88
+ console.log('Final response - Size:', finalResponseJson.length, 'Tokens:', finalTokens);
89
+ // This demonstrates the issue: the final response can be much larger
90
+ // than what calculateResponseMetrics estimates
91
+ console.log('Token difference:', finalTokens - metricsWithMetadata.estimatedTokens);
92
+ // With 100 items and metadata, we should be approaching or exceeding limits
93
+ if (finalTokens > 18000) {
94
+ console.log('WARNING: Response exceeds safe token limit!');
95
+ }
96
+ });
97
+ (0, globals_1.it)('should demonstrate that 50 items with metadata stays under limit', () => {
98
+ // Create sample items
99
+ const dbItems = [];
100
+ for (let i = 0; i < 50; i++) {
101
+ dbItems.push({
102
+ id: `id-${i}`,
103
+ session_id: 'test-session',
104
+ key: `test_item_${i}`,
105
+ value: `This is test content that is moderately long to simulate real data. `.repeat(3),
106
+ category: 'task',
107
+ priority: 'high',
108
+ channel: 'test',
109
+ metadata: JSON.stringify({ index: i }),
110
+ size: 200,
111
+ created_at: '2025-01-20T10:00:00Z',
112
+ updated_at: '2025-01-20T10:00:00Z',
113
+ });
114
+ }
115
+ // Transform with metadata
116
+ const itemsWithMetadata = dbItems.map(item => ({
117
+ key: item.key,
118
+ value: item.value,
119
+ category: item.category,
120
+ priority: item.priority,
121
+ channel: item.channel,
122
+ metadata: item.metadata ? JSON.parse(item.metadata) : null,
123
+ size: item.size || calculateSize(item.value),
124
+ created_at: item.created_at,
125
+ updated_at: item.updated_at,
126
+ }));
127
+ const metrics = calculateResponseMetrics(itemsWithMetadata);
128
+ // Build full response
129
+ const fullResponse = {
130
+ items: itemsWithMetadata,
131
+ pagination: {
132
+ total: 50,
133
+ returned: 50,
134
+ offset: 0,
135
+ hasMore: false,
136
+ nextOffset: null,
137
+ totalCount: 50,
138
+ page: 1,
139
+ pageSize: 50,
140
+ totalPages: 1,
141
+ hasNextPage: false,
142
+ hasPreviousPage: false,
143
+ previousOffset: null,
144
+ totalSize: metrics.totalSize,
145
+ averageSize: metrics.averageSize,
146
+ defaultsApplied: { limit: true, sort: true },
147
+ truncated: false,
148
+ truncatedCount: 0,
149
+ },
150
+ };
151
+ const finalResponseJson = JSON.stringify(fullResponse, null, 2);
152
+ const finalTokens = estimateTokens(finalResponseJson);
153
+ console.log('50 items with metadata - Tokens:', finalTokens);
154
+ // 50 items should be safe
155
+ (0, globals_1.expect)(finalTokens).toBeLessThan(18000);
156
+ (0, globals_1.expect)(finalTokens).toBeLessThan(25000); // Well under MCP limit
157
+ });
158
+ });
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+ // Attempt to reproduce the exact issue from #24
3
+ // User reported: "Error: MCP tool "context_get" response (26866 tokens) exceeds maximum allowed tokens (25000)"
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ const database_1 = require("../../utils/database");
39
+ const RepositoryManager_1 = require("../../repositories/RepositoryManager");
40
+ const os = __importStar(require("os"));
41
+ const path = __importStar(require("path"));
42
+ const fs = __importStar(require("fs"));
43
+ function estimateTokens(text) {
44
+ return Math.ceil(text.length / 4);
45
+ }
46
+ function calculateSize(value) {
47
+ return Buffer.byteLength(value, 'utf8');
48
+ }
49
+ describe('Issue #24 - Reproduce exact error', () => {
50
+ let dbManager;
51
+ let repositories;
52
+ let tempDbPath;
53
+ let testSessionId;
54
+ beforeEach(() => {
55
+ tempDbPath = path.join(os.tmpdir(), `test-issue24-reproduce-${Date.now()}.db`);
56
+ dbManager = new database_1.DatabaseManager({
57
+ filename: tempDbPath,
58
+ maxSize: 10 * 1024 * 1024,
59
+ walMode: true,
60
+ });
61
+ repositories = new RepositoryManager_1.RepositoryManager(dbManager);
62
+ // Create test session
63
+ const session = repositories.sessions.create({
64
+ name: 'Issue #24 Reproduce',
65
+ description: 'Attempting to reproduce exact token overflow',
66
+ });
67
+ testSessionId = session.id;
68
+ });
69
+ afterEach(() => {
70
+ dbManager.close();
71
+ try {
72
+ fs.unlinkSync(tempDbPath);
73
+ fs.unlinkSync(`${tempDbPath}-wal`);
74
+ fs.unlinkSync(`${tempDbPath}-shm`);
75
+ }
76
+ catch (_e) {
77
+ // Ignore
78
+ }
79
+ });
80
+ it('should reproduce token overflow with realistic data', () => {
81
+ // To get 26866 tokens, we need roughly 107,464 characters
82
+ // Let's create items with realistic content that would cause this
83
+ const itemCount = 150; // More items than default limit
84
+ const largeContent = `
85
+ This is a realistic context item that might be saved during a coding session.
86
+ It contains multiple lines of information, including:
87
+ - Task descriptions and implementation details
88
+ - Code snippets and examples
89
+ - Decision rationale and architectural notes
90
+ - Progress updates and status information
91
+ - Error messages and debugging information
92
+ - References to files and functions
93
+ - Links and external resources
94
+
95
+ The content is substantial enough to represent real-world usage where developers
96
+ save important context about their work. This might include detailed explanations
97
+ of complex logic, API documentation, configuration settings, or troubleshooting notes.
98
+
99
+ Additional metadata might include timestamps, categories, priorities, and custom
100
+ tags that help organize and retrieve the information later. All of this contributes
101
+ to the overall size of the response when multiple items are returned together.
102
+ `.trim();
103
+ // Create many items with substantial content
104
+ for (let i = 0; i < itemCount; i++) {
105
+ repositories.contexts.save(testSessionId, {
106
+ key: `context_item_${String(i).padStart(3, '0')}`,
107
+ value: `${largeContent}\n\nItem ${i} specific notes: ${String(i).repeat(20)}`,
108
+ category: ['task', 'decision', 'progress', 'note'][i % 4],
109
+ priority: ['high', 'normal', 'low'][i % 3],
110
+ channel: `channel-${i % 5}`,
111
+ metadata: JSON.stringify({
112
+ index: i,
113
+ timestamp: new Date().toISOString(),
114
+ tags: ['important', 'review', 'todo', 'done'][i % 4],
115
+ additionalData: {
116
+ relatedFiles: [`file${i}.ts`, `test${i}.spec.ts`],
117
+ relatedIssues: [`#${i * 10}`, `#${i * 10 + 1}`],
118
+ },
119
+ }),
120
+ });
121
+ }
122
+ // Query without specifying limit (should use default)
123
+ const result = repositories.contexts.queryEnhanced({
124
+ sessionId: testSessionId,
125
+ includeMetadata: true,
126
+ // No limit specified - will use repository default of 100
127
+ });
128
+ console.log(`Created ${itemCount} items, retrieved ${result.items.length} items`);
129
+ // Transform items with metadata as the handler does
130
+ const itemsWithMetadata = result.items.map(item => ({
131
+ key: item.key,
132
+ value: item.value,
133
+ category: item.category,
134
+ priority: item.priority,
135
+ channel: item.channel,
136
+ metadata: item.metadata ? JSON.parse(item.metadata) : null,
137
+ size: item.size || calculateSize(item.value),
138
+ created_at: item.created_at,
139
+ updated_at: item.updated_at,
140
+ }));
141
+ // Create the full response structure
142
+ const fullResponse = {
143
+ items: itemsWithMetadata,
144
+ pagination: {
145
+ total: result.totalCount,
146
+ returned: result.items.length,
147
+ offset: 0,
148
+ hasMore: result.totalCount > result.items.length,
149
+ nextOffset: result.items.length < result.totalCount ? result.items.length : null,
150
+ totalCount: result.totalCount,
151
+ page: 1,
152
+ pageSize: result.items.length,
153
+ totalPages: Math.ceil(result.totalCount / result.items.length),
154
+ hasNextPage: result.totalCount > result.items.length,
155
+ hasPreviousPage: false,
156
+ previousOffset: null,
157
+ totalSize: itemsWithMetadata.reduce((sum, item) => sum + (item.size || 0), 0),
158
+ averageSize: Math.round(itemsWithMetadata.reduce((sum, item) => sum + (item.size || 0), 0) / result.items.length),
159
+ defaultsApplied: { limit: true, sort: true },
160
+ truncated: false,
161
+ truncatedCount: 0,
162
+ },
163
+ };
164
+ const responseJson = JSON.stringify(fullResponse, null, 2);
165
+ const tokens = estimateTokens(responseJson);
166
+ console.log(`Response size: ${responseJson.length} bytes`);
167
+ console.log(`Estimated tokens: ${tokens}`);
168
+ console.log(`Average item size: ${Math.round(responseJson.length / result.items.length)} bytes`);
169
+ // Check if we're reproducing the issue
170
+ if (tokens > 25000) {
171
+ console.log('✅ Successfully reproduced token overflow!');
172
+ console.log(`Tokens: ${tokens} > 25000 limit`);
173
+ }
174
+ else if (tokens > 20000) {
175
+ console.log('⚠️ Approaching token limit but not exceeding');
176
+ }
177
+ else {
178
+ console.log('❌ Did not reproduce token overflow');
179
+ console.log('Need larger items or more items to reproduce');
180
+ }
181
+ // The fix should prevent this from happening
182
+ // With our changes:
183
+ // 1. Default limit is 50 when includeMetadata is true (at handler level)
184
+ // 2. Token limit is 18000 (more conservative)
185
+ // 3. Response is truncated if it exceeds limits
186
+ });
187
+ it('should show how the fix prevents overflow', () => {
188
+ // Same setup but showing how limit of 50 prevents the issue
189
+ const itemCount = 150;
190
+ const largeContent = `Large content...`.repeat(50); // Smaller for this test
191
+ for (let i = 0; i < itemCount; i++) {
192
+ repositories.contexts.save(testSessionId, {
193
+ key: `item_${i}`,
194
+ value: largeContent,
195
+ category: 'task',
196
+ priority: 'high',
197
+ });
198
+ }
199
+ // With the fix, when includeMetadata is true, default limit should be 50
200
+ // But this is handled at the handler level, not repository level
201
+ // Repository still defaults to 100
202
+ const resultWith50 = repositories.contexts.queryEnhanced({
203
+ sessionId: testSessionId,
204
+ includeMetadata: true,
205
+ limit: 50, // Explicitly set to what our fix would use
206
+ });
207
+ const itemsWithMetadata = resultWith50.items.map(item => ({
208
+ key: item.key,
209
+ value: item.value,
210
+ category: item.category,
211
+ priority: item.priority,
212
+ channel: item.channel,
213
+ metadata: item.metadata ? JSON.parse(item.metadata) : null,
214
+ size: item.size || calculateSize(item.value),
215
+ created_at: item.created_at,
216
+ updated_at: item.updated_at,
217
+ }));
218
+ const responseJson = JSON.stringify({ items: itemsWithMetadata }, null, 2);
219
+ const tokens = estimateTokens(responseJson);
220
+ console.log(`With limit=50: ${resultWith50.items.length} items, ${tokens} tokens`);
221
+ // Should be well under limit
222
+ expect(tokens).toBeLessThan(18000);
223
+ expect(tokens).toBeLessThan(25000);
224
+ });
225
+ });
@@ -0,0 +1,199 @@
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
+ const database_1 = require("../../utils/database");
37
+ const RepositoryManager_1 = require("../../repositories/RepositoryManager");
38
+ const os = __importStar(require("os"));
39
+ const path = __importStar(require("path"));
40
+ const fs = __importStar(require("fs"));
41
+ describe('Issue #24 - Token Limit with includeMetadata', () => {
42
+ let dbManager;
43
+ let repositories;
44
+ let tempDbPath;
45
+ let testSessionId;
46
+ beforeEach(() => {
47
+ tempDbPath = path.join(os.tmpdir(), `test-issue24-${Date.now()}.db`);
48
+ dbManager = new database_1.DatabaseManager({
49
+ filename: tempDbPath,
50
+ maxSize: 10 * 1024 * 1024,
51
+ walMode: true,
52
+ });
53
+ repositories = new RepositoryManager_1.RepositoryManager(dbManager);
54
+ // Create test session
55
+ const session = repositories.sessions.create({
56
+ name: 'Issue #24 Test Session',
57
+ description: 'Testing token limit with metadata',
58
+ });
59
+ testSessionId = session.id;
60
+ });
61
+ afterEach(() => {
62
+ dbManager.close();
63
+ try {
64
+ fs.unlinkSync(tempDbPath);
65
+ fs.unlinkSync(`${tempDbPath}-wal`);
66
+ fs.unlinkSync(`${tempDbPath}-shm`);
67
+ }
68
+ catch (_e) {
69
+ // Ignore
70
+ }
71
+ });
72
+ it('should not exceed token limit when includeMetadata is true with many items', () => {
73
+ // Create many items with substantial content to simulate real-world scenario
74
+ const itemCount = 200; // Create enough items to potentially exceed token limits
75
+ for (let i = 0; i < itemCount; i++) {
76
+ repositories.contexts.save(testSessionId, {
77
+ key: `test_item_${i}`,
78
+ value: `This is a test item with a moderately long value that simulates real-world context data.
79
+ It includes multiple lines and enough content to make the response substantial when
80
+ many items are returned together. Each item contributes to the total token count.
81
+ Item number: ${i}. Additional metadata and timestamps will be included when
82
+ includeMetadata is set to true, further increasing the response size.`.repeat(2),
83
+ category: i % 2 === 0 ? 'task' : 'decision',
84
+ priority: i % 3 === 0 ? 'high' : 'normal',
85
+ channel: 'test-channel',
86
+ metadata: JSON.stringify({
87
+ index: i,
88
+ timestamp: new Date().toISOString(),
89
+ tags: ['test', 'issue24', 'metadata'],
90
+ }),
91
+ });
92
+ }
93
+ // Query with includeMetadata: true (this was causing the issue)
94
+ const result = repositories.contexts.queryEnhanced({
95
+ sessionId: testSessionId,
96
+ includeMetadata: true,
97
+ // No limit specified - repository uses default of 100
98
+ // The handler level would apply dynamic limits, but repository doesn't know about includeMetadata
99
+ });
100
+ // Repository default is 100 items (it doesn't know about metadata)
101
+ // The dynamic limiting happens at the handler level in index.ts
102
+ expect(result.items.length).toBeLessThanOrEqual(100);
103
+ expect(result.totalCount).toBe(itemCount);
104
+ // Simulate the response construction with metadata
105
+ const itemsWithMetadata = result.items.map(item => ({
106
+ key: item.key,
107
+ value: item.value,
108
+ category: item.category,
109
+ priority: item.priority,
110
+ channel: item.channel,
111
+ metadata: item.metadata ? JSON.parse(item.metadata) : null,
112
+ size: item.size || Buffer.byteLength(item.value, 'utf8'),
113
+ created_at: item.created_at,
114
+ updated_at: item.updated_at,
115
+ }));
116
+ // Create the full response structure
117
+ const response = {
118
+ items: itemsWithMetadata,
119
+ pagination: {
120
+ total: result.totalCount,
121
+ returned: result.items.length,
122
+ offset: 0,
123
+ hasMore: result.totalCount > result.items.length,
124
+ nextOffset: result.items.length < result.totalCount ? result.items.length : null,
125
+ totalCount: result.totalCount,
126
+ page: 1,
127
+ pageSize: 50,
128
+ totalPages: Math.ceil(result.totalCount / 50),
129
+ hasNextPage: result.totalCount > result.items.length,
130
+ hasPreviousPage: false,
131
+ previousOffset: null,
132
+ totalSize: 0,
133
+ averageSize: 0,
134
+ defaultsApplied: { limit: true, sort: true },
135
+ truncated: false,
136
+ truncatedCount: 0,
137
+ },
138
+ };
139
+ // Calculate token estimate for the response
140
+ const responseJson = JSON.stringify(response, null, 2);
141
+ const estimatedTokens = Math.ceil(responseJson.length / 4);
142
+ console.log(`Response size: ${responseJson.length} bytes`);
143
+ console.log(`Estimated tokens: ${estimatedTokens}`);
144
+ console.log(`Items returned: ${result.items.length} of ${result.totalCount}`);
145
+ // Note: Repository returns 100 items by default, which may exceed token limits
146
+ // This demonstrates why the handler level needs to apply dynamic limiting
147
+ // The handler would truncate this response to prevent token overflow
148
+ console.log(`Repository returned ${result.items.length} items with ${estimatedTokens} tokens`);
149
+ // This shows the problem: repository default of 100 can exceed limits
150
+ if (estimatedTokens > 25000) {
151
+ console.log('This demonstrates why dynamic limiting at handler level is needed!');
152
+ }
153
+ });
154
+ it('should handle explicit high limit with metadata by truncating', () => {
155
+ // Create many items
156
+ const itemCount = 500;
157
+ for (let i = 0; i < itemCount; i++) {
158
+ repositories.contexts.save(testSessionId, {
159
+ key: `large_item_${i}`,
160
+ value: `Large content ${i}`.repeat(50), // Make items larger
161
+ category: 'task',
162
+ priority: 'high',
163
+ channel: 'large-channel',
164
+ });
165
+ }
166
+ // Query with a high explicit limit and metadata
167
+ const result = repositories.contexts.queryEnhanced({
168
+ sessionId: testSessionId,
169
+ includeMetadata: true,
170
+ limit: 1000, // Explicitly request many items
171
+ });
172
+ // Repository will cap at the actual number of items available
173
+ // Since we created 500 items, we should get all 500
174
+ // The token limiting would happen at the handler level, not repository level
175
+ expect(result.items.length).toBe(500); // Gets all items since limit > itemCount
176
+ // Calculate response size
177
+ const itemsWithMetadata = result.items.map(item => ({
178
+ key: item.key,
179
+ value: item.value,
180
+ category: item.category,
181
+ priority: item.priority,
182
+ channel: item.channel,
183
+ metadata: item.metadata ? JSON.parse(item.metadata) : null,
184
+ size: item.size || Buffer.byteLength(item.value, 'utf8'),
185
+ created_at: item.created_at,
186
+ updated_at: item.updated_at,
187
+ }));
188
+ const responseJson = JSON.stringify({ items: itemsWithMetadata }, null, 2);
189
+ const estimatedTokens = Math.ceil(responseJson.length / 4);
190
+ console.log(`Large query - Items: ${result.items.length}, Tokens: ${estimatedTokens}`);
191
+ // This test shows that without handler-level limiting, large queries would overflow
192
+ // The handler's dynamic limiting would truncate this to safe levels
193
+ if (estimatedTokens > 25000) {
194
+ console.log(`Would need truncation: ${estimatedTokens} tokens exceeds 25000 limit`);
195
+ }
196
+ // The test demonstrates the need for handler-level dynamic limiting
197
+ expect(result.items.length).toBe(500); // Repository returns what was requested
198
+ });
199
+ });