@desplega.ai/agent-swarm 1.2.0 → 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.
Files changed (123) hide show
  1. package/.claude/settings.local.json +20 -1
  2. package/.dockerignore +3 -0
  3. package/.env.docker.example +22 -1
  4. package/.env.example +17 -0
  5. package/.github/workflows/docker-publish.yml +92 -0
  6. package/CONTRIBUTING.md +270 -0
  7. package/DEPLOYMENT.md +391 -0
  8. package/Dockerfile.worker +29 -1
  9. package/FAQ.md +19 -0
  10. package/LICENSE +21 -0
  11. package/MCP.md +249 -0
  12. package/README.md +105 -185
  13. package/assets/agent-swarm-logo-orange.png +0 -0
  14. package/assets/agent-swarm-logo.png +0 -0
  15. package/assets/agent-swarm.png +0 -0
  16. package/deploy/docker-push.ts +30 -0
  17. package/docker-compose.example.yml +137 -0
  18. package/docker-entrypoint.sh +223 -7
  19. package/package.json +13 -4
  20. package/{cc-plugin → plugin}/.claude-plugin/plugin.json +1 -1
  21. package/plugin/README.md +1 -0
  22. package/plugin/agents/.gitkeep +0 -0
  23. package/plugin/agents/codebase-analyzer.md +143 -0
  24. package/plugin/agents/codebase-locator.md +122 -0
  25. package/plugin/agents/codebase-pattern-finder.md +227 -0
  26. package/plugin/agents/web-search-researcher.md +109 -0
  27. package/plugin/commands/create-plan.md +415 -0
  28. package/plugin/commands/implement-plan.md +89 -0
  29. package/plugin/commands/research.md +200 -0
  30. package/plugin/commands/start-leader.md +101 -0
  31. package/plugin/commands/start-worker.md +56 -0
  32. package/plugin/commands/swarm-chat.md +78 -0
  33. package/plugin/commands/todos.md +66 -0
  34. package/plugin/commands/work-on-task.md +44 -0
  35. package/plugin/skills/.gitkeep +0 -0
  36. package/scripts/generate-mcp-docs.ts +415 -0
  37. package/slack-manifest.json +69 -0
  38. package/src/be/db.ts +1431 -25
  39. package/src/cli.tsx +135 -11
  40. package/src/commands/lead.ts +13 -0
  41. package/src/commands/runner.ts +255 -0
  42. package/src/commands/setup.tsx +5 -5
  43. package/src/commands/worker.ts +8 -220
  44. package/src/hooks/hook.ts +108 -14
  45. package/src/http.ts +361 -5
  46. package/src/prompts/base-prompt.ts +131 -0
  47. package/src/server.ts +56 -0
  48. package/src/slack/app.ts +73 -0
  49. package/src/slack/commands.ts +88 -0
  50. package/src/slack/handlers.ts +281 -0
  51. package/src/slack/index.ts +3 -0
  52. package/src/slack/responses.ts +175 -0
  53. package/src/slack/router.ts +170 -0
  54. package/src/slack/types.ts +20 -0
  55. package/src/slack/watcher.ts +119 -0
  56. package/src/tools/create-channel.ts +80 -0
  57. package/src/tools/get-tasks.ts +54 -21
  58. package/src/tools/join-swarm.ts +28 -4
  59. package/src/tools/list-channels.ts +37 -0
  60. package/src/tools/list-services.ts +110 -0
  61. package/src/tools/poll-task.ts +47 -3
  62. package/src/tools/post-message.ts +87 -0
  63. package/src/tools/read-messages.ts +192 -0
  64. package/src/tools/register-service.ts +118 -0
  65. package/src/tools/send-task.ts +80 -7
  66. package/src/tools/store-progress.ts +9 -3
  67. package/src/tools/task-action.ts +211 -0
  68. package/src/tools/unregister-service.ts +110 -0
  69. package/src/tools/update-profile.ts +105 -0
  70. package/src/tools/update-service-status.ts +118 -0
  71. package/src/types.ts +110 -3
  72. package/src/utils/pretty-print.ts +224 -0
  73. package/thoughts/shared/plans/.gitkeep +0 -0
  74. package/thoughts/shared/plans/2025-12-18-inverse-teleport.md +1142 -0
  75. package/thoughts/shared/plans/2025-12-18-slack-integration.md +1195 -0
  76. package/thoughts/shared/plans/2025-12-19-agent-log-streaming.md +732 -0
  77. package/thoughts/shared/plans/2025-12-19-role-based-swarm-plugin.md +361 -0
  78. package/thoughts/shared/plans/2025-12-20-mobile-responsive-ui.md +501 -0
  79. package/thoughts/shared/plans/2025-12-20-startup-team-swarm.md +560 -0
  80. package/thoughts/shared/research/.gitkeep +0 -0
  81. package/thoughts/shared/research/2025-12-18-slack-integration.md +442 -0
  82. package/thoughts/shared/research/2025-12-19-agent-log-streaming.md +339 -0
  83. package/thoughts/shared/research/2025-12-19-agent-secrets-cli-research.md +390 -0
  84. package/thoughts/shared/research/2025-12-21-gemini-cli-integration.md +376 -0
  85. package/thoughts/shared/research/2025-12-22-setup-experience-improvements.md +264 -0
  86. package/tsconfig.json +3 -1
  87. package/ui/bun.lock +692 -0
  88. package/ui/index.html +22 -0
  89. package/ui/package.json +32 -0
  90. package/ui/pnpm-lock.yaml +3034 -0
  91. package/ui/postcss.config.js +6 -0
  92. package/ui/public/logo.png +0 -0
  93. package/ui/src/App.tsx +43 -0
  94. package/ui/src/components/ActivityFeed.tsx +415 -0
  95. package/ui/src/components/AgentDetailPanel.tsx +534 -0
  96. package/ui/src/components/AgentsPanel.tsx +549 -0
  97. package/ui/src/components/ChatPanel.tsx +1820 -0
  98. package/ui/src/components/ConfigModal.tsx +232 -0
  99. package/ui/src/components/Dashboard.tsx +534 -0
  100. package/ui/src/components/Header.tsx +168 -0
  101. package/ui/src/components/ServicesPanel.tsx +612 -0
  102. package/ui/src/components/StatsBar.tsx +288 -0
  103. package/ui/src/components/StatusBadge.tsx +124 -0
  104. package/ui/src/components/TaskDetailPanel.tsx +807 -0
  105. package/ui/src/components/TasksPanel.tsx +575 -0
  106. package/ui/src/hooks/queries.ts +170 -0
  107. package/ui/src/index.css +235 -0
  108. package/ui/src/lib/api.ts +161 -0
  109. package/ui/src/lib/config.ts +35 -0
  110. package/ui/src/lib/theme.ts +214 -0
  111. package/ui/src/lib/utils.ts +48 -0
  112. package/ui/src/main.tsx +32 -0
  113. package/ui/src/types/api.ts +164 -0
  114. package/ui/src/vite-env.d.ts +1 -0
  115. package/ui/tailwind.config.js +35 -0
  116. package/ui/tsconfig.json +31 -0
  117. package/ui/vite.config.ts +22 -0
  118. package/cc-plugin/README.md +0 -49
  119. package/cc-plugin/commands/setup-leader.md +0 -73
  120. package/cc-plugin/commands/start-worker.md +0 -64
  121. package/docker-compose.worker.yml +0 -35
  122. package/example-req-meta.json +0 -24
  123. /package/{cc-plugin → plugin}/hooks/hooks.json +0 -0
