@agent-relay/dashboard 2.0.82 → 2.0.84

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 (228) hide show
  1. package/out/404.html +1 -1
  2. package/out/_next/static/chunks/1028-da5d75e35d1420f1.js +1 -0
  3. package/out/_next/static/chunks/1528-78b17000a7e10bc6.js +2 -0
  4. package/out/_next/static/chunks/1695-4a5d33ba715e09b4.js +1 -0
  5. package/out/_next/static/chunks/1705-36c2180d00a4a569.js +1 -0
  6. package/out/_next/static/chunks/1dd3208c-e1f87c7b3dc1a820.js +1 -0
  7. package/out/_next/static/chunks/3663-47290254b8f6f5dd.js +1 -0
  8. package/out/_next/static/chunks/3677-4b225baf4801d9b9.js +73 -0
  9. package/out/_next/static/chunks/5118-7e8ada2df38eef07.js +1 -0
  10. package/out/_next/static/chunks/5888-15cbe97c90ed5fae.js +1 -0
  11. package/out/_next/static/chunks/6773-a45343a98df3abb5.js +1 -0
  12. package/out/_next/static/chunks/6940-b824612b605e79b3.js +9 -0
  13. package/out/_next/static/chunks/7894-f4a15249082a680d.js +1 -0
  14. package/out/_next/static/chunks/9175-b3617c1e5cbfed0e.js +1 -0
  15. package/out/_next/static/chunks/9372-1a804b8d08c7a236.js +1 -0
  16. package/out/_next/static/chunks/{ab6c8a12-0a58072fbb505134.js → ab6c8a12-91438a812d94ecf0.js} +1 -1
  17. package/out/_next/static/chunks/app/_not-found/page-8e8842f82d204726.js +1 -0
  18. package/out/_next/static/chunks/app/about/page-b78577a7da8fa459.js +1 -0
  19. package/out/_next/static/chunks/app/app/[[...slug]]/page-3dffd65b6344f53e.js +1 -0
  20. package/out/_next/static/chunks/app/app/onboarding/page-b89be9aa6264a5e1.js +1 -0
  21. package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-fbd00893ef69e499.js +1 -0
  22. package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-de2ea13649d0b6d3.js +1 -0
  23. package/out/_next/static/chunks/app/blog/page-a08e263c57a156fa.js +1 -0
  24. package/out/_next/static/chunks/app/careers/page-02228e1d6969b232.js +1 -0
  25. package/out/_next/static/chunks/app/changelog/page-1b5c1d79efc6e53a.js +1 -0
  26. package/out/_next/static/chunks/app/cloud/link/page-99654edffffb3af2.js +1 -0
  27. package/out/_next/static/chunks/app/complete-profile/page-59d146e5ddeafc5c.js +1 -0
  28. package/out/_next/static/chunks/app/connect-repos/page-995e16a976a6632c.js +1 -0
  29. package/out/_next/static/chunks/app/contact/page-273396a5ad57bcee.js +1 -0
  30. package/out/_next/static/chunks/app/dev/cli-tools/page-a71b80dcb2d5fc8d.js +1 -0
  31. package/out/_next/static/chunks/app/dev/log-viewer/page-46a6151ae1be0796.js +1 -0
  32. package/out/_next/static/chunks/app/docs/page-7c7cb603b24b7c40.js +1 -0
  33. package/out/_next/static/chunks/app/history/page-0c5cab1dab4e8886.js +1 -0
  34. package/out/_next/static/chunks/app/layout-96d72ba8ef8a43a0.js +1 -0
  35. package/out/_next/static/chunks/app/login/page-0ccbab34213df842.js +1 -0
  36. package/out/_next/static/chunks/app/metrics/page-8616272aeab9c8b0.js +1 -0
  37. package/out/_next/static/chunks/app/page-09ce10603ad9a251.js +1 -0
  38. package/out/_next/static/chunks/app/pricing/page-91c975079120c941.js +1 -0
  39. package/out/_next/static/chunks/app/privacy/{page-c21d51ac2dee3a88.js → page-a49ab271cc686644.js} +1 -1
  40. package/out/_next/static/chunks/app/providers/{page-59114505f4353512.js → page-d775d6eb5bc29e96.js} +1 -1
  41. package/out/_next/static/chunks/app/providers/setup/[provider]/page-ec4ef3cd80de807e.js +1 -0
  42. package/out/_next/static/chunks/app/security/page-d9da9bd9191e8f95.js +1 -0
  43. package/out/_next/static/chunks/app/signup/page-930eca0bf5fd299d.js +1 -0
  44. package/out/_next/static/chunks/app/terms/page-3e4827620b98613c.js +1 -0
  45. package/out/_next/static/chunks/framework-648e1ae7da590300.js +1 -0
  46. package/out/_next/static/chunks/{main-acb1b24265295d6a.js → main-2b1990080c292d92.js} +1 -1
  47. package/out/_next/static/chunks/main-app-9f6b7ff9e754a8f5.js +1 -0
  48. package/out/_next/static/chunks/pages/_app-a077b72e02273ab1.js +1 -0
  49. package/out/_next/static/chunks/pages/_error-84001666436a04e4.js +1 -0
  50. package/out/_next/static/chunks/{webpack-dd93b81e2659669c.js → webpack-7586035f1585f2db.js} +1 -1
  51. package/out/_next/static/css/eb9fc69d1e3d2bed.css +1 -0
  52. package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_buildManifest.js +1 -1
  53. package/out/about.html +2 -2
  54. package/out/about.txt +2 -2
  55. package/out/app/onboarding.html +1 -1
  56. package/out/app/onboarding.txt +2 -2
  57. package/out/app.html +1 -1
  58. package/out/app.txt +2 -2
  59. package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +3 -3
  60. package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
  61. package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
  62. package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
  63. package/out/blog.html +2 -2
  64. package/out/blog.txt +1 -1
  65. package/out/careers.html +2 -2
  66. package/out/careers.txt +2 -2
  67. package/out/changelog.html +2 -2
  68. package/out/changelog.txt +2 -2
  69. package/out/cloud/link.html +1 -1
  70. package/out/cloud/link.txt +2 -2
  71. package/out/complete-profile.html +2 -2
  72. package/out/complete-profile.txt +2 -2
  73. package/out/connect-repos.html +1 -1
  74. package/out/connect-repos.txt +2 -2
  75. package/out/contact.html +2 -2
  76. package/out/contact.txt +2 -2
  77. package/out/dev/cli-tools.html +1 -0
  78. package/out/dev/cli-tools.txt +7 -0
  79. package/out/dev/log-viewer.html +23 -0
  80. package/out/dev/log-viewer.txt +7 -0
  81. package/out/docs.html +2 -2
  82. package/out/docs.txt +2 -2
  83. package/out/history.html +1 -1
  84. package/out/history.txt +2 -2
  85. package/out/index.html +1 -1
  86. package/out/index.txt +2 -2
  87. package/out/login.html +2 -2
  88. package/out/login.txt +2 -2
  89. package/out/metrics.html +1 -1
  90. package/out/metrics.txt +2 -2
  91. package/out/pricing.html +2 -2
  92. package/out/pricing.txt +2 -2
  93. package/out/privacy.html +2 -2
  94. package/out/privacy.txt +2 -2
  95. package/out/providers/setup/claude.html +1 -1
  96. package/out/providers/setup/claude.txt +2 -2
  97. package/out/providers/setup/codex.html +1 -1
  98. package/out/providers/setup/codex.txt +2 -2
  99. package/out/providers/setup/cursor.html +1 -1
  100. package/out/providers/setup/cursor.txt +2 -2
  101. package/out/providers.html +1 -1
  102. package/out/providers.txt +2 -2
  103. package/out/security.html +2 -2
  104. package/out/security.txt +2 -2
  105. package/out/signup.html +2 -2
  106. package/out/signup.txt +2 -2
  107. package/out/terms.html +2 -2
  108. package/out/terms.txt +2 -2
  109. package/package.json +5 -1
  110. package/src/adapters/DashboardConfigProvider.tsx +56 -0
  111. package/src/adapters/cloudFetchAdapter.ts +278 -0
  112. package/src/adapters/index.ts +3 -0
  113. package/src/adapters/types.ts +508 -0
  114. package/src/app/app/[[...slug]]/DashboardPageClient.tsx +67 -18
  115. package/src/app/app/onboarding/page.tsx +870 -170
  116. package/src/app/cloud/link/page.tsx +14 -6
  117. package/src/app/connect-repos/page.tsx +9 -3
  118. package/src/app/dev/cli-tools/page.tsx +130 -0
  119. package/src/app/dev/log-viewer/MockLogViewer.tsx +132 -0
  120. package/src/app/dev/log-viewer/fixtures.ts +110 -0
  121. package/src/app/dev/log-viewer/page.tsx +288 -0
  122. package/src/app/history/page.tsx +28 -12
  123. package/src/app/page.tsx +1 -1
  124. package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +209 -59
  125. package/src/components/AgentCard.tsx +4 -4
  126. package/src/components/AgentLogPreview.tsx +2 -38
  127. package/src/components/App.tsx +441 -2624
  128. package/src/components/CliToolHarness.test.tsx +83 -0
  129. package/src/components/CliToolHarness.tsx +292 -0
  130. package/src/components/CoordinatorPanel.tsx +13 -6
  131. package/src/components/LogViewer.tsx +2 -42
  132. package/src/components/ProviderAuthFlow.tsx +201 -81
  133. package/src/components/ProvisioningProgress.tsx +1 -1
  134. package/src/components/ReactionChips.tsx +2 -1
  135. package/src/components/SpawnModal.test.tsx +51 -18
  136. package/src/components/SpawnModal.tsx +175 -207
  137. package/src/components/TerminalProviderSetup.tsx +1 -1
  138. package/src/components/ThreadPanel.tsx +2 -0
  139. package/src/components/WorkspaceContext.tsx +7 -19
  140. package/src/components/XTermLogViewer.tsx +190 -27
  141. package/src/components/channels/ChannelMessageList.tsx +94 -4
  142. package/src/components/channels/ChannelViewV1.tsx +35 -11
  143. package/src/components/channels/api.ts +21 -20
  144. package/src/components/channels/types.ts +16 -0
  145. package/src/components/hooks/index.ts +0 -19
  146. package/src/components/hooks/useMessages.test.ts +80 -0
  147. package/src/components/hooks/useMessages.ts +13 -4
  148. package/src/components/hooks/useOrchestrator.ts +1 -1
  149. package/src/components/hooks/usePresence.ts +45 -6
  150. package/src/components/hooks/useThread.ts +83 -46
  151. package/src/components/hooks/useTrajectory.ts +62 -5
  152. package/src/components/hooks/useWebSocket.test.ts +358 -0
  153. package/src/components/hooks/useWebSocket.ts +243 -5
  154. package/src/components/index.ts +2 -14
  155. package/src/components/layout/Header.tsx +9 -15
  156. package/src/components/layout/Sidebar.tsx +1 -8
  157. package/src/components/settings/SettingsPage.tsx +108 -47
  158. package/src/components/settings/index.ts +0 -3
  159. package/src/landing/blogData.ts +1 -1
  160. package/src/lib/agent-merge.test.ts +2 -2
  161. package/src/lib/api.ts +8 -38
  162. package/src/lib/identity.test.ts +139 -0
  163. package/src/lib/identity.ts +48 -0
  164. package/src/lib/relaycastMessageAdapters.test.ts +182 -0
  165. package/src/lib/relaycastMessageAdapters.ts +105 -0
  166. package/src/lib/sanitize-logs.test.ts +227 -0
  167. package/src/lib/sanitize-logs.ts +202 -0
  168. package/src/providers/AgentProvider.tsx +799 -0
  169. package/src/providers/ChannelProvider.tsx +528 -0
  170. package/src/providers/CloudWorkspaceProvider.tsx +402 -0
  171. package/src/providers/MessageProvider.tsx +875 -0
  172. package/src/providers/RelayConfigProvider.tsx +94 -0
  173. package/src/providers/SendProvider.tsx +497 -0
  174. package/src/providers/SettingsProvider.tsx +247 -0
  175. package/src/providers/index.ts +26 -0
  176. package/src/types/index.ts +10 -10
  177. package/out/_next/static/chunks/11-9a2993a37266dcb3.js +0 -9
  178. package/out/_next/static/chunks/118-ae2b650136a5a5fc.js +0 -1
  179. package/out/_next/static/chunks/1dd3208c-40ab0fc0f60392b8.js +0 -1
  180. package/out/_next/static/chunks/202-fc0763dd7488e58f.js +0 -1
  181. package/out/_next/static/chunks/259-83b77fa1b91ba5aa.js +0 -1
  182. package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +0 -1
  183. package/out/_next/static/chunks/528-f5f676996d613c25.js +0 -2
  184. package/out/_next/static/chunks/663-ddb04081febc3678.js +0 -1
  185. package/out/_next/static/chunks/687-88b6b139a6bb0e2e.js +0 -1
  186. package/out/_next/static/chunks/695-51d25b1988644374.js +0 -1
  187. package/out/_next/static/chunks/773-54a2641043c81e55.js +0 -1
  188. package/out/_next/static/chunks/app/_not-found/page-6da9b72091e5b511.js +0 -1
  189. package/out/_next/static/chunks/app/about/page-fff7c6457683f243.js +0 -1
  190. package/out/_next/static/chunks/app/app/[[...slug]]/page-f7eca1b66fb4249b.js +0 -1
  191. package/out/_next/static/chunks/app/app/onboarding/page-129abc5da2e67971.js +0 -1
  192. package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-5d5f28fd126b692f.js +0 -1
  193. package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-b194f207fbd91862.js +0 -1
  194. package/out/_next/static/chunks/app/blog/page-b9bd9d8703fca76a.js +0 -1
  195. package/out/_next/static/chunks/app/careers/page-a4bd8d5f4de8f4eb.js +0 -1
  196. package/out/_next/static/chunks/app/changelog/page-9a1f6ad1743d63c5.js +0 -1
  197. package/out/_next/static/chunks/app/cloud/link/page-0844c5699b027c3b.js +0 -1
  198. package/out/_next/static/chunks/app/complete-profile/page-39ed5a67916beb87.js +0 -1
  199. package/out/_next/static/chunks/app/connect-repos/page-297eddee0c39f2a3.js +0 -1
  200. package/out/_next/static/chunks/app/contact/page-3c1dd8690217fade.js +0 -1
  201. package/out/_next/static/chunks/app/docs/page-1875e981f2c3fd13.js +0 -1
  202. package/out/_next/static/chunks/app/history/page-2d5c5695c9e8b40c.js +0 -1
  203. package/out/_next/static/chunks/app/layout-0a4b99656da25511.js +0 -1
  204. package/out/_next/static/chunks/app/login/page-f69c076f5a6fc520.js +0 -1
  205. package/out/_next/static/chunks/app/metrics/page-bebbee055669a17e.js +0 -1
  206. package/out/_next/static/chunks/app/page-0ee604f7070d14c0.js +0 -1
  207. package/out/_next/static/chunks/app/pricing/page-eeae7d594af333b6.js +0 -1
  208. package/out/_next/static/chunks/app/providers/setup/[provider]/page-daf9b3e05e77ae19.js +0 -1
  209. package/out/_next/static/chunks/app/security/page-cd562730fe84a0a2.js +0 -1
  210. package/out/_next/static/chunks/app/signup/page-c242ca08101a84ff.js +0 -1
  211. package/out/_next/static/chunks/app/terms/page-c7001720e7941dc6.js +0 -1
  212. package/out/_next/static/chunks/framework-3664cab31236a9fa.js +0 -1
  213. package/out/_next/static/chunks/main-app-7f73a939a312a228.js +0 -1
  214. package/out/_next/static/chunks/pages/_app-10a93ab5b7c32eb3.js +0 -1
  215. package/out/_next/static/chunks/pages/_error-2d792b2a41857be4.js +0 -1
  216. package/out/_next/static/css/8968d98ed4c4d33f.css +0 -1
  217. package/src/components/BillingResult.tsx +0 -447
  218. package/src/components/CloudSessionProvider.tsx +0 -130
  219. package/src/components/SessionExpiredModal.tsx +0 -128
  220. package/src/components/WorkspaceStatusIndicator.tsx +0 -396
  221. package/src/components/hooks/useSession.ts +0 -209
  222. package/src/components/hooks/useWorkspaceMembers.ts +0 -132
  223. package/src/components/hooks/useWorkspaceStatus.ts +0 -237
  224. package/src/components/settings/BillingSettingsPanel.tsx +0 -564
  225. package/src/components/settings/TeamSettingsPanel.tsx +0 -560
  226. package/src/components/settings/WorkspaceSettingsPanel.tsx +0 -1368
  227. package/src/lib/cloudApi.ts +0 -893
  228. /package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_ssgManifest.js +0 -0
