@desplega.ai/agent-swarm 1.10.3 → 1.10.8

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.
@@ -24,7 +24,6 @@ const userNameCache = new Map<string, string>();
24
24
 
25
25
  async function getUserDisplayName(client: WebClient, userId: string): Promise<string> {
26
26
  if (userNameCache.has(userId)) {
27
- // biome-ignore lint: This is fine
28
27
  return userNameCache.get(userId)!;
29
28
  }
30
29
  try {
@@ -0,0 +1,555 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import { createServer as createHttpServer, type Server } from "node:http";
4
+ import {
5
+ closeDb,
6
+ createAgent,
7
+ createTaskExtended,
8
+ getAgentById,
9
+ getDb,
10
+ initDb,
11
+ updateAgentStatus,
12
+ } from "../be/db";
13
+
14
+ const TEST_DB_PATH = "./test-rest-api.sqlite";
15
+ const TEST_PORT = 13015;
16
+
17
+ // Helper to parse path segments
18
+ function getPathSegments(url: string): string[] {
19
+ const pathEnd = url.indexOf("?");
20
+ const path = pathEnd === -1 ? url : url.slice(0, pathEnd);
21
+ return path.split("/").filter(Boolean);
22
+ }
23
+
24
+ function _parseQueryParams(url: string): URLSearchParams {
25
+ const queryIndex = url.indexOf("?");
26
+ if (queryIndex === -1) return new URLSearchParams();
27
+ return new URLSearchParams(url.slice(queryIndex + 1));
28
+ }
29
+
30
+ // Minimal HTTP handler for REST API endpoints
31
+ async function handleRequest(
32
+ req: { method: string; url: string; headers: { get: (key: string) => string | null } },
33
+ _body: string,
34
+ ): Promise<{ status: number; body: unknown }> {
35
+ const pathSegments = getPathSegments(req.url || "");
36
+ const myAgentId = req.headers.get("x-agent-id");
37
+
38
+ // GET /me - Get current agent info
39
+ if (req.method === "GET" && (req.url === "/me" || req.url?.startsWith("/me?"))) {
40
+ if (!myAgentId) {
41
+ return { status: 400, body: { error: "Missing X-Agent-ID header" } };
42
+ }
43
+
44
+ const agent = getAgentById(myAgentId);
45
+
46
+ if (!agent) {
47
+ return { status: 404, body: { error: "Agent not found" } };
48
+ }
49
+
50
+ return { status: 200, body: agent };
51
+ }
52
+
53
+ // POST /ping - Update agent heartbeat
54
+ if (req.method === "POST" && req.url === "/ping") {
55
+ if (!myAgentId) {
56
+ return { status: 400, body: { error: "Missing X-Agent-ID header" } };
57
+ }
58
+
59
+ const tx = getDb().transaction(() => {
60
+ const agent = getAgentById(myAgentId);
61
+
62
+ if (!agent) {
63
+ return { error: true };
64
+ }
65
+
66
+ let status: "idle" | "busy" = "idle";
67
+ if (agent.status === "busy") {
68
+ status = "busy";
69
+ }
70
+
71
+ updateAgentStatus(agent.id, status);
72
+ return { error: false };
73
+ });
74
+
75
+ const result = tx();
76
+ if (result.error) {
77
+ return { status: 404, body: { error: "Agent not found" } };
78
+ }
79
+
80
+ return { status: 204, body: "" };
81
+ }
82
+
83
+ // POST /close - Mark agent as offline
84
+ if (req.method === "POST" && req.url === "/close") {
85
+ if (!myAgentId) {
86
+ return { status: 400, body: { error: "Missing X-Agent-ID header" } };
87
+ }
88
+
89
+ const tx = getDb().transaction(() => {
90
+ const agent = getAgentById(myAgentId);
91
+
92
+ if (!agent) {
93
+ return { error: true };
94
+ }
95
+
96
+ updateAgentStatus(agent.id, "offline");
97
+ return { error: false };
98
+ });
99
+
100
+ const result = tx();
101
+ if (result.error) {
102
+ return { status: 404, body: { error: "Agent not found" } };
103
+ }
104
+
105
+ return { status: 204, body: "" };
106
+ }
107
+
108
+ // GET /api/agents/:id - Get single agent
109
+ if (
110
+ req.method === "GET" &&
111
+ pathSegments[0] === "api" &&
112
+ pathSegments[1] === "agents" &&
113
+ pathSegments[2]
114
+ ) {
115
+ const agentId = pathSegments[2];
116
+ const agent = getAgentById(agentId);
117
+
118
+ if (!agent) {
119
+ return { status: 404, body: { error: "Agent not found" } };
120
+ }
121
+
122
+ return { status: 200, body: agent };
123
+ }
124
+
125
+ // GET /api/tasks/:id - Get single task
126
+ if (
127
+ req.method === "GET" &&
128
+ pathSegments[0] === "api" &&
129
+ pathSegments[1] === "tasks" &&
130
+ pathSegments[2] &&
131
+ !pathSegments[3]
132
+ ) {
133
+ const taskId = pathSegments[2];
134
+ const task = getDb().query("SELECT * FROM agent_tasks WHERE id = ?").get(taskId) as unknown;
135
+
136
+ if (!task) {
137
+ return { status: 404, body: { error: "Task not found" } };
138
+ }
139
+
140
+ return { status: 200, body: task };
141
+ }
142
+
143
+ // GET /api/stats - Dashboard summary stats
144
+ if (req.method === "GET" && pathSegments[0] === "api" && pathSegments[1] === "stats") {
145
+ const agents = getDb().query("SELECT * FROM agents").all() as Array<{ status: string }>;
146
+ const tasks = getDb().query("SELECT * FROM agent_tasks").all() as Array<{ status: string }>;
147
+
148
+ const stats = {
149
+ agents: {
150
+ total: agents.length,
151
+ idle: agents.filter((a) => a.status === "idle").length,
152
+ busy: agents.filter((a) => a.status === "busy").length,
153
+ offline: agents.filter((a) => a.status === "offline").length,
154
+ },
155
+ tasks: {
156
+ total: tasks.length,
157
+ pending: tasks.filter((t) => t.status === "pending").length,
158
+ in_progress: tasks.filter((t) => t.status === "in_progress").length,
159
+ completed: tasks.filter((t) => t.status === "completed").length,
160
+ failed: tasks.filter((t) => t.status === "failed").length,
161
+ },
162
+ };
163
+
164
+ return { status: 200, body: stats };
165
+ }
166
+
167
+ return { status: 404, body: { error: "Not found" } };
168
+ }
169
+
170
+ // Create test HTTP server
171
+ function createTestServer(): Server {
172
+ return createHttpServer(async (req, res) => {
173
+ res.setHeader("Content-Type", "application/json");
174
+
175
+ const chunks: Buffer[] = [];
176
+ for await (const chunk of req) {
177
+ chunks.push(chunk);
178
+ }
179
+ const body = Buffer.concat(chunks).toString();
180
+
181
+ const headers = {
182
+ get: (key: string) => req.headers[key.toLowerCase()] as string | null,
183
+ };
184
+
185
+ const result = await handleRequest(
186
+ { method: req.method || "GET", url: req.url || "/", headers },
187
+ body,
188
+ );
189
+
190
+ res.writeHead(result.status);
191
+ res.end(JSON.stringify(result.body));
192
+ });
193
+ }
194
+
195
+ describe("REST API Endpoints", () => {
196
+ let server: Server;
197
+ const baseUrl = `http://localhost:${TEST_PORT}`;
198
+
199
+ beforeAll(async () => {
200
+ // Clean up any existing test database
201
+ try {
202
+ await unlink(TEST_DB_PATH);
203
+ } catch {
204
+ // File doesn't exist, that's fine
205
+ }
206
+
207
+ // Initialize test database
208
+ initDb(TEST_DB_PATH);
209
+
210
+ // Start test server
211
+ server = createTestServer();
212
+ await new Promise<void>((resolve) => {
213
+ server.listen(TEST_PORT, () => {
214
+ console.log(`Test server listening on port ${TEST_PORT}`);
215
+ resolve();
216
+ });
217
+ });
218
+ });
219
+
220
+ afterAll(async () => {
221
+ // Close server
222
+ await new Promise<void>((resolve) => {
223
+ server.close(() => resolve());
224
+ });
225
+
226
+ // Close database
227
+ closeDb();
228
+
229
+ // Clean up test database file
230
+ try {
231
+ await unlink(TEST_DB_PATH);
232
+ await unlink(`${TEST_DB_PATH}-wal`);
233
+ await unlink(`${TEST_DB_PATH}-shm`);
234
+ } catch {
235
+ // Files may not exist
236
+ }
237
+ });
238
+
239
+ describe("GET /me", () => {
240
+ test("should return 400 if X-Agent-ID header is missing", async () => {
241
+ const response = await fetch(`${baseUrl}/me`);
242
+
243
+ expect(response.status).toBe(400);
244
+ const data = (await response.json()) as any;
245
+ expect(data.error).toContain("X-Agent-ID");
246
+ });
247
+
248
+ test("should return 404 if agent does not exist", async () => {
249
+ const response = await fetch(`${baseUrl}/me`, {
250
+ headers: {
251
+ "X-Agent-ID": "non-existent-agent",
252
+ },
253
+ });
254
+
255
+ expect(response.status).toBe(404);
256
+ const data = (await response.json()) as any;
257
+ expect(data.error).toContain("not found");
258
+ });
259
+
260
+ test("should return agent info for existing agent", async () => {
261
+ const agentId = "test-agent-me";
262
+ createAgent({
263
+ id: agentId,
264
+ name: "Test Agent Me",
265
+ isLead: false,
266
+ status: "idle",
267
+ });
268
+
269
+ const response = await fetch(`${baseUrl}/me`, {
270
+ headers: {
271
+ "X-Agent-ID": agentId,
272
+ },
273
+ });
274
+
275
+ expect(response.status).toBe(200);
276
+ const data = (await response.json()) as any;
277
+ expect(data.id).toBe(agentId);
278
+ expect(data.name).toBe("Test Agent Me");
279
+ expect(data.status).toBe("idle");
280
+ });
281
+ });
282
+
283
+ describe("POST /ping", () => {
284
+ test("should return 400 if X-Agent-ID header is missing", async () => {
285
+ const response = await fetch(`${baseUrl}/ping`, {
286
+ method: "POST",
287
+ });
288
+
289
+ expect(response.status).toBe(400);
290
+ const data = (await response.json()) as any;
291
+ expect(data.error).toContain("X-Agent-ID");
292
+ });
293
+
294
+ test("should return 404 if agent does not exist", async () => {
295
+ const response = await fetch(`${baseUrl}/ping`, {
296
+ method: "POST",
297
+ headers: {
298
+ "X-Agent-ID": "non-existent-agent",
299
+ },
300
+ });
301
+
302
+ expect(response.status).toBe(404);
303
+ });
304
+
305
+ test("should update agent heartbeat for existing agent", async () => {
306
+ const agentId = "test-agent-ping";
307
+ createAgent({
308
+ id: agentId,
309
+ name: "Test Agent Ping",
310
+ isLead: false,
311
+ status: "offline",
312
+ });
313
+
314
+ const response = await fetch(`${baseUrl}/ping`, {
315
+ method: "POST",
316
+ headers: {
317
+ "X-Agent-ID": agentId,
318
+ },
319
+ });
320
+
321
+ expect(response.status).toBe(204);
322
+
323
+ // Verify agent status was updated to idle
324
+ const agent = getAgentById(agentId);
325
+ expect(agent?.status).toBe("idle");
326
+ });
327
+
328
+ test("should preserve busy status when pinging", async () => {
329
+ const agentId = "test-agent-ping-busy";
330
+ createAgent({
331
+ id: agentId,
332
+ name: "Test Agent Ping Busy",
333
+ isLead: false,
334
+ status: "busy",
335
+ });
336
+
337
+ const response = await fetch(`${baseUrl}/ping`, {
338
+ method: "POST",
339
+ headers: {
340
+ "X-Agent-ID": agentId,
341
+ },
342
+ });
343
+
344
+ expect(response.status).toBe(204);
345
+
346
+ // Verify agent status remains busy
347
+ const agent = getAgentById(agentId);
348
+ expect(agent?.status).toBe("busy");
349
+ });
350
+ });
351
+
352
+ describe("POST /close", () => {
353
+ test("should return 400 if X-Agent-ID header is missing", async () => {
354
+ const response = await fetch(`${baseUrl}/close`, {
355
+ method: "POST",
356
+ });
357
+
358
+ expect(response.status).toBe(400);
359
+ const data = (await response.json()) as any;
360
+ expect(data.error).toContain("X-Agent-ID");
361
+ });
362
+
363
+ test("should return 404 if agent does not exist", async () => {
364
+ const response = await fetch(`${baseUrl}/close`, {
365
+ method: "POST",
366
+ headers: {
367
+ "X-Agent-ID": "non-existent-agent",
368
+ },
369
+ });
370
+
371
+ expect(response.status).toBe(404);
372
+ });
373
+
374
+ test("should mark agent as offline", async () => {
375
+ const agentId = "test-agent-close";
376
+ createAgent({
377
+ id: agentId,
378
+ name: "Test Agent Close",
379
+ isLead: false,
380
+ status: "idle",
381
+ });
382
+
383
+ const response = await fetch(`${baseUrl}/close`, {
384
+ method: "POST",
385
+ headers: {
386
+ "X-Agent-ID": agentId,
387
+ },
388
+ });
389
+
390
+ expect(response.status).toBe(204);
391
+
392
+ // Verify agent status was updated to offline
393
+ const agent = getAgentById(agentId);
394
+ expect(agent?.status).toBe("offline");
395
+ });
396
+ });
397
+
398
+ describe("GET /api/agents/:id", () => {
399
+ test("should return 404 if agent does not exist", async () => {
400
+ const response = await fetch(`${baseUrl}/api/agents/non-existent-agent`);
401
+
402
+ expect(response.status).toBe(404);
403
+ const data = (await response.json()) as any;
404
+ expect(data.error).toContain("not found");
405
+ });
406
+
407
+ test("should return agent details for existing agent", async () => {
408
+ const agentId = "test-agent-get";
409
+ createAgent({
410
+ id: agentId,
411
+ name: "Test Agent Get",
412
+ isLead: true,
413
+ status: "idle",
414
+ });
415
+
416
+ const response = await fetch(`${baseUrl}/api/agents/${agentId}`);
417
+
418
+ expect(response.status).toBe(200);
419
+ const data = (await response.json()) as any;
420
+ expect(data.id).toBe(agentId);
421
+ expect(data.name).toBe("Test Agent Get");
422
+ expect(data.isLead).toBe(true);
423
+ expect(data.status).toBe("idle");
424
+ });
425
+
426
+ test("should return agent with profile fields", async () => {
427
+ const agentId = "test-agent-with-profile";
428
+
429
+ // First create agent, then update its profile
430
+ createAgent({
431
+ id: agentId,
432
+ name: "Agent with Profile",
433
+ isLead: false,
434
+ status: "idle",
435
+ });
436
+
437
+ // Update profile fields via SQL since createAgent doesn't accept them
438
+ getDb().run("UPDATE agents SET description = ?, role = ?, capabilities = ? WHERE id = ?", [
439
+ "Test description",
440
+ "Test role",
441
+ JSON.stringify(["test-cap-1", "test-cap-2"]),
442
+ agentId,
443
+ ]);
444
+
445
+ const response = await fetch(`${baseUrl}/api/agents/${agentId}`);
446
+
447
+ expect(response.status).toBe(200);
448
+ const data = (await response.json()) as any;
449
+ expect(data.id).toBe(agentId);
450
+ expect(data.description).toBe("Test description");
451
+ expect(data.role).toBe("Test role");
452
+ expect(data.capabilities).toEqual(["test-cap-1", "test-cap-2"]);
453
+ });
454
+ });
455
+
456
+ describe("GET /api/tasks/:id", () => {
457
+ test("should return 404 if task does not exist", async () => {
458
+ const response = await fetch(`${baseUrl}/api/tasks/non-existent-task`);
459
+
460
+ expect(response.status).toBe(404);
461
+ const data = (await response.json()) as any;
462
+ expect(data.error).toContain("not found");
463
+ });
464
+
465
+ test("should return task details for existing task", async () => {
466
+ const task = createTaskExtended("Test task for GET endpoint", {
467
+ creatorAgentId: "test-agent-get",
468
+ });
469
+
470
+ const response = await fetch(`${baseUrl}/api/tasks/${task.id}`);
471
+
472
+ expect(response.status).toBe(200);
473
+ const data = (await response.json()) as any;
474
+ expect(data.id).toBe(task.id);
475
+ expect(data.task).toBe("Test task for GET endpoint");
476
+ expect(data.status).toBe("unassigned");
477
+ });
478
+ });
479
+
480
+ describe("GET /api/stats", () => {
481
+ test("should return dashboard statistics", async () => {
482
+ // Create some test data
483
+ createAgent({
484
+ id: "stats-agent-1",
485
+ name: "Stats Agent 1",
486
+ isLead: false,
487
+ status: "idle",
488
+ });
489
+
490
+ createAgent({
491
+ id: "stats-agent-2",
492
+ name: "Stats Agent 2",
493
+ isLead: false,
494
+ status: "busy",
495
+ });
496
+
497
+ createAgent({
498
+ id: "stats-agent-3",
499
+ name: "Stats Agent 3",
500
+ isLead: false,
501
+ status: "offline",
502
+ });
503
+
504
+ createTaskExtended("Stats task 1", {
505
+ creatorAgentId: "stats-agent-1",
506
+ agentId: "stats-agent-1",
507
+ });
508
+
509
+ createTaskExtended("Stats task 2", {
510
+ creatorAgentId: "stats-agent-1",
511
+ });
512
+
513
+ const response = await fetch(`${baseUrl}/api/stats`);
514
+
515
+ expect(response.status).toBe(200);
516
+ const data = (await response.json()) as any;
517
+
518
+ expect(data.agents).toBeDefined();
519
+ expect(data.agents.total).toBeGreaterThanOrEqual(3);
520
+ expect(data.agents.idle).toBeGreaterThanOrEqual(1);
521
+ expect(data.agents.busy).toBeGreaterThanOrEqual(1);
522
+ expect(data.agents.offline).toBeGreaterThanOrEqual(1);
523
+
524
+ expect(data.tasks).toBeDefined();
525
+ expect(data.tasks.total).toBeGreaterThanOrEqual(2);
526
+ expect(data.tasks.pending).toBeGreaterThanOrEqual(1);
527
+ expect(data.tasks.unassigned).toBeUndefined(); // Check that invalid status isn't counted
528
+ });
529
+
530
+ test("should return empty stats for empty database", async () => {
531
+ // Clean up the database for this test
532
+ closeDb();
533
+ await unlink(TEST_DB_PATH);
534
+ await unlink(`${TEST_DB_PATH}-wal`).catch(() => {});
535
+ await unlink(`${TEST_DB_PATH}-shm`).catch(() => {});
536
+ initDb(TEST_DB_PATH);
537
+
538
+ const response = await fetch(`${baseUrl}/api/stats`);
539
+
540
+ expect(response.status).toBe(200);
541
+ const data = (await response.json()) as any;
542
+
543
+ expect(data.agents.total).toBe(0);
544
+ expect(data.agents.idle).toBe(0);
545
+ expect(data.agents.busy).toBe(0);
546
+ expect(data.agents.offline).toBe(0);
547
+
548
+ expect(data.tasks.total).toBe(0);
549
+ expect(data.tasks.pending).toBe(0);
550
+ expect(data.tasks.in_progress).toBe(0);
551
+ expect(data.tasks.completed).toBe(0);
552
+ expect(data.tasks.failed).toBe(0);
553
+ });
554
+ });
555
+ });
@@ -225,7 +225,7 @@ describe("Runner-Level Polling API", () => {
225
225
  });
226
226
 
227
227
  expect(response.status).toBe(201);
228
- const data = await response.json();
228
+ const data = (await response.json()) as any;
229
229
  expect(data.id).toBe(agentId);
230
230
  expect(data.name).toBe("Test Agent 1");
231
231
  expect(data.status).toBe("idle");
@@ -243,7 +243,7 @@ describe("Runner-Level Polling API", () => {
243
243
  });
244
244
 
245
245
  expect(response.status).toBe(200); // Not 201 since it exists
246
- const data = await response.json();
246
+ const data = (await response.json()) as any;
247
247
  expect(data.id).toBe(agentId);
248
248
  expect(data.name).toBe("Test Agent 1"); // Original name preserved
249
249
  });
