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.
- package/dist/client.d.ts +99 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +69 -1
- package/dist/client.js.map +1 -1
- package/dist/heartbeat.d.ts +16 -0
- package/dist/heartbeat.d.ts.map +1 -1
- package/dist/heartbeat.js +97 -26
- package/dist/heartbeat.js.map +1 -1
- package/dist/index.js +68 -4
- package/dist/index.js.map +1 -1
- package/dist/tools/index.d.ts +15 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +228 -48
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/tools.test.js +7 -1
- package/dist/tools/tools.test.js.map +1 -1
- package/dist/websocket.d.ts +66 -0
- package/dist/websocket.d.ts.map +1 -0
- package/dist/websocket.js +196 -0
- package/dist/websocket.js.map +1 -0
- package/package.json +4 -1
- package/src/client.ts +190 -2
- package/src/heartbeat.ts +108 -25
- package/src/index.ts +77 -4
- package/src/tools/index.ts +269 -60
- package/src/tools/tools.test.ts +7 -1
- package/src/websocket.ts +237 -0
package/src/websocket.ts
ADDED
|
@@ -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
|
+
}
|