@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
package/src/types.ts CHANGED
@@ -1,21 +1,56 @@
1
1
  import * as z from "zod";
2
2
 
3
- export const AgentTaskStatusSchema = z.enum(["pending", "in_progress", "completed", "failed"]);
3
+ // Task status - includes new unassigned and offered states
4
+ export const AgentTaskStatusSchema = z.enum([
5
+ "unassigned", // Task pool - no owner yet
6
+ "offered", // Offered to agent, awaiting accept/reject
7
+ "pending", // Assigned/accepted, waiting to start
8
+ "in_progress",
9
+ "completed",
10
+ "failed",
11
+ ]);
12
+
13
+ export const AgentTaskSourceSchema = z.enum(["mcp", "slack", "api"]);
14
+ export type AgentTaskSource = z.infer<typeof AgentTaskSourceSchema>;
4
15
 
5
16
  export const AgentTaskSchema = z.object({
6
17
  id: z.uuid(),
7
- agentId: z.uuid(),
18
+ agentId: z.uuid().nullable(), // Nullable for unassigned tasks
19
+ creatorAgentId: z.uuid().optional(), // Who created this task (optional for Slack/API)
8
20
  task: z.string().min(1),
9
21
  status: AgentTaskStatusSchema,
22
+ source: AgentTaskSourceSchema.default("mcp"),
23
+
24
+ // Task metadata
25
+ taskType: z.string().max(50).optional(), // e.g., "bug", "feature", "chore"
26
+ tags: z.array(z.string()).default([]), // e.g., ["urgent", "frontend"]
27
+ priority: z.number().int().min(0).max(100).default(50),
28
+ dependsOn: z.array(z.uuid()).default([]), // Task IDs this depends on
10
29
 
30
+ // Acceptance tracking
31
+ offeredTo: z.uuid().optional(), // Agent the task was offered to
32
+ offeredAt: z.iso.datetime().optional(),
33
+ acceptedAt: z.iso.datetime().optional(),
34
+ rejectionReason: z.string().optional(),
35
+
36
+ // Timestamps
11
37
  createdAt: z.iso.datetime().default(() => new Date().toISOString()),
12
38
  lastUpdatedAt: z.iso.datetime().default(() => new Date().toISOString()),
13
-
14
39
  finishedAt: z.iso.datetime().optional(),
15
40
 
41
+ // Completion data
16
42
  failureReason: z.string().optional(),
17
43
  output: z.string().optional(),
18
44
  progress: z.string().optional(),
45
+
46
+ // Slack-specific metadata (optional)
47
+ slackChannelId: z.string().optional(),
48
+ slackThreadTs: z.string().optional(),
49
+ slackUserId: z.string().optional(),
50
+
51
+ // Mention-to-task metadata (optional)
52
+ mentionMessageId: z.uuid().optional(),
53
+ mentionChannelId: z.uuid().optional(),
19
54
  });
20
55
 
21
56
  export const AgentStatusSchema = z.enum(["idle", "busy", "offline"]);
@@ -26,6 +61,11 @@ export const AgentSchema = z.object({
26
61
  isLead: z.boolean().default(false),
27
62
  status: AgentStatusSchema,
28
63
 
64
+ // Profile fields
65
+ description: z.string().optional(),
66
+ role: z.string().max(100).optional(), // Free-form, e.g., "frontend dev"
67
+ capabilities: z.array(z.string()).default([]), // e.g., ["typescript", "react"]
68
+
29
69
  createdAt: z.iso.datetime().default(() => new Date().toISOString()),
30
70
  lastUpdatedAt: z.iso.datetime().default(() => new Date().toISOString()),
31
71
  });
@@ -41,6 +81,62 @@ export type AgentStatus = z.infer<typeof AgentStatusSchema>;
41
81
  export type Agent = z.infer<typeof AgentSchema>;
42
82
  export type AgentWithTasks = z.infer<typeof AgentWithTasksSchema>;
43
83
 
