@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
@@ -1,209 +0,0 @@
1
- /**
2
- * useSession Hook
3
- *
4
- * React hook for managing cloud session state.
5
- * Automatically detects session expiration and triggers re-login flow.
6
- */
7
-
8
- import { useState, useEffect, useCallback, useRef } from 'react';
9
- import {
10
- cloudApi,
11
- onSessionExpired,
12
- getCsrfToken,
13
- type CloudUser,
14
- type SessionError,
15
- type SessionStatus,
16
- } from '../../lib/cloudApi';
17
-
18
- export interface UseSessionOptions {
19
- /** Check session on mount (default: true) */
20
- checkOnMount?: boolean;
21
- /** Interval to periodically check session in ms (default: 60000, set to 0 to disable) */
22
- checkInterval?: number;
23
- /** Callback when session expires */
24
- onExpired?: (error: SessionError) => void;
25
- }
26
-
27
- export interface UseSessionReturn {
28
- /** Current user data (null if not authenticated) */
29
- user: CloudUser | null;
30
- /** Whether the session check is in progress */
31
- isLoading: boolean;
32
- /** Whether user is authenticated */
33
- isAuthenticated: boolean;
34
- /** Whether session has expired (requires re-login) */
35
- isExpired: boolean;
36
- /** Session error if any */
37
- error: SessionError | null;
38
- /** CSRF token for API requests */
39
- csrfToken: string | null;
40
- /** Manually check session status */
41
- checkSession: () => Promise<SessionStatus>;
42
- /** Clear the expired state (e.g., after dismissing modal) */
43
- clearExpired: () => void;
44
- /** Redirect to login page */
45
- redirectToLogin: () => void;
46
- /** Logout the current user */
47
- logout: () => Promise<void>;
48
- }
49
-
50
- const DEFAULT_OPTIONS: Required<UseSessionOptions> = {
51
- checkOnMount: true,
52
- checkInterval: 60000, // 1 minute
53
- onExpired: () => {},
54
- };
55
-
56
- export function useSession(options: UseSessionOptions = {}): UseSessionReturn {
57
- const opts = { ...DEFAULT_OPTIONS, ...options };
58
-
59
- const [user, setUser] = useState<CloudUser | null>(null);
60
- const [isLoading, setIsLoading] = useState(true);
61
- const [isExpired, setIsExpired] = useState(false);
62
- const [error, setError] = useState<SessionError | null>(null);
63
-
64
- const intervalRef = useRef<NodeJS.Timeout | null>(null);
65
- const mountedRef = useRef(true);
66
-
67
- // Check session status
68
- const checkSession = useCallback(async (): Promise<SessionStatus> => {
69
- try {
70
- const status = await cloudApi.checkSession();
71
-
72
- if (!mountedRef.current) return status;
73
-
74
- if (!status.authenticated) {
75
- setUser(null);
76
- if (status.code) {
77
- const sessionError: SessionError = {
78
- error: 'Session expired',
79
- code: status.code,
80
- message: status.message || 'Your session has expired. Please log in again.',
81
- };
82
- setError(sessionError);
83
- setIsExpired(true);
84
- opts.onExpired(sessionError);
85
- }
86
- }
87
-
88
- return status;
89
- } catch (_e) {
90
- return {
91
- authenticated: false,
92
- code: 'SESSION_ERROR',
93
- message: 'Failed to check session',
94
- };
95
- }
96
- }, [opts]);
97
-
98
- // Fetch user data
99
- const fetchUser = useCallback(async () => {
100
- setIsLoading(true);
101
- try {
102
- const result = await cloudApi.getMe();
103
-
104
- if (!mountedRef.current) return;
105
-
106
- if (result.success) {
107
- setUser(result.data);
108
- setIsExpired(false);
109
- setError(null);
110
- } else if (result.sessionExpired) {
111
- setUser(null);
112
- setIsExpired(true);
113
- } else {
114
- setError({
115
- error: result.error,
116
- code: 'SESSION_ERROR',
117
- message: result.error,
118
- });
119
- }
120
- } finally {
121
- if (mountedRef.current) {
122
- setIsLoading(false);
123
- }
124
- }
125
- }, []);
126
-
127
- // Handle session expiration from any API call
128
- useEffect(() => {
129
- const unsubscribe = onSessionExpired((sessionError) => {
130
- if (!mountedRef.current) return;
131
-
132
- setUser(null);
133
- setIsExpired(true);
134
- setError(sessionError);
135
- opts.onExpired(sessionError);
136
- });
137
-
138
- return unsubscribe;
139
- }, [opts]);
140
-
141
- // Check session on mount
142
- useEffect(() => {
143
- mountedRef.current = true;
144
-
145
- if (opts.checkOnMount) {
146
- fetchUser();
147
- }
148
-
149
- return () => {
150
- mountedRef.current = false;
151
- };
152
- }, [opts.checkOnMount, fetchUser]);
153
-
154
- // Periodic session check
155
- useEffect(() => {
156
- if (opts.checkInterval <= 0) return;
157
-
158
- intervalRef.current = setInterval(() => {
159
- // Only check if we think we're authenticated
160
- if (user) {
161
- checkSession();
162
- }
163
- }, opts.checkInterval);
164
-
165
- return () => {
166
- if (intervalRef.current) {
167
- clearInterval(intervalRef.current);
168
- intervalRef.current = null;
169
- }
170
- };
171
- }, [opts.checkInterval, user, checkSession]);
172
-
173
- // Clear expired state
174
- const clearExpired = useCallback(() => {
175
- setIsExpired(false);
176
- setError(null);
177
- }, []);
178
-
179
- // Redirect to login
180
- const redirectToLogin = useCallback(() => {
181
- // Preserve current path for redirect after login
182
- const returnTo = encodeURIComponent(window.location.pathname + window.location.search);
183
- window.location.href = `/login?returnTo=${returnTo}`;
184
- }, []);
185
-
186
- // Logout
187
- const logout = useCallback(async () => {
188
- await cloudApi.logout();
189
- setUser(null);
190
- setIsExpired(false);
191
- setError(null);
192
- window.location.href = '/login';
193
- }, []);
194
-
195
- return {
196
- user,
197
- isLoading,
198
- isAuthenticated: user !== null,
199
- isExpired,
200
- error,
201
- csrfToken: getCsrfToken(),
202
- checkSession,
203
- clearExpired,
204
- redirectToLogin,
205
- logout,
206
- };
207
- }
208
-
209
- export type { SessionError, CloudUser };
@@ -1,132 +0,0 @@
1
- /**
2
- * useWorkspaceMembers Hook
3
- *
4
- * Fetches and caches workspace members for filtering online users.
5
- * Returns the set of usernames that have access to the workspace.
6
- */
7
-
8
- import { useState, useEffect, useCallback, useMemo } from 'react';
9
- import { cloudApi } from '../../lib/cloudApi';
10
- import type { UserPresence } from './usePresence';
11
-
12
- interface WorkspaceMember {
13
- id: string;
14
- userId: string;
15
- role: string;
16
- isPending: boolean;
17
- user?: {
18
- githubUsername?: string;
19
- displayName?: string;
20
- email?: string;
21
- avatarUrl?: string;
22
- };
23
- }
24
-
25
- export interface UseWorkspaceMembersOptions {
26
- /** The workspace ID to fetch members for */
27
- workspaceId?: string;
28
- /** Whether to enable fetching (e.g., only in cloud mode) */
29
- enabled?: boolean;
30
- }
31
-
32
- export interface UseWorkspaceMembersReturn {
33
- /** Set of usernames with workspace access (lowercase for comparison) */
34
- memberUsernames: Set<string>;
35
- /** Whether members are currently loading */
36
- isLoading: boolean;
37
- /** Error message if fetch failed */
38
- error: string | null;
39
- /** Refetch workspace members */
40
- refetch: () => Promise<void>;
41
- }
42
-
43
- /**
44
- * Hook to fetch workspace members and provide a set of usernames with access.
45
- * Used to filter online users to show only those with workspace access.
46
- */
47
- export function useWorkspaceMembers(
48
- options: UseWorkspaceMembersOptions = {}
49
- ): UseWorkspaceMembersReturn {
50
- const { workspaceId, enabled = true } = options;
51
-
52
- const [members, setMembers] = useState<WorkspaceMember[]>([]);
53
- const [isLoading, setIsLoading] = useState(false);
54
- const [error, setError] = useState<string | null>(null);
55
-
56
- const fetchMembers = useCallback(async () => {
57
- if (!workspaceId || !enabled) {
58
- setMembers([]);
59
- return;
60
- }
61
-
62
- setIsLoading(true);
63
- setError(null);
64
-
65
- try {
66
- const result = await cloudApi.getWorkspaceMembers(workspaceId);
67
- if (result.success) {
68
- setMembers(result.data.members as WorkspaceMember[]);
69
- } else {
70
- setError(result.error);
71
- setMembers([]);
72
- }
73
- } catch (err) {
74
- setError(err instanceof Error ? err.message : 'Failed to fetch members');
75
- setMembers([]);
76
- } finally {
77
- setIsLoading(false);
78
- }
79
- }, [workspaceId, enabled]);
80
-
81
- // Fetch members when workspace changes
82
- useEffect(() => {
83
- fetchMembers();
84
- }, [fetchMembers]);
85
-
86
- // Build set of member usernames (lowercase for case-insensitive comparison)
87
- // Include githubUsername, displayName, and email prefix to match all possible presence usernames
88
- // (The auth API uses: displayName || githubUsername || email.split('@')[0])
89
- const memberUsernames = useMemo(() => {
90
- const usernames = new Set<string>();
91
- for (const member of members) {
92
- // Add GitHub username if available
93
- if (member.user?.githubUsername) {
94
- usernames.add(member.user.githubUsername.toLowerCase());
95
- }
96
- // Also add displayName for email-only users who don't have a GitHub username
97
- if (member.user?.displayName) {
98
- usernames.add(member.user.displayName.toLowerCase());
99
- }
100
- // Also add email prefix for email-only users without displayName
101
- if (member.user?.email) {
102
- usernames.add(member.user.email.split('@')[0].toLowerCase());
103
- }
104
- }
105
- return usernames;
106
- }, [members]);
107
-
108
- return {
109
- memberUsernames,
110
- isLoading,
111
- error,
112
- refetch: fetchMembers,
113
- };
114
- }
115
-
116
- /**
117
- * Filter online users to only include those with workspace access.
118
- * If no members are loaded (non-cloud mode or error), returns all users.
119
- */
120
- export function filterOnlineUsersByWorkspace(
121
- onlineUsers: UserPresence[],
122
- memberUsernames: Set<string>
123
- ): UserPresence[] {
124
- // If no members loaded, show all users (non-cloud mode fallback)
125
- if (memberUsernames.size === 0) {
126
- return onlineUsers;
127
- }
128
-
129
- return onlineUsers.filter((user) =>
130
- memberUsernames.has(user.username.toLowerCase())
131
- );
132
- }
@@ -1,237 +0,0 @@
1
- /**
2
- * useWorkspaceStatus Hook
3
- *
4
- * React hook for monitoring workspace status with auto-wakeup capability.
5
- * Polls for status updates and can automatically restart stopped workspaces.
6
- */
7
-
8
- import { useState, useEffect, useCallback, useRef } from 'react';
9
- import { cloudApi } from '../../lib/cloudApi';
10
-
11
- export interface WorkspaceStatus {
12
- id: string;
13
- name: string;
14
- status: string;
15
- publicUrl?: string;
16
- isStopped: boolean;
17
- isRunning: boolean;
18
- isProvisioning: boolean;
19
- hasError: boolean;
20
- config: {
21
- providers: string[];
22
- repositories: string[];
23
- };
24
- }
25
-
26
- export interface UseWorkspaceStatusOptions {
27
- /** Poll for status updates (default: true) */
28
- autoRefresh?: boolean;
29
- /** Interval to poll for status in ms (default: 30000) */
30
- refreshInterval?: number;
31
- /** Auto-wakeup when workspace is stopped (default: false) */
32
- autoWakeup?: boolean;
33
- /** Callback when workspace status changes */
34
- onStatusChange?: (status: string, wasRestarted: boolean) => void;
35
- }
36
-
37
- export interface UseWorkspaceStatusReturn {
38
- /** Current workspace data (null if no workspace) */
39
- workspace: WorkspaceStatus | null;
40
- /** Whether workspace exists */
41
- exists: boolean;
42
- /** Whether the status check is in progress */
43
- isLoading: boolean;
44
- /** Whether a wakeup is in progress */
45
- isWakingUp: boolean;
46
- /** Status message for display */
47
- statusMessage: string;
48
- /** Action needed (wakeup, check_error, etc) */
49
- actionNeeded: 'wakeup' | 'check_error' | null;
50
- /** Error if any */
51
- error: string | null;
52
- /** Manually refresh status */
53
- refresh: () => Promise<void>;
54
- /** Manually wake up workspace */
55
- wakeup: () => Promise<{ success: boolean; message: string }>;
56
- }
57
-
58
- const DEFAULT_OPTIONS: Required<UseWorkspaceStatusOptions> = {
59
- autoRefresh: true,
60
- refreshInterval: 30000, // 30 seconds
61
- autoWakeup: false,
62
- onStatusChange: () => {},
63
- };
64
-
65
- export function useWorkspaceStatus(
66
- options: UseWorkspaceStatusOptions = {}
67
- ): UseWorkspaceStatusReturn {
68
- // Stabilize options to prevent infinite re-renders
69
- // Use refs for callbacks and useMemo for primitive values
70
- const autoRefresh = options.autoRefresh ?? DEFAULT_OPTIONS.autoRefresh;
71
- const refreshInterval = options.refreshInterval ?? DEFAULT_OPTIONS.refreshInterval;
72
- const autoWakeup = options.autoWakeup ?? DEFAULT_OPTIONS.autoWakeup;
73
-
74
- // Store callback in ref to avoid recreating refresh on every render
75
- const onStatusChangeRef = useRef(options.onStatusChange ?? DEFAULT_OPTIONS.onStatusChange);
76
- onStatusChangeRef.current = options.onStatusChange ?? DEFAULT_OPTIONS.onStatusChange;
77
-
78
- const [workspace, setWorkspace] = useState<WorkspaceStatus | null>(null);
79
- const [exists, setExists] = useState(false);
80
- const [isLoading, setIsLoading] = useState(true);
81
- const [isWakingUp, setIsWakingUp] = useState(false);
82
- const [statusMessage, setStatusMessage] = useState('');
83
- const [actionNeeded, setActionNeeded] = useState<'wakeup' | 'check_error' | null>(null);
84
- const [error, setError] = useState<string | null>(null);
85
-
86
- const intervalRef = useRef<NodeJS.Timeout | null>(null);
87
- const mountedRef = useRef(true);
88
- const previousStatusRef = useRef<string | null>(null);
89
-
90
- // Fetch workspace status
91
- const refresh = useCallback(async () => {
92
- try {
93
- setIsLoading(true);
94
- setError(null);
95
-
96
- const result = await cloudApi.getPrimaryWorkspace();
97
-
98
- if (!mountedRef.current) return;
99
-
100
- if (result.success) {
101
- setExists(result.data.exists);
102
- setStatusMessage(result.data.statusMessage);
103
- setActionNeeded(result.data.actionNeeded || null);
104
-
105
- if (result.data.workspace) {
106
- const ws = result.data.workspace;
107
- setWorkspace(ws);
108
-
109
- // Check for status change
110
- if (previousStatusRef.current && previousStatusRef.current !== ws.status) {
111
- onStatusChangeRef.current(ws.status, false);
112
- }
113
- previousStatusRef.current = ws.status;
114
- } else {
115
- setWorkspace(null);
116
- }
117
- } else {
118
- setError(result.error);
119
- }
120
- } catch (_e) {
121
- if (mountedRef.current) {
122
- setError('Failed to fetch workspace status');
123
- }
124
- } finally {
125
- if (mountedRef.current) {
126
- setIsLoading(false);
127
- }
128
- }
129
- }, []); // No dependencies - uses refs for callbacks
130
-
131
- // Store refresh interval in ref for wakeup callback
132
- const refreshIntervalRef = useRef(refreshInterval);
133
- refreshIntervalRef.current = refreshInterval;
134
- const autoRefreshRef = useRef(autoRefresh);
135
- autoRefreshRef.current = autoRefresh;
136
-
137
- // Wake up workspace
138
- const wakeup = useCallback(async (): Promise<{ success: boolean; message: string }> => {
139
- if (!workspace?.id) {
140
- return { success: false, message: 'No workspace to wake up' };
141
- }
142
-
143
- try {
144
- setIsWakingUp(true);
145
- setError(null);
146
-
147
- const result = await cloudApi.wakeupWorkspace(workspace.id);
148
-
149
- if (!mountedRef.current) {
150
- return { success: false, message: 'Component unmounted' };
151
- }
152
-
153
- if (result.success) {
154
- // Update local state
155
- if (result.data.wasRestarted) {
156
- setStatusMessage(result.data.message);
157
- setActionNeeded(null);
158
- onStatusChangeRef.current('starting', true);
159
-
160
- // Start more frequent polling to catch when workspace is ready
161
- if (intervalRef.current) {
162
- clearInterval(intervalRef.current);
163
- }
164
- intervalRef.current = setInterval(refresh, 5000); // Poll every 5s during startup
165
-
166
- // Reset to normal interval after 2 minutes
167
- setTimeout(() => {
168
- if (mountedRef.current && intervalRef.current) {
169
- clearInterval(intervalRef.current);
170
- if (autoRefreshRef.current) {
171
- intervalRef.current = setInterval(refresh, refreshIntervalRef.current);
172
- }
173
- }
174
- }, 120000);
175
- }
176
-
177
- return { success: true, message: result.data.message };
178
- } else {
179
- setError(result.error);
180
- return { success: false, message: result.error };
181
- }
182
- } catch (e) {
183
- const message = e instanceof Error ? e.message : 'Failed to wake up workspace';
184
- if (mountedRef.current) {
185
- setError(message);
186
- }
187
- return { success: false, message };
188
- } finally {
189
- if (mountedRef.current) {
190
- setIsWakingUp(false);
191
- }
192
- }
193
- }, [workspace?.id, refresh]);
194
-
195
- // Initial fetch
196
- useEffect(() => {
197
- mountedRef.current = true;
198
- refresh();
199
-
200
- return () => {
201
- mountedRef.current = false;
202
- };
203
- }, [refresh]);
204
-
205
- // Auto-refresh polling
206
- useEffect(() => {
207
- if (!autoRefresh) return;
208
-
209
- intervalRef.current = setInterval(refresh, refreshInterval);
210
-
211
- return () => {
212
- if (intervalRef.current) {
213
- clearInterval(intervalRef.current);
214
- intervalRef.current = null;
215
- }
216
- };
217
- }, [autoRefresh, refreshInterval, refresh]);
218
-
219
- // Auto-wakeup when workspace is stopped
220
- useEffect(() => {
221
- if (autoWakeup && workspace?.isStopped && !isWakingUp) {
222
- wakeup();
223
- }
224
- }, [autoWakeup, workspace?.isStopped, isWakingUp, wakeup]);
225
-
226
- return {
227
- workspace,
228
- exists,
229
- isLoading,
230
- isWakingUp,
231
- statusMessage,
232
- actionNeeded,
233
- error,
234
- refresh,
235
- wakeup,
236
- };
237
- }