@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.
- package/.claude/settings.local.json +20 -1
- package/.dockerignore +3 -0
- package/.env.docker.example +22 -1
- package/.env.example +17 -0
- package/.github/workflows/docker-publish.yml +92 -0
- package/CONTRIBUTING.md +270 -0
- package/DEPLOYMENT.md +391 -0
- package/Dockerfile.worker +29 -1
- package/FAQ.md +19 -0
- package/LICENSE +21 -0
- package/MCP.md +249 -0
- package/README.md +105 -185
- package/assets/agent-swarm-logo-orange.png +0 -0
- package/assets/agent-swarm-logo.png +0 -0
- package/assets/agent-swarm.png +0 -0
- package/deploy/docker-push.ts +30 -0
- package/docker-compose.example.yml +137 -0
- package/docker-entrypoint.sh +223 -7
- package/package.json +13 -4
- package/{cc-plugin → plugin}/.claude-plugin/plugin.json +1 -1
- package/plugin/README.md +1 -0
- package/plugin/agents/.gitkeep +0 -0
- package/plugin/agents/codebase-analyzer.md +143 -0
- package/plugin/agents/codebase-locator.md +122 -0
- package/plugin/agents/codebase-pattern-finder.md +227 -0
- package/plugin/agents/web-search-researcher.md +109 -0
- package/plugin/commands/create-plan.md +415 -0
- package/plugin/commands/implement-plan.md +89 -0
- package/plugin/commands/research.md +200 -0
- package/plugin/commands/start-leader.md +101 -0
- package/plugin/commands/start-worker.md +56 -0
- package/plugin/commands/swarm-chat.md +78 -0
- package/plugin/commands/todos.md +66 -0
- package/plugin/commands/work-on-task.md +44 -0
- package/plugin/skills/.gitkeep +0 -0
- package/scripts/generate-mcp-docs.ts +415 -0
- package/slack-manifest.json +69 -0
- package/src/be/db.ts +1431 -25
- package/src/cli.tsx +135 -11
- package/src/commands/lead.ts +13 -0
- package/src/commands/runner.ts +255 -0
- package/src/commands/setup.tsx +5 -5
- package/src/commands/worker.ts +8 -220
- package/src/hooks/hook.ts +108 -14
- package/src/http.ts +361 -5
- package/src/prompts/base-prompt.ts +131 -0
- package/src/server.ts +56 -0
- package/src/slack/app.ts +73 -0
- package/src/slack/commands.ts +88 -0
- package/src/slack/handlers.ts +281 -0
- package/src/slack/index.ts +3 -0
- package/src/slack/responses.ts +175 -0
- package/src/slack/router.ts +170 -0
- package/src/slack/types.ts +20 -0
- package/src/slack/watcher.ts +119 -0
- package/src/tools/create-channel.ts +80 -0
- package/src/tools/get-tasks.ts +54 -21
- package/src/tools/join-swarm.ts +28 -4
- package/src/tools/list-channels.ts +37 -0
- package/src/tools/list-services.ts +110 -0
- package/src/tools/poll-task.ts +47 -3
- package/src/tools/post-message.ts +87 -0
- package/src/tools/read-messages.ts +192 -0
- package/src/tools/register-service.ts +118 -0
- package/src/tools/send-task.ts +80 -7
- package/src/tools/store-progress.ts +9 -3
- package/src/tools/task-action.ts +211 -0
- package/src/tools/unregister-service.ts +110 -0
- package/src/tools/update-profile.ts +105 -0
- package/src/tools/update-service-status.ts +118 -0
- package/src/types.ts +110 -3
- package/src/utils/pretty-print.ts +224 -0
- package/thoughts/shared/plans/.gitkeep +0 -0
- package/thoughts/shared/plans/2025-12-18-inverse-teleport.md +1142 -0
- package/thoughts/shared/plans/2025-12-18-slack-integration.md +1195 -0
- package/thoughts/shared/plans/2025-12-19-agent-log-streaming.md +732 -0
- package/thoughts/shared/plans/2025-12-19-role-based-swarm-plugin.md +361 -0
- package/thoughts/shared/plans/2025-12-20-mobile-responsive-ui.md +501 -0
- package/thoughts/shared/plans/2025-12-20-startup-team-swarm.md +560 -0
- package/thoughts/shared/research/.gitkeep +0 -0
- package/thoughts/shared/research/2025-12-18-slack-integration.md +442 -0
- package/thoughts/shared/research/2025-12-19-agent-log-streaming.md +339 -0
- package/thoughts/shared/research/2025-12-19-agent-secrets-cli-research.md +390 -0
- package/thoughts/shared/research/2025-12-21-gemini-cli-integration.md +376 -0
- package/thoughts/shared/research/2025-12-22-setup-experience-improvements.md +264 -0
- package/tsconfig.json +3 -1
- package/ui/bun.lock +692 -0
- package/ui/index.html +22 -0
- package/ui/package.json +32 -0
- package/ui/pnpm-lock.yaml +3034 -0
- package/ui/postcss.config.js +6 -0
- package/ui/public/logo.png +0 -0
- package/ui/src/App.tsx +43 -0
- package/ui/src/components/ActivityFeed.tsx +415 -0
- package/ui/src/components/AgentDetailPanel.tsx +534 -0
- package/ui/src/components/AgentsPanel.tsx +549 -0
- package/ui/src/components/ChatPanel.tsx +1820 -0
- package/ui/src/components/ConfigModal.tsx +232 -0
- package/ui/src/components/Dashboard.tsx +534 -0
- package/ui/src/components/Header.tsx +168 -0
- package/ui/src/components/ServicesPanel.tsx +612 -0
- package/ui/src/components/StatsBar.tsx +288 -0
- package/ui/src/components/StatusBadge.tsx +124 -0
- package/ui/src/components/TaskDetailPanel.tsx +807 -0
- package/ui/src/components/TasksPanel.tsx +575 -0
- package/ui/src/hooks/queries.ts +170 -0
- package/ui/src/index.css +235 -0
- package/ui/src/lib/api.ts +161 -0
- package/ui/src/lib/config.ts +35 -0
- package/ui/src/lib/theme.ts +214 -0
- package/ui/src/lib/utils.ts +48 -0
- package/ui/src/main.tsx +32 -0
- package/ui/src/types/api.ts +164 -0
- package/ui/src/vite-env.d.ts +1 -0
- package/ui/tailwind.config.js +35 -0
- package/ui/tsconfig.json +31 -0
- package/ui/vite.config.ts +22 -0
- package/cc-plugin/README.md +0 -49
- package/cc-plugin/commands/setup-leader.md +0 -73
- package/cc-plugin/commands/start-worker.md +0 -64
- package/docker-compose.worker.yml +0 -35
- package/example-req-meta.json +0 -24
- /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 {
|
|
12
|
-
|
|
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
|
}
|