84
+ // Channel Types
85
+ export const ChannelTypeSchema = z.enum(["public", "dm"]);
86
+
87
+ export const ChannelSchema = z.object({
88
+ id: z.uuid(),
89
+ name: z.string().min(1).max(100),
90
+ description: z.string().max(500).optional(),
91
+ type: ChannelTypeSchema.default("public"),
92
+ createdBy: z.uuid().optional(),
93
+ participants: z.array(z.uuid()).default([]), // For DMs
94
+ createdAt: z.iso.datetime(),
95
+ });
96
+
97
+ export const ChannelMessageSchema = z.object({
98
+ id: z.uuid(),
99
+ channelId: z.uuid(),
100
+ agentId: z.uuid().nullable(), // Null for human users
101
+ agentName: z.string().optional(), // Denormalized for convenience, "Human" when agentId is null
102
+ content: z.string().min(1).max(4000),
103
+ replyToId: z.uuid().optional(),
104
+ mentions: z.array(z.uuid()).default([]), // Agent IDs mentioned
105
+ createdAt: z.iso.datetime(),
106
+ });
107
+
108
+ export type ChannelType = z.infer<typeof ChannelTypeSchema>;
109
+ export type Channel = z.infer<typeof ChannelSchema>;
110
+ export type ChannelMessage = z.infer<typeof ChannelMessageSchema>;
111
+
112
+ // Service Types (for PM2/background services)
113
+ export const ServiceStatusSchema = z.enum(["starting", "healthy", "unhealthy", "stopped"]);
114
+
115
+ export const ServiceSchema = z.object({
116
+ id: z.uuid(),
117
+ agentId: z.uuid(),
118
+ name: z.string().min(1).max(50),
119
+ port: z.number().int().min(1).max(65535).default(3000),
120
+ description: z.string().optional(),
121
+ url: z.string().url().optional(),
122
+ healthCheckPath: z.string().default("/health"),
123
+ status: ServiceStatusSchema.default("starting"),
124
+
125
+ // PM2 configuration (required for ecosystem-based restart)
126
+ script: z.string().min(1), // Path to script (required)
127
+ cwd: z.string().optional(), // Working directory (defaults to script dir)
128
+ interpreter: z.string().optional(), // e.g., "node", "bun" (auto-detected if not set)
129
+ args: z.array(z.string()).optional(), // Command line arguments
130
+ env: z.record(z.string(), z.string()).optional(), // Environment variables
131
+
132
+ metadata: z.record(z.string(), z.unknown()).default({}),
133
+ createdAt: z.iso.datetime(),
134
+ lastUpdatedAt: z.iso.datetime(),
135
+ });
136
+
137
+ export type ServiceStatus = z.infer<typeof ServiceStatusSchema>;
138
+ export type Service = z.infer<typeof ServiceSchema>;
139
+
44
140
  // Agent Log Types