package/src/http.ts CHANGED
@@ -8,8 +8,29 @@ import {
8
8
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
9
9
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
10
10
  import { createServer } from "@/server";
11
- import { closeDb, getAgentById, getDb, updateAgentStatus } from "./be/db";
12
- import type { AgentStatus } from "./types";
11
+ import {
12
+ closeDb,
13
+ getAgentById,
14
+ getAgentWithTasks,
15
+ getAllAgents,
16
+ getAllAgentsWithTasks,
17
+ getAllChannels,
18
+ getAllLogs,
19
+ getAllServices,
20
+ getAllTasks,
21
+ getChannelById,
22
+ getChannelMessages,
23
+ getDb,
24
+ getInboxSummary,
25
+ getLogsByAgentId,
26
+ getLogsByTaskId,
27
+ getServicesByAgentId,
28
+ getTaskById,
29
+ postMessage,
30
+ updateAgentStatus,
31
+ } from "./be/db";
32
+ import { startSlackApp, stopSlackApp } from "./slack";
33
+ import type { AgentLog, AgentStatus } from "./types";
13
34
 
14
35
  const port = parseInt(process.env.PORT || process.argv[2] || "3013", 10);
15
36
  const apiKey = process.env.API_KEY || "";
@@ -36,6 +57,18 @@ function setCorsHeaders(res: ServerResponse) {
36
57
  res.setHeader("Access-Control-Expose-Headers", "*");
37
58
  }
38
59
 
60
+ function parseQueryParams(url: string): URLSearchParams {
61
+ const queryIndex = url.indexOf("?");
62
+ if (queryIndex === -1) return new URLSearchParams();
63
+ return new URLSearchParams(url.slice(queryIndex + 1));
64
+ }
65
+
66
+ function getPathSegments(url: string): string[] {
67
+ const pathEnd = url.indexOf("?");
68
+ const path = pathEnd === -1 ? url : url.slice(0, pathEnd);
69
+ return path.split("/").filter(Boolean);
70
+ }
71
+
39
72
  const httpServer = createHttpServer(async (req, res) => {
40
73
  setCorsHeaders(res);
41
74
 
@@ -80,7 +113,7 @@ const httpServer = createHttpServer(async (req, res) => {
80
113
  }
81
114
  }
82
115
 
83
- if (req.method === "GET" && req.url === "/me") {
116
+ if (req.method === "GET" && (req.url === "/me" || req.url?.startsWith("/me?"))) {
84
117
  if (!myAgentId) {
85
118
  res.writeHead(400, { "Content-Type": "application/json" });
86
119
  res.end(JSON.stringify({ error: "Missing X-Agent-ID header" }));
@@ -95,6 +128,16 @@ const httpServer = createHttpServer(async (req, res) => {
95
128
  return;
96
129
  }
97
130
 
131
+ // Check for ?include=inbox query param
132
+ const includeInbox = parseQueryParams(req.url || "").get("include") === "inbox";
133
+
134
+ if (includeInbox) {
135
+ const inbox = getInboxSummary(myAgentId);
136
+ res.writeHead(200, { "Content-Type": "application/json" });
137
+ res.end(JSON.stringify({ ...agent, inbox }));
138
+ return;
139
+ }
140
+
98
141
  res.writeHead(200, { "Content-Type": "application/json" });
99
142
  res.end(JSON.stringify(agent));
100
143
  return;
@@ -166,6 +209,313 @@ const httpServer = createHttpServer(async (req, res) => {
166
209
  return;
167
210
  }
168
211
 
212
+ // GET /ecosystem - Generate PM2 ecosystem config for agent's services
213
+ if (req.method === "GET" && req.url === "/ecosystem") {
214
+ if (!myAgentId) {
215
+ res.writeHead(400, { "Content-Type": "application/json" });
216
+ res.end(JSON.stringify({ error: "Missing X-Agent-ID header" }));
217
+ return;
218
+ }
219
+
220
+ const services = getServicesByAgentId(myAgentId);
221
+
222
+ // Generate PM2 ecosystem format
223
+ const ecosystem = {
224
+ apps: services
225
+ .filter((s) => s.script) // Only include services with script path
226
+ .map((s) => {
227
+ const app: Record<string, unknown> = {
228
+ name: s.name,
229
+ script: s.script,
230
+ };
231
+
232
+ if (s.cwd) app.cwd = s.cwd;
233
+ if (s.interpreter) app.interpreter = s.interpreter;
234
+ if (s.args && s.args.length > 0) app.args = s.args;
235
+ if (s.env && Object.keys(s.env).length > 0) app.env = s.env;
236
+ if (s.port)
237
+ app.env = { ...((app.env as Record<string, string>) || {}), PORT: String(s.port) };
238
+
239
+ return app;
240
+ }),
241
+ };
242
+
243
+ res.writeHead(200, { "Content-Type": "application/json" });
244
+ res.end(JSON.stringify(ecosystem));
245
+ return;
246
+ }
247
+
248
+ // ============================================================================
249
+ // REST API Endpoints (for frontend dashboard)
250
+ // ============================================================================
251
+
252
+ const pathSegments = getPathSegments(req.url || "");
253
+ const queryParams = parseQueryParams(req.url || "");
254
+
255
+ // GET /api/agents - List all agents (optionally with tasks)
256
+ if (
257
+ req.method === "GET" &&
258
+ pathSegments[0] === "api" &&
259
+ pathSegments[1] === "agents" &&
260
+ !pathSegments[2]
261
+ ) {
262
+ const includeTasks = queryParams.get("include") === "tasks";
263
+ const agents = includeTasks ? getAllAgentsWithTasks() : getAllAgents();
264
+ res.writeHead(200, { "Content-Type": "application/json" });
265
+ res.end(JSON.stringify({ agents }));
266
+ return;
267
+ }
268
+
269
+ // GET /api/agents/:id - Get single agent (optionally with tasks)
270
+ if (
271
+ req.method === "GET" &&
272
+ pathSegments[0] === "api" &&
273
+ pathSegments[1] === "agents" &&
274
+ pathSegments[2]
275
+ ) {
276
+ const agentId = pathSegments[2];
277
+ const includeTasks = queryParams.get("include") === "tasks";
278
+ const agent = includeTasks ? getAgentWithTasks(agentId) : getAgentById(agentId);
279
+
280
+ if (!agent) {
281
+ res.writeHead(404, { "Content-Type": "application/json" });
282
+ res.end(JSON.stringify({ error: "Agent not found" }));
283
+ return;
284
+ }
285
+
286
+ res.writeHead(200, { "Content-Type": "application/json" });
287
+ res.end(JSON.stringify(agent));
288
+ return;
289
+ }
290
+
291
+ // GET /api/tasks - List all tasks (with optional filters: status, agentId, search)
292
+ if (
293
+ req.method === "GET" &&
294
+ pathSegments[0] === "api" &&
295
+ pathSegments[1] === "tasks" &&
296
+ !pathSegments[2]
297
+ ) {
298
+ const status = queryParams.get("status") as import("./types").AgentTaskStatus | null;
299
+ const agentId = queryParams.get("agentId");
300
+ const search = queryParams.get("search");
301
+ const tasks = getAllTasks({
302
+ status: status || undefined,
303
+ agentId: agentId || undefined,
304
+ search: search || undefined,
305
+ });
306
+ res.writeHead(200, { "Content-Type": "application/json" });
307
+ res.end(JSON.stringify({ tasks }));
308
+ return;
309
+ }
310
+
311
+ // GET /api/tasks/:id - Get single task with logs
312
+ if (
313
+ req.method === "GET" &&
314
+ pathSegments[0] === "api" &&
315
+ pathSegments[1] === "tasks" &&
316
+ pathSegments[2]
317
+ ) {
318
+ const taskId = pathSegments[2];
319
+ const task = getTaskById(taskId);
320
+
321
+ if (!task) {
322
+ res.writeHead(404, { "Content-Type": "application/json" });
323
+ res.end(JSON.stringify({ error: "Task not found" }));
324
+ return;
325
+ }
326
+
327
+ const logs = getLogsByTaskId(taskId);
328
+ res.writeHead(200, { "Content-Type": "application/json" });
329
+ res.end(JSON.stringify({ ...task, logs }));
330
+ return;
331
+ }
332
+
333
+ // GET /api/logs - List recent logs (optionally filtered by agentId)
334
+ if (req.method === "GET" && pathSegments[0] === "api" && pathSegments[1] === "logs") {
335
+ const limitParam = queryParams.get("limit");
336
+ const limit = limitParam ? parseInt(limitParam, 10) : 100;
337
+ const agentId = queryParams.get("agentId");
338
+ let logs: AgentLog[] = [];
339
+ if (agentId) {
340
+ logs = getLogsByAgentId(agentId).slice(0, limit);
341
+ } else {
342
+ logs = getAllLogs(limit);
343
+ }
344
+ res.writeHead(200, { "Content-Type": "application/json" });
345
+ res.end(JSON.stringify({ logs }));
346
+ return;
347
+ }
348
+
349
+ // GET /api/stats - Dashboard summary stats
350
+ if (req.method === "GET" && pathSegments[0] === "api" && pathSegments[1] === "stats") {
351
+ const agents = getAllAgents();
352
+ const tasks = getAllTasks();
353
+
354
+ const stats = {
355
+ agents: {
356
+ total: agents.length,
357
+ idle: agents.filter((a) => a.status === "idle").length,
358
+ busy: agents.filter((a) => a.status === "busy").length,
359
+ offline: agents.filter((a) => a.status === "offline").length,
360
+ },
361
+ tasks: {
362
+ total: tasks.length,
363
+ pending: tasks.filter((t) => t.status === "pending").length,
364
+ in_progress: tasks.filter((t) => t.status === "in_progress").length,
365
+ completed: tasks.filter((t) => t.status === "completed").length,
366
+ failed: tasks.filter((t) => t.status === "failed").length,
367
+ },
368
+ };
369
+
370
+ res.writeHead(200, { "Content-Type": "application/json" });
371
+ res.end(JSON.stringify(stats));
372
+ return;
373
+ }
374
+
375
+ // GET /api/services - List all services (with optional filters: status, agentId, name)
376
+ if (
377
+ req.method === "GET" &&
378
+ pathSegments[0] === "api" &&
379
+ pathSegments[1] === "services" &&
380
+ !pathSegments[2]
381
+ ) {
382
+ const status = queryParams.get("status") as import("./types").ServiceStatus | null;
383
+ const agentId = queryParams.get("agentId");
384
+ const name = queryParams.get("name");
385
+ const services = getAllServices({
386
+ status: status || undefined,
387
+ agentId: agentId || undefined,
388
+ name: name || undefined,
389
+ });
390
+ res.writeHead(200, { "Content-Type": "application/json" });
391
+ res.end(JSON.stringify({ services }));
392
+ return;
393
+ }
394
+
395
+ // GET /api/channels - List all channels
396
+ if (
397
+ req.method === "GET" &&
398
+ pathSegments[0] === "api" &&
399
+ pathSegments[1] === "channels" &&
400
+ !pathSegments[2]
401
+ ) {
402
+ const channels = getAllChannels();
403
+ res.writeHead(200, { "Content-Type": "application/json" });
404
+ res.end(JSON.stringify({ channels }));
405
+ return;
406
+ }
407
+
408
+ // GET /api/channels/:id/messages - Get messages in a channel
409
+ if (
410
+ req.method === "GET" &&
411
+ pathSegments[0] === "api" &&
412
+ pathSegments[1] === "channels" &&
413
+ pathSegments[2] &&
414
+ pathSegments[3] === "messages" &&
415
+ !pathSegments[4]
416
+ ) {
417
+ const channelId = pathSegments[2];
418
+ const channel = getChannelById(channelId);
419
+
420
+ if (!channel) {
421
+ res.writeHead(404, { "Content-Type": "application/json" });
422
+ res.end(JSON.stringify({ error: "Channel not found" }));
423
+ return;
424
+ }
425
+
426
+ const limitParam = queryParams.get("limit");
427
+ const limit = limitParam ? parseInt(limitParam, 10) : 50;
428
+ const since = queryParams.get("since") || undefined;
429
+ const before = queryParams.get("before") || undefined;
430
+
431
+ const messages = getChannelMessages(channelId, { limit, since, before });
432
+ res.writeHead(200, { "Content-Type": "application/json" });
433
+ res.end(JSON.stringify({ messages }));
434
+ return;
435
+ }
436
+
437
+ // GET /api/channels/:id/messages/:messageId/thread - Get thread messages
438
+ if (
439
+ req.method === "GET" &&
440
+ pathSegments[0] === "api" &&
441
+ pathSegments[1] === "channels" &&
442
+ pathSegments[2] &&
443
+ pathSegments[3] === "messages" &&
444
+ pathSegments[4] &&
445
+ pathSegments[5] === "thread"
446
+ ) {
447
+ const channelId = pathSegments[2];
448
+ const parentMessageId = pathSegments[4];
449
+
450
+ const channel = getChannelById(channelId);
451
+ if (!channel) {
452
+ res.writeHead(404, { "Content-Type": "application/json" });
453
+ res.end(JSON.stringify({ error: "Channel not found" }));
454
+ return;
455
+ }
456
+
457
+ // Get all messages that reply to this message
458
+ const allMessages = getChannelMessages(channelId, { limit: 1000 });
459
+ const threadMessages = allMessages.filter((m) => m.replyToId === parentMessageId);
460
+
461
+ res.writeHead(200, { "Content-Type": "application/json" });
462
+ res.end(JSON.stringify({ messages: threadMessages }));
463
+ return;
464
+ }
465
+
466
+ // POST /api/channels/:id/messages - Post a message
467
+ if (
468
+ req.method === "POST" &&
469
+ pathSegments[0] === "api" &&
470
+ pathSegments[1] === "channels" &&
471
+ pathSegments[2] &&
472
+ pathSegments[3] === "messages"
473
+ ) {
474
+ const channelId = pathSegments[2];
475
+ const channel = getChannelById(channelId);
476
+
477
+ if (!channel) {
478
+ res.writeHead(404, { "Content-Type": "application/json" });
479
+ res.end(JSON.stringify({ error: "Channel not found" }));
480
+ return;
481
+ }
482
+
483
+ // Parse request body
484
+ const chunks: Buffer[] = [];
485
+ for await (const chunk of req) {
486
+ chunks.push(chunk);
487
+ }
488
+ const body = JSON.parse(Buffer.concat(chunks).toString());
489
+
490
+ if (!body.content || typeof body.content !== "string") {
491
+ res.writeHead(400, { "Content-Type": "application/json" });
492
+ res.end(JSON.stringify({ error: "Missing or invalid content" }));
493
+ return;
494
+ }
495
+
496
+ // agentId is optional (null for human users)
497
+ const agentId = body.agentId || null;
498
+
499
+ // If agentId provided, verify agent exists
500
+ if (agentId) {
501
+ const agent = getAgentById(agentId);
502
+ if (!agent) {
503
+ res.writeHead(400, { "Content-Type": "application/json" });
504
+ res.end(JSON.stringify({ error: "Invalid agentId" }));
505
+ return;
506
+ }
507
+ }
508
+
509
+ const message = postMessage(channelId, agentId, body.content, {
510
+ replyToId: body.replyToId,
511
+ mentions: body.mentions,
512
+ });
513
+
514
+ res.writeHead(201, { "Content-Type": "application/json" });
515
+ res.end(JSON.stringify(message));
516
+ return;
517
+ }
518
+
169
519
  if (req.url !== "/mcp") {
170
520
  res.writeHead(404);
171
521
  res.end("Not Found");
@@ -236,9 +586,12 @@ const httpServer = createHttpServer(async (req, res) => {
236
586
  globalState.__httpServer = httpServer;
237
587
  globalState.__transports = transports;
238
588
 
239
- function shutdown() {
589
+ async function shutdown() {
240
590
  console.log("Shutting down HTTP server...");
241
591
 
592
+ // Stop Slack bot
593
+ await stopSlackApp();
594
+
242
595
  // Close all active transports (SSE connections, etc.)
243
596
  for (const [id, transport] of Object.entries(transports)) {
244
597
  console.log(`[HTTP] Closing transport ${id}`);
@@ -262,8 +615,11 @@ if (!globalState.__sigintRegistered) {
262
615
  }
263
616
 
264
617
  httpServer
265
- .listen(port, () => {
618
+ .listen(port, async () => {
266
619
  console.log(`MCP HTTP server running on http://localhost:${port}/mcp`);
620
+
621
+ // Start Slack bot (if configured)
622
+ await startSlackApp();
267
623
  })
268
624
  .on("error", (err) => {
269
625
  console.error("HTTP Server Error:", err);
@@ -0,0 +1,131 @@
1
+ const BASE_PROMPT_ROLE = `
2
+ You are part of an agent swarm, your role is: {role} and your unique identified is {agentId}.
3
+
4
+ The agent swarm operates in a collaborative manner to achieve complex tasks by dividing responsibilities among specialized agents.
5
+ `;
6
+
7
+ const BASE_PROMPT_REGISTER = `
8
+ If you are not yet registered in the swarm, use the \`join-swarm\` tool to register yourself.
9
+ `;
10
+
11
+ const BASE_PROMPT_LEAD = `
12
+ As the lead agent, you are responsible for coordinating the activities of all worker agents in the swarm.
13
+
14
+ The lead assigns tasks, monitors progress, and ensures that the swarm operates efficiently towards achieving its overall objectives. The lead communicates with workers, provides guidance, and resolves any conflicts that may arise within the swarm.
15
+
16
+ It should not perform worker tasks itself, but rather focus on leadership and coordination.
17
+
18
+ #### General monitor and control tools
19
+
20
+ - get-swarm: To get the list of all workers in the swarm along with their status.
21
+ - get-tasks: To get the list of all tasks assigned to workers.
22
+ - get-task-details: To get detailed information about a specific task.
23
+
24
+ #### Task assignment tools
25
+
26
+ - send-task: Quickly assign a new task to a specific worker, or to the general pool of unassigned tasks.
27
+ - task-action: Manage tasks with different actions like claim, release, accept, reject, and complete.
28
+ - store-progress: Critical tool to save your work and progress on tasks! Also useful to fix issues with tasks assigned to workers.
29
+ - poll-task: Can be used to claim tasks from the unassigned pool if needed, or sometimes you might also get tasks assigned to you directly.
30
+ `;
31
+
32
+ const BASE_PROMPT_WORKER = `
33
+ As a worker agent of the swarm, you are responsible for executing tasks assigned by the lead agent.
34
+
35
+ - Each worker focuses on specific tasks or objectives, contributing to the overall goals of the swarm.
36
+ - Workers MUST report their progress back to the lead and collaborate with other workers as needed to complete their assignments effectively.
37
+
38
+ #### Useful tools for workers
39
+
40
+ - poll-task: Automatically waits for new tasks assigned by the lead or claimed from the unassigned pool.
41
+ - read-messages: To read messages sent to you by the lead or other workers, by default when a task is found, it will auto-assign it to you.
42
+ - store-progress: Critical tool to save your work and progress on tasks!
43
+ - task-action: Manage tasks with different actions like claim, release, accept, reject, and complete.
44
+ `;
45
+
46
+ const BASE_PROMPT_FILESYSTEM = `
47
+ ### You are given a full Ubuntu filesystem at /workspace, where you can find the following CRUCIAL files and directories:
48
+
49
+ - /workspace/personal - Your personal directory for storing files, code, and data related to your tasks.
50
+ - /workspace/personal/todos.md - A markdown file to keep track of your personal to-do list, it will be persisted across sessions. Use the /todos command to interact with it.
51
+ - /workspace/shared - A shared directory accessible by all agents in the swarm for collaboration, critical if you want to share files or data with other agents, specially the lead agent.
52
+ - /workspace/shared/thoughts/{name}/{plans,research} directories - A shared thoughts directory, where you and all other agents will be storing your plans and research notes. Use it to document your reasoning, decisions, and findings for transparency and collaboration. The commands to interact with it are /research, /create-plan and /implement-plan.
53
+ - There will be a /workspace/shared/thoughts/shared/... directory for general swarm-wide notes.
54
+ - There will be a /workspace/shared/thoughts/{yourId}/... directory for each agent to store their individual notes, you can access other agents' notes here as well.
55
+ `;
56
+
57
+ const BASE_PROMPT_GUIDELINES = `
58
+ ### Agent Swarm Operational Guidelines
59
+
60
+ - Communication: Use the /swarm-chat command to communicate with other agents and the human operator. Keep everyone informed of your progress and any issues you encounter. It will allow you to see the internal Slack-like communication platform to: create and view channels (or DMs), read messages, and post messages.
61
+ -
62
+ `;
63
+
64
+ const BASE_PROMPT_SYSTEM = `
65
+ ### System packages available
66
+
67
+ You have a full Ubuntu environment with some packages pre-installed: node, bun, python3, curl, wget, git, gh, jq, etc.
68
+
69
+ If you need to install additional packages, use "sudo apt-get install {package_name}".
70
+
71
+ ### External Swarm Access & Service Registry
72
+
73
+ Port 3000 is exposed for web apps or APIs. Use PM2 for robust process management:
74
+
75
+ **PM2 Commands:**
76
+ - \`pm2 start <script> --name <name>\` - Start a service
77
+ - \`pm2 stop|restart|delete <name>\` - Manage services
78
+ - \`pm2 logs [name]\` - View logs
79
+ - \`pm2 list\` - Show running processes
80
+
81
+ **Service Registry Tools:**
82
+ - \`register-service\` - Register your service for discovery and auto-restart
83
+ - \`unregister-service\` - Remove your service from the registry
84
+ - \`list-services\` - Find services exposed by other agents
85
+ - \`update-service-status\` - Update your service's health status
86
+
87
+ **Starting a New Service:**
88
+ 1. Start with PM2: \`pm2 start /workspace/myapp/server.js --name my-api\`
89
+ 2. Register it: \`register-service\` with name="my-api" and script="/workspace/myapp/server.js"
90
+ 3. Mark healthy: \`update-service-status\` with status="healthy"
91
+
92
+ **Updating a Service:**
93
+ 1. Update locally: \`pm2 restart my-api\`
94
+ 2. If config changed, re-register: \`register-service\` with updated params (it upserts)
95
+
96
+ **Stopping a Service:**
97
+ 1. Stop locally: \`pm2 delete my-api\`
98
+ 2. Remove from registry: \`unregister-service\` with name="my-api"
99
+
100
+ **Auto-Restart:** Registered services are automatically restarted on container restart via ecosystem.config.js.
101
+
102
+ Your service URL will be: \`https://{agentId}.{swarmUrl}\` (based on your agent ID, not name)
103
+
104
+ **Health Checks:** Implement a \`/health\` endpoint returning 200 OK for monitoring.
105
+ `;
106
+
107
+ export type BasePromptArgs = {
108
+ role: string;
109
+ agentId: string;
110
+ swarmUrl: string;
111
+ };
112
+
113
+ export const getBasePrompt = (args: BasePromptArgs): string => {
114
+ const { role, agentId, swarmUrl } = args;
115
+
116
+ let prompt = BASE_PROMPT_ROLE.replace("{role}", role).replace("{agentId}", agentId);
117
+
118
+ prompt += BASE_PROMPT_REGISTER;
119
+
120
+ if (role === "lead") {
121
+ prompt += BASE_PROMPT_LEAD;
122
+ } else {
123
+ prompt += BASE_PROMPT_WORKER;
124
+ }
125
+
126
+ prompt += BASE_PROMPT_FILESYSTEM;
127
+ prompt += BASE_PROMPT_GUIDELINES;
128
+ prompt += BASE_PROMPT_SYSTEM.replace("{swarmUrl}", swarmUrl);
129
+
130
+ return prompt;
131
+ };
package/src/server.ts CHANGED
@@ -1,14 +1,43 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import pkg from "../package.json";
3
3
  import { initDb } from "./be/db";
4
+ import { registerCreateChannelTool } from "./tools/create-channel";
4
5
  import { registerGetSwarmTool } from "./tools/get-swarm";
5
6
  import { registerGetTaskDetailsTool } from "./tools/get-task-details";
6
7
  import { registerGetTasksTool } from "./tools/get-tasks";
7
8
  import { registerJoinSwarmTool } from "./tools/join-swarm";
9
+ // Messaging capability
10
+ import { registerListChannelsTool } from "./tools/list-channels";
11
+ import { registerListServicesTool } from "./tools/list-services";
8
12
  import { registerMyAgentInfoTool } from "./tools/my-agent-info";
9
13
  import { registerPollTaskTool } from "./tools/poll-task";
14
+ import { registerPostMessageTool } from "./tools/post-message";
15
+ import { registerReadMessagesTool } from "./tools/read-messages";
16
+ // Services capability
17
+ import { registerRegisterServiceTool } from "./tools/register-service";
10
18
  import { registerSendTaskTool } from "./tools/send-task";
11
19
  import { registerStoreProgressTool } from "./tools/store-progress";
20
+ // Task pool capability
21
+ import { registerTaskActionTool } from "./tools/task-action";
22
+ import { registerUnregisterServiceTool } from "./tools/unregister-service";
23
+ // Profiles capability
24
+ import { registerUpdateProfileTool } from "./tools/update-profile";
25
+ import { registerUpdateServiceStatusTool } from "./tools/update-service-status";
26
+
27
+ // Capability-based feature flags
28
+ // Default: all capabilities enabled
29
+ const DEFAULT_CAPABILITIES = "core,task-pool,messaging,profiles,services";
30
+ const CAPABILITIES = new Set(
31
+ (process.env.CAPABILITIES || DEFAULT_CAPABILITIES).split(",").map((s) => s.trim()),
32
+ );
33
+
34
+ export function hasCapability(cap: string): boolean {
35
+ return CAPABILITIES.has(cap);
36
+ }
37
+
38
+ export function getEnabledCapabilities(): string[] {
39
+ return Array.from(CAPABILITIES);
40
+ }
12
41
 
13
42
  export function createServer() {
14
43
  // Initialize database with WAL mode
@@ -27,6 +56,7 @@ export function createServer() {
27
56
  },
28
57
  );
29
58
 
59
+ // Core tools - always registered
30
60
  registerJoinSwarmTool(server);
31
61
  registerPollTaskTool(server);
32
62
  registerGetSwarmTool(server);
@@ -36,5 +66,31 @@ export function createServer() {
36
66
  registerStoreProgressTool(server);
37
67
  registerMyAgentInfoTool(server);
38
68
 
69
+ // Task pool capability - task pool operations (create unassigned, claim, release, accept, reject)
70
+ if (hasCapability("task-pool")) {
71
+ registerTaskActionTool(server);
72
+ }
73
+
74
+ // Messaging capability - channel-based communication
75
+ if (hasCapability("messaging")) {
76
+ registerListChannelsTool(server);
77
+ registerCreateChannelTool(server);
78
+ registerPostMessageTool(server);
79
+ registerReadMessagesTool(server);
80
+ }
81
+
82
+ // Profiles capability - agent profile management
83
+ if (hasCapability("profiles")) {
84
+ registerUpdateProfileTool(server);
85
+ }
86
+
87
+ // Services capability - PM2/background service registry
88
+ if (hasCapability("services")) {
89
+ registerRegisterServiceTool(server);
90
+ registerUnregisterServiceTool(server);
91
+ registerListServicesTool(server);
92
+ registerUpdateServiceStatusTool(server);
93
+ }
94
+
39
95
  return server;
40
96
  }