agenthub-multiagent-mcp 1.1.5 → 1.3.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.
@@ -0,0 +1,237 @@
1
+ /**
2
+ * WebSocket client for real-time push notifications from AgentHub server.
3
+ *
4
+ * Receives pushed tasks and messages and stores them in a queue
5
+ * for inclusion in tool responses.
6
+ */
7
+
8
+ import WebSocket from "ws";
9
+ import type { Message, PendingTask } from "./client.js";
10
+
11
+ export interface WSMessage {
12
+ type: "task" | "message" | "ping" | "pong" | "ack";
13
+ timestamp: string;
14
+ task?: PendingTask;
15
+ message?: Message;
16
+ data?: unknown;
17
+ }
18
+
19
+ export interface PushedItems {
20
+ tasks: PendingTask[];
21
+ messages: Message[];
22
+ }
23
+
24
+ export class WebSocketClient {
25
+ private ws: WebSocket | null = null;
26
+ private baseUrl: string;
27
+ private apiKey: string;
28
+ private agentId: string | null = null;
29
+ private reconnectAttempts = 0;
30
+ private maxReconnectAttempts = 5;
31
+ private reconnectDelay = 1000; // Start with 1 second
32
+ private reconnectTimer: NodeJS.Timeout | null = null;
33
+ private isConnecting = false;
34
+ private isClosing = false;
35
+
36
+ // Queue for pushed items
37
+ private pushedTasks: PendingTask[] = [];
38
+ private pushedMessages: Message[] = [];
39
+
40
+ constructor(baseUrl: string, apiKey: string) {
41
+ // Convert HTTP URL to WebSocket URL
42
+ this.baseUrl = baseUrl
43
+ .replace(/^https:/, "wss:")
44
+ .replace(/^http:/, "ws:")
45
+ .replace(/\/$/, "");
46
+ this.apiKey = apiKey;
47
+ }
48
+
49
+ /**
50
+ * Connect to WebSocket for a specific agent
51
+ */
52
+ connect(agentId: string): void {
53
+ if (this.isConnecting || (this.ws && this.ws.readyState === WebSocket.OPEN)) {
54
+ return;
55
+ }
56
+
57
+ this.agentId = agentId;
58
+ this.isConnecting = true;
59
+ this.isClosing = false;
60
+
61
+ const url = `${this.baseUrl}/ws/agents/${agentId}?api_key=${encodeURIComponent(this.apiKey)}`;
62
+
63
+ try {
64
+ this.ws = new WebSocket(url);
65
+
66
+ this.ws.on("open", () => {
67
+ this.isConnecting = false;
68
+ this.reconnectAttempts = 0;
69
+ this.reconnectDelay = 1000;
70
+ console.error(`[AgentHub] WebSocket connected for agent: ${agentId}`);
71
+ });
72
+
73
+ this.ws.on("message", (data: WebSocket.RawData) => {
74
+ try {
75
+ const msg = JSON.parse(data.toString()) as WSMessage;
76
+ this.handleMessage(msg);
77
+ } catch (error) {
78
+ console.error("[AgentHub] Failed to parse WebSocket message:", error);
79
+ }
80
+ });
81
+
82
+ this.ws.on("close", () => {
83
+ this.isConnecting = false;
84
+ if (!this.isClosing) {
85
+ console.error("[AgentHub] WebSocket disconnected, will reconnect...");
86
+ this.scheduleReconnect();
87
+ }
88
+ });
89
+
90
+ this.ws.on("error", (error) => {
91
+ this.isConnecting = false;
92
+ console.error("[AgentHub] WebSocket error:", error.message);
93
+ });
94
+ } catch (error) {
95
+ this.isConnecting = false;
96
+ console.error("[AgentHub] Failed to create WebSocket:", error);
97
+ this.scheduleReconnect();
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Handle incoming WebSocket message
103
+ */
104
+ private handleMessage(msg: WSMessage): void {
105
+ switch (msg.type) {
106
+ case "task":
107
+ if (msg.task) {
108
+ console.error(`[AgentHub] Received pushed task: ${msg.task.id}`);
109
+ this.pushedTasks.push(msg.task);
110
+ // Send ack
111
+ this.sendAck(msg.task.id);
112
+ }
113
+ break;
114
+
115
+ case "message":
116
+ if (msg.message) {
117
+ console.error(`[AgentHub] Received pushed message: ${msg.message.id}`);
118
+ this.pushedMessages.push(msg.message);
119
+ // Send ack
120
+ this.sendAck(msg.message.id);
121
+ }
122
+ break;
123
+
124
+ case "ping":
125
+ // Respond with pong
126
+ this.send({ type: "pong", timestamp: new Date().toISOString() });
127
+ break;
128
+
129
+ default:
130
+ // Ignore other message types
131
+ break;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Send acknowledgment for received item
137
+ */
138
+ private sendAck(itemId: string): void {
139
+ this.send({
140
+ type: "ack",
141
+ timestamp: new Date().toISOString(),
142
+ data: { id: itemId },
143
+ });
144
+ }
145
+
146
+ /**
147
+ * Send a message over WebSocket
148
+ */
149
+ private send(msg: Partial<WSMessage>): void {
150
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
151
+ try {
152
+ this.ws.send(JSON.stringify(msg));
153
+ } catch (error) {
154
+ console.error("[AgentHub] Failed to send WebSocket message:", error);
155
+ }
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Schedule reconnection with exponential backoff
161
+ */
162
+ private scheduleReconnect(): void {
163
+ if (this.isClosing || !this.agentId) {
164
+ return;
165
+ }
166
+
167
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
168
+ console.error("[AgentHub] Max reconnect attempts reached, giving up");
169
+ return;
170
+ }
171
+
172
+ if (this.reconnectTimer) {
173
+ clearTimeout(this.reconnectTimer);
174
+ }
175
+
176
+ this.reconnectTimer = setTimeout(() => {
177
+ this.reconnectAttempts++;
178
+ console.error(`[AgentHub] Reconnect attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}`);
179
+ if (this.agentId) {
180
+ this.connect(this.agentId);
181
+ }
182
+ }, this.reconnectDelay);
183
+
184
+ // Exponential backoff with max of 30 seconds
185
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000);
186
+ }
187
+
188
+ /**
189
+ * Get and clear pushed items
190
+ */
191
+ getPushedItems(): PushedItems {
192
+ const items = {
193
+ tasks: [...this.pushedTasks],
194
+ messages: [...this.pushedMessages],
195
+ };
196
+
197
+ // Clear the queues
198
+ this.pushedTasks = [];
199
+ this.pushedMessages = [];
200
+
201
+ return items;
202
+ }
203
+
204
+ /**
205
+ * Check if connected
206
+ */
207
+ isConnected(): boolean {
208
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
209
+ }
210
+
211
+ /**
212
+ * Close the WebSocket connection
213
+ */
214
+ close(): void {
215
+ this.isClosing = true;
216
+
217
+ if (this.reconnectTimer) {
218
+ clearTimeout(this.reconnectTimer);
219
+ this.reconnectTimer = null;
220
+ }
221
+
222
+ if (this.ws) {
223
+ try {
224
+ this.ws.close();
225
+ } catch (error) {
226
+ // Ignore close errors
227
+ }
228
+ this.ws = null;
229
+ }
230
+
231
+ this.agentId = null;
232
+ this.pushedTasks = [];
233
+ this.pushedMessages = [];
234
+
235
+ console.error("[AgentHub] WebSocket closed");
236
+ }
237
+ }