@sparkleideas/shared 3.0.0-alpha.7

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 (96) hide show
  1. package/README.md +323 -0
  2. package/__tests__/hooks/bash-safety.test.ts +289 -0
  3. package/__tests__/hooks/file-organization.test.ts +335 -0
  4. package/__tests__/hooks/git-commit.test.ts +336 -0
  5. package/__tests__/hooks/index.ts +23 -0
  6. package/__tests__/hooks/session-hooks.test.ts +357 -0
  7. package/__tests__/hooks/task-hooks.test.ts +193 -0
  8. package/docs/EVENTS_IMPLEMENTATION_SUMMARY.md +388 -0
  9. package/docs/EVENTS_QUICK_REFERENCE.md +470 -0
  10. package/docs/EVENTS_README.md +352 -0
  11. package/package.json +39 -0
  12. package/src/core/config/defaults.ts +207 -0
  13. package/src/core/config/index.ts +15 -0
  14. package/src/core/config/loader.ts +271 -0
  15. package/src/core/config/schema.ts +188 -0
  16. package/src/core/config/validator.ts +209 -0
  17. package/src/core/event-bus.ts +236 -0
  18. package/src/core/index.ts +22 -0
  19. package/src/core/interfaces/agent.interface.ts +251 -0
  20. package/src/core/interfaces/coordinator.interface.ts +363 -0
  21. package/src/core/interfaces/event.interface.ts +267 -0
  22. package/src/core/interfaces/index.ts +19 -0
  23. package/src/core/interfaces/memory.interface.ts +332 -0
  24. package/src/core/interfaces/task.interface.ts +223 -0
  25. package/src/core/orchestrator/event-coordinator.ts +122 -0
  26. package/src/core/orchestrator/health-monitor.ts +214 -0
  27. package/src/core/orchestrator/index.ts +89 -0
  28. package/src/core/orchestrator/lifecycle-manager.ts +263 -0
  29. package/src/core/orchestrator/session-manager.ts +279 -0
  30. package/src/core/orchestrator/task-manager.ts +317 -0
  31. package/src/events/domain-events.ts +584 -0
  32. package/src/events/event-store.test.ts +387 -0
  33. package/src/events/event-store.ts +588 -0
  34. package/src/events/example-usage.ts +293 -0
  35. package/src/events/index.ts +90 -0
  36. package/src/events/projections.ts +561 -0
  37. package/src/events/state-reconstructor.ts +349 -0
  38. package/src/events.ts +367 -0
  39. package/src/hooks/INTEGRATION.md +658 -0
  40. package/src/hooks/README.md +532 -0
  41. package/src/hooks/example-usage.ts +499 -0
  42. package/src/hooks/executor.ts +379 -0
  43. package/src/hooks/hooks.test.ts +421 -0
  44. package/src/hooks/index.ts +131 -0
  45. package/src/hooks/registry.ts +333 -0
  46. package/src/hooks/safety/bash-safety.ts +604 -0
  47. package/src/hooks/safety/file-organization.ts +473 -0
  48. package/src/hooks/safety/git-commit.ts +623 -0
  49. package/src/hooks/safety/index.ts +46 -0
  50. package/src/hooks/session-hooks.ts +559 -0
  51. package/src/hooks/task-hooks.ts +513 -0
  52. package/src/hooks/types.ts +357 -0
  53. package/src/hooks/verify-exports.test.ts +125 -0
  54. package/src/index.ts +195 -0
  55. package/src/mcp/connection-pool.ts +438 -0
  56. package/src/mcp/index.ts +183 -0
  57. package/src/mcp/server.ts +774 -0
  58. package/src/mcp/session-manager.ts +428 -0
  59. package/src/mcp/tool-registry.ts +566 -0
  60. package/src/mcp/transport/http.ts +557 -0
  61. package/src/mcp/transport/index.ts +294 -0
  62. package/src/mcp/transport/stdio.ts +324 -0
  63. package/src/mcp/transport/websocket.ts +484 -0
  64. package/src/mcp/types.ts +565 -0
  65. package/src/plugin-interface.ts +663 -0
  66. package/src/plugin-loader.ts +638 -0
  67. package/src/plugin-registry.ts +604 -0
  68. package/src/plugins/index.ts +34 -0
  69. package/src/plugins/official/hive-mind-plugin.ts +330 -0
  70. package/src/plugins/official/index.ts +24 -0
  71. package/src/plugins/official/maestro-plugin.ts +508 -0
  72. package/src/plugins/types.ts +108 -0
  73. package/src/resilience/bulkhead.ts +277 -0
  74. package/src/resilience/circuit-breaker.ts +326 -0
  75. package/src/resilience/index.ts +26 -0
  76. package/src/resilience/rate-limiter.ts +420 -0
  77. package/src/resilience/retry.ts +224 -0
  78. package/src/security/index.ts +39 -0
  79. package/src/security/input-validation.ts +265 -0
  80. package/src/security/secure-random.ts +159 -0
  81. package/src/services/index.ts +16 -0
  82. package/src/services/v3-progress.service.ts +505 -0
  83. package/src/types/agent.types.ts +144 -0
  84. package/src/types/index.ts +22 -0
  85. package/src/types/mcp.types.ts +300 -0
  86. package/src/types/memory.types.ts +263 -0
  87. package/src/types/swarm.types.ts +255 -0
  88. package/src/types/task.types.ts +205 -0
  89. package/src/types.ts +367 -0
  90. package/src/utils/secure-logger.d.ts +69 -0
  91. package/src/utils/secure-logger.d.ts.map +1 -0
  92. package/src/utils/secure-logger.js +208 -0
  93. package/src/utils/secure-logger.js.map +1 -0
  94. package/src/utils/secure-logger.ts +257 -0
  95. package/tmp.json +0 -0
  96. package/tsconfig.json +9 -0
