@desplega.ai/agent-swarm 1.2.1 → 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 (119) hide show
  1. package/.claude/settings.local.json +20 -1
  2. package/.env.docker.example +22 -1
  3. package/.env.example +17 -0
  4. package/.github/workflows/docker-publish.yml +92 -0
  5. package/CONTRIBUTING.md +270 -0
  6. package/DEPLOYMENT.md +391 -0
  7. package/Dockerfile.worker +29 -1
  8. package/FAQ.md +19 -0
  9. package/LICENSE +21 -0
  10. package/MCP.md +249 -0
  11. package/README.md +103 -207
  12. package/assets/agent-swarm-logo-orange.png +0 -0
  13. package/assets/agent-swarm-logo.png +0 -0
  14. package/docker-compose.example.yml +137 -0
  15. package/docker-entrypoint.sh +223 -7
  16. package/package.json +8 -3
  17. package/{cc-plugin → plugin}/.claude-plugin/plugin.json +1 -1
  18. package/plugin/README.md +1 -0
  19. package/plugin/agents/.gitkeep +0 -0
  20. package/plugin/agents/codebase-analyzer.md +143 -0
  21. package/plugin/agents/codebase-locator.md +122 -0
  22. package/plugin/agents/codebase-pattern-finder.md +227 -0
  23. package/plugin/agents/web-search-researcher.md +109 -0
  24. package/plugin/commands/create-plan.md +415 -0
  25. package/plugin/commands/implement-plan.md +89 -0
  26. package/plugin/commands/research.md +200 -0
  27. package/plugin/commands/start-leader.md +101 -0
  28. package/plugin/commands/start-worker.md +56 -0
  29. package/plugin/commands/swarm-chat.md +78 -0
  30. package/plugin/commands/todos.md +66 -0
  31. package/plugin/commands/work-on-task.md +44 -0
  32. package/plugin/skills/.gitkeep +0 -0
  33. package/scripts/generate-mcp-docs.ts +415 -0
  34. package/slack-manifest.json +69 -0
  35. package/src/be/db.ts +1431 -25
  36. package/src/cli.tsx +135 -11
  37. package/src/commands/lead.ts +13 -0
  38. package/src/commands/runner.ts +255 -0
  39. package/src/commands/worker.ts +8 -220
  40. package/src/hooks/hook.ts +102 -14
  41. package/src/http.ts +361 -5
  42. package/src/prompts/base-prompt.ts +131 -0
  43. package/src/server.ts +56 -0
  44. package/src/slack/app.ts +73 -0
  45. package/src/slack/commands.ts +88 -0
  46. package/src/slack/handlers.ts +281 -0
  47. package/src/slack/index.ts +3 -0
  48. package/src/slack/responses.ts +175 -0
  49. package/src/slack/router.ts +170 -0
  50. package/src/slack/types.ts +20 -0
  51. package/src/slack/watcher.ts +119 -0
  52. package/src/tools/create-channel.ts +80 -0
  53. package/src/tools/get-tasks.ts +54 -21
  54. package/src/tools/join-swarm.ts +28 -4
  55. package/src/tools/list-channels.ts +37 -0
  56. package/src/tools/list-services.ts +110 -0
  57. package/src/tools/poll-task.ts +46 -3
  58. package/src/tools/post-message.ts +87 -0
  59. package/src/tools/read-messages.ts +192 -0
  60. package/src/tools/register-service.ts +118 -0
  61. package/src/tools/send-task.ts +80 -7
  62. package/src/tools/store-progress.ts +9 -3
  63. package/src/tools/task-action.ts +211 -0
  64. package/src/tools/unregister-service.ts +110 -0
  65. package/src/tools/update-profile.ts +105 -0
  66. package/src/tools/update-service-status.ts +118 -0
  67. package/src/types.ts +110 -3
  68. package/src/utils/pretty-print.ts +224 -0
  69. package/thoughts/shared/plans/.gitkeep +0 -0
  70. package/thoughts/shared/plans/2025-12-18-inverse-teleport.md +1142 -0
  71. package/thoughts/shared/plans/2025-12-18-slack-integration.md +1195 -0
  72. package/thoughts/shared/plans/2025-12-19-agent-log-streaming.md +732 -0
  73. package/thoughts/shared/plans/2025-12-19-role-based-swarm-plugin.md +361 -0
  74. package/thoughts/shared/plans/2025-12-20-mobile-responsive-ui.md +501 -0
  75. package/thoughts/shared/plans/2025-12-20-startup-team-swarm.md +560 -0
  76. package/thoughts/shared/research/.gitkeep +0 -0
  77. package/thoughts/shared/research/2025-12-18-slack-integration.md +442 -0
  78. package/thoughts/shared/research/2025-12-19-agent-log-streaming.md +339 -0
  79. package/thoughts/shared/research/2025-12-19-agent-secrets-cli-research.md +390 -0
  80. package/thoughts/shared/research/2025-12-21-gemini-cli-integration.md +376 -0
  81. package/thoughts/shared/research/2025-12-22-setup-experience-improvements.md +264 -0
  82. package/tsconfig.json +3 -1
  83. package/ui/bun.lock +692 -0
  84. package/ui/index.html +22 -0
  85. package/ui/package.json +32 -0
  86. package/ui/pnpm-lock.yaml +3034 -0
  87. package/ui/postcss.config.js +6 -0
  88. package/ui/public/logo.png +0 -0
  89. package/ui/src/App.tsx +43 -0
  90. package/ui/src/components/ActivityFeed.tsx +415 -0
  91. package/ui/src/components/AgentDetailPanel.tsx +534 -0
  92. package/ui/src/components/AgentsPanel.tsx +549 -0
  93. package/ui/src/components/ChatPanel.tsx +1820 -0
  94. package/ui/src/components/ConfigModal.tsx +232 -0
  95. package/ui/src/components/Dashboard.tsx +534 -0
  96. package/ui/src/components/Header.tsx +168 -0
  97. package/ui/src/components/ServicesPanel.tsx +612 -0
  98. package/ui/src/components/StatsBar.tsx +288 -0
  99. package/ui/src/components/StatusBadge.tsx +124 -0
  100. package/ui/src/components/TaskDetailPanel.tsx +807 -0
  101. package/ui/src/components/TasksPanel.tsx +575 -0
  102. package/ui/src/hooks/queries.ts +170 -0
  103. package/ui/src/index.css +235 -0
  104. package/ui/src/lib/api.ts +161 -0
  105. package/ui/src/lib/config.ts +35 -0
  106. package/ui/src/lib/theme.ts +214 -0
  107. package/ui/src/lib/utils.ts +48 -0
  108. package/ui/src/main.tsx +32 -0
  109. package/ui/src/types/api.ts +164 -0
  110. package/ui/src/vite-env.d.ts +1 -0
  111. package/ui/tailwind.config.js +35 -0
  112. package/ui/tsconfig.json +31 -0
  113. package/ui/vite.config.ts +22 -0
  114. package/cc-plugin/README.md +0 -49
  115. package/cc-plugin/commands/setup-leader.md +0 -73
  116. package/cc-plugin/commands/start-worker.md +0 -64
  117. package/docker-compose.worker.yml +0 -35
  118. package/example-req-meta.json +0 -24
  119. /package/{cc-plugin → plugin}/hooks/hooks.json +0 -0