@@ -258,7 +258,7 @@ describe("Runner-Level Polling API", () => {
258
258
  });
259
259
 
260
260
  expect(response.status).toBe(201);
261
- const data = await response.json();
261
+ const data = (await response.json()) as any;
262
262
  expect(data.id).toBeDefined();
263
263
  expect(data.id.length).toBe(36); // UUID format
264
264
  expect(data.name).toBe("Auto-ID Agent");
@@ -275,7 +275,7 @@ describe("Runner-Level Polling API", () => {
275
275
  });
276
276
 
277
277
  expect(response.status).toBe(400);
278
- const data = await response.json();
278
+ const data = (await response.json()) as any;
279
279
  expect(data.error).toContain("name");
280
280
  });
281
281
 
@@ -291,7 +291,7 @@ describe("Runner-Level Polling API", () => {
291
291
  });
292
292
 
293
293
  expect(response.status).toBe(201);
294
- const data = await response.json();
294
+ const data = (await response.json()) as any;
295
295
  expect(data.id).toBe(agentId);
296
296
  expect(data.isLead).toBe(true);
297
297
  });
@@ -302,7 +302,7 @@ describe("Runner-Level Polling API", () => {
302
302
  const response = await fetch(`${baseUrl}/api/poll`);
303
303
 
304
304
  expect(response.status).toBe(400);
305
- const data = await response.json();
305
+ const data = (await response.json()) as any;
306
306
  expect(data.error).toContain("X-Agent-ID");
307
307
  });
