@agent-relay/dashboard 2.0.80 → 2.0.82

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 (244) hide show
  1. package/out/404.html +1 -1
  2. package/out/_next/static/chunks/{118-4c8241b0218335de.js → 118-ae2b650136a5a5fc.js} +1 -1
  3. package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +1 -0
  4. package/out/_next/static/chunks/app/app/[[...slug]]/{page-1e81c047cff17212.js → page-f7eca1b66fb4249b.js} +1 -1
  5. package/out/_next/static/chunks/app/{page-6892fe2dd07fb48b.js → page-0ee604f7070d14c0.js} +1 -1
  6. package/out/_next/static/css/8968d98ed4c4d33f.css +1 -0
  7. package/out/about.html +2 -2
  8. package/out/about.txt +1 -1
  9. package/out/app/onboarding.html +1 -1
  10. package/out/app/onboarding.txt +1 -1
  11. package/out/app.html +1 -1
  12. package/out/app.txt +2 -2
  13. package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +2 -2
  14. package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
  15. package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
  16. package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
  17. package/out/blog.html +2 -2
  18. package/out/blog.txt +1 -1
  19. package/out/careers.html +2 -2
  20. package/out/careers.txt +1 -1
  21. package/out/changelog.html +2 -2
  22. package/out/changelog.txt +1 -1
  23. package/out/cloud/link.html +1 -1
  24. package/out/cloud/link.txt +2 -2
  25. package/out/complete-profile.html +2 -2
  26. package/out/complete-profile.txt +1 -1
  27. package/out/connect-repos.html +1 -1
  28. package/out/connect-repos.txt +1 -1
  29. package/out/contact.html +2 -2
  30. package/out/contact.txt +1 -1
  31. package/out/docs.html +2 -2
  32. package/out/docs.txt +1 -1
  33. package/out/history.html +1 -1
  34. package/out/history.txt +2 -2
  35. package/out/index.html +1 -1
  36. package/out/index.txt +2 -2
  37. package/out/login.html +2 -2
  38. package/out/login.txt +1 -1
  39. package/out/metrics.html +1 -1
  40. package/out/metrics.txt +2 -2
  41. package/out/pricing.html +2 -2
  42. package/out/pricing.txt +1 -1
  43. package/out/privacy.html +2 -2
  44. package/out/privacy.txt +1 -1
  45. package/out/providers/setup/claude.html +1 -1
  46. package/out/providers/setup/claude.txt +1 -1
  47. package/out/providers/setup/codex.html +1 -1
  48. package/out/providers/setup/codex.txt +1 -1
  49. package/out/providers/setup/cursor.html +1 -1
  50. package/out/providers/setup/cursor.txt +1 -1
  51. package/out/providers.html +1 -1
  52. package/out/providers.txt +1 -1
  53. package/out/security.html +2 -2
  54. package/out/security.txt +1 -1
  55. package/out/signup.html +2 -2
  56. package/out/signup.txt +1 -1
  57. package/out/terms.html +2 -2
  58. package/out/terms.txt +1 -1
  59. package/package.json +7 -1
  60. package/src/app/about/page.tsx +7 -0
  61. package/src/app/app/[[...slug]]/DashboardPageClient.tsx +853 -0
  62. package/src/app/app/[[...slug]]/page.tsx +23 -0
  63. package/src/app/app/onboarding/page.tsx +394 -0
  64. package/src/app/apple-icon.png +0 -0
  65. package/src/app/blog/go-to-bed-wake-up-to-a-finished-product/page.tsx +88 -0
  66. package/src/app/blog/let-them-cook-multi-agent-orchestration/page.tsx +93 -0
  67. package/src/app/blog/page.tsx +15 -0
  68. package/src/app/careers/page.tsx +7 -0
  69. package/src/app/changelog/page.tsx +7 -0
  70. package/src/app/cloud/link/page.tsx +464 -0
  71. package/src/app/complete-profile/page.tsx +204 -0
  72. package/src/app/connect-repos/page.tsx +410 -0
  73. package/src/app/contact/page.tsx +7 -0
  74. package/src/app/docs/page.tsx +7 -0
  75. package/src/app/favicon.png +0 -0
  76. package/src/app/globals.css +200 -0
  77. package/src/app/history/page.tsx +658 -0
  78. package/src/app/layout.tsx +25 -0
  79. package/src/app/login/page.tsx +424 -0
  80. package/src/app/metrics/page.tsx +781 -0
  81. package/src/app/page.tsx +59 -0
  82. package/src/app/pricing/page.tsx +7 -0
  83. package/src/app/privacy/page.tsx +7 -0
  84. package/src/app/providers/page.tsx +193 -0
  85. package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +197 -0
  86. package/src/app/providers/setup/[provider]/constants.ts +35 -0
  87. package/src/app/providers/setup/[provider]/page.tsx +42 -0
  88. package/src/app/security/page.tsx +7 -0
  89. package/src/app/signup/page.tsx +533 -0
  90. package/src/app/terms/page.tsx +7 -0
  91. package/src/components/ActivityFeed.tsx +216 -0
  92. package/src/components/AddWorkspaceModal.tsx +170 -0
  93. package/src/components/AgentCard.test.tsx +134 -0
  94. package/src/components/AgentCard.tsx +585 -0
  95. package/src/components/AgentList.test.tsx +147 -0
  96. package/src/components/AgentList.tsx +419 -0
  97. package/src/components/AgentLogPreview.tsx +173 -0
  98. package/src/components/AgentProfilePanel.tsx +569 -0
  99. package/src/components/App.tsx +3424 -0
  100. package/src/components/BillingPanel.tsx +922 -0
  101. package/src/components/BillingResult.tsx +447 -0
  102. package/src/components/BroadcastComposer.tsx +690 -0
  103. package/src/components/ChannelAdminPanel.tsx +773 -0
  104. package/src/components/ChannelBrowser.tsx +385 -0
  105. package/src/components/ChannelChat.tsx +261 -0
  106. package/src/components/ChannelSidebar.tsx +399 -0
  107. package/src/components/CloudSessionProvider.tsx +130 -0
  108. package/src/components/CommandPalette.tsx +815 -0
  109. package/src/components/ConfirmationDialog.tsx +133 -0
  110. package/src/components/ConversationHistory.tsx +518 -0
  111. package/src/components/CoordinatorPanel.tsx +956 -0
  112. package/src/components/DecisionQueue.tsx +717 -0
  113. package/src/components/DirectMessageView.tsx +164 -0
  114. package/src/components/FileAutocomplete.tsx +368 -0
  115. package/src/components/FleetOverview.tsx +278 -0
  116. package/src/components/LogViewer.tsx +310 -0
  117. package/src/components/LogViewerPanel.tsx +482 -0
  118. package/src/components/Logo.tsx +284 -0
  119. package/src/components/MentionAutocomplete.tsx +384 -0
  120. package/src/components/MessageComposer.tsx +473 -0
  121. package/src/components/MessageList.tsx +725 -0
  122. package/src/components/MessageSenderName.tsx +91 -0
  123. package/src/components/MessageStatusIndicator.tsx +142 -0
  124. package/src/components/NewConversationModal.tsx +400 -0
  125. package/src/components/NotificationToast.tsx +488 -0
  126. package/src/components/OnlineUsersIndicator.tsx +164 -0
  127. package/src/components/Pagination.tsx +124 -0
  128. package/src/components/PricingPlans.tsx +386 -0
  129. package/src/components/ProjectList.tsx +711 -0
  130. package/src/components/ProviderAuthFlow.tsx +343 -0
  131. package/src/components/ProviderConnectionList.tsx +375 -0
  132. package/src/components/ProvisioningProgress.tsx +730 -0
  133. package/src/components/ReactionChips.tsx +70 -0
  134. package/src/components/ReactionPicker.tsx +121 -0
  135. package/src/components/RepoAccessPanel.tsx +787 -0
  136. package/src/components/RepositoriesPanel.tsx +901 -0
  137. package/src/components/ServerCard.tsx +202 -0
  138. package/src/components/SessionExpiredModal.tsx +128 -0
  139. package/src/components/SpawnModal.test.tsx +190 -0
  140. package/src/components/SpawnModal.tsx +1001 -0
  141. package/src/components/TaskAssignmentUI.tsx +375 -0
  142. package/src/components/TerminalProviderSetup.tsx +517 -0
  143. package/src/components/ThemeProvider.tsx +159 -0
  144. package/src/components/ThinkingIndicator.tsx +231 -0
  145. package/src/components/ThreadList.tsx +198 -0
  146. package/src/components/ThreadPanel.tsx +405 -0
  147. package/src/components/TrajectoryViewer.tsx +698 -0
  148. package/src/components/TypingIndicator.tsx +69 -0
  149. package/src/components/UsageBanner.tsx +231 -0
  150. package/src/components/UserProfilePanel.tsx +233 -0
  151. package/src/components/WorkspaceContext.tsx +95 -0
  152. package/src/components/WorkspaceSelector.tsx +234 -0
  153. package/src/components/WorkspaceStatusIndicator.tsx +396 -0
  154. package/src/components/XTermInteractive.tsx +516 -0
  155. package/src/components/XTermLogViewer.tsx +719 -0
  156. package/src/components/channels/ChannelDialogs.tsx +1411 -0
  157. package/src/components/channels/ChannelHeader.tsx +317 -0
  158. package/src/components/channels/ChannelMessageList.tsx +463 -0
  159. package/src/components/channels/ChannelViewV1.tsx +146 -0
  160. package/src/components/channels/MessageInput.tsx +302 -0
  161. package/src/components/channels/SearchInput.tsx +172 -0
  162. package/src/components/channels/SearchResults.tsx +336 -0
  163. package/src/components/channels/api.test.ts +1527 -0
  164. package/src/components/channels/api.ts +703 -0
  165. package/src/components/channels/index.ts +76 -0
  166. package/src/components/channels/mockApi.ts +344 -0
  167. package/src/components/channels/types.ts +566 -0
  168. package/src/components/hooks/index.ts +58 -0
  169. package/src/components/hooks/useAgentLogs.ts +504 -0
  170. package/src/components/hooks/useAgents.ts +127 -0
  171. package/src/components/hooks/useBroadcastDedup.test.ts +371 -0
  172. package/src/components/hooks/useBroadcastDedup.ts +86 -0
  173. package/src/components/hooks/useChannelAdmin.ts +329 -0
  174. package/src/components/hooks/useChannelBrowser.ts +239 -0
  175. package/src/components/hooks/useChannelCommands.ts +138 -0
  176. package/src/components/hooks/useChannels.ts +367 -0
  177. package/src/components/hooks/useDebounce.ts +29 -0
  178. package/src/components/hooks/useDirectMessage.test.ts +952 -0
  179. package/src/components/hooks/useDirectMessage.ts +141 -0
  180. package/src/components/hooks/useMessages.ts +310 -0
  181. package/src/components/hooks/useOrchestrator.test.ts +165 -0
  182. package/src/components/hooks/useOrchestrator.ts +424 -0
  183. package/src/components/hooks/usePinnedAgents.test.ts +356 -0
  184. package/src/components/hooks/usePinnedAgents.ts +140 -0
  185. package/src/components/hooks/usePresence.test.ts +245 -0
  186. package/src/components/hooks/usePresence.ts +377 -0
  187. package/src/components/hooks/useRecentRepos.ts +130 -0
  188. package/src/components/hooks/useSession.ts +209 -0
  189. package/src/components/hooks/useThread.ts +138 -0
  190. package/src/components/hooks/useTrajectory.ts +265 -0
  191. package/src/components/hooks/useWebSocket.ts +290 -0
  192. package/src/components/hooks/useWorkspaceMembers.ts +132 -0
  193. package/src/components/hooks/useWorkspaceRepos.ts +73 -0
  194. package/src/components/hooks/useWorkspaceStatus.ts +237 -0
  195. package/src/components/index.ts +81 -0
  196. package/src/components/layout/Header.tsx +311 -0
  197. package/src/components/layout/RepoContextHeader.tsx +361 -0
  198. package/src/components/layout/Sidebar.archive.test.tsx +126 -0
  199. package/src/components/layout/Sidebar.test.tsx +691 -0
  200. package/src/components/layout/Sidebar.tsx +900 -0
  201. package/src/components/layout/index.ts +7 -0
  202. package/src/components/settings/BillingSettingsPanel.tsx +564 -0
  203. package/src/components/settings/SettingsPage.tsx +683 -0
  204. package/src/components/settings/TeamSettingsPanel.tsx +560 -0
  205. package/src/components/settings/WorkspaceSettingsPanel.tsx +1368 -0
  206. package/src/components/settings/index.ts +11 -0
  207. package/src/components/settings/types.ts +79 -0
  208. package/src/components/utils/messageFormatting.test.tsx +331 -0
  209. package/src/components/utils/messageFormatting.tsx +597 -0
  210. package/src/index.ts +63 -0
  211. package/src/landing/AboutPage.tsx +77 -0
  212. package/src/landing/BlogContent.tsx +187 -0
  213. package/src/landing/BlogPage.tsx +47 -0
  214. package/src/landing/CareersPage.tsx +53 -0
  215. package/src/landing/ChangelogPage.tsx +33 -0
  216. package/src/landing/ContactPage.tsx +41 -0
  217. package/src/landing/DocsPage.tsx +43 -0
  218. package/src/landing/LandingPage.tsx +702 -0
  219. package/src/landing/PricingPage.tsx +549 -0
  220. package/src/landing/PrivacyPage.tsx +117 -0
  221. package/src/landing/SecurityPage.tsx +42 -0
  222. package/src/landing/StaticPage.tsx +165 -0
  223. package/src/landing/TermsPage.tsx +125 -0
  224. package/src/landing/blogData.ts +312 -0
  225. package/src/landing/index.ts +18 -0
  226. package/src/landing/styles.css +3673 -0
  227. package/src/lib/agent-merge.test.ts +43 -0
  228. package/src/lib/agent-merge.ts +35 -0
  229. package/src/lib/api.ts +1294 -0
  230. package/src/lib/cloudApi.ts +893 -0
  231. package/src/lib/colors.test.ts +175 -0
  232. package/src/lib/colors.ts +218 -0
  233. package/src/lib/config.ts +109 -0
  234. package/src/lib/hierarchy.ts +242 -0
  235. package/src/lib/stuckDetection.ts +142 -0
  236. package/src/lib/useUrlRouting.ts +190 -0
  237. package/src/types/index.ts +317 -0
  238. package/src/types/threading.ts +7 -0
  239. package/out/_next/static/chunks/285-dc644487a8d6500d.js +0 -1
  240. package/out/_next/static/css/4c58d9cf493aa626.css +0 -1
  241. /package/out/_next/static/{AqelRhy1vr2nBUcU0Iqcp → IxfA6RZu4trcsEMYlkQra}/_buildManifest.js +0 -0
  242. /package/out/_next/static/{AqelRhy1vr2nBUcU0Iqcp → IxfA6RZu4trcsEMYlkQra}/_ssgManifest.js +0 -0
  243. /package/out/_next/static/chunks/{528-d375bc8b46912d2c.js → 528-f5f676996d613c25.js} +0 -0
  244. /package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/{page-a58308f43557b908.js → page-b194f207fbd91862.js} +0 -0