@@ -0,0 +1,402 @@
1
+ /**
2
+ * Cloud Workspace Provider
3
+ *
4
+ * Manages workspace list, selection, cloud user state, and effective workspace ID.
5
+ * Handles both cloud mode (API-backed workspaces) and local orchestrator mode.
6
+ */
7
+
8
+ import React, { createContext, useContext, useState, useCallback, useEffect, useMemo } from 'react';
9
+ import { useDashboardConfig, type CloudUser } from '../adapters';
10
+ import type { Agent } from '../types';
11
+ import { api, setActiveWorkspaceId as setApiWorkspaceId, getActiveWorkspaceId } from '../lib/api';
12
+ import { useOrchestrator } from '../components/hooks/useOrchestrator';
13
+ import type { Workspace } from '../components/WorkspaceSelector';
14
+ import type { CurrentUser } from '../components/MessageList';
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Types
18
+ // ---------------------------------------------------------------------------
19
+
20
+ export interface CloudWorkspace {
21
+ id: string;
22
+ name: string;
23
+ status: string;
24
+ publicUrl?: string;
25
+ accessType?: 'owner' | 'member' | 'contributor';
26
+ permission?: 'admin' | 'write' | 'read';
27
+ }
28
+
29
+ interface CloudWorkspaceContextValue {
30
+ /** Cloud user (null when not authenticated or in local mode) */
31
+ cloudUser: CloudUser | null;
32
+ /** Current user display info for messages */
33
+ currentUser: CurrentUser | undefined;
34
+ /** Whether workspace features are enabled in dashboard config */
35
+ isWorkspaceFeaturesEnabled: boolean;
36
+ /** Whether header settings link should show */
37
+ canOpenHeaderSettings: boolean;
38
+ /** Whether workspace settings link should show */
39
+ canOpenWorkspaceSettings: boolean;
40
+
41
+ // Workspace state
42
+ cloudWorkspaces: CloudWorkspace[];
43
+ effectiveWorkspaces: Workspace[];
44
+ effectiveActiveWorkspaceId: string | null;
45
+ effectiveIsLoading: boolean;
46
+ activeCloudWorkspaceId: string | null;
47
+
48
+ // Orchestrator pass-through (for local mode)
49
+ orchestratorWorkspaces: Workspace[];
50
+ orchestratorAgents: ReturnType<typeof useOrchestrator>['agents'];
51
+ activeWorkspaceId: string | undefined;
52
+ isOrchestratorConnected: boolean;
53
+ orchestratorSpawnAgent: ReturnType<typeof useOrchestrator>['spawnAgent'];
54
+ orchestratorStopAgent: ReturnType<typeof useOrchestrator>['stopAgent'];
55
+
56
+ // Actions
57
+ handleEffectiveWorkspaceSelect: (workspace: { id: string; name: string }) => Promise<void>;
58
+ switchWorkspace: (workspaceId: string) => Promise<void>;
59
+ addWorkspace: (path: string, name?: string) => Promise<void>;
60
+ removeWorkspace: (workspaceId: string) => Promise<void>;
61
+ setCloudWorkspaces: React.Dispatch<React.SetStateAction<CloudWorkspace[]>>;
62
+ setActiveCloudWorkspaceId: React.Dispatch<React.SetStateAction<string | null>>;
63
+
64
+ // Local agents from linked brokers
65
+ localAgents: Agent[];
66
+
67
+ // Dashboard config features
68
+ features: ReturnType<typeof useDashboardConfig>['features'];
69
+ apiAdapter: ReturnType<typeof useDashboardConfig>['api'];
70
+ authAdapter: ReturnType<typeof useDashboardConfig>['auth'];
71
+ }
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // Context
75
+ // ---------------------------------------------------------------------------
76
+
77
+ const CloudWorkspaceContext = createContext<CloudWorkspaceContextValue | null>(null);
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Provider
81
+ // ---------------------------------------------------------------------------
82
+
83
+ export interface CloudWorkspaceProviderProps {
84
+ children: React.ReactNode;
85
+ orchestratorUrl?: string;
86
+ }
87
+
88
+ export function CloudWorkspaceProvider({ children, orchestratorUrl }: CloudWorkspaceProviderProps) {
89
+ const config = useDashboardConfig();
90
+ const { features, api: apiAdapter, auth: authAdapter } = config;
91
+
92
+ // Orchestrator for multi-workspace management
93
+ const {
94
+ workspaces: orchestratorWorkspaces,
95
+ activeWorkspaceId,
96
+ agents: orchestratorAgents,
97
+ isConnected: isOrchestratorConnected,
98
+ isLoading: isOrchestratorLoading,
99
+ switchWorkspace,
100
+ addWorkspace: orchestratorAddWorkspace,
101
+ removeWorkspace,
102
+ spawnAgent: orchestratorSpawnAgent,
103
+ stopAgent: orchestratorStopAgent,
104
+ } = useOrchestrator({ apiUrl: orchestratorUrl });
105
+
106
+ // Cloud user state
107
+ const [cloudUser, setCloudUser] = useState<CloudUser | null>(null);
108
+
109
+ useEffect(() => {
110
+ if (!features.auth || !authAdapter) {
111
+ setCloudUser(null);
112
+ return;
113
+ }
114
+
115
+ let cancelled = false;
116
+
117
+ const fetchCurrentUser = async () => {
118
+ try {
119
+ const result = await authAdapter.getUser();
120
+ if (cancelled) return;
121
+ setCloudUser(result.success ? result.data : null);
122
+ } catch {
123
+ if (!cancelled) {
124
+ setCloudUser(null);
125
+ }
126
+ }
127
+ };
128
+
129
+ void fetchCurrentUser();
130
+ const interval = setInterval(() => {
131
+ void fetchCurrentUser();
132
+ }, 30000);
133
+
134
+ return () => {
135
+ cancelled = true;
136
+ clearInterval(interval);
137
+ };
138
+ }, [features.auth, authAdapter]);
139
+
140
+ // Derive current user from auth adapter
141
+ const currentUser: CurrentUser | undefined = cloudUser
142
+ ? {
143
+ displayName: cloudUser.githubUsername || cloudUser.displayName || '',
144
+ avatarUrl: cloudUser.avatarUrl,
145
+ }
146
+ : undefined;
147
+
148
+ const hasWorkspaceApi = features.workspaces && !!apiAdapter;
149
+ const isWorkspaceFeaturesEnabled = features.workspaces;
150
+ const canOpenWorkspaceSettings = features.workspaces;
151
+ const canOpenHeaderSettings =
152
+ features.auth || features.billing || features.teams || features.workspaces;
153
+
154
+ // Cloud workspaces state
155
+ const [cloudWorkspaces, setCloudWorkspaces] = useState<CloudWorkspace[]>([]);
156
+ const [activeCloudWorkspaceId, setActiveCloudWorkspaceId] = useState<string | null>(() => getActiveWorkspaceId());
157
+ const [isLoadingCloudWorkspaces, setIsLoadingCloudWorkspaces] = useState(false);
158
+
159
+ // Local agents from linked brokers
160
+ const [localAgents, setLocalAgents] = useState<Agent[]>([]);
161
+
162
+ // Fetch cloud workspaces when in cloud mode
163
+ useEffect(() => {
164
+ if (!hasWorkspaceApi || !apiAdapter) {
165
+ setCloudWorkspaces([]);
166
+ setIsLoadingCloudWorkspaces(false);
167
+ return;
168
+ }
169
+
170
+ let cancelled = false;
171
+
172
+ const fetchCloudWorkspaces = async (isInitialLoad: boolean) => {
173
+ if (isInitialLoad) {
174
+ setIsLoadingCloudWorkspaces(true);
175
+ }
176
+ try {
177
+ const result = await apiAdapter.getAccessibleWorkspaces();
178
+ if (cancelled) return;
179
+ if (result.success && result.data.workspaces) {
180
+ setCloudWorkspaces(result.data.workspaces);
181
+ const workspaceIds = new Set(result.data.workspaces.map((w: CloudWorkspace) => w.id));
182
+ if (activeCloudWorkspaceId && !workspaceIds.has(activeCloudWorkspaceId)) {
183
+ if (result.data.workspaces.length > 0) {
184
+ const firstWorkspaceId = result.data.workspaces[0].id;
185
+ setActiveCloudWorkspaceId(firstWorkspaceId);
186
+ setApiWorkspaceId(firstWorkspaceId);
187
+ } else {
188
+ setActiveCloudWorkspaceId(null);
189
+ setApiWorkspaceId(null);
190
+ }
191
+ } else if (!activeCloudWorkspaceId && result.data.workspaces.length > 0) {
192
+ const firstWorkspaceId = result.data.workspaces[0].id;
193
+ setActiveCloudWorkspaceId(firstWorkspaceId);
194
+ setApiWorkspaceId(firstWorkspaceId);
195
+ }
196
+ }
197
+ } catch (err) {
198
+ if (cancelled) return;
199
+ console.error('Failed to fetch cloud workspaces:', err);
200
+ } finally {
201
+ if (isInitialLoad && !cancelled) {
202
+ setIsLoadingCloudWorkspaces(false);
203
+ }
204
+ }
205
+ };
206
+
207
+ void fetchCloudWorkspaces(true);
208
+ const interval = setInterval(() => {
209
+ void fetchCloudWorkspaces(false);
210
+ }, 30000);
211
+ return () => {
212
+ cancelled = true;
213
+ clearInterval(interval);
214
+ };
215
+ }, [hasWorkspaceApi, apiAdapter, activeCloudWorkspaceId]);
216
+
217
+ // Fetch agents for the active workspace
218
+ useEffect(() => {
219
+ if (!activeCloudWorkspaceId) {
220
+ setLocalAgents([]);
221
+ return;
222
+ }
223
+
224
+ const useCloudAgents = hasWorkspaceApi && !!apiAdapter;
225
+
226
+ const fetchAgents = async () => {
227
+ try {
228
+ if (useCloudAgents && apiAdapter) {
229
+ const result = await apiAdapter.getAgents(activeCloudWorkspaceId);
230
+ if (result.success && result.data?.agents) {
231
+ const agents: Agent[] = result.data.agents.map((a: { name: string; status: string }) => ({
232
+ name: a.name,
233
+ status: a.status === 'online' ? 'online' : 'offline',
234
+ isLocal: false,
235
+ isSpawned: true,
236
+ }));
237
+ setLocalAgents(agents);
238
+ }
239
+ } else {
240
+ const result = await api.get<{
241
+ agents: Array<{
242
+ name: string;
243
+ status: string;
244
+ isLocal: boolean;
245
+ isHuman?: boolean;
246
+ avatarUrl?: string;
247
+ brokerId?: string;
248
+ brokerName?: string;
249
+ brokerStatus?: string;
250
+ machineId: string;
251
+ lastSeenAt: string | null;
252
+ }>;
253
+ }>(`/api/brokers/workspace/${activeCloudWorkspaceId}/agents`);
254
+
255
+ if (result.agents) {
256
+ const agents: Agent[] = result.agents.map((a) => {
257
+ const brokerStatus = (a as { brokerStatus?: string }).brokerStatus;
258
+ const resolvedStatus = brokerStatus ?? a.status;
259
+ const brokerName = (a as { brokerName?: string }).brokerName;
260
+ return {
261
+ name: a.name,
262
+ status: resolvedStatus === 'online' ? 'online' : 'offline',
263
+ isLocal: !a.isHuman,
264
+ isHuman: a.isHuman,
265
+ avatarUrl: a.avatarUrl,
266
+ brokerName: a.isHuman ? undefined : brokerName,
267
+ machineId: a.isHuman ? undefined : a.machineId,
268
+ lastSeen: a.lastSeenAt || undefined,
269
+ };
270
+ });
271
+ setLocalAgents(agents);
272
+ }
273
+ }
274
+ } catch (err) {
275
+ const message = err instanceof Error ? err.message : String(err);
276
+ if (message.includes('HTTP 404')) {
277
+ console.error(
278
+ 'BREAKING CHANGE: daemon agent endpoints were removed. Dashboard now requires /api/brokers/workspace/:id/agents.',
279
+ err,
280
+ );
281
+ } else {
282
+ console.error('Failed to fetch agents:', err);
283
+ }
284
+ setLocalAgents([]);
285
+ }
286
+ };
287
+
288
+ void fetchAgents();
289
+ const interval = setInterval(fetchAgents, useCloudAgents ? 5000 : 15000);
290
+ return () => clearInterval(interval);
291
+ }, [hasWorkspaceApi, apiAdapter, activeCloudWorkspaceId]);
292
+
293
+ // Determine effective workspaces
294
+ const effectiveWorkspaces = useMemo(() => {
295
+ if (isWorkspaceFeaturesEnabled && cloudWorkspaces.length > 0) {
296
+ return cloudWorkspaces.map(ws => ({
297
+ id: ws.id,
298
+ name: ws.name,
299
+ path: ws.publicUrl || `/workspace/${ws.name}`,
300
+ status: ws.status === 'running' ? 'active' as const : 'inactive' as const,
301
+ provider: 'claude' as const,
302
+ lastActiveAt: new Date(),
303
+ }));
304
+ }
305
+ return orchestratorWorkspaces;
306
+ }, [isWorkspaceFeaturesEnabled, cloudWorkspaces, orchestratorWorkspaces]);
307
+
308
+ const effectiveActiveWorkspaceId = isWorkspaceFeaturesEnabled
309
+ ? activeCloudWorkspaceId
310
+ : (activeWorkspaceId ?? 'default');
311
+ const effectiveIsLoading = isWorkspaceFeaturesEnabled ? isLoadingCloudWorkspaces : isOrchestratorLoading;
312
+
313
+ // Sync the active workspace ID with the api module
314
+ useEffect(() => {
315
+ if (isWorkspaceFeaturesEnabled && activeCloudWorkspaceId) {
316
+ setApiWorkspaceId(activeCloudWorkspaceId);
317
+ } else if (isWorkspaceFeaturesEnabled && !activeCloudWorkspaceId) {
318
+ setApiWorkspaceId(null);
319
+ } else if (!isWorkspaceFeaturesEnabled) {
320
+ setApiWorkspaceId(null);
321
+ }
322
+ }, [isWorkspaceFeaturesEnabled, activeCloudWorkspaceId]);
323
+
324
+ // Handle workspace selection
325
+ const handleEffectiveWorkspaceSelect = useCallback(async (workspace: { id: string; name: string }) => {
326
+ if (isWorkspaceFeaturesEnabled) {
327
+ setActiveCloudWorkspaceId(workspace.id);
328
+ setApiWorkspaceId(workspace.id);
329
+ } else {
330
+ await switchWorkspace(workspace.id);
331
+ }
332
+ }, [isWorkspaceFeaturesEnabled, switchWorkspace]);
333
+
334
+ // Keep local username for channel API calls
335
+ useEffect(() => {
336
+ if (typeof window !== 'undefined') {
337
+ if (currentUser?.displayName) {
338
+ localStorage.setItem('relay_username', currentUser.displayName);
339
+ } else if (!isWorkspaceFeaturesEnabled) {
340
+ localStorage.removeItem('relay_username');
341
+ }
342
+ }
343
+ }, [currentUser?.displayName, isWorkspaceFeaturesEnabled]);
344
+
345
+ const handleAddWorkspace = useCallback(async (path: string, name?: string) => {
346
+ await orchestratorAddWorkspace(path, name);
347
+ }, [orchestratorAddWorkspace]);
348
+
349
+ const value = useMemo<CloudWorkspaceContextValue>(() => ({
350
+ cloudUser,
351
+ currentUser,
352
+ isWorkspaceFeaturesEnabled,
353
+ canOpenHeaderSettings,
354
+ canOpenWorkspaceSettings,
355
+ cloudWorkspaces,
356
+ effectiveWorkspaces,
357
+ effectiveActiveWorkspaceId,
358
+ effectiveIsLoading,
359
+ activeCloudWorkspaceId,
360
+ orchestratorWorkspaces,
361
+ orchestratorAgents,
362
+ activeWorkspaceId,
363
+ isOrchestratorConnected,
364
+ orchestratorSpawnAgent,
365
+ orchestratorStopAgent,
366
+ handleEffectiveWorkspaceSelect,
367
+ switchWorkspace,
368
+ addWorkspace: handleAddWorkspace,
369
+ removeWorkspace,
370
+ setCloudWorkspaces,
371
+ setActiveCloudWorkspaceId,
372
+ localAgents,
373
+ features,
374
+ apiAdapter,
375
+ authAdapter,
376
+ }), [
377
+ cloudUser, currentUser, isWorkspaceFeaturesEnabled, canOpenHeaderSettings,
378
+ canOpenWorkspaceSettings, cloudWorkspaces, effectiveWorkspaces, effectiveActiveWorkspaceId,
379
+ effectiveIsLoading, activeCloudWorkspaceId, orchestratorWorkspaces, orchestratorAgents,
380
+ activeWorkspaceId, isOrchestratorConnected, orchestratorSpawnAgent, orchestratorStopAgent,
381
+ handleEffectiveWorkspaceSelect, switchWorkspace, handleAddWorkspace, removeWorkspace,
382
+ localAgents, features, apiAdapter, authAdapter,
383
+ ]);
384
+
385
+ return (
386
+ <CloudWorkspaceContext.Provider value={value}>
387
+ {children}
388
+ </CloudWorkspaceContext.Provider>
389
+ );
390
+ }
391
+
392
+ // ---------------------------------------------------------------------------
393
+ // Hook
394
+ // ---------------------------------------------------------------------------
395
+
396
+ export function useCloudWorkspace(): CloudWorkspaceContextValue {
397
+ const ctx = useContext(CloudWorkspaceContext);
398
+ if (!ctx) {
399
+ throw new Error('useCloudWorkspace must be used within a CloudWorkspaceProvider');
400
+ }
401
+ return ctx;
402
+ }