@atercates/claude-deck 0.2.1

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 (293) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/app/api/claude/hidden/route.ts +66 -0
  4. package/app/api/claude/projects/[name]/sessions/route.ts +71 -0
  5. package/app/api/claude/projects/route.ts +44 -0
  6. package/app/api/code-search/available/route.ts +12 -0
  7. package/app/api/code-search/route.ts +47 -0
  8. package/app/api/dev-servers/[id]/logs/route.ts +23 -0
  9. package/app/api/dev-servers/[id]/restart/route.ts +20 -0
  10. package/app/api/dev-servers/[id]/route.ts +51 -0
  11. package/app/api/dev-servers/[id]/stop/route.ts +20 -0
  12. package/app/api/dev-servers/detect/route.ts +39 -0
  13. package/app/api/dev-servers/route.ts +48 -0
  14. package/app/api/exec/route.ts +60 -0
  15. package/app/api/files/content/route.ts +76 -0
  16. package/app/api/files/route.ts +37 -0
  17. package/app/api/files/upload-temp/route.ts +41 -0
  18. package/app/api/git/check/route.ts +54 -0
  19. package/app/api/git/clone/route.ts +99 -0
  20. package/app/api/git/commit/route.ts +75 -0
  21. package/app/api/git/discard/route.ts +38 -0
  22. package/app/api/git/file-content/route.ts +64 -0
  23. package/app/api/git/history/[hash]/diff/route.ts +38 -0
  24. package/app/api/git/history/[hash]/route.ts +34 -0
  25. package/app/api/git/history/route.ts +27 -0
  26. package/app/api/git/multi-status/route.ts +46 -0
  27. package/app/api/git/pr/route.ts +164 -0
  28. package/app/api/git/push/route.ts +64 -0
  29. package/app/api/git/stage/route.ts +40 -0
  30. package/app/api/git/status/route.ts +51 -0
  31. package/app/api/git/unstage/route.ts +46 -0
  32. package/app/api/groups/[...path]/route.ts +136 -0
  33. package/app/api/groups/route.ts +93 -0
  34. package/app/api/orchestrate/spawn/route.ts +45 -0
  35. package/app/api/orchestrate/workers/[id]/route.ts +89 -0
  36. package/app/api/orchestrate/workers/route.ts +31 -0
  37. package/app/api/projects/[id]/detect/route.ts +27 -0
  38. package/app/api/projects/[id]/dev-servers/[dsId]/route.ts +66 -0
  39. package/app/api/projects/[id]/dev-servers/route.ts +51 -0
  40. package/app/api/projects/[id]/repositories/[repoId]/route.ts +67 -0
  41. package/app/api/projects/[id]/repositories/route.ts +74 -0
  42. package/app/api/projects/[id]/route.ts +108 -0
  43. package/app/api/projects/detect/route.ts +33 -0
  44. package/app/api/projects/route.ts +59 -0
  45. package/app/api/sessions/[id]/claude-session/route.ts +42 -0
  46. package/app/api/sessions/[id]/fork/route.ts +74 -0
  47. package/app/api/sessions/[id]/mcp-config/route.ts +34 -0
  48. package/app/api/sessions/[id]/messages/route.ts +60 -0
  49. package/app/api/sessions/[id]/pr/route.ts +188 -0
  50. package/app/api/sessions/[id]/preview/route.ts +42 -0
  51. package/app/api/sessions/[id]/route.ts +229 -0
  52. package/app/api/sessions/[id]/send-keys/route.ts +119 -0
  53. package/app/api/sessions/[id]/summarize/route.ts +331 -0
  54. package/app/api/sessions/init-script/route.ts +84 -0
  55. package/app/api/sessions/route.ts +209 -0
  56. package/app/api/sessions/status/route.ts +237 -0
  57. package/app/api/system/route.ts +9 -0
  58. package/app/api/tmux/kill-all/route.ts +57 -0
  59. package/app/api/tmux/rename/route.ts +30 -0
  60. package/app/globals.css +174 -0
  61. package/app/icon.svg +11 -0
  62. package/app/layout.tsx +122 -0
  63. package/app/page.tsx +629 -0
  64. package/components/ChatMessage.tsx +65 -0
  65. package/components/ChatView.tsx +276 -0
  66. package/components/ClaudeProjects/ClaudeProjectCard.tsx +195 -0
  67. package/components/ClaudeProjects/ClaudeProjectsSection.tsx +89 -0
  68. package/components/ClaudeProjects/ClaudeSessionCard.tsx +100 -0
  69. package/components/ClaudeProjects/index.ts +1 -0
  70. package/components/CodeSearch/CodeSearchResults.tsx +177 -0
  71. package/components/ConductorPanel.tsx +256 -0
  72. package/components/DevServers/DevServerCard.tsx +311 -0
  73. package/components/DevServers/DevServersSection.tsx +91 -0
  74. package/components/DevServers/ServerLogsModal.tsx +151 -0
  75. package/components/DevServers/StartServerDialog.tsx +359 -0
  76. package/components/DevServers/index.ts +4 -0
  77. package/components/DiffViewer/DiffModal.tsx +151 -0
  78. package/components/DiffViewer/UnifiedDiff.tsx +185 -0
  79. package/components/DiffViewer/index.tsx +2 -0
  80. package/components/DirectoryPicker.tsx +355 -0
  81. package/components/FileExplorer/FileEditor.tsx +276 -0
  82. package/components/FileExplorer/FileTabs.tsx +118 -0
  83. package/components/FileExplorer/FileTree.tsx +214 -0
  84. package/components/FileExplorer/HtmlRenderer.tsx +16 -0
  85. package/components/FileExplorer/MarkdownRenderer.tsx +18 -0
  86. package/components/FileExplorer/index.tsx +520 -0
  87. package/components/FilePicker.tsx +339 -0
  88. package/components/FolderPicker.tsx +201 -0
  89. package/components/GitDrawer/FileEditDialog.tsx +400 -0
  90. package/components/GitDrawer/index.tsx +464 -0
  91. package/components/GitPanel/CommitForm.tsx +205 -0
  92. package/components/GitPanel/CommitHistory.tsx +174 -0
  93. package/components/GitPanel/CommitItem.tsx +196 -0
  94. package/components/GitPanel/FileChanges.tsx +414 -0
  95. package/components/GitPanel/GitPanelTabs.tsx +39 -0
  96. package/components/GitPanel/index.tsx +817 -0
  97. package/components/MessageInput.tsx +82 -0
  98. package/components/NewClaudeSessionDialog.tsx +166 -0
  99. package/components/NewSessionDialog/AdvancedSettings.tsx +78 -0
  100. package/components/NewSessionDialog/AgentSelector.tsx +37 -0
  101. package/components/NewSessionDialog/CreatingOverlay.tsx +94 -0
  102. package/components/NewSessionDialog/NewSessionDialog.types.ts +136 -0
  103. package/components/NewSessionDialog/ProjectSelector.tsx +146 -0
  104. package/components/NewSessionDialog/WorkingDirectoryInput.tsx +55 -0
  105. package/components/NewSessionDialog/WorktreeSection.tsx +92 -0
  106. package/components/NewSessionDialog/hooks/useNewSessionForm.ts +370 -0
  107. package/components/NewSessionDialog/index.tsx +106 -0
  108. package/components/NotificationSettings.tsx +127 -0
  109. package/components/PRCreationModal.tsx +272 -0
  110. package/components/Pane/DesktopTabBar.tsx +353 -0
  111. package/components/Pane/MobileTabBar.tsx +210 -0
  112. package/components/Pane/OpenInVSCode.tsx +69 -0
  113. package/components/Pane/PaneSkeletons.tsx +57 -0
  114. package/components/Pane/index.tsx +558 -0
  115. package/components/PaneLayout.tsx +60 -0
  116. package/components/Projects/DevServersSection.tsx +140 -0
  117. package/components/Projects/DirectoryField.tsx +92 -0
  118. package/components/Projects/NewProjectDialog.tsx +188 -0
  119. package/components/Projects/NewProjectDialog.types.ts +46 -0
  120. package/components/Projects/ProjectCard.tsx +276 -0
  121. package/components/Projects/ProjectSettingsDialog.tsx +811 -0
  122. package/components/Projects/hooks/useNewProjectForm.ts +249 -0
  123. package/components/Projects/index.ts +3 -0
  124. package/components/Providers.tsx +49 -0
  125. package/components/QuickSwitcher.tsx +306 -0
  126. package/components/SessionList/KillAllConfirm.tsx +46 -0
  127. package/components/SessionList/SelectionToolbar.tsx +164 -0
  128. package/components/SessionList/SessionList.types.ts +37 -0
  129. package/components/SessionList/SessionListHeader.tsx +71 -0
  130. package/components/SessionList/hooks/useSessionListMutations.ts +269 -0
  131. package/components/SessionList/index.tsx +189 -0
  132. package/components/ShellDrawer/index.tsx +106 -0
  133. package/components/SidebarFooter.tsx +55 -0
  134. package/components/Terminal/KeybarToggleButton.tsx +45 -0
  135. package/components/Terminal/ScrollToBottomButton.tsx +32 -0
  136. package/components/Terminal/SearchBar.tsx +71 -0
  137. package/components/Terminal/TerminalToolbar.tsx +551 -0
  138. package/components/Terminal/VirtualKeyboard.tsx +711 -0
  139. package/components/Terminal/constants.ts +20 -0
  140. package/components/Terminal/hooks/index.ts +5 -0
  141. package/components/Terminal/hooks/resize-handlers.ts +140 -0
  142. package/components/Terminal/hooks/terminal-init.ts +151 -0
  143. package/components/Terminal/hooks/touch-scroll.ts +155 -0
  144. package/components/Terminal/hooks/useTerminalConnection.ts +282 -0
  145. package/components/Terminal/hooks/useTerminalConnection.types.ts +39 -0
  146. package/components/Terminal/hooks/useTerminalSearch.ts +103 -0
  147. package/components/Terminal/hooks/websocket-connection.ts +274 -0
  148. package/components/Terminal/index.tsx +320 -0
  149. package/components/ThemeToggle.tsx +168 -0
  150. package/components/TmuxSessions.tsx +132 -0
  151. package/components/ToolCallDisplay.tsx +71 -0
  152. package/components/WorkerCard.tsx +245 -0
  153. package/components/a/ABadge.tsx +115 -0
  154. package/components/a/AButton.tsx +163 -0
  155. package/components/a/ADialog.tsx +93 -0
  156. package/components/a/ADropdownMenu.tsx +279 -0
  157. package/components/a/AIconButton.tsx +190 -0
  158. package/components/a/ASheet.tsx +150 -0
  159. package/components/a/ATooltip.tsx +77 -0
  160. package/components/a/index.ts +64 -0
  161. package/components/mobile/SwipeSidebar.tsx +122 -0
  162. package/components/ui/badge.tsx +41 -0
  163. package/components/ui/button.tsx +60 -0
  164. package/components/ui/context-menu.tsx +197 -0
  165. package/components/ui/dialog.tsx +143 -0
  166. package/components/ui/dropdown-menu.tsx +257 -0
  167. package/components/ui/input.tsx +21 -0
  168. package/components/ui/scroll-area.tsx +52 -0
  169. package/components/ui/select.tsx +159 -0
  170. package/components/ui/skeleton.tsx +111 -0
  171. package/components/ui/switch.tsx +31 -0
  172. package/components/ui/textarea.tsx +21 -0
  173. package/components/ui/tooltip.tsx +32 -0
  174. package/components/views/DesktopView.tsx +244 -0
  175. package/components/views/MobileView.tsx +110 -0
  176. package/components/views/types.ts +75 -0
  177. package/contexts/PaneContext.tsx +336 -0
  178. package/data/claude/index.ts +9 -0
  179. package/data/claude/keys.ts +6 -0
  180. package/data/claude/queries.ts +120 -0
  181. package/data/claude/useClaudeUpdates.ts +37 -0
  182. package/data/code-search/index.ts +2 -0
  183. package/data/code-search/keys.ts +7 -0
  184. package/data/code-search/queries.ts +61 -0
  185. package/data/dev-servers/index.ts +8 -0
  186. package/data/dev-servers/keys.ts +4 -0
  187. package/data/dev-servers/queries.ts +104 -0
  188. package/data/files/index.ts +3 -0
  189. package/data/files/keys.ts +4 -0
  190. package/data/files/queries.ts +25 -0
  191. package/data/git/keys.ts +15 -0
  192. package/data/git/queries.ts +395 -0
  193. package/data/groups/index.ts +1 -0
  194. package/data/groups/mutations.ts +95 -0
  195. package/data/projects/index.ts +10 -0
  196. package/data/projects/keys.ts +4 -0
  197. package/data/projects/queries.ts +193 -0
  198. package/data/repositories/index.ts +7 -0
  199. package/data/repositories/keys.ts +5 -0
  200. package/data/repositories/queries.ts +122 -0
  201. package/data/sessions/index.ts +12 -0
  202. package/data/sessions/keys.ts +8 -0
  203. package/data/sessions/queries.ts +218 -0
  204. package/data/statuses/index.ts +1 -0
  205. package/data/statuses/queries.ts +69 -0
  206. package/hooks/useCopyToClipboard.ts +48 -0
  207. package/hooks/useDevServersManager.ts +73 -0
  208. package/hooks/useDirectoryBrowser.ts +90 -0
  209. package/hooks/useDrawerAnimation.ts +27 -0
  210. package/hooks/useFileDrop.ts +87 -0
  211. package/hooks/useFileEditor.ts +184 -0
  212. package/hooks/useGroups.ts +37 -0
  213. package/hooks/useHomePath.ts +34 -0
  214. package/hooks/useKeyRepeat.ts +55 -0
  215. package/hooks/useKeybarVisibility.ts +42 -0
  216. package/hooks/useNotifications.ts +257 -0
  217. package/hooks/useProjects.ts +53 -0
  218. package/hooks/useSessionStatuses.ts +30 -0
  219. package/hooks/useSessions.ts +86 -0
  220. package/hooks/useSpeechRecognition.ts +124 -0
  221. package/hooks/useViewport.ts +32 -0
  222. package/hooks/useViewportHeight.ts +50 -0
  223. package/lib/async-operations.ts +35 -0
  224. package/lib/banner.ts +81 -0
  225. package/lib/claude/jsonl-cache.ts +86 -0
  226. package/lib/claude/jsonl-reader.ts +271 -0
  227. package/lib/claude/process-manager.ts +278 -0
  228. package/lib/claude/stream-parser.ts +173 -0
  229. package/lib/claude/types.ts +154 -0
  230. package/lib/claude/watcher.ts +71 -0
  231. package/lib/client/session-registry.ts +111 -0
  232. package/lib/code-search.ts +121 -0
  233. package/lib/db/index.ts +48 -0
  234. package/lib/db/migrations.ts +45 -0
  235. package/lib/db/queries.ts +460 -0
  236. package/lib/db/schema.ts +114 -0
  237. package/lib/db/types.ts +92 -0
  238. package/lib/db.ts +2 -0
  239. package/lib/dev-servers.ts +509 -0
  240. package/lib/diff-parser.ts +221 -0
  241. package/lib/env-setup.ts +285 -0
  242. package/lib/file-upload.ts +34 -0
  243. package/lib/file-utils.ts +50 -0
  244. package/lib/files.ts +207 -0
  245. package/lib/git-history.ts +294 -0
  246. package/lib/git-status.ts +391 -0
  247. package/lib/git.ts +257 -0
  248. package/lib/mcp-config.ts +81 -0
  249. package/lib/multi-repo-git.ts +179 -0
  250. package/lib/notifications.ts +219 -0
  251. package/lib/orchestration.ts +448 -0
  252. package/lib/panes.ts +232 -0
  253. package/lib/ports.ts +97 -0
  254. package/lib/pr-generation.ts +307 -0
  255. package/lib/pr.ts +234 -0
  256. package/lib/projects.ts +578 -0
  257. package/lib/providers/registry.ts +70 -0
  258. package/lib/providers.ts +121 -0
  259. package/lib/query-client.ts +14 -0
  260. package/lib/rangeSelectionUtils.ts +65 -0
  261. package/lib/status-detector.ts +375 -0
  262. package/lib/terminal-themes.ts +265 -0
  263. package/lib/theme-config.ts +327 -0
  264. package/lib/utils.ts +6 -0
  265. package/lib/worktrees.ts +262 -0
  266. package/mcp/orchestration-server.ts +438 -0
  267. package/package.json +139 -0
  268. package/postcss.config.mjs +7 -0
  269. package/public/icon.svg +10 -0
  270. package/public/icons/icon-128x128.png +0 -0
  271. package/public/icons/icon-144x144.png +0 -0
  272. package/public/icons/icon-152x152.png +0 -0
  273. package/public/icons/icon-192x192.png +0 -0
  274. package/public/icons/icon-384x384.png +0 -0
  275. package/public/icons/icon-512x512.png +0 -0
  276. package/public/icons/icon-72x72.png +0 -0
  277. package/public/icons/icon-96x96.png +0 -0
  278. package/public/manifest.json +61 -0
  279. package/public/sw.js +64 -0
  280. package/scripts/agent-os +91 -0
  281. package/scripts/install.sh +48 -0
  282. package/scripts/lib/ai-clis.sh +132 -0
  283. package/scripts/lib/commands.sh +487 -0
  284. package/scripts/lib/common.sh +89 -0
  285. package/scripts/lib/prerequisites.sh +462 -0
  286. package/scripts/setup.sh +134 -0
  287. package/server.ts +155 -0
  288. package/stores/fileOpen.ts +26 -0
  289. package/stores/index.ts +1 -0
  290. package/stores/initialPrompt.ts +24 -0
  291. package/stores/sessionSelection.ts +48 -0
  292. package/styles/themes.css +603 -0
  293. package/tsconfig.json +33 -0
