agenthub-multiagent-mcp 1.1.2 → 1.1.4

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/src/e2e.test.ts CHANGED
@@ -1,298 +1,300 @@
1
- /**
2
- * End-to-end multi-agent communication tests
3
- *
4
- * These tests simulate real-world multi-agent scenarios:
5
- * - Multiple agents registering and communicating
6
- * - Channel-based group communication
7
- * - Message status tracking
8
- * - Agent coordination workflows
9
- *
10
- * Requires the AgentHub server to be running on localhost:8787
11
- */
12
-
13
- import { describe, it, expect, beforeAll, afterAll } from "vitest";
14
- import { ApiClient } from "./client.js";
15
-
16
- const TEST_URL = process.env.AGENTHUB_URL || "http://localhost:8787";
17
- const TEST_API_KEY = process.env.AGENTHUB_API_KEY || "test-key-123";
18
-
19
- describe("E2E: Multi-Agent Communication", () => {
20
- let client: ApiClient;
21
- const timestamp = Date.now();
22
-
23
- // Create 3 agents for testing
24
- const agents = {
25
- coordinator: `coordinator-${timestamp}`,
26
- backend: `backend-${timestamp}`,
27
- frontend: `frontend-${timestamp}`,
28
- };
29
-
30
- beforeAll(async () => {
31
- client = new ApiClient(TEST_URL, TEST_API_KEY);
32
-
33
- // Register all agents
34
- await client.registerAgent(agents.coordinator, "Coordinator Agent");
35
- await client.registerAgent(agents.backend, "Backend Developer");
36
- await client.registerAgent(agents.frontend, "Frontend Developer");
37
- });
38
-
39
- afterAll(async () => {
40
- // Cleanup - disconnect all agents
41
- for (const agentId of Object.values(agents)) {
42
- try {
43
- await client.disconnect(agentId);
44
- } catch {
45
- // Ignore cleanup errors
46
- }
47
- }
48
- });
49
-
50
- describe("Scenario: Task Assignment Flow", () => {
51
- let taskMessageId: string;
52
- let responseMessageId: string;
53
-
54
- it("step 1: coordinator assigns task to backend", async () => {
55
- const result = await client.sendMessage({
56
- from_agent: agents.coordinator,
57
- to_agent: agents.backend,
58
- type: "task",
59
- subject: "Implement API endpoint",
60
- body: "Please implement the /users endpoint with CRUD operations",
61
- priority: "high",
62
- });
63
-
64
- expect(result.message_id).toBeDefined();
65
- expect(result.status).toBe("pending");
66
- taskMessageId = result.message_id;
67
- });
68
-
69
- it("step 2: backend receives task in inbox", async () => {
70
- const inbox = await client.getInbox(agents.backend, false);
71
-
72
- expect(inbox.total).toBeGreaterThan(0);
73
- const taskMessage = inbox.messages.find((m) => m.id === taskMessageId);
74
- expect(taskMessage).toBeDefined();
75
- expect(taskMessage?.from_agent).toBe(agents.coordinator);
76
- expect(taskMessage?.subject).toBe("Implement API endpoint");
77
- expect(taskMessage?.type).toBe("task");
78
- expect(taskMessage?.priority).toBe("high");
79
- });
80
-
81
- it("step 3: backend marks task as read", async () => {
82
- // First deliver
83
- await fetch(`${TEST_URL}/messages/${taskMessageId}/status`, {
84
- method: "PATCH",
85
- headers: {
86
- "Content-Type": "application/json",
87
- "X-API-Key": TEST_API_KEY,
88
- },
89
- body: JSON.stringify({ status: "delivered" }),
90
- });
91
-
92
- const result = await client.markRead(taskMessageId);
93
- expect(result.updated).toBe(true);
94
- });
95
-
96
- it("step 4: backend starts work on the task", async () => {
97
- const result = await client.startWork(
98
- agents.backend,
99
- "Implementing /users endpoint",
100
- "API Development"
101
- );
102
-
103
- expect(result.acknowledged).toBe(true);
104
- });
105
-
106
- it("step 5: backend sends completion response", async () => {
107
- const result = await client.reply(
108
- taskMessageId,
109
- agents.backend,
110
- "Task completed. The /users endpoint is now live with full CRUD support."
111
- );
112
-
113
- expect(result.message_id).toBeDefined();
114
- responseMessageId = result.message_id;
115
- });
116
-
117
- it("step 6: coordinator receives completion in inbox", async () => {
118
- const inbox = await client.getInbox(agents.coordinator, false);
119
-
120
- const responseMessage = inbox.messages.find((m) => m.id === responseMessageId);
121
- expect(responseMessage).toBeDefined();
122
- expect(responseMessage?.from_agent).toBe(agents.backend);
123
- expect(responseMessage?.type).toBe("response");
124
- expect(responseMessage?.reply_to).toBe(taskMessageId);
125
- });
126
- });
127
-
128
- describe("Scenario: Broadcast Announcements", () => {
129
- let broadcastId: string;
130
-
131
- it("step 1: coordinator broadcasts announcement", async () => {
132
- const result = await client.sendMessage({
133
- from_agent: agents.coordinator,
134
- broadcast: true,
135
- type: "status",
136
- subject: "Sprint Planning Meeting",
137
- body: "Sprint planning in 30 minutes. Please prepare your updates.",
138
- });
139
-
140
- expect(result.message_id).toBeDefined();
141
- broadcastId = result.message_id;
142
- });
143
-
144
- it("step 2: backend receives broadcast", async () => {
145
- const inbox = await client.getInbox(agents.backend, false);
146
-
147
- const broadcast = inbox.messages.find(
148
- (m) => m.broadcast && m.subject === "Sprint Planning Meeting"
149
- );
150
- expect(broadcast).toBeDefined();
151
- expect(broadcast?.from_agent).toBe(agents.coordinator);
152
- });
153
-
154
- it("step 3: frontend receives broadcast", async () => {
155
- const inbox = await client.getInbox(agents.frontend, false);
156
-
157
- const broadcast = inbox.messages.find(
158
- (m) => m.broadcast && m.subject === "Sprint Planning Meeting"
159
- );
160
- expect(broadcast).toBeDefined();
161
- expect(broadcast?.from_agent).toBe(agents.coordinator);
162
- });
163
-
164
- it("step 4: broadcast is tracked correctly", async () => {
165
- // Note: In this implementation, broadcasters may receive their own broadcasts
166
- // This test verifies the broadcast was delivered to multiple agents
167
- const backendInbox = await client.getInbox(agents.backend, false);
168
- const frontendInbox = await client.getInbox(agents.frontend, false);
169
-
170
- const backendBroadcast = backendInbox.messages.find((m) => m.id === broadcastId);
171
- const frontendBroadcast = frontendInbox.messages.find((m) => m.id === broadcastId);
172
-
173
- expect(backendBroadcast).toBeDefined();
174
- expect(frontendBroadcast).toBeDefined();
175
- expect(backendBroadcast?.broadcast).toBe(true);
176
- expect(frontendBroadcast?.broadcast).toBe(true);
177
- });
178
- });
179
-
180
- describe("Scenario: Agent Status Tracking", () => {
181
- it("all agents should be visible in agent list", async () => {
182
- const result = await client.listAgents();
183
-
184
- const registeredAgents = result.agents.filter((a) =>
185
- Object.values(agents).includes(a.id)
186
- );
187
- expect(registeredAgents.length).toBe(3);
188
- });
189
-
190
- it("backend should show as busy after starting work", async () => {
191
- const agent = await client.getAgent(agents.backend);
192
-
193
- expect(agent.status).toBe("busy");
194
- expect(agent.current_task).toContain("Implementing");
195
- });
196
-
197
- it("frontend should show as online (not busy)", async () => {
198
- const agent = await client.getAgent(agents.frontend);
199
-
200
- expect(agent.status).toBe("online");
201
- });
202
-
203
- it("agents can update their status", async () => {
204
- await client.heartbeat(agents.frontend, "busy");
205
- const agent = await client.getAgent(agents.frontend);
206
-
207
- expect(agent.status).toBe("busy");
208
- });
209
- });
210
-
211
- describe("Scenario: Multi-Agent Q&A", () => {
212
- let questionId: string;
213
- let backendAnswerId: string;
214
- let frontendAnswerId: string;
215
-
216
- it("step 1: coordinator asks question to backend", async () => {
217
- const result = await client.sendMessage({
218
- from_agent: agents.coordinator,
219
- to_agent: agents.backend,
220
- type: "question",
221
- subject: "API Rate Limiting",
222
- body: "What's the current rate limit configuration?",
223
- });
224
-
225
- questionId = result.message_id;
226
- expect(questionId).toBeDefined();
227
- });
228
-
229
- it("step 2: backend answers the question", async () => {
230
- const result = await client.reply(
231
- questionId,
232
- agents.backend,
233
- "Rate limit is set to 100 requests/minute per API key."
234
- );
235
-
236
- backendAnswerId = result.message_id;
237
- expect(backendAnswerId).toBeDefined();
238
- });
239
-
240
- it("step 3: coordinator asks frontend a different question", async () => {
241
- const result = await client.sendMessage({
242
- from_agent: agents.coordinator,
243
- to_agent: agents.frontend,
244
- type: "question",
245
- subject: "UI Framework",
246
- body: "Which UI framework are we using for the dashboard?",
247
- });
248
-
249
- const frontendQuestionId = result.message_id;
250
-
251
- // Frontend answers
252
- const answerResult = await client.reply(
253
- frontendQuestionId,
254
- agents.frontend,
255
- "We're using React with TailwindCSS for the dashboard."
256
- );
257
-
258
- frontendAnswerId = answerResult.message_id;
259
- expect(frontendAnswerId).toBeDefined();
260
- });
261
-
262
- it("step 4: coordinator has received all answers", async () => {
263
- const inbox = await client.getInbox(agents.coordinator, false);
264
-
265
- const backendAnswer = inbox.messages.find((m) => m.id === backendAnswerId);
266
- const frontendAnswer = inbox.messages.find((m) => m.id === frontendAnswerId);
267
-
268
- expect(backendAnswer).toBeDefined();
269
- expect(frontendAnswer).toBeDefined();
270
- expect(backendAnswer?.type).toBe("response");
271
- expect(frontendAnswer?.type).toBe("response");
272
- });
273
- });
274
-
275
- describe("Scenario: Urgent Priority Handling", () => {
276
- it("should handle urgent priority messages", async () => {
277
- const result = await client.sendMessage({
278
- from_agent: agents.coordinator,
279
- to_agent: agents.backend,
280
- type: "task",
281
- subject: "CRITICAL: Production Bug",
282
- body: "There's a critical bug in production. Please investigate immediately.",
283
- priority: "urgent",
284
- });
285
-
286
- expect(result.message_id).toBeDefined();
287
-
288
- // Verify the message has urgent priority
289
- const inbox = await client.getInbox(agents.backend, false);
290
- const urgentMessage = inbox.messages.find(
291
- (m) => m.subject === "CRITICAL: Production Bug"
292
- );
293
-
294
- expect(urgentMessage).toBeDefined();
295
- expect(urgentMessage?.priority).toBe("urgent");
296
- });
297
- });
298
- });
1
+ /**
2
+ * End-to-end multi-agent communication tests
3
+ *
4
+ * These tests simulate real-world multi-agent scenarios:
5
+ * - Multiple agents registering and communicating
6
+ * - Channel-based group communication
7
+ * - Message status tracking
8
+ * - Agent coordination workflows
9
+ *
10
+ * Requires the AgentHub server to be running on localhost:8787
11
+ * Set AGENTHUB_INTEGRATION_TESTS=1 to run these tests
12
+ */
13
+
14
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
15
+ import { ApiClient } from "./client.js";
16
+
17
+ const TEST_URL = process.env.AGENTHUB_URL || "http://localhost:8787";
18
+ const TEST_API_KEY = process.env.AGENTHUB_API_KEY || "test-key-123";
19
+ const RUN_INTEGRATION = process.env.AGENTHUB_INTEGRATION_TESTS === "1";
20
+
21
+ describe.skipIf(!RUN_INTEGRATION)("E2E: Multi-Agent Communication", () => {
22
+ let client: ApiClient;
23
+ const timestamp = Date.now();
24
+
25
+ // Create 3 agents for testing
26
+ const agents = {
27
+ coordinator: `coordinator-${timestamp}`,
28
+ backend: `backend-${timestamp}`,
29
+ frontend: `frontend-${timestamp}`,
30
+ };
31
+
32
+ beforeAll(async () => {
33
+ client = new ApiClient(TEST_URL, TEST_API_KEY);
34
+
35
+ // Register all agents
36
+ await client.registerAgent(agents.coordinator, "Coordinator Agent");
37
+ await client.registerAgent(agents.backend, "Backend Developer");
38
+ await client.registerAgent(agents.frontend, "Frontend Developer");
39
+ });
40
+
41
+ afterAll(async () => {
42
+ // Cleanup - disconnect all agents
43
+ for (const agentId of Object.values(agents)) {
44
+ try {
45
+ await client.disconnect(agentId);
46
+ } catch {
47
+ // Ignore cleanup errors
48
+ }
49
+ }
50
+ });
51
+
52
+ describe("Scenario: Task Assignment Flow", () => {
53
+ let taskMessageId: string;
54
+ let responseMessageId: string;
55
+
56
+ it("step 1: coordinator assigns task to backend", async () => {
57
+ const result = await client.sendMessage({
58
+ from_agent: agents.coordinator,
59
+ to_agent: agents.backend,
60
+ type: "task",
61
+ subject: "Implement API endpoint",
62
+ body: "Please implement the /users endpoint with CRUD operations",
63
+ priority: "high",
64
+ });
65
+
66
+ expect(result.message_id).toBeDefined();
67
+ expect(result.status).toBe("pending");
68
+ taskMessageId = result.message_id;
69
+ });
70
+
71
+ it("step 2: backend receives task in inbox", async () => {
72
+ const inbox = await client.getInbox(agents.backend, false);
73
+
74
+ expect(inbox.total).toBeGreaterThan(0);
75
+ const taskMessage = inbox.messages.find((m) => m.id === taskMessageId);
76
+ expect(taskMessage).toBeDefined();
77
+ expect(taskMessage?.from_agent).toBe(agents.coordinator);
78
+ expect(taskMessage?.subject).toBe("Implement API endpoint");
79
+ expect(taskMessage?.type).toBe("task");
80
+ expect(taskMessage?.priority).toBe("high");
81
+ });
82
+
83
+ it("step 3: backend marks task as read", async () => {
84
+ // First deliver
85
+ await fetch(`${TEST_URL}/messages/${taskMessageId}/status`, {
86
+ method: "PATCH",
87
+ headers: {
88
+ "Content-Type": "application/json",
89
+ "X-API-Key": TEST_API_KEY,
90
+ },
91
+ body: JSON.stringify({ status: "delivered" }),
92
+ });
93
+
94
+ const result = await client.markRead(taskMessageId);
95
+ expect(result.updated).toBe(true);
96
+ });
97
+
98
+ it("step 4: backend starts work on the task", async () => {
99
+ const result = await client.startWork(
100
+ agents.backend,
101
+ "Implementing /users endpoint",
102
+ "API Development"
103
+ );
104
+
105
+ expect(result.acknowledged).toBe(true);
106
+ });
107
+
108
+ it("step 5: backend sends completion response", async () => {
109
+ const result = await client.reply(
110
+ taskMessageId,
111
+ agents.backend,
112
+ "Task completed. The /users endpoint is now live with full CRUD support."
113
+ );
114
+
115
+ expect(result.message_id).toBeDefined();
116
+ responseMessageId = result.message_id;
117
+ });
118
+
119
+ it("step 6: coordinator receives completion in inbox", async () => {
120
+ const inbox = await client.getInbox(agents.coordinator, false);
121
+
122
+ const responseMessage = inbox.messages.find((m) => m.id === responseMessageId);
123
+ expect(responseMessage).toBeDefined();
124
+ expect(responseMessage?.from_agent).toBe(agents.backend);
125
+ expect(responseMessage?.type).toBe("response");
126
+ expect(responseMessage?.reply_to).toBe(taskMessageId);
127
+ });
128
+ });
129
+
130
+ describe("Scenario: Broadcast Announcements", () => {
131
+ let broadcastId: string;
132
+
133
+ it("step 1: coordinator broadcasts announcement", async () => {
134
+ const result = await client.sendMessage({
135
+ from_agent: agents.coordinator,
136
+ broadcast: true,
137
+ type: "status",
138
+ subject: "Sprint Planning Meeting",
139
+ body: "Sprint planning in 30 minutes. Please prepare your updates.",
140
+ });
141
+
142
+ expect(result.message_id).toBeDefined();
143
+ broadcastId = result.message_id;
144
+ });
145
+
146
+ it("step 2: backend receives broadcast", async () => {
147
+ const inbox = await client.getInbox(agents.backend, false);
148
+
149
+ const broadcast = inbox.messages.find(
150
+ (m) => m.broadcast && m.subject === "Sprint Planning Meeting"
151
+ );
152
+ expect(broadcast).toBeDefined();
153
+ expect(broadcast?.from_agent).toBe(agents.coordinator);
154
+ });
155
+
156
+ it("step 3: frontend receives broadcast", async () => {
157
+ const inbox = await client.getInbox(agents.frontend, false);
158
+
159
+ const broadcast = inbox.messages.find(
160
+ (m) => m.broadcast && m.subject === "Sprint Planning Meeting"
161
+ );
162
+ expect(broadcast).toBeDefined();
163
+ expect(broadcast?.from_agent).toBe(agents.coordinator);
164
+ });
165
+
166
+ it("step 4: broadcast is tracked correctly", async () => {
167
+ // Note: In this implementation, broadcasters may receive their own broadcasts
168
+ // This test verifies the broadcast was delivered to multiple agents
169
+ const backendInbox = await client.getInbox(agents.backend, false);
170
+ const frontendInbox = await client.getInbox(agents.frontend, false);
171
+
172
+ const backendBroadcast = backendInbox.messages.find((m) => m.id === broadcastId);
173
+ const frontendBroadcast = frontendInbox.messages.find((m) => m.id === broadcastId);
174
+
175
+ expect(backendBroadcast).toBeDefined();
176
+ expect(frontendBroadcast).toBeDefined();
177
+ expect(backendBroadcast?.broadcast).toBe(true);
178
+ expect(frontendBroadcast?.broadcast).toBe(true);
179
+ });
180
+ });
181
+
182
+ describe("Scenario: Agent Status Tracking", () => {
183
+ it("all agents should be visible in agent list", async () => {
184
+ const result = await client.listAgents();
185
+
186
+ const registeredAgents = result.agents.filter((a) =>
187
+ Object.values(agents).includes(a.id)
188
+ );
189
+ expect(registeredAgents.length).toBe(3);
190
+ });
191
+
192
+ it("backend should show as busy after starting work", async () => {
193
+ const agent = await client.getAgent(agents.backend);
194
+
195
+ expect(agent.status).toBe("busy");
196
+ expect(agent.current_task).toContain("Implementing");
197
+ });
198
+
199
+ it("frontend should show as online (not busy)", async () => {
200
+ const agent = await client.getAgent(agents.frontend);
201
+
202
+ expect(agent.status).toBe("online");
203
+ });
204
+
205
+ it("agents can update their status", async () => {
206
+ await client.heartbeat(agents.frontend, "busy");
207
+ const agent = await client.getAgent(agents.frontend);
208
+
209
+ expect(agent.status).toBe("busy");
210
+ });
211
+ });
212
+
213
+ describe("Scenario: Multi-Agent Q&A", () => {
214
+ let questionId: string;
215
+ let backendAnswerId: string;
216
+ let frontendAnswerId: string;
217
+
218
+ it("step 1: coordinator asks question to backend", async () => {
219
+ const result = await client.sendMessage({
220
+ from_agent: agents.coordinator,
221
+ to_agent: agents.backend,
222
+ type: "question",
223
+ subject: "API Rate Limiting",
224
+ body: "What's the current rate limit configuration?",
225
+ });
226
+
227
+ questionId = result.message_id;
228
+ expect(questionId).toBeDefined();
229
+ });
230
+
231
+ it("step 2: backend answers the question", async () => {
232
+ const result = await client.reply(
233
+ questionId,
234
+ agents.backend,
235
+ "Rate limit is set to 100 requests/minute per API key."
236
+ );
237
+
238
+ backendAnswerId = result.message_id;
239
+ expect(backendAnswerId).toBeDefined();
240
+ });
241
+
242
+ it("step 3: coordinator asks frontend a different question", async () => {
243
+ const result = await client.sendMessage({
244
+ from_agent: agents.coordinator,
245
+ to_agent: agents.frontend,
246
+ type: "question",
247
+ subject: "UI Framework",
248
+ body: "Which UI framework are we using for the dashboard?",
249
+ });
250
+
251
+ const frontendQuestionId = result.message_id;
252
+
253
+ // Frontend answers
254
+ const answerResult = await client.reply(
255
+ frontendQuestionId,
256
+ agents.frontend,
257
+ "We're using React with TailwindCSS for the dashboard."
258
+ );
259
+
260
+ frontendAnswerId = answerResult.message_id;
261
+ expect(frontendAnswerId).toBeDefined();
262
+ });
263
+
264
+ it("step 4: coordinator has received all answers", async () => {
265
+ const inbox = await client.getInbox(agents.coordinator, false);
266
+
267
+ const backendAnswer = inbox.messages.find((m) => m.id === backendAnswerId);
268
+ const frontendAnswer = inbox.messages.find((m) => m.id === frontendAnswerId);
269
+
270
+ expect(backendAnswer).toBeDefined();
271
+ expect(frontendAnswer).toBeDefined();
272
+ expect(backendAnswer?.type).toBe("response");
273
+ expect(frontendAnswer?.type).toBe("response");
274
+ });
275
+ });
276
+
277
+ describe("Scenario: Urgent Priority Handling", () => {
278
+ it("should handle urgent priority messages", async () => {
279
+ const result = await client.sendMessage({
280
+ from_agent: agents.coordinator,
281
+ to_agent: agents.backend,
282
+ type: "task",
283
+ subject: "CRITICAL: Production Bug",
284
+ body: "There's a critical bug in production. Please investigate immediately.",
285
+ priority: "urgent",
286
+ });
287
+
288
+ expect(result.message_id).toBeDefined();
289
+
290
+ // Verify the message has urgent priority
291
+ const inbox = await client.getInbox(agents.backend, false);
292
+ const urgentMessage = inbox.messages.find(
293
+ (m) => m.subject === "CRITICAL: Production Bug"
294
+ );
295
+
296
+ expect(urgentMessage).toBeDefined();
297
+ expect(urgentMessage?.priority).toBe("urgent");
298
+ });
299
+ });
300
+ });