@@ -0,0 +1,585 @@
1
+ /**
2
+ * AgentCard Component
3
+ *
4
+ * Displays an agent with a distinctive neural/holographic design language.
5
+ * Features gradient backgrounds, animated status indicators, and depth effects.
6
+ */
7
+
8
+ import React from 'react';
9
+ import type { Agent } from '../types';
10
+ import {
11
+ getAgentColor,
12
+ getAgentInitials,
13
+ STATUS_COLORS,
14
+ type AgentStatus,
15
+ } from '../lib/colors';
16
+ import { getAgentDisplayName, getAgentBreadcrumb } from '../lib/hierarchy';
17
+ import { ThinkingIndicator, ThinkingDot } from './ThinkingIndicator';
18
+ import { getStuckDuration, formatStuckDuration } from '../lib/stuckDetection';
19
+
20
+ export interface AgentCardProps {
21
+ agent: Agent;
22
+ isSelected?: boolean;
23
+ showBreadcrumb?: boolean;
24
+ compact?: boolean;
25
+ displayNameOverride?: string;
26
+ /** Whether the agent is pinned to the top */
27
+ isPinned?: boolean;
28
+ /** Whether max pins has been reached (disables pin button for unpinned agents) */
29
+ isMaxPinned?: boolean;
30
+ onClick?: (agent: Agent) => void;
31
+ onMessageClick?: (agent: Agent) => void;
32
+ onReleaseClick?: (agent: Agent) => void;
33
+ onLogsClick?: (agent: Agent) => void;
34
+ onProfileClick?: (agent: Agent) => void;
35
+ /** Handler for pin/unpin toggle */
36
+ onPinToggle?: (agent: Agent) => void;
37
+ }
38
+
39
+ /**
40
+ * Get a descriptive tooltip for an agent's connection status.
41
+ */
42
+ function getStatusTooltip(status: AgentStatus, isProcessing?: boolean, isStuck?: boolean, stuckDuration?: number): string {
43
+ if (isStuck && stuckDuration) {
44
+ return `Stuck - Agent received message ${formatStuckDuration(stuckDuration)} ago but hasn't responded`;
45
+ }
46
+ if (isProcessing) {
47
+ return 'Processing - Agent is actively working';
48
+ }
49
+ switch (status) {
50
+ case 'online':
51
+ return 'Connected - Agent is online and ready';
52
+ case 'offline':
53
+ return 'Disconnected - Agent is not connected';
54
+ case 'busy':
55
+ return 'Busy - Agent is occupied with a task';
56
+ case 'processing':
57
+ return 'Processing - Agent is actively working';
58
+ case 'error':
59
+ return 'Error - Agent encountered an error';
60
+ case 'attention':
61
+ return 'Attention - Agent requires user input';
62
+ case 'stuck':
63
+ return 'Stuck - Agent may be blocked or unresponsive';
64
+ default:
65
+ return `Status: ${status}`;
66
+ }
67
+ }
68
+
69
+ export function AgentCard({
70
+ agent,
71
+ isSelected = false,
72
+ showBreadcrumb = false,
73
+ compact = false,
74
+ displayNameOverride,
75
+ isPinned = false,
76
+ isMaxPinned = false,
77
+ onClick,
78
+ onMessageClick,
79
+ onReleaseClick,
80
+ onLogsClick,
81
+ onProfileClick,
82
+ onPinToggle,
83
+ }: AgentCardProps) {
84
+ const colors = getAgentColor(agent.name);
85
+ const initials = getAgentInitials(agent.name);
86
+ const displayName = displayNameOverride || getAgentDisplayName(agent.name);
87
+ const stuckDuration = getStuckDuration(agent);
88
+ const isStuck = agent.isStuck || stuckDuration > 0;
89
+ const statusColor = isStuck ? STATUS_COLORS.stuck : (STATUS_COLORS[agent.status] || STATUS_COLORS.offline);
90
+ const isOnline = agent.status === 'online';
91
+ const statusTooltip = getStatusTooltip(agent.status, agent.isProcessing, isStuck, stuckDuration);
92
+
93
+ const handleClick = () => {
94
+ onClick?.(agent);
95
+ };
96
+
97
+ const handleMessageClick = (e: React.MouseEvent) => {
98
+ e.stopPropagation();
99
+ onMessageClick?.(agent);
100
+ };
101
+
102
+ const handleReleaseClick = (e: React.MouseEvent) => {
103
+ e.stopPropagation();
104
+ onReleaseClick?.(agent);
105
+ };
106
+
107
+ const handleLogsClick = (e: React.MouseEvent) => {
108
+ e.stopPropagation();
109
+ onLogsClick?.(agent);
110
+ };
111
+
112
+ const handleProfileClick = (e: React.MouseEvent) => {
113
+ e.stopPropagation();
114
+ onProfileClick?.(agent);
115
+ };
116
+
117
+ const handlePinToggle = (e: React.MouseEvent) => {
118
+ e.stopPropagation();
119
+ onPinToggle?.(agent);
120
+ };
121
+
122
+ if (compact) {
123
+ return (
124
+ <div
125
+ className={`
126
+ group relative flex items-start gap-3 py-2.5 px-3 rounded-lg cursor-pointer
127
+ transition-all duration-300 ease-out
128
+ hover:bg-bg-hover
129
+ ${isSelected ? 'bg-bg-active' : ''}
130
+ `}
131
+ onClick={handleClick}
132
+ style={{
133
+ borderLeft: isSelected ? `2px solid ${colors.primary}` : '2px solid transparent',
134
+ boxShadow: isSelected ? `inset 4px 0 12px -4px ${colors.primary}40` : 'none',
135
+ }}
136
+ >
137
+ {/* Agent Avatar with Glow */}
138
+ <div className="relative">
139
+ <div
140
+ className={`
141
+ w-8 h-8 rounded-lg flex items-center justify-center font-bold text-[11px] tracking-wide
142
+ transition-all duration-300 relative overflow-hidden
143
+ ${isOnline ? 'shadow-lg' : 'opacity-60'}
144
+ `}
145
+ style={{
146
+ background: `linear-gradient(135deg, ${colors.primary}, ${colors.primary}99)`,
147
+ boxShadow: isOnline ? `0 2px 12px ${colors.primary}50` : 'none',
148
+ }}
149
+ >
150
+ {/* Subtle shine effect */}
151
+ <div
152
+ className="absolute inset-0 opacity-30"
153
+ style={{
154
+ background: 'linear-gradient(135deg, rgba(255,255,255,0.3) 0%, transparent 50%)',
155
+ }}
156
+ />
157
+ <span className="relative z-10" style={{ color: colors.text }}>{initials}</span>
158
+ </div>
159
+ {/* Status Ring */}
160
+ {isOnline && (
161
+ <div
162
+ className="absolute -bottom-0.5 -right-0.5 w-3 h-3 rounded-full border-2 border-bg-primary"
163
+ style={{
164
+ backgroundColor: statusColor,
165
+ boxShadow: `0 0 8px ${statusColor}`,
166
+ }}
167
+ title={statusTooltip}
168
+ />
169
+ )}
170
+ </div>
171
+
172
+ {/* Agent Info */}
173
+ <div className="flex-1 min-w-0 flex flex-col">
174
+ <div className="flex items-start gap-1.5 flex-wrap">
175
+ <span
176
+ className={`
177
+ text-[13px] font-semibold tracking-tight transition-colors duration-200
178
+ whitespace-normal break-words leading-snug
179
+ ${isOnline ? 'text-text-primary' : 'text-text-secondary'}
180
+ `}
181
+ >
182
+ {displayName}
183
+ </span>
184
+ {agent.isLocal && (
185
+ <span
186
+ className="inline-flex items-center px-1.5 py-0.5 rounded text-[9px] font-medium uppercase tracking-wider
187
+ bg-warning-light text-warning border border-warning/30"
188
+ title={`Local agent from ${agent.daemonName || 'linked daemon'}`}
189
+ >
190
+ Local
191
+ </span>
192
+ )}
193
+ {isPinned && (
194
+ <span
195
+ className="inline-flex items-center text-amber-400"
196
+ title="Pinned to top"
197
+ >
198
+ <PinIcon size={12} filled />
199
+ </span>
200
+ )}
201
+ </div>
202
+ {agent.cli && (
203
+ <span className="text-[10px] text-text-muted truncate font-mono opacity-70 mt-0.5">
204
+ {agent.cli}
205
+ </span>
206
+ )}
207
+ {(agent.model || agent.profile?.model) && (
208
+ <span className="text-[9px] text-accent-muted font-mono opacity-80 mt-0.5" title={`Model: ${agent.model || agent.profile?.model}`}>
209
+ {agent.model || agent.profile?.model}
210
+ </span>
211
+ )}
212
+
213
+ {/* Actions & Status */}
214
+ <div className="mt-2 flex items-center flex-wrap gap-2">
215
+ {onPinToggle && (
216
+ <button
217
+ className={`
218
+ relative bg-transparent border border-transparent p-1.5 cursor-pointer
219
+ flex items-center justify-center rounded-md transition-all duration-200
220
+ ${isPinned
221
+ ? 'text-amber-400 hover:bg-amber-400/10 hover:border-amber-400/30'
222
+ : 'text-text-dim hover:bg-amber-400/10 hover:border-amber-400/30 hover:text-amber-400'}
223
+ ${!isPinned && isMaxPinned ? 'opacity-40 cursor-not-allowed' : ''}
224
+ `}
225
+ onClick={handlePinToggle}
226
+ title={isPinned ? 'Unpin from top' : isMaxPinned ? 'Maximum pins reached (5)' : 'Pin to top'}
227
+ disabled={!isPinned && isMaxPinned}
228
+ >
229
+ <PinIcon size={16} filled={isPinned} />
230
+ </button>
231
+ )}
232
+ {onProfileClick && (
233
+ <button
234
+ className="relative bg-transparent border border-transparent text-text-dim p-1.5 cursor-pointer
235
+ flex items-center justify-center rounded-md transition-all duration-200
236
+ opacity-100
237
+ hover:bg-accent-purple/10 hover:border-accent-purple/30 hover:text-accent-purple"
238
+ onClick={handleProfileClick}
239
+ title="View profile"
240
+ >
241
+ <ProfileIcon />
242
+ </button>
243
+ )}
244
+ {onLogsClick && (
245
+ <button
246
+ className="relative bg-transparent border border-transparent text-text-dim p-1.5 cursor-pointer
247
+ flex items-center justify-center rounded-md transition-all duration-200
248
+ opacity-100
249
+ hover:bg-accent-cyan/10 hover:border-accent-cyan/30 hover:text-accent-cyan"
250
+ onClick={handleLogsClick}
251
+ title="View logs"
252
+ >
253
+ <LogsIcon />
254
+ </button>
255
+ )}
256
+ {agent.isSpawned && onReleaseClick && (
257
+ <button
258
+ className="relative bg-transparent border border-transparent text-text-dim p-1.5 cursor-pointer
259
+ flex items-center justify-center rounded-md transition-all duration-200
260
+ opacity-100
261
+ hover:bg-error/10 hover:border-error/30 hover:text-error"
262
+ onClick={handleReleaseClick}
263
+ title="Kill agent"
264
+ >
265
+ <ReleaseIcon />
266
+ </button>
267
+ )}
268
+ {agent.isProcessing ? (
269
+ <div title={statusTooltip}>
270
+ <ThinkingDot isProcessing={true} />
271
+ </div>
272
+ ) : (
273
+ <div
274
+ className={`
275
+ w-2 h-2 rounded-full transition-all duration-300
276
+ ${isOnline ? 'animate-pulse' : ''}
277
+ `}
278
+ style={{
279
+ backgroundColor: statusColor,
280
+ boxShadow: isOnline ? `0 0 6px ${statusColor}` : 'none',
281
+ }}
282
+ title={statusTooltip}
283
+ />
284
+ )}
285
+ {isStuck && (
286
+ <div
287
+ className="flex items-center gap-1 px-1.5 py-0.5 rounded bg-warning-light text-warning text-[10px] font-medium animate-pulse"
288
+ title={statusTooltip}
289
+ >
290
+ <StuckIcon />
291
+ <span>{formatStuckDuration(stuckDuration)}</span>
292
+ </div>
293
+ )}
294
+ </div>
295
+ </div>
296
+ </div>
297
+ );
298
+ }
299
+
300
+ return (
301
+ <div
302
+ className={`
303
+ rounded-lg p-3 cursor-pointer transition-all duration-200
304
+ hover:shadow-md hover:-translate-y-px
305
+ ${isSelected ? 'border-2 shadow-[0_0_0_2px_rgba(74,158,255,0.15)]' : 'border'}
306
+ `}
307
+ onClick={handleClick}
308
+ style={{
309
+ backgroundColor: colors.light,
310
+ borderColor: colors.primary,
311
+ }}
312
+ >
313
+ <div className="flex items-center gap-3">
314
+ <div
315
+ className="w-10 h-10 rounded-lg flex items-center justify-center font-semibold text-sm relative"
316
+ style={{ backgroundColor: colors.primary }}
317
+ >
318
+ <span style={{ color: colors.text }}>{initials}</span>
319
+ <div
320
+ className="absolute -bottom-0.5 -right-0.5 w-3 h-3 rounded-full border-2 border-bg-secondary"
321
+ style={{ backgroundColor: statusColor }}
322
+ title={statusTooltip}
323
+ />
324
+ </div>
325
+ <div className="flex-1 min-w-0">
326
+ <div className="flex items-center gap-2">
327
+ <span className="font-semibold text-sm text-text-primary">{displayName}</span>
328
+ {agent.isLocal && (
329
+ <span
330
+ className="inline-flex items-center px-1.5 py-0.5 rounded text-[9px] font-medium uppercase tracking-wider
331
+ bg-warning-light text-warning border border-warning/30"
332
+ title={`Local agent from ${agent.daemonName || 'linked daemon'}`}
333
+ >
334
+ Local
335
+ </span>
336
+ )}
337
+ {isPinned && (
338
+ <span
339
+ className="inline-flex items-center text-amber-400"
340
+ title="Pinned to top"
341
+ >
342
+ <PinIcon size={14} filled />
343
+ </span>
344
+ )}
345
+ {agent.needsAttention && (
346
+ <span className="bg-error text-white text-[10px] font-bold w-4 h-4 rounded-full flex items-center justify-center" title="Needs Attention - Agent requires user input or has pending decisions">!</span>
347
+ )}
348
+ {isStuck && (
349
+ <span className="bg-warning text-white text-[10px] font-bold px-1.5 py-0.5 rounded flex items-center gap-1" title={statusTooltip}>
350
+ <StuckIcon /> {formatStuckDuration(stuckDuration)}
351
+ </span>
352
+ )}
353
+ </div>
354
+ {showBreadcrumb ? (
355
+ <span className="text-xs text-text-muted truncate block">{agent.isLocal ? agent.daemonName || agent.machineId : getAgentBreadcrumb(agent.name)}</span>
356
+ ) : (
357
+ <span className="text-xs text-text-muted truncate block">{agent.isLocal ? agent.daemonName || agent.machineId : agent.name}</span>
358
+ )}
359
+ {agent.agentId && (
360
+ <span className="text-[10px] text-text-muted font-mono opacity-70" title="Agent ID (use to resume)">
361
+ ID: {agent.agentId}
362
+ </span>
363
+ )}
364
+ </div>
365
+ </div>
366
+
367
+ {agent.isProcessing && (
368
+ <div className="mt-2 p-2 bg-accent-light rounded flex items-center gap-2 border border-accent/20">
369
+ <ThinkingIndicator
370
+ isProcessing={true}
371
+ processingStartedAt={agent.processingStartedAt}
372
+ size="medium"
373
+ showElapsed={true}
374
+ />
375
+ <span className="text-xs text-accent font-medium">Thinking...</span>
376
+ </div>
377
+ )}
378
+
379
+ {agent.currentTask && !agent.isProcessing && (
380
+ <div className="mt-2 p-2 bg-bg-hover rounded text-xs">
381
+ <span className="text-text-muted mr-1">Working on:</span>
382
+ <span className="text-text-primary line-clamp-2" title={agent.currentTask}>{agent.currentTask}</span>
383
+ </div>
384
+ )}
385
+
386
+ <div className="mt-3 flex justify-between items-center">
387
+ <div className="flex gap-2 text-xs text-text-muted flex-wrap">
388
+ {agent.cli && <span className="bg-bg-hover py-0.5 px-1.5 rounded">{agent.cli}</span>}
389
+ {(agent.model || agent.profile?.model) && (
390
+ <span className="bg-accent-cyan/10 text-accent-cyan py-0.5 px-1.5 rounded font-mono text-[10px]" title={`Model: ${agent.model || agent.profile?.model}`}>
391
+ {agent.model || agent.profile?.model}
392
+ </span>
393
+ )}
394
+ {agent.messageCount !== undefined && agent.messageCount > 0 && (
395
+ <span style={{ color: colors.primary }}>{agent.messageCount} msgs</span>
396
+ )}
397
+ {agent.isSpawned && (
398
+ <span className="bg-accent-light text-accent text-[10px] py-0.5 px-1.5 rounded uppercase font-medium">spawned</span>
399
+ )}
400
+ </div>
401
+ <div className="flex gap-1.5">
402
+ {onPinToggle && (
403
+ <button
404
+ className={`
405
+ rounded-md py-1.5 px-2.5 cursor-pointer flex items-center justify-center gap-1 transition-all duration-200
406
+ border
407
+ ${isPinned
408
+ ? 'bg-amber-500/10 text-amber-400 border-amber-500/30 hover:bg-amber-500/20'
409
+ : 'bg-bg-tertiary text-text-muted border-border hover:bg-amber-500/10 hover:text-amber-400 hover:border-amber-500/30'}
410
+ ${!isPinned && isMaxPinned ? 'opacity-40 cursor-not-allowed' : 'hover:scale-105'}
411
+ active:scale-[0.98]
412
+ `}
413
+ onClick={handlePinToggle}
414
+ title={isPinned ? 'Unpin from top' : isMaxPinned ? 'Maximum pins reached (5)' : 'Pin to top'}
415
+ disabled={!isPinned && isMaxPinned}
416
+ >
417
+ <PinIcon size={16} filled={isPinned} />
418
+ </button>
419
+ )}
420
+ {onProfileClick && (
421
+ <button
422
+ className="bg-accent-purple/10 text-accent-purple border border-accent-purple/30 rounded-md py-1.5 px-2.5 cursor-pointer flex items-center justify-center gap-1 transition-all duration-200 hover:bg-accent-purple/20 hover:scale-105 active:scale-[0.98]"
423
+ onClick={handleProfileClick}
424
+ title="View profile"
425
+ >
426
+ <ProfileIcon />
427
+ </button>
428
+ )}
429
+ {onLogsClick && (
430
+ <button
431
+ className="bg-accent-cyan/10 text-accent-cyan border border-accent-cyan/30 rounded-md py-1.5 px-2.5 cursor-pointer flex items-center justify-center gap-1 transition-all duration-200 hover:bg-accent-cyan/20 hover:scale-105 active:scale-[0.98]"
432
+ onClick={handleLogsClick}
433
+ title="View logs"
434
+ >
435
+ <LogsIcon />
436
+ </button>
437
+ )}
438
+ {agent.isSpawned && onReleaseClick && (
439
+ <button
440
+ className="bg-error/10 text-error border border-error/30 rounded-md py-1.5 px-2.5 cursor-pointer flex items-center justify-center gap-1 transition-all duration-200 hover:bg-error/20 hover:scale-105 active:scale-[0.98]"
441
+ onClick={handleReleaseClick}
442
+ title="Release agent"
443
+ >
444
+ <ReleaseIcon />
445
+ </button>
446
+ )}
447
+ {onMessageClick && (
448
+ <button
449
+ className="text-white border-none rounded py-1 px-2 cursor-pointer flex items-center justify-center transition-opacity duration-200 hover:opacity-90"
450
+ style={{ backgroundColor: colors.primary }}
451
+ onClick={handleMessageClick}
452
+ title="Send message"
453
+ >
454
+ <MessageIcon />
455
+ </button>
456
+ )}
457
+ </div>
458
+ </div>
459
+ </div>
460
+ );
461
+ }
462
+
463
+ function MessageIcon() {
464
+ return (
465
+ <svg
466
+ width="16"
467
+ height="16"
468
+ viewBox="0 0 24 24"
469
+ fill="none"
470
+ stroke="currentColor"
471
+ strokeWidth="2"
472
+ strokeLinecap="round"
473
+ strokeLinejoin="round"
474
+ >
475
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
476
+ </svg>
477
+ );
478
+ }
479
+
480
+ function ReleaseIcon() {
481
+ return (
482
+ <svg
483
+ width="16"
484
+ height="16"
485
+ viewBox="0 0 24 24"
486
+ fill="none"
487
+ className="release-icon"
488
+ >
489
+ <path
490
+ d="M12 22c5.523 0 10-4.477 10-10a9.96 9.96 0 0 0-3-7.141"
491
+ stroke="currentColor"
492
+ strokeWidth="2"
493
+ strokeLinecap="round"
494
+ />
495
+ <path
496
+ d="M12 22C6.477 22 2 17.523 2 12a9.96 9.96 0 0 1 3-7.141"
497
+ stroke="currentColor"
498
+ strokeWidth="2"
499
+ strokeLinecap="round"
500
+ />
501
+ <line
502
+ x1="12"
503
+ y1="2"
504
+ x2="12"
505
+ y2="12"
506
+ stroke="currentColor"
507
+ strokeWidth="2.5"
508
+ strokeLinecap="round"
509
+ />
510
+ </svg>
511
+ );
512
+ }
513
+
514
+ function LogsIcon() {
515
+ return (
516
+ <svg
517
+ width="16"
518
+ height="16"
519
+ viewBox="0 0 24 24"
520
+ fill="none"
521
+ stroke="currentColor"
522
+ strokeWidth="2"
523
+ strokeLinecap="round"
524
+ strokeLinejoin="round"
525
+ >
526
+ <polyline points="4 17 10 11 4 5" />
527
+ <line x1="12" y1="19" x2="20" y2="19" />
528
+ </svg>
529
+ );
530
+ }
531
+
532
+ function StuckIcon() {
533
+ return (
534
+ <svg
535
+ width="12"
536
+ height="12"
537
+ viewBox="0 0 24 24"
538
+ fill="none"
539
+ stroke="currentColor"
540
+ strokeWidth="2"
541
+ strokeLinecap="round"
542
+ strokeLinejoin="round"
543
+ >
544
+ <circle cx="12" cy="12" r="10" />
545
+ <line x1="12" y1="8" x2="12" y2="12" />
546
+ <line x1="12" y1="16" x2="12.01" y2="16" />
547
+ </svg>
548
+ );
549
+ }
550
+
551
+ function ProfileIcon() {
552
+ return (
553
+ <svg
554
+ width="16"
555
+ height="16"
556
+ viewBox="0 0 24 24"
557
+ fill="none"
558
+ stroke="currentColor"
559
+ strokeWidth="2"
560
+ strokeLinecap="round"
561
+ strokeLinejoin="round"
562
+ >
563
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
564
+ <circle cx="12" cy="7" r="4" />
565
+ </svg>
566
+ );
567
+ }
568
+
569
+ function PinIcon({ size = 16, filled = false }: { size?: number; filled?: boolean }) {
570
+ return (
571
+ <svg
572
+ width={size}
573
+ height={size}
574
+ viewBox="0 0 24 24"
575
+ fill={filled ? 'currentColor' : 'none'}
576
+ stroke="currentColor"
577
+ strokeWidth="2"
578
+ strokeLinecap="round"
579
+ strokeLinejoin="round"
580
+ >
581
+ <path d="M12 17v5" />
582
+ <path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4.76Z" />
583
+ </svg>
584
+ );
585
+ }