@@ -0,0 +1,177 @@
1
+ "use client";
2
+
3
+ import { useCodeSearch } from "@/data/code-search";
4
+ import { Loader2, FileCode, Search } from "lucide-react";
5
+ import { useState, useEffect } from "react";
6
+ import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
7
+ import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";
8
+ import { cn } from "@/lib/utils";
9
+ import type { FormattedMatch } from "@/lib/code-search";
10
+
11
+ interface CodeSearchResultsProps {
12
+ workingDirectory: string;
13
+ query: string;
14
+ onSelectFile: (file: string, line: number) => void;
15
+ }
16
+
17
+ export function CodeSearchResults({
18
+ workingDirectory,
19
+ query,
20
+ onSelectFile,
21
+ }: CodeSearchResultsProps) {
22
+ const { data, isLoading, isError, error } = useCodeSearch(
23
+ workingDirectory,
24
+ query,
25
+ query.length > 2
26
+ );
27
+
28
+ const [selectedIndex, setSelectedIndex] = useState(0);
29
+
30
+ useEffect(() => {
31
+ const handleKeyDown = (e: KeyboardEvent) => {
32
+ if (!data?.results.length) return;
33
+
34
+ if (e.key === "ArrowDown") {
35
+ e.preventDefault();
36
+ setSelectedIndex((i) => Math.min(i + 1, data.results.length - 1));
37
+ } else if (e.key === "ArrowUp") {
38
+ e.preventDefault();
39
+ setSelectedIndex((i) => Math.max(i - 1, 0));
40
+ } else if (e.key === "Enter") {
41
+ e.preventDefault();
42
+ const result = data.results[selectedIndex];
43
+ if (result) {
44
+ onSelectFile(result.file, result.line);
45
+ }
46
+ }
47
+ };
48
+
49
+ window.addEventListener("keydown", handleKeyDown);
50
+ return () => window.removeEventListener("keydown", handleKeyDown);
51
+ }, [data, selectedIndex, onSelectFile]);
52
+
53
+ if (isLoading) {
54
+ return (
55
+ <div className="flex items-center justify-center p-8">
56
+ <Loader2 className="text-muted-foreground h-6 w-6 animate-spin" />
57
+ </div>
58
+ );
59
+ }
60
+
61
+ if (isError) {
62
+ return (
63
+ <div className="text-destructive p-4 text-sm">
64
+ {error instanceof Error ? error.message : "Failed to search code"}
65
+ </div>
66
+ );
67
+ }
68
+
69
+ if (query.length < 3) {
70
+ return (
71
+ <div className="text-muted-foreground flex flex-col items-center justify-center p-8">
72
+ <Search className="mb-2 h-8 w-8 opacity-50" />
73
+ <p className="text-sm">Type at least 3 characters to search</p>
74
+ </div>
75
+ );
76
+ }
77
+
78
+ if (!data?.results.length) {
79
+ return (
80
+ <div className="text-muted-foreground flex flex-col items-center justify-center p-8">
81
+ <FileCode className="mb-2 h-8 w-8 opacity-50" />
82
+ <p className="text-sm">No matches found for &quot;{query}&quot;</p>
83
+ </div>
84
+ );
85
+ }
86
+
87
+ return (
88
+ <div className="flex flex-col divide-y">
89
+ {data.results.map((result, index) => (
90
+ <SearchResultItem
91
+ key={`${result.file}:${result.line}`}
92
+ result={result}
93
+ isSelected={index === selectedIndex}
94
+ onClick={() => onSelectFile(result.file, result.line)}
95
+ />
96
+ ))}
97
+ </div>
98
+ );
99
+ }
100
+
101
+ interface SearchResultItemProps {
102
+ result: FormattedMatch;
103
+ isSelected: boolean;
104
+ onClick: () => void;
105
+ }
106
+
107
+ function SearchResultItem({
108
+ result,
109
+ isSelected,
110
+ onClick,
111
+ }: SearchResultItemProps) {
112
+ const language = getLanguageFromPath(result.file);
113
+ const fileName = result.file.split("/").pop() || result.file;
114
+ const filePath = result.file.includes("/")
115
+ ? result.file.slice(0, result.file.lastIndexOf("/"))
116
+ : "";
117
+
118
+ return (
119
+ <button
120
+ onClick={onClick}
121
+ className={cn(
122
+ "flex min-h-[44px] flex-col gap-2 p-3 text-left transition-colors",
123
+ "hover:bg-accent",
124
+ isSelected && "bg-accent"
125
+ )}
126
+ >
127
+ <div className="flex items-center gap-2">
128
+ <FileCode className="text-muted-foreground h-4 w-4 flex-shrink-0" />
129
+ <div className="min-w-0 flex-1">
130
+ <span className="font-medium">{fileName}</span>
131
+ {filePath && (
132
+ <span className="text-muted-foreground text-xs"> · {filePath}</span>
133
+ )}
134
+ <span className="text-muted-foreground text-xs">
135
+ {" "}
136
+ · Line {result.line}
137
+ </span>
138
+ </div>
139
+ </div>
140
+
141
+ <div className="bg-muted/50 rounded p-2 font-mono text-xs">
142
+ <SyntaxHighlighter
143
+ language={language}
144
+ style={vscDarkPlus}
145
+ customStyle={{
146
+ background: "transparent",
147
+ padding: 0,
148
+ margin: 0,
149
+ }}
150
+ wrapLines
151
+ showLineNumbers={false}
152
+ >
153
+ {result.lineText}
154
+ </SyntaxHighlighter>
155
+ </div>
156
+ </button>
157
+ );
158
+ }
159
+
160
+ function getLanguageFromPath(path: string): string {
161
+ const ext = path.split(".").pop()?.toLowerCase() || "";
162
+ const map: Record<string, string> = {
163
+ ts: "typescript",
164
+ tsx: "typescript",
165
+ js: "javascript",
166
+ jsx: "javascript",
167
+ json: "json",
168
+ md: "markdown",
169
+ css: "css",
170
+ html: "html",
171
+ py: "python",
172
+ rb: "ruby",
173
+ go: "go",
174
+ rs: "rust",
175
+ };
176
+ return map[ext] || "text";
177
+ }
@@ -0,0 +1,256 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import { WorkerCard, type WorkerInfo, type WorkerStatus } from "./WorkerCard";
5
+ import { Button } from "./ui/button";
6
+ import {
7
+ RefreshCw,
8
+ Users,
9
+ CheckCircle,
10
+ Loader2,
11
+ AlertCircle,
12
+ XCircle,
13
+ } from "lucide-react";
14
+ import { cn } from "@/lib/utils";
15
+
16
+ interface WorkersSummary {
17
+ total: number;
18
+ pending: number;
19
+ running: number;
20
+ waiting: number;
21
+ completed: number;
22
+ failed: number;
23
+ }
24
+
25
+ interface ConductorPanelProps {
26
+ conductorSessionId: string;
27
+ onAttachToWorker?: (workerId: string) => void;
28
+ }
29
+
30
+ export function ConductorPanel({
31
+ conductorSessionId,
32
+ onAttachToWorker,
33
+ }: ConductorPanelProps) {
34
+ const [workers, setWorkers] = useState<WorkerInfo[]>([]);
35
+ const [summary, setSummary] = useState<WorkersSummary | null>(null);
36
+ const [expandedWorkers, setExpandedWorkers] = useState<Set<string>>(
37
+ new Set()
38
+ );
39
+ const [workerOutputs, setWorkerOutputs] = useState<Record<string, string>>(
40
+ {}
41
+ );
42
+ const [loading, setLoading] = useState(true);
43
+ const [refreshing, setRefreshing] = useState(false);
44
+
45
+ const fetchWorkers = useCallback(async () => {
46
+ try {
47
+ const res = await fetch(
48
+ `/api/orchestrate/workers?conductorId=${conductorSessionId}`
49
+ );
50
+ const data = await res.json();
51
+ if (data.workers) {
52
+ setWorkers(data.workers);
53
+ }
54
+ } catch (error) {
55
+ console.error("Failed to fetch workers:", error);
56
+ }
57
+ }, [conductorSessionId]);
58
+
59
+ const fetchSummary = useCallback(async () => {
60
+ try {
61
+ const res = await fetch(
62
+ `/api/orchestrate/workers?conductorId=${conductorSessionId}&summary=true`
63
+ );
64
+ const data = await res.json();
65
+ if (data.summary) {
66
+ setSummary(data.summary);
67
+ }
68
+ } catch (error) {
69
+ console.error("Failed to fetch summary:", error);
70
+ }
71
+ }, [conductorSessionId]);
72
+
73
+ const fetchWorkerOutput = useCallback(async (workerId: string) => {
74
+ try {
75
+ const res = await fetch(`/api/orchestrate/workers/${workerId}?lines=30`);
76
+ const data = await res.json();
77
+ if (data.output) {
78
+ setWorkerOutputs((prev) => ({ ...prev, [workerId]: data.output }));
79
+ }
80
+ } catch (error) {
81
+ console.error("Failed to fetch worker output:", error);
82
+ }
83
+ }, []);
84
+
85
+ const refresh = useCallback(async () => {
86
+ setRefreshing(true);
87
+ await Promise.all([fetchWorkers(), fetchSummary()]);
88
+ setRefreshing(false);
89
+ }, [fetchWorkers, fetchSummary]);
90
+
91
+ // Initial load
92
+ useEffect(() => {
93
+ const load = async () => {
94
+ setLoading(true);
95
+ await Promise.all([fetchWorkers(), fetchSummary()]);
96
+ setLoading(false);
97
+ };
98
+ load();
99
+ }, [fetchWorkers, fetchSummary]);
100
+
101
+ // Poll for updates every 5 seconds
102
+ useEffect(() => {
103
+ const interval = setInterval(refresh, 5000);
104
+ return () => clearInterval(interval);
105
+ }, [refresh]);
106
+
107
+ // Fetch output for expanded workers
108
+ useEffect(() => {
109
+ expandedWorkers.forEach((workerId) => {
110
+ if (!workerOutputs[workerId]) {
111
+ fetchWorkerOutput(workerId);
112
+ }
113
+ });
114
+ }, [expandedWorkers, workerOutputs, fetchWorkerOutput]);
115
+
116
+ const toggleExpand = (workerId: string) => {
117
+ setExpandedWorkers((prev) => {
118
+ const next = new Set(prev);
119
+ if (next.has(workerId)) {
120
+ next.delete(workerId);
121
+ } else {
122
+ next.add(workerId);
123
+ // Fetch output when expanding
124
+ fetchWorkerOutput(workerId);
125
+ }
126
+ return next;
127
+ });
128
+ };
129
+
130
+ const handleSendMessage = async (workerId: string, message: string) => {
131
+ try {
132
+ await fetch(`/api/orchestrate/workers/${workerId}`, {
133
+ method: "POST",
134
+ headers: { "Content-Type": "application/json" },
135
+ body: JSON.stringify({ action: "send", message }),
136
+ });
137
+ // Refresh output after sending
138
+ setTimeout(() => fetchWorkerOutput(workerId), 1000);
139
+ } catch (error) {
140
+ console.error("Failed to send message:", error);
141
+ }
142
+ };
143
+
144
+ const handleKillWorker = async (workerId: string) => {
145
+ if (!confirm("Kill this worker?")) return;
146
+ try {
147
+ await fetch(`/api/orchestrate/workers/${workerId}`, {
148
+ method: "DELETE",
149
+ });
150
+ await refresh();
151
+ } catch (error) {
152
+ console.error("Failed to kill worker:", error);
153
+ }
154
+ };
155
+
156
+ if (loading) {
157
+ return (
158
+ <div className="flex h-full items-center justify-center">
159
+ <Loader2 className="text-muted-foreground h-6 w-6 animate-spin" />
160
+ </div>
161
+ );
162
+ }
163
+
164
+ if (workers.length === 0) {
165
+ return (
166
+ <div className="text-muted-foreground flex h-full flex-col items-center justify-center">
167
+ <Users className="mb-4 h-12 w-12 opacity-50" />
168
+ <p className="text-lg font-medium">No workers yet</p>
169
+ <p className="text-sm">This conductor hasn't spawned any workers.</p>
170
+ <p className="mt-4 max-w-md text-center text-xs">
171
+ Use the MCP tools or API to spawn workers. The conductor can delegate
172
+ tasks to parallel worker sessions.
173
+ </p>
174
+ </div>
175
+ );
176
+ }
177
+
178
+ return (
179
+ <div className="flex h-full flex-col">
180
+ {/* Header with summary */}
181
+ <div className="flex items-center justify-between border-b p-4">
182
+ <div className="flex items-center gap-4">
183
+ <div className="flex items-center gap-2">
184
+ <Users className="text-primary h-5 w-5" />
185
+ <span className="font-semibold">Workers</span>
186
+ <span className="text-muted-foreground">
187
+ ({summary?.total || workers.length})
188
+ </span>
189
+ </div>
190
+
191
+ {summary && (
192
+ <div className="flex items-center gap-3 text-sm">
193
+ {summary.running > 0 && (
194
+ <div className="flex items-center gap-1 text-green-500">
195
+ <Loader2 className="h-3 w-3 animate-spin" />
196
+ <span>{summary.running} running</span>
197
+ </div>
198
+ )}
199
+ {summary.waiting > 0 && (
200
+ <div className="flex items-center gap-1 text-yellow-500">
201
+ <AlertCircle className="h-3 w-3" />
202
+ <span>{summary.waiting} waiting</span>
203
+ </div>
204
+ )}
205
+ {summary.completed > 0 && (
206
+ <div className="flex items-center gap-1 text-green-500">
207
+ <CheckCircle className="h-3 w-3" />
208
+ <span>{summary.completed} done</span>
209
+ </div>
210
+ )}
211
+ {summary.failed > 0 && (
212
+ <div className="flex items-center gap-1 text-red-500">
213
+ <XCircle className="h-3 w-3" />
214
+ <span>{summary.failed} failed</span>
215
+ </div>
216
+ )}
217
+ </div>
218
+ )}
219
+ </div>
220
+
221
+ <Button
222
+ variant="ghost"
223
+ size="icon-sm"
224
+ onClick={refresh}
225
+ disabled={refreshing}
226
+ >
227
+ <RefreshCw className={cn("h-4 w-4", refreshing && "animate-spin")} />
228
+ </Button>
229
+ </div>
230
+
231
+ {/* Workers grid */}
232
+ <div className="flex-1 overflow-auto p-4">
233
+ <div className="grid grid-cols-1 gap-3 lg:grid-cols-2">
234
+ {workers.map((worker) => (
235
+ <WorkerCard
236
+ key={worker.id}
237
+ worker={worker}
238
+ isExpanded={expandedWorkers.has(worker.id)}
239
+ output={workerOutputs[worker.id]}
240
+ onToggleExpand={() => toggleExpand(worker.id)}
241
+ onViewOutput={() => {
242
+ toggleExpand(worker.id);
243
+ fetchWorkerOutput(worker.id);
244
+ }}
245
+ onSendMessage={(msg) => handleSendMessage(worker.id, msg)}
246
+ onKill={() => handleKillWorker(worker.id)}
247
+ onAttach={
248
+ onAttachToWorker ? () => onAttachToWorker(worker.id) : undefined
249
+ }
250
+ />
251
+ ))}
252
+ </div>
253
+ </div>
254
+ </div>
255
+ );
256
+ }