@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,415 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * MCP Tools Documentation Generator
4
+ *
5
+ * This script dynamically discovers and parses tool files in src/tools/
6
+ * and generates MCP.md documentation.
7
+ *
8
+ * Run with: bun run docs:mcp
9
+ */
10
+
11
+ import { Glob } from "bun";
12
+ import path from "node:path";
13
+
14
+ const TOOLS_DIR = path.join(import.meta.dir, "../src/tools");
15
+ const SERVER_FILE = path.join(import.meta.dir, "../src/server.ts");
16
+ const OUTPUT_FILE = path.join(import.meta.dir, "../MCP.md");
17
+
18
+ interface ToolCategory {
19
+ name: string;
20
+ title: string;
21
+ description: string;
22
+ tools: string[];
23
+ }
24
+
25
+ interface ToolInfo {
26
+ name: string;
27
+ title: string;
28
+ description: string;
29
+ fields: FieldInfo[];
30
+ }
31
+
32
+ interface FieldInfo {
33
+ name: string;
34
+ type: string;
35
+ required: boolean;
36
+ default?: string;
37
+ description: string;
38
+ }
39
+
40
+ /**
41
+ * Dynamically discover tool categories from server.ts
42
+ */
43
+ async function discoverCategories(): Promise<ToolCategory[]> {
44
+ const serverContent = await Bun.file(SERVER_FILE).text();
45
+ const categories: ToolCategory[] = [];
46
+
47
+ // Extract core tools (always registered, no capability check)
48
+ const coreTools: string[] = [];
49
+ const coreMatch = serverContent.match(
50
+ /\/\/ Core tools[\s\S]*?(?=\/\/.*capability|if \(hasCapability)/
51
+ );
52
+ if (coreMatch) {
53
+ const registerCalls = coreMatch[0].matchAll(/register(\w+)Tool\(server\)/g);
54
+ for (const match of registerCalls) {
55
+ const funcName = match[1];
56
+ const toolName = camelToKebab(funcName);
57
+ coreTools.push(toolName);
58
+ }
59
+ }
60
+ categories.push({
61
+ name: "core",
62
+ title: "Core Tools",
63
+ description: "Always available tools for basic swarm operations.",
64
+ tools: coreTools,
65
+ });
66
+
67
+ // Extract capability-based tools
68
+ const capabilityBlocks = serverContent.matchAll(
69
+ /\/\/\s*([\w\s]+)\s*capability[\s\S]*?if\s*\(hasCapability\(["'](\w+(?:-\w+)*)["']\)\)\s*\{([\s\S]*?)\}/g
70
+ );
71
+
72
+ for (const match of capabilityBlocks) {
73
+ const [, commentDesc, capName, block] = match;
74
+ const tools: string[] = [];
75
+
76
+ const registerCalls = block.matchAll(/register(\w+)Tool\(server\)/g);
77
+ for (const call of registerCalls) {
78
+ const funcName = call[1];
79
+ const toolName = camelToKebab(funcName);
80
+ tools.push(toolName);
81
+ }
82
+
83
+ if (tools.length > 0) {
84
+ categories.push({
85
+ name: capName,
86
+ title: formatCategoryTitle(capName),
87
+ description: commentDesc.trim(),
88
+ tools,
89
+ });
90
+ }
91
+ }
92
+
93
+ return categories;
94
+ }
95
+
96
+ /**
97
+ * Discover all tool files in the tools directory
98
+ */
99
+ async function discoverToolFiles(): Promise<string[]> {
100
+ const glob = new Glob("*.ts");
101
+ const files: string[] = [];
102
+
103
+ for await (const file of glob.scan(TOOLS_DIR)) {
104
+ // Skip utility files
105
+ if (file === "utils.ts" || file === "index.ts") continue;
106
+ files.push(file.replace(".ts", ""));
107
+ }
108
+
109
+ return files;
110
+ }
111
+
112
+ /**
113
+ * Parse a tool file to extract metadata
114
+ */
115
+ async function parseToolFile(toolFileName: string): Promise<ToolInfo | null> {
116
+ const filePath = path.join(TOOLS_DIR, `${toolFileName}.ts`);
117
+ const content = await Bun.file(filePath).text();
118
+
119
+ // Extract tool name from createToolRegistrar call
120
+ const nameMatch = content.match(/createToolRegistrar\(server\)\(\s*["']([^"']+)["']/);
121
+ if (!nameMatch) return null;
122
+
123
+ const name = nameMatch[1];
124
+
125
+ // Extract title
126
+ const titleMatch = content.match(/title:\s*["']([^"']+)["']/);
127
+ const title = titleMatch ? titleMatch[1] : formatTitle(name);
128
+
129
+ // Extract description - handle multiline and various quote types
130
+ let description = "";
131
+ // Try to find description that ends before inputSchema, outputSchema, or annotations
132
+ const descPatterns = [
133
+ /description:\s*["'`]([\s\S]*?)["'`]\s*,\s*(?:inputSchema|outputSchema|annotations|_meta)/,
134
+ /description:\s*["']([^"']+)["']/,
135
+ /description:\s*`([^`]+)`/,
136
+ ];
137
+
138
+ for (const pattern of descPatterns) {
139
+ const match = content.match(pattern);
140
+ if (match) {
141
+ description = match[1].replace(/\s+/g, " ").trim();
142
+ break;
143
+ }
144
+ }
145
+
146
+ // Parse schema fields
147
+ const fields = parseSchemaFields(content);
148
+
149
+ return { name, title, description, fields };
150
+ }
151
+
152
+ /**
153
+ * Parse input schema fields from file content
154
+ */
155
+ function parseSchemaFields(content: string): FieldInfo[] {
156
+ const fields: FieldInfo[] = [];
157
+
158
+ // Find inputSchema block
159
+ const schemaStart = content.indexOf("inputSchema:");
160
+ if (schemaStart === -1) return fields;
161
+
162
+ // Find the z.object({ ... }) block
163
+ const objectStart = content.indexOf("z.object({", schemaStart);
164
+ if (objectStart === -1) return fields;
165
+
166
+ // Extract the object content by counting braces
167
+ let braceCount = 0;
168
+ let inObject = false;
169
+ let objectContent = "";
170
+ let i = objectStart + "z.object(".length;
171
+
172
+ while (i < content.length) {
173
+ const char = content[i];
174
+ if (char === "{") {
175
+ braceCount++;
176
+ inObject = true;
177
+ }
178
+ if (inObject) objectContent += char;
179
+ if (char === "}") {
180
+ braceCount--;
181
+ if (braceCount === 0 && inObject) break;
182
+ }
183
+ i++;
184
+ }
185
+
186
+ if (!objectContent) return fields;
187
+
188
+ // Remove outer braces and parse fields
189
+ objectContent = objectContent.slice(1, -1);
190
+
191
+ // Parse each field by tracking brace/paren depth
192
+ let currentField = "";
193
+ let depth = 0;
194
+
195
+ for (let j = 0; j < objectContent.length; j++) {
196
+ const char = objectContent[j];
197
+ if (char === "(" || char === "{" || char === "[") depth++;
198
+ if (char === ")" || char === "}" || char === "]") depth--;
199
+
200
+ currentField += char;
201
+
202
+ // Field ends when we hit a comma at depth 0, or end of content
203
+ const isEndOfField =
204
+ (char === "," && depth === 0) || j === objectContent.length - 1;
205
+
206
+ if (isEndOfField && currentField.trim()) {
207
+ const field = parseField(currentField);
208
+ if (field) fields.push(field);
209
+ currentField = "";
210
+ }
211
+ }
212
+
213
+ return fields;
214
+ }
215
+
216
+ /**
217
+ * Parse a single field definition
218
+ */
219
+ function parseField(fieldStr: string): FieldInfo | null {
220
+ // Match field name and type chain
221
+ const fieldMatch = fieldStr.match(/^\s*(\w+):\s*z\.([\s\S]+)/);
222
+ if (!fieldMatch) return null;
223
+
224
+ const [, name, typeChain] = fieldMatch;
225
+
226
+ // Determine type
227
+ let type = "unknown";
228
+ if (typeChain.startsWith("string")) type = "string";
229
+ else if (typeChain.startsWith("number")) type = "number";
230
+ else if (typeChain.startsWith("boolean")) type = "boolean";
231
+ else if (typeChain.startsWith("array")) type = "array";
232
+ else if (typeChain.startsWith("uuid")) type = "uuid";
233
+ else if (typeChain.startsWith("object")) type = "object";
234
+ else if (typeChain.startsWith("record")) type = "object";
235
+ else if (typeChain.startsWith("enum")) {
236
+ const enumMatch = typeChain.match(/enum\(\[([\s\S]*?)\]/);
237
+ if (enumMatch) {
238
+ const values = enumMatch[1]
239
+ .replace(/["']/g, "")
240
+ .split(",")
241
+ .map((s) => s.trim())
242
+ .filter(Boolean);
243
+ type = values.join(" \\| ");
244
+ }
245
+ }
246
+
247
+ // Check if optional or has default
248
+ let required = true;
249
+ let defaultValue: string | undefined;
250
+
251
+ if (typeChain.includes(".optional()")) required = false;
252
+ if (typeChain.includes(".default(")) {
253
+ required = false;
254
+ const defaultMatch = typeChain.match(/\.default\(([^)]+)\)/);
255
+ if (defaultMatch) {
256
+ defaultValue = defaultMatch[1].trim();
257
+ }
258
+ }
259
+
260
+ // Extract description
261
+ let description = "";
262
+ const descMatch = typeChain.match(/\.describe\(["'`]([\s\S]*?)["'`]\)/);
263
+ if (descMatch) {
264
+ description = descMatch[1].replace(/\s+/g, " ").trim();
265
+ }
266
+
267
+ return { name, type, required, default: defaultValue, description };
268
+ }
269
+
270
+ /**
271
+ * Convert CamelCase to kebab-case
272
+ */
273
+ function camelToKebab(str: string): string {
274
+ return str
275
+ .replace(/([a-z])([A-Z])/g, "$1-$2")
276
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
277
+ .toLowerCase();
278
+ }
279
+
280
+ /**
281
+ * Format category name to title
282
+ */
283
+ function formatCategoryTitle(name: string): string {
284
+ return name
285
+ .split("-")
286
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
287
+ .join(" ") + " Tools";
288
+ }
289
+
290
+ /**
291
+ * Format tool name to title
292
+ */
293
+ function formatTitle(name: string): string {
294
+ return name
295
+ .split("-")
296
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
297
+ .join(" ");
298
+ }
299
+
300
+ /**
301
+ * Generate markdown for a single tool
302
+ */
303
+ function generateToolMarkdown(tool: ToolInfo): string {
304
+ let md = `### ${tool.name}\n\n`;
305
+ md += `**${tool.title}**\n\n`;
306
+
307
+ if (tool.description) {
308
+ md += `${tool.description}\n\n`;
309
+ }
310
+
311
+ if (tool.fields.length > 0) {
312
+ md += `| Parameter | Type | Required | Default | Description |\n`;
313
+ md += `|-----------|------|----------|---------|-------------|\n`;
314
+ for (const field of tool.fields) {
315
+ const req = field.required ? "Yes" : "No";
316
+ const def = field.default ?? "-";
317
+ const desc = field.description || "-";
318
+ md += `| \`${field.name}\` | \`${field.type}\` | ${req} | ${def} | ${desc} |\n`;
319
+ }
320
+ md += "\n";
321
+ } else {
322
+ md += "*No parameters*\n\n";
323
+ }
324
+
325
+ return md;
326
+ }
327
+
328
+ /**
329
+ * Main generation function
330
+ */
331
+ async function generateDocs() {
332
+ console.log("Discovering tool categories from server.ts...");
333
+ const categories = await discoverCategories();
334
+
335
+ console.log("Discovering tool files...");
336
+ const allToolFiles = await discoverToolFiles();
337
+
338
+ console.log(`Found ${allToolFiles.length} tool files`);
339
+ console.log(`Found ${categories.length} categories:`);
340
+ for (const cat of categories) {
341
+ console.log(` - ${cat.name}: ${cat.tools.length} tools`);
342
+ }
343
+
344
+ // Parse all tool files
345
+ const toolInfoMap = new Map<string, ToolInfo>();
346
+ for (const fileName of allToolFiles) {
347
+ const info = await parseToolFile(fileName);
348
+ if (info) {
349
+ toolInfoMap.set(info.name, info);
350
+ }
351
+ }
352
+
353
+ console.log(`Parsed ${toolInfoMap.size} tools`);
354
+
355
+ // Generate markdown
356
+ let markdown = `# MCP Tools Reference
357
+
358
+ > Auto-generated from source. Do not edit manually.
359
+ > Run \`bun run docs:mcp\` to regenerate.
360
+
361
+ ## Table of Contents
362
+
363
+ `;
364
+
365
+ // Generate TOC
366
+ for (const category of categories) {
367
+ const anchor = category.title.toLowerCase().replace(/\s+/g, "-");
368
+ markdown += `- [${category.title}](#${anchor})\n`;
369
+ for (const toolName of category.tools) {
370
+ markdown += ` - [${toolName}](#${toolName})\n`;
371
+ }
372
+ }
373
+
374
+ markdown += "\n---\n\n";
375
+
376
+ // Generate tool documentation by category
377
+ for (const category of categories) {
378
+ markdown += `## ${category.title}\n\n`;
379
+ markdown += `*${category.description}*\n\n`;
380
+
381
+ for (const toolName of category.tools) {
382
+ const tool = toolInfoMap.get(toolName);
383
+ if (tool) {
384
+ markdown += generateToolMarkdown(tool);
385
+ } else {
386
+ console.warn(`Warning: No info found for tool "${toolName}"`);
387
+ markdown += `### ${toolName}\n\n*Documentation not available*\n\n`;
388
+ }
389
+ }
390
+ }
391
+
392
+ // Check for uncategorized tools
393
+ const categorizedTools = new Set(categories.flatMap((c) => c.tools));
394
+ const uncategorized = [...toolInfoMap.keys()].filter(
395
+ (name) => !categorizedTools.has(name)
396
+ );
397
+
398
+ if (uncategorized.length > 0) {
399
+ markdown += `## Other Tools\n\n`;
400
+ markdown += `*Tools not assigned to a capability group*\n\n`;
401
+ for (const toolName of uncategorized) {
402
+ const tool = toolInfoMap.get(toolName);
403
+ if (tool) {
404
+ markdown += generateToolMarkdown(tool);
405
+ }
406
+ }
407
+ }
408
+
409
+ // Write to file
410
+ await Bun.write(OUTPUT_FILE, markdown);
411
+ console.log(`\nGenerated ${OUTPUT_FILE}`);
412
+ }
413
+
414
+ // Run
415
+ generateDocs().catch(console.error);
@@ -0,0 +1,69 @@
1
+ {
2
+ "display_information": {
3
+ "name": "agent-swarm",
4
+ "description": "Your Agent Swarm",
5
+ "background_color": "#000000"
6
+ },
7
+ "features": {
8
+ "app_home": {
9
+ "home_tab_enabled": false,
10
+ "messages_tab_enabled": true,
11
+ "messages_tab_read_only_enabled": false
12
+ },
13
+ "bot_user": {
14
+ "display_name": "agent-swarm",
15
+ "always_online": true
16
+ },
17
+ "slash_commands": [
18
+ {
19
+ "command": "/agent-swarm-status",
20
+ "description": "Check status of available agents",
21
+ "should_escape": false
22
+ },
23
+ {
24
+ "command": "/agent-swarm-help",
25
+ "description": "Show help for using the Agent Swarm bot",
26
+ "should_escape": false
27
+ }
28
+ ]
29
+ },
30
+ "oauth_config": {
31
+ "scopes": {
32
+ "bot": [
33
+ "app_mentions:read",
34
+ "channels:history",
35
+ "channels:read",
36
+ "chat:write",
37
+ "chat:write.customize",
38
+ "chat:write.public",
39
+ "commands",
40
+ "groups:history",
41
+ "groups:read",
42
+ "im:history",
43
+ "im:read",
44
+ "im:write",
45
+ "mpim:history",
46
+ "mpim:read",
47
+ "mpim:write",
48
+ "users:read"
49
+ ]
50
+ }
51
+ },
52
+ "settings": {
53
+ "event_subscriptions": {
54
+ "bot_events": [
55
+ "app_mention",
56
+ "message.channels",
57
+ "message.groups",
58
+ "message.im",
59
+ "message.mpim"
60
+ ]
61
+ },
62
+ "interactivity": {
63
+ "is_enabled": true
64
+ },
65
+ "org_deploy_enabled": false,
66
+ "socket_mode_enabled": true,
67
+ "token_rotation_enabled": false
68
+ }
69
+ }