308
308
 
@@ -314,7 +314,7 @@ describe("Runner-Level Polling API", () => {
314
314
  });
315
315
 
316
316
  expect(response.status).toBe(404);
317
- const data = await response.json();
317
+ const data = (await response.json()) as any;
318
318
  expect(data.error).toContain("not found");
319
319
  });
320
320
 
@@ -326,7 +326,7 @@ describe("Runner-Level Polling API", () => {
326
326
  });
327
327
 
328
328
  expect(response.status).toBe(200);
329
- const data = await response.json();
329
+ const data = (await response.json()) as any;
330
330
  expect(data.trigger).toBeNull();
331
331
  });
332
332
 
@@ -356,7 +356,7 @@ describe("Runner-Level Polling API", () => {
356
356
  });
357
357
 
358
358
  expect(response.status).toBe(200);
359
- const data = await response.json();
359
+ const data = (await response.json()) as any;
360
360
  expect(data.trigger).not.toBeNull();
361
361
  expect(data.trigger.type).toBe("task_assigned");
362
362
  expect(data.trigger.taskId).toBe(task.id);
@@ -388,7 +388,7 @@ describe("Runner-Level Polling API", () => {
388
388
  });
389
389
 
390
390
  expect(response.status).toBe(200);
391
- const data = await response.json();
391
+ const data = (await response.json()) as any;
392
392
  expect(data.trigger).not.toBeNull();
