@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,508 @@
1
+ import type { ComponentType } from 'react';
2
+
3
+ export interface DashboardFeatures {
4
+ billing: boolean;
5
+ teams: boolean;
6
+ workspaces: boolean;
7
+ auth: boolean;
8
+ }
9
+
10
+ export interface CloudSettingsSlots {
11
+ BillingPanel?: ComponentType;
12
+ TeamPanel?: ComponentType;
13
+ WorkspacePanel?: ComponentType;
14
+ }
15
+
16
+ export type SessionErrorCode = 'SESSION_EXPIRED' | 'USER_NOT_FOUND' | 'SESSION_ERROR';
17
+
18
+ export interface SessionError {
19
+ error: string;
20
+ code: SessionErrorCode;
21
+ message: string;
22
+ }
23
+
24
+ export type SessionExpiredCallback = (error: SessionError) => void;
25
+
26
+ export interface SessionStatus {
27
+ authenticated: boolean;
28
+ code?: SessionErrorCode;
29
+ message?: string;
30
+ user?: {
31
+ id: string;
32
+ githubUsername: string;
33
+ email?: string;
34
+ avatarUrl?: string;
35
+ plan: string;
36
+ };
37
+ }
38
+
39
+ export interface CloudUser {
40
+ id: string;
41
+ githubUsername: string;
42
+ email?: string;
43
+ avatarUrl?: string;
44
+ plan: string;
45
+ connectedProviders: Array<{
46
+ provider: string;
47
+ email?: string;
48
+ connectedAt: string;
49
+ }>;
50
+ pendingInvites: number;
51
+ onboardingCompleted: boolean;
52
+ displayName?: string;
53
+ }
54
+
55
+ export interface NangoLoginSession {
56
+ sessionToken: string;
57
+ tempUserId: string;
58
+ }
59
+
60
+ export interface NangoLoginStatus {
61
+ ready: boolean;
62
+ user?: {
63
+ id: string;
64
+ githubUsername: string;
65
+ email?: string;
66
+ avatarUrl?: string;
67
+ plan: string;
68
+ };
69
+ }
70
+
71
+ export interface NangoRepoSession {
72
+ sessionToken: string;
73
+ }
74
+
75
+ export interface NangoRepoStatus {
76
+ ready: boolean;
77
+ pendingApproval?: boolean;
78
+ message?: string;
79
+ repos?: Array<{
80
+ id: string;
81
+ fullName: string;
82
+ isPrivate: boolean;
83
+ defaultBranch: string;
84
+ }>;
85
+ }
86
+
87
+ export type CloudApiResult<T> =
88
+ | { success: true; data: T }
89
+ | { success: false; error: string; sessionExpired?: boolean };
90
+
91
+ export type CloudApiResultWithoutSession<T> =
92
+ | { success: true; data: T }
93
+ | { success: false; error: string };
94
+
95
+ export interface WorkspaceSummary {
96
+ id: string;
97
+ name: string;
98
+ status: string;
99
+ publicUrl?: string;
100
+ isStopped: boolean;
101
+ isRunning: boolean;
102
+ isProvisioning: boolean;
103
+ hasError: boolean;
104
+ }
105
+
106
+ export interface CloudApiAdapter {
107
+ getNangoLoginSession(): Promise<CloudApiResultWithoutSession<NangoLoginSession>>;
108
+ checkNangoLoginStatus(
109
+ connectionId: string
110
+ ): Promise<CloudApiResultWithoutSession<NangoLoginStatus>>;
111
+ getNangoRepoSession(): Promise<CloudApiResult<NangoRepoSession>>;
112
+ checkNangoRepoStatus(connectionId: string): Promise<CloudApiResult<NangoRepoStatus>>;
113
+ checkSession(): Promise<SessionStatus>;
114
+ getMe(): Promise<CloudApiResult<CloudUser>>;
115
+ logout(): Promise<{ success: boolean; error?: string }>;
116
+ getWorkspaces(): Promise<
117
+ CloudApiResult<{
118
+ workspaces: Array<{
119
+ id: string;
120
+ name: string;
121
+ slug: string;
122
+ repositories: number;
123
+ members: number;
124
+ plan: string;
125
+ }>;
126
+ }>
127
+ >;
128
+ getWorkspace(id: string): Promise<
129
+ CloudApiResult<{
130
+ id: string;
131
+ name: string;
132
+ slug: string;
133
+ config: Record<string, unknown>;
134
+ createdAt: string;
135
+ }>
136
+ >;
137
+ createWorkspace(data: { name: string; slug?: string }): Promise<
138
+ CloudApiResult<{
139
+ id: string;
140
+ name: string;
141
+ slug: string;
142
+ }>
143
+ >;
144
+ getPrimaryWorkspace(): Promise<
145
+ CloudApiResult<{
146
+ exists: boolean;
147
+ message?: string;
148
+ workspace?: {
149
+ id: string;
150
+ name: string;
151
+ status: string;
152
+ publicUrl?: string;
153
+ isStopped: boolean;
154
+ isRunning: boolean;
155
+ isProvisioning: boolean;
156
+ hasError: boolean;
157
+ config: {
158
+ providers: string[];
159
+ repositories: string[];
160
+ };
161
+ };
162
+ statusMessage: string;
163
+ actionNeeded?: 'wakeup' | 'check_error' | null;
164
+ }>
165
+ >;
166
+ getWorkspaceSummary(): Promise<
167
+ CloudApiResult<{
168
+ workspaces: WorkspaceSummary[];
169
+ summary: {
170
+ total: number;
171
+ running: number;
172
+ stopped: number;
173
+ provisioning: number;
174
+ error: number;
175
+ };
176
+ overallStatus: 'ready' | 'provisioning' | 'stopped' | 'none' | 'error';
177
+ }>
178
+ >;
179
+ getAccessibleWorkspaces(): Promise<
180
+ CloudApiResult<{
181
+ workspaces: Array<{
182
+ id: string;
183
+ name: string;
184
+ status: string;
185
+ publicUrl?: string;
186
+ providers?: string[];
187
+ repositories?: string[];
188
+ accessType: 'owner' | 'member' | 'contributor';
189
+ permission: 'admin' | 'write' | 'read';
190
+ createdAt: string;
191
+ }>;
192
+ summary: {
193
+ owned: number;
194
+ member: number;
195
+ contributor: number;
196
+ total: number;
197
+ };
198
+ }>
199
+ >;
200
+ getWorkspaceStatus(id: string): Promise<CloudApiResult<{ status: string }>>;
201
+ wakeupWorkspace(id: string): Promise<
202
+ CloudApiResult<{
203
+ status: string;
204
+ wasRestarted: boolean;
205
+ message: string;
206
+ estimatedStartTime?: number;
207
+ publicUrl?: string;
208
+ }>
209
+ >;
210
+ restartWorkspace(id: string): Promise<
211
+ CloudApiResult<{
212
+ success: boolean;
213
+ action: 'restarted' | 'reprovisioning';
214
+ message: string;
215
+ }>
216
+ >;
217
+ rebuildWorkspace(id: string): Promise<CloudApiResult<{ success: boolean; message: string }>>;
218
+ getProviders(workspaceId: string): Promise<
219
+ CloudApiResult<{
220
+ providers: Array<{
221
+ id: string;
222
+ name: string;
223
+ displayName: string;
224
+ description: string;
225
+ color: string;
226
+ authStrategy: string;
227
+ cliCommand?: string;
228
+ isConnected: boolean;
229
+ connectedAs?: string;
230
+ connectedAt?: string;
231
+ }>;
232
+ }>
233
+ >;
234
+ disconnectProvider(provider: string, workspaceId: string): Promise<CloudApiResult<{ success: boolean }>>;
235
+ getUserCredentials(): Promise<
236
+ CloudApiResult<{
237
+ credentials: Array<{
238
+ id: string;
239
+ provider: string;
240
+ providerAccountEmail?: string;
241
+ createdAt: string;
242
+ updatedAt: string;
243
+ workspaces: Array<{ id: string; name: string }>;
244
+ }>;
245
+ }>
246
+ >;
247
+ assignCredentialToWorkspace(
248
+ credentialId: string,
249
+ workspaceId: string
250
+ ): Promise<CloudApiResult<{ success: boolean }>>;
251
+ unassignCredentialFromWorkspace(
252
+ credentialId: string,
253
+ workspaceId: string
254
+ ): Promise<CloudApiResult<{ success: boolean }>>;
255
+ getWorkspaceMembers(workspaceId: string): Promise<
256
+ CloudApiResult<{
257
+ members: Array<{
258
+ id: string;
259
+ userId: string;
260
+ role: string;
261
+ isPending: boolean;
262
+ user?: {
263
+ githubUsername: string;
264
+ email?: string;
265
+ avatarUrl?: string;
266
+ };
267
+ }>;
268
+ }>
269
+ >;
270
+ getRepoCollaborators(workspaceId: string): Promise<
271
+ CloudApiResult<{
272
+ collaborators: Array<{
273
+ id: number;
274
+ login: string;
275
+ avatarUrl: string;
276
+ permission: 'admin' | 'write' | 'read' | 'none';
277
+ repos: string[];
278
+ }>;
279
+ totalRepos: number;
280
+ message?: string;
281
+ }>
282
+ >;
283
+ inviteMember(
284
+ workspaceId: string,
285
+ githubUsername: string,
286
+ role?: string
287
+ ): Promise<CloudApiResult<{ success: boolean; member: unknown }>>;
288
+ getPendingInvites(): Promise<
289
+ CloudApiResult<{
290
+ invites: Array<{
291
+ id: string;
292
+ workspaceId: string;
293
+ workspaceName: string;
294
+ role: string;
295
+ invitedAt: string;
296
+ invitedBy: string;
297
+ }>;
298
+ }>
299
+ >;
300
+ acceptInvite(inviteId: string): Promise<CloudApiResult<{ success: boolean; workspaceId: string }>>;
301
+ declineInvite(inviteId: string): Promise<CloudApiResult<{ success: boolean }>>;
302
+ updateMemberRole(
303
+ workspaceId: string,
304
+ memberId: string,
305
+ role: string
306
+ ): Promise<CloudApiResult<{ success: boolean; role: string }>>;
307
+ removeMember(workspaceId: string, memberId: string): Promise<CloudApiResult<{ success: boolean }>>;
308
+ getBillingPlans(): Promise<
309
+ CloudApiResult<{
310
+ plans: Array<{
311
+ tier: string;
312
+ name: string;
313
+ description: string;
314
+ price: { monthly: number; yearly: number };
315
+ features: string[];
316
+ limits: Record<string, number>;
317
+ recommended?: boolean;
318
+ }>;
319
+ publishableKey: string;
320
+ }>
321
+ >;
322
+ getSubscription(): Promise<
323
+ CloudApiResult<{
324
+ tier: string;
325
+ subscription: {
326
+ id: string;
327
+ tier: string;
328
+ status: string;
329
+ currentPeriodStart: string;
330
+ currentPeriodEnd: string;
331
+ cancelAtPeriodEnd: boolean;
332
+ interval: 'month' | 'year';
333
+ } | null;
334
+ customer: {
335
+ id: string;
336
+ email: string;
337
+ name?: string;
338
+ paymentMethods: Array<{
339
+ id: string;
340
+ type: string;
341
+ last4?: string;
342
+ brand?: string;
343
+ isDefault: boolean;
344
+ }>;
345
+ invoices: Array<{
346
+ id: string;
347
+ number: string;
348
+ amount: number;
349
+ status: string;
350
+ date: string;
351
+ pdfUrl?: string;
352
+ }>;
353
+ } | null;
354
+ }>
355
+ >;
356
+ createCheckoutSession(
357
+ tier: string,
358
+ interval?: 'month' | 'year'
359
+ ): Promise<CloudApiResult<{ sessionId: string; checkoutUrl: string }>>;
360
+ createBillingPortal(): Promise<CloudApiResult<{ sessionId: string; portalUrl: string }>>;
361
+ changeSubscription(
362
+ tier: string,
363
+ interval?: 'month' | 'year'
364
+ ): Promise<CloudApiResult<{ subscription: { tier: string; status: string } }>>;
365
+ cancelSubscription(): Promise<
366
+ CloudApiResult<{
367
+ subscription: { cancelAtPeriodEnd: boolean; currentPeriodEnd: string };
368
+ message: string;
369
+ }>
370
+ >;
371
+ resumeSubscription(): Promise<
372
+ CloudApiResult<{
373
+ subscription: { cancelAtPeriodEnd: boolean };
374
+ message: string;
375
+ }>
376
+ >;
377
+ getInvoices(): Promise<
378
+ CloudApiResult<{
379
+ invoices: Array<{
380
+ id: string;
381
+ number: string;
382
+ amount: number;
383
+ status: string;
384
+ date: string;
385
+ pdfUrl?: string;
386
+ }>;
387
+ }>
388
+ >;
389
+ stopWorkspace(id: string): Promise<CloudApiResult<{ success: boolean; message: string }>>;
390
+ deleteWorkspace(id: string): Promise<CloudApiResult<{ success: boolean; message: string }>>;
391
+ addReposToWorkspace(
392
+ workspaceId: string,
393
+ repositoryIds: string[]
394
+ ): Promise<CloudApiResult<{ success: boolean; message: string }>>;
395
+ setCustomDomain(workspaceId: string, domain: string): Promise<
396
+ CloudApiResult<{
397
+ success: boolean;
398
+ domain: string;
399
+ status: string;
400
+ instructions: {
401
+ type: string;
402
+ name: string;
403
+ value: string;
404
+ ttl: number;
405
+ };
406
+ verifyEndpoint: string;
407
+ message: string;
408
+ }>
409
+ >;
410
+ verifyCustomDomain(workspaceId: string): Promise<
411
+ CloudApiResult<{
412
+ success: boolean;
413
+ status: string;
414
+ domain?: string;
415
+ message?: string;
416
+ error?: string;
417
+ }>
418
+ >;
419
+ removeCustomDomain(workspaceId: string): Promise<CloudApiResult<{ success: boolean; message: string }>>;
420
+ getWorkspaceDetails(id: string): Promise<
421
+ CloudApiResult<{
422
+ id: string;
423
+ name: string;
424
+ status: string;
425
+ publicUrl?: string;
426
+ computeProvider: string;
427
+ config: {
428
+ providers: string[];
429
+ repositories: string[];
430
+ supervisorEnabled?: boolean;
431
+ maxAgents?: number;
432
+ };
433
+ customDomain?: string;
434
+ customDomainStatus?: string;
435
+ errorMessage?: string;
436
+ repositories: Array<{
437
+ id: string;
438
+ fullName: string;
439
+ syncStatus: string;
440
+ lastSyncedAt?: string;
441
+ }>;
442
+ createdAt: string;
443
+ updatedAt: string;
444
+ }>
445
+ >;
446
+ getRepos(): Promise<
447
+ CloudApiResult<{
448
+ repositories: Array<{
449
+ id: string;
450
+ fullName: string;
451
+ isPrivate: boolean;
452
+ defaultBranch: string;
453
+ syncStatus: string;
454
+ hasNangoConnection: boolean;
455
+ lastSyncedAt?: string;
456
+ }>;
457
+ }>
458
+ >;
459
+ syncRepo(repoId: string): Promise<
460
+ CloudApiResult<{
461
+ message: string;
462
+ syncStatus: string;
463
+ result?: unknown;
464
+ }>
465
+ >;
466
+ spawnAgent(
467
+ workspaceId: string,
468
+ params: {
469
+ name: string;
470
+ provider?: string;
471
+ task?: string;
472
+ cwd?: string;
473
+ model?: string;
474
+ }
475
+ ): Promise<
476
+ CloudApiResult<{
477
+ name: string;
478
+ sandboxId: string;
479
+ status: string;
480
+ cli: string;
481
+ workspaceId: string;
482
+ createdAt: string;
483
+ }>
484
+ >;
485
+ getAgents(workspaceId: string): Promise<
486
+ CloudApiResult<{
487
+ agents: Array<{
488
+ name: string;
489
+ sandboxId: string;
490
+ status: string;
491
+ cli: string;
492
+ workspaceId: string;
493
+ createdAt: string;
494
+ }>;
495
+ workspaceId: string;
496
+ }>
497
+ >;
498
+ stopAgent(workspaceId: string, agentName: string): Promise<CloudApiResult<{ success: boolean }>>;
499
+ getOnboardingNextStep(): Promise<unknown>;
500
+ }
501
+
502
+ export interface CloudAuthAdapter {
503
+ checkSession(): Promise<SessionStatus>;
504
+ getUser(): Promise<CloudApiResult<CloudUser>>;
505
+ logout(): Promise<{ success: boolean; error?: string }>;
506
+ redirectToLogin(): void;
507
+ onSessionExpired(callback: SessionExpiredCallback): () => void;
508
+ }
@@ -2,18 +2,18 @@
2
2
  * Dashboard V2 - Main App Page (Client Component)