@@ -0,0 +1,534 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+ import Box from "@mui/joy/Box";
3
+ import Tabs from "@mui/joy/Tabs";
4
+ import TabList from "@mui/joy/TabList";
5
+ import Tab from "@mui/joy/Tab";
6
+ import TabPanel from "@mui/joy/TabPanel";
7
+ import { useColorScheme } from "@mui/joy/styles";
8
+ import Header from "./Header";
9
+ import StatsBar from "./StatsBar";
10
+ import AgentsPanel from "./AgentsPanel";
11
+ import TasksPanel from "./TasksPanel";
12
+ import ServicesPanel from "./ServicesPanel";
13
+ import ActivityFeed from "./ActivityFeed";
14
+ import AgentDetailPanel from "./AgentDetailPanel";
15
+ import TaskDetailPanel from "./TaskDetailPanel";
16
+ import ChatPanel from "./ChatPanel";
17
+ import type { TaskStatus } from "../types/api";
18
+
19
+ interface DashboardProps {
20
+ onSettingsClick: () => void;
21
+ }
22
+
23
+ function getUrlParams() {
24
+ const params = new URLSearchParams(window.location.search);
25
+ return {
26
+ tab: params.get("tab") as "agents" | "tasks" | "chat" | "services" | null,
27
+ agent: params.get("agent"),
28
+ task: params.get("task"),
29
+ channel: params.get("channel"),
30
+ thread: params.get("thread"),
31
+ agentStatus: params.get("agentStatus") as "all" | "busy" | "idle" | "offline" | null,
32
+ taskStatus: params.get("taskStatus") as TaskStatus | "all" | null,
33
+ expand: params.get("expand") === "true",
34
+ };
35
+ }
36
+
37
+ function updateUrl(params: {
38
+ tab?: string;
39
+ agent?: string | null;
40
+ task?: string | null;
41
+ channel?: string | null;
42
+ thread?: string | null;
43
+ agentStatus?: string | null;
44
+ taskStatus?: string | null;
45
+ expand?: boolean;
46
+ }) {
47
+ const url = new URL(window.location.href);
48
+
49
+ if (params.tab) {
50
+ url.searchParams.set("tab", params.tab);
51
+ }
52
+
53
+ if (params.agent) {
54
+ url.searchParams.set("agent", params.agent);
55
+ url.searchParams.delete("task");
56
+ } else if (params.agent === null) {
57
+ url.searchParams.delete("agent");
58
+ url.searchParams.delete("expand");
59
+ }
60
+
61
+ if (params.task) {
62
+ url.searchParams.set("task", params.task);
63
+ url.searchParams.delete("agent");
64
+ } else if (params.task === null) {
65
+ url.searchParams.delete("task");
66
+ url.searchParams.delete("expand");
67
+ }
68
+
69
+ if (params.channel) {
70
+ url.searchParams.set("channel", params.channel);
71
+ } else if (params.channel === null) {
72
+ url.searchParams.delete("channel");
73
+ url.searchParams.delete("thread");
74
+ }
75
+
76
+ if (params.thread) {
77
+ url.searchParams.set("thread", params.thread);
78
+ } else if (params.thread === null) {
79
+ url.searchParams.delete("thread");
80
+ }
81
+
82
+ if (params.agentStatus && params.agentStatus !== "all") {
83
+ url.searchParams.set("agentStatus", params.agentStatus);
84
+ } else if (params.agentStatus === "all" || params.agentStatus === null) {
85
+ url.searchParams.delete("agentStatus");
86
+ }
87
+
88
+ if (params.taskStatus && params.taskStatus !== "all") {
89
+ url.searchParams.set("taskStatus", params.taskStatus);
90
+ } else if (params.taskStatus === "all" || params.taskStatus === null) {
91
+ url.searchParams.delete("taskStatus");
92
+ }
93
+
94
+ if (params.expand === true) {
95
+ url.searchParams.set("expand", "true");
96
+ } else if (params.expand === false) {
97
+ url.searchParams.delete("expand");
98
+ }
99
+
100
+ window.history.replaceState({}, "", url.toString());
101
+ }
102
+
103
+ export default function Dashboard({ onSettingsClick }: DashboardProps) {
104
+ const [activeTab, setActiveTab] = useState<"agents" | "tasks" | "chat" | "services">("agents");
105
+ const [selectedAgentId, setSelectedAgentId] = useState<string | null>(null);
106
+ const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
107
+ const [selectedChannelId, setSelectedChannelId] = useState<string | null>(null);
108
+ const [selectedThreadId, setSelectedThreadId] = useState<string | null>(null);
109
+ const [preFilterAgentId, setPreFilterAgentId] = useState<string | undefined>(undefined);
110
+ const [agentStatusFilter, setAgentStatusFilter] = useState<"all" | "busy" | "idle" | "offline">("all");
111
+ const [taskStatusFilter, setTaskStatusFilter] = useState<TaskStatus | "all">("all");
112
+ const [expandDetail, setExpandDetail] = useState(false);
113
+
114
+ const { mode } = useColorScheme();
115
+ const isDark = mode === "dark";
116
+
117
+ const colors = {
118
+ amber: isDark ? "#F5A623" : "#D48806",
119
+ hoverBg: isDark ? "rgba(245, 166, 35, 0.08)" : "rgba(212, 136, 6, 0.08)",
120
+ };
121
+
122
+ // Read URL params on mount
123
+ useEffect(() => {
124
+ const params = getUrlParams();
125
+ if (params.tab === "tasks") {
126
+ setActiveTab("tasks");
127
+ if (params.task) {
128
+ setSelectedTaskId(params.task);
129
+ }
130
+ if (params.taskStatus) {
131
+ setTaskStatusFilter(params.taskStatus);
132
+ }
133
+ } else if (params.tab === "chat") {
134
+ setActiveTab("chat");
135
+ if (params.channel) {
136
+ setSelectedChannelId(params.channel);
137
+ }
138
+ if (params.thread) {
139
+ setSelectedThreadId(params.thread);
140
+ }
141
+ } else if (params.tab === "services") {
142
+ setActiveTab("services");
143
+ } else {
144
+ setActiveTab("agents");
145
+ if (params.agent) {
146
+ setSelectedAgentId(params.agent);
147
+ }
148
+ if (params.agentStatus) {
149
+ setAgentStatusFilter(params.agentStatus);
150
+ }
151
+ }
152
+ if (params.expand) {
153
+ setExpandDetail(true);
154
+ }
155
+ }, []);
156
+
157
+ // Update URL when agent selection changes
158
+ const handleSelectAgent = useCallback((agentId: string | null) => {
159
+ setSelectedAgentId(agentId);
160
+ // Reset expand when selecting a new agent or deselecting
161
+ setExpandDetail(false);
162
+ updateUrl({ tab: "agents", agent: agentId, expand: false });
163
+ }, []);
164
+
165
+ // Update URL when task selection changes
166
+ const handleSelectTask = useCallback((taskId: string | null) => {
167
+ setSelectedTaskId(taskId);
168
+ // Reset expand when selecting a new task or deselecting
169
+ setExpandDetail(false);
170
+ updateUrl({ tab: "tasks", task: taskId, expand: false });
171
+ }, []);
172
+
173
+ // Toggle expand state
174
+ const handleToggleExpand = useCallback(() => {
175
+ setExpandDetail((prev) => {
176
+ const newValue = !prev;
177
+ updateUrl({ expand: newValue });
178
+ return newValue;
179
+ });
180
+ }, []);
181
+
182
+ const handleGoToTasks = () => {
183
+ if (selectedAgentId) {
184
+ setPreFilterAgentId(selectedAgentId);
185
+ }
186
+ setSelectedAgentId(null);
187
+ setExpandDetail(false);
188
+ setActiveTab("tasks");
189
+ updateUrl({ tab: "tasks", agent: null, expand: false });
190
+ };
191
+
192
+ const handleTabChange = (_: unknown, value: string | number | null) => {
193
+ const tab = value as "agents" | "tasks" | "chat" | "services";
194
+ setActiveTab(tab);
195
+ // Clear selections, filters, and expand when switching tabs
196
+ setExpandDetail(false);
197
+ if (tab === "agents") {
198
+ setSelectedTaskId(null);
199
+ setSelectedChannelId(null);
200
+ setSelectedThreadId(null);
201
+ setPreFilterAgentId(undefined);
202
+ setTaskStatusFilter("all");
203
+ updateUrl({ tab: "agents", task: null, channel: null, taskStatus: null, expand: false });
204
+ } else if (tab === "tasks") {
205
+ setSelectedAgentId(null);
206
+ setSelectedChannelId(null);
207
+ setSelectedThreadId(null);
208
+ setAgentStatusFilter("all");
209
+ updateUrl({ tab: "tasks", agent: null, channel: null, agentStatus: null, expand: false });
210
+ } else if (tab === "services") {
211
+ setSelectedAgentId(null);
212
+ setSelectedTaskId(null);
213
+ setSelectedChannelId(null);
214
+ setSelectedThreadId(null);
215
+ setPreFilterAgentId(undefined);
216
+ setAgentStatusFilter("all");
217
+ setTaskStatusFilter("all");
218
+ updateUrl({ tab: "services", agent: null, task: null, channel: null, agentStatus: null, taskStatus: null, expand: false });
219
+ } else {
220
+ // chat tab
221
+ setSelectedAgentId(null);
222
+ setSelectedTaskId(null);
223
+ setPreFilterAgentId(undefined);
224
+ setAgentStatusFilter("all");
225
+ setTaskStatusFilter("all");
226
+ updateUrl({ tab: "chat", agent: null, task: null, agentStatus: null, taskStatus: null, expand: false });
227
+ }
228
+ };
229
+
230
+ // Navigation handlers for ActivityFeed
231
+ const handleNavigateToAgent = useCallback((agentId: string) => {
232
+ setActiveTab("agents");
233
+ setSelectedAgentId(agentId);
234
+ setSelectedTaskId(null);
235
+ setPreFilterAgentId(undefined);
236
+ setExpandDetail(false);
237
+ updateUrl({ tab: "agents", agent: agentId, expand: false });
238
+ }, []);
239
+
240
+ const handleNavigateToTask = useCallback((taskId: string) => {
241
+ setActiveTab("tasks");
242
+ setSelectedTaskId(taskId);
243
+ setSelectedAgentId(null);
244
+ setExpandDetail(false);
245
+ updateUrl({ tab: "tasks", task: taskId, expand: false });
246
+ }, []);
247
+
248
+ const handleNavigateToChat = useCallback((channelId: string, messageId?: string) => {
249
+ setActiveTab("chat");
250
+ setSelectedChannelId(channelId);
251
+ setSelectedThreadId(messageId || null);
252
+ setSelectedAgentId(null);
253
+ setSelectedTaskId(null);
254
+ setExpandDetail(false);
255
+ updateUrl({ tab: "chat", channel: channelId, thread: messageId || null, agent: null, task: null, expand: false });
256
+ }, []);
257
+
258
+ // Chat handlers
259
+ const handleSelectChannel = useCallback((channelId: string | null) => {
260
+ setSelectedChannelId(channelId);
261
+ setSelectedThreadId(null);
262
+ updateUrl({ channel: channelId, thread: null });
263
+ }, []);
264
+
265
+ const handleSelectThread = useCallback((threadId: string | null) => {
266
+ setSelectedThreadId(threadId);
267
+ updateUrl({ thread: threadId });
268
+ }, []);
269
+
270
+ // Filter change handlers with URL updates
271
+ const handleAgentStatusFilterChange = useCallback((status: "all" | "busy" | "idle" | "offline") => {
272
+ setAgentStatusFilter(status);
273
+ updateUrl({ agentStatus: status });
274
+ }, []);
275
+
276
+ const handleTaskStatusFilterChange = useCallback((status: TaskStatus | "all") => {
277
+ setTaskStatusFilter(status);
278
+ updateUrl({ taskStatus: status });
279
+ }, []);
280
+
281
+ // StatsBar handlers
282
+ const handleFilterAgents = useCallback((status: "all" | "busy" | "idle") => {
283
+ setAgentStatusFilter(status);
284
+ setActiveTab("agents");
285
+ updateUrl({ tab: "agents", agentStatus: status });
286
+ }, []);
287
+
288
+ const handleNavigateToTasksWithFilter = useCallback((status?: TaskStatus) => {
289
+ setActiveTab("tasks");
290
+ setTaskStatusFilter(status || "all");
291
+ setSelectedAgentId(null);
292
+ setPreFilterAgentId(undefined);
293
+ updateUrl({ tab: "tasks", agent: null, taskStatus: status || "all" });
294
+ }, []);
295
+
296
+ return (
297
+ <Box
298
+ className="honeycomb-bg"
299
+ sx={{
300
+ height: "100vh",
301
+ bgcolor: "background.body",
302
+ display: "flex",
303
+ flexDirection: "column",
304
+ overflow: "hidden",
305
+ }}
306
+ >
307
+ <Header onSettingsClick={onSettingsClick} />
308
+
309
+ {/* Tabs */}
310
+ <Box sx={{ px: { xs: 1.5, sm: 2, md: 3 }, pt: { xs: 1.5, md: 2 }, pb: { xs: 2, md: 3 }, flex: 1, display: "flex", flexDirection: "column", minHeight: 0 }}>
311
+ <Tabs
312
+ value={activeTab}
313
+ onChange={handleTabChange}
314
+ sx={{
315
+ bgcolor: "transparent",
316
+ "--Tabs-gap": "0px",
317
+ flex: 1,
318
+ display: "flex",
319
+ flexDirection: "column",
320
+ minHeight: 0,
321
+ }}
322
+ >
323
+ <TabList
324
+ sx={{
325
+ gap: 0.5,
326
+ bgcolor: "transparent",
327
+ borderBottom: "1px solid",
328
+ borderColor: "neutral.outlinedBorder",
329
+ overflowX: { xs: "auto", md: "visible" },
330
+ flexWrap: { xs: "nowrap", md: "wrap" },
331
+ "& .MuiTab-root": {
332
+ fontFamily: "code",
333
+ fontSize: { xs: "0.7rem", md: "0.8rem" },
334
+ letterSpacing: "0.03em",
335
+ fontWeight: 600,
336
+ color: "text.tertiary",
337
+ bgcolor: "transparent",
338
+ border: "1px solid transparent",
339
+ borderBottom: "none",
340
+ borderRadius: "6px 6px 0 0",
341
+ px: { xs: 2, md: 3 },
342
+ py: 1,
343
+ transition: "all 0.2s ease",
344
+ "&:hover": {
345
+ color: "text.secondary",
346
+ bgcolor: colors.hoverBg,
347
+ },
348
+ "&.Mui-selected": {
349
+ color: colors.amber,
350
+ bgcolor: "background.surface",
351
+ borderColor: "neutral.outlinedBorder",
352
+ borderBottomColor: "background.surface",
353
+ marginBottom: "-1px",
354
+ },
355
+ },
356
+ }}
357
+ >
358
+ <Tab value="agents">AGENTS</Tab>
359
+ <Tab value="tasks">TASKS</Tab>
360
+ <Tab value="chat">CHAT</Tab>
361
+ <Tab value="services">SERVICES</Tab>
362
+ </TabList>
363
+
364
+ {/* Agents Tab */}
365
+ <TabPanel
366
+ value="agents"
367
+ sx={{
368
+ p: 0,
369
+ pt: 2,
370
+ flex: 1,
371
+ minHeight: 0,
372
+ "&[hidden]": {
373
+ display: "none",
374
+ },
375
+ }}
376
+ >
377
+ <Box
378
+ sx={{
379
+ height: "100%",
380
+ display: "flex",
381
+ flexDirection: { xs: "column", lg: "row" },
382
+ gap: { xs: 2, md: 3 },
383
+ }}
384
+ >
385
+ {/* Main Content - hidden when expanded or when detail selected on mobile */}
386
+ {!(selectedAgentId && expandDetail) && (
387
+ <Box
388
+ sx={{
389
+ flex: 1,
390
+ display: {
391
+ xs: selectedAgentId ? "none" : "flex",
392
+ md: "flex",
393
+ },
394
+ flexDirection: { xs: "column", lg: "row" },
395
+ gap: { xs: 2, md: 3 },
396
+ minWidth: 0,
397
+ }}
398
+ >
399
+ {/* Agents Panel */}
400
+ <Box sx={{ flex: 2, minWidth: 0, display: "flex", flexDirection: "column", gap: 2 }}>
401
+ <StatsBar
402
+ onFilterAgents={handleFilterAgents}
403
+ onNavigateToTasks={handleNavigateToTasksWithFilter}
404
+ />
405
+ <AgentsPanel
406
+ selectedAgentId={selectedAgentId}
407
+ onSelectAgent={handleSelectAgent}
408
+ statusFilter={agentStatusFilter}
409
+ onStatusFilterChange={handleAgentStatusFilterChange}
410
+ />
411
+ </Box>
412
+
413
+ {/* Activity Feed - hidden on mobile */}
414
+ <Box sx={{ flex: 1, minWidth: 0, display: { xs: "none", lg: "block" } }}>
415
+ <ActivityFeed
416
+ onNavigateToAgent={handleNavigateToAgent}
417
+ onNavigateToTask={handleNavigateToTask}
418
+ onNavigateToChat={handleNavigateToChat}
419
+ />
420
+ </Box>
421
+ </Box>
422
+ )}
423
+
424
+ {/* Agent Detail Panel */}
425
+ {selectedAgentId && (
426
+ <AgentDetailPanel
427
+ agentId={selectedAgentId}
428
+ onClose={() => handleSelectAgent(null)}
429
+ onGoToTasks={handleGoToTasks}
430
+ expanded={expandDetail}
431
+ onToggleExpand={handleToggleExpand}
432
+ />
433
+ )}
434
+ </Box>
435
+ </TabPanel>
436
+
437
+ {/* Tasks Tab */}
438
+ <TabPanel
439
+ value="tasks"
440
+ sx={{
441
+ p: 0,
442
+ pt: 2,
443
+ flex: 1,
444
+ minHeight: 0,
445
+ "&[hidden]": {
446
+ display: "none",
447
+ },
448
+ }}
449
+ >
450
+ <Box
451
+ sx={{
452
+ height: "100%",
453
+ display: "flex",
454
+ flexDirection: { xs: "column", lg: "row" },
455
+ gap: { xs: 2, md: 3 },
456
+ }}
457
+ >
458
+ {/* Tasks Panel - hidden when expanded or when detail selected on mobile */}
459
+ {!(selectedTaskId && expandDetail) && (
460
+ <Box
461
+ sx={{
462
+ flex: 1,
463
+ minWidth: 0,
464
+ display: {
465
+ xs: selectedTaskId ? "none" : "block",
466
+ md: "block",
467
+ },
468
+ }}
469
+ >
470
+ <TasksPanel
471
+ selectedTaskId={selectedTaskId}
472
+ onSelectTask={handleSelectTask}
473
+ preFilterAgentId={preFilterAgentId}
474
+ statusFilter={taskStatusFilter}
475
+ onStatusFilterChange={handleTaskStatusFilterChange}
476
+ />
477
+ </Box>
478
+ )}
479
+
480
+ {/* Task Detail Panel */}
481
+ {selectedTaskId && (
482
+ <TaskDetailPanel
483
+ taskId={selectedTaskId}
484
+ onClose={() => handleSelectTask(null)}
485
+ expanded={expandDetail}
486
+ onToggleExpand={handleToggleExpand}
487
+ />
488
+ )}
489
+ </Box>
490
+ </TabPanel>
491
+
492
+ {/* Chat Tab */}
493
+ <TabPanel
494
+ value="chat"
495
+ sx={{
496
+ p: 0,
497
+ pt: 2,
498
+ flex: 1,
499
+ minHeight: 0,
500
+ "&[hidden]": {
501
+ display: "none",
502
+ },
503
+ }}
504
+ >
505
+ <ChatPanel
506
+ selectedChannelId={selectedChannelId}
507
+ selectedThreadId={selectedThreadId}
508
+ onSelectChannel={handleSelectChannel}
509
+ onSelectThread={handleSelectThread}
510
+ onNavigateToAgent={handleNavigateToAgent}
511
+ onNavigateToTask={handleNavigateToTask}
512
+ />
513
+ </TabPanel>
514
+
515
+ {/* Services Tab */}
516
+ <TabPanel
517
+ value="services"
518
+ sx={{
519
+ p: 0,
520
+ pt: 2,
521
+ flex: 1,
522
+ minHeight: 0,
523
+ "&[hidden]": {
524
+ display: "none",
525
+ },
526
+ }}
527
+ >
528
+ <ServicesPanel />
529
+ </TabPanel>
530
+ </Tabs>
531
+ </Box>
532
+ </Box>
533
+ );
534
+ }
@@ -0,0 +1,168 @@
1
+ import Box from "@mui/joy/Box";
2
+ import Typography from "@mui/joy/Typography";
3
+ import IconButton from "@mui/joy/IconButton";
4
+ import { useColorScheme } from "@mui/joy/styles";
5
+ import { useHealth } from "../hooks/queries";
6
+
7
+ interface HeaderProps {
8
+ onSettingsClick: () => void;
9
+ }
10
+
11
+ export default function Header({ onSettingsClick }: HeaderProps) {
12
+ const { data: health, isError, isLoading } = useHealth();
13
+ const { mode, setMode } = useColorScheme();
14
+
15
+ const toggleMode = () => {
16
+ setMode(mode === "dark" ? "light" : "dark");
17
+ };
18
+
19
+ const connectionStatus = isLoading
20
+ ? "connecting"
21
+ : isError
22
+ ? "error"
23
+ : "connected";
24
+
25
+ const isDark = mode === "dark";
26
+
27
+ const statusColors = {
28
+ connected: {
29
+ bg: isDark ? "rgba(212, 165, 116, 0.15)" : "rgba(139, 105, 20, 0.12)",
30
+ border: isDark ? "#D4A574" : "#8B6914",
31
+ text: isDark ? "#D4A574" : "#8B6914",
32
+ },
33
+ connecting: {
34
+ bg: isDark ? "rgba(245, 166, 35, 0.15)" : "rgba(212, 136, 6, 0.12)",
35
+ border: isDark ? "#F5A623" : "#D48806",
36
+ text: isDark ? "#F5A623" : "#D48806",
37
+ },
38
+ error: {
39
+ bg: isDark ? "rgba(168, 84, 84, 0.15)" : "rgba(181, 66, 66, 0.12)",
40
+ border: isDark ? "#A85454" : "#B54242",
41
+ text: isDark ? "#A85454" : "#B54242",
42
+ },
43
+ };
44
+
45
+ const colors = statusColors[connectionStatus];
46
+
47
+ return (
48
+ <Box
49
+ component="header"
50
+ sx={{
51
+ display: "flex",
52
+ alignItems: "center",
53
+ justifyContent: "space-between",
54
+ px: { xs: 1.5, sm: 2, md: 3 },
55
+ py: { xs: 1.5, md: 2 },
56
+ borderBottom: "1px solid",
57
+ borderColor: "neutral.outlinedBorder",
58
+ bgcolor: "background.surface",
59
+ }}
60
+ >
61
+ {/* Title */}
62
+ <Typography
63
+ level="h3"
64
+ sx={{
65
+ fontFamily: "display",
66
+ fontWeight: 700,
67
+ fontSize: { xs: "1.1rem", sm: "1.25rem", md: "1.5rem" },
68
+ background: isDark
69
+ ? "#F5A623"
70
+ : "#9A5F00",
71
+ backgroundClip: "text",
72
+ WebkitBackgroundClip: "text",
73
+ WebkitTextFillColor: "transparent",
74
+ textShadow: isDark ? "0 0 30px rgba(245, 166, 35, 0.3)" : "none",
75
+ letterSpacing: { xs: "0.1em", md: "0.15em" },
76
+ }}
77
+ >
78
+ <Box component="span" sx={{ display: { xs: "none", sm: "inline" } }}>
79
+ AGENT SWARM
80
+ </Box>
81
+ <Box component="span" sx={{ display: { xs: "inline", sm: "none" } }}>
82
+ SWARM
83
+ </Box>
84
+ </Typography>
85
+
86
+ {/* Right side: version + theme toggle + settings */}
87
+ <Box sx={{ display: "flex", alignItems: "center", gap: { xs: 0.75, md: 1.5 } }}>
88
+ {/* Connection Status / Version */}
89
+ <Box
90
+ component="span"
91
+ sx={{
92
+ fontFamily: "code",
93
+ fontSize: "0.65rem",
94
+ bgcolor: colors.bg,
95
+ border: "1px solid",
96
+ borderColor: colors.border,
97
+ color: colors.text,
98
+ borderRadius: "6px",
99
+ px: 1.5,
100
+ py: 0.5,
101
+ display: "inline-flex",
102
+ alignItems: "center",
103
+ boxShadow: isDark ? `0 0 10px ${colors.border}33` : "none",
104
+ animation:
105
+ connectionStatus === "connecting"
106
+ ? "heartbeat 1.5s ease-in-out infinite"
107
+ : undefined,
108
+ "@keyframes heartbeat": {
109
+ "0%, 100%": { transform: "scale(1)" },
110
+ "14%": { transform: "scale(1.1)" },
111
+ "28%": { transform: "scale(1)" },
112
+ "42%": { transform: "scale(1.1)" },
113
+ "70%": { transform: "scale(1)" },
114
+ },
115
+ }}
116
+ >
117
+ {connectionStatus === "connected" && health?.version
118
+ ? `v${health.version}`
119
+ : connectionStatus.toUpperCase()}
120
+ </Box>
121
+
122
+ {/* Theme Toggle */}
123
+ <IconButton
124
+ variant="outlined"
125
+ onClick={toggleMode}
126
+ sx={{
127
+ minWidth: { xs: 44, md: "auto" },
128
+ minHeight: { xs: 44, md: "auto" },
129
+ borderColor: "neutral.outlinedBorder",
130
+ color: "text.secondary",
131
+ transition: "all 0.2s ease",
132
+ "&:hover": {
133
+ borderColor: "primary.500",
134
+ color: "primary.500",
135
+ bgcolor: "primary.softBg",
136
+ },
137
+ }}
138
+ >
139
+ <Box component="span" sx={{ fontSize: "1rem" }}>
140
+ {mode === "dark" ? "☀️" : "🌙"}
141
+ </Box>
142
+ </IconButton>
143
+
144
+ {/* Settings Button */}
145
+ <IconButton
146
+ variant="outlined"
147
+ onClick={onSettingsClick}
148
+ sx={{
149
+ minWidth: { xs: 44, md: "auto" },
150
+ minHeight: { xs: 44, md: "auto" },
151
+ borderColor: "neutral.outlinedBorder",
152
+ color: "text.secondary",
153
+ transition: "all 0.2s ease",
154
+ "&:hover": {
155
+ borderColor: "primary.500",
156
+ color: "primary.500",
157
+ bgcolor: "primary.softBg",
158
+ },
159
+ }}
160
+ >
161
+ <Box component="span" sx={{ fontSize: "1.2rem" }}>
162
+ &#x2699;
163
+ </Box>
164
+ </IconButton>
165
+ </Box>
166
+ </Box>
167
+ );
168
+ }