@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
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
Binary file
package/ui/src/App.tsx ADDED
@@ -0,0 +1,43 @@
1
+ import { useState, useEffect } from "react";
2
+ import Box from "@mui/joy/Box";
3
+ import { getConfig } from "./lib/config";
4
+ import ConfigModal from "./components/ConfigModal";
5
+ import Dashboard from "./components/Dashboard";
6
+
7
+ export default function App() {
8
+ const [configOpen, setConfigOpen] = useState(false);
9
+ const [isConfigured, setIsConfigured] = useState(false);
10
+
11
+ useEffect(() => {
12
+ const config = getConfig();
13
+ if (!config.apiUrl) {
14
+ setConfigOpen(true);
15
+ } else {
16
+ setIsConfigured(true);
17
+ }
18
+ }, []);
19
+
20
+ const handleConfigSave = () => {
21
+ setConfigOpen(false);
22
+ setIsConfigured(true);
23
+ };
24
+
25
+ return (
26
+ <Box
27
+ sx={{
28
+ minHeight: "100vh",
29
+ bgcolor: "background.body",
30
+ }}
31
+ >
32
+ <ConfigModal
33
+ open={configOpen || !isConfigured}
34
+ onClose={() => isConfigured && setConfigOpen(false)}
35
+ onSave={handleConfigSave}
36
+ blocking={!isConfigured}
37
+ />
38
+ {isConfigured && (
39
+ <Dashboard onSettingsClick={() => setConfigOpen(true)} />
40
+ )}
41
+ </Box>
42
+ );
43
+ }
@@ -0,0 +1,415 @@
1
+ import { useMemo } from "react";
2
+ import Box from "@mui/joy/Box";
3
+ import Card from "@mui/joy/Card";
4
+ import Typography from "@mui/joy/Typography";
5
+ import Link from "@mui/joy/Link";
6
+ import { useColorScheme } from "@mui/joy/styles";
7
+ import { useLogs, useAgents, useChannels } from "../hooks/queries";
8
+ import { formatSmartTime } from "../lib/utils";
9
+ import type { AgentLog } from "../types/api";
10
+
11
+ interface ActivityFeedProps {
12
+ onNavigateToAgent?: (agentId: string) => void;
13
+ onNavigateToTask?: (taskId: string) => void;
14
+ onNavigateToChat?: (channelId: string, messageId?: string) => void;
15
+ }
16
+
17
+ export default function ActivityFeed({ onNavigateToAgent, onNavigateToTask, onNavigateToChat }: ActivityFeedProps) {
18
+ const { data: logs, isLoading } = useLogs(30);
19
+ const { data: agents } = useAgents();
20
+ const { data: channels } = useChannels();
21
+ const { mode } = useColorScheme();
22
+ const isDark = mode === "dark";
23
+
24
+ // Create agent name lookup
25
+ const agentNames = useMemo(() => {
26
+ const map = new Map<string, string>();
27
+ agents?.forEach((agent) => map.set(agent.id, agent.name));
28
+ return map;
29
+ }, [agents]);
30
+
31
+ // Create channel name lookup
32
+ const channelNames = useMemo(() => {
33
+ const map = new Map<string, string>();
34
+ channels?.forEach((channel) => map.set(channel.id, channel.name));
35
+ return map;
36
+ }, [channels]);
37
+
38
+ const colors = {
39
+ amber: isDark ? "#F5A623" : "#D48806",
40
+ dormant: isDark ? "#6B5344" : "#A89A7C",
41
+ honey: isDark ? "#FFB84D" : "#B87300",
42
+ blue: "#3B82F6",
43
+ gold: isDark ? "#D4A574" : "#8B6914",
44
+ warmGray: isDark ? "#C9B896" : "#8B7355",
45
+ tertiary: isDark ? "#8B7355" : "#6B5344",
46
+ hoverBg: isDark ? "rgba(245, 166, 35, 0.03)" : "rgba(212, 136, 6, 0.03)",
47
+ purple: isDark ? "#A855F7" : "#7C3AED",
48
+ };
49
+
50
+ const getEventColor = (eventType: string) => {
51
+ switch (eventType) {
52
+ case "agent_joined":
53
+ return colors.amber;
54
+ case "agent_left":
55
+ return colors.dormant;
56
+ case "agent_status_change":
57
+ return colors.honey;
58
+ case "task_created":
59
+ return colors.blue;
60
+ case "task_status_change":
61
+ return colors.gold;
62
+ case "task_progress":
63
+ return colors.warmGray;
64
+ case "channel_message":
65
+ return colors.purple;
66
+ default:
67
+ return colors.tertiary;
68
+ }
69
+ };
70
+
71
+ const getEventGlow = (eventType: string) => {
72
+ const color = getEventColor(eventType);
73
+ return isDark ? `0 0 8px ${color}66` : `0 0 4px ${color}44`;
74
+ };
75
+
76
+ const renderEventContent = (log: AgentLog) => {
77
+ const agentName = log.agentId ? (agentNames.get(log.agentId) || log.agentId.slice(0, 8)) : null;
78
+
79
+ const agentLink = log.agentId && onNavigateToAgent ? (
80
+ <Link
81
+ component="button"
82
+ onClick={(e) => {
83
+ e.stopPropagation();
84
+ onNavigateToAgent(log.agentId!);
85
+ }}
86
+ sx={{
87
+ fontFamily: "'Space Grotesk', sans-serif",
88
+ fontSize: "0.75rem",
89
+ fontWeight: 600,
90
+ color: colors.amber,
91
+ textDecoration: "none",
92
+ cursor: "pointer",
93
+ whiteSpace: "nowrap",
94
+ "&:hover": {
95
+ textDecoration: "underline",
96
+ color: colors.honey,
97
+ },
98
+ }}
99
+ >
100
+ {agentName}
101
+ </Link>
102
+ ) : log.agentId ? (
103
+ <span style={{ fontWeight: 600, color: colors.amber, whiteSpace: "nowrap" }}>{agentName}</span>
104
+ ) : null;
105
+
106
+ const taskLink = log.taskId && onNavigateToTask ? (
107
+ <Link
108
+ component="button"
109
+ onClick={(e) => {
110
+ e.stopPropagation();
111
+ onNavigateToTask(log.taskId!);
112
+ }}
113
+ sx={{
114
+ fontFamily: "'JetBrains Mono', monospace",
115
+ fontSize: "0.7rem",
116
+ color: colors.gold,
117
+ textDecoration: "none",
118
+ cursor: "pointer",
119
+ bgcolor: isDark ? "rgba(212, 165, 116, 0.1)" : "rgba(139, 105, 20, 0.08)",
120
+ px: 0.75,
121
+ py: 0.25,
122
+ borderRadius: "4px",
123
+ "&:hover": {
124
+ textDecoration: "underline",
125
+ bgcolor: isDark ? "rgba(212, 165, 116, 0.15)" : "rgba(139, 105, 20, 0.12)",
126
+ },
127
+ }}
128
+ >
129
+ #{log.taskId.slice(0, 8)}
130
+ </Link>
131
+ ) : log.taskId ? (
132
+ <span style={{
133
+ fontFamily: "'JetBrains Mono', monospace",
134
+ fontSize: "0.7rem",
135
+ backgroundColor: isDark ? "rgba(212, 165, 116, 0.1)" : "rgba(139, 105, 20, 0.08)",
136
+ padding: "2px 6px",
137
+ borderRadius: "4px",
138
+ }}>
139
+ #{log.taskId.slice(0, 8)}
140
+ </span>
141
+ ) : null;
142
+
143
+ // Format progress messages nicely
144
+ const formatProgress = (value: string | null | undefined) => {
145
+ if (!value) return null;
146
+ // Truncate long progress messages
147
+ const maxLen = 60;
148
+ const truncated = value.length > maxLen ? value.slice(0, maxLen) + "..." : value;
149
+ return (
150
+ <Box
151
+ component="span"
152
+ sx={{
153
+ display: "block",
154
+ mt: 0.5,
155
+ pl: 1.5,
156
+ borderLeft: "2px solid",
157
+ borderColor: colors.warmGray,
158
+ fontStyle: "italic",
159
+ color: "text.secondary",
160
+ fontSize: "0.7rem",
161
+ }}
162
+ >
163
+ {truncated}
164
+ </Box>
165
+ );
166
+ };
167
+
168
+ switch (log.eventType) {
169
+ case "agent_joined":
170
+ return <>{agentLink} joined the swarm</>;
171
+ case "agent_left":
172
+ return <>{agentLink} left the swarm</>;
173
+ case "agent_status_change":
174
+ return (
175
+ <>
176
+ {agentLink} is now{" "}
177
+ <Box
178
+ component="span"
179
+ sx={{
180
+ fontWeight: 600,
181
+ color: log.newValue === "busy" ? colors.amber : log.newValue === "idle" ? colors.gold : colors.dormant,
182
+ }}
183
+ >
184
+ {log.newValue}
185
+ </Box>
186
+ </>
187
+ );
188
+ case "task_created":
189
+ return (
190
+ <>
191
+ New task {taskLink} created
192
+ {log.newValue && formatProgress(log.newValue)}
193
+ </>
194
+ );
195
+ case "task_status_change":
196
+ return (
197
+ <>
198
+ Task {taskLink} →{" "}
199
+ <Box
200
+ component="span"
201
+ sx={{
202
+ fontWeight: 600,
203
+ color: log.newValue === "completed" ? "#22C55E" : log.newValue === "failed" ? colors.dormant : colors.gold,
204
+ }}
205
+ >
206
+ {log.newValue}
207
+ </Box>
208
+ </>
209
+ );
210
+ case "task_progress":
211
+ return (
212
+ <>
213
+ {taskLink}
214
+ {formatProgress(log.newValue)}
215
+ </>
216
+ );
217
+ case "channel_message": {
218
+ // Parse metadata to get channelId and messageId
219
+ let channelId: string | undefined;
220
+ let messageId: string | undefined;
221
+ if (log.metadata) {
222
+ try {
223
+ const meta = JSON.parse(log.metadata);
224
+ channelId = meta.channelId;
225
+ messageId = meta.messageId;
226
+ } catch {
227
+ // ignore parse errors
228
+ }
229
+ }
230
+ const channelName = channelId ? (channelNames.get(channelId) || "chat") : "chat";
231
+ const senderName = log.agentId ? agentLink : (
232
+ <span style={{ fontWeight: 600, color: colors.warmGray }}>Human</span>
233
+ );
234
+
235
+ const channelLink = onNavigateToChat && channelId ? (
236
+ <Link
237
+ component="button"
238
+ onClick={(e) => {
239
+ e.stopPropagation();
240
+ onNavigateToChat(channelId!, messageId);
241
+ }}
242
+ sx={{
243
+ fontFamily: "'Space Grotesk', sans-serif",
244
+ fontSize: "0.75rem",
245
+ fontWeight: 600,
246
+ color: colors.purple,
247
+ textDecoration: "none",
248
+ cursor: "pointer",
249
+ "&:hover": {
250
+ textDecoration: "underline",
251
+ },
252
+ }}
253
+ >
254
+ #{channelName}
255
+ </Link>
256
+ ) : (
257
+ <span style={{ fontWeight: 600, color: colors.purple }}>#{channelName}</span>
258
+ );
259
+
260
+ return (
261
+ <>
262
+ {senderName} in {channelLink}
263
+ </>
264
+ );
265
+ }
266
+ default:
267
+ return <>{log.eventType}</>;
268
+ }
269
+ };
270
+
271
+ return (
272
+ <Card
273
+ variant="outlined"
274
+ className="card-hover"
275
+ sx={{
276
+ p: 0,
277
+ overflow: "hidden",
278
+ height: "100%",
279
+ display: "flex",
280
+ flexDirection: "column",
281
+ bgcolor: "background.surface",
282
+ borderColor: "neutral.outlinedBorder",
283
+ }}
284
+ >
285
+ {/* Header */}
286
+ <Box
287
+ sx={{
288
+ px: 2,
289
+ py: 1.5,
290
+ borderBottom: "1px solid",
291
+ borderColor: "neutral.outlinedBorder",
292
+ bgcolor: "background.level1",
293
+ flexShrink: 0,
294
+ display: "flex",
295
+ alignItems: "center",
296
+ gap: 1,
297
+ }}
298
+ >
299
+ {/* Hex accent */}
300
+ <Box
301
+ sx={{
302
+ width: 8,
303
+ height: 10,
304
+ clipPath: "polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)",
305
+ bgcolor: colors.blue,
306
+ boxShadow: isDark ? "0 0 8px rgba(59, 130, 246, 0.5)" : "0 0 4px rgba(59, 130, 246, 0.3)",
307
+ }}
308
+ />
309
+ <Typography
310
+ level="title-md"
311
+ sx={{
312
+ fontFamily: "display",
313
+ fontWeight: 600,
314
+ color: colors.blue,
315
+ letterSpacing: "0.03em",
316
+ }}
317
+ >
318
+ ACTIVITY
319
+ </Typography>
320
+ </Box>
321
+
322
+ {/* Timeline */}
323
+ <Box sx={{ p: 2, flex: 1, overflowY: "auto" }}>
324
+ {isLoading ? (
325
+ <Typography sx={{ fontFamily: "code", color: "text.tertiary" }}>
326
+ Loading activity...
327
+ </Typography>
328
+ ) : !logs || logs.length === 0 ? (
329
+ <Typography sx={{ fontFamily: "code", color: "text.tertiary" }}>
330
+ No recent activity
331
+ </Typography>
332
+ ) : (
333
+ <Box
334
+ sx={{
335
+ display: "flex",
336
+ flexDirection: "column",
337
+ gap: 0,
338
+ position: "relative",
339
+ // Vertical timeline line
340
+ "&::before": {
341
+ content: '""',
342
+ position: "absolute",
343
+ left: 5,
344
+ top: 12,
345
+ bottom: 12,
346
+ width: 2,
347
+ bgcolor: "neutral.outlinedBorder",
348
+ borderRadius: 1,
349
+ },
350
+ }}
351
+ >
352
+ {logs.map((log, index) => (
353
+ <Box
354
+ key={log.id}
355
+ sx={{
356
+ display: "flex",
357
+ gap: 2,
358
+ py: 1.5,
359
+ pl: 0,
360
+ position: "relative",
361
+ transition: "background-color 0.2s ease",
362
+ "&:hover": {
363
+ bgcolor: colors.hoverBg,
364
+ },
365
+ }}
366
+ >
367
+ {/* Timeline node */}
368
+ <Box
369
+ sx={{
370
+ width: 12,
371
+ height: 12,
372
+ borderRadius: "50%",
373
+ bgcolor: getEventColor(log.eventType),
374
+ boxShadow: getEventGlow(log.eventType),
375
+ flexShrink: 0,
376
+ position: "relative",
377
+ zIndex: 1,
378
+ border: "2px solid",
379
+ borderColor: "background.surface",
380
+ animation: index === 0 ? "pulse-amber 2s ease-in-out infinite" : undefined,
381
+ }}
382
+ />
383
+
384
+ {/* Content */}
385
+ <Box sx={{ flex: 1, minWidth: 0 }}>
386
+ <Typography
387
+ component="div"
388
+ sx={{
389
+ fontFamily: "code",
390
+ fontSize: "0.75rem",
391
+ color: "text.primary",
392
+ mb: 0.25,
393
+ lineHeight: 1.4,
394
+ }}
395
+ >
396
+ {renderEventContent(log)}
397
+ </Typography>
398
+ <Typography
399
+ sx={{
400
+ fontFamily: "code",
401
+ fontSize: "0.65rem",
402
+ color: "text.tertiary",
403
+ }}
404
+ >
405
+ {formatSmartTime(log.createdAt)}
406
+ </Typography>
407
+ </Box>
408
+ </Box>
409
+ ))}
410
+ </Box>
411
+ )}
412
+ </Box>
413
+ </Card>
414
+ );
415
+ }