@skillsmith/core 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/src/analysis/types.d.ts +2 -0
  3. package/dist/src/analysis/types.d.ts.map +1 -1
  4. package/dist/src/analysis/types.js +13 -1
  5. package/dist/src/analysis/types.js.map +1 -1
  6. package/dist/src/analytics/AnalyticsRepository.d.ts +4 -0
  7. package/dist/src/analytics/AnalyticsRepository.d.ts.map +1 -1
  8. package/dist/src/analytics/AnalyticsRepository.js +26 -44
  9. package/dist/src/analytics/AnalyticsRepository.js.map +1 -1
  10. package/dist/src/analytics/schema.d.ts +1 -1
  11. package/dist/src/analytics/schema.d.ts.map +1 -1
  12. package/dist/src/analytics/schema.js +68 -0
  13. package/dist/src/analytics/schema.js.map +1 -1
  14. package/dist/src/api/client.d.ts +33 -29
  15. package/dist/src/api/client.d.ts.map +1 -1
  16. package/dist/src/api/client.js +15 -10
  17. package/dist/src/api/client.js.map +1 -1
  18. package/dist/src/billing/BillingService.d.ts +139 -0
  19. package/dist/src/billing/BillingService.d.ts.map +1 -0
  20. package/dist/src/billing/BillingService.js +393 -0
  21. package/dist/src/billing/BillingService.js.map +1 -0
  22. package/dist/src/billing/GDPRComplianceService.d.ts +176 -0
  23. package/dist/src/billing/GDPRComplianceService.d.ts.map +1 -0
  24. package/dist/src/billing/GDPRComplianceService.js +361 -0
  25. package/dist/src/billing/GDPRComplianceService.js.map +1 -0
  26. package/dist/src/billing/StripeClient.d.ts +177 -0
  27. package/dist/src/billing/StripeClient.d.ts.map +1 -0
  28. package/dist/src/billing/StripeClient.js +462 -0
  29. package/dist/src/billing/StripeClient.js.map +1 -0
  30. package/dist/src/billing/StripeReconciliationJob.d.ts +95 -0
  31. package/dist/src/billing/StripeReconciliationJob.d.ts.map +1 -0
  32. package/dist/src/billing/StripeReconciliationJob.js +405 -0
  33. package/dist/src/billing/StripeReconciliationJob.js.map +1 -0
  34. package/dist/src/billing/StripeWebhookHandler.d.ts +92 -0
  35. package/dist/src/billing/StripeWebhookHandler.d.ts.map +1 -0
  36. package/dist/src/billing/StripeWebhookHandler.js +409 -0
  37. package/dist/src/billing/StripeWebhookHandler.js.map +1 -0
  38. package/dist/src/billing/index.d.ts +18 -0
  39. package/dist/src/billing/index.d.ts.map +1 -0
  40. package/dist/src/billing/index.js +19 -0
  41. package/dist/src/billing/index.js.map +1 -0
  42. package/dist/src/billing/types.d.ts +266 -0
  43. package/dist/src/billing/types.d.ts.map +1 -0
  44. package/dist/src/billing/types.js +23 -0
  45. package/dist/src/billing/types.js.map +1 -0
  46. package/dist/src/embeddings/hnsw-store.d.ts +568 -0
  47. package/dist/src/embeddings/hnsw-store.d.ts.map +1 -0
  48. package/dist/src/embeddings/hnsw-store.js +805 -0
  49. package/dist/src/embeddings/hnsw-store.js.map +1 -0
  50. package/dist/src/embeddings/index.d.ts +2 -0
  51. package/dist/src/embeddings/index.d.ts.map +1 -1
  52. package/dist/src/embeddings/index.js +2 -0
  53. package/dist/src/embeddings/index.js.map +1 -1
  54. package/dist/src/index.d.ts +1 -0
  55. package/dist/src/index.d.ts.map +1 -1
  56. package/dist/src/index.js +2 -0
  57. package/dist/src/index.js.map +1 -1
  58. package/dist/src/learning/PatternStore.d.ts +457 -0
  59. package/dist/src/learning/PatternStore.d.ts.map +1 -0
  60. package/dist/src/learning/PatternStore.js +893 -0
  61. package/dist/src/learning/PatternStore.js.map +1 -0
  62. package/dist/src/learning/ReasoningBankIntegration.d.ts +403 -0
  63. package/dist/src/learning/ReasoningBankIntegration.d.ts.map +1 -0
  64. package/dist/src/learning/ReasoningBankIntegration.js +627 -0
  65. package/dist/src/learning/ReasoningBankIntegration.js.map +1 -0
  66. package/dist/src/learning/index.d.ts +15 -0
  67. package/dist/src/learning/index.d.ts.map +1 -0
  68. package/dist/src/learning/index.js +15 -0
  69. package/dist/src/learning/index.js.map +1 -0
  70. package/dist/src/routing/SONARouter.d.ts +154 -0
  71. package/dist/src/routing/SONARouter.d.ts.map +1 -0
  72. package/dist/src/routing/SONARouter.js +679 -0
  73. package/dist/src/routing/SONARouter.js.map +1 -0
  74. package/dist/src/routing/index.d.ts +9 -0
  75. package/dist/src/routing/index.d.ts.map +1 -0
  76. package/dist/src/routing/index.js +10 -0
  77. package/dist/src/routing/index.js.map +1 -0
  78. package/dist/src/routing/types.d.ts +331 -0
  79. package/dist/src/routing/types.d.ts.map +1 -0
  80. package/dist/src/routing/types.js +203 -0
  81. package/dist/src/routing/types.js.map +1 -0
  82. package/dist/src/scripts/__tests__/scan-imported-skills.test.js +5 -0
  83. package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
  84. package/dist/src/security/SkillSandbox.d.ts +156 -0
  85. package/dist/src/security/SkillSandbox.d.ts.map +1 -0
  86. package/dist/src/security/SkillSandbox.js +303 -0
  87. package/dist/src/security/SkillSandbox.js.map +1 -0
  88. package/dist/src/security/index.d.ts +3 -1
  89. package/dist/src/security/index.d.ts.map +1 -1
  90. package/dist/src/security/index.js +5 -1
  91. package/dist/src/security/index.js.map +1 -1
  92. package/dist/src/security/rate-limiter/presets.d.ts +12 -0
  93. package/dist/src/security/rate-limiter/presets.d.ts.map +1 -1
  94. package/dist/src/security/rate-limiter/presets.js +12 -0
  95. package/dist/src/security/rate-limiter/presets.js.map +1 -1
  96. package/dist/src/security/sanitization.d.ts +85 -0
  97. package/dist/src/security/sanitization.d.ts.map +1 -1
  98. package/dist/src/security/sanitization.js +133 -0
  99. package/dist/src/security/sanitization.js.map +1 -1
  100. package/dist/src/security/scanner/SecurityScanner.d.ts +23 -0
  101. package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
  102. package/dist/src/security/scanner/SecurityScanner.js +232 -28
  103. package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
  104. package/dist/src/security/scanner/patterns.d.ts +13 -0
  105. package/dist/src/security/scanner/patterns.d.ts.map +1 -1
  106. package/dist/src/security/scanner/patterns.js +51 -0
  107. package/dist/src/security/scanner/patterns.js.map +1 -1
  108. package/dist/src/security/scanner/types.d.ts +13 -1
  109. package/dist/src/security/scanner/types.d.ts.map +1 -1
  110. package/dist/src/security/scanner/weights.d.ts.map +1 -1
  111. package/dist/src/security/scanner/weights.js +1 -0
  112. package/dist/src/security/scanner/weights.js.map +1 -1
  113. package/dist/src/session/SessionManager.d.ts +7 -0
  114. package/dist/src/session/SessionManager.d.ts.map +1 -1
  115. package/dist/src/session/SessionManager.js +117 -10
  116. package/dist/src/session/SessionManager.js.map +1 -1
  117. package/dist/src/sync/SyncEngine.d.ts.map +1 -1
  118. package/dist/src/sync/SyncEngine.js +52 -32
  119. package/dist/src/sync/SyncEngine.js.map +1 -1
  120. package/dist/src/testing/MultiLLMProvider.d.ts +374 -0
  121. package/dist/src/testing/MultiLLMProvider.d.ts.map +1 -0
  122. package/dist/src/testing/MultiLLMProvider.js +720 -0
  123. package/dist/src/testing/MultiLLMProvider.js.map +1 -0
  124. package/dist/src/testing/index.d.ts +8 -0
  125. package/dist/src/testing/index.d.ts.map +1 -0
  126. package/dist/src/testing/index.js +9 -0
  127. package/dist/src/testing/index.js.map +1 -0
  128. package/dist/src/types.d.ts +3 -0
  129. package/dist/src/types.d.ts.map +1 -1
  130. package/dist/tests/SecurityScanner.test.js +337 -1
  131. package/dist/tests/SecurityScanner.test.js.map +1 -1
  132. package/dist/tests/billing/BillingService.test.d.ts +7 -0
  133. package/dist/tests/billing/BillingService.test.d.ts.map +1 -0
  134. package/dist/tests/billing/BillingService.test.js +168 -0
  135. package/dist/tests/billing/BillingService.test.js.map +1 -0
  136. package/dist/tests/billing/GDPRCompliance.test.d.ts +7 -0
  137. package/dist/tests/billing/GDPRCompliance.test.d.ts.map +1 -0
  138. package/dist/tests/billing/GDPRCompliance.test.js +195 -0
  139. package/dist/tests/billing/GDPRCompliance.test.js.map +1 -0
  140. package/dist/tests/billing/StripeReconciliation.test.d.ts +7 -0
  141. package/dist/tests/billing/StripeReconciliation.test.d.ts.map +1 -0
  142. package/dist/tests/billing/StripeReconciliation.test.js +266 -0
  143. package/dist/tests/billing/StripeReconciliation.test.js.map +1 -0
  144. package/dist/tests/billing/stripe-validators.test.d.ts +7 -0
  145. package/dist/tests/billing/stripe-validators.test.d.ts.map +1 -0
  146. package/dist/tests/billing/stripe-validators.test.js +107 -0
  147. package/dist/tests/billing/stripe-validators.test.js.map +1 -0
  148. package/dist/tests/embeddings/hnsw-store.test.d.ts +7 -0
  149. package/dist/tests/embeddings/hnsw-store.test.d.ts.map +1 -0
  150. package/dist/tests/embeddings/hnsw-store.test.js +295 -0
  151. package/dist/tests/embeddings/hnsw-store.test.js.map +1 -0
  152. package/dist/tests/integration/neural/e2e-learning.test.d.ts +17 -0
  153. package/dist/tests/integration/neural/e2e-learning.test.d.ts.map +1 -0
  154. package/dist/tests/integration/neural/e2e-learning.test.js +238 -0
  155. package/dist/tests/integration/neural/e2e-learning.test.js.map +1 -0
  156. package/dist/tests/integration/neural/helpers.d.ts +132 -0
  157. package/dist/tests/integration/neural/helpers.d.ts.map +1 -0
  158. package/dist/tests/integration/neural/helpers.js +287 -0
  159. package/dist/tests/integration/neural/helpers.js.map +1 -0
  160. package/dist/tests/integration/neural/personalization.test.d.ts +21 -0
  161. package/dist/tests/integration/neural/personalization.test.d.ts.map +1 -0
  162. package/dist/tests/integration/neural/personalization.test.js +304 -0
  163. package/dist/tests/integration/neural/personalization.test.js.map +1 -0
  164. package/dist/tests/integration/neural/preference-learner.test.d.ts +23 -0
  165. package/dist/tests/integration/neural/preference-learner.test.d.ts.map +1 -0
  166. package/dist/tests/integration/neural/preference-learner.test.js +289 -0
  167. package/dist/tests/integration/neural/preference-learner.test.js.map +1 -0
  168. package/dist/tests/integration/neural/privacy.test.d.ts +19 -0
  169. package/dist/tests/integration/neural/privacy.test.d.ts.map +1 -0
  170. package/dist/tests/integration/neural/privacy.test.js +249 -0
  171. package/dist/tests/integration/neural/privacy.test.js.map +1 -0
  172. package/dist/tests/integration/neural/setup.d.ts +175 -0
  173. package/dist/tests/integration/neural/setup.d.ts.map +1 -0
  174. package/dist/tests/integration/neural/setup.js +487 -0
  175. package/dist/tests/integration/neural/setup.js.map +1 -0
  176. package/dist/tests/integration/neural/signal-collection.test.d.ts +21 -0
  177. package/dist/tests/integration/neural/signal-collection.test.d.ts.map +1 -0
  178. package/dist/tests/integration/neural/signal-collection.test.js +232 -0
  179. package/dist/tests/integration/neural/signal-collection.test.js.map +1 -0
  180. package/dist/tests/learning/PatternStore.test.d.ts +8 -0
  181. package/dist/tests/learning/PatternStore.test.d.ts.map +1 -0
  182. package/dist/tests/learning/PatternStore.test.js +589 -0
  183. package/dist/tests/learning/PatternStore.test.js.map +1 -0
  184. package/dist/tests/learning/ReasoningBankIntegration.test.d.ts +8 -0
  185. package/dist/tests/learning/ReasoningBankIntegration.test.d.ts.map +1 -0
  186. package/dist/tests/learning/ReasoningBankIntegration.test.js +269 -0
  187. package/dist/tests/learning/ReasoningBankIntegration.test.js.map +1 -0
  188. package/dist/tests/routing/SONARouter.test.d.ts +8 -0
  189. package/dist/tests/routing/SONARouter.test.d.ts.map +1 -0
  190. package/dist/tests/routing/SONARouter.test.js +400 -0
  191. package/dist/tests/routing/SONARouter.test.js.map +1 -0
  192. package/dist/tests/security/ContinuousSecurity.test.js +10 -12
  193. package/dist/tests/security/ContinuousSecurity.test.js.map +1 -1
  194. package/dist/tests/security/SkillSandbox.test.d.ts +8 -0
  195. package/dist/tests/security/SkillSandbox.test.d.ts.map +1 -0
  196. package/dist/tests/security/SkillSandbox.test.js +321 -0
  197. package/dist/tests/security/SkillSandbox.test.js.map +1 -0
  198. package/dist/tests/sync/SyncEngine.test.js +4 -2
  199. package/dist/tests/sync/SyncEngine.test.js.map +1 -1
  200. package/dist/tests/testing/MultiLLMProvider.test.d.ts +14 -0
  201. package/dist/tests/testing/MultiLLMProvider.test.d.ts.map +1 -0
  202. package/dist/tests/testing/MultiLLMProvider.test.js +438 -0
  203. package/dist/tests/testing/MultiLLMProvider.test.js.map +1 -0
  204. package/package.json +16 -3