@@ -0,0 +1,484 @@
1
+ /**
2
+ * V3 MCP WebSocket Transport
3
+ *
4
+ * Standalone WebSocket transport for MCP communication:
5
+ * - Native WebSocket server without HTTP dependency
6
+ * - Binary message support for efficiency
7
+ * - Heartbeat/ping-pong for connection health
8
+ * - Automatic reconnection handling
9
+ *
10
+ * Performance Targets:
11
+ * - Message latency: <3ms
12
+ * - Connection overhead: <10ms
13
+ */
14
+
15
+ import { EventEmitter } from 'events';
16
+ import { WebSocketServer, WebSocket, RawData } from 'ws';
17
+ import { createServer, Server } from 'http';
18
+ import {
19
+ ITransport,
20
+ TransportType,
21
+ MCPRequest,
22
+ MCPResponse,
23
+ MCPNotification,
24
+ RequestHandler,
25
+ NotificationHandler,
26
+ TransportHealthStatus,
27
+ ILogger,
28
+ AuthConfig,
29
+ } from '../types.js';
30
+
31
+ /**
32
+ * WebSocket Transport Configuration
33
+ */
34
+ export interface WebSocketTransportConfig {
35
+ host: string;
36
+ port: number;
37
+ path?: string;
38
+ maxConnections?: number;
39
+ heartbeatInterval?: number;
40
+ heartbeatTimeout?: number;
41
+ maxMessageSize?: number;
42
+ auth?: AuthConfig;
43
+ enableBinaryMode?: boolean;
44
+ }
45
+
46
+ /**
47
+ * Client connection info
48
+ */
49
+ interface ClientConnection {
50
+ id: string;
51
+ ws: WebSocket;
52
+ createdAt: Date;
53
+ lastActivity: Date;
54
+ messageCount: number;
55
+ isAlive: boolean;
56
+ isAuthenticated: boolean;
57
+ }
58
+
59
+ /**
60
+ * WebSocket Transport Implementation
61
+ */
62
+ export class WebSocketTransport extends EventEmitter implements ITransport {
63
+ public readonly type: TransportType = 'websocket';
64
+
65
+ private requestHandler?: RequestHandler;
66
+ private notificationHandler?: NotificationHandler;
67
+ private server?: Server;
68
+ private wss?: WebSocketServer;
69
+ private clients: Map<string, ClientConnection> = new Map();
70
+ private heartbeatTimer?: NodeJS.Timeout;
71
+ private running = false;
72
+ private connectionCounter = 0;
73
+
74
+ // Statistics
75
+ private messagesReceived = 0;
76
+ private messagesSent = 0;
77
+ private errors = 0;
78
+ private totalConnections = 0;
79
+
80
+ constructor(
81
+ private readonly logger: ILogger,
82
+ private readonly config: WebSocketTransportConfig
83
+ ) {
84
+ super();
85
+ }
86
+
87
+ /**
88
+ * Start the transport
89
+ */
90
+ async start(): Promise<void> {
91
+ if (this.running) {
92
+ throw new Error('WebSocket transport already running');
93
+ }
94
+
95
+ this.logger.info('Starting WebSocket transport', {
96
+ host: this.config.host,
97
+ port: this.config.port,
98
+ path: this.config.path || '/ws',
99
+ });
100
+
101
+ // Create HTTP server for WebSocket upgrade
102
+ this.server = createServer((req, res) => {
103
+ // Simple HTTP response for non-WebSocket requests
104
+ res.writeHead(426, { 'Content-Type': 'text/plain' });
105
+ res.end('Upgrade Required - WebSocket connection expected');
106
+ });
107
+
108
+ // Create WebSocket server
109
+ this.wss = new WebSocketServer({
110
+ server: this.server,
111
+ path: this.config.path || '/ws',
112
+ maxPayload: this.config.maxMessageSize || 10 * 1024 * 1024,
113
+ perMessageDeflate: true, // Enable compression
114
+ });
115
+
116
+ this.setupWebSocketHandlers();
117
+ this.startHeartbeat();
118
+
119
+ // Start server
120
+ await new Promise<void>((resolve, reject) => {
121
+ this.server!.listen(this.config.port, this.config.host, () => {
122
+ resolve();
123
+ });
124
+ this.server!.on('error', reject);
125
+ });
126
+
127
+ this.running = true;
128
+ this.logger.info('WebSocket transport started', {
129
+ url: `ws://${this.config.host}:${this.config.port}${this.config.path || '/ws'}`,
130
+ });
131
+ }
132
+
133
+ /**
134
+ * Stop the transport
135
+ */
136
+ async stop(): Promise<void> {
137
+ if (!this.running) {
138
+ return;
139
+ }
140
+
141
+ this.logger.info('Stopping WebSocket transport');
142
+ this.running = false;
143
+
144
+ this.stopHeartbeat();
145
+
146
+ // Close all client connections
147
+ for (const client of this.clients.values()) {
148
+ try {
149
+ client.ws.close(1000, 'Server shutting down');
150
+ } catch {
151
+ // Ignore errors
152
+ }
153
+ }
154
+ this.clients.clear();
155
+
156
+ // Close WebSocket server
157
+ if (this.wss) {
158
+ this.wss.close();
159
+ this.wss = undefined;
160
+ }
161
+
162
+ // Close HTTP server
163
+ if (this.server) {
164
+ await new Promise<void>((resolve) => {
165
+ this.server!.close(() => resolve());
166
+ });
167
+ this.server = undefined;
168
+ }
169
+
170
+ this.logger.info('WebSocket transport stopped');
171
+ }
172
+
173
+ /**
174
+ * Register request handler
175
+ */
176
+ onRequest(handler: RequestHandler): void {
177
+ this.requestHandler = handler;
178
+ }
179
+
180
+ /**
181
+ * Register notification handler
182
+ */
183
+ onNotification(handler: NotificationHandler): void {
184
+ this.notificationHandler = handler;
185
+ }
186
+
187
+ /**
188
+ * Get health status
189
+ */
190
+ async getHealthStatus(): Promise<TransportHealthStatus> {
191
+ return {
192
+ healthy: this.running,
193
+ metrics: {
194
+ messagesReceived: this.messagesReceived,
195
+ messagesSent: this.messagesSent,
196
+ errors: this.errors,
197
+ activeConnections: this.clients.size,
198
+ totalConnections: this.totalConnections,
199
+ },
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Send notification to all connected clients
205
+ */
206
+ async sendNotification(notification: MCPNotification): Promise<void> {
207
+ const message = this.serializeMessage(notification);
208
+
209
+ for (const client of this.clients.values()) {
210
+ try {
211
+ if (client.ws.readyState === WebSocket.OPEN) {
212
+ client.ws.send(message);
213
+ this.messagesSent++;
214
+ }
215
+ } catch (error) {
216
+ this.logger.error('Failed to send notification', { clientId: client.id, error });
217
+ this.errors++;
218
+ }
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Send notification to specific client
224
+ */
225
+ async sendToClient(clientId: string, notification: MCPNotification): Promise<boolean> {
226
+ const client = this.clients.get(clientId);
227
+ if (!client || client.ws.readyState !== WebSocket.OPEN) {
228
+ return false;
229
+ }
230
+
231
+ try {
232
+ client.ws.send(this.serializeMessage(notification));
233
+ this.messagesSent++;
234
+ return true;
235
+ } catch (error) {
236
+ this.logger.error('Failed to send to client', { clientId, error });
237
+ this.errors++;
238
+ return false;
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Get connected clients
244
+ */
245
+ getClients(): string[] {
246
+ return Array.from(this.clients.keys());
247
+ }
248
+
249
+ /**
250
+ * Get client info
251
+ */
252
+ getClientInfo(clientId: string): ClientConnection | undefined {
253
+ return this.clients.get(clientId);
254
+ }
255
+
256
+ /**
257
+ * Disconnect specific client
258
+ */
259
+ disconnectClient(clientId: string, reason = 'Disconnected by server'): boolean {
260
+ const client = this.clients.get(clientId);
261
+ if (!client) {
262
+ return false;
263
+ }
264
+
265
+ try {
266
+ client.ws.close(1000, reason);
267
+ return true;
268
+ } catch {
269
+ return false;
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Setup WebSocket handlers
275
+ */
276
+ private setupWebSocketHandlers(): void {
277
+ if (!this.wss) return;
278
+
279
+ this.wss.on('connection', (ws, req) => {
280
+ // Check max connections
281
+ if (this.config.maxConnections && this.clients.size >= this.config.maxConnections) {
282
+ this.logger.warn('Max connections reached, rejecting client');
283
+ ws.close(1013, 'Server at capacity');
284
+ return;
285
+ }
286
+
287
+ const clientId = `client-${++this.connectionCounter}`;
288
+ const client: ClientConnection = {
289
+ id: clientId,
290
+ ws,
291
+ createdAt: new Date(),
292
+ lastActivity: new Date(),
293
+ messageCount: 0,
294
+ isAlive: true,
295
+ isAuthenticated: !this.config.auth?.enabled,
296
+ };
297
+
298
+ this.clients.set(clientId, client);
299
+ this.totalConnections++;
300
+
301
+ this.logger.info('Client connected', {
302
+ id: clientId,
303
+ total: this.clients.size,
304
+ });
305
+
306
+ // Setup message handler
307
+ ws.on('message', async (data) => {
308
+ await this.handleMessage(client, data);
309
+ });
310
+
311
+ // Setup pong handler for heartbeat
312
+ ws.on('pong', () => {
313
+ client.isAlive = true;
314
+ });
315
+
316
+ // Setup close handler
317
+ ws.on('close', (code, reason) => {
318
+ this.clients.delete(clientId);
319
+ this.logger.info('Client disconnected', {
320
+ id: clientId,
321
+ code,
322
+ reason: reason.toString(),
323
+ total: this.clients.size,
324
+ });
325
+ this.emit('client:disconnected', clientId);
326
+ });
327
+
328
+ // Setup error handler
329
+ ws.on('error', (error) => {
330
+ this.logger.error('Client error', { id: clientId, error });
331
+ this.errors++;
332
+ this.clients.delete(clientId);
333
+ });
334
+
335
+ this.emit('client:connected', clientId);
336
+ });
337
+ }
338
+
339
+ /**
340
+ * Handle incoming message
341
+ */
342
+ private async handleMessage(client: ClientConnection, data: RawData): Promise<void> {
343
+ client.lastActivity = new Date();
344
+ client.messageCount++;
345
+ this.messagesReceived++;
346
+
347
+ try {
348
+ const message = this.parseMessage(data);
349
+
350
+ // Check authentication for non-authenticated clients
351
+ if (!client.isAuthenticated && this.config.auth?.enabled) {
352
+ if (message.method !== 'authenticate') {
353
+ client.ws.send(this.serializeMessage({
354
+ jsonrpc: '2.0',
355
+ id: message.id || null,
356
+ error: { code: -32001, message: 'Authentication required' },
357
+ } as MCPResponse));
358
+ return;
359
+ }
360
+ }
361
+
362
+ if (message.jsonrpc !== '2.0') {
363
+ client.ws.send(this.serializeMessage({
364
+ jsonrpc: '2.0',
365
+ id: message.id || null,
366
+ error: { code: -32600, message: 'Invalid JSON-RPC version' },
367
+ } as MCPResponse));
368
+ return;
369
+ }
370
+
371
+ if (message.id === undefined) {
372
+ // Notification
373
+ if (this.notificationHandler) {
374
+ await this.notificationHandler(message as MCPNotification);
375
+ }
376
+ } else {
377
+ // Request
378
+ if (!this.requestHandler) {
379
+ client.ws.send(this.serializeMessage({
380
+ jsonrpc: '2.0',
381
+ id: message.id,
382
+ error: { code: -32603, message: 'No request handler' },
383
+ } as MCPResponse));
384
+ return;
385
+ }
386
+
387
+ const startTime = performance.now();
388
+ const response = await this.requestHandler(message as MCPRequest);
389
+ const duration = performance.now() - startTime;
390
+
391
+ this.logger.debug('Request processed', {
392
+ clientId: client.id,
393
+ method: message.method,
394
+ duration: `${duration.toFixed(2)}ms`,
395
+ });
396
+
397
+ client.ws.send(this.serializeMessage(response));
398
+ this.messagesSent++;
399
+ }
400
+ } catch (error) {
401
+ this.errors++;
402
+ this.logger.error('Message handling error', { clientId: client.id, error });
403
+
404
+ try {
405
+ client.ws.send(this.serializeMessage({
406
+ jsonrpc: '2.0',
407
+ id: null,
408
+ error: { code: -32700, message: 'Parse error' },
409
+ } as MCPResponse));
410
+ } catch {
411
+ // Ignore send errors
412
+ }
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Parse incoming message
418
+ */
419
+ private parseMessage(data: RawData): any {
420
+ if (this.config.enableBinaryMode && Buffer.isBuffer(data)) {
421
+ // Could implement binary protocol here
422
+ return JSON.parse(data.toString());
423
+ }
424
+ return JSON.parse(data.toString());
425
+ }
426
+
427
+ /**
428
+ * Serialize outgoing message
429
+ */
430
+ private serializeMessage(message: MCPResponse | MCPNotification): string | Buffer {
431
+ if (this.config.enableBinaryMode) {
432
+ // Could implement binary protocol here
433
+ return JSON.stringify(message);
434
+ }
435
+ return JSON.stringify(message);
436
+ }
437
+
438
+ /**
439
+ * Start heartbeat interval
440
+ */
441
+ private startHeartbeat(): void {
442
+ const interval = this.config.heartbeatInterval || 30000; // 30 seconds
443
+ const timeout = this.config.heartbeatTimeout || 10000; // 10 seconds
444
+
445
+ this.heartbeatTimer = setInterval(() => {
446
+ for (const client of this.clients.values()) {
447
+ if (!client.isAlive) {
448
+ // Client didn't respond to last ping
449
+ this.logger.warn('Client heartbeat timeout', { id: client.id });
450
+ client.ws.terminate();
451
+ this.clients.delete(client.id);
452
+ continue;
453
+ }
454
+
455
+ client.isAlive = false;
456
+ try {
457
+ client.ws.ping();
458
+ } catch {
459
+ // Ignore ping errors
460
+ }
461
+ }
462
+ }, interval);
463
+ }
464
+
465
+ /**
466
+ * Stop heartbeat interval
467
+ */
468
+ private stopHeartbeat(): void {
469
+ if (this.heartbeatTimer) {
470
+ clearInterval(this.heartbeatTimer);
471
+ this.heartbeatTimer = undefined;
472
+ }
473
+ }
474
+ }
475
+
476
+ /**
477
+ * Create WebSocket transport
478
+ */
479
+ export function createWebSocketTransport(
480
+ logger: ILogger,
481
+ config: WebSocketTransportConfig
482
+ ): WebSocketTransport {
483
+ return new WebSocketTransport(logger, config);
484
+ }