@agent-relay/dashboard 2.0.81 → 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/{dYlczDQI12PIQ3tqq3N4Y → IxfA6RZu4trcsEMYlkQra}/_buildManifest.js +0 -0
  242. /package/out/_next/static/{dYlczDQI12PIQ3tqq3N4Y → 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,175 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ parseAgentHierarchy,
4
+ getAgentPrefix,
5
+ getAgentColor,
6
+ getAgentInitials,
7
+ groupAgentsByPrefix,
8
+ sortAgentsByHierarchy,
9
+ STATUS_COLORS,
10
+ } from './colors.js';
11
+
12
+ describe('colors', () => {
13
+ describe('parseAgentHierarchy', () => {
14
+ it('parses single-level name', () => {
15
+ expect(parseAgentHierarchy('Lead')).toEqual(['lead']);
16
+ });
17
+
18
+ it('parses two-level name', () => {
19
+ expect(parseAgentHierarchy('backend-api')).toEqual(['backend', 'api']);
20
+ });
21
+
22
+ it('parses three-level name', () => {
23
+ expect(parseAgentHierarchy('frontend-ui-components')).toEqual([
24
+ 'frontend',
25
+ 'ui',
26
+ 'components',
27
+ ]);
28
+ });
29
+
30
+ it('handles uppercase names', () => {
31
+ expect(parseAgentHierarchy('Backend-API')).toEqual(['backend', 'api']);
32
+ });
33
+
34
+ it('handles empty string', () => {
35
+ expect(parseAgentHierarchy('')).toEqual([]);
36
+ });
37
+ });
38
+
39
+ describe('getAgentPrefix', () => {
40
+ it('returns first segment for hyphenated name', () => {
41
+ expect(getAgentPrefix('backend-api-auth')).toBe('backend');
42
+ });
43
+
44
+ it('returns lowercase name for single segment', () => {
45
+ expect(getAgentPrefix('Lead')).toBe('lead');
46
+ });
47
+
48
+ it('handles empty string', () => {
49
+ expect(getAgentPrefix('')).toBe('');
50
+ });
51
+ });
52
+
53
+ describe('getAgentColor', () => {
54
+ it('returns predefined color for known prefix', () => {
55
+ const color = getAgentColor('backend-api');
56
+ expect(color.primary).toBe('#1264a3'); // Blue for backend
57
+ });
58
+
59
+ it('returns predefined color for frontend prefix', () => {
60
+ const color = getAgentColor('frontend-ui');
61
+ expect(color.primary).toBe('#7c3aed'); // Purple for frontend
62
+ });
63
+
64
+ it('returns predefined color for lead prefix', () => {
65
+ const color = getAgentColor('lead-main');
66
+ expect(color.primary).toBe('#2bac76'); // Green for lead
67
+ });
68
+
69
+ it('returns consistent fallback color for unknown prefix', () => {
70
+ const color1 = getAgentColor('custom-agent');
71
+ const color2 = getAgentColor('custom-other');
72
+ // Same prefix should get same color
73
+ expect(color1).toEqual(color2);
74
+ });
75
+
76
+ it('returns different colors for different unknown prefixes', () => {
77
+ const color1 = getAgentColor('alpha-agent');
78
+ const color2 = getAgentColor('beta-agent');
79
+ // Different prefixes should likely get different colors
80
+ // (not guaranteed due to hash collisions, but likely)
81
+ expect(color1.primary).not.toBe(color2.primary);
82
+ });
83
+
84
+ it('includes all required color properties', () => {
85
+ const color = getAgentColor('any-agent');
86
+ expect(color).toHaveProperty('primary');
87
+ expect(color).toHaveProperty('light');
88
+ expect(color).toHaveProperty('dark');
89
+ expect(color).toHaveProperty('text');
90
+ });
91
+ });
92
+
93
+ describe('getAgentInitials', () => {
94
+ it('returns first two letters for single segment', () => {
95
+ expect(getAgentInitials('Lead')).toBe('LE');
96
+ });
97
+
98
+ it('returns first letter of each segment for two segments', () => {
99
+ expect(getAgentInitials('backend-api')).toBe('BA');
100
+ });
101
+
102
+ it('returns first letter of first two segments for three segments', () => {
103
+ expect(getAgentInitials('frontend-ui-components')).toBe('FU');
104
+ });
105
+
106
+ it('handles single character segments', () => {
107
+ expect(getAgentInitials('a-b-c')).toBe('AB');
108
+ });
109
+ });
110
+
111
+ describe('groupAgentsByPrefix', () => {
112
+ it('groups agents by their prefix', () => {
113
+ const agents = [
114
+ { name: 'backend-api' },
115
+ { name: 'backend-db' },
116
+ { name: 'frontend-ui' },
117
+ { name: 'Lead' },
118
+ ];
119
+
120
+ const groups = groupAgentsByPrefix(agents);
121
+
122
+ expect(groups.get('backend')).toHaveLength(2);
123
+ expect(groups.get('frontend')).toHaveLength(1);
124
+ expect(groups.get('lead')).toHaveLength(1);
125
+ });
126
+
127
+ it('handles empty array', () => {
128
+ const groups = groupAgentsByPrefix([]);
129
+ expect(groups.size).toBe(0);
130
+ });
131
+ });
132
+
133
+ describe('sortAgentsByHierarchy', () => {
134
+ it('sorts agents by prefix then by name', () => {
135
+ const agents = [
136
+ { name: 'frontend-ui' },
137
+ { name: 'backend-db' },
138
+ { name: 'backend-api' },
139
+ { name: 'Lead' },
140
+ ];
141
+
142
+ const sorted = sortAgentsByHierarchy(agents);
143
+
144
+ expect(sorted.map((a) => a.name)).toEqual([
145
+ 'backend-api',
146
+ 'backend-db',
147
+ 'frontend-ui',
148
+ 'Lead',
149
+ ]);
150
+ });
151
+
152
+ it('does not mutate original array', () => {
153
+ const agents = [{ name: 'z-agent' }, { name: 'a-agent' }];
154
+ const original = [...agents];
155
+
156
+ sortAgentsByHierarchy(agents);
157
+
158
+ expect(agents).toEqual(original);
159
+ });
160
+ });
161
+
162
+ describe('STATUS_COLORS', () => {
163
+ it('has all required status colors', () => {
164
+ expect(STATUS_COLORS.online).toBeDefined();
165
+ expect(STATUS_COLORS.offline).toBeDefined();
166
+ expect(STATUS_COLORS.busy).toBeDefined();
167
+ expect(STATUS_COLORS.error).toBeDefined();
168
+ expect(STATUS_COLORS.attention).toBeDefined();
169
+ });
170
+
171
+ it('uses green for online status', () => {
172
+ expect(STATUS_COLORS.online).toBe('#22c55e');
173
+ });
174
+ });
175
+ });
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Hierarchical Color Coding System
3
+ *
4
+ * Inspired by AI Maestro's naming convention:
5
+ * - Agent names use hyphens to denote hierarchy: project-category-agent
6
+ * - Each top-level prefix gets a consistent color
7
+ * - Colors are visually distinct and accessible
8
+ */
9
+
10
+ export interface ColorScheme {
11
+ primary: string;
12
+ light: string;
13
+ dark: string;
14
+ text: string;
15
+ }
16
+
17
+ // Predefined color schemes for common agent prefixes
18
+ const PREFIX_COLORS: Record<string, ColorScheme> = {
19
+ backend: {
20
+ primary: '#1264a3',
21
+ light: '#e8f4fd',
22
+ dark: '#0d4f82',
23
+ text: '#ffffff',
24
+ },
25
+ frontend: {
26
+ primary: '#7c3aed',
27
+ light: '#f3e8ff',
28
+ dark: '#5b21b6',
29
+ text: '#ffffff',
30
+ },
31
+ infra: {
32
+ primary: '#ea580c',
33
+ light: '#fff7ed',
34
+ dark: '#c2410c',
35
+ text: '#ffffff',
36
+ },
37
+ lead: {
38
+ primary: '#2bac76',
39
+ light: '#ecfdf5',
40
+ dark: '#059669',
41
+ text: '#ffffff',
42
+ },
43
+ test: {
44
+ primary: '#0d9488',
45
+ light: '#f0fdfa',
46
+ dark: '#0f766e',
47
+ text: '#ffffff',
48
+ },
49
+ data: {
50
+ primary: '#dc2626',
51
+ light: '#fef2f2',
52
+ dark: '#b91c1c',
53
+ text: '#ffffff',
54
+ },
55
+ api: {
56
+ primary: '#2563eb',
57
+ light: '#eff6ff',
58
+ dark: '#1d4ed8',
59
+ text: '#ffffff',
60
+ },
61
+ worker: {
62
+ primary: '#9333ea',
63
+ light: '#faf5ff',
64
+ dark: '#7e22ce',
65
+ text: '#ffffff',
66
+ },
67
+ monitor: {
68
+ primary: '#0891b2',
69
+ light: '#ecfeff',
70
+ dark: '#0e7490',
71
+ text: '#ffffff',
72
+ },
73
+ security: {
74
+ primary: '#be123c',
75
+ light: '#fff1f2',
76
+ dark: '#9f1239',
77
+ text: '#ffffff',
78
+ },
79
+ };
80
+
81
+ // Fallback colors for unknown prefixes (generated from name hash)
82
+ const FALLBACK_COLORS: ColorScheme[] = [
83
+ { primary: '#6366f1', light: '#eef2ff', dark: '#4f46e5', text: '#ffffff' },
84
+ { primary: '#ec4899', light: '#fdf2f8', dark: '#db2777', text: '#ffffff' },
85
+ { primary: '#14b8a6', light: '#f0fdfa', dark: '#0d9488', text: '#ffffff' },
86
+ { primary: '#f59e0b', light: '#fffbeb', dark: '#d97706', text: '#000000' },
87
+ { primary: '#8b5cf6', light: '#f5f3ff', dark: '#7c3aed', text: '#ffffff' },
88
+ { primary: '#06b6d4', light: '#ecfeff', dark: '#0891b2', text: '#ffffff' },
89
+ { primary: '#f43f5e', light: '#fff1f2', dark: '#e11d48', text: '#ffffff' },
90
+ { primary: '#84cc16', light: '#f7fee7', dark: '#65a30d', text: '#000000' },
91
+ ];
92
+
93
+ // Status colors
94
+ export const STATUS_COLORS = {
95
+ online: '#22c55e', // Green
96
+ offline: '#6b7280', // Gray
97
+ busy: '#eab308', // Yellow
98
+ processing: '#6366f1', // Indigo (thinking/processing)
99
+ error: '#ef4444', // Red
100
+ attention: '#ef4444', // Red (for badge)
101
+ stuck: '#f97316', // Orange (received message but no response)
102
+ } as const;
103
+
104
+ export type AgentStatus = keyof typeof STATUS_COLORS;
105
+
106
+ /**
107
+ * Parse agent name into hierarchy levels
108
+ * e.g., "backend-api-auth" => ["backend", "api", "auth"]
109
+ */
110
+ export function parseAgentHierarchy(name: string): string[] {
111
+ return name.toLowerCase().split('-').filter(Boolean);
112
+ }
113
+
114
+ /**
115
+ * Get the top-level prefix from an agent name
116
+ * e.g., "backend-api-auth" => "backend"
117
+ */
118
+ export function getAgentPrefix(name: string): string {
119
+ const parts = parseAgentHierarchy(name);
120
+ return parts[0] || name.toLowerCase();
121
+ }
122
+
123
+ /**
124
+ * Generate a hash code from a string for consistent color assignment
125
+ */
126
+ function hashCode(str: string): number {
127
+ let hash = 0;
128
+ for (let i = 0; i < str.length; i++) {
129
+ const char = str.charCodeAt(i);
130
+ hash = ((hash << 5) - hash) + char;
131
+ hash = hash & hash; // Convert to 32bit integer
132
+ }
133
+ return Math.abs(hash);
134
+ }
135
+
136
+ /**
137
+ * Get color scheme for an agent based on its name
138
+ * Uses prefix matching first, falls back to hash-based color
139
+ */
140
+ export function getAgentColor(name: string): ColorScheme {
141
+ const prefix = getAgentPrefix(name);
142
+
143
+ // Check for predefined prefix color
144
+ if (prefix in PREFIX_COLORS) {
145
+ return PREFIX_COLORS[prefix];
146
+ }
147
+
148
+ // Fall back to hash-based color for consistency
149
+ const hash = hashCode(prefix);
150
+ return FALLBACK_COLORS[hash % FALLBACK_COLORS.length];
151
+ }
152
+
153
+ /**
154
+ * Get CSS custom properties for an agent's color scheme
155
+ * Useful for inline styles or CSS variables
156
+ */
157
+ export function getAgentColorVars(name: string): Record<string, string> {
158
+ const colors = getAgentColor(name);
159
+ return {
160
+ '--agent-primary': colors.primary,
161
+ '--agent-light': colors.light,
162
+ '--agent-dark': colors.dark,
163
+ '--agent-text': colors.text,
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Get initials from agent name (first 2 chars of first segment)
169
+ */
170
+ export function getAgentInitials(name: string): string {
171
+ const parts = parseAgentHierarchy(name);
172
+ if (parts.length === 0) return name.substring(0, 2).toUpperCase();
173
+
174
+ // Use first letter of first two segments, or first two letters of first segment
175
+ if (parts.length >= 2) {
176
+ return (parts[0][0] + parts[1][0]).toUpperCase();
177
+ }
178
+ return parts[0].substring(0, 2).toUpperCase();
179
+ }
180
+
181
+ /**
182
+ * Group agents by their top-level prefix
183
+ */
184
+ export function groupAgentsByPrefix<T extends { name: string }>(
185
+ agents: T[]
186
+ ): Map<string, T[]> {
187
+ const groups = new Map<string, T[]>();
188
+
189
+ for (const agent of agents) {
190
+ const prefix = getAgentPrefix(agent.name);
191
+ const group = groups.get(prefix) || [];
192
+ group.push(agent);
193
+ groups.set(prefix, group);
194
+ }
195
+
196
+ return groups;
197
+ }
198
+
199
+ /**
200
+ * Sort agents by hierarchy for display
201
+ * Groups by prefix, then sorts alphabetically within groups
202
+ */
203
+ export function sortAgentsByHierarchy<T extends { name: string }>(
204
+ agents: T[]
205
+ ): T[] {
206
+ return [...agents].sort((a, b) => {
207
+ const prefixA = getAgentPrefix(a.name);
208
+ const prefixB = getAgentPrefix(b.name);
209
+
210
+ // Sort by prefix first
211
+ if (prefixA !== prefixB) {
212
+ return prefixA.localeCompare(prefixB);
213
+ }
214
+
215
+ // Then by full name
216
+ return a.name.localeCompare(b.name);
217
+ });
218
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Dashboard Configuration
3
+ *
4
+ * Centralized configuration for API and WebSocket URLs.
5
+ * Works out-of-the-box with sensible defaults - no configuration required.
6
+ *
7
+ * Defaults (no env vars needed):
8
+ * - Development: WebSocket connects to localhost:3889 (dashboard server)
9
+ * - Production: WebSocket auto-detects from page URL (same host/port)
10
+ * - API: Uses relative URLs (works for same-origin requests)
11
+ *
12
+ * Optional overrides (for advanced deployments):
13
+ * - NEXT_PUBLIC_WS_URL: Override WebSocket URL entirely
14
+ * - NEXT_PUBLIC_API_URL: Override API base URL
15
+ * - NEXT_PUBLIC_DEV_SERVER_PORT: Change dev server port (default: 3889)
16
+ */
17
+
18
+ // Default port for dashboard server in development
19
+ const DEFAULT_DEV_SERVER_PORT = '3889';
20
+
21
+ /**
22
+ * Get the configured dev server port
23
+ */
24
+ function getDevServerPort(): string {
25
+ return process.env.NEXT_PUBLIC_DEV_SERVER_PORT || DEFAULT_DEV_SERVER_PORT;
26
+ }
27
+
28
+ /**
29
+ * Check if we're in development mode
30
+ */
31
+ function isDevelopment(): boolean {
32
+ return process.env.NODE_ENV === 'development';
33
+ }
34
+
35
+ /**
36
+ * Check if we're running in the browser
37
+ */
38
+ function isBrowser(): boolean {
39
+ return typeof window !== 'undefined';
40
+ }
41
+
42
+ /**
43
+ * Get the WebSocket URL for the main dashboard connection
44
+ *
45
+ * Priority:
46
+ * 1. NEXT_PUBLIC_WS_URL environment variable
47
+ * 2. Development mode: connect to dev server port
48
+ * 3. Production: auto-detect from page URL
49
+ */
50
+ export function getWebSocketUrl(path: string = '/ws'): string {
51
+ // Check for explicit environment variable
52
+ const envWsUrl = process.env.NEXT_PUBLIC_WS_URL;
53
+ if (envWsUrl) {
54
+ // Append path if base URL provided
55
+ return envWsUrl.endsWith('/') ? `${envWsUrl.slice(0, -1)}${path}` : `${envWsUrl}${path}`;
56
+ }
57
+
58
+ // SSR fallback - return localhost with dev port
59
+ if (!isBrowser()) {
60
+ const port = getDevServerPort();
61
+ return `ws://localhost:${port}${path}`;
62
+ }
63
+
64
+ const host = window.location.hostname || 'localhost';
65
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
66
+
67
+ // Development mode: Next.js on 3888, dashboard server on dev port (3889)
68
+ // Next.js rewrites don't support WebSocket upgrade, so connect directly
69
+ if (isDevelopment() && window.location.port === '3888') {
70
+ const devPort = getDevServerPort();
71
+ return `ws://${host}:${devPort}${path}`;
72
+ }
73
+
74
+ // Production: use same host/port as the page
75
+ return `${protocol}//${window.location.host}${path}`;
76
+ }
77
+
78
+ /**
79
+ * Get the API base URL
80
+ *
81
+ * Priority:
82
+ * 1. NEXT_PUBLIC_API_URL environment variable
83
+ * 2. Empty string (relative URLs) - works for same-origin
84
+ */
85
+ export function getApiBaseUrl(): string {
86
+ return process.env.NEXT_PUBLIC_API_URL || '';
87
+ }
88
+
89
+ /**
90
+ * Configuration object for easy access
91
+ */
92
+ export const config = {
93
+ /** Get WebSocket URL for a given path */
94
+ getWebSocketUrl,
95
+
96
+ /** Get API base URL */
97
+ getApiBaseUrl,
98
+
99
+ /** Check if in development mode */
100
+ isDevelopment,
101
+
102
+ /** Check if in browser */
103
+ isBrowser,
104
+
105
+ /** Dev server port */
106
+ devServerPort: getDevServerPort(),
107
+ };
108
+
109
+ export default config;