3
3
  *
4
4
  * In cloud mode: Shows workspace selection and connects to selected workspace's dashboard.
5
- * In local mode: Connects to local daemon WebSocket.
5
+ * In local mode: Connects to local broker WebSocket.
6
6
  */
7
7
 
8
8
  'use client';
9
9
 
10
- import React, { useState, useEffect, useCallback } from 'react';
10
+ import React, { useState, useEffect, useCallback, useMemo } from 'react';
11
11
  import { App } from '../../../components/App';
12
- import { CloudSessionProvider } from '../../../components/CloudSessionProvider';
13
12
  import { LogoIcon } from '../../../components/Logo';
14
13
  import { setActiveWorkspaceId } from '../../../lib/api';
15
14
  import { ProvisioningProgress } from '../../../components/ProvisioningProgress';
16
15
  import { ProviderConnectionList, type ProviderInfo } from '../../../components/ProviderConnectionList';
16
+ import { DashboardConfigProvider, createCloudApiAdapter, createCloudAuthAdapter, setCloudCsrfToken } from '../../../adapters';
17
17
 
18
18
  interface Workspace {
19
19
  id: string;
@@ -56,6 +56,16 @@ const AI_PROVIDERS: ProviderInfo[] = [
56
56
  // Force cloud mode via env var - prevents silent fallback to local mode
57
57
  const FORCE_CLOUD_MODE = process.env.NEXT_PUBLIC_FORCE_CLOUD_MODE === 'true';
58
58
 
59
+ // Detect cloud mode from server-injected config (relay-cloud injects this in app.html)
60
+ function detectCloudConfig(): { isCloud: boolean; features?: Record<string, boolean> } {
61
+ if (typeof window !== 'undefined') {
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ const config = (window as any).__RELAY_CLOUD_CONFIG__;
64
+ if (config) return config as { isCloud: boolean; features?: Record<string, boolean> };
65
+ }
66
+ return { isCloud: false };
67
+ }
68
+
59
69
  export default function DashboardPageClient() {
60
70
  const [state, setState] = useState<PageState>('loading');
61
71
  const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
@@ -63,8 +73,7 @@ export default function DashboardPageClient() {
63
73
  const [selectedWorkspace, setSelectedWorkspace] = useState<Workspace | null>(null);
64
74
  const [wsUrl, setWsUrl] = useState<string | undefined>(undefined);
65
75
  const [error, setError] = useState<string | null>(null);
66
- // Track cloud mode for potential future use
67
- const [_isCloudMode, setIsCloudMode] = useState(FORCE_CLOUD_MODE);
76
+ const [isCloudMode, setIsCloudMode] = useState(() => FORCE_CLOUD_MODE || detectCloudConfig().isCloud);
68
77
  const [csrfToken, setCsrfToken] = useState<string | null>(null);
69
78
  const [provisioningInfo, setProvisioningInfo] = useState<ProvisioningInfo | null>(null);
70
79
  const [connectedProviders, setConnectedProviders] = useState<string[]>([]);
@@ -81,6 +90,16 @@ export default function DashboardPageClient() {
81
90
  if (FORCE_CLOUD_MODE) {
82
91
  throw new Error('Cloud mode enforced but session endpoint returned 404. Is the cloud server running?');
83
92
  }
93
+ // Always sync username from the health endpoint so it reflects the current project
94
+ try {
95
+ const healthRes = await fetch('/api/health');
96
+ if (healthRes.ok) {
97
+ const health = await healthRes.json();
98
+ if (health.projectName) {
99
+ localStorage.setItem('relay_username', health.projectName);
100
+ }
101
+ }
102
+ } catch { /* ignore */ }
84
103
  setIsCloudMode(false);
85
104
  setState('local');
86
105
  return;
@@ -90,6 +109,7 @@ export default function DashboardPageClient() {
90
109
  const token = sessionRes.headers.get('X-CSRF-Token');
91
110
  if (token) {
92
111
  setCsrfToken(token);
112
+ setCloudCsrfToken(token);
93
113
  }
94
114
 
95
115
  const session = await sessionRes.json();
@@ -162,9 +182,12 @@ export default function DashboardPageClient() {
162
182
 
163
183
  // Check for previously connected workspace (stored in localStorage)
164
184
  // This enables seamless reconnection on page reload
165
- const savedWorkspaceId = typeof window !== 'undefined'
185
+ const rawSavedId = typeof window !== 'undefined'
166
186
  ? localStorage.getItem('agentrelay_workspace_id')
167
187
  : null;
188
+ // Reject placeholder values like "default" that aren't real UUIDs
189
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
190
+ const savedWorkspaceId = rawSavedId && UUID_RE.test(rawSavedId) ? rawSavedId : null;
168
191
 
169
192
  if (savedWorkspaceId) {
170
193
  const savedWorkspace = runningWorkspaces.find((w: Workspace) => w.id === savedWorkspaceId);
@@ -180,12 +203,11 @@ export default function DashboardPageClient() {
180
203
  connectToWorkspace(runningWorkspaces[0]);
181
204
  } else if (runningWorkspaces.length > 1) {
182
205
  setState('select-workspace');
183
- } else if ((workspacesData.workspaces || []).length > 0) {
184
- // Has workspaces but none running
185
- setState('select-workspace');
186
206
  } else if ((reposData.repositories || []).length > 0) {
187
- // Has repos but no workspaces - show create workspace
188
- setState('no-workspaces');
207
+ // No running workspaces but has repos onboarding handles
208
+ // both "no workspace" and "workspace exists but not running" cases
209
+ window.location.href = '/app/onboarding';
210
+ return;
189
211
  } else {
190
212
  // No repos, no workspaces - redirect to connect repos
191
213
  window.location.href = '/connect-repos';
@@ -393,6 +415,38 @@ export default function DashboardPageClient() {
393
415
  }
394
416
  }, [connectToWorkspace, csrfToken]);
395
417
 
418
+ // Create cloud config for wrapping App when in cloud mode.
419
+ // Memoized to avoid recreating adapters on every render.
420
+ // IMPORTANT: All hooks must be called before any early returns (React rules of hooks).
421
+ const cloudConfig = useMemo(() => {
422
+ if (!isCloudMode) return null;
423
+ const serverConfig = detectCloudConfig();
424
+ return {
425
+ features: {
426
+ workspaces: true,
427
+ auth: true,
428
+ billing: serverConfig.features?.billing ?? true,
429
+ teams: serverConfig.features?.teams ?? true,
430
+ },
431
+ api: createCloudApiAdapter(),
432
+ auth: createCloudAuthAdapter(),
433
+ isCloudMode: true,
434
+ };
435
+ }, [isCloudMode]);
436
+
437
+ // Helper: wraps App with cloud config provider when in cloud mode
438
+ const renderApp = (props?: { wsUrl?: string }) => {
439
+ const app = <App wsUrl={props?.wsUrl} enableReactions />;
440
+ if (cloudConfig) {
441
+ return (
442
+ <DashboardConfigProvider config={cloudConfig}>
443
+ {app}
444
+ </DashboardConfigProvider>
445
+ );
446
+ }
447
+ return app;
448
+ };
449
+
396
450
  // Loading state
397
451
  if (state === 'loading') {
398
452
  return (
@@ -410,17 +464,12 @@ export default function DashboardPageClient() {
410
464
 
411
465
  // Local mode - just render the App component
412
466
  if (state === 'local') {
413
- return <App />;
467
+ return renderApp();
414
468
  }
415
469
 
416
470
  // Connected to workspace - render App with workspace's WebSocket
417
- // Wrap in CloudSessionProvider so App has access to cloud session context
418
471
  if (state === 'connected' && wsUrl) {
419
- return (
420
- <CloudSessionProvider cloudMode={true}>
421
- <App wsUrl={wsUrl} />
422
- </CloudSessionProvider>
423
- );
472
+ return renderApp({ wsUrl });
424
473
  }
425
474
 
426
475
  // Connecting state