@qwickapps/qwickbrain-proxy 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/engineering/bugs/BUG-qwickbrain-proxy-cache-and-design.md +840 -0
- package/.github/workflows/publish.yml +13 -0
- package/CHANGELOG.md +54 -0
- package/dist/db/schema.d.ts +63 -6
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +17 -2
- package/dist/db/schema.js.map +1 -1
- package/dist/lib/__tests__/cache-manager.test.js +146 -83
- package/dist/lib/__tests__/cache-manager.test.js.map +1 -1
- package/dist/lib/__tests__/proxy-server.test.js +16 -44
- package/dist/lib/__tests__/proxy-server.test.js.map +1 -1
- package/dist/lib/__tests__/sse-invalidation-listener.test.d.ts +2 -0
- package/dist/lib/__tests__/sse-invalidation-listener.test.d.ts.map +1 -0
- package/dist/lib/__tests__/sse-invalidation-listener.test.js +245 -0
- package/dist/lib/__tests__/sse-invalidation-listener.test.js.map +1 -0
- package/dist/lib/__tests__/write-queue-manager.test.d.ts +2 -0
- package/dist/lib/__tests__/write-queue-manager.test.d.ts.map +1 -0
- package/dist/lib/__tests__/write-queue-manager.test.js +291 -0
- package/dist/lib/__tests__/write-queue-manager.test.js.map +1 -0
- package/dist/lib/cache-manager.d.ts +35 -6
- package/dist/lib/cache-manager.d.ts.map +1 -1
- package/dist/lib/cache-manager.js +154 -41
- package/dist/lib/cache-manager.js.map +1 -1
- package/dist/lib/connection-manager.d.ts.map +1 -1
- package/dist/lib/connection-manager.js +4 -1
- package/dist/lib/connection-manager.js.map +1 -1
- package/dist/lib/proxy-server.d.ts +6 -0
- package/dist/lib/proxy-server.d.ts.map +1 -1
- package/dist/lib/proxy-server.js +182 -87
- package/dist/lib/proxy-server.js.map +1 -1
- package/dist/lib/qwickbrain-client.d.ts +4 -0
- package/dist/lib/qwickbrain-client.d.ts.map +1 -1
- package/dist/lib/qwickbrain-client.js +133 -0
- package/dist/lib/qwickbrain-client.js.map +1 -1
- package/dist/lib/sse-invalidation-listener.d.ts +27 -0
- package/dist/lib/sse-invalidation-listener.d.ts.map +1 -0
- package/dist/lib/sse-invalidation-listener.js +145 -0
- package/dist/lib/sse-invalidation-listener.js.map +1 -0
- package/dist/lib/tools.d.ts +21 -0
- package/dist/lib/tools.d.ts.map +1 -0
- package/dist/lib/tools.js +488 -0
- package/dist/lib/tools.js.map +1 -0
- package/dist/lib/write-queue-manager.d.ts +88 -0
- package/dist/lib/write-queue-manager.d.ts.map +1 -0
- package/dist/lib/write-queue-manager.js +191 -0
- package/dist/lib/write-queue-manager.js.map +1 -0
- package/dist/types/config.d.ts +7 -42
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +1 -6
- package/dist/types/config.js.map +1 -1
- package/drizzle/0002_lru_cache_migration.sql +94 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +6 -2
- package/scripts/rebuild-sqlite.sh +26 -0
- package/src/db/schema.ts +17 -2
- package/src/lib/__tests__/cache-manager.test.ts +180 -90
- package/src/lib/__tests__/proxy-server.test.ts +16 -51
- package/src/lib/__tests__/sse-invalidation-listener.test.ts +326 -0
- package/src/lib/__tests__/write-queue-manager.test.ts +383 -0
- package/src/lib/cache-manager.ts +198 -46
- package/src/lib/connection-manager.ts +4 -1
- package/src/lib/proxy-server.ts +222 -90
- package/src/lib/qwickbrain-client.ts +145 -1
- package/src/lib/sse-invalidation-listener.ts +171 -0
- package/src/lib/tools.ts +500 -0
- package/src/lib/write-queue-manager.ts +271 -0
- package/src/types/config.ts +1 -6
package/src/lib/proxy-server.ts
CHANGED
|
@@ -7,6 +7,9 @@ import {
|
|
|
7
7
|
import { ConnectionManager } from './connection-manager.js';
|
|
8
8
|
import { CacheManager } from './cache-manager.js';
|
|
9
9
|
import { QwickBrainClient } from './qwickbrain-client.js';
|
|
10
|
+
import { WriteQueueManager } from './write-queue-manager.js';
|
|
11
|
+
import { SSEInvalidationListener } from './sse-invalidation-listener.js';
|
|
12
|
+
import { QWICKBRAIN_TOOLS, requiresConnection } from './tools.js';
|
|
10
13
|
import type { Config } from '../types/config.js';
|
|
11
14
|
import type { DB } from '../db/client.js';
|
|
12
15
|
import type { MCPResponse, MCPResponseMetadata } from '../types/mcp.js';
|
|
@@ -17,6 +20,8 @@ export class ProxyServer {
|
|
|
17
20
|
private connectionManager: ConnectionManager;
|
|
18
21
|
private cacheManager: CacheManager;
|
|
19
22
|
private qwickbrainClient: QwickBrainClient;
|
|
23
|
+
private writeQueueManager: WriteQueueManager;
|
|
24
|
+
private sseInvalidationListener: SSEInvalidationListener | null = null;
|
|
20
25
|
private config: Config;
|
|
21
26
|
|
|
22
27
|
constructor(db: DB, config: Config) {
|
|
@@ -41,6 +46,16 @@ export class ProxyServer {
|
|
|
41
46
|
);
|
|
42
47
|
|
|
43
48
|
this.cacheManager = new CacheManager(db, config.cache);
|
|
49
|
+
this.writeQueueManager = new WriteQueueManager(db, this.qwickbrainClient);
|
|
50
|
+
|
|
51
|
+
// Initialize SSE invalidation listener if in SSE mode
|
|
52
|
+
if (config.qwickbrain.mode === 'sse' && config.qwickbrain.url) {
|
|
53
|
+
this.sseInvalidationListener = new SSEInvalidationListener(
|
|
54
|
+
config.qwickbrain.url,
|
|
55
|
+
this.cacheManager,
|
|
56
|
+
config.qwickbrain.apiKey
|
|
57
|
+
);
|
|
58
|
+
}
|
|
44
59
|
|
|
45
60
|
this.setupHandlers();
|
|
46
61
|
this.setupConnectionListeners();
|
|
@@ -57,6 +72,14 @@ export class ProxyServer {
|
|
|
57
72
|
|
|
58
73
|
this.connectionManager.on('connected', ({ latencyMs }) => {
|
|
59
74
|
console.error(`Connected to QwickBrain (latency: ${latencyMs}ms)`);
|
|
75
|
+
// Event-driven: trigger background sync when connection restored
|
|
76
|
+
this.onConnectionRestored().catch(err => {
|
|
77
|
+
console.error('Background sync error:', err);
|
|
78
|
+
});
|
|
79
|
+
// Sync pending write operations
|
|
80
|
+
this.syncWriteQueue().catch(err => {
|
|
81
|
+
console.error('Write queue sync error:', err);
|
|
82
|
+
});
|
|
60
83
|
});
|
|
61
84
|
|
|
62
85
|
this.connectionManager.on('disconnected', ({ error }) => {
|
|
@@ -64,61 +87,43 @@ export class ProxyServer {
|
|
|
64
87
|
});
|
|
65
88
|
}
|
|
66
89
|
|
|
67
|
-
private
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
90
|
+
private async onConnectionRestored(): Promise<void> {
|
|
91
|
+
console.error('Starting background cache sync...');
|
|
92
|
+
|
|
93
|
+
// Preload critical documents in background
|
|
94
|
+
const preloadItems = this.config.cache.preload || [];
|
|
95
|
+
for (const itemType of preloadItems) {
|
|
96
|
+
try {
|
|
97
|
+
if (itemType === 'workflows') {
|
|
98
|
+
// TODO: List and cache all workflows
|
|
99
|
+
console.error('Preloading workflows...');
|
|
100
|
+
} else if (itemType === 'rules') {
|
|
101
|
+
// TODO: List and cache all rules
|
|
102
|
+
console.error('Preloading rules...');
|
|
78
103
|
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(`Failed to preload ${itemType}:`, error);
|
|
79
106
|
}
|
|
107
|
+
}
|
|
80
108
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
type: 'object',
|
|
100
|
-
properties: {
|
|
101
|
-
name: { type: 'string', description: 'Document name' },
|
|
102
|
-
doc_type: { type: 'string', description: 'Document type (rule, frd, design, etc.)' },
|
|
103
|
-
project: { type: 'string', description: 'Project name (optional)' },
|
|
104
|
-
},
|
|
105
|
-
required: ['name', 'doc_type'],
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
name: 'get_memory',
|
|
110
|
-
description: 'Get a memory/context document by name (cached)',
|
|
111
|
-
inputSchema: {
|
|
112
|
-
type: 'object',
|
|
113
|
-
properties: {
|
|
114
|
-
name: { type: 'string', description: 'Memory name' },
|
|
115
|
-
project: { type: 'string', description: 'Project name (optional)' },
|
|
116
|
-
},
|
|
117
|
-
required: ['name'],
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
};
|
|
109
|
+
console.error('Background cache sync complete');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private async syncWriteQueue(): Promise<void> {
|
|
113
|
+
const pendingCount = await this.writeQueueManager.getPendingCount();
|
|
114
|
+
if (pendingCount === 0) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.error(`Syncing ${pendingCount} pending write operations...`);
|
|
119
|
+
const { synced, failed } = await this.writeQueueManager.syncPendingOperations();
|
|
120
|
+
console.error(`Write queue sync complete: ${synced} synced, ${failed} failed`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private setupHandlers(): void {
|
|
124
|
+
// Static tool listing - always returns all tools regardless of connection state
|
|
125
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
126
|
+
return { tools: QWICKBRAIN_TOOLS };
|
|
122
127
|
});
|
|
123
128
|
|
|
124
129
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -145,8 +150,54 @@ export class ProxyServer {
|
|
|
145
150
|
args?.project as string | undefined
|
|
146
151
|
);
|
|
147
152
|
break;
|
|
153
|
+
case 'create_document':
|
|
154
|
+
case 'update_document':
|
|
155
|
+
result = await this.handleCreateDocument(
|
|
156
|
+
args?.doc_type as string,
|
|
157
|
+
args?.name as string,
|
|
158
|
+
args?.content as string,
|
|
159
|
+
args?.project as string | undefined,
|
|
160
|
+
args?.metadata as Record<string, unknown> | undefined
|
|
161
|
+
);
|
|
162
|
+
break;
|
|
163
|
+
case 'set_memory':
|
|
164
|
+
case 'update_memory':
|
|
165
|
+
result = await this.handleSetMemory(
|
|
166
|
+
args?.name as string,
|
|
167
|
+
args?.content as string,
|
|
168
|
+
args?.project as string | undefined,
|
|
169
|
+
args?.metadata as Record<string, unknown> | undefined
|
|
170
|
+
);
|
|
171
|
+
break;
|
|
148
172
|
default:
|
|
149
173
|
// Generic forwarding for all other tools (analyze_repository, search_codebase, etc.)
|
|
174
|
+
// Check if tool requires connection
|
|
175
|
+
if (requiresConnection(name) && this.connectionManager.getState() !== 'connected') {
|
|
176
|
+
// Return offline error for non-cacheable tools
|
|
177
|
+
return {
|
|
178
|
+
content: [
|
|
179
|
+
{
|
|
180
|
+
type: 'text',
|
|
181
|
+
text: JSON.stringify({
|
|
182
|
+
error: {
|
|
183
|
+
code: 'OFFLINE',
|
|
184
|
+
message: `QwickBrain offline - "${name}" requires active connection`,
|
|
185
|
+
suggestions: [
|
|
186
|
+
'Check internet connection',
|
|
187
|
+
'Wait for automatic reconnection',
|
|
188
|
+
'Cached tools (get_workflow, get_document, get_memory) work offline',
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
_metadata: {
|
|
192
|
+
source: 'cache',
|
|
193
|
+
status: this.connectionManager.getState(),
|
|
194
|
+
},
|
|
195
|
+
}, null, 2),
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
isError: true,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
150
201
|
result = await this.handleGenericTool(name, args || {});
|
|
151
202
|
break;
|
|
152
203
|
}
|
|
@@ -200,17 +251,17 @@ export class ProxyServer {
|
|
|
200
251
|
name: string,
|
|
201
252
|
project?: string
|
|
202
253
|
): Promise<MCPResponse> {
|
|
203
|
-
// Try cache first
|
|
254
|
+
// Try cache first (LRU cache never expires, always valid if present)
|
|
204
255
|
const cached = await this.cacheManager.getDocument(docType, name, project);
|
|
205
256
|
|
|
206
|
-
if (cached
|
|
257
|
+
if (cached) {
|
|
207
258
|
return {
|
|
208
259
|
data: cached.data,
|
|
209
260
|
_metadata: this.createMetadata('cache', cached.age),
|
|
210
261
|
};
|
|
211
262
|
}
|
|
212
263
|
|
|
213
|
-
//
|
|
264
|
+
// Not cached - try remote if connected
|
|
214
265
|
if (this.connectionManager.getState() === 'connected') {
|
|
215
266
|
try {
|
|
216
267
|
const result = await this.connectionManager.execute(async () => {
|
|
@@ -232,22 +283,11 @@ export class ProxyServer {
|
|
|
232
283
|
};
|
|
233
284
|
} catch (error) {
|
|
234
285
|
console.error('Failed to fetch from QwickBrain:', error);
|
|
235
|
-
// Fall through to
|
|
286
|
+
// Fall through to error
|
|
236
287
|
}
|
|
237
288
|
}
|
|
238
289
|
|
|
239
|
-
//
|
|
240
|
-
if (cached) {
|
|
241
|
-
return {
|
|
242
|
-
data: cached.data,
|
|
243
|
-
_metadata: {
|
|
244
|
-
...this.createMetadata('stale_cache', cached.age),
|
|
245
|
-
warning: `QwickBrain unavailable - serving cached data (${cached.age}s old)`,
|
|
246
|
-
},
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// No cache, no connection
|
|
290
|
+
// No cache and remote failed/unavailable
|
|
251
291
|
return {
|
|
252
292
|
error: {
|
|
253
293
|
code: 'UNAVAILABLE',
|
|
@@ -263,16 +303,17 @@ export class ProxyServer {
|
|
|
263
303
|
}
|
|
264
304
|
|
|
265
305
|
private async handleGetMemory(name: string, project?: string): Promise<MCPResponse> {
|
|
266
|
-
//
|
|
306
|
+
// Try cache first (LRU cache never expires, always valid if present)
|
|
267
307
|
const cached = await this.cacheManager.getMemory(name, project);
|
|
268
308
|
|
|
269
|
-
if (cached
|
|
309
|
+
if (cached) {
|
|
270
310
|
return {
|
|
271
311
|
data: cached.data,
|
|
272
312
|
_metadata: this.createMetadata('cache', cached.age),
|
|
273
313
|
};
|
|
274
314
|
}
|
|
275
315
|
|
|
316
|
+
// Not cached - try remote if connected
|
|
276
317
|
if (this.connectionManager.getState() === 'connected') {
|
|
277
318
|
try {
|
|
278
319
|
const result = await this.connectionManager.execute(async () => {
|
|
@@ -287,19 +328,11 @@ export class ProxyServer {
|
|
|
287
328
|
};
|
|
288
329
|
} catch (error) {
|
|
289
330
|
console.error('Failed to fetch memory from QwickBrain:', error);
|
|
331
|
+
// Fall through to error
|
|
290
332
|
}
|
|
291
333
|
}
|
|
292
334
|
|
|
293
|
-
|
|
294
|
-
return {
|
|
295
|
-
data: cached.data,
|
|
296
|
-
_metadata: {
|
|
297
|
-
...this.createMetadata('stale_cache', cached.age),
|
|
298
|
-
warning: `QwickBrain unavailable - serving cached memory (${cached.age}s old)`,
|
|
299
|
-
},
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
|
|
335
|
+
// No cache and remote failed/unavailable
|
|
303
336
|
return {
|
|
304
337
|
error: {
|
|
305
338
|
code: 'UNAVAILABLE',
|
|
@@ -310,6 +343,94 @@ export class ProxyServer {
|
|
|
310
343
|
};
|
|
311
344
|
}
|
|
312
345
|
|
|
346
|
+
private async handleCreateDocument(
|
|
347
|
+
docType: string,
|
|
348
|
+
name: string,
|
|
349
|
+
content: string,
|
|
350
|
+
project?: string,
|
|
351
|
+
metadata?: Record<string, unknown>
|
|
352
|
+
): Promise<MCPResponse> {
|
|
353
|
+
// Always update local cache first
|
|
354
|
+
await this.cacheManager.setDocument(docType, name, content, project, metadata);
|
|
355
|
+
|
|
356
|
+
// If connected, sync immediately
|
|
357
|
+
if (this.connectionManager.getState() === 'connected') {
|
|
358
|
+
try {
|
|
359
|
+
await this.connectionManager.execute(async () => {
|
|
360
|
+
await this.qwickbrainClient.createDocument(docType, name, content, project, metadata);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
data: { success: true },
|
|
365
|
+
_metadata: this.createMetadata('live'),
|
|
366
|
+
};
|
|
367
|
+
} catch (error) {
|
|
368
|
+
console.error('Failed to create document on QwickBrain:', error);
|
|
369
|
+
// Fall through to queue
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// If offline or sync failed, queue for later
|
|
374
|
+
await this.writeQueueManager.queueOperation('create_document', {
|
|
375
|
+
docType,
|
|
376
|
+
name,
|
|
377
|
+
content,
|
|
378
|
+
project,
|
|
379
|
+
metadata,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
data: { success: true, queued: true },
|
|
384
|
+
_metadata: {
|
|
385
|
+
...this.createMetadata('cache'),
|
|
386
|
+
warning: 'Operation queued - will sync when connection restored',
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private async handleSetMemory(
|
|
392
|
+
name: string,
|
|
393
|
+
content: string,
|
|
394
|
+
project?: string,
|
|
395
|
+
metadata?: Record<string, unknown>
|
|
396
|
+
): Promise<MCPResponse> {
|
|
397
|
+
// Always update local cache first
|
|
398
|
+
await this.cacheManager.setMemory(name, content, project, metadata);
|
|
399
|
+
|
|
400
|
+
// If connected, sync immediately
|
|
401
|
+
if (this.connectionManager.getState() === 'connected') {
|
|
402
|
+
try {
|
|
403
|
+
await this.connectionManager.execute(async () => {
|
|
404
|
+
await this.qwickbrainClient.setMemory(name, content, project, metadata);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
data: { success: true },
|
|
409
|
+
_metadata: this.createMetadata('live'),
|
|
410
|
+
};
|
|
411
|
+
} catch (error) {
|
|
412
|
+
console.error('Failed to set memory on QwickBrain:', error);
|
|
413
|
+
// Fall through to queue
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// If offline or sync failed, queue for later
|
|
418
|
+
await this.writeQueueManager.queueOperation('set_memory', {
|
|
419
|
+
name,
|
|
420
|
+
content,
|
|
421
|
+
project,
|
|
422
|
+
metadata,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
data: { success: true, queued: true },
|
|
427
|
+
_metadata: {
|
|
428
|
+
...this.createMetadata('cache'),
|
|
429
|
+
warning: 'Operation queued - will sync when connection restored',
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
313
434
|
private async handleGenericTool(name: string, args: Record<string, unknown>): Promise<MCPResponse> {
|
|
314
435
|
// Generic tool forwarding - no caching for non-document tools
|
|
315
436
|
if (this.connectionManager.getState() !== 'connected') {
|
|
@@ -362,18 +483,18 @@ export class ProxyServer {
|
|
|
362
483
|
}
|
|
363
484
|
|
|
364
485
|
async start(): Promise<void> {
|
|
365
|
-
//
|
|
366
|
-
|
|
367
|
-
if (documentsDeleted > 0 || memoriesDeleted > 0) {
|
|
368
|
-
console.error(`Cache cleanup: removed ${documentsDeleted} documents, ${memoriesDeleted} memories`);
|
|
369
|
-
}
|
|
486
|
+
// LRU cache handles eviction automatically when storage limit reached
|
|
487
|
+
// No need for startup cleanup with LRU-based cache
|
|
370
488
|
|
|
371
|
-
//
|
|
372
|
-
await this.qwickbrainClient.connect();
|
|
373
|
-
|
|
374
|
-
// Start connection manager
|
|
489
|
+
// Start connection manager (handles connection gracefully, doesn't throw)
|
|
375
490
|
await this.connectionManager.start();
|
|
376
491
|
|
|
492
|
+
// Start SSE invalidation listener if configured
|
|
493
|
+
if (this.sseInvalidationListener) {
|
|
494
|
+
await this.sseInvalidationListener.start();
|
|
495
|
+
console.error('SSE cache invalidation listener started');
|
|
496
|
+
}
|
|
497
|
+
|
|
377
498
|
// Start MCP server
|
|
378
499
|
const transport = new StdioServerTransport();
|
|
379
500
|
await this.server.connect(transport);
|
|
@@ -383,7 +504,18 @@ export class ProxyServer {
|
|
|
383
504
|
|
|
384
505
|
async stop(): Promise<void> {
|
|
385
506
|
this.connectionManager.stop();
|
|
386
|
-
|
|
507
|
+
|
|
508
|
+
// Stop SSE invalidation listener
|
|
509
|
+
if (this.sseInvalidationListener) {
|
|
510
|
+
this.sseInvalidationListener.stop();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
try {
|
|
514
|
+
await this.qwickbrainClient.disconnect();
|
|
515
|
+
} catch (error) {
|
|
516
|
+
// Ignore disconnect errors (client may never have connected)
|
|
517
|
+
console.error('Disconnect error (ignoring):', error);
|
|
518
|
+
}
|
|
387
519
|
await this.server.close();
|
|
388
520
|
}
|
|
389
521
|
}
|
|
@@ -232,10 +232,14 @@ export class QwickBrainClient {
|
|
|
232
232
|
if (this.mode === 'mcp' || this.mode === 'sse') {
|
|
233
233
|
// For MCP/SSE mode, check if client is connected
|
|
234
234
|
if (!this.client) {
|
|
235
|
+
// Initialize connection on first health check
|
|
235
236
|
await this.connect();
|
|
237
|
+
if (!this.client) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
236
240
|
}
|
|
237
241
|
// Try listing tools as health check
|
|
238
|
-
await this.client
|
|
242
|
+
await this.client.listTools();
|
|
239
243
|
return true;
|
|
240
244
|
} else {
|
|
241
245
|
// For HTTP mode, ping health endpoint
|
|
@@ -246,6 +250,9 @@ export class QwickBrainClient {
|
|
|
246
250
|
return response.ok;
|
|
247
251
|
}
|
|
248
252
|
} catch (error) {
|
|
253
|
+
// Connection dropped - clear client to force reconnection
|
|
254
|
+
this.client = null;
|
|
255
|
+
this.transport = null;
|
|
249
256
|
return false;
|
|
250
257
|
}
|
|
251
258
|
}
|
|
@@ -306,6 +313,143 @@ export class QwickBrainClient {
|
|
|
306
313
|
}
|
|
307
314
|
}
|
|
308
315
|
|
|
316
|
+
async createDocument(
|
|
317
|
+
docType: string,
|
|
318
|
+
name: string,
|
|
319
|
+
content: string,
|
|
320
|
+
project?: string,
|
|
321
|
+
metadata?: Record<string, unknown>
|
|
322
|
+
): Promise<void> {
|
|
323
|
+
if (this.mode === 'mcp' || this.mode === 'sse') {
|
|
324
|
+
if (!this.client) {
|
|
325
|
+
throw new Error('MCP client not connected');
|
|
326
|
+
}
|
|
327
|
+
await this.client.callTool({
|
|
328
|
+
name: 'create_document',
|
|
329
|
+
arguments: {
|
|
330
|
+
doc_type: docType,
|
|
331
|
+
name,
|
|
332
|
+
content,
|
|
333
|
+
project,
|
|
334
|
+
metadata,
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
} else {
|
|
338
|
+
if (!this.config.url) {
|
|
339
|
+
throw new Error('HTTP mode requires url to be configured');
|
|
340
|
+
}
|
|
341
|
+
const response = await fetch(`${this.config.url}/mcp/document/create`, {
|
|
342
|
+
method: 'POST',
|
|
343
|
+
headers: {
|
|
344
|
+
'Content-Type': 'application/json',
|
|
345
|
+
...(this.config.apiKey && { 'Authorization': `Bearer ${this.config.apiKey}` }),
|
|
346
|
+
},
|
|
347
|
+
body: JSON.stringify({ doc_type: docType, name, content, project, metadata }),
|
|
348
|
+
});
|
|
349
|
+
if (!response.ok) {
|
|
350
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async setMemory(
|
|
356
|
+
name: string,
|
|
357
|
+
content: string,
|
|
358
|
+
project?: string,
|
|
359
|
+
metadata?: Record<string, unknown>
|
|
360
|
+
): Promise<void> {
|
|
361
|
+
if (this.mode === 'mcp' || this.mode === 'sse') {
|
|
362
|
+
if (!this.client) {
|
|
363
|
+
throw new Error('MCP client not connected');
|
|
364
|
+
}
|
|
365
|
+
await this.client.callTool({
|
|
366
|
+
name: 'set_memory',
|
|
367
|
+
arguments: {
|
|
368
|
+
name,
|
|
369
|
+
content,
|
|
370
|
+
project,
|
|
371
|
+
metadata,
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
} else {
|
|
375
|
+
if (!this.config.url) {
|
|
376
|
+
throw new Error('HTTP mode requires url to be configured');
|
|
377
|
+
}
|
|
378
|
+
const response = await fetch(`${this.config.url}/mcp/memory/set`, {
|
|
379
|
+
method: 'POST',
|
|
380
|
+
headers: {
|
|
381
|
+
'Content-Type': 'application/json',
|
|
382
|
+
...(this.config.apiKey && { 'Authorization': `Bearer ${this.config.apiKey}` }),
|
|
383
|
+
},
|
|
384
|
+
body: JSON.stringify({ name, content, project, metadata }),
|
|
385
|
+
});
|
|
386
|
+
if (!response.ok) {
|
|
387
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async deleteDocument(docType: string, name: string, project?: string): Promise<void> {
|
|
393
|
+
if (this.mode === 'mcp' || this.mode === 'sse') {
|
|
394
|
+
if (!this.client) {
|
|
395
|
+
throw new Error('MCP client not connected');
|
|
396
|
+
}
|
|
397
|
+
await this.client.callTool({
|
|
398
|
+
name: 'delete_document',
|
|
399
|
+
arguments: {
|
|
400
|
+
doc_type: docType,
|
|
401
|
+
name,
|
|
402
|
+
project,
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
} else {
|
|
406
|
+
if (!this.config.url) {
|
|
407
|
+
throw new Error('HTTP mode requires url to be configured');
|
|
408
|
+
}
|
|
409
|
+
const response = await fetch(`${this.config.url}/mcp/document/delete`, {
|
|
410
|
+
method: 'POST',
|
|
411
|
+
headers: {
|
|
412
|
+
'Content-Type': 'application/json',
|
|
413
|
+
...(this.config.apiKey && { 'Authorization': `Bearer ${this.config.apiKey}` }),
|
|
414
|
+
},
|
|
415
|
+
body: JSON.stringify({ doc_type: docType, name, project }),
|
|
416
|
+
});
|
|
417
|
+
if (!response.ok) {
|
|
418
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async deleteMemory(name: string, project?: string): Promise<void> {
|
|
424
|
+
if (this.mode === 'mcp' || this.mode === 'sse') {
|
|
425
|
+
if (!this.client) {
|
|
426
|
+
throw new Error('MCP client not connected');
|
|
427
|
+
}
|
|
428
|
+
await this.client.callTool({
|
|
429
|
+
name: 'delete_memory',
|
|
430
|
+
arguments: {
|
|
431
|
+
name,
|
|
432
|
+
project,
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
} else {
|
|
436
|
+
if (!this.config.url) {
|
|
437
|
+
throw new Error('HTTP mode requires url to be configured');
|
|
438
|
+
}
|
|
439
|
+
const response = await fetch(`${this.config.url}/mcp/memory/delete`, {
|
|
440
|
+
method: 'POST',
|
|
441
|
+
headers: {
|
|
442
|
+
'Content-Type': 'application/json',
|
|
443
|
+
...(this.config.apiKey && { 'Authorization': `Bearer ${this.config.apiKey}` }),
|
|
444
|
+
},
|
|
445
|
+
body: JSON.stringify({ name, project }),
|
|
446
|
+
});
|
|
447
|
+
if (!response.ok) {
|
|
448
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
309
453
|
async disconnect(): Promise<void> {
|
|
310
454
|
if ((this.mode === 'mcp' || this.mode === 'sse') && this.client && this.transport) {
|
|
311
455
|
await this.client.close();
|