45
141
  export const AgentLogEventTypeSchema = z.enum([
46
142
  "agent_joined",
@@ -49,6 +145,17 @@ export const AgentLogEventTypeSchema = z.enum([
49
145
  "task_created",
50
146
  "task_status_change",
51
147
  "task_progress",
148
+ // Task pool events
149
+ "task_offered",
150
+ "task_accepted",
151
+ "task_rejected",
152
+ "task_claimed",
153
+ "task_released",
154
+ "channel_message",
155
+ // Service registry events
156
+ "service_registered",
157
+ "service_unregistered",
158
+ "service_status_change",
52
159
  ]);
53
160
 
54
161
  export const AgentLogSchema = z.object({
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Pretty print utilities for Claude CLI output
3
+ */
4
+
5
+ // ANSI color codes
6
+ const colors = {
7
+ reset: "\x1b[0m",
8
+ bold: "\x1b[1m",
9
+ dim: "\x1b[2m",
10
+ italic: "\x1b[3m",
11
+ underline: "\x1b[4m",
12
+
13
+ // Foreground colors
14
+ black: "\x1b[30m",
15
+ red: "\x1b[31m",
16
+ green: "\x1b[32m",
17
+ yellow: "\x1b[33m",
18
+ blue: "\x1b[34m",
19
+ magenta: "\x1b[35m",
20
+ cyan: "\x1b[36m",
21
+ white: "\x1b[37m",
22
+
23
+ // Bright colors
24
+ brightBlack: "\x1b[90m",
25
+ brightRed: "\x1b[91m",
26
+ brightGreen: "\x1b[92m",
27
+ brightYellow: "\x1b[93m",
28
+ brightBlue: "\x1b[94m",
29
+ brightMagenta: "\x1b[95m",
30
+ brightCyan: "\x1b[96m",
31
+ brightWhite: "\x1b[97m",
32
+
33
+ // Background colors
34
+ bgBlue: "\x1b[44m",
35
+ bgMagenta: "\x1b[45m",
36
+ bgCyan: "\x1b[46m",
37
+ };
38
+
39
+ const c = colors;
40
+
41
+ /** Truncate string with ellipsis */
42
+ function truncate(str: string, maxLen: number): string {
43
+ if (str.length <= maxLen) return str;
44
+ return `${str.slice(0, maxLen - 3)}...`;
45
+ }
46
+
47
+ /** Format a tool name nicely */
48
+ function formatToolName(name: string): string {
49
+ // Shorten MCP tool names
50
+ if (name.startsWith("mcp__")) {
51
+ const parts = name.split("__");
52
+ return parts.length >= 3 ? `${parts[1]}:${parts[2]}` : name;
53
+ }
54
+ return name;
55
+ }
56
+
57
+ /** Format input parameters for tool calls */
58
+ function formatToolInput(input: Record<string, unknown>): string {
59
+ const entries = Object.entries(input);
60
+ if (entries.length === 0) return "";
61
+
62
+ const formatted = entries
63
+ .map(([k, v]) => {
64
+ const value = typeof v === "string" ? truncate(v, 50) : JSON.stringify(v);
65
+ return `${c.dim}${k}=${c.reset}${truncate(String(value), 60)}`;
66
+ })
67
+ .join(", ");
68
+
69
+ return ` (${formatted})`;
70
+ }
71
+
72
+ /** Pretty print a single JSON line from Claude output */
73
+ export function prettyPrintLine(line: string, role: string): void {
74
+ if (!line.trim()) return;
75
+
76
+ let json: Record<string, unknown>;
77
+ try {
78
+ json = JSON.parse(line.trim());
79
+ } catch {
80
+ // Raw output - just print it
81
+ console.log(`${c.dim}[${role}]${c.reset} ${line.trim()}`);
82
+ return;
83
+ }
84
+
85
+ const type = json.type as string;
86
+ const prefix = `${c.dim}[${role}]${c.reset}`;
87
+
88
+ switch (type) {
89
+ case "system": {
90
+ const subtype = json.subtype as string;
91
+ if (subtype === "init") {
92
+ const model = json.model as string;
93
+ const tools = json.tools as string[];
94
+ console.log(
95
+ `${prefix} ${c.cyan}●${c.reset} ${c.bold}Session started${c.reset} ${c.dim}(${model}, ${tools?.length || 0} tools)${c.reset}`,
96
+ );
97
+ } else if (subtype === "hook_response") {
98
+ const hookName = json.hook_name as string;
99
+ const stdout = json.stdout as string;
100
+ console.log(`${prefix} ${c.yellow}⚡${c.reset} Hook: ${c.yellow}${hookName}${c.reset}`);
101
+ if (stdout) {
102
+ const lines = stdout.split("\n").filter((l) => l.trim());
103
+ for (const l of lines.slice(0, 3)) {
104
+ console.log(`${prefix} ${c.dim}${truncate(l, 80)}${c.reset}`);
105
+ }
106
+ if (lines.length > 3) {
107
+ console.log(`${prefix} ${c.dim}... (${lines.length - 3} more lines)${c.reset}`);
108
+ }
109
+ }
110
+ } else {
111
+ const msg = (json.message as string) || (json.content as string) || "";
112
+ console.log(
113
+ `${prefix} ${c.cyan}ℹ${c.reset} System${subtype ? ` (${subtype})` : ""}: ${truncate(msg, 100)}`,
114
+ );
115
+ }
116
+ break;
117
+ }
118
+
119
+ case "assistant": {
120
+ const message = json.message as Record<string, unknown>;
121
+ if (!message) break;
122
+
123
+ const content = message.content as Array<Record<string, unknown>>;
124
+ if (!content) break;
125
+
126
+ for (const block of content) {
127
+ if (block.type === "text") {
128
+ const text = block.text as string;
129
+ console.log(`${prefix} ${c.green}◆${c.reset} ${c.bold}Assistant:${c.reset}`);
130
+ // Print text with nice indentation, truncate long lines
131
+ const lines = text.split("\n");
132
+ for (const l of lines.slice(0, 5)) {
133
+ console.log(`${prefix} ${truncate(l, 100)}`);
134
+ }
135
+ if (lines.length > 5) {
136
+ console.log(`${prefix} ${c.dim}... (${lines.length - 5} more lines)${c.reset}`);
137
+ }
138
+ } else if (block.type === "tool_use") {
139
+ const toolName = formatToolName((block.name as string) || "unknown");
140
+ const input = (block.input as Record<string, unknown>) || {};
141
+ console.log(
142
+ `${prefix} ${c.magenta}▶${c.reset} Tool: ${c.magenta}${toolName}${c.reset}${formatToolInput(input)}`,
143
+ );
144
+ } else if (block.type === "thinking") {
145
+ const thinking = block.thinking as string;
146
+ console.log(`${prefix} ${c.blue}💭${c.reset} ${c.dim}Thinking...${c.reset}`);
147
+ if (thinking) {
148
+ console.log(`${prefix} ${c.dim}${truncate(thinking, 80)}${c.reset}`);
149
+ }
150
+ }
151
+ }
152
+ break;
153
+ }
154
+
155
+ case "user": {
156
+ const message = json.message as Record<string, unknown>;
157
+ const toolResult = json.tool_use_result as string;
158
+
159
+ if (toolResult) {
160
+ const isError = toolResult.includes("Error") || toolResult.includes("error");
161
+ const icon = isError ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
162
+ console.log(`${prefix} ${icon} Result: ${truncate(toolResult, 100)}`);
163
+ } else if (message) {
164
+ const content = message.content as Array<Record<string, unknown>>;
165
+ if (content) {
166
+ for (const block of content) {
167
+ if (block.type === "tool_result") {
168
+ const result = block.content as string;
169
+ const isError = block.is_error as boolean;
170
+ const icon = isError ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
171
+ console.log(`${prefix} ${icon} Result: ${truncate(result || "", 100)}`);
172
+ }
173
+ }
174
+ }
175
+ }
176
+ break;
177
+ }
178
+
179
+ case "result": {
180
+ const subtype = json.subtype as string;
181
+ const isError = json.is_error as boolean;
182
+ const duration = json.duration_ms as number;
183
+ const cost = json.total_cost_usd as number;
184
+ const numTurns = json.num_turns as number;
185
+ const result = json.result as string;
186
+
187
+ const icon = isError ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
188
+ const durationStr = duration ? `${(duration / 1000).toFixed(1)}s` : "";
189
+ const costStr = cost ? `$${cost.toFixed(4)}` : "";
190
+
191
+ console.log(
192
+ `${prefix} ${icon} ${c.bold}Done${c.reset} ${c.dim}(${subtype}, ${numTurns} turns, ${durationStr}, ${costStr})${c.reset}`,
193
+ );
194
+
195
+ if (result) {
196
+ const lines = result.split("\n").filter((l) => l.trim());
197
+ for (const l of lines.slice(0, 3)) {
198
+ console.log(`${prefix} ${truncate(l, 100)}`);
199
+ }
200
+ if (lines.length > 3) {
201
+ console.log(`${prefix} ${c.dim}... (${lines.length - 3} more lines)${c.reset}`);
202
+ }
203
+ }
204
+ break;
205
+ }
206
+
207
+ case "error": {
208
+ const error = (json.error as string) || (json.message as string) || JSON.stringify(json);
209
+ console.log(`${prefix} ${c.red}✗ Error:${c.reset} ${truncate(error, 100)}`);
210
+ break;
211
+ }
212
+
213
+ default: {
214
+ // Unknown type - print a summary
215
+ console.log(`${prefix} ${c.dim}[${type}]${c.reset} ${truncate(JSON.stringify(json), 100)}`);
216
+ }
217
+ }
218
+ }
219
+
220
+ /** Pretty print stderr output */
221
+ export function prettyPrintStderr(text: string, role: string): void {
222
+ const prefix = `${c.dim}[${role}]${c.reset}`;
223
+ console.error(`${prefix} ${c.red}stderr:${c.reset} ${truncate(text.trim(), 100)}`);
224
+ }
File without changes