393
393
  expect(data.trigger.type).toBe("task_offered");
394
394
  expect(data.trigger.taskId).toBe(task.id);
@@ -420,7 +420,7 @@ describe("Runner-Level Polling API", () => {
420
420
  });
421
421
 
422
422
  expect(response.status).toBe(200);
423
- const data = await response.json();
423
+ const data = (await response.json()) as any;
424
424
  expect(data.trigger).not.toBeNull();
425
425
  expect(data.trigger.type).toBe("pool_tasks_available");
426
426
  expect(data.trigger.count).toBeGreaterThan(0);
@@ -448,7 +448,7 @@ describe("Runner-Level Polling API", () => {
448
448
  });
449
449
 
450
450
  expect(response.status).toBe(200);
451
- const data = await response.json();
451
+ const data = (await response.json()) as any;
452
452
  // Worker should NOT see pool tasks
453
453
  expect(data.trigger).toBeNull();
454
454
  });
@@ -107,7 +107,6 @@ export const registerPollTaskTool = (server: McpServer) => {
107
107
  while (new Date() < maxTime) {
108
108
  // Fetch and update in a single transaction to avoid race conditions
109
109
  const startedTask = getDb().transaction(() => {
110
- // biome-ignore lint/style/noNonNullAssertion: agent existence verified above
111
110
  const agentNow = getAgentById(agentId)!;
112
111
 
113
112
  if (agentNow.status !== "busy") {