@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,339 @@
1
+ ---
2
+ date: 2025-12-19T10:45:00-05:00
3
+ researcher: Claude-Main
4
+ git_commit: 7cb27a1
5
+ branch: main
6
+ repository: cc-orch-mcp
7
+ topic: "Agent Log Streaming and SSE Implementation"
8
+ tags: [research, logging, sse, streaming, worker-agents, frontend]
9
+ status: complete
10
+ last_updated: 2025-12-19
11
+ last_updated_by: Claude-Main
12
+ ---
13
+
14
+ # Research: Agent Log Streaming and SSE Implementation
15
+
16
+ **Date**: 2025-12-19T10:45:00-05:00
17
+ **Researcher**: Claude-Main
18
+ **Git Commit**: 7cb27a1
19
+ **Branch**: main
20
+ **Repository**: cc-orch-mcp
21
+
22
+ ## Research Question
23
+
24
+ How to store logs of Claude agents in the file system (not DB), stream them to the API for storage, and stream them via SSE to the frontend when viewing a task being executed.
25
+
26
+ ## Summary
27
+
28
+ Three existing log sources were identified in the codebase. The recommended approach uses Claude Code hooks to track the current task ID via `/tmp/.task.json`, then streams log chunks from the runner to the API, which broadcasts to frontend via SSE.
29
+
30
+ ## Detailed Findings
31
+
32
+ ### Existing Log Sources
33
+
34
+ | Source | Location | Content | Format |
35
+ |--------|----------|---------|--------|
36
+ | **Claude Code native** | `~/.claude/projects/{path}/*.jsonl` | Full conversation + tool calls + thinking | JSONL per-message |
37
+ | **Worker runner logs** | `./logs/{sessionId}/{timestamp}.jsonl` | Raw Claude CLI stream output | JSONL continuous |
38
+ | **Database event logs** | `agent_log` table in SQLite | Task state changes + progress | Structured rows |
39
+
40
+ ### Claude Code Native JSONL Files
41
+
42
+ Located at `~/.claude/projects/{escaped-project-path}/`:
43
+
44
+ **File types:**
45
+ - `{uuid}.jsonl` - Main conversation sessions
46
+ - `agent-{short-id}.jsonl` - Sub-agent (Task tool) sessions
47
+
48
+ **Record structure:**
49
+ - `sessionId` - UUID linking all messages
50
+ - `type` - `user`, `assistant`, `system`, `summary`
51
+ - `timestamp` - ISO datetime
52
+ - `message.content` - Array of thinking blocks, text, tool_use
53
+ - `message.model` - Model used
54
+ - `message.usage` - Token counts
55
+
56
+ **Sub-agent files include:**
57
+ - `agentId` - Short ID matching filename
58
+ - `slug` - Human-readable name
59
+ - `isSidechain: true` - Marker for sub-agent
60
+
61
+ ### Worker Runner Implementation
62
+
63
+ **File:** `src/commands/runner.ts`
64
+
65
+ The runner spawns Claude CLI with `--output-format stream-json` (line 43) and captures:
66
+
67
+ **Stdout parsing (lines 77-126):**
68
+ ```typescript
69
+ for await (const chunk of proc.stdout) {
70
+ const text = new TextDecoder().decode(chunk);
71
+ logFileHandle.write(text); // writes to local JSONL file
72
+
73
+ // Parses JSON lines into event types:
74
+ // - assistant (message content)
75
+ // - tool_use (tool calls)
76
+ // - result (tool results)
77
+ // - error (errors)
78
+ // - system (system messages)
79
+ }
80
+ ```
81
+
82
+ **Stderr capture (lines 128-142):**
83
+ ```typescript
84
+ for await (const chunk of proc.stderr) {
85
+ const text = new TextDecoder().decode(chunk);
86
+ logFileHandle.write(JSON.stringify({ type: "stderr", content: text, timestamp }));
87
+ }
88
+ ```
89
+
90
+ **Log directory structure:**
91
+ ```
92
+ ./logs/{sessionId}/
93
+ ├── {timestamp}.jsonl # Per-iteration logs
94
+ └── errors.jsonl # Error entries
95
+ ```
96
+
97
+ ### Hook System
98
+
99
+ **File:** `src/hooks/hook.ts`
100
+
101
+ Hooks intercept Claude Code events via stdin JSON:
102
+
103
+ **HookMessage fields:**
104
+ - `hook_event_name` - Event type (SessionStart, PostToolUse, Stop, etc.)
105
+ - `tool_name` - For tool events
106
+ - `tool_input` - Tool input parameters
107
+ - `tool_response` - Tool response data
108
+
109
+ **PostToolUse event** can intercept:
110
+ - `poll-task` response containing `task.id`
111
+ - `store-progress` input containing `status` (completed/failed)
112
+
113
+ ### HTTP Server
114
+
115
+ **File:** `src/http.ts`
116
+
117
+ Current endpoints:
118
+ - `GET /api/tasks/:id` - Returns task with database logs
119
+ - `GET /api/logs` - Returns recent activity logs
120
+
121
+ MCP SSE support exists via `StreamableHTTPServerTransport` (lines 339-347).
122
+
123
+ ### Frontend
124
+
125
+ **File:** `ui/src/components/TaskDetailPanel.tsx`
126
+
127
+ Currently uses polling via TanStack Query with 5-second interval (`ui/src/main.tsx:13`).
128
+
129
+ ## Proposed Architecture
130
+
131
+ ```
132
+ Worker Container API Server Frontend
133
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
134
+ │ Claude CLI │ │ │ │ │
135
+ │ (stream-json) │ │ POST /api/tasks │ │ EventSource │
136
+ │ ↓ │ │ /:id/logs │──────────▶│ subscription │
137
+ │ runner.ts │──────────────────▶│ ↓ │ │ ↓ │
138
+ │ (parse chunks) │ │ Store + SSE │ │ TaskDetailPanel │
139
+ │ ↑ │ │ broadcast │ │ (live logs) │
140
+ │ hook writes │ │ ↓ │ │ │
141
+ │ /tmp/.task.json │ │ GET /api/tasks │◀──────────│ │
142
+ └─────────────────┘ │ /:id/logs/stream│ └─────────────────┘
143
+ └─────────────────┘
144
+ ```
145
+
146
+ ## Implementation Plan
147
+
148
+ ### Step 1: Hook - Track Current Task ID
149
+
150
+ **File:** `src/hooks/hook.ts`
151
+
152
+ In `PostToolUse` handler:
153
+ - When `poll-task` succeeds, write `/tmp/.task.json` with `{ taskId, assignedAt }`
154
+ - When `store-progress` completes/fails, clear `/tmp/.task.json`
155
+
156
+ ```typescript
157
+ const TASK_FILE = "/tmp/.task.json";
158
+
159
+ case "PostToolUse":
160
+ // Track task assignment from poll-task
161
+ if (msg.tool_name?.endsWith("poll-task")) {
162
+ const response = msg.tool_response as { success?: boolean; task?: { id: string } };
163
+ if (response?.success && response?.task?.id) {
164
+ await Bun.write(TASK_FILE, JSON.stringify({
165
+ taskId: response.task.id,
166
+ assignedAt: new Date().toISOString()
167
+ }));
168
+ }
169
+ }
170
+
171
+ // Clear on task completion/failure
172
+ if (msg.tool_name?.endsWith("store-progress")) {
173
+ const input = msg.tool_input as { status?: string };
174
+ if (input?.status === "completed" || input?.status === "failed") {
175
+ try {
176
+ const file = Bun.file(TASK_FILE);
177
+ if (await file.exists()) {
178
+ await Bun.write(TASK_FILE, "");
179
+ }
180
+ } catch {}
181
+ }
182
+ }
183
+ break;
184
+ ```
185
+
186
+ ### Step 2: Runner - Stream Logs to API
187
+
188
+ **File:** `src/commands/runner.ts`
189
+
190
+ - Add `getCurrentTaskId()` function to read `/tmp/.task.json`
191
+ - In stdout/stderr loops, POST log chunks to `/api/tasks/:id/logs`
192
+
193
+ ```typescript
194
+ const TASK_FILE = "/tmp/.task.json";
195
+ const API_BASE_URL = process.env.MCP_BASE_URL || "http://localhost:3013";
196
+ const API_KEY = process.env.API_KEY || "";
197
+
198
+ async function getCurrentTaskId(): Promise<string | null> {
199
+ try {
200
+ const file = Bun.file(TASK_FILE);
201
+ if (await file.exists()) {
202
+ const content = await file.text();
203
+ if (!content.trim()) return null;
204
+ const data = JSON.parse(content);
205
+ return data.taskId || null;
206
+ }
207
+ } catch {}
208
+ return null;
209
+ }
210
+
211
+ async function streamLogToApi(taskId: string, logEntry: object): Promise<void> {
212
+ try {
213
+ await fetch(`${API_BASE_URL}/api/tasks/${taskId}/logs`, {
214
+ method: "POST",
215
+ headers: {
216
+ "Content-Type": "application/json",
217
+ ...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
218
+ },
219
+ body: JSON.stringify(logEntry),
220
+ });
221
+ } catch (err) {
222
+ console.error(`[runner] Failed to stream log: ${err}`);
223
+ }
224
+ }
225
+ ```
226
+
227
+ ### Step 3: API Server - Log Ingestion + SSE Broadcast
228
+
229
+ **File:** `src/http.ts`
230
+
231
+ New endpoints:
232
+ - `POST /api/tasks/:id/logs` - Worker pushes log chunks
233
+ - `GET /api/tasks/:id/logs` - Get logs from disk
234
+ - `GET /api/tasks/:id/logs/stream` - SSE subscription for new logs
235
+
236
+ ```typescript
237
+ // File-based storage in /logs directory
238
+ const LOG_DIR = process.env.LOG_DIR || "./logs";
239
+ const taskLogSubscribers: Record<string, Set<ServerResponse>> = {};
240
+
241
+ // POST /api/tasks/:id/logs - Receive log chunks
242
+ // - Append to /logs/{taskId}.jsonl file
243
+ // - Broadcast to SSE subscribers
244
+
245
+ // GET /api/tasks/:id/logs - Read existing logs
246
+ // - Read and parse /logs/{taskId}.jsonl
247
+ // - Return all log entries
248
+
249
+ // GET /api/tasks/:id/logs/stream - SSE endpoint
250
+ // - Register subscriber for new logs
251
+ // - Broadcast when POST receives new entries
252
+ // - Cleanup on close
253
+ ```
254
+
255
+ ### Step 4: Frontend - SSE Subscription
256
+
257
+ **Files:** `ui/src/lib/api.ts`, `ui/src/components/TaskDetailPanel.tsx`
258
+
259
+ - Add `subscribeToTaskLogs()` SSE helper using EventSource
260
+ - Subscribe to SSE for in-progress tasks
261
+ - Display streaming logs in real-time
262
+
263
+ ```typescript
264
+ export function subscribeToTaskLogs(
265
+ taskId: string,
266
+ onLog: (log: { type: string; content: unknown; timestamp: string }) => void,
267
+ onError?: (error: Event) => void
268
+ ): () => void {
269
+ const url = `${baseUrl}/api/tasks/${taskId}/logs/stream`;
270
+ const eventSource = new EventSource(url);
271
+ eventSource.onmessage = (event) => onLog(JSON.parse(event.data));
272
+ eventSource.onerror = onError;
273
+ return () => eventSource.close();
274
+ }
275
+ ```
276
+
277
+ ### Step 5: Types
278
+
279
+ **Files:** `src/types.ts`, `ui/src/types/api.ts`
280
+
281
+ ```typescript
282
+ export interface StreamingLogEntry {
283
+ type: string;
284
+ content: unknown;
285
+ timestamp: string;
286
+ receivedAt?: string;
287
+ }
288
+ ```
289
+
290
+ ## Code References
291
+
292
+ - `src/commands/runner.ts:37-164` - Claude CLI spawning and stream capture
293
+ - `src/commands/runner.ts:77-126` - Stdout parsing loop
294
+ - `src/commands/runner.ts:128-142` - Stderr capture loop
295
+ - `src/hooks/hook.ts:172-188` - PostToolUse handler
296
+ - `src/hooks/hook.ts:175-181` - send-task response handling (pattern to follow)
297
+ - `src/tools/poll-task.ts:101-108` - poll-task response structure with task.id
298
+ - `src/tools/store-progress.ts:79-101` - store-progress status handling
299
+ - `src/http.ts:339-347` - Existing SSE/MCP transport pattern
300
+ - `ui/src/main.tsx:13` - Current 5-second polling interval
301
+ - `ui/src/components/TaskDetailPanel.tsx:29-30` - Current data fetching
302
+
303
+ ## Files to Modify
304
+
305
+ | File | Changes |
306
+ |------|---------|
307
+ | `src/hooks/hook.ts` | Add task file tracking in PostToolUse |
308
+ | `src/commands/runner.ts` | Add API streaming in stdout/stderr loops |
309
+ | `src/http.ts` | Add POST + GET + SSE endpoints for task logs |
310
+ | `src/types.ts` | Add StreamingLogEntry type |
311
+ | `ui/src/lib/api.ts` | Add SSE subscription helper |
312
+ | `ui/src/components/TaskDetailPanel.tsx` | Add SSE subscription + live log display |
313
+ | `ui/src/types/api.ts` | Add StreamingLogEntry type |
314
+
315
+ ## Testing Plan
316
+
317
+ 1. Start API server: `bun run dev:http`
318
+ 2. Run a worker with a task
319
+ 3. Verify `/tmp/.task.json` is created on poll-task
320
+ 4. Verify logs appear at `GET /api/tasks/:id/logs`
321
+ 5. Verify SSE stream at `GET /api/tasks/:id/logs/stream`
322
+ 6. Verify `/tmp/.task.json` is cleared on completion
323
+ 7. Verify frontend shows live logs
324
+
325
+ ## Future Enhancements (Out of Scope)
326
+
327
+ - Persist logs to file system instead of memory
328
+ - Add log pagination/search
329
+ - Add log level filtering
330
+ - Stream Claude Code native JSONL files (`~/.claude/projects/...`)
331
+ - Tail existing log files for historical context
332
+
333
+ ## Design Decisions
334
+
335
+ **Log Storage:** Persist to disk in the mounted `/logs` directory (not in-memory). API serves existing logs via GET, then streams new logs via SSE.
336
+
337
+ **Log Retention:** Keep all logs per task (no limit).
338
+
339
+ **Claude Code native JSONL:** Out of scope for now, potentially in future iterations.