@@ -0,0 +1,679 @@
1
+ /**
2
+ * @fileoverview SONARouter - Specialized Optimized Network Architecture Router
3
+ * @module @skillsmith/core/routing/SONARouter
4
+ * @see SMI-1521: SONA routing for MCP tool optimization
5
+ *
6
+ * Routes MCP tool requests through an 8-expert MoE (Mixture of Experts)
7
+ * network to optimize tool execution based on accuracy requirements,
8
+ * latency constraints, and load distribution.
9
+ *
10
+ * Features:
11
+ * - O(1) routing decisions with LRU caching
12
+ * - V3 MoE integration with fallback to local scoring
13
+ * - Adaptive load balancing across experts
14
+ * - Health monitoring and circuit breaker patterns
15
+ * - Feature flags for gradual rollout
16
+ * - Prometheus-compatible metrics
17
+ *
18
+ * Performance targets:
19
+ * - 2.8-4.4x speed improvement
20
+ * - <5ms routing overhead (P95)
21
+ * - >60% cache hit rate
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const router = new SONARouter()
26
+ * await router.initialize()
27
+ *
28
+ * const decision = await router.route({
29
+ * requestId: 'req-123',
30
+ * tool: 'search',
31
+ * arguments: { query: 'testing skills' },
32
+ * timestamp: new Date(),
33
+ * })
34
+ *
35
+ * console.log(decision.expertId) // 'accuracy-semantic'
36
+ * console.log(decision.confidence) // 0.92
37
+ * ```
38
+ */
39
+ import { DEFAULT_SONA_CONFIG, TOOL_WEIGHTS } from './types.js';
40
+ // ============================================================================
41
+ // LRU Cache Implementation
42
+ // ============================================================================
43
+ /**
44
+ * Simple LRU cache for routing decisions
45
+ */
46
+ class LRUCache {
47
+ cache;
48
+ maxSize;
49
+ ttlMs;
50
+ constructor(maxSize, ttlMs) {
51
+ this.cache = new Map();
52
+ this.maxSize = maxSize;
53
+ this.ttlMs = ttlMs;
54
+ }
55
+ get(key) {
56
+ const entry = this.cache.get(key);
57
+ if (!entry)
58
+ return null;
59
+ // Check TTL
60
+ if (Date.now() - entry.timestamp > this.ttlMs) {
61
+ this.cache.delete(key);
62
+ return null;
63
+ }
64
+ // Move to end (most recently used)
65
+ this.cache.delete(key);
66
+ this.cache.set(key, entry);
67
+ return entry.value;
68
+ }
69
+ set(key, value) {
70
+ // Remove oldest if at capacity
71
+ if (this.cache.size >= this.maxSize) {
72
+ const firstKey = this.cache.keys().next().value;
73
+ if (firstKey !== undefined) {
74
+ this.cache.delete(firstKey);
75
+ }
76
+ }
77
+ this.cache.set(key, { value, timestamp: Date.now() });
78
+ }
79
+ clear() {
80
+ this.cache.clear();
81
+ }
82
+ get size() {
83
+ return this.cache.size;
84
+ }
85
+ }
86
+ // ============================================================================
87
+ // Metrics Collector
88
+ // ============================================================================
89
+ /**
90
+ * Simple metrics collector for SONA routing
91
+ */
92
+ class MetricsCollector {
93
+ totalRequests = 0;
94
+ requestsByTool = {};
95
+ requestsByExpert = {};
96
+ cacheHits = 0;
97
+ cacheMisses = 0;
98
+ totalRoutingTimeMs = 0;
99
+ totalExecutionTimeMs = 0;
100
+ errorCount = 0;
101
+ errorsByType = {};
102
+ recordRouting(tool, expertId, routingTimeMs, cacheHit) {
103
+ this.totalRequests++;
104
+ this.requestsByTool[tool] = (this.requestsByTool[tool] || 0) + 1;
105
+ this.requestsByExpert[expertId] = (this.requestsByExpert[expertId] || 0) + 1;
106
+ this.totalRoutingTimeMs += routingTimeMs;
107
+ if (cacheHit) {
108
+ this.cacheHits++;
109
+ }
110
+ else {
111
+ this.cacheMisses++;
112
+ }
113
+ }
114
+ recordExecution(executionTimeMs) {
115
+ this.totalExecutionTimeMs += executionTimeMs;
116
+ }
117
+ recordError(errorType) {
118
+ this.errorCount++;
119
+ this.errorsByType[errorType] = (this.errorsByType[errorType] || 0) + 1;
120
+ }
121
+ getMetrics() {
122
+ const totalCache = this.cacheHits + this.cacheMisses;
123
+ const avgRoutingMs = this.totalRequests > 0 ? this.totalRoutingTimeMs / this.totalRequests : 0;
124
+ const avgExecutionMs = this.totalRequests > 0 ? this.totalExecutionTimeMs / this.totalRequests : 0;
125
+ return {
126
+ totalRequests: this.totalRequests,
127
+ requestsByTool: this.requestsByTool,
128
+ requestsByExpert: this.requestsByExpert,
129
+ cache: {
130
+ hits: this.cacheHits,
131
+ misses: this.cacheMisses,
132
+ hitRate: totalCache > 0 ? this.cacheHits / totalCache : 0,
133
+ },
134
+ errors: {
135
+ total: this.errorCount,
136
+ byType: this.errorsByType,
137
+ byExpert: {},
138
+ },
139
+ speedImprovement: {
140
+ baselineMs: 100, // Baseline without SONA
141
+ currentMs: avgRoutingMs + avgExecutionMs,
142
+ improvementRatio: avgRoutingMs + avgExecutionMs > 0 ? 100 / (avgRoutingMs + avgExecutionMs) : 1,
143
+ },
144
+ };
145
+ }
146
+ reset() {
147
+ this.totalRequests = 0;
148
+ this.requestsByTool = {};
149
+ this.requestsByExpert = {};
150
+ this.cacheHits = 0;
151
+ this.cacheMisses = 0;
152
+ this.totalRoutingTimeMs = 0;
153
+ this.totalExecutionTimeMs = 0;
154
+ this.errorCount = 0;
155
+ this.errorsByType = {};
156
+ }
157
+ }
158
+ // ============================================================================
159
+ // Main SONARouter Class
160
+ // ============================================================================
161
+ /**
162
+ * SONARouter routes MCP tool requests through an 8-expert MoE network.
163
+ *
164
+ * The router uses a weighted scoring algorithm to select the optimal expert
165
+ * for each request based on tool-specific weight profiles and real-time
166
+ * expert health/load status.
167
+ *
168
+ * When V3 MoE integration is enabled, it leverages Claude-Flow's neural
169
+ * routing for improved accuracy. Otherwise, it uses the local scoring algorithm.
170
+ */
171
+ export class SONARouter {
172
+ config;
173
+ experts;
174
+ expertStatus;
175
+ cache;
176
+ metrics;
177
+ v3MoE = null;
178
+ v3SONA = null;
179
+ initialized = false;
180
+ healthCheckInterval = null;
181
+ /**
182
+ * Create a new SONARouter instance.
183
+ *
184
+ * @param config - Router configuration
185
+ */
186
+ constructor(config = {}) {
187
+ this.config = {
188
+ ...DEFAULT_SONA_CONFIG,
189
+ ...config,
190
+ fallback: {
191
+ ...DEFAULT_SONA_CONFIG.fallback,
192
+ ...config.fallback,
193
+ },
194
+ };
195
+ // Initialize expert registry
196
+ this.experts = new Map();
197
+ this.expertStatus = new Map();
198
+ for (const expert of this.config.experts) {
199
+ this.experts.set(expert.id, expert);
200
+ this.expertStatus.set(expert.id, this.createInitialStatus(expert.id));
201
+ }
202
+ // Initialize cache and metrics
203
+ this.cache = new LRUCache(this.config.cacheMaxSize, this.config.cacheTtlMs);
204
+ this.metrics = new MetricsCollector();
205
+ }
206
+ // ==========================================================================
207
+ // Initialization
208
+ // ==========================================================================
209
+ /**
210
+ * Initialize the router with V3 MoE integration.
211
+ *
212
+ * Attempts to load V3 MoERouter and SONAOptimizer.
213
+ * Falls back to local scoring if V3 is unavailable.
214
+ */
215
+ async initialize() {
216
+ if (this.initialized) {
217
+ return;
218
+ }
219
+ // Try to load V3 MoE if enabled
220
+ if (this.config.useV3MoE !== false) {
221
+ await this.initializeV3MoE();
222
+ }
223
+ // Start health check loop
224
+ if (this.config.healthCheckIntervalMs > 0) {
225
+ this.startHealthChecks();
226
+ }
227
+ this.initialized = true;
228
+ }
229
+ /**
230
+ * Check if router is initialized
231
+ */
232
+ isInitialized() {
233
+ return this.initialized;
234
+ }
235
+ /**
236
+ * Check if V3 MoE is being used
237
+ */
238
+ isUsingV3MoE() {
239
+ return this.v3MoE !== null;
240
+ }
241
+ /**
242
+ * Shutdown the router and cleanup resources
243
+ */
244
+ async shutdown() {
245
+ if (this.healthCheckInterval) {
246
+ clearInterval(this.healthCheckInterval);
247
+ this.healthCheckInterval = null;
248
+ }
249
+ this.cache.clear();
250
+ this.initialized = false;
251
+ }
252
+ // ==========================================================================
253
+ // Routing
254
+ // ==========================================================================
255
+ /**
256
+ * Route a tool request to the optimal expert.
257
+ *
258
+ * @param request - Tool request to route
259
+ * @returns Routing decision with selected expert and confidence
260
+ */
261
+ async route(request) {
262
+ this.ensureInitialized();
263
+ const startTime = Date.now();
264
+ // Check cache first
265
+ if (this.config.enableCache && request.priority !== 'high') {
266
+ const cacheKey = this.generateCacheKey(request);
267
+ const cached = this.cache.get(cacheKey);
268
+ if (cached) {
269
+ const decision = { ...cached, cacheHit: true };
270
+ this.metrics.recordRouting(request.tool, decision.expertId, Date.now() - startTime, true);
271
+ return decision;
272
+ }
273
+ }
274
+ // Get eligible experts
275
+ const eligible = this.getEligibleExperts(request);
276
+ if (eligible.length === 0) {
277
+ return this.createFallbackDecision(request, 'NO_ELIGIBLE_EXPERTS', startTime);
278
+ }
279
+ // Score and select expert
280
+ const scoredExperts = this.scoreExperts(eligible, request);
281
+ const decision = this.selectBestExpert(scoredExperts, request, startTime);
282
+ // Cache the decision
283
+ if (this.config.enableCache) {
284
+ const cacheKey = this.generateCacheKey(request);
285
+ this.cache.set(cacheKey, decision);
286
+ }
287
+ // Record metrics
288
+ this.metrics.recordRouting(request.tool, decision.expertId, decision.decisionTimeMs, false);
289
+ return decision;
290
+ }
291
+ /**
292
+ * Execute a tool request with SONA routing.
293
+ *
294
+ * Routes the request, executes via the selected expert, and records outcome.
295
+ *
296
+ * @param request - Tool request to execute
297
+ * @param executor - Function to execute the tool with given expert
298
+ * @returns Tool response with execution metadata
299
+ */
300
+ async executeWithRouting(request, executor) {
301
+ const routingStart = Date.now();
302
+ // Route the request
303
+ const decision = await this.route(request);
304
+ const routingTimeMs = Date.now() - routingStart;
305
+ // Execute with selected expert
306
+ const executionStart = Date.now();
307
+ try {
308
+ const data = await executor(decision.expertId, request);
309
+ const executionTimeMs = Date.now() - executionStart;
310
+ // Record success
311
+ this.recordOutcome(request, decision.expertId, true);
312
+ this.metrics.recordExecution(executionTimeMs);
313
+ return {
314
+ requestId: request.requestId,
315
+ success: true,
316
+ data,
317
+ meta: {
318
+ expertId: decision.expertId,
319
+ totalTimeMs: routingTimeMs + executionTimeMs,
320
+ routingTimeMs,
321
+ executionTimeMs,
322
+ cacheHit: decision.cacheHit ?? false,
323
+ usedFallback: decision.expertId === 'direct-fallback',
324
+ },
325
+ };
326
+ }
327
+ catch (error) {
328
+ const executionTimeMs = Date.now() - executionStart;
329
+ // Record failure
330
+ this.recordOutcome(request, decision.expertId, false);
331
+ this.metrics.recordError(error instanceof Error ? error.name : 'UnknownError');
332
+ // Try fallback if enabled
333
+ if (this.config.fallback.enabled && decision.expertId !== 'direct-fallback') {
334
+ try {
335
+ const fallbackData = await executor('direct-fallback', request);
336
+ return {
337
+ requestId: request.requestId,
338
+ success: true,
339
+ data: fallbackData,
340
+ meta: {
341
+ expertId: 'direct-fallback',
342
+ totalTimeMs: Date.now() - routingStart,
343
+ routingTimeMs,
344
+ executionTimeMs: Date.now() - executionStart - executionTimeMs,
345
+ cacheHit: false,
346
+ usedFallback: true,
347
+ },
348
+ };
349
+ }
350
+ catch {
351
+ // Fallback also failed
352
+ }
353
+ }
354
+ return {
355
+ requestId: request.requestId,
356
+ success: false,
357
+ error: {
358
+ code: error instanceof Error ? error.name : 'EXECUTION_ERROR',
359
+ message: error instanceof Error ? error.message : String(error),
360
+ },
361
+ meta: {
362
+ expertId: decision.expertId,
363
+ totalTimeMs: routingTimeMs + executionTimeMs,
364
+ routingTimeMs,
365
+ executionTimeMs,
366
+ cacheHit: decision.cacheHit ?? false,
367
+ usedFallback: false,
368
+ },
369
+ };
370
+ }
371
+ }
372
+ // ==========================================================================
373
+ // Expert Management
374
+ // ==========================================================================
375
+ /**
376
+ * Get current status of all experts
377
+ */
378
+ getExpertStatus() {
379
+ return Array.from(this.expertStatus.values());
380
+ }
381
+ /**
382
+ * Get status of a specific expert
383
+ */
384
+ getExpert(expertId) {
385
+ return this.experts.get(expertId);
386
+ }
387
+ /**
388
+ * Update expert health status
389
+ */
390
+ updateExpertHealth(expertId, state, load) {
391
+ const status = this.expertStatus.get(expertId);
392
+ if (status) {
393
+ status.state = state;
394
+ if (load !== undefined) {
395
+ status.load = load;
396
+ }
397
+ status.lastHealthCheck = new Date();
398
+ }
399
+ }
400
+ // ==========================================================================
401
+ // Metrics
402
+ // ==========================================================================
403
+ /**
404
+ * Get current routing metrics
405
+ */
406
+ getMetrics() {
407
+ return this.metrics.getMetrics();
408
+ }
409
+ /**
410
+ * Reset metrics
411
+ */
412
+ resetMetrics() {
413
+ this.metrics.reset();
414
+ }
415
+ // ==========================================================================
416
+ // Private Methods
417
+ // ==========================================================================
418
+ ensureInitialized() {
419
+ if (!this.initialized) {
420
+ throw new Error('SONARouter not initialized. Call initialize() first.');
421
+ }
422
+ }
423
+ createInitialStatus(expertId) {
424
+ return {
425
+ id: expertId,
426
+ state: 'healthy',
427
+ load: 0,
428
+ activeRequests: 0,
429
+ successRate: 1.0,
430
+ p95LatencyMs: 0,
431
+ lastHealthCheck: new Date(),
432
+ };
433
+ }
434
+ async initializeV3MoE() {
435
+ try {
436
+ // Dynamic import of V3 MoE router
437
+ const moeModule = await import('claude-flow/v3/@claude-flow/cli/dist/src/ruvector/moe-router.js');
438
+ this.v3MoE = moeModule.getMoERouter();
439
+ await this.v3MoE.initialize();
440
+ // Dynamic import of V3 SONA optimizer
441
+ const sonaModule = await import('claude-flow/v3/@claude-flow/cli/dist/src/memory/sona-optimizer.js');
442
+ const sonaOptimizer = await sonaModule.getSONAOptimizer();
443
+ await sonaOptimizer.initialize();
444
+ this.v3SONA = sonaOptimizer;
445
+ console.log('[SONARouter] V3 MoE integration initialized');
446
+ }
447
+ catch {
448
+ // V3 not available, use local routing
449
+ console.log('[SONARouter] V3 MoE not available, using local scoring algorithm');
450
+ this.v3MoE = null;
451
+ this.v3SONA = null;
452
+ }
453
+ }
454
+ startHealthChecks() {
455
+ this.healthCheckInterval = setInterval(() => {
456
+ this.runHealthChecks();
457
+ }, this.config.healthCheckIntervalMs);
458
+ }
459
+ runHealthChecks() {
460
+ // Simple health check - in production would ping actual expert endpoints
461
+ for (const [_expertId, status] of this.expertStatus) {
462
+ // Simulate health based on load
463
+ if (status.load > 0.9) {
464
+ status.state = 'degraded';
465
+ }
466
+ else if (status.load > 0.95) {
467
+ status.state = 'unhealthy';
468
+ }
469
+ else {
470
+ status.state = 'healthy';
471
+ }
472
+ status.lastHealthCheck = new Date();
473
+ }
474
+ }
475
+ generateCacheKey(request) {
476
+ // Cache key based on tool and argument hash
477
+ const argsHash = this.hashObject(request.arguments);
478
+ return `${request.tool}:${argsHash}`;
479
+ }
480
+ hashObject(obj) {
481
+ const str = JSON.stringify(obj, Object.keys(obj).sort());
482
+ let hash = 0;
483
+ for (let i = 0; i < str.length; i++) {
484
+ const char = str.charCodeAt(i);
485
+ hash = (hash << 5) - hash + char;
486
+ hash = hash & hash;
487
+ }
488
+ return hash.toString(36);
489
+ }
490
+ getEligibleExperts(request) {
491
+ const eligible = [];
492
+ for (const [, expert] of this.experts) {
493
+ // Check if expert supports this tool
494
+ if (!expert.capabilities.supportedTools.includes(request.tool)) {
495
+ continue;
496
+ }
497
+ // Check health
498
+ const status = this.expertStatus.get(expert.id);
499
+ if (!status || status.state === 'unhealthy') {
500
+ continue;
501
+ }
502
+ // Check load
503
+ if (status.load >= 0.95) {
504
+ continue;
505
+ }
506
+ eligible.push(expert);
507
+ }
508
+ return eligible;
509
+ }
510
+ scoreExperts(experts, request) {
511
+ const toolWeights = TOOL_WEIGHTS[request.tool];
512
+ const scoredExperts = [];
513
+ for (const expert of experts) {
514
+ const status = this.expertStatus.get(expert.id);
515
+ const scores = this.calculateScores(expert, status, toolWeights, request);
516
+ scoredExperts.push({ expert, scores });
517
+ }
518
+ // Sort by total score descending
519
+ scoredExperts.sort((a, b) => b.scores.totalScore - a.scores.totalScore);
520
+ return scoredExperts;
521
+ }
522
+ calculateScores(expert, status, toolWeights, request) {
523
+ // Accuracy score based on historical performance
524
+ const accuracyScore = expert.capabilities.accuracyScore * (1 - status.load * 0.1);
525
+ // Latency score (normalized, lower is better)
526
+ const latencyBaseline = 200; // ms
527
+ let latencyScore = Math.max(0, 1 - expert.capabilities.avgLatencyMs / latencyBaseline);
528
+ // Apply latency constraint if specified
529
+ if (request.maxLatencyMs && expert.capabilities.avgLatencyMs > request.maxLatencyMs) {
530
+ latencyScore = latencyScore * 0.5; // Heavy penalty
531
+ }
532
+ // Reliability score based on success rate
533
+ const reliabilityScore = status.successRate;
534
+ // Efficiency score (inverse of load)
535
+ const efficiencyScore = 1 - status.load;
536
+ // Calculate weighted total
537
+ let totalScore = toolWeights.accuracy * accuracyScore +
538
+ toolWeights.latency * latencyScore +
539
+ toolWeights.reliability * reliabilityScore +
540
+ toolWeights.efficiency * efficiencyScore;
541
+ // Priority boost for specialized experts
542
+ if (expert.type === 'specialized' && expert.capabilities.supportedTools.length === 1) {
543
+ totalScore = totalScore * 1.1;
544
+ }
545
+ // Priority tiebreaker
546
+ totalScore = totalScore + expert.priority / 10000;
547
+ return {
548
+ accuracyScore,
549
+ latencyScore,
550
+ reliabilityScore,
551
+ efficiencyScore,
552
+ totalScore,
553
+ };
554
+ }
555
+ selectBestExpert(scoredExperts, request, startTime) {
556
+ const selected = scoredExperts[0];
557
+ const alternatives = scoredExperts.slice(1, 4); // Top 3 alternatives
558
+ // Calculate confidence based on score margin
559
+ let confidence;
560
+ if (alternatives.length > 0) {
561
+ const scoreMargin = selected.scores.totalScore - alternatives[0].scores.totalScore;
562
+ confidence = Math.min(1.0, 0.5 + scoreMargin * 2);
563
+ }
564
+ else {
565
+ confidence = 1.0;
566
+ }
567
+ return {
568
+ requestId: request.requestId,
569
+ expertId: selected.expert.id,
570
+ confidence,
571
+ scores: selected.scores,
572
+ alternatives: alternatives.map((alt) => ({
573
+ expertId: alt.expert.id,
574
+ score: alt.scores.totalScore,
575
+ reason: this.generateAlternativeReason(alt.expert, alt.scores),
576
+ })),
577
+ reason: this.generateDecisionReason(selected.expert, selected.scores, request.tool),
578
+ decidedAt: new Date(),
579
+ decisionTimeMs: Date.now() - startTime,
580
+ };
581
+ }
582
+ generateDecisionReason(expert, scores, tool) {
583
+ const toolWeights = TOOL_WEIGHTS[tool];
584
+ const primaryFactor = toolWeights.accuracy >= toolWeights.latency ? 'accuracy' : 'latency';
585
+ return (`Selected ${expert.name} (${expert.type}) for ${tool}: ` +
586
+ `optimized for ${primaryFactor} with score ${scores.totalScore.toFixed(3)}`);
587
+ }
588
+ generateAlternativeReason(expert, scores) {
589
+ return `${expert.name}: score ${scores.totalScore.toFixed(3)}`;
590
+ }
591
+ createFallbackDecision(request, reason, startTime) {
592
+ return {
593
+ requestId: request.requestId,
594
+ expertId: 'direct-fallback',
595
+ confidence: 1.0,
596
+ scores: {
597
+ accuracyScore: 0,
598
+ latencyScore: 0,
599
+ reliabilityScore: 1.0,
600
+ efficiencyScore: 0,
601
+ totalScore: 0,
602
+ },
603
+ alternatives: [],
604
+ reason: `Fallback: ${reason}`,
605
+ decidedAt: new Date(),
606
+ decisionTimeMs: Date.now() - startTime,
607
+ };
608
+ }
609
+ recordOutcome(request, expertId, success) {
610
+ // Update expert status
611
+ const status = this.expertStatus.get(expertId);
612
+ if (status) {
613
+ // Update success rate (rolling average of last 100)
614
+ status.successRate = status.successRate * 0.99 + (success ? 0.01 : 0);
615
+ }
616
+ // Update V3 SONA if available
617
+ if (this.v3SONA) {
618
+ this.v3SONA.processTrajectoryOutcome({
619
+ trajectoryId: request.requestId,
620
+ task: `${request.tool}:${JSON.stringify(request.arguments)}`,
621
+ agent: expertId,
622
+ success,
623
+ });
624
+ }
625
+ // Update V3 MoE weights if available
626
+ if (this.v3MoE) {
627
+ this.v3MoE.updateExpertWeights(expertId, success ? 1.0 : -0.5);
628
+ }
629
+ }
630
+ }
631
+ // ============================================================================
632
+ // Factory Functions
633
+ // ============================================================================
634
+ /**
635
+ * Create and initialize a SONARouter instance
636
+ */
637
+ export async function createSONARouter(config) {
638
+ const router = new SONARouter(config);
639
+ await router.initialize();
640
+ return router;
641
+ }
642
+ /**
643
+ * Check if SONA routing should be used for a request
644
+ */
645
+ export function shouldUseSONARouting(tool, featureFlags, userTier) {
646
+ // Master switch
647
+ if (!featureFlags['sona.enabled']) {
648
+ return false;
649
+ }
650
+ // Tool-specific flag
651
+ const toolFlag = `sona.tools.${tool}`;
652
+ if (!featureFlags[toolFlag]) {
653
+ return false;
654
+ }
655
+ // Tier check
656
+ if (userTier) {
657
+ const tierFlag = `sona.tiers.${userTier}`;
658
+ if (!featureFlags[tierFlag]) {
659
+ return false;
660
+ }
661
+ }
662
+ return true;
663
+ }
664
+ // ============================================================================
665
+ // Type Guards
666
+ // ============================================================================
667
+ /**
668
+ * Check if a routing decision indicates high confidence
669
+ */
670
+ export function isHighConfidenceDecision(decision) {
671
+ return decision.confidence >= 0.8;
672
+ }
673
+ /**
674
+ * Check if a routing decision used fallback
675
+ */
676
+ export function usedFallback(decision) {
677
+ return decision.expertId === 'direct-fallback';
678
+ }
679
+ //# sourceMappingURL=SONARouter.js.map