agentic-qe 1.8.3 → 1.9.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/.claude/skills/agentic-jujutsu/SKILL.md +645 -0
- package/.claude/skills/cicd-pipeline-qe-orchestrator/README.md +2 -2
- package/.claude/skills/cicd-pipeline-qe-orchestrator/SKILL.md +6 -6
- package/CHANGELOG.md +595 -0
- package/README.md +117 -16
- package/config/constitution.schema.json +423 -0
- package/config/otel-collector.yaml +234 -0
- package/dist/App.d.ts +5 -0
- package/dist/App.d.ts.map +1 -0
- package/dist/App.js +15 -0
- package/dist/App.js.map +1 -0
- package/dist/cli/commands/constitution.d.ts +34 -0
- package/dist/cli/commands/constitution.d.ts.map +1 -0
- package/dist/cli/commands/constitution.js +679 -0
- package/dist/cli/commands/constitution.js.map +1 -0
- package/dist/cli/commands/init-claude-md-template.js +3 -3
- package/dist/cli/commands/init.d.ts +8 -75
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +22 -2292
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/telemetry.d.ts +36 -0
- package/dist/cli/commands/telemetry.d.ts.map +1 -0
- package/dist/cli/commands/telemetry.js +364 -0
- package/dist/cli/commands/telemetry.js.map +1 -0
- package/dist/cli/index.js +66 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/agents.d.ts +22 -0
- package/dist/cli/init/agents.d.ts.map +1 -0
- package/dist/cli/init/agents.js +522 -0
- package/dist/cli/init/agents.js.map +1 -0
- package/dist/cli/init/bash-wrapper.d.ts +14 -0
- package/dist/cli/init/bash-wrapper.d.ts.map +1 -0
- package/dist/cli/init/bash-wrapper.js +47 -0
- package/dist/cli/init/bash-wrapper.js.map +1 -0
- package/dist/cli/init/claude-config.d.ts +24 -0
- package/dist/cli/init/claude-config.d.ts.map +1 -0
- package/dist/cli/init/claude-config.js +275 -0
- package/dist/cli/init/claude-config.js.map +1 -0
- package/dist/cli/init/claude-md.d.ts +19 -0
- package/dist/cli/init/claude-md.d.ts.map +1 -0
- package/dist/cli/init/claude-md.js +153 -0
- package/dist/cli/init/claude-md.js.map +1 -0
- package/dist/cli/init/commands.d.ts +6 -0
- package/dist/cli/init/commands.d.ts.map +1 -0
- package/dist/cli/init/commands.js +83 -0
- package/dist/cli/init/commands.js.map +1 -0
- package/dist/cli/init/database-init.d.ts +15 -0
- package/dist/cli/init/database-init.d.ts.map +1 -0
- package/dist/cli/init/database-init.js +203 -0
- package/dist/cli/init/database-init.js.map +1 -0
- package/dist/cli/init/directory-structure.d.ts +14 -0
- package/dist/cli/init/directory-structure.d.ts.map +1 -0
- package/dist/cli/init/directory-structure.js +107 -0
- package/dist/cli/init/directory-structure.js.map +1 -0
- package/dist/cli/init/documentation.d.ts +14 -0
- package/dist/cli/init/documentation.d.ts.map +1 -0
- package/dist/cli/init/documentation.js +195 -0
- package/dist/cli/init/documentation.js.map +1 -0
- package/dist/cli/init/fleet-config.d.ts +34 -0
- package/dist/cli/init/fleet-config.d.ts.map +1 -0
- package/dist/cli/init/fleet-config.js +269 -0
- package/dist/cli/init/fleet-config.js.map +1 -0
- package/dist/cli/init/helpers.d.ts +6 -0
- package/dist/cli/init/helpers.d.ts.map +1 -0
- package/dist/cli/init/helpers.js +94 -0
- package/dist/cli/init/helpers.js.map +1 -0
- package/dist/cli/init/index.d.ts +32 -0
- package/dist/cli/init/index.d.ts.map +1 -0
- package/dist/cli/init/index.js +294 -0
- package/dist/cli/init/index.js.map +1 -0
- package/dist/cli/init/skills.d.ts +6 -0
- package/dist/cli/init/skills.d.ts.map +1 -0
- package/dist/cli/init/skills.js +138 -0
- package/dist/cli/init/skills.js.map +1 -0
- package/dist/cli/init/utils/file-utils.d.ts +74 -0
- package/dist/cli/init/utils/file-utils.d.ts.map +1 -0
- package/dist/cli/init/utils/file-utils.js +187 -0
- package/dist/cli/init/utils/file-utils.js.map +1 -0
- package/dist/cli/init/utils/index.d.ts +18 -0
- package/dist/cli/init/utils/index.d.ts.map +1 -0
- package/dist/cli/init/utils/index.js +48 -0
- package/dist/cli/init/utils/index.js.map +1 -0
- package/dist/cli/init/utils/log-utils.d.ts +47 -0
- package/dist/cli/init/utils/log-utils.d.ts.map +1 -0
- package/dist/cli/init/utils/log-utils.js +68 -0
- package/dist/cli/init/utils/log-utils.js.map +1 -0
- package/dist/cli/init/utils/path-utils.d.ts +91 -0
- package/dist/cli/init/utils/path-utils.d.ts.map +1 -0
- package/dist/cli/init/utils/path-utils.js +208 -0
- package/dist/cli/init/utils/path-utils.js.map +1 -0
- package/dist/cli/init/utils/validation-utils.d.ts +44 -0
- package/dist/cli/init/utils/validation-utils.d.ts.map +1 -0
- package/dist/cli/init/utils/validation-utils.js +68 -0
- package/dist/cli/init/utils/validation-utils.js.map +1 -0
- package/dist/cli/init/utils.d.ts +183 -0
- package/dist/cli/init/utils.d.ts.map +1 -0
- package/dist/cli/init/utils.js +354 -0
- package/dist/cli/init/utils.js.map +1 -0
- package/dist/components/Dashboard/Dashboard.d.ts +4 -0
- package/dist/components/Dashboard/Dashboard.d.ts.map +1 -0
- package/dist/components/Dashboard/Dashboard.js +148 -0
- package/dist/components/Dashboard/Dashboard.js.map +1 -0
- package/dist/components/Dashboard/DashboardHeader.d.ts +4 -0
- package/dist/components/Dashboard/DashboardHeader.d.ts.map +1 -0
- package/dist/components/Dashboard/DashboardHeader.js +138 -0
- package/dist/components/Dashboard/DashboardHeader.js.map +1 -0
- package/dist/constitution/evaluators/ast-evaluator.d.ts +42 -0
- package/dist/constitution/evaluators/ast-evaluator.d.ts.map +1 -0
- package/dist/constitution/evaluators/ast-evaluator.js +303 -0
- package/dist/constitution/evaluators/ast-evaluator.js.map +1 -0
- package/dist/constitution/evaluators/base.d.ts +144 -0
- package/dist/constitution/evaluators/base.d.ts.map +1 -0
- package/dist/constitution/evaluators/base.js +144 -0
- package/dist/constitution/evaluators/base.js.map +1 -0
- package/dist/constitution/evaluators/index.d.ts +19 -0
- package/dist/constitution/evaluators/index.d.ts.map +1 -0
- package/dist/constitution/evaluators/index.js +56 -0
- package/dist/constitution/evaluators/index.js.map +1 -0
- package/dist/constitution/evaluators/metric-evaluator.d.ts +59 -0
- package/dist/constitution/evaluators/metric-evaluator.d.ts.map +1 -0
- package/dist/constitution/evaluators/metric-evaluator.js +195 -0
- package/dist/constitution/evaluators/metric-evaluator.js.map +1 -0
- package/dist/constitution/evaluators/pattern-evaluator.d.ts +66 -0
- package/dist/constitution/evaluators/pattern-evaluator.d.ts.map +1 -0
- package/dist/constitution/evaluators/pattern-evaluator.js +221 -0
- package/dist/constitution/evaluators/pattern-evaluator.js.map +1 -0
- package/dist/constitution/evaluators/semantic-evaluator.d.ts +68 -0
- package/dist/constitution/evaluators/semantic-evaluator.d.ts.map +1 -0
- package/dist/constitution/evaluators/semantic-evaluator.js +250 -0
- package/dist/constitution/evaluators/semantic-evaluator.js.map +1 -0
- package/dist/constitution/index.d.ts +105 -0
- package/dist/constitution/index.d.ts.map +1 -0
- package/dist/constitution/index.js +207 -0
- package/dist/constitution/index.js.map +1 -0
- package/dist/constitution/loader.d.ts +141 -0
- package/dist/constitution/loader.d.ts.map +1 -0
- package/dist/constitution/loader.js +515 -0
- package/dist/constitution/loader.js.map +1 -0
- package/dist/constitution/schema.d.ts +409 -0
- package/dist/constitution/schema.d.ts.map +1 -0
- package/dist/constitution/schema.js +71 -0
- package/dist/constitution/schema.js.map +1 -0
- package/dist/contexts/DashboardContext.d.ts +41 -0
- package/dist/contexts/DashboardContext.d.ts.map +1 -0
- package/dist/contexts/DashboardContext.js +187 -0
- package/dist/contexts/DashboardContext.js.map +1 -0
- package/dist/core/memory/MemoryManagerFactory.d.ts +77 -0
- package/dist/core/memory/MemoryManagerFactory.d.ts.map +1 -0
- package/dist/core/memory/MemoryManagerFactory.js +270 -0
- package/dist/core/memory/MemoryManagerFactory.js.map +1 -0
- package/dist/core/memory/SwarmMemoryManager.d.ts +58 -0
- package/dist/core/memory/SwarmMemoryManager.d.ts.map +1 -1
- package/dist/core/memory/SwarmMemoryManager.js +375 -131
- package/dist/core/memory/SwarmMemoryManager.js.map +1 -1
- package/dist/core/memory/index.d.ts +1 -0
- package/dist/core/memory/index.d.ts.map +1 -1
- package/dist/core/memory/index.js +12 -1
- package/dist/core/memory/index.js.map +1 -1
- package/dist/hooks/useKeyboardShortcuts.d.ts +12 -0
- package/dist/hooks/useKeyboardShortcuts.d.ts.map +1 -0
- package/dist/hooks/useKeyboardShortcuts.js +69 -0
- package/dist/hooks/useKeyboardShortcuts.js.map +1 -0
- package/dist/mcp/handlers/memory/memory-backup.js +6 -6
- package/dist/mcp/handlers/memory/memory-backup.js.map +1 -1
- package/dist/mcp/handlers/phase2/Phase2Tools.d.ts.map +1 -1
- package/dist/mcp/handlers/phase2/Phase2Tools.js +4 -2
- package/dist/mcp/handlers/phase2/Phase2Tools.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +4 -38
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/services/AgentRegistry.d.ts.map +1 -1
- package/dist/mcp/services/AgentRegistry.js +4 -4
- package/dist/mcp/services/AgentRegistry.js.map +1 -1
- package/dist/persistence/event-store.d.ts +162 -0
- package/dist/persistence/event-store.d.ts.map +1 -0
- package/dist/persistence/event-store.js +315 -0
- package/dist/persistence/event-store.js.map +1 -0
- package/dist/persistence/index.d.ts +145 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +227 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/metrics-aggregator.d.ts +187 -0
- package/dist/persistence/metrics-aggregator.d.ts.map +1 -0
- package/dist/persistence/metrics-aggregator.js +495 -0
- package/dist/persistence/metrics-aggregator.js.map +1 -0
- package/dist/persistence/reasoning-store.d.ts +178 -0
- package/dist/persistence/reasoning-store.d.ts.map +1 -0
- package/dist/persistence/reasoning-store.js +440 -0
- package/dist/persistence/reasoning-store.js.map +1 -0
- package/dist/persistence/schema.d.ts +181 -0
- package/dist/persistence/schema.d.ts.map +1 -0
- package/dist/persistence/schema.js +186 -0
- package/dist/persistence/schema.js.map +1 -0
- package/dist/telemetry/bootstrap.d.ts +67 -0
- package/dist/telemetry/bootstrap.d.ts.map +1 -0
- package/dist/telemetry/bootstrap.js +320 -0
- package/dist/telemetry/bootstrap.js.map +1 -0
- package/dist/telemetry/index.d.ts +16 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +84 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/instrumentation/agent.d.ts +158 -0
- package/dist/telemetry/instrumentation/agent.d.ts.map +1 -0
- package/dist/telemetry/instrumentation/agent.js +372 -0
- package/dist/telemetry/instrumentation/agent.js.map +1 -0
- package/dist/telemetry/instrumentation/index.d.ts +24 -0
- package/dist/telemetry/instrumentation/index.d.ts.map +1 -0
- package/dist/telemetry/instrumentation/index.js +54 -0
- package/dist/telemetry/instrumentation/index.js.map +1 -0
- package/dist/telemetry/instrumentation/memory.d.ts +313 -0
- package/dist/telemetry/instrumentation/memory.d.ts.map +1 -0
- package/dist/telemetry/instrumentation/memory.js +552 -0
- package/dist/telemetry/instrumentation/memory.js.map +1 -0
- package/dist/telemetry/instrumentation/task.d.ts +146 -0
- package/dist/telemetry/instrumentation/task.d.ts.map +1 -0
- package/dist/telemetry/instrumentation/task.js +305 -0
- package/dist/telemetry/instrumentation/task.js.map +1 -0
- package/dist/telemetry/metrics/agent-metrics.d.ts +109 -0
- package/dist/telemetry/metrics/agent-metrics.d.ts.map +1 -0
- package/dist/telemetry/metrics/agent-metrics.js +213 -0
- package/dist/telemetry/metrics/agent-metrics.js.map +1 -0
- package/dist/telemetry/metrics/collectors/cost.d.ts +246 -0
- package/dist/telemetry/metrics/collectors/cost.d.ts.map +1 -0
- package/dist/telemetry/metrics/collectors/cost.js +526 -0
- package/dist/telemetry/metrics/collectors/cost.js.map +1 -0
- package/dist/telemetry/metrics/collectors/pricing-config.d.ts +87 -0
- package/dist/telemetry/metrics/collectors/pricing-config.d.ts.map +1 -0
- package/dist/telemetry/metrics/collectors/pricing-config.js +207 -0
- package/dist/telemetry/metrics/collectors/pricing-config.js.map +1 -0
- package/dist/telemetry/metrics/index.d.ts +54 -0
- package/dist/telemetry/metrics/index.d.ts.map +1 -0
- package/dist/telemetry/metrics/index.js +116 -0
- package/dist/telemetry/metrics/index.js.map +1 -0
- package/dist/telemetry/metrics/quality-metrics.d.ts +171 -0
- package/dist/telemetry/metrics/quality-metrics.d.ts.map +1 -0
- package/dist/telemetry/metrics/quality-metrics.js +259 -0
- package/dist/telemetry/metrics/quality-metrics.js.map +1 -0
- package/dist/telemetry/metrics/system-metrics.d.ts +129 -0
- package/dist/telemetry/metrics/system-metrics.d.ts.map +1 -0
- package/dist/telemetry/metrics/system-metrics.js +380 -0
- package/dist/telemetry/metrics/system-metrics.js.map +1 -0
- package/dist/telemetry/types.d.ts +195 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/telemetry/types.js +90 -0
- package/dist/telemetry/types.js.map +1 -0
- package/dist/visualization/api/RestEndpoints.d.ts +136 -0
- package/dist/visualization/api/RestEndpoints.d.ts.map +1 -0
- package/dist/visualization/api/RestEndpoints.js +428 -0
- package/dist/visualization/api/RestEndpoints.js.map +1 -0
- package/dist/visualization/api/WebSocketServer.d.ts +165 -0
- package/dist/visualization/api/WebSocketServer.d.ts.map +1 -0
- package/dist/visualization/api/WebSocketServer.js +518 -0
- package/dist/visualization/api/WebSocketServer.js.map +1 -0
- package/dist/visualization/core/DataTransformer.d.ts +89 -0
- package/dist/visualization/core/DataTransformer.d.ts.map +1 -0
- package/dist/visualization/core/DataTransformer.js +478 -0
- package/dist/visualization/core/DataTransformer.js.map +1 -0
- package/dist/visualization/index.d.ts +92 -0
- package/dist/visualization/index.d.ts.map +1 -0
- package/dist/visualization/index.js +121 -0
- package/dist/visualization/index.js.map +1 -0
- package/dist/visualization/types.d.ts +148 -0
- package/dist/visualization/types.d.ts.map +1 -0
- package/dist/visualization/types.js +7 -0
- package/dist/visualization/types.js.map +1 -0
- package/dist/voting/consensus.d.ts +87 -0
- package/dist/voting/consensus.d.ts.map +1 -0
- package/dist/voting/consensus.js +568 -0
- package/dist/voting/consensus.js.map +1 -0
- package/dist/voting/index.d.ts +12 -0
- package/dist/voting/index.d.ts.map +1 -0
- package/dist/voting/index.js +36 -0
- package/dist/voting/index.js.map +1 -0
- package/dist/voting/orchestrator.d.ts +65 -0
- package/dist/voting/orchestrator.d.ts.map +1 -0
- package/dist/voting/orchestrator.js +306 -0
- package/dist/voting/orchestrator.js.map +1 -0
- package/dist/voting/panel-assembly.d.ts +54 -0
- package/dist/voting/panel-assembly.d.ts.map +1 -0
- package/dist/voting/panel-assembly.js +192 -0
- package/dist/voting/panel-assembly.js.map +1 -0
- package/dist/voting/protocol.d.ts +119 -0
- package/dist/voting/protocol.d.ts.map +1 -0
- package/dist/voting/protocol.js +18 -0
- package/dist/voting/protocol.js.map +1 -0
- package/dist/voting/types.d.ts +125 -0
- package/dist/voting/types.d.ts.map +1 -0
- package/dist/voting/types.js +7 -0
- package/dist/voting/types.js.map +1 -0
- package/package.json +43 -2
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview WebSocket server for real-time event streaming
|
|
3
|
+
* @module visualization/api/WebSocketServer
|
|
4
|
+
*/
|
|
5
|
+
import { EventEmitter } from 'events';
|
|
6
|
+
import { EventStore } from '../../persistence/event-store';
|
|
7
|
+
import { ReasoningStore } from '../../persistence/reasoning-store';
|
|
8
|
+
import { RealtimeEventMessage, SubscriptionOptions } from '../types';
|
|
9
|
+
import * as http from 'http';
|
|
10
|
+
/**
|
|
11
|
+
* WebSocket server configuration
|
|
12
|
+
*/
|
|
13
|
+
export interface WebSocketServerConfig {
|
|
14
|
+
/** HTTP server to attach to */
|
|
15
|
+
server?: http.Server;
|
|
16
|
+
/** Port to listen on (if no server provided) */
|
|
17
|
+
port?: number;
|
|
18
|
+
/** Heartbeat interval in milliseconds */
|
|
19
|
+
heartbeatInterval?: number;
|
|
20
|
+
/** Client timeout in milliseconds */
|
|
21
|
+
clientTimeout?: number;
|
|
22
|
+
/** Maximum backlog size per client */
|
|
23
|
+
maxBacklogSize?: number;
|
|
24
|
+
/** Enable message compression */
|
|
25
|
+
compression?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* WebSocket server for real-time visualization event streaming
|
|
29
|
+
*
|
|
30
|
+
* Features:
|
|
31
|
+
* - Real-time event streaming with <500ms latency
|
|
32
|
+
* - Client subscription management with filtering
|
|
33
|
+
* - Backpressure handling for high-throughput scenarios
|
|
34
|
+
* - Automatic heartbeat and connection management
|
|
35
|
+
* - Message types: event, reasoning, metrics, heartbeat
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const wsServer = new WebSocketServer(eventStore, reasoningStore, {
|
|
40
|
+
* port: 8080,
|
|
41
|
+
* heartbeatInterval: 30000,
|
|
42
|
+
* maxBacklogSize: 1000
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* await wsServer.start();
|
|
46
|
+
*
|
|
47
|
+
* // Broadcast event to subscribed clients
|
|
48
|
+
* wsServer.broadcastEvent({
|
|
49
|
+
* type: 'event',
|
|
50
|
+
* timestamp: new Date().toISOString(),
|
|
51
|
+
* data: { agent_id: 'test-gen', event_type: 'test_generated' }
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare class WebSocketServer extends EventEmitter {
|
|
56
|
+
private clients;
|
|
57
|
+
private eventStore;
|
|
58
|
+
private reasoningStore;
|
|
59
|
+
private transformer;
|
|
60
|
+
private config;
|
|
61
|
+
private heartbeatTimer?;
|
|
62
|
+
private isRunning;
|
|
63
|
+
private messageQueue;
|
|
64
|
+
private wss?;
|
|
65
|
+
private httpServer?;
|
|
66
|
+
/**
|
|
67
|
+
* Default configuration
|
|
68
|
+
*/
|
|
69
|
+
private static readonly DEFAULT_CONFIG;
|
|
70
|
+
constructor(eventStore: EventStore, reasoningStore: ReasoningStore, config?: WebSocketServerConfig);
|
|
71
|
+
/**
|
|
72
|
+
* Start the WebSocket server
|
|
73
|
+
*/
|
|
74
|
+
start(): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Stop the WebSocket server
|
|
77
|
+
*/
|
|
78
|
+
stop(): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Handle new WebSocket connection
|
|
81
|
+
*/
|
|
82
|
+
private handleConnection;
|
|
83
|
+
/**
|
|
84
|
+
* Send initial data to newly connected client
|
|
85
|
+
*/
|
|
86
|
+
private sendInitialData;
|
|
87
|
+
/**
|
|
88
|
+
* Handle incoming message from client
|
|
89
|
+
*/
|
|
90
|
+
private handleMessage;
|
|
91
|
+
/**
|
|
92
|
+
* Handle client disconnection
|
|
93
|
+
*/
|
|
94
|
+
private handleDisconnect;
|
|
95
|
+
/**
|
|
96
|
+
* Handle client error
|
|
97
|
+
*/
|
|
98
|
+
private handleError;
|
|
99
|
+
/**
|
|
100
|
+
* Handle pong response (heartbeat)
|
|
101
|
+
*/
|
|
102
|
+
private handlePong;
|
|
103
|
+
/**
|
|
104
|
+
* Disconnect a client
|
|
105
|
+
*/
|
|
106
|
+
private disconnectClient;
|
|
107
|
+
/**
|
|
108
|
+
* Update client subscription
|
|
109
|
+
*/
|
|
110
|
+
private updateSubscription;
|
|
111
|
+
/**
|
|
112
|
+
* Remove client subscription
|
|
113
|
+
*/
|
|
114
|
+
private removeSubscription;
|
|
115
|
+
/**
|
|
116
|
+
* Broadcast event to all subscribed clients
|
|
117
|
+
* @param message - Event message to broadcast
|
|
118
|
+
*/
|
|
119
|
+
broadcastEvent(message: RealtimeEventMessage): void;
|
|
120
|
+
/**
|
|
121
|
+
* Send message to specific client with backpressure handling
|
|
122
|
+
*/
|
|
123
|
+
private sendMessage;
|
|
124
|
+
/**
|
|
125
|
+
* Flush message queue for a client
|
|
126
|
+
*/
|
|
127
|
+
private flushMessageQueue;
|
|
128
|
+
/**
|
|
129
|
+
* Check if message should be sent to client based on subscriptions
|
|
130
|
+
*/
|
|
131
|
+
private shouldSendToClient;
|
|
132
|
+
/**
|
|
133
|
+
* Start heartbeat timer
|
|
134
|
+
*/
|
|
135
|
+
private startHeartbeat;
|
|
136
|
+
/**
|
|
137
|
+
* Stop heartbeat timer
|
|
138
|
+
*/
|
|
139
|
+
private stopHeartbeat;
|
|
140
|
+
/**
|
|
141
|
+
* Generate unique client ID
|
|
142
|
+
*/
|
|
143
|
+
private generateClientId;
|
|
144
|
+
/**
|
|
145
|
+
* Parse subscription options from URL query parameters
|
|
146
|
+
*/
|
|
147
|
+
private parseSubscriptionOptions;
|
|
148
|
+
/**
|
|
149
|
+
* Get server statistics
|
|
150
|
+
*/
|
|
151
|
+
getStatistics(): {
|
|
152
|
+
isRunning: boolean;
|
|
153
|
+
connectedClients: number;
|
|
154
|
+
totalMessagesSent: number;
|
|
155
|
+
averageQueueSize: number;
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Get connected clients
|
|
159
|
+
*/
|
|
160
|
+
getConnectedClients(): Array<{
|
|
161
|
+
id: string;
|
|
162
|
+
subscriptions: SubscriptionOptions;
|
|
163
|
+
}>;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=WebSocketServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebSocketServer.d.ts","sourceRoot":"","sources":["../../../src/visualization/api/WebSocketServer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAEnE,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AACrE,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAa7B;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,+BAA+B;IAC/B,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC;IACrB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qCAAqC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sCAAsC;IACtC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iCAAiC;IACjC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,eAAgB,SAAQ,YAAY;IAC/C,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,YAAY,CAAsC;IAC1D,OAAO,CAAC,GAAG,CAAC,CAAW;IACvB,OAAO,CAAC,UAAU,CAAC,CAAc;IAEjC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAOpC;gBAGA,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,cAAc,EAC9B,MAAM,GAAE,qBAA0B;IAYpC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAqD5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAqC3B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAuCxB;;OAEG;IACH,OAAO,CAAC,eAAe;IAmCvB;;OAEG;IACH,OAAO,CAAC,aAAa;IAoBrB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAIxB;;OAEG;IACH,OAAO,CAAC,WAAW;IAKnB;;OAEG;IACH,OAAO,CAAC,UAAU;IAOlB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAkBxB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;;OAGG;IACH,cAAc,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI;IAkBnD;;OAEG;IACH,OAAO,CAAC,WAAW;IAmBnB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA8BzB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA8B1B;;OAEG;IACH,OAAO,CAAC,cAAc;IAiCtB;;OAEG;IACH,OAAO,CAAC,aAAa;IAOrB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAIxB;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAchC;;OAEG;IACH,aAAa,IAAI;QACf,SAAS,EAAE,OAAO,CAAC;QACnB,gBAAgB,EAAE,MAAM,CAAC;QACzB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC;KAC1B;IAcD;;OAEG;IACH,mBAAmB,IAAI,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,mBAAmB,CAAA;KAAE,CAAC;CAMjF"}
|
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview WebSocket server for real-time event streaming
|
|
4
|
+
* @module visualization/api/WebSocketServer
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.WebSocketServer = void 0;
|
|
41
|
+
const events_1 = require("events");
|
|
42
|
+
const DataTransformer_1 = require("../core/DataTransformer");
|
|
43
|
+
const http = __importStar(require("http"));
|
|
44
|
+
const ws_1 = require("ws");
|
|
45
|
+
/**
|
|
46
|
+
* WebSocket server for real-time visualization event streaming
|
|
47
|
+
*
|
|
48
|
+
* Features:
|
|
49
|
+
* - Real-time event streaming with <500ms latency
|
|
50
|
+
* - Client subscription management with filtering
|
|
51
|
+
* - Backpressure handling for high-throughput scenarios
|
|
52
|
+
* - Automatic heartbeat and connection management
|
|
53
|
+
* - Message types: event, reasoning, metrics, heartbeat
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* const wsServer = new WebSocketServer(eventStore, reasoningStore, {
|
|
58
|
+
* port: 8080,
|
|
59
|
+
* heartbeatInterval: 30000,
|
|
60
|
+
* maxBacklogSize: 1000
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* await wsServer.start();
|
|
64
|
+
*
|
|
65
|
+
* // Broadcast event to subscribed clients
|
|
66
|
+
* wsServer.broadcastEvent({
|
|
67
|
+
* type: 'event',
|
|
68
|
+
* timestamp: new Date().toISOString(),
|
|
69
|
+
* data: { agent_id: 'test-gen', event_type: 'test_generated' }
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
class WebSocketServer extends events_1.EventEmitter {
|
|
74
|
+
constructor(eventStore, reasoningStore, config = {}) {
|
|
75
|
+
super();
|
|
76
|
+
this.eventStore = eventStore;
|
|
77
|
+
this.reasoningStore = reasoningStore;
|
|
78
|
+
this.transformer = new DataTransformer_1.DataTransformer(eventStore, reasoningStore);
|
|
79
|
+
this.config = { ...WebSocketServer.DEFAULT_CONFIG, ...config };
|
|
80
|
+
this.clients = new Map();
|
|
81
|
+
this.messageQueue = new Map();
|
|
82
|
+
this.isRunning = false;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Start the WebSocket server
|
|
86
|
+
*/
|
|
87
|
+
async start() {
|
|
88
|
+
if (this.isRunning) {
|
|
89
|
+
throw new Error('WebSocket server is already running');
|
|
90
|
+
}
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
try {
|
|
93
|
+
// Create HTTP server if not provided
|
|
94
|
+
this.httpServer = this.config.server || http.createServer();
|
|
95
|
+
// Create WebSocket server
|
|
96
|
+
this.wss = new ws_1.WebSocketServer({
|
|
97
|
+
server: this.httpServer,
|
|
98
|
+
perMessageDeflate: this.config.compression,
|
|
99
|
+
});
|
|
100
|
+
// Handle WebSocket connections
|
|
101
|
+
this.wss.on('connection', (socket, req) => {
|
|
102
|
+
this.handleConnection(socket, req);
|
|
103
|
+
});
|
|
104
|
+
// Handle WebSocket server errors
|
|
105
|
+
this.wss.on('error', (error) => {
|
|
106
|
+
this.emit('error', { error: error.message, source: 'wss' });
|
|
107
|
+
});
|
|
108
|
+
// Start HTTP server if we created it
|
|
109
|
+
if (!this.config.server) {
|
|
110
|
+
this.httpServer.listen(this.config.port, () => {
|
|
111
|
+
console.log(`WebSocket server listening on port ${this.config.port}`);
|
|
112
|
+
this.startHeartbeat();
|
|
113
|
+
this.isRunning = true;
|
|
114
|
+
this.emit('started', { port: this.config.port });
|
|
115
|
+
resolve();
|
|
116
|
+
});
|
|
117
|
+
this.httpServer.on('error', (error) => {
|
|
118
|
+
this.emit('error', { error: error.message, source: 'http' });
|
|
119
|
+
reject(error);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// Server already listening, just start heartbeat
|
|
124
|
+
this.startHeartbeat();
|
|
125
|
+
this.isRunning = true;
|
|
126
|
+
this.emit('started', { port: this.config.port });
|
|
127
|
+
resolve();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
reject(error);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Stop the WebSocket server
|
|
137
|
+
*/
|
|
138
|
+
async stop() {
|
|
139
|
+
if (!this.isRunning) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.stopHeartbeat();
|
|
143
|
+
// Close all client connections
|
|
144
|
+
for (const client of this.clients.values()) {
|
|
145
|
+
this.disconnectClient(client.id, 'Server shutting down');
|
|
146
|
+
}
|
|
147
|
+
// Close WebSocket server
|
|
148
|
+
return new Promise((resolve) => {
|
|
149
|
+
if (this.wss) {
|
|
150
|
+
this.wss.close(() => {
|
|
151
|
+
// Close HTTP server if we created it
|
|
152
|
+
if (this.httpServer && !this.config.server) {
|
|
153
|
+
this.httpServer.close(() => {
|
|
154
|
+
this.isRunning = false;
|
|
155
|
+
this.emit('stopped');
|
|
156
|
+
resolve();
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
this.isRunning = false;
|
|
161
|
+
this.emit('stopped');
|
|
162
|
+
resolve();
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
this.isRunning = false;
|
|
168
|
+
this.emit('stopped');
|
|
169
|
+
resolve();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Handle new WebSocket connection
|
|
175
|
+
*/
|
|
176
|
+
handleConnection(socket, request) {
|
|
177
|
+
const clientId = this.generateClientId();
|
|
178
|
+
const client = {
|
|
179
|
+
id: clientId,
|
|
180
|
+
socket,
|
|
181
|
+
subscriptions: this.parseSubscriptionOptions(request.url),
|
|
182
|
+
lastHeartbeat: Date.now(),
|
|
183
|
+
};
|
|
184
|
+
this.clients.set(clientId, client);
|
|
185
|
+
this.messageQueue.set(clientId, []);
|
|
186
|
+
// Setup WebSocket event handlers
|
|
187
|
+
socket.on('message', (data) => {
|
|
188
|
+
try {
|
|
189
|
+
this.handleMessage(clientId, data);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
this.emit('error', { clientId, error, source: 'message_handler' });
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
socket.on('close', () => {
|
|
196
|
+
this.handleDisconnect(clientId);
|
|
197
|
+
});
|
|
198
|
+
socket.on('error', (error) => {
|
|
199
|
+
this.handleError(clientId, error);
|
|
200
|
+
});
|
|
201
|
+
socket.on('pong', () => {
|
|
202
|
+
this.handlePong(clientId);
|
|
203
|
+
});
|
|
204
|
+
this.emit('client_connected', { clientId, subscriptions: client.subscriptions });
|
|
205
|
+
// Send initial data based on subscriptions
|
|
206
|
+
this.sendInitialData(client);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Send initial data to newly connected client
|
|
210
|
+
*/
|
|
211
|
+
sendInitialData(client) {
|
|
212
|
+
const { session_id, agent_id, since } = client.subscriptions;
|
|
213
|
+
// Send recent events
|
|
214
|
+
if (session_id) {
|
|
215
|
+
const events = this.eventStore.getEventsBySession(session_id);
|
|
216
|
+
const filteredEvents = since
|
|
217
|
+
? events.filter(e => e.timestamp >= since)
|
|
218
|
+
: events.slice(-50); // Last 50 events
|
|
219
|
+
for (const event of filteredEvents) {
|
|
220
|
+
this.sendMessage(client.id, {
|
|
221
|
+
type: 'event',
|
|
222
|
+
timestamp: event.timestamp,
|
|
223
|
+
data: event,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Send reasoning chains
|
|
228
|
+
if (session_id) {
|
|
229
|
+
const chains = this.reasoningStore.getChainsBySession(session_id, { limit: 10 });
|
|
230
|
+
for (const chain of chains) {
|
|
231
|
+
const tree = this.transformer.buildReasoningTree(chain.id);
|
|
232
|
+
if (tree) {
|
|
233
|
+
this.sendMessage(client.id, {
|
|
234
|
+
type: 'reasoning',
|
|
235
|
+
timestamp: chain.created_at,
|
|
236
|
+
data: tree,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Handle incoming message from client
|
|
244
|
+
*/
|
|
245
|
+
handleMessage(clientId, data) {
|
|
246
|
+
try {
|
|
247
|
+
const message = JSON.parse(data.toString());
|
|
248
|
+
if (message.type === 'subscribe') {
|
|
249
|
+
this.updateSubscription(clientId, message.options);
|
|
250
|
+
}
|
|
251
|
+
else if (message.type === 'unsubscribe') {
|
|
252
|
+
this.removeSubscription(clientId);
|
|
253
|
+
}
|
|
254
|
+
else if (message.type === 'ping') {
|
|
255
|
+
this.sendMessage(clientId, {
|
|
256
|
+
type: 'heartbeat',
|
|
257
|
+
timestamp: new Date().toISOString(),
|
|
258
|
+
data: { status: 'pong' },
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
this.emit('error', { clientId, error });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Handle client disconnection
|
|
268
|
+
*/
|
|
269
|
+
handleDisconnect(clientId) {
|
|
270
|
+
this.disconnectClient(clientId, 'Client disconnected');
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Handle client error
|
|
274
|
+
*/
|
|
275
|
+
handleError(clientId, error) {
|
|
276
|
+
this.emit('error', { clientId, error });
|
|
277
|
+
this.disconnectClient(clientId, `Error: ${error.message}`);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Handle pong response (heartbeat)
|
|
281
|
+
*/
|
|
282
|
+
handlePong(clientId) {
|
|
283
|
+
const client = this.clients.get(clientId);
|
|
284
|
+
if (client) {
|
|
285
|
+
client.lastHeartbeat = Date.now();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Disconnect a client
|
|
290
|
+
*/
|
|
291
|
+
disconnectClient(clientId, reason) {
|
|
292
|
+
const client = this.clients.get(clientId);
|
|
293
|
+
if (!client)
|
|
294
|
+
return;
|
|
295
|
+
try {
|
|
296
|
+
// Close the WebSocket connection
|
|
297
|
+
if (client.socket.readyState === ws_1.WebSocket.OPEN || client.socket.readyState === ws_1.WebSocket.CONNECTING) {
|
|
298
|
+
client.socket.close(1000, reason);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
this.emit('error', { clientId, error, source: 'disconnect' });
|
|
303
|
+
}
|
|
304
|
+
this.clients.delete(clientId);
|
|
305
|
+
this.messageQueue.delete(clientId);
|
|
306
|
+
this.emit('client_disconnected', { clientId, reason });
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Update client subscription
|
|
310
|
+
*/
|
|
311
|
+
updateSubscription(clientId, options) {
|
|
312
|
+
const client = this.clients.get(clientId);
|
|
313
|
+
if (!client)
|
|
314
|
+
return;
|
|
315
|
+
client.subscriptions = { ...client.subscriptions, ...options };
|
|
316
|
+
this.emit('subscription_updated', { clientId, subscriptions: client.subscriptions });
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Remove client subscription
|
|
320
|
+
*/
|
|
321
|
+
removeSubscription(clientId) {
|
|
322
|
+
const client = this.clients.get(clientId);
|
|
323
|
+
if (!client)
|
|
324
|
+
return;
|
|
325
|
+
client.subscriptions = {};
|
|
326
|
+
this.emit('subscription_removed', { clientId });
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Broadcast event to all subscribed clients
|
|
330
|
+
* @param message - Event message to broadcast
|
|
331
|
+
*/
|
|
332
|
+
broadcastEvent(message) {
|
|
333
|
+
const startTime = Date.now();
|
|
334
|
+
for (const client of this.clients.values()) {
|
|
335
|
+
if (this.shouldSendToClient(client, message)) {
|
|
336
|
+
this.sendMessage(client.id, message);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const latency = Date.now() - startTime;
|
|
340
|
+
this.emit('broadcast_complete', { latency, clientCount: this.clients.size });
|
|
341
|
+
// Emit warning if latency exceeds 500ms threshold
|
|
342
|
+
if (latency > 500) {
|
|
343
|
+
this.emit('latency_warning', { latency, threshold: 500 });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Send message to specific client with backpressure handling
|
|
348
|
+
*/
|
|
349
|
+
sendMessage(clientId, message) {
|
|
350
|
+
const client = this.clients.get(clientId);
|
|
351
|
+
if (!client)
|
|
352
|
+
return;
|
|
353
|
+
const queue = this.messageQueue.get(clientId);
|
|
354
|
+
// Check backlog size
|
|
355
|
+
if (queue.length >= this.config.maxBacklogSize) {
|
|
356
|
+
// Drop oldest message (FIFO)
|
|
357
|
+
queue.shift();
|
|
358
|
+
this.emit('backpressure', { clientId, queueSize: queue.length });
|
|
359
|
+
}
|
|
360
|
+
queue.push(message);
|
|
361
|
+
// Attempt to flush queue
|
|
362
|
+
this.flushMessageQueue(clientId);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Flush message queue for a client
|
|
366
|
+
*/
|
|
367
|
+
flushMessageQueue(clientId) {
|
|
368
|
+
const client = this.clients.get(clientId);
|
|
369
|
+
const queue = this.messageQueue.get(clientId);
|
|
370
|
+
if (!client || !queue)
|
|
371
|
+
return;
|
|
372
|
+
while (queue.length > 0) {
|
|
373
|
+
const message = queue[0];
|
|
374
|
+
// Check if socket is open
|
|
375
|
+
if (client.socket.readyState !== ws_1.WebSocket.OPEN) {
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
// Check for backpressure (buffered data)
|
|
379
|
+
if (client.socket.bufferedAmount > 0) {
|
|
380
|
+
// Socket buffer is full, wait for next flush
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
// Send message
|
|
384
|
+
try {
|
|
385
|
+
client.socket.send(JSON.stringify(message));
|
|
386
|
+
queue.shift();
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
this.emit('send_error', { clientId, error });
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Check if message should be sent to client based on subscriptions
|
|
396
|
+
*/
|
|
397
|
+
shouldSendToClient(client, message) {
|
|
398
|
+
const { session_id, agent_id, event_types, since } = client.subscriptions;
|
|
399
|
+
// Check timestamp filter
|
|
400
|
+
if (since && message.timestamp < since) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
// Check event-specific filters
|
|
404
|
+
if (message.type === 'event') {
|
|
405
|
+
const eventData = message.data;
|
|
406
|
+
if (session_id && eventData.session_id !== session_id) {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
if (agent_id && eventData.agent_id !== agent_id) {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
if (event_types && eventData.event_type && !event_types.includes(eventData.event_type)) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Start heartbeat timer
|
|
420
|
+
*/
|
|
421
|
+
startHeartbeat() {
|
|
422
|
+
this.heartbeatTimer = setInterval(() => {
|
|
423
|
+
const now = Date.now();
|
|
424
|
+
for (const client of this.clients.values()) {
|
|
425
|
+
// Check for timeout
|
|
426
|
+
if (now - client.lastHeartbeat > this.config.clientTimeout) {
|
|
427
|
+
this.disconnectClient(client.id, 'Heartbeat timeout');
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
// Send WebSocket ping frame
|
|
431
|
+
try {
|
|
432
|
+
if (client.socket.readyState === ws_1.WebSocket.OPEN) {
|
|
433
|
+
client.socket.ping();
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
this.emit('error', { clientId: client.id, error, source: 'ping' });
|
|
438
|
+
}
|
|
439
|
+
// Send heartbeat message
|
|
440
|
+
this.sendMessage(client.id, {
|
|
441
|
+
type: 'heartbeat',
|
|
442
|
+
timestamp: new Date().toISOString(),
|
|
443
|
+
data: {
|
|
444
|
+
connected_clients: this.clients.size,
|
|
445
|
+
uptime_ms: now - (client.lastHeartbeat - this.config.heartbeatInterval),
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}, this.config.heartbeatInterval);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Stop heartbeat timer
|
|
453
|
+
*/
|
|
454
|
+
stopHeartbeat() {
|
|
455
|
+
if (this.heartbeatTimer) {
|
|
456
|
+
clearInterval(this.heartbeatTimer);
|
|
457
|
+
this.heartbeatTimer = undefined;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Generate unique client ID
|
|
462
|
+
*/
|
|
463
|
+
generateClientId() {
|
|
464
|
+
return `client-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Parse subscription options from URL query parameters
|
|
468
|
+
*/
|
|
469
|
+
parseSubscriptionOptions(url) {
|
|
470
|
+
if (!url)
|
|
471
|
+
return {};
|
|
472
|
+
const urlObj = new URL(url, 'http://localhost');
|
|
473
|
+
const params = urlObj.searchParams;
|
|
474
|
+
return {
|
|
475
|
+
session_id: params.get('session_id') || undefined,
|
|
476
|
+
agent_id: params.get('agent_id') || undefined,
|
|
477
|
+
event_types: params.get('event_types')?.split(','),
|
|
478
|
+
since: params.get('since') || undefined,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Get server statistics
|
|
483
|
+
*/
|
|
484
|
+
getStatistics() {
|
|
485
|
+
let totalQueueSize = 0;
|
|
486
|
+
for (const queue of this.messageQueue.values()) {
|
|
487
|
+
totalQueueSize += queue.length;
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
isRunning: this.isRunning,
|
|
491
|
+
connectedClients: this.clients.size,
|
|
492
|
+
totalMessagesSent: 0, // Would track in real implementation
|
|
493
|
+
averageQueueSize: this.clients.size > 0 ? totalQueueSize / this.clients.size : 0,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Get connected clients
|
|
498
|
+
*/
|
|
499
|
+
getConnectedClients() {
|
|
500
|
+
return Array.from(this.clients.values()).map(client => ({
|
|
501
|
+
id: client.id,
|
|
502
|
+
subscriptions: client.subscriptions,
|
|
503
|
+
}));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
exports.WebSocketServer = WebSocketServer;
|
|
507
|
+
/**
|
|
508
|
+
* Default configuration
|
|
509
|
+
*/
|
|
510
|
+
WebSocketServer.DEFAULT_CONFIG = {
|
|
511
|
+
server: undefined,
|
|
512
|
+
port: 8080,
|
|
513
|
+
heartbeatInterval: 30000, // 30 seconds
|
|
514
|
+
clientTimeout: 60000, // 60 seconds
|
|
515
|
+
maxBacklogSize: 1000,
|
|
516
|
+
compression: true,
|
|
517
|
+
};
|
|
518
|
+
//# sourceMappingURL=WebSocketServer.js.map
|