Kraft102 commited on
Commit
76b54f1
·
verified ·
1 Parent(s): bbf18eb

Deploy from GitHub Actions 2025-12-16_02-29-01

Browse files
apps/backend/src/index.ts CHANGED
@@ -32,7 +32,7 @@ if (typeof global.Path2D === 'undefined') {
32
  global.Path2D = class Path2D { };
33
  }
34
 
35
- const __dirname = typeof import.meta !== 'undefined' && import.meta.url
36
  ? new URL('.', import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1')
37
  : process.cwd();
38
  // Load .env from backend directory, or root if not found
@@ -174,7 +174,7 @@ async function startServer() {
174
  // 🧠 NEURAL ORGANS INITIALIZATION
175
  // Phase B: Knowledge Synthesis
176
  // ============================================
177
-
178
  // 1. Initialize Vector Service (Dark Matter)
179
  vectorService.init().then(() => {
180
  console.log('🌑 [DARK MATTER] Vector Engine Ready (384 dimensions)');
@@ -187,11 +187,11 @@ async function startServer() {
187
  console.log('🐝 [HIVE] Neural Telepathy Bus Attached');
188
 
189
  // 3. Start Knowledge Compiler (Brain) - Watch DropZone
190
- const DROPZONE_PATH = process.env.DROPZONE_PATH ||
191
- (process.platform === 'win32'
192
  ? 'C:\\Users\\claus\\Desktop\\WidgeTDC_DropZone'
193
  : '/app/data/dropzone');
194
-
195
  neuralCompiler.startWatching(DROPZONE_PATH).then(() => {
196
  console.log(`📚 [BRAIN] Neural Compiler watching: ${DROPZONE_PATH}`);
197
  }).catch(err => {
@@ -210,11 +210,11 @@ async function startServer() {
210
  // 5. Start Prometheus Code Scanner (every hour)
211
  const SCAN_INTERVAL = parseInt(process.env.PROMETHEUS_SCAN_INTERVAL || '3600000');
212
  setTimeout(() => {
213
- prometheus.scanAndPropose(path.join(__dirname, 'services')).catch(() => {});
214
  }, 10000); // Initial scan after 10s
215
-
216
  setInterval(() => {
217
- prometheus.scanAndPropose(path.join(__dirname, 'services')).catch(() => {});
218
  }, SCAN_INTERVAL);
219
  console.log('🔥 [PROMETHEUS] Code Evolution Scanner Active');
220
 
@@ -407,7 +407,10 @@ async function startServer() {
407
  vidensarkivBatchAddHandler,
408
  vidensarkivGetRelatedHandler,
409
  vidensarkivListHandler,
410
- vidensarkivStatsHandler
 
 
 
411
  } = await import('./mcp/toolHandlers.js');
412
 
413
  mcpRegistry.registerTool('vidensarkiv.search', vidensarkivSearchHandler);
@@ -416,6 +419,9 @@ async function startServer() {
416
  mcpRegistry.registerTool('vidensarkiv.get_related', vidensarkivGetRelatedHandler);
417
  mcpRegistry.registerTool('vidensarkiv.list', vidensarkivListHandler);
418
  mcpRegistry.registerTool('vidensarkiv.stats', vidensarkivStatsHandler);
 
 
 
419
 
420
  // TaskRecorder Tools
421
  const {
@@ -565,11 +571,11 @@ async function startServer() {
565
  })();
566
 
567
  // Step 4: Setup routes
568
-
569
  // 🛡️ ANGEL PROXY: Security Shield on all API routes
570
  app.use('/api', AngelProxy.cortexFirewall);
571
  console.log('🛡️ [ANGEL] Cortex Firewall Active on /api/*');
572
-
573
  app.use('/api/mcp', mcpRouter);
574
  app.use('/api/mcp/autonomous', autonomousRouter);
575
  app.use('/api/memory', memoryRouter);
 
32
  global.Path2D = class Path2D { };
33
  }
34
 
35
+ const __dirname = typeof import.meta !== 'undefined' && import.meta.url
36
  ? new URL('.', import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1')
37
  : process.cwd();
38
  // Load .env from backend directory, or root if not found
 
174
  // 🧠 NEURAL ORGANS INITIALIZATION
175
  // Phase B: Knowledge Synthesis
176
  // ============================================
177
+
178
  // 1. Initialize Vector Service (Dark Matter)
179
  vectorService.init().then(() => {
180
  console.log('🌑 [DARK MATTER] Vector Engine Ready (384 dimensions)');
 
187
  console.log('🐝 [HIVE] Neural Telepathy Bus Attached');
188
 
189
  // 3. Start Knowledge Compiler (Brain) - Watch DropZone
190
+ const DROPZONE_PATH = process.env.DROPZONE_PATH ||
191
+ (process.platform === 'win32'
192
  ? 'C:\\Users\\claus\\Desktop\\WidgeTDC_DropZone'
193
  : '/app/data/dropzone');
194
+
195
  neuralCompiler.startWatching(DROPZONE_PATH).then(() => {
196
  console.log(`📚 [BRAIN] Neural Compiler watching: ${DROPZONE_PATH}`);
197
  }).catch(err => {
 
210
  // 5. Start Prometheus Code Scanner (every hour)
211
  const SCAN_INTERVAL = parseInt(process.env.PROMETHEUS_SCAN_INTERVAL || '3600000');
212
  setTimeout(() => {
213
+ prometheus.scanAndPropose(path.join(__dirname, 'services')).catch(() => { });
214
  }, 10000); // Initial scan after 10s
215
+
216
  setInterval(() => {
217
+ prometheus.scanAndPropose(path.join(__dirname, 'services')).catch(() => { });
218
  }, SCAN_INTERVAL);
219
  console.log('🔥 [PROMETHEUS] Code Evolution Scanner Active');
220
 
 
407
  vidensarkivBatchAddHandler,
408
  vidensarkivGetRelatedHandler,
409
  vidensarkivListHandler,
410
+ vidensarkivStatsHandler,
411
+ vidensarkivReadHandler,
412
+ vidensarkivListFilesHandler,
413
+ vidensarkivReadFileHandler
414
  } = await import('./mcp/toolHandlers.js');
415
 
416
  mcpRegistry.registerTool('vidensarkiv.search', vidensarkivSearchHandler);
 
419
  mcpRegistry.registerTool('vidensarkiv.get_related', vidensarkivGetRelatedHandler);
420
  mcpRegistry.registerTool('vidensarkiv.list', vidensarkivListHandler);
421
  mcpRegistry.registerTool('vidensarkiv.stats', vidensarkivStatsHandler);
422
+ mcpRegistry.registerTool('vidensarkiv.read', vidensarkivReadHandler);
423
+ mcpRegistry.registerTool('vidensarkiv.list_files', vidensarkivListFilesHandler);
424
+ mcpRegistry.registerTool('vidensarkiv.read_file', vidensarkivReadFileHandler);
425
 
426
  // TaskRecorder Tools
427
  const {
 
571
  })();
572
 
573
  // Step 4: Setup routes
574
+
575
  // 🛡️ ANGEL PROXY: Security Shield on all API routes
576
  app.use('/api', AngelProxy.cortexFirewall);
577
  console.log('🛡️ [ANGEL] Cortex Firewall Active on /api/*');
578
+
579
  app.use('/api/mcp', mcpRouter);
580
  app.use('/api/mcp/autonomous', autonomousRouter);
581
  app.use('/api/memory', memoryRouter);
apps/backend/src/mcp/toolHandlers.ts CHANGED
@@ -1,6 +1,8 @@
1
  import { McpContext } from '@widget-tdc/mcp-types';
2
  import { spawn } from 'child_process';
3
  import path from 'path';
 
 
4
  import { MemoryRepository } from '../services/memory/memoryRepository.js';
5
  import { SragRepository } from '../services/srag/sragRepository.js';
6
  import { EvolutionRepository } from '../services/evolution/evolutionRepository.js';
@@ -461,6 +463,103 @@ export async function autonomousAgentTeamCoordinateHandler(payload: any, _ctx: M
461
  }
462
 
463
  // Vidensarkiv (PgVector) tool handlers
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  export async function vidensarkivSearchHandler(payload: any, ctx: McpContext): Promise<any> {
465
  const vectorStore = getNeo4jVectorStore();
466
  const { query, limit = 10, namespace } = payload;
@@ -1194,7 +1293,7 @@ export async function agenticRunHandler(payload: any, ctx: McpContext): Promise<
1194
  // ---------------------------------------------------
1195
  export async function widgetsUpdateStateHandler(payload: any, ctx: McpContext): Promise<any> {
1196
  const { widgetId, state } = payload;
1197
-
1198
  if (!widgetId) {
1199
  throw new Error('widgetId required');
1200
  }
@@ -1280,7 +1379,7 @@ export async function visionaryGenerateHandler(payload: any, ctx: McpContext): P
1280
  }
1281
 
1282
  const llmService = getLlmService();
1283
-
1284
  const systemContext = `
1285
  You are The Visionary, an expert system architect and visualization specialist.
1286
  Your goal is to generate valid Mermaid.js diagram code based on user requests.
@@ -1342,7 +1441,7 @@ ${(memories || []).join('\n')}
1342
  // ---------------------------------------------------
1343
  export async function dataAnalysisHandler(payload: any, ctx: McpContext): Promise<any> {
1344
  const { data, type, params } = payload;
1345
-
1346
  if (!data || !Array.isArray(data)) {
1347
  throw new Error("Invalid payload: 'data' must be an array of objects.");
1348
  }
@@ -1351,12 +1450,12 @@ export async function dataAnalysisHandler(payload: any, ctx: McpContext): Promis
1351
  // Resolve path to python script
1352
  // Assuming running from apps/backend
1353
  const scriptPath = path.resolve(process.cwd(), 'python/analysis_engine.py');
1354
-
1355
  // Check python command availability (python3 or python)
1356
  const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
1357
 
1358
  const pythonProcess = spawn(pythonCmd, [scriptPath]);
1359
-
1360
  let resultData = '';
1361
  let errorData = '';
1362
 
@@ -1382,9 +1481,9 @@ export async function dataAnalysisHandler(payload: any, ctx: McpContext): Promis
1382
  try {
1383
  const parsedResult = JSON.parse(resultData);
1384
  if (!parsedResult.success) {
1385
- reject(new Error(parsedResult.error));
1386
  } else {
1387
- resolve(parsedResult.data);
1388
  }
1389
  } catch (e) {
1390
  console.error('Failed to parse Python output:', resultData);
 
1
  import { McpContext } from '@widget-tdc/mcp-types';
2
  import { spawn } from 'child_process';
3
  import path from 'path';
4
+ import * as fs from 'fs/promises';
5
+ import * as os from 'os';
6
  import { MemoryRepository } from '../services/memory/memoryRepository.js';
7
  import { SragRepository } from '../services/srag/sragRepository.js';
8
  import { EvolutionRepository } from '../services/evolution/evolutionRepository.js';
 
463
  }
464
 
465
  // Vidensarkiv (PgVector) tool handlers
466
+ export async function vidensarkivReadHandler(payload: any, _ctx: McpContext): Promise<any> {
467
+ const vectorStore = getNeo4jVectorStore();
468
+ const { id } = payload;
469
+
470
+ if (!id) {
471
+ throw new Error('id is required');
472
+ }
473
+
474
+ const record = await vectorStore.get(id);
475
+
476
+ if (!record) {
477
+ throw new Error(`Document with id ${id} not found`);
478
+ }
479
+
480
+ return {
481
+ success: true,
482
+ file: {
483
+ id: record.id,
484
+ content: record.content,
485
+ metadata: record.metadata
486
+ }
487
+ };
488
+ }
489
+
490
+ export async function vidensarkivListFilesHandler(payload: any, _ctx: McpContext): Promise<any> {
491
+ const { subfolder } = payload;
492
+ const homeDir = os.homedir();
493
+ // Use C:\Users\claus\Desktop\vidensarkiv if on Windows/Dev, or appropriate path
494
+ // For now, respect the ~/Desktop/vidensarkiv convention
495
+ const basePath = path.join(homeDir, 'Desktop', 'vidensarkiv');
496
+ const targetPath = subfolder ? path.join(basePath, subfolder) : basePath;
497
+
498
+ try {
499
+ // Ensure directory exists
500
+ try {
501
+ await fs.access(targetPath);
502
+ } catch {
503
+ await fs.mkdir(targetPath, { recursive: true });
504
+ }
505
+
506
+ const entries = await fs.readdir(targetPath, { withFileTypes: true });
507
+
508
+ const files = entries.map(entry => ({
509
+ name: entry.name,
510
+ path: subfolder ? path.join(subfolder, entry.name) : entry.name,
511
+ type: entry.isDirectory() ? 'folder' : 'file',
512
+ size: 0,
513
+ modified: new Date().toISOString()
514
+ }));
515
+
516
+ // Add size and mtime - parallelize
517
+ await Promise.all(files.map(async (file) => {
518
+ if (file.type === 'file') {
519
+ try {
520
+ const stat = await fs.stat(path.join(targetPath, file.name));
521
+ file.size = stat.size;
522
+ file.modified = stat.mtime.toISOString();
523
+ } catch (e) { console.error('Stat error', e); }
524
+ }
525
+ }));
526
+
527
+ return {
528
+ success: true,
529
+ files
530
+ };
531
+ } catch (error: any) {
532
+ return {
533
+ success: false,
534
+ error: error.message,
535
+ files: []
536
+ };
537
+ }
538
+ }
539
+
540
+ export async function vidensarkivReadFileHandler(payload: any, _ctx: McpContext): Promise<any> {
541
+ const { filepath } = payload;
542
+ if (!filepath) throw new Error('filepath is required');
543
+
544
+ const homeDir = os.homedir();
545
+ const basePath = path.join(homeDir, 'Desktop', 'vidensarkiv');
546
+ // Prevent directory traversal
547
+ const safePath = path.join(basePath, filepath).replace(/\.\./g, '');
548
+
549
+ try {
550
+ const content = await fs.readFile(safePath, 'utf8');
551
+ return {
552
+ success: true,
553
+ content
554
+ };
555
+ } catch (error: any) {
556
+ return {
557
+ success: false,
558
+ error: error.message
559
+ };
560
+ }
561
+ }
562
+
563
  export async function vidensarkivSearchHandler(payload: any, ctx: McpContext): Promise<any> {
564
  const vectorStore = getNeo4jVectorStore();
565
  const { query, limit = 10, namespace } = payload;
 
1293
  // ---------------------------------------------------
1294
  export async function widgetsUpdateStateHandler(payload: any, ctx: McpContext): Promise<any> {
1295
  const { widgetId, state } = payload;
1296
+
1297
  if (!widgetId) {
1298
  throw new Error('widgetId required');
1299
  }
 
1379
  }
1380
 
1381
  const llmService = getLlmService();
1382
+
1383
  const systemContext = `
1384
  You are The Visionary, an expert system architect and visualization specialist.
1385
  Your goal is to generate valid Mermaid.js diagram code based on user requests.
 
1441
  // ---------------------------------------------------
1442
  export async function dataAnalysisHandler(payload: any, ctx: McpContext): Promise<any> {
1443
  const { data, type, params } = payload;
1444
+
1445
  if (!data || !Array.isArray(data)) {
1446
  throw new Error("Invalid payload: 'data' must be an array of objects.");
1447
  }
 
1450
  // Resolve path to python script
1451
  // Assuming running from apps/backend
1452
  const scriptPath = path.resolve(process.cwd(), 'python/analysis_engine.py');
1453
+
1454
  // Check python command availability (python3 or python)
1455
  const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
1456
 
1457
  const pythonProcess = spawn(pythonCmd, [scriptPath]);
1458
+
1459
  let resultData = '';
1460
  let errorData = '';
1461
 
 
1481
  try {
1482
  const parsedResult = JSON.parse(resultData);
1483
  if (!parsedResult.success) {
1484
+ reject(new Error(parsedResult.error));
1485
  } else {
1486
+ resolve(parsedResult.data);
1487
  }
1488
  } catch (e) {
1489
  console.error('Failed to parse Python output:', resultData);
apps/backend/src/platform/vector/Neo4jVectorStoreAdapter.ts CHANGED
@@ -86,13 +86,13 @@ export class Neo4jVectorStoreAdapter {
86
  ` + "`vector.similarity_function`" + `: 'cosine'
87
  }}
88
  `);
89
-
90
  // Create constraint for ID
91
  await this.graphAdapter.query(`
92
  CREATE CONSTRAINT vector_document_id IF NOT EXISTS
93
  FOR (n:VectorDocument) REQUIRE n.id IS UNIQUE
94
  `);
95
-
96
  logger.info(`🧠 Neo4j Vector Store initialized (${this.indexName}, ${this.dimension}D)`);
97
  this.isInitialized = true;
98
  } catch (error) {
@@ -140,12 +140,12 @@ export class Neo4jVectorStoreAdapter {
140
  */
141
  async batchUpsert(options: { records: VectorRecord[]; namespace?: string }): Promise<void> {
142
  if (!this.isInitialized) await this.initialize();
143
-
144
  // Process in batches of 50 to avoid large transactions
145
  const batchSize = 50;
146
  for (let i = 0; i < options.records.length; i += batchSize) {
147
  const batch = options.records.slice(i, i + batchSize);
148
-
149
  // Generate embeddings in parallel for the batch if needed
150
  const recordsWithEmbeddings = await Promise.all(batch.map(async (r) => {
151
  if (!r.embedding && r.content) {
@@ -174,7 +174,7 @@ export class Neo4jVectorStoreAdapter {
174
  namespace: options.namespace || 'default'
175
  });
176
  }
177
-
178
  logger.info(`📦 Batch upserted ${options.records.length} vectors to Neo4j`);
179
  }
180
 
@@ -183,7 +183,7 @@ export class Neo4jVectorStoreAdapter {
183
  */
184
  async search(query: VectorQuery): Promise<VectorSearchResult[]> {
185
  if (!this.isInitialized) await this.initialize();
186
-
187
  let searchVector = query.vector;
188
  if (!searchVector && query.text) {
189
  searchVector = await this.embeddings.generateEmbedding(query.text);
@@ -199,7 +199,7 @@ export class Neo4jVectorStoreAdapter {
199
  // Using Neo4j vector index query
200
  // Note: We filter by namespace AFTER the vector search if using db.index.vector.queryNodes
201
  // Ideally, we would use pre-filtering but that requires more complex cypher or newer Neo4j features
202
-
203
  const cypher = `
204
  CALL db.index.vector.queryNodes($indexName, $k, $embedding)
205
  YIELD node, score
@@ -208,7 +208,7 @@ export class Neo4jVectorStoreAdapter {
208
  `;
209
 
210
  // We request more results than limit to account for filtering
211
- const k = limit * 5;
212
 
213
  const result = await this.graphAdapter.query(cypher, {
214
  indexName: this.indexName,
@@ -222,12 +222,12 @@ export class Neo4jVectorStoreAdapter {
222
  // record is an object where keys are the return values from Cypher
223
  // We returned: id, content, metadata, score
224
  // Note: metadata was returned as 'node', which contains properties
225
-
226
  const properties = record.metadata?.properties || {};
227
-
228
  // Clean up internal properties if needed
229
  delete properties.embedding;
230
-
231
  return {
232
  id: record.id,
233
  content: record.content,
@@ -236,12 +236,41 @@ export class Neo4jVectorStoreAdapter {
236
  };
237
  });
238
  }
239
-
240
  // Helper to be implemented after updating adapter
241
  async searchRaw(query: VectorQuery): Promise<VectorSearchResult[]> {
242
  return this.search(query);
243
  }
244
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  /**
246
  * Get statistics
247
  */
@@ -260,7 +289,7 @@ export class Neo4jVectorStoreAdapter {
260
  `;
261
 
262
  const result = await this.graphAdapter.query(cypher);
263
-
264
  const perNamespace: Record<string, number> = {};
265
  let totalRecords = 0;
266
 
 
86
  ` + "`vector.similarity_function`" + `: 'cosine'
87
  }}
88
  `);
89
+
90
  // Create constraint for ID
91
  await this.graphAdapter.query(`
92
  CREATE CONSTRAINT vector_document_id IF NOT EXISTS
93
  FOR (n:VectorDocument) REQUIRE n.id IS UNIQUE
94
  `);
95
+
96
  logger.info(`🧠 Neo4j Vector Store initialized (${this.indexName}, ${this.dimension}D)`);
97
  this.isInitialized = true;
98
  } catch (error) {
 
140
  */
141
  async batchUpsert(options: { records: VectorRecord[]; namespace?: string }): Promise<void> {
142
  if (!this.isInitialized) await this.initialize();
143
+
144
  // Process in batches of 50 to avoid large transactions
145
  const batchSize = 50;
146
  for (let i = 0; i < options.records.length; i += batchSize) {
147
  const batch = options.records.slice(i, i + batchSize);
148
+
149
  // Generate embeddings in parallel for the batch if needed
150
  const recordsWithEmbeddings = await Promise.all(batch.map(async (r) => {
151
  if (!r.embedding && r.content) {
 
174
  namespace: options.namespace || 'default'
175
  });
176
  }
177
+
178
  logger.info(`📦 Batch upserted ${options.records.length} vectors to Neo4j`);
179
  }
180
 
 
183
  */
184
  async search(query: VectorQuery): Promise<VectorSearchResult[]> {
185
  if (!this.isInitialized) await this.initialize();
186
+
187
  let searchVector = query.vector;
188
  if (!searchVector && query.text) {
189
  searchVector = await this.embeddings.generateEmbedding(query.text);
 
199
  // Using Neo4j vector index query
200
  // Note: We filter by namespace AFTER the vector search if using db.index.vector.queryNodes
201
  // Ideally, we would use pre-filtering but that requires more complex cypher or newer Neo4j features
202
+
203
  const cypher = `
204
  CALL db.index.vector.queryNodes($indexName, $k, $embedding)
205
  YIELD node, score
 
208
  `;
209
 
210
  // We request more results than limit to account for filtering
211
+ const k = limit * 5;
212
 
213
  const result = await this.graphAdapter.query(cypher, {
214
  indexName: this.indexName,
 
222
  // record is an object where keys are the return values from Cypher
223
  // We returned: id, content, metadata, score
224
  // Note: metadata was returned as 'node', which contains properties
225
+
226
  const properties = record.metadata?.properties || {};
227
+
228
  // Clean up internal properties if needed
229
  delete properties.embedding;
230
+
231
  return {
232
  id: record.id,
233
  content: record.content,
 
236
  };
237
  });
238
  }
239
+
240
  // Helper to be implemented after updating adapter
241
  async searchRaw(query: VectorQuery): Promise<VectorSearchResult[]> {
242
  return this.search(query);
243
  }
244
 
245
+ /**
246
+ * Get a vector document by ID
247
+ */
248
+ async get(id: string): Promise<VectorRecord | null> {
249
+ if (!this.isInitialized) await this.initialize();
250
+
251
+ const cypher = `
252
+ MATCH (n:VectorDocument {id: $id})
253
+ RETURN n.id as id, n.content as content, n.embedding as embedding, n.namespace as namespace, n as metadata
254
+ `;
255
+
256
+ const result = await this.graphAdapter.query(cypher, { id });
257
+
258
+ if (result.records.length === 0) {
259
+ return null;
260
+ }
261
+
262
+ const record = result.records[0];
263
+ const properties = record.metadata?.properties || {};
264
+ delete properties.embedding; // Remove large embedding array
265
+
266
+ return {
267
+ id: record.id,
268
+ content: record.content,
269
+ metadata: properties,
270
+ namespace: record.namespace
271
+ };
272
+ }
273
+
274
  /**
275
  * Get statistics
276
  */
 
289
  `;
290
 
291
  const result = await this.graphAdapter.query(cypher);
292
+
293
  const perNamespace: Record<string, number> = {};
294
  let totalRecords = 0;
295