@vfarcic/dot-ai 0.48.0 → 0.50.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/README.md +49 -2
- package/dist/core/base-vector-service.d.ts +86 -0
- package/dist/core/base-vector-service.d.ts.map +1 -0
- package/dist/core/base-vector-service.js +223 -0
- package/dist/core/capabilities.d.ts +71 -0
- package/dist/core/capabilities.d.ts.map +1 -0
- package/dist/core/capabilities.js +215 -0
- package/dist/core/capability-vector-service.d.ts +80 -0
- package/dist/core/capability-vector-service.d.ts.map +1 -0
- package/dist/core/capability-vector-service.js +142 -0
- package/dist/core/claude.d.ts +14 -1
- package/dist/core/claude.d.ts.map +1 -1
- package/dist/core/claude.js +109 -13
- package/dist/core/discovery.d.ts +6 -0
- package/dist/core/discovery.d.ts.map +1 -1
- package/dist/core/discovery.js +7 -1
- package/dist/core/embedding-service.d.ts +3 -3
- package/dist/core/embedding-service.d.ts.map +1 -1
- package/dist/core/embedding-service.js +6 -7
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +7 -4
- package/dist/core/pattern-vector-service.d.ts +11 -80
- package/dist/core/pattern-vector-service.d.ts.map +1 -1
- package/dist/core/pattern-vector-service.js +40 -277
- package/dist/core/schema.d.ts +11 -30
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +107 -126
- package/dist/core/vector-db-service.d.ts +6 -0
- package/dist/core/vector-db-service.d.ts.map +1 -1
- package/dist/core/vector-db-service.js +40 -10
- package/dist/tools/organizational-data.d.ts +18 -3
- package/dist/tools/organizational-data.d.ts.map +1 -1
- package/dist/tools/organizational-data.js +1750 -17
- package/dist/tools/recommend.d.ts.map +1 -1
- package/dist/tools/recommend.js +3 -7
- package/dist/tools/version.d.ts +9 -0
- package/dist/tools/version.d.ts.map +1 -1
- package/dist/tools/version.js +115 -5
- package/package.json +1 -1
- package/prompts/capability-inference.md +121 -0
- package/prompts/doc-testing-test-section.md +40 -2
- package/prompts/resource-selection.md +10 -3
- package/shared-prompts/prd-done.md +5 -4
- package/shared-prompts/prd-update-decisions.md +9 -0
- package/shared-prompts/prd-update-progress.md +33 -0
- package/prompts/concept-extraction.md +0 -95
|
@@ -49,23 +49,33 @@ const error_handling_1 = require("../core/error-handling");
|
|
|
49
49
|
// Import only what we need - other imports removed as they're no longer used with Vector DB
|
|
50
50
|
const pattern_creation_session_1 = require("../core/pattern-creation-session");
|
|
51
51
|
const index_1 = require("../core/index");
|
|
52
|
+
const capabilities_1 = require("../core/capabilities");
|
|
52
53
|
const session_utils_1 = require("../core/session-utils");
|
|
53
54
|
const fs = __importStar(require("fs"));
|
|
54
55
|
const path = __importStar(require("path"));
|
|
55
56
|
// Tool metadata for MCP registration
|
|
56
57
|
exports.ORGANIZATIONAL_DATA_TOOL_NAME = 'manageOrgData';
|
|
57
|
-
exports.ORGANIZATIONAL_DATA_TOOL_DESCRIPTION = '
|
|
58
|
-
// Extensible schema -
|
|
58
|
+
exports.ORGANIZATIONAL_DATA_TOOL_DESCRIPTION = 'Unified tool for managing cluster data: organizational patterns and resource capabilities. For patterns: supports step-by-step creation workflow. For capabilities: supports scan, list, get, delete, deleteAll, and progress operations for cluster resource capability discovery and management. Use dataType parameter to specify what to manage: "pattern" for organizational patterns, "capabilities" for resource capabilities.';
|
|
59
|
+
// Extensible schema - supports patterns and capabilities
|
|
59
60
|
exports.ORGANIZATIONAL_DATA_TOOL_INPUT_SCHEMA = {
|
|
60
|
-
dataType: zod_1.z.enum(['pattern']).describe('Type of
|
|
61
|
-
operation: zod_1.z.enum(['create', 'list', 'get', 'delete']).describe('Operation to perform on the
|
|
61
|
+
dataType: zod_1.z.enum(['pattern', 'capabilities']).describe('Type of cluster data to manage: "pattern" for organizational patterns, "capabilities" for resource capabilities'),
|
|
62
|
+
operation: zod_1.z.enum(['create', 'list', 'get', 'delete', 'deleteAll', 'scan', 'analyze', 'progress', 'search']).describe('Operation to perform on the cluster data'),
|
|
62
63
|
// Workflow fields for step-by-step pattern creation
|
|
63
|
-
sessionId: zod_1.z.string().optional().describe('
|
|
64
|
+
sessionId: zod_1.z.string().optional().describe('Session ID (required for continuing workflow steps, optional for progress - uses latest session if omitted)'),
|
|
65
|
+
step: zod_1.z.string().optional().describe('Current workflow step (required when sessionId is provided)'),
|
|
64
66
|
response: zod_1.z.string().optional().describe('User response to previous workflow step question'),
|
|
65
|
-
// Generic fields for get/delete operations
|
|
66
|
-
id: zod_1.z.string().optional().describe('Data item ID (required for get/delete operations)'),
|
|
67
|
+
// Generic fields for get/delete/search operations
|
|
68
|
+
id: zod_1.z.string().optional().describe('Data item ID (required for get/delete operations) or search query (required for search operations)'),
|
|
67
69
|
// Generic fields for list operations
|
|
68
|
-
limit: zod_1.z.number().optional().describe('Maximum number of items to return (default: 10)')
|
|
70
|
+
limit: zod_1.z.number().optional().describe('Maximum number of items to return (default: 10)'),
|
|
71
|
+
// Resource-specific fields (for capabilities operations)
|
|
72
|
+
resource: zod_1.z.object({
|
|
73
|
+
kind: zod_1.z.string(),
|
|
74
|
+
group: zod_1.z.string(),
|
|
75
|
+
apiVersion: zod_1.z.string()
|
|
76
|
+
}).optional().describe('Kubernetes resource reference (for capabilities operations)'),
|
|
77
|
+
// Resource list for specific resource scanning
|
|
78
|
+
resourceList: zod_1.z.string().optional().describe('Comma-separated list of resources to scan (format: Kind.group or Kind for core resources)')
|
|
69
79
|
};
|
|
70
80
|
/**
|
|
71
81
|
* Get Vector DB-based pattern service with optional embedding support
|
|
@@ -120,6 +130,48 @@ async function validateVectorDBConnection(patternService, logger, requestId) {
|
|
|
120
130
|
}
|
|
121
131
|
return { success: true };
|
|
122
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Validate embedding service configuration and fail if unavailable
|
|
135
|
+
*/
|
|
136
|
+
async function validateEmbeddingService(logger, requestId) {
|
|
137
|
+
const { EmbeddingService } = await Promise.resolve().then(() => __importStar(require('../core/embedding-service')));
|
|
138
|
+
const embeddingService = new EmbeddingService();
|
|
139
|
+
const status = embeddingService.getStatus();
|
|
140
|
+
if (!status.available) {
|
|
141
|
+
logger.warn('Embedding service required but not available', {
|
|
142
|
+
requestId,
|
|
143
|
+
reason: status.reason
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
success: false,
|
|
147
|
+
error: {
|
|
148
|
+
message: 'OpenAI API key required for pattern management',
|
|
149
|
+
details: 'Pattern management requires OpenAI embeddings for semantic search and storage. The system cannot proceed without proper configuration.',
|
|
150
|
+
reason: status.reason,
|
|
151
|
+
setup: {
|
|
152
|
+
required: 'export OPENAI_API_KEY=your-openai-api-key',
|
|
153
|
+
optional: [
|
|
154
|
+
'export OPENAI_MODEL=text-embedding-3-small (default)',
|
|
155
|
+
'export OPENAI_DIMENSIONS=1536 (default)'
|
|
156
|
+
],
|
|
157
|
+
docs: 'Get API key from https://platform.openai.com/api-keys'
|
|
158
|
+
},
|
|
159
|
+
currentConfig: {
|
|
160
|
+
OPENAI_API_KEY: process.env.OPENAI_API_KEY ? 'set' : 'not set',
|
|
161
|
+
QDRANT_URL: process.env.QDRANT_URL || 'http://localhost:6333',
|
|
162
|
+
status: 'embedding service unavailable'
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
logger.info('Embedding service available', {
|
|
168
|
+
requestId,
|
|
169
|
+
provider: status.provider,
|
|
170
|
+
model: status.model,
|
|
171
|
+
dimensions: status.dimensions
|
|
172
|
+
});
|
|
173
|
+
return { success: true };
|
|
174
|
+
}
|
|
123
175
|
/**
|
|
124
176
|
* Handle pattern operations with workflow support
|
|
125
177
|
*/
|
|
@@ -136,6 +188,17 @@ async function handlePatternOperation(operation, args, logger, requestId) {
|
|
|
136
188
|
message: 'Vector DB connection required for pattern management'
|
|
137
189
|
};
|
|
138
190
|
}
|
|
191
|
+
// Validate embedding service and fail if unavailable
|
|
192
|
+
const embeddingCheck = await validateEmbeddingService(logger, requestId);
|
|
193
|
+
if (!embeddingCheck.success) {
|
|
194
|
+
return {
|
|
195
|
+
success: false,
|
|
196
|
+
operation,
|
|
197
|
+
dataType: 'pattern',
|
|
198
|
+
error: embeddingCheck.error,
|
|
199
|
+
message: 'OpenAI API key required for pattern management'
|
|
200
|
+
};
|
|
201
|
+
}
|
|
139
202
|
const sessionManager = new pattern_creation_session_1.PatternCreationSessionManager();
|
|
140
203
|
switch (operation) {
|
|
141
204
|
case 'create': {
|
|
@@ -366,6 +429,1680 @@ async function handlePatternOperation(operation, args, logger, requestId) {
|
|
|
366
429
|
});
|
|
367
430
|
}
|
|
368
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* Create standardized capability scan completion response
|
|
434
|
+
*/
|
|
435
|
+
function createCapabilityScanCompletionResponse(sessionId, totalProcessed, successful, failed, processingTime, mode, stopped = false) {
|
|
436
|
+
let message;
|
|
437
|
+
if (stopped) {
|
|
438
|
+
message = `⏹️ Capability scan stopped by user after processing ${successful} of ${totalProcessed} resources.`;
|
|
439
|
+
}
|
|
440
|
+
else if (failed > 0) {
|
|
441
|
+
message = `✅ Capability scan completed with ${failed} errors. ${successful}/${totalProcessed} resources processed successfully.`;
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
message = `✅ Capability scan completed successfully! Processed ${totalProcessed} resources.`;
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
success: true,
|
|
448
|
+
operation: 'scan',
|
|
449
|
+
dataType: 'capabilities',
|
|
450
|
+
mode: mode,
|
|
451
|
+
step: 'complete',
|
|
452
|
+
sessionId: sessionId,
|
|
453
|
+
summary: {
|
|
454
|
+
totalScanned: totalProcessed,
|
|
455
|
+
successful: successful,
|
|
456
|
+
failed: failed,
|
|
457
|
+
processingTime: processingTime,
|
|
458
|
+
...(stopped && { stopped: true })
|
|
459
|
+
},
|
|
460
|
+
message: message,
|
|
461
|
+
availableOptions: {
|
|
462
|
+
viewResults: "Use 'list' operation to browse all discovered capabilities",
|
|
463
|
+
getDetails: "Use 'get' operation with capability ID to view specific capability details",
|
|
464
|
+
checkStatus: successful > 0 ? "Capabilities are now available for AI-powered recommendations" : "No capabilities were stored"
|
|
465
|
+
},
|
|
466
|
+
userNote: "The above options are available for you to choose from - the system will not execute them automatically."
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Handle capabilities operations - PRD #48 Implementation
|
|
471
|
+
*/
|
|
472
|
+
async function handleCapabilitiesOperation(operation, args, logger, requestId) {
|
|
473
|
+
logger.info('Capabilities operation requested', { requestId, operation });
|
|
474
|
+
switch (operation) {
|
|
475
|
+
case 'scan':
|
|
476
|
+
return await handleCapabilityScan(args, logger, requestId);
|
|
477
|
+
case 'progress':
|
|
478
|
+
return await handleCapabilityProgress(args, logger, requestId);
|
|
479
|
+
case 'list':
|
|
480
|
+
case 'get':
|
|
481
|
+
case 'search':
|
|
482
|
+
case 'delete':
|
|
483
|
+
case 'deleteAll': {
|
|
484
|
+
// Create and initialize capability service for list/get/delete operations
|
|
485
|
+
const capabilityService = new index_1.CapabilityVectorService();
|
|
486
|
+
try {
|
|
487
|
+
const vectorDBHealthy = await capabilityService.healthCheck();
|
|
488
|
+
if (!vectorDBHealthy) {
|
|
489
|
+
return {
|
|
490
|
+
success: false,
|
|
491
|
+
operation,
|
|
492
|
+
dataType: 'capabilities',
|
|
493
|
+
error: {
|
|
494
|
+
message: 'Vector DB (Qdrant) connection required',
|
|
495
|
+
details: 'Capability operations require a working Qdrant connection.',
|
|
496
|
+
setup: {
|
|
497
|
+
docker: 'docker run -p 6333:6333 qdrant/qdrant',
|
|
498
|
+
config: 'export QDRANT_URL=http://localhost:6333'
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
await capabilityService.initialize();
|
|
504
|
+
if (operation === 'list') {
|
|
505
|
+
return await handleCapabilityList(args, logger, requestId, capabilityService);
|
|
506
|
+
}
|
|
507
|
+
else if (operation === 'get') {
|
|
508
|
+
return await handleCapabilityGet(args, logger, requestId, capabilityService);
|
|
509
|
+
}
|
|
510
|
+
else if (operation === 'search') {
|
|
511
|
+
return await handleCapabilitySearch(args, logger, requestId, capabilityService);
|
|
512
|
+
}
|
|
513
|
+
else if (operation === 'delete') {
|
|
514
|
+
return await handleCapabilityDelete(args, logger, requestId, capabilityService);
|
|
515
|
+
}
|
|
516
|
+
else if (operation === 'deleteAll') {
|
|
517
|
+
return await handleCapabilityDeleteAll(args, logger, requestId, capabilityService);
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
// This should never happen since we already check the operation in the switch case
|
|
521
|
+
throw new Error(`Unexpected operation: ${operation}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
logger.error(`Capability ${operation} operation failed`, error, { requestId });
|
|
526
|
+
return {
|
|
527
|
+
success: false,
|
|
528
|
+
operation,
|
|
529
|
+
dataType: 'capabilities',
|
|
530
|
+
error: {
|
|
531
|
+
message: `Capability ${operation} failed`,
|
|
532
|
+
details: error instanceof Error ? error.message : String(error)
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
default:
|
|
538
|
+
return {
|
|
539
|
+
success: false,
|
|
540
|
+
operation,
|
|
541
|
+
dataType: 'capabilities',
|
|
542
|
+
error: {
|
|
543
|
+
message: `Unsupported capabilities operation: ${operation}`,
|
|
544
|
+
supportedOperations: ['scan', 'list', 'get', 'delete', 'deleteAll']
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Convert numeric response to option value
|
|
551
|
+
*/
|
|
552
|
+
function parseNumericResponse(response, validOptions) {
|
|
553
|
+
// If response is a number, map it to the corresponding option
|
|
554
|
+
const num = parseInt(response, 10);
|
|
555
|
+
if (!isNaN(num) && num >= 1 && num <= validOptions.length) {
|
|
556
|
+
return validOptions[num - 1]; // Convert 1-based to 0-based index
|
|
557
|
+
}
|
|
558
|
+
// Otherwise return the original response (for backward compatibility)
|
|
559
|
+
return response;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Get session file path following established pattern
|
|
563
|
+
*/
|
|
564
|
+
function getCapabilitySessionPath(sessionId, args) {
|
|
565
|
+
const sessionDir = (0, session_utils_1.getAndValidateSessionDirectory)(args, false);
|
|
566
|
+
const sessionSubDir = path.join(sessionDir, 'capability-sessions');
|
|
567
|
+
// Ensure capability-sessions subdirectory exists
|
|
568
|
+
if (!fs.existsSync(sessionSubDir)) {
|
|
569
|
+
fs.mkdirSync(sessionSubDir, { recursive: true });
|
|
570
|
+
}
|
|
571
|
+
return path.join(sessionSubDir, `${sessionId}.json`);
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Load session from file system following established pattern
|
|
575
|
+
*/
|
|
576
|
+
function loadCapabilitySession(sessionId, args) {
|
|
577
|
+
try {
|
|
578
|
+
const sessionPath = getCapabilitySessionPath(sessionId, args);
|
|
579
|
+
if (!fs.existsSync(sessionPath)) {
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
const sessionData = fs.readFileSync(sessionPath, 'utf8');
|
|
583
|
+
const session = JSON.parse(sessionData);
|
|
584
|
+
// Update last activity
|
|
585
|
+
session.lastActivity = new Date().toISOString();
|
|
586
|
+
saveCapabilitySession(session, args);
|
|
587
|
+
return session;
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
// Log error but don't throw - return null to create new session
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Save session to file system following established pattern
|
|
596
|
+
*/
|
|
597
|
+
function saveCapabilitySession(session, args) {
|
|
598
|
+
try {
|
|
599
|
+
const sessionPath = getCapabilitySessionPath(session.sessionId, args);
|
|
600
|
+
fs.writeFileSync(sessionPath, JSON.stringify(session, null, 2), 'utf8');
|
|
601
|
+
}
|
|
602
|
+
catch (error) {
|
|
603
|
+
// Log error but don't throw - workflow can continue
|
|
604
|
+
console.warn('Failed to save capability session:', error);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Get or create session with file-based persistence
|
|
609
|
+
*/
|
|
610
|
+
function getOrCreateCapabilitySession(sessionId, args, logger, requestId) {
|
|
611
|
+
if (sessionId) {
|
|
612
|
+
const existing = loadCapabilitySession(sessionId, args);
|
|
613
|
+
if (existing) {
|
|
614
|
+
logger.info('Loaded existing capability session', {
|
|
615
|
+
requestId,
|
|
616
|
+
sessionId,
|
|
617
|
+
currentStep: existing.currentStep
|
|
618
|
+
});
|
|
619
|
+
return existing;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
// Create new session
|
|
623
|
+
const newSessionId = sessionId || `cap-scan-${Date.now()}`;
|
|
624
|
+
const session = {
|
|
625
|
+
sessionId: newSessionId,
|
|
626
|
+
currentStep: 'resource-selection',
|
|
627
|
+
startedAt: new Date().toISOString(),
|
|
628
|
+
lastActivity: new Date().toISOString()
|
|
629
|
+
};
|
|
630
|
+
saveCapabilitySession(session, args);
|
|
631
|
+
logger.info('Created new capability session', {
|
|
632
|
+
requestId,
|
|
633
|
+
sessionId: newSessionId,
|
|
634
|
+
currentStep: session.currentStep
|
|
635
|
+
});
|
|
636
|
+
return session;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Validate client is on the correct step - step parameter is MANDATORY
|
|
640
|
+
*/
|
|
641
|
+
function validateCapabilityStep(session, clientStep) {
|
|
642
|
+
if (!clientStep) {
|
|
643
|
+
return {
|
|
644
|
+
valid: false,
|
|
645
|
+
error: `Step parameter is required. You are currently on step '${session.currentStep}'. Please call with step='${session.currentStep}' and appropriate parameters.`
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
if (clientStep !== session.currentStep) {
|
|
649
|
+
return {
|
|
650
|
+
valid: false,
|
|
651
|
+
error: `Step mismatch: you're on step '${session.currentStep}', but called with step '${clientStep}'. Please call with step='${session.currentStep}'.`
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
return { valid: true };
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Transition session to next step with proper state updates
|
|
658
|
+
*/
|
|
659
|
+
function transitionCapabilitySession(session, nextStep, updates, args) {
|
|
660
|
+
session.currentStep = nextStep;
|
|
661
|
+
session.lastActivity = new Date().toISOString();
|
|
662
|
+
if (updates) {
|
|
663
|
+
Object.assign(session, updates);
|
|
664
|
+
}
|
|
665
|
+
saveCapabilitySession(session, args);
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Clean up session file after successful completion
|
|
669
|
+
*/
|
|
670
|
+
function cleanupCapabilitySession(session, args, logger, requestId) {
|
|
671
|
+
try {
|
|
672
|
+
const sessionPath = getCapabilitySessionPath(session.sessionId, args);
|
|
673
|
+
if (fs.existsSync(sessionPath)) {
|
|
674
|
+
fs.unlinkSync(sessionPath);
|
|
675
|
+
logger.info('Capability session cleaned up after completion', {
|
|
676
|
+
requestId,
|
|
677
|
+
sessionId: session.sessionId
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
// Log cleanup failure but don't fail the operation
|
|
683
|
+
logger.warn('Failed to cleanup capability session file', {
|
|
684
|
+
requestId,
|
|
685
|
+
sessionId: session.sessionId,
|
|
686
|
+
error: error instanceof Error ? error.message : String(error)
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Handle capability scanning workflow with step-based state management
|
|
692
|
+
*/
|
|
693
|
+
async function handleCapabilityScan(args, logger, requestId) {
|
|
694
|
+
// Validate Vector DB and embedding service dependencies upfront
|
|
695
|
+
// This prevents users from going through the entire workflow only to fail at storage
|
|
696
|
+
const capabilityService = new index_1.CapabilityVectorService();
|
|
697
|
+
// Check Vector DB connection and initialize collection
|
|
698
|
+
try {
|
|
699
|
+
const vectorDBHealthy = await capabilityService.healthCheck();
|
|
700
|
+
if (!vectorDBHealthy) {
|
|
701
|
+
return {
|
|
702
|
+
success: false,
|
|
703
|
+
operation: 'scan',
|
|
704
|
+
dataType: 'capabilities',
|
|
705
|
+
error: {
|
|
706
|
+
message: 'Vector DB (Qdrant) connection required for capability management',
|
|
707
|
+
details: 'Capability scanning requires Qdrant for storing and searching capabilities. The system cannot proceed without a working Vector DB connection.',
|
|
708
|
+
setup: {
|
|
709
|
+
required: 'Qdrant server must be running',
|
|
710
|
+
default: 'http://localhost:6333',
|
|
711
|
+
docker: 'docker run -p 6333:6333 qdrant/qdrant',
|
|
712
|
+
config: 'export QDRANT_URL=http://localhost:6333'
|
|
713
|
+
},
|
|
714
|
+
currentConfig: {
|
|
715
|
+
QDRANT_URL: process.env.QDRANT_URL || 'http://localhost:6333 (default)',
|
|
716
|
+
status: 'connection failed'
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
catch (error) {
|
|
723
|
+
logger.error('Vector DB connection check failed', error, { requestId });
|
|
724
|
+
return {
|
|
725
|
+
success: false,
|
|
726
|
+
operation: 'scan',
|
|
727
|
+
dataType: 'capabilities',
|
|
728
|
+
error: {
|
|
729
|
+
message: 'Vector DB (Qdrant) connection failed',
|
|
730
|
+
details: 'Cannot establish connection to Qdrant server. Please ensure Qdrant is running and accessible.',
|
|
731
|
+
technicalDetails: error instanceof Error ? error.message : String(error),
|
|
732
|
+
setup: {
|
|
733
|
+
required: 'Qdrant server must be running',
|
|
734
|
+
default: 'http://localhost:6333',
|
|
735
|
+
docker: 'docker run -p 6333:6333 qdrant/qdrant',
|
|
736
|
+
config: 'export QDRANT_URL=http://localhost:6333'
|
|
737
|
+
},
|
|
738
|
+
currentConfig: {
|
|
739
|
+
QDRANT_URL: process.env.QDRANT_URL || 'http://localhost:6333 (default)',
|
|
740
|
+
status: 'connection error'
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
// Initialize the collection now that we know Qdrant is healthy
|
|
746
|
+
try {
|
|
747
|
+
await capabilityService.initialize();
|
|
748
|
+
}
|
|
749
|
+
catch (error) {
|
|
750
|
+
logger.error('Failed to initialize capabilities collection', error, { requestId });
|
|
751
|
+
return {
|
|
752
|
+
success: false,
|
|
753
|
+
operation: 'scan',
|
|
754
|
+
dataType: 'capabilities',
|
|
755
|
+
error: {
|
|
756
|
+
message: 'Vector DB collection initialization failed',
|
|
757
|
+
details: 'Could not create or access the capabilities collection in Qdrant.',
|
|
758
|
+
technicalDetails: error instanceof Error ? error.message : String(error),
|
|
759
|
+
setup: {
|
|
760
|
+
possibleCauses: [
|
|
761
|
+
'Qdrant version compatibility issue',
|
|
762
|
+
'Insufficient permissions',
|
|
763
|
+
'Collection dimension mismatch',
|
|
764
|
+
'Corrupted existing collection'
|
|
765
|
+
],
|
|
766
|
+
recommendations: [
|
|
767
|
+
'Check Qdrant logs for detailed error information',
|
|
768
|
+
'Verify Qdrant version compatibility',
|
|
769
|
+
'Consider removing existing capabilities collection if corrupted'
|
|
770
|
+
]
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
// Check embedding service (OpenAI API) availability
|
|
776
|
+
const embeddingCheck = await validateEmbeddingService(logger, requestId);
|
|
777
|
+
if (!embeddingCheck.success) {
|
|
778
|
+
return {
|
|
779
|
+
success: false,
|
|
780
|
+
operation: 'scan',
|
|
781
|
+
dataType: 'capabilities',
|
|
782
|
+
error: {
|
|
783
|
+
message: 'OpenAI API required for capability semantic search',
|
|
784
|
+
details: 'Capability scanning requires OpenAI embeddings for semantic search functionality. The system cannot proceed without proper OpenAI API configuration.',
|
|
785
|
+
...embeddingCheck.error,
|
|
786
|
+
recommendation: 'Set up OpenAI API key to enable full capability scanning with semantic search'
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
logger.info('Capability scanning dependencies validated', {
|
|
791
|
+
requestId,
|
|
792
|
+
vectorDB: 'healthy',
|
|
793
|
+
embeddings: 'available'
|
|
794
|
+
});
|
|
795
|
+
// Get or create session with step-based state management
|
|
796
|
+
const session = getOrCreateCapabilitySession(args.sessionId, args, logger, requestId);
|
|
797
|
+
// Validate client is on correct step - only for existing sessions
|
|
798
|
+
// New sessions (no sessionId provided) are allowed to start without step parameter
|
|
799
|
+
// If sessionId is provided, client must follow step-based protocol
|
|
800
|
+
const clientProvidedSessionId = !!args.sessionId;
|
|
801
|
+
if (clientProvidedSessionId) {
|
|
802
|
+
const stepValidation = validateCapabilityStep(session, args.step);
|
|
803
|
+
if (!stepValidation.valid) {
|
|
804
|
+
return {
|
|
805
|
+
success: false,
|
|
806
|
+
operation: 'scan',
|
|
807
|
+
dataType: 'capabilities',
|
|
808
|
+
error: {
|
|
809
|
+
message: 'Step validation failed',
|
|
810
|
+
details: stepValidation.error,
|
|
811
|
+
currentStep: session.currentStep,
|
|
812
|
+
expectedCall: `Call with step='${session.currentStep}' and appropriate parameters`
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
// Handle workflow based on current step
|
|
818
|
+
switch (session.currentStep) {
|
|
819
|
+
case 'resource-selection':
|
|
820
|
+
return await handleResourceSelection(session, args, logger, requestId);
|
|
821
|
+
case 'resource-specification':
|
|
822
|
+
return await handleResourceSpecification(session, args, logger, requestId);
|
|
823
|
+
case 'processing-mode':
|
|
824
|
+
return await handleProcessingMode(session, args, logger, requestId, capabilityService);
|
|
825
|
+
case 'scanning':
|
|
826
|
+
return await handleScanning(session, args, logger, requestId, capabilityService);
|
|
827
|
+
case 'complete':
|
|
828
|
+
return {
|
|
829
|
+
success: false,
|
|
830
|
+
operation: 'scan',
|
|
831
|
+
dataType: 'capabilities',
|
|
832
|
+
error: {
|
|
833
|
+
message: 'Workflow already complete',
|
|
834
|
+
details: `Session ${session.sessionId} has already completed capability scanning.`,
|
|
835
|
+
sessionId: session.sessionId
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
default:
|
|
839
|
+
return {
|
|
840
|
+
success: false,
|
|
841
|
+
operation: 'scan',
|
|
842
|
+
dataType: 'capabilities',
|
|
843
|
+
error: {
|
|
844
|
+
message: 'Invalid workflow state',
|
|
845
|
+
details: `Unknown step: ${session.currentStep}`,
|
|
846
|
+
currentStep: session.currentStep
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Handle resource selection step
|
|
853
|
+
*/
|
|
854
|
+
async function handleResourceSelection(session, args, _logger, _requestId) {
|
|
855
|
+
if (!args.response) {
|
|
856
|
+
// Show initial resource selection prompt
|
|
857
|
+
return {
|
|
858
|
+
success: true,
|
|
859
|
+
operation: 'scan',
|
|
860
|
+
dataType: 'capabilities',
|
|
861
|
+
// CRITICAL: Put required parameters at top level for maximum visibility
|
|
862
|
+
REQUIRED_NEXT_CALL: {
|
|
863
|
+
tool: 'dot-ai:manageOrgData',
|
|
864
|
+
parameters: {
|
|
865
|
+
dataType: 'capabilities',
|
|
866
|
+
operation: 'scan',
|
|
867
|
+
sessionId: session.sessionId,
|
|
868
|
+
step: 'resource-selection', // MANDATORY PARAMETER
|
|
869
|
+
response: 'user_choice_here' // Replace with actual user choice
|
|
870
|
+
},
|
|
871
|
+
note: 'The step parameter is MANDATORY when sessionId is provided'
|
|
872
|
+
},
|
|
873
|
+
workflow: {
|
|
874
|
+
step: 'resource-selection',
|
|
875
|
+
question: 'Scan all cluster resources or specify subset?',
|
|
876
|
+
options: [
|
|
877
|
+
{ number: 1, value: 'all', display: '1. all - Scan all available cluster resources' },
|
|
878
|
+
{ number: 2, value: 'specific', display: '2. specific - Specify particular resource types to scan' }
|
|
879
|
+
],
|
|
880
|
+
sessionId: session.sessionId,
|
|
881
|
+
instruction: 'IMPORTANT: You MUST ask the user to make a choice. Do NOT automatically select an option.',
|
|
882
|
+
userPrompt: 'Would you like to scan all cluster resources or specify a subset?',
|
|
883
|
+
clientInstructions: {
|
|
884
|
+
behavior: 'interactive',
|
|
885
|
+
requirement: 'Ask user to choose between options',
|
|
886
|
+
prohibit: 'Do not auto-select options',
|
|
887
|
+
nextStep: `Call with step='resource-selection', sessionId='${session.sessionId}', and response parameter containing the semantic value (all or specific)`,
|
|
888
|
+
responseFormat: 'Convert user input to semantic values: 1→all, 2→specific, or pass through semantic words directly',
|
|
889
|
+
requiredParameters: {
|
|
890
|
+
step: 'resource-selection',
|
|
891
|
+
sessionId: session.sessionId,
|
|
892
|
+
response: 'user choice (all or specific)'
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
// Process user response
|
|
899
|
+
const normalizedResponse = parseNumericResponse(args.response, ['all', 'specific']);
|
|
900
|
+
if (normalizedResponse === 'all') {
|
|
901
|
+
// Transition to processing mode for all resources
|
|
902
|
+
transitionCapabilitySession(session, 'processing-mode', { selectedResources: 'all' }, args);
|
|
903
|
+
return {
|
|
904
|
+
success: true,
|
|
905
|
+
operation: 'scan',
|
|
906
|
+
dataType: 'capabilities',
|
|
907
|
+
// CRITICAL: Put required parameters at top level for maximum visibility
|
|
908
|
+
REQUIRED_NEXT_CALL: {
|
|
909
|
+
tool: 'dot-ai:manageOrgData',
|
|
910
|
+
parameters: {
|
|
911
|
+
dataType: 'capabilities',
|
|
912
|
+
operation: 'scan',
|
|
913
|
+
sessionId: session.sessionId,
|
|
914
|
+
step: 'processing-mode', // MANDATORY PARAMETER
|
|
915
|
+
response: 'user_choice_here' // Replace with actual user choice
|
|
916
|
+
},
|
|
917
|
+
note: 'The step parameter is MANDATORY when sessionId is provided'
|
|
918
|
+
},
|
|
919
|
+
workflow: {
|
|
920
|
+
step: 'processing-mode',
|
|
921
|
+
question: 'Processing mode: auto (batch process) or manual (review each)?',
|
|
922
|
+
options: [
|
|
923
|
+
{ number: 1, value: 'auto', display: '1. auto - Batch process automatically' },
|
|
924
|
+
{ number: 2, value: 'manual', display: '2. manual - Review each step' }
|
|
925
|
+
],
|
|
926
|
+
sessionId: session.sessionId,
|
|
927
|
+
selectedResources: 'all',
|
|
928
|
+
instruction: 'IMPORTANT: You MUST ask the user to make a choice. Do NOT automatically select a processing mode.',
|
|
929
|
+
userPrompt: 'How would you like to process the resources?',
|
|
930
|
+
clientInstructions: {
|
|
931
|
+
behavior: 'interactive',
|
|
932
|
+
requirement: 'Ask user to choose processing mode',
|
|
933
|
+
prohibit: 'Do not auto-select processing mode',
|
|
934
|
+
nextStep: `Call with step='processing-mode', sessionId='${session.sessionId}', and response parameter containing the semantic value (auto or manual)`,
|
|
935
|
+
responseFormat: 'Convert user input to semantic values: 1→auto, 2→manual, or pass through semantic words directly',
|
|
936
|
+
requiredParameters: {
|
|
937
|
+
step: 'processing-mode',
|
|
938
|
+
sessionId: session.sessionId,
|
|
939
|
+
response: 'user choice (auto or manual)'
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
if (normalizedResponse === 'specific') {
|
|
946
|
+
// Transition to resource specification
|
|
947
|
+
transitionCapabilitySession(session, 'resource-specification', {}, args);
|
|
948
|
+
return {
|
|
949
|
+
success: true,
|
|
950
|
+
operation: 'scan',
|
|
951
|
+
dataType: 'capabilities',
|
|
952
|
+
// CRITICAL: Put required parameters at top level for maximum visibility
|
|
953
|
+
REQUIRED_NEXT_CALL: {
|
|
954
|
+
tool: 'dot-ai:manageOrgData',
|
|
955
|
+
parameters: {
|
|
956
|
+
dataType: 'capabilities',
|
|
957
|
+
operation: 'scan',
|
|
958
|
+
sessionId: session.sessionId,
|
|
959
|
+
step: 'resource-specification', // MANDATORY PARAMETER
|
|
960
|
+
resourceList: 'user_resource_list_here' // Replace with actual resource list
|
|
961
|
+
},
|
|
962
|
+
note: 'The step parameter is MANDATORY when sessionId is provided'
|
|
963
|
+
},
|
|
964
|
+
workflow: {
|
|
965
|
+
step: 'resource-specification',
|
|
966
|
+
question: 'Which resources would you like to scan?',
|
|
967
|
+
sessionId: session.sessionId,
|
|
968
|
+
instruction: 'IMPORTANT: You MUST ask the user to specify which resources to scan. Do NOT provide a default list.',
|
|
969
|
+
userPrompt: 'Please specify which resources you want to scan.',
|
|
970
|
+
resourceFormat: {
|
|
971
|
+
description: 'Specify resources using Kubernetes naming convention',
|
|
972
|
+
format: 'Kind.group for CRDs, Kind for core resources',
|
|
973
|
+
examples: {
|
|
974
|
+
crds: ['SQL.devopstoolkit.live', 'Server.dbforpostgresql.azure.upbound.io'],
|
|
975
|
+
core: ['Pod', 'Service', 'ConfigMap'],
|
|
976
|
+
apps: ['Deployment.apps', 'StatefulSet.apps']
|
|
977
|
+
},
|
|
978
|
+
input: 'Comma-separated list (e.g.: SQL.devopstoolkit.live, Deployment.apps, Pod)'
|
|
979
|
+
},
|
|
980
|
+
clientInstructions: {
|
|
981
|
+
behavior: 'interactive',
|
|
982
|
+
requirement: 'Ask user to provide specific resource list',
|
|
983
|
+
prohibit: 'Do not suggest or auto-select resources',
|
|
984
|
+
nextStep: `Call with step='resource-specification', sessionId='${session.sessionId}', and resourceList parameter`,
|
|
985
|
+
requiredParameters: {
|
|
986
|
+
step: 'resource-specification',
|
|
987
|
+
sessionId: session.sessionId,
|
|
988
|
+
resourceList: 'comma-separated list of resources'
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
return {
|
|
995
|
+
success: false,
|
|
996
|
+
operation: 'scan',
|
|
997
|
+
dataType: 'capabilities',
|
|
998
|
+
error: {
|
|
999
|
+
message: 'Invalid resource selection response',
|
|
1000
|
+
details: `Expected 'all' or 'specific', got: ${args.response}`,
|
|
1001
|
+
currentStep: session.currentStep
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Handle resource specification step
|
|
1007
|
+
*/
|
|
1008
|
+
async function handleResourceSpecification(session, args, _logger, _requestId) {
|
|
1009
|
+
if (!args.resourceList) {
|
|
1010
|
+
return {
|
|
1011
|
+
success: false,
|
|
1012
|
+
operation: 'scan',
|
|
1013
|
+
dataType: 'capabilities',
|
|
1014
|
+
error: {
|
|
1015
|
+
message: 'Missing resource list',
|
|
1016
|
+
details: 'Expected resourceList parameter with comma-separated resource names',
|
|
1017
|
+
currentStep: session.currentStep,
|
|
1018
|
+
expectedCall: `Call with step='resource-specification' and resourceList parameter`
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
// Parse and validate resource list
|
|
1023
|
+
const resources = args.resourceList.split(',').map((r) => r.trim()).filter((r) => r.length > 0);
|
|
1024
|
+
if (resources.length === 0) {
|
|
1025
|
+
return {
|
|
1026
|
+
success: false,
|
|
1027
|
+
operation: 'scan',
|
|
1028
|
+
dataType: 'capabilities',
|
|
1029
|
+
error: {
|
|
1030
|
+
message: 'Empty resource list',
|
|
1031
|
+
details: 'Resource list cannot be empty',
|
|
1032
|
+
currentStep: session.currentStep
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
// Transition to processing mode for specific resources
|
|
1037
|
+
transitionCapabilitySession(session, 'processing-mode', {
|
|
1038
|
+
selectedResources: resources,
|
|
1039
|
+
resourceList: args.resourceList
|
|
1040
|
+
}, args);
|
|
1041
|
+
return {
|
|
1042
|
+
success: true,
|
|
1043
|
+
operation: 'scan',
|
|
1044
|
+
dataType: 'capabilities',
|
|
1045
|
+
// CRITICAL: Put required parameters at top level for maximum visibility
|
|
1046
|
+
REQUIRED_NEXT_CALL: {
|
|
1047
|
+
tool: 'dot-ai:manageOrgData',
|
|
1048
|
+
parameters: {
|
|
1049
|
+
dataType: 'capabilities',
|
|
1050
|
+
operation: 'scan',
|
|
1051
|
+
sessionId: session.sessionId,
|
|
1052
|
+
step: 'processing-mode', // MANDATORY PARAMETER
|
|
1053
|
+
response: 'user_choice_here' // Replace with actual user choice
|
|
1054
|
+
},
|
|
1055
|
+
note: 'The step parameter is MANDATORY when sessionId is provided'
|
|
1056
|
+
},
|
|
1057
|
+
workflow: {
|
|
1058
|
+
step: 'processing-mode',
|
|
1059
|
+
question: `Processing mode for ${resources.length} selected resources: auto (batch process) or manual (review each)?`,
|
|
1060
|
+
options: [
|
|
1061
|
+
{ number: 1, value: 'auto', display: '1. auto - Batch process automatically' },
|
|
1062
|
+
{ number: 2, value: 'manual', display: '2. manual - Review each step' }
|
|
1063
|
+
],
|
|
1064
|
+
sessionId: session.sessionId,
|
|
1065
|
+
selectedResources: resources,
|
|
1066
|
+
instruction: 'IMPORTANT: You MUST ask the user to choose processing mode for the specified resources.',
|
|
1067
|
+
userPrompt: `How would you like to process these ${resources.length} resources?`,
|
|
1068
|
+
clientInstructions: {
|
|
1069
|
+
behavior: 'interactive',
|
|
1070
|
+
requirement: 'Ask user to choose processing mode for specific resources',
|
|
1071
|
+
context: `Processing ${resources.length} user-specified resources: ${resources.join(', ')}`,
|
|
1072
|
+
prohibit: 'Do not auto-select processing mode',
|
|
1073
|
+
nextStep: `Call with step='processing-mode', sessionId='${session.sessionId}', and response parameter containing the semantic value (auto or manual)`,
|
|
1074
|
+
responseFormat: 'Convert user input to semantic values: 1→auto, 2→manual, or pass through semantic words directly',
|
|
1075
|
+
requiredParameters: {
|
|
1076
|
+
step: 'processing-mode',
|
|
1077
|
+
sessionId: session.sessionId,
|
|
1078
|
+
response: 'user choice (auto or manual)'
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Handle processing mode step
|
|
1086
|
+
*/
|
|
1087
|
+
async function handleProcessingMode(session, args, logger, requestId, capabilityService) {
|
|
1088
|
+
if (!args.response) {
|
|
1089
|
+
return {
|
|
1090
|
+
success: false,
|
|
1091
|
+
operation: 'scan',
|
|
1092
|
+
dataType: 'capabilities',
|
|
1093
|
+
error: {
|
|
1094
|
+
message: 'Missing processing mode response',
|
|
1095
|
+
details: 'Expected response parameter with processing mode choice',
|
|
1096
|
+
currentStep: session.currentStep,
|
|
1097
|
+
expectedCall: `Call with step='processing-mode' and response parameter (auto or manual)`
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
// Process user response
|
|
1102
|
+
const processingModeResponse = parseNumericResponse(args.response, ['auto', 'manual']);
|
|
1103
|
+
if (processingModeResponse !== 'auto' && processingModeResponse !== 'manual') {
|
|
1104
|
+
return {
|
|
1105
|
+
success: false,
|
|
1106
|
+
operation: 'scan',
|
|
1107
|
+
dataType: 'capabilities',
|
|
1108
|
+
error: {
|
|
1109
|
+
message: 'Invalid processing mode response',
|
|
1110
|
+
details: `Expected 'auto' or 'manual', got: ${args.response}`,
|
|
1111
|
+
currentStep: session.currentStep
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
// Transition to scanning with processing mode and initialize resource tracking
|
|
1116
|
+
transitionCapabilitySession(session, 'scanning', {
|
|
1117
|
+
processingMode: processingModeResponse,
|
|
1118
|
+
currentResourceIndex: 0 // Start with first resource
|
|
1119
|
+
}, args);
|
|
1120
|
+
// Begin actual capability scanning - clear response from previous step
|
|
1121
|
+
return await handleScanning(session, { ...args, response: undefined }, logger, requestId, capabilityService);
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Handle scanning step (actual capability analysis)
|
|
1125
|
+
*/
|
|
1126
|
+
async function handleScanning(session, args, logger, requestId, capabilityService) {
|
|
1127
|
+
try {
|
|
1128
|
+
// If this is a response to a manual mode preview, handle it first
|
|
1129
|
+
if (session.processingMode === 'manual' && args.response) {
|
|
1130
|
+
const userResponse = parseNumericResponse(args.response, ['yes', 'no', 'stop']);
|
|
1131
|
+
if (userResponse === 'stop') {
|
|
1132
|
+
// User wants to stop scanning
|
|
1133
|
+
transitionCapabilitySession(session, 'complete', {}, args);
|
|
1134
|
+
cleanupCapabilitySession(session, args, logger, requestId);
|
|
1135
|
+
const currentIndex = session.currentResourceIndex || 0;
|
|
1136
|
+
const totalResources = Array.isArray(session.selectedResources) ? session.selectedResources.length : 1;
|
|
1137
|
+
return createCapabilityScanCompletionResponse(session.sessionId, totalResources, currentIndex, // Resources processed so far
|
|
1138
|
+
0, 'stopped interactively', 'manual', true // stopped = true
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
if (userResponse === 'yes' || userResponse === 'no') {
|
|
1142
|
+
// TODO: If 'yes', store the capability in Vector DB (Milestone 2)
|
|
1143
|
+
// For now, just log the decision
|
|
1144
|
+
logger.info(`User ${userResponse === 'yes' ? 'accepted' : 'skipped'} capability for resource`, {
|
|
1145
|
+
requestId,
|
|
1146
|
+
sessionId: session.sessionId,
|
|
1147
|
+
resourceIndex: session.currentResourceIndex,
|
|
1148
|
+
decision: userResponse
|
|
1149
|
+
});
|
|
1150
|
+
// Move to the next resource
|
|
1151
|
+
const nextIndex = (session.currentResourceIndex || 0) + 1;
|
|
1152
|
+
transitionCapabilitySession(session, 'scanning', { currentResourceIndex: nextIndex }, args);
|
|
1153
|
+
// Continue processing (will handle the next resource or complete if done)
|
|
1154
|
+
return await handleScanning(session, { ...args, response: undefined }, logger, requestId, capabilityService);
|
|
1155
|
+
}
|
|
1156
|
+
// Invalid response
|
|
1157
|
+
return {
|
|
1158
|
+
success: false,
|
|
1159
|
+
operation: 'scan',
|
|
1160
|
+
dataType: 'capabilities',
|
|
1161
|
+
error: {
|
|
1162
|
+
message: 'Invalid response to capability preview',
|
|
1163
|
+
details: `Expected 'yes', 'no', or 'stop', got: ${args.response}`,
|
|
1164
|
+
currentStep: session.currentStep
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
// Import capability engine
|
|
1169
|
+
const { CapabilityInferenceEngine } = await Promise.resolve().then(() => __importStar(require('../core/capabilities')));
|
|
1170
|
+
const { ClaudeIntegration } = await Promise.resolve().then(() => __importStar(require('../core/claude')));
|
|
1171
|
+
// Validate Claude API key
|
|
1172
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
1173
|
+
if (!apiKey) {
|
|
1174
|
+
return {
|
|
1175
|
+
success: false,
|
|
1176
|
+
operation: 'scan',
|
|
1177
|
+
dataType: 'capabilities',
|
|
1178
|
+
error: {
|
|
1179
|
+
message: 'ANTHROPIC_API_KEY required for capability inference',
|
|
1180
|
+
details: 'Set ANTHROPIC_API_KEY environment variable to enable AI-powered capability analysis'
|
|
1181
|
+
}
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
// Initialize capability engine
|
|
1185
|
+
const claudeIntegration = new ClaudeIntegration(apiKey);
|
|
1186
|
+
const engine = new CapabilityInferenceEngine(claudeIntegration, logger);
|
|
1187
|
+
// Get the resource to analyze
|
|
1188
|
+
let resourceName;
|
|
1189
|
+
let currentIndex;
|
|
1190
|
+
let totalResources;
|
|
1191
|
+
if (session.selectedResources === 'all') {
|
|
1192
|
+
// For 'all' mode, discover actual cluster resources first
|
|
1193
|
+
try {
|
|
1194
|
+
logger.info('Discovering all cluster resources for capability scanning', { requestId, sessionId: session.sessionId });
|
|
1195
|
+
// Import discovery engine
|
|
1196
|
+
const { KubernetesDiscovery } = await Promise.resolve().then(() => __importStar(require('../core/discovery')));
|
|
1197
|
+
const discovery = new KubernetesDiscovery();
|
|
1198
|
+
await discovery.connect();
|
|
1199
|
+
// Discover all available resources
|
|
1200
|
+
const resourceMap = await discovery.discoverResources();
|
|
1201
|
+
const allResources = [...resourceMap.resources, ...resourceMap.custom];
|
|
1202
|
+
// Extract resource names for capability analysis
|
|
1203
|
+
const discoveredResourceNames = allResources.map(resource => {
|
|
1204
|
+
// For CRDs (custom resources), prioritize full name format
|
|
1205
|
+
if (resource.name && resource.name.includes('.')) {
|
|
1206
|
+
return resource.name;
|
|
1207
|
+
}
|
|
1208
|
+
// For standard resources, use kind
|
|
1209
|
+
if (resource.kind) {
|
|
1210
|
+
return resource.kind;
|
|
1211
|
+
}
|
|
1212
|
+
// Fallback to name if no kind
|
|
1213
|
+
if (resource.name) {
|
|
1214
|
+
return resource.name;
|
|
1215
|
+
}
|
|
1216
|
+
return 'unknown-resource';
|
|
1217
|
+
}).filter(name => name !== 'unknown-resource');
|
|
1218
|
+
logger.info('Discovered cluster resources for capability scanning', {
|
|
1219
|
+
requestId,
|
|
1220
|
+
sessionId: session.sessionId,
|
|
1221
|
+
totalDiscovered: discoveredResourceNames.length,
|
|
1222
|
+
sampleResources: discoveredResourceNames.slice(0, 5)
|
|
1223
|
+
});
|
|
1224
|
+
if (discoveredResourceNames.length === 0) {
|
|
1225
|
+
return {
|
|
1226
|
+
success: false,
|
|
1227
|
+
operation: 'scan',
|
|
1228
|
+
dataType: 'capabilities',
|
|
1229
|
+
error: {
|
|
1230
|
+
message: 'No resources discovered in cluster',
|
|
1231
|
+
details: 'Cluster resource discovery returned empty results. Check cluster connectivity and permissions.',
|
|
1232
|
+
sessionId: session.sessionId
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
// Update session with discovered resources and start batch processing
|
|
1237
|
+
transitionCapabilitySession(session, 'scanning', {
|
|
1238
|
+
selectedResources: discoveredResourceNames,
|
|
1239
|
+
processingMode: session.processingMode,
|
|
1240
|
+
currentResourceIndex: 0
|
|
1241
|
+
}, args);
|
|
1242
|
+
// Continue to batch processing with discovered resources
|
|
1243
|
+
resourceName = discoveredResourceNames[0]; // First resource for manual mode
|
|
1244
|
+
currentIndex = 0;
|
|
1245
|
+
totalResources = discoveredResourceNames.length;
|
|
1246
|
+
}
|
|
1247
|
+
catch (error) {
|
|
1248
|
+
logger.error('Failed to discover cluster resources', error, {
|
|
1249
|
+
requestId,
|
|
1250
|
+
sessionId: session.sessionId
|
|
1251
|
+
});
|
|
1252
|
+
return {
|
|
1253
|
+
success: false,
|
|
1254
|
+
operation: 'scan',
|
|
1255
|
+
dataType: 'capabilities',
|
|
1256
|
+
error: {
|
|
1257
|
+
message: 'Cluster resource discovery failed',
|
|
1258
|
+
details: error instanceof Error ? error.message : String(error),
|
|
1259
|
+
sessionId: session.sessionId,
|
|
1260
|
+
suggestedActions: [
|
|
1261
|
+
'Check cluster connectivity',
|
|
1262
|
+
'Verify kubectl access permissions',
|
|
1263
|
+
'Try specifying specific resources instead of "all"'
|
|
1264
|
+
]
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
else if (Array.isArray(session.selectedResources)) {
|
|
1270
|
+
// Get the current resource based on currentResourceIndex
|
|
1271
|
+
currentIndex = session.currentResourceIndex || 0;
|
|
1272
|
+
totalResources = session.selectedResources.length;
|
|
1273
|
+
if (currentIndex >= totalResources) {
|
|
1274
|
+
// All resources processed - mark as complete
|
|
1275
|
+
transitionCapabilitySession(session, 'complete', {}, args);
|
|
1276
|
+
cleanupCapabilitySession(session, args, logger, requestId);
|
|
1277
|
+
return createCapabilityScanCompletionResponse(session.sessionId, totalResources, totalResources, // Assume all successful for manual mode
|
|
1278
|
+
0, 'completed interactively', 'manual');
|
|
1279
|
+
}
|
|
1280
|
+
resourceName = session.selectedResources[currentIndex];
|
|
1281
|
+
if (!resourceName) {
|
|
1282
|
+
throw new Error(`No resource found at index ${currentIndex}`);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
else {
|
|
1286
|
+
throw new Error('Invalid selectedResources in session state');
|
|
1287
|
+
}
|
|
1288
|
+
// Get actual CRD schema and metadata for comprehensive analysis
|
|
1289
|
+
let resourceSchema;
|
|
1290
|
+
let resourceMetadata;
|
|
1291
|
+
try {
|
|
1292
|
+
// Import and connect to discovery engine
|
|
1293
|
+
const { KubernetesDiscovery } = await Promise.resolve().then(() => __importStar(require('../core/discovery')));
|
|
1294
|
+
const discovery = new KubernetesDiscovery();
|
|
1295
|
+
await discovery.connect();
|
|
1296
|
+
// Get CRD definition with schema and metadata if it's a custom resource
|
|
1297
|
+
if (resourceName.includes('.')) {
|
|
1298
|
+
const crdList = await discovery.discoverCRDDetails();
|
|
1299
|
+
const matchingCRD = crdList.find((crd) => crd.name === resourceName);
|
|
1300
|
+
if (matchingCRD) {
|
|
1301
|
+
resourceSchema = JSON.stringify(matchingCRD.schema, null, 2);
|
|
1302
|
+
resourceMetadata = matchingCRD.metadata;
|
|
1303
|
+
logger.info('Found CRD schema and metadata for capability analysis', {
|
|
1304
|
+
requestId,
|
|
1305
|
+
sessionId: session.sessionId,
|
|
1306
|
+
resource: resourceName,
|
|
1307
|
+
hasSchema: !!resourceSchema,
|
|
1308
|
+
hasMetadata: !!resourceMetadata,
|
|
1309
|
+
metadataLabels: Object.keys(resourceMetadata?.labels || {}),
|
|
1310
|
+
categories: resourceMetadata?.categories?.length || 0
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
catch (error) {
|
|
1316
|
+
logger.warn('Could not retrieve CRD schema and metadata, using name-based inference', {
|
|
1317
|
+
requestId,
|
|
1318
|
+
sessionId: session.sessionId,
|
|
1319
|
+
resource: resourceName,
|
|
1320
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
logger.info('Analyzing resource for capability inference', {
|
|
1324
|
+
requestId,
|
|
1325
|
+
sessionId: session.sessionId,
|
|
1326
|
+
resource: resourceName,
|
|
1327
|
+
mode: session.processingMode
|
|
1328
|
+
});
|
|
1329
|
+
if (session.processingMode === 'manual') {
|
|
1330
|
+
// Manual mode: Show capability data for user review
|
|
1331
|
+
const capability = await engine.inferCapabilities(resourceName, resourceSchema, resourceMetadata);
|
|
1332
|
+
const capabilityId = CapabilityInferenceEngine.generateCapabilityId(resourceName);
|
|
1333
|
+
return {
|
|
1334
|
+
success: true,
|
|
1335
|
+
operation: 'scan',
|
|
1336
|
+
dataType: 'capabilities',
|
|
1337
|
+
mode: 'manual',
|
|
1338
|
+
step: 'scanning',
|
|
1339
|
+
sessionId: session.sessionId,
|
|
1340
|
+
preview: {
|
|
1341
|
+
resource: resourceName,
|
|
1342
|
+
resourceIndex: `${currentIndex + 1}/${totalResources}`,
|
|
1343
|
+
id: capabilityId,
|
|
1344
|
+
data: capability,
|
|
1345
|
+
question: 'Continue storing this capability?',
|
|
1346
|
+
options: [
|
|
1347
|
+
{ number: 1, value: 'yes', display: '1. yes - Store this capability' },
|
|
1348
|
+
{ number: 2, value: 'no', display: '2. no - Skip this resource' },
|
|
1349
|
+
{ number: 3, value: 'stop', display: '3. stop - End scanning process' }
|
|
1350
|
+
],
|
|
1351
|
+
instruction: 'Review the capability analysis results before storing',
|
|
1352
|
+
clientInstructions: {
|
|
1353
|
+
behavior: 'interactive',
|
|
1354
|
+
requirement: 'Ask user to review capability data and decide on storage',
|
|
1355
|
+
nextStep: `Call with step='scanning' and response parameter containing their choice (yes/no/stop)`,
|
|
1356
|
+
responseFormat: 'Convert user input to semantic values: 1→yes, 2→no, 3→stop'
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
else {
|
|
1362
|
+
// Auto mode: Process ALL resources in batch without user interaction
|
|
1363
|
+
// At this point, selectedResources should always be an array (either discovered or specified)
|
|
1364
|
+
if (!Array.isArray(session.selectedResources)) {
|
|
1365
|
+
throw new Error(`Invalid selectedResources state: expected array, got ${typeof session.selectedResources}. This indicates a bug in resource discovery.`);
|
|
1366
|
+
}
|
|
1367
|
+
const resources = session.selectedResources;
|
|
1368
|
+
const totalResources = resources.length;
|
|
1369
|
+
const processedResults = [];
|
|
1370
|
+
const errors = [];
|
|
1371
|
+
logger.info('Starting auto batch processing', {
|
|
1372
|
+
requestId,
|
|
1373
|
+
sessionId: session.sessionId,
|
|
1374
|
+
totalResources,
|
|
1375
|
+
resources: resources
|
|
1376
|
+
});
|
|
1377
|
+
// Initialize progress tracking
|
|
1378
|
+
const startTime = Date.now();
|
|
1379
|
+
const updateProgress = (current, currentResource, successful, failed, recentErrors) => {
|
|
1380
|
+
const elapsed = Date.now() - startTime;
|
|
1381
|
+
const percentage = Math.round((current / totalResources) * 100);
|
|
1382
|
+
// Calculate estimated time remaining
|
|
1383
|
+
let estimatedTimeRemaining;
|
|
1384
|
+
if (current > 0) {
|
|
1385
|
+
const avgTimePerResource = elapsed / current;
|
|
1386
|
+
const remainingResources = totalResources - current;
|
|
1387
|
+
const estimatedRemainingMs = remainingResources * avgTimePerResource;
|
|
1388
|
+
const estimatedMinutes = Math.round(estimatedRemainingMs / 60000 * 10) / 10;
|
|
1389
|
+
estimatedTimeRemaining = estimatedMinutes > 1 ?
|
|
1390
|
+
`${estimatedMinutes} minutes` :
|
|
1391
|
+
`${Math.round(estimatedRemainingMs / 1000)} seconds`;
|
|
1392
|
+
}
|
|
1393
|
+
const progressData = {
|
|
1394
|
+
status: 'processing',
|
|
1395
|
+
current: current,
|
|
1396
|
+
total: totalResources,
|
|
1397
|
+
percentage: percentage,
|
|
1398
|
+
currentResource: currentResource,
|
|
1399
|
+
startedAt: new Date(startTime).toISOString(),
|
|
1400
|
+
lastUpdated: new Date().toISOString(),
|
|
1401
|
+
estimatedTimeRemaining,
|
|
1402
|
+
successfulResources: successful,
|
|
1403
|
+
failedResources: failed,
|
|
1404
|
+
errors: recentErrors.slice(-5) // Keep last 5 errors for troubleshooting
|
|
1405
|
+
};
|
|
1406
|
+
// Update session file with progress
|
|
1407
|
+
transitionCapabilitySession(session, 'scanning', {
|
|
1408
|
+
processingMode: session.processingMode,
|
|
1409
|
+
selectedResources: session.selectedResources,
|
|
1410
|
+
currentResourceIndex: current - 1,
|
|
1411
|
+
progress: progressData
|
|
1412
|
+
}, args);
|
|
1413
|
+
};
|
|
1414
|
+
// Get all CRD definitions once for efficiency
|
|
1415
|
+
let availableCRDs = [];
|
|
1416
|
+
try {
|
|
1417
|
+
const { KubernetesDiscovery } = await Promise.resolve().then(() => __importStar(require('../core/discovery')));
|
|
1418
|
+
const discovery = new KubernetesDiscovery();
|
|
1419
|
+
await discovery.connect();
|
|
1420
|
+
availableCRDs = await discovery.discoverCRDDetails();
|
|
1421
|
+
logger.info('Retrieved CRD definitions for batch processing', {
|
|
1422
|
+
requestId,
|
|
1423
|
+
sessionId: session.sessionId,
|
|
1424
|
+
totalCRDs: availableCRDs.length
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
catch (error) {
|
|
1428
|
+
logger.warn('Could not retrieve CRDs for batch processing, falling back to name-based inference', {
|
|
1429
|
+
requestId,
|
|
1430
|
+
sessionId: session.sessionId,
|
|
1431
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
// Process each resource in the batch with progress tracking
|
|
1435
|
+
for (let i = 0; i < resources.length; i++) {
|
|
1436
|
+
const currentResource = resources[i];
|
|
1437
|
+
// Get CRD schema and metadata for this resource
|
|
1438
|
+
let currentSchema;
|
|
1439
|
+
let currentMetadata;
|
|
1440
|
+
if (currentResource.includes('.')) {
|
|
1441
|
+
const matchingCRD = availableCRDs.find((crd) => crd.name === currentResource);
|
|
1442
|
+
if (matchingCRD) {
|
|
1443
|
+
currentSchema = JSON.stringify(matchingCRD.schema, null, 2);
|
|
1444
|
+
currentMetadata = matchingCRD.metadata;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
// Update progress before processing
|
|
1448
|
+
updateProgress(i + 1, currentResource, processedResults.length, errors.length, errors);
|
|
1449
|
+
try {
|
|
1450
|
+
logger.info(`Processing resource ${i + 1}/${totalResources}`, {
|
|
1451
|
+
requestId,
|
|
1452
|
+
sessionId: session.sessionId,
|
|
1453
|
+
resource: currentResource,
|
|
1454
|
+
percentage: Math.round(((i + 1) / totalResources) * 100)
|
|
1455
|
+
});
|
|
1456
|
+
const capability = await engine.inferCapabilities(currentResource, currentSchema, currentMetadata);
|
|
1457
|
+
const capabilityId = CapabilityInferenceEngine.generateCapabilityId(currentResource);
|
|
1458
|
+
// Store capability in Vector DB
|
|
1459
|
+
await capabilityService.storeCapability(capability);
|
|
1460
|
+
processedResults.push({
|
|
1461
|
+
resource: currentResource,
|
|
1462
|
+
id: capabilityId,
|
|
1463
|
+
capabilities: capability.capabilities,
|
|
1464
|
+
providers: capability.providers,
|
|
1465
|
+
complexity: capability.complexity,
|
|
1466
|
+
confidence: capability.confidence
|
|
1467
|
+
});
|
|
1468
|
+
logger.info(`Successfully processed resource ${i + 1}/${totalResources}`, {
|
|
1469
|
+
requestId,
|
|
1470
|
+
sessionId: session.sessionId,
|
|
1471
|
+
resource: currentResource,
|
|
1472
|
+
capabilitiesFound: capability.capabilities.length,
|
|
1473
|
+
percentage: Math.round(((i + 1) / totalResources) * 100)
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
catch (error) {
|
|
1477
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1478
|
+
const errorDetail = {
|
|
1479
|
+
resource: currentResource,
|
|
1480
|
+
error: errorMessage,
|
|
1481
|
+
index: i + 1,
|
|
1482
|
+
timestamp: new Date().toISOString()
|
|
1483
|
+
};
|
|
1484
|
+
logger.error(`Failed to process resource ${i + 1}/${totalResources}`, error, {
|
|
1485
|
+
requestId,
|
|
1486
|
+
sessionId: session.sessionId,
|
|
1487
|
+
resource: currentResource,
|
|
1488
|
+
percentage: Math.round(((i + 1) / totalResources) * 100)
|
|
1489
|
+
});
|
|
1490
|
+
errors.push(errorDetail);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
// Final progress update - mark as completed
|
|
1494
|
+
const finalElapsed = Date.now() - startTime;
|
|
1495
|
+
const finalMinutes = Math.round(finalElapsed / 60000 * 10) / 10;
|
|
1496
|
+
const successful = processedResults.length;
|
|
1497
|
+
const failed = errors.length;
|
|
1498
|
+
const completionData = {
|
|
1499
|
+
status: 'completed',
|
|
1500
|
+
current: totalResources,
|
|
1501
|
+
total: totalResources,
|
|
1502
|
+
percentage: 100,
|
|
1503
|
+
currentResource: 'Processing complete',
|
|
1504
|
+
startedAt: new Date(startTime).toISOString(),
|
|
1505
|
+
lastUpdated: new Date().toISOString(),
|
|
1506
|
+
completedAt: new Date().toISOString(),
|
|
1507
|
+
totalProcessingTime: finalMinutes > 1 ? `${finalMinutes} minutes` : `${Math.round(finalElapsed / 1000)} seconds`,
|
|
1508
|
+
successfulResources: successful,
|
|
1509
|
+
failedResources: failed,
|
|
1510
|
+
errors: errors.slice(-5)
|
|
1511
|
+
};
|
|
1512
|
+
// Update session with completion status
|
|
1513
|
+
transitionCapabilitySession(session, 'complete', {
|
|
1514
|
+
progress: completionData
|
|
1515
|
+
}, args);
|
|
1516
|
+
logger.info('Auto batch processing completed', {
|
|
1517
|
+
requestId,
|
|
1518
|
+
sessionId: session.sessionId,
|
|
1519
|
+
processed: totalResources,
|
|
1520
|
+
successful,
|
|
1521
|
+
failed,
|
|
1522
|
+
processingTime: completionData.totalProcessingTime
|
|
1523
|
+
});
|
|
1524
|
+
// Clean up session file after a brief delay to allow progress viewing
|
|
1525
|
+
setTimeout(() => {
|
|
1526
|
+
cleanupCapabilitySession(session, args, logger, requestId);
|
|
1527
|
+
}, 30000); // Keep for 30 seconds after completion
|
|
1528
|
+
return createCapabilityScanCompletionResponse(session.sessionId, totalResources, successful, failed, completionData.totalProcessingTime || 'completed', 'auto');
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
catch (error) {
|
|
1532
|
+
logger.error('Capability scanning failed', error, {
|
|
1533
|
+
requestId,
|
|
1534
|
+
sessionId: session.sessionId,
|
|
1535
|
+
resource: (error && typeof error === 'object' && 'resourceName' in error) ? error.resourceName : 'unknown',
|
|
1536
|
+
step: session.currentStep
|
|
1537
|
+
});
|
|
1538
|
+
// Clean up session on error
|
|
1539
|
+
cleanupCapabilitySession(session, args, logger, requestId);
|
|
1540
|
+
return {
|
|
1541
|
+
success: false,
|
|
1542
|
+
operation: 'scan',
|
|
1543
|
+
dataType: 'capabilities',
|
|
1544
|
+
error: {
|
|
1545
|
+
message: 'Capability scanning failed',
|
|
1546
|
+
details: error instanceof Error ? error.message : String(error)
|
|
1547
|
+
}
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Handle capability listing (placeholder for future implementation)
|
|
1553
|
+
*/
|
|
1554
|
+
async function handleCapabilityList(args, logger, requestId, capabilityService) {
|
|
1555
|
+
try {
|
|
1556
|
+
// Get all capabilities with optional limit
|
|
1557
|
+
const limit = args.limit || 10;
|
|
1558
|
+
const capabilities = await capabilityService.getAllCapabilities(limit);
|
|
1559
|
+
const count = await capabilityService.getCapabilitiesCount();
|
|
1560
|
+
logger.info('Capabilities listed successfully', {
|
|
1561
|
+
requestId,
|
|
1562
|
+
count: capabilities.length,
|
|
1563
|
+
totalCount: count,
|
|
1564
|
+
limit
|
|
1565
|
+
});
|
|
1566
|
+
return {
|
|
1567
|
+
success: true,
|
|
1568
|
+
operation: 'list',
|
|
1569
|
+
dataType: 'capabilities',
|
|
1570
|
+
data: {
|
|
1571
|
+
capabilities: capabilities.map(cap => ({
|
|
1572
|
+
id: cap.id,
|
|
1573
|
+
resourceName: cap.resourceName,
|
|
1574
|
+
capabilities: cap.capabilities,
|
|
1575
|
+
description: cap.description.length > 100 ? cap.description.substring(0, 100) + '...' : cap.description,
|
|
1576
|
+
complexity: cap.complexity,
|
|
1577
|
+
confidence: cap.confidence,
|
|
1578
|
+
analyzedAt: cap.analyzedAt
|
|
1579
|
+
})),
|
|
1580
|
+
totalCount: count,
|
|
1581
|
+
returnedCount: capabilities.length,
|
|
1582
|
+
limit
|
|
1583
|
+
},
|
|
1584
|
+
message: `Retrieved ${capabilities.length} capabilities (${count} total)`,
|
|
1585
|
+
clientInstructions: {
|
|
1586
|
+
behavior: 'Display capability list with IDs prominently visible for user reference',
|
|
1587
|
+
requirement: 'Each capability must show: ID, resource name, main capabilities, and description',
|
|
1588
|
+
format: 'List format with ID clearly labeled (e.g., "ID: abc123") so users can reference specific capabilities',
|
|
1589
|
+
prohibit: 'Do not hide or omit capability IDs from the display - users need them for get operations'
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
catch (error) {
|
|
1594
|
+
logger.error('Failed to list capabilities', error, {
|
|
1595
|
+
requestId
|
|
1596
|
+
});
|
|
1597
|
+
return {
|
|
1598
|
+
success: false,
|
|
1599
|
+
operation: 'list',
|
|
1600
|
+
dataType: 'capabilities',
|
|
1601
|
+
error: {
|
|
1602
|
+
message: 'Failed to list capabilities',
|
|
1603
|
+
details: error instanceof Error ? error.message : String(error)
|
|
1604
|
+
}
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Handle capability retrieval (placeholder for future implementation)
|
|
1610
|
+
*/
|
|
1611
|
+
async function handleCapabilityGet(args, logger, requestId, capabilityService) {
|
|
1612
|
+
try {
|
|
1613
|
+
// Validate required parameters
|
|
1614
|
+
if (!args.id) {
|
|
1615
|
+
return {
|
|
1616
|
+
success: false,
|
|
1617
|
+
operation: 'get',
|
|
1618
|
+
dataType: 'capabilities',
|
|
1619
|
+
error: {
|
|
1620
|
+
message: 'Missing required parameter: id',
|
|
1621
|
+
details: 'Specify id to retrieve capability data',
|
|
1622
|
+
example: { id: 'capability-id-example' }
|
|
1623
|
+
}
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
// Get capability by ID
|
|
1627
|
+
const capability = await capabilityService.getCapability(args.id);
|
|
1628
|
+
if (!capability) {
|
|
1629
|
+
logger.warn('Capability not found', {
|
|
1630
|
+
requestId,
|
|
1631
|
+
capabilityId: args.id
|
|
1632
|
+
});
|
|
1633
|
+
return {
|
|
1634
|
+
success: false,
|
|
1635
|
+
operation: 'get',
|
|
1636
|
+
dataType: 'capabilities',
|
|
1637
|
+
error: {
|
|
1638
|
+
message: `Capability not found for ID: ${args.id}`,
|
|
1639
|
+
details: 'Resource capability may not have been scanned yet',
|
|
1640
|
+
suggestion: 'Use scan operation to analyze this resource first'
|
|
1641
|
+
}
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
logger.info('Capability retrieved successfully', {
|
|
1645
|
+
requestId,
|
|
1646
|
+
capabilityId: args.id,
|
|
1647
|
+
resourceName: capability.resourceName,
|
|
1648
|
+
capabilitiesFound: capability.capabilities.length,
|
|
1649
|
+
confidence: capability.confidence
|
|
1650
|
+
});
|
|
1651
|
+
return {
|
|
1652
|
+
success: true,
|
|
1653
|
+
operation: 'get',
|
|
1654
|
+
dataType: 'capabilities',
|
|
1655
|
+
data: capability,
|
|
1656
|
+
message: `Retrieved capability data for ${capability.resourceName} (ID: ${args.id})`,
|
|
1657
|
+
clientInstructions: {
|
|
1658
|
+
behavior: 'Display comprehensive capability details in organized sections',
|
|
1659
|
+
requirement: 'Show resource name, capabilities, providers, complexity, use case, and confidence prominently',
|
|
1660
|
+
format: 'Structured display with clear sections: Resource Info, Capabilities, Technical Details, and Analysis Results',
|
|
1661
|
+
sections: {
|
|
1662
|
+
resourceInfo: 'Resource name and description with use case',
|
|
1663
|
+
capabilities: 'List all capabilities, providers, and abstractions clearly',
|
|
1664
|
+
technicalDetails: 'Complexity level and provider information',
|
|
1665
|
+
analysisResults: 'Confidence score, analysis timestamp, and ID for reference'
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
catch (error) {
|
|
1671
|
+
logger.error('Failed to get capability', error, {
|
|
1672
|
+
requestId,
|
|
1673
|
+
capabilityId: args.id
|
|
1674
|
+
});
|
|
1675
|
+
return {
|
|
1676
|
+
success: false,
|
|
1677
|
+
operation: 'get',
|
|
1678
|
+
dataType: 'capabilities',
|
|
1679
|
+
error: {
|
|
1680
|
+
message: 'Failed to retrieve capability',
|
|
1681
|
+
details: error instanceof Error ? error.message : String(error)
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
/**
|
|
1687
|
+
* Handle capability progress query (check progress of running scan)
|
|
1688
|
+
*/
|
|
1689
|
+
async function handleCapabilityProgress(args, logger, requestId) {
|
|
1690
|
+
try {
|
|
1691
|
+
logger.info('Capability progress query requested', {
|
|
1692
|
+
requestId,
|
|
1693
|
+
sessionId: args.sessionId
|
|
1694
|
+
});
|
|
1695
|
+
// Get session directory first
|
|
1696
|
+
const sessionDir = (0, session_utils_1.getAndValidateSessionDirectory)(args, false);
|
|
1697
|
+
let sessionId = args.sessionId;
|
|
1698
|
+
let sessionFilePath;
|
|
1699
|
+
// If no sessionId provided, auto-discover the latest session
|
|
1700
|
+
if (!sessionId) {
|
|
1701
|
+
logger.info('No sessionId provided, searching for latest session file', { requestId });
|
|
1702
|
+
try {
|
|
1703
|
+
// Check if capability-sessions subdirectory exists
|
|
1704
|
+
const sessionSubDir = path.join(sessionDir, 'capability-sessions');
|
|
1705
|
+
if (!fs.existsSync(sessionSubDir)) {
|
|
1706
|
+
logger.info('No capability-sessions directory found', { requestId, sessionDir });
|
|
1707
|
+
return {
|
|
1708
|
+
success: false,
|
|
1709
|
+
operation: 'progress',
|
|
1710
|
+
dataType: 'capabilities',
|
|
1711
|
+
error: {
|
|
1712
|
+
message: 'No capability scan sessions found',
|
|
1713
|
+
details: 'No capability-sessions directory found in the session directory',
|
|
1714
|
+
help: 'Start a new capability scan with the scan operation first',
|
|
1715
|
+
sessionDirectory: sessionDir
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
// Find all capability scan session files in the subdirectory
|
|
1720
|
+
const sessionFiles = fs.readdirSync(sessionSubDir)
|
|
1721
|
+
.filter(file => file.endsWith('.json'))
|
|
1722
|
+
.map(file => {
|
|
1723
|
+
const filePath = path.join(sessionSubDir, file);
|
|
1724
|
+
const stats = fs.statSync(filePath);
|
|
1725
|
+
return {
|
|
1726
|
+
filename: file,
|
|
1727
|
+
path: filePath,
|
|
1728
|
+
mtime: stats.mtime,
|
|
1729
|
+
sessionId: file.replace('.json', '') // Remove .json extension to get sessionId
|
|
1730
|
+
};
|
|
1731
|
+
})
|
|
1732
|
+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); // Sort by newest first
|
|
1733
|
+
if (sessionFiles.length === 0) {
|
|
1734
|
+
logger.info('No capability scan session files found', { requestId, sessionDir });
|
|
1735
|
+
return {
|
|
1736
|
+
success: false,
|
|
1737
|
+
operation: 'progress',
|
|
1738
|
+
dataType: 'capabilities',
|
|
1739
|
+
error: {
|
|
1740
|
+
message: 'No capability scan sessions found',
|
|
1741
|
+
details: 'No active or recent capability scans found in the session directory',
|
|
1742
|
+
help: 'Start a new capability scan with the scan operation first',
|
|
1743
|
+
sessionDirectory: sessionDir
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
// Use the latest session (first after sorting)
|
|
1748
|
+
const latestSession = sessionFiles[0];
|
|
1749
|
+
sessionId = latestSession.sessionId;
|
|
1750
|
+
sessionFilePath = latestSession.path;
|
|
1751
|
+
logger.info('Using latest session file', {
|
|
1752
|
+
requestId,
|
|
1753
|
+
sessionId,
|
|
1754
|
+
totalSessions: sessionFiles.length,
|
|
1755
|
+
sessionFile: latestSession.filename
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
catch (error) {
|
|
1759
|
+
logger.error('Failed to discover session files', error, { requestId, sessionDir });
|
|
1760
|
+
return {
|
|
1761
|
+
success: false,
|
|
1762
|
+
operation: 'progress',
|
|
1763
|
+
dataType: 'capabilities',
|
|
1764
|
+
error: {
|
|
1765
|
+
message: 'Failed to discover session files',
|
|
1766
|
+
details: error instanceof Error ? error.message : String(error),
|
|
1767
|
+
sessionDirectory: sessionDir
|
|
1768
|
+
}
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
else {
|
|
1773
|
+
// Use provided sessionId - look in capability-sessions subdirectory
|
|
1774
|
+
const sessionSubDir = path.join(sessionDir, 'capability-sessions');
|
|
1775
|
+
sessionFilePath = path.join(sessionSubDir, `${sessionId}.json`);
|
|
1776
|
+
if (!fs.existsSync(sessionFilePath)) {
|
|
1777
|
+
logger.warn('Session file not found for provided sessionId', {
|
|
1778
|
+
requestId,
|
|
1779
|
+
sessionId,
|
|
1780
|
+
filePath: sessionFilePath
|
|
1781
|
+
});
|
|
1782
|
+
return {
|
|
1783
|
+
success: false,
|
|
1784
|
+
operation: 'progress',
|
|
1785
|
+
dataType: 'capabilities',
|
|
1786
|
+
error: {
|
|
1787
|
+
message: 'Session not found',
|
|
1788
|
+
details: `No capability scan found for session: ${sessionId}`,
|
|
1789
|
+
help: 'Use the scan operation to start a new capability scan, or omit sessionId to use the latest session'
|
|
1790
|
+
}
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
// Read and parse session file
|
|
1795
|
+
const sessionData = JSON.parse(fs.readFileSync(sessionFilePath, 'utf8'));
|
|
1796
|
+
// Extract progress information
|
|
1797
|
+
const progress = sessionData.progress;
|
|
1798
|
+
if (!progress) {
|
|
1799
|
+
return {
|
|
1800
|
+
success: true,
|
|
1801
|
+
operation: 'progress',
|
|
1802
|
+
dataType: 'capabilities',
|
|
1803
|
+
sessionId: sessionId,
|
|
1804
|
+
status: 'no-progress-data',
|
|
1805
|
+
message: 'Session exists but no progress tracking is active',
|
|
1806
|
+
currentStep: sessionData.currentStep,
|
|
1807
|
+
startedAt: sessionData.startedAt,
|
|
1808
|
+
lastActivity: sessionData.lastActivity
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
// Build comprehensive progress response
|
|
1812
|
+
const response = {
|
|
1813
|
+
success: true,
|
|
1814
|
+
operation: 'progress',
|
|
1815
|
+
dataType: 'capabilities',
|
|
1816
|
+
sessionId: sessionId,
|
|
1817
|
+
progress: {
|
|
1818
|
+
status: progress.status,
|
|
1819
|
+
current: progress.current,
|
|
1820
|
+
total: progress.total,
|
|
1821
|
+
percentage: progress.percentage,
|
|
1822
|
+
currentResource: progress.currentResource,
|
|
1823
|
+
startedAt: progress.startedAt,
|
|
1824
|
+
lastUpdated: progress.lastUpdated
|
|
1825
|
+
},
|
|
1826
|
+
sessionInfo: {
|
|
1827
|
+
currentStep: sessionData.currentStep,
|
|
1828
|
+
processingMode: sessionData.processingMode,
|
|
1829
|
+
resourceCount: Array.isArray(sessionData.selectedResources)
|
|
1830
|
+
? sessionData.selectedResources.length
|
|
1831
|
+
: (sessionData.selectedResources === 'all' ? 'all resources' : 'unknown'),
|
|
1832
|
+
startedAt: sessionData.startedAt,
|
|
1833
|
+
lastActivity: sessionData.lastActivity
|
|
1834
|
+
}
|
|
1835
|
+
};
|
|
1836
|
+
// Add completion information if scan is done
|
|
1837
|
+
if (progress.status === 'completed') {
|
|
1838
|
+
response.progress.completedAt = progress.completedAt;
|
|
1839
|
+
response.progress.totalProcessingTime = progress.totalProcessingTime;
|
|
1840
|
+
response.message = 'Capability scan completed successfully';
|
|
1841
|
+
}
|
|
1842
|
+
else {
|
|
1843
|
+
response.progress.estimatedTimeRemaining = progress.estimatedTimeRemaining;
|
|
1844
|
+
response.message = `Capability scan in progress: ${progress.current}/${progress.total} resources processed`;
|
|
1845
|
+
}
|
|
1846
|
+
// Add user-friendly display information with client formatting instructions
|
|
1847
|
+
response.display = {
|
|
1848
|
+
summary: progress.status === 'completed'
|
|
1849
|
+
? `✅ Scan complete: processed ${progress.total} resources in ${progress.totalProcessingTime}`
|
|
1850
|
+
: `⏳ Processing: ${progress.current}/${progress.total} (${progress.percentage}%) - ${progress.estimatedTimeRemaining} remaining`,
|
|
1851
|
+
currentResource: progress.currentResource,
|
|
1852
|
+
timeline: {
|
|
1853
|
+
started: progress.startedAt,
|
|
1854
|
+
lastUpdate: progress.lastUpdated,
|
|
1855
|
+
...(progress.completedAt && { completed: progress.completedAt })
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
// Add client instructions for readable formatting
|
|
1859
|
+
response.clientInstructions = {
|
|
1860
|
+
behavior: 'Display capability scan progress in a clean, readable format',
|
|
1861
|
+
requirement: 'Show progress information in separate lines, not as a single condensed line',
|
|
1862
|
+
format: progress.status === 'completed'
|
|
1863
|
+
? 'Completion format: Status, total processed, processing time on separate lines'
|
|
1864
|
+
: 'Progress format: Status line, current resource line, time estimates line, timestamps line',
|
|
1865
|
+
example: progress.status === 'completed'
|
|
1866
|
+
? '✅ Capability Scan Complete\\n📊 Processed: X resources\\n⏰ Processing time: X minutes'
|
|
1867
|
+
: '⏳ Progress: X/Y resources (Z%)\\n📊 Current resource: ResourceName\\n⏰ Est. remaining: X minutes\\n🕒 Started: timestamp\\n🔄 Last updated: timestamp',
|
|
1868
|
+
prohibit: 'Do not display all progress information on a single line'
|
|
1869
|
+
};
|
|
1870
|
+
logger.info('Progress query completed successfully', {
|
|
1871
|
+
requestId,
|
|
1872
|
+
sessionId: args.sessionId,
|
|
1873
|
+
status: progress.status,
|
|
1874
|
+
percentage: progress.percentage
|
|
1875
|
+
});
|
|
1876
|
+
return response;
|
|
1877
|
+
}
|
|
1878
|
+
catch (error) {
|
|
1879
|
+
logger.error('Failed to query capability progress', error, {
|
|
1880
|
+
requestId,
|
|
1881
|
+
sessionId: args.sessionId
|
|
1882
|
+
});
|
|
1883
|
+
return {
|
|
1884
|
+
success: false,
|
|
1885
|
+
operation: 'progress',
|
|
1886
|
+
dataType: 'capabilities',
|
|
1887
|
+
error: {
|
|
1888
|
+
message: 'Failed to query scan progress',
|
|
1889
|
+
details: error instanceof Error ? error.message : String(error)
|
|
1890
|
+
}
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
/**
|
|
1895
|
+
* Handle capability deletion (delete single capability by ID)
|
|
1896
|
+
*/
|
|
1897
|
+
async function handleCapabilityDelete(args, logger, requestId, capabilityService) {
|
|
1898
|
+
try {
|
|
1899
|
+
// Validate required parameters
|
|
1900
|
+
if (!args.id) {
|
|
1901
|
+
return {
|
|
1902
|
+
success: false,
|
|
1903
|
+
operation: 'delete',
|
|
1904
|
+
dataType: 'capabilities',
|
|
1905
|
+
error: {
|
|
1906
|
+
message: 'Missing required parameter: id',
|
|
1907
|
+
details: 'Specify id to delete capability data',
|
|
1908
|
+
example: { id: 'capability-id-example' }
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
// Check if capability exists before deletion
|
|
1913
|
+
const capability = await capabilityService.getCapability(args.id);
|
|
1914
|
+
if (!capability) {
|
|
1915
|
+
logger.warn('Capability not found for deletion', {
|
|
1916
|
+
requestId,
|
|
1917
|
+
capabilityId: args.id
|
|
1918
|
+
});
|
|
1919
|
+
return {
|
|
1920
|
+
success: false,
|
|
1921
|
+
operation: 'delete',
|
|
1922
|
+
dataType: 'capabilities',
|
|
1923
|
+
error: {
|
|
1924
|
+
message: `Capability not found for ID: ${args.id}`,
|
|
1925
|
+
details: 'Cannot delete capability that does not exist'
|
|
1926
|
+
}
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
// Delete capability by ID
|
|
1930
|
+
await capabilityService.deleteCapabilityById(args.id);
|
|
1931
|
+
logger.info('Capability deleted successfully', {
|
|
1932
|
+
requestId,
|
|
1933
|
+
capabilityId: args.id,
|
|
1934
|
+
resourceName: capability.resourceName
|
|
1935
|
+
});
|
|
1936
|
+
return {
|
|
1937
|
+
success: true,
|
|
1938
|
+
operation: 'delete',
|
|
1939
|
+
dataType: 'capabilities',
|
|
1940
|
+
deletedCapability: {
|
|
1941
|
+
id: args.id,
|
|
1942
|
+
resourceName: capability.resourceName
|
|
1943
|
+
},
|
|
1944
|
+
message: `Capability deleted: ${capability.resourceName}`
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
catch (error) {
|
|
1948
|
+
logger.error('Failed to delete capability', error, {
|
|
1949
|
+
requestId,
|
|
1950
|
+
capabilityId: args.id
|
|
1951
|
+
});
|
|
1952
|
+
return {
|
|
1953
|
+
success: false,
|
|
1954
|
+
operation: 'delete',
|
|
1955
|
+
dataType: 'capabilities',
|
|
1956
|
+
error: {
|
|
1957
|
+
message: 'Failed to delete capability',
|
|
1958
|
+
details: error instanceof Error ? error.message : String(error)
|
|
1959
|
+
}
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Handle capability bulk deletion (delete all capabilities)
|
|
1965
|
+
*/
|
|
1966
|
+
async function handleCapabilityDeleteAll(args, logger, requestId, capabilityService) {
|
|
1967
|
+
try {
|
|
1968
|
+
// Get count first to provide feedback (but don't retrieve all data)
|
|
1969
|
+
const totalCount = await capabilityService.getCapabilitiesCount();
|
|
1970
|
+
if (totalCount === 0) {
|
|
1971
|
+
logger.info('No capabilities found to delete', { requestId });
|
|
1972
|
+
return {
|
|
1973
|
+
success: true,
|
|
1974
|
+
operation: 'deleteAll',
|
|
1975
|
+
dataType: 'capabilities',
|
|
1976
|
+
deletedCount: 0,
|
|
1977
|
+
totalCount: 0,
|
|
1978
|
+
message: 'No capabilities found to delete'
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
logger.info('Starting efficient bulk capability deletion', {
|
|
1982
|
+
requestId,
|
|
1983
|
+
totalCapabilities: totalCount,
|
|
1984
|
+
method: 'collection recreation'
|
|
1985
|
+
});
|
|
1986
|
+
// Efficiently delete all capabilities by recreating collection
|
|
1987
|
+
await capabilityService.deleteAllCapabilities();
|
|
1988
|
+
logger.info('Bulk capability deletion completed successfully', {
|
|
1989
|
+
requestId,
|
|
1990
|
+
deleted: totalCount,
|
|
1991
|
+
method: 'collection recreation'
|
|
1992
|
+
});
|
|
1993
|
+
return {
|
|
1994
|
+
success: true,
|
|
1995
|
+
operation: 'deleteAll',
|
|
1996
|
+
dataType: 'capabilities',
|
|
1997
|
+
deletedCount: totalCount,
|
|
1998
|
+
totalCount,
|
|
1999
|
+
errorCount: 0,
|
|
2000
|
+
message: `Successfully deleted all ${totalCount} capabilities`,
|
|
2001
|
+
confirmation: 'All capability data has been permanently removed from the Vector DB',
|
|
2002
|
+
method: 'Efficient collection recreation (no individual record retrieval)'
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
catch (error) {
|
|
2006
|
+
logger.error('Failed to delete all capabilities', error, {
|
|
2007
|
+
requestId
|
|
2008
|
+
});
|
|
2009
|
+
return {
|
|
2010
|
+
success: false,
|
|
2011
|
+
operation: 'deleteAll',
|
|
2012
|
+
dataType: 'capabilities',
|
|
2013
|
+
error: {
|
|
2014
|
+
message: 'Failed to delete all capabilities',
|
|
2015
|
+
details: error instanceof Error ? error.message : String(error)
|
|
2016
|
+
}
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* Handle capability search operation
|
|
2022
|
+
*/
|
|
2023
|
+
async function handleCapabilitySearch(args, logger, requestId, capabilityService) {
|
|
2024
|
+
try {
|
|
2025
|
+
// Validate required search query (stored in id field)
|
|
2026
|
+
if (!args.id || typeof args.id !== 'string' || args.id.trim() === '') {
|
|
2027
|
+
return {
|
|
2028
|
+
success: false,
|
|
2029
|
+
operation: 'search',
|
|
2030
|
+
dataType: 'capabilities',
|
|
2031
|
+
error: {
|
|
2032
|
+
message: 'Search query required',
|
|
2033
|
+
details: 'The id field must contain a search query (e.g., "postgresql database in azure")'
|
|
2034
|
+
}
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
const searchQuery = args.id.trim();
|
|
2038
|
+
const limit = args.limit || 25;
|
|
2039
|
+
logger.info('Searching capabilities', {
|
|
2040
|
+
requestId,
|
|
2041
|
+
query: searchQuery,
|
|
2042
|
+
limit
|
|
2043
|
+
});
|
|
2044
|
+
// Perform the search using existing service
|
|
2045
|
+
const searchResults = await capabilityService.searchCapabilities(searchQuery, { limit });
|
|
2046
|
+
// Format results for user display
|
|
2047
|
+
const formattedResults = searchResults.map((result, index) => ({
|
|
2048
|
+
rank: index + 1,
|
|
2049
|
+
score: Math.round(result.score * 100) / 100, // Round to 2 decimal places
|
|
2050
|
+
id: capabilities_1.CapabilityInferenceEngine.generateCapabilityId(result.data.resourceName),
|
|
2051
|
+
resourceName: result.data.resourceName,
|
|
2052
|
+
capabilities: result.data.capabilities,
|
|
2053
|
+
providers: result.data.providers,
|
|
2054
|
+
complexity: result.data.complexity,
|
|
2055
|
+
description: result.data.description,
|
|
2056
|
+
useCase: result.data.useCase,
|
|
2057
|
+
confidence: result.data.confidence,
|
|
2058
|
+
analyzedAt: result.data.analyzedAt
|
|
2059
|
+
}));
|
|
2060
|
+
logger.info('Capability search completed', {
|
|
2061
|
+
requestId,
|
|
2062
|
+
query: searchQuery,
|
|
2063
|
+
resultsFound: searchResults.length,
|
|
2064
|
+
limit
|
|
2065
|
+
});
|
|
2066
|
+
return {
|
|
2067
|
+
success: true,
|
|
2068
|
+
operation: 'search',
|
|
2069
|
+
dataType: 'capabilities',
|
|
2070
|
+
data: {
|
|
2071
|
+
query: searchQuery,
|
|
2072
|
+
results: formattedResults,
|
|
2073
|
+
resultCount: searchResults.length,
|
|
2074
|
+
limit
|
|
2075
|
+
},
|
|
2076
|
+
message: `Found ${searchResults.length} capabilities matching "${searchQuery}"`,
|
|
2077
|
+
clientInstructions: {
|
|
2078
|
+
behavior: 'Display search results with relevance scores and capability details',
|
|
2079
|
+
sections: {
|
|
2080
|
+
searchSummary: 'Show query and result count prominently',
|
|
2081
|
+
resultsList: 'Display each result with rank, score, resource name, and capabilities',
|
|
2082
|
+
capabilityDetails: 'Include providers, complexity, and description for context',
|
|
2083
|
+
actionGuidance: 'Show IDs for get operations and suggest refinement if needed'
|
|
2084
|
+
},
|
|
2085
|
+
format: 'Ranked list with scores (higher scores = better matches)',
|
|
2086
|
+
emphasize: 'Resource names and main capabilities for easy scanning'
|
|
2087
|
+
}
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
catch (error) {
|
|
2091
|
+
logger.error('Failed to search capabilities', error, {
|
|
2092
|
+
requestId,
|
|
2093
|
+
query: args.id
|
|
2094
|
+
});
|
|
2095
|
+
return {
|
|
2096
|
+
success: false,
|
|
2097
|
+
operation: 'search',
|
|
2098
|
+
dataType: 'capabilities',
|
|
2099
|
+
error: {
|
|
2100
|
+
message: 'Capability search failed',
|
|
2101
|
+
details: error instanceof Error ? error.message : String(error)
|
|
2102
|
+
}
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
369
2106
|
/**
|
|
370
2107
|
* Main tool handler - routes to appropriate data type handler
|
|
371
2108
|
*/
|
|
@@ -399,19 +2136,15 @@ async function handleOrganizationalDataTool(args, _dotAI, logger, requestId) {
|
|
|
399
2136
|
case 'pattern':
|
|
400
2137
|
result = await handlePatternOperation(args.operation, args, logger, requestId);
|
|
401
2138
|
break;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
// break;
|
|
406
|
-
// case 'memory':
|
|
407
|
-
// result = await handleMemoryOperation(args.operation, args, logger, requestId);
|
|
408
|
-
// break;
|
|
2139
|
+
case 'capabilities':
|
|
2140
|
+
result = await handleCapabilitiesOperation(args.operation, args, logger, requestId);
|
|
2141
|
+
break;
|
|
409
2142
|
default:
|
|
410
|
-
throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, `Unsupported data type: ${args.dataType}. Currently supported: pattern`, {
|
|
2143
|
+
throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, `Unsupported data type: ${args.dataType}. Currently supported: pattern, capabilities`, {
|
|
411
2144
|
operation: 'data_type_validation',
|
|
412
2145
|
component: 'OrganizationalDataTool',
|
|
413
2146
|
requestId,
|
|
414
|
-
input: { dataType: args.dataType, supportedTypes: ['pattern'] }
|
|
2147
|
+
input: { dataType: args.dataType, supportedTypes: ['pattern', 'capabilities'] }
|
|
415
2148
|
});
|
|
416
2149
|
}
|
|
417
2150
|
logger.info('Organizational-data tool request completed successfully', {
|