@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,13 +1,14 @@
1
1
  /**
2
2
  * Provider Setup Client Component
3
3
  *
4
- * Full-page interactive terminal for provider authentication and setup.
5
- * Uses the shared TerminalProviderSetup component.
4
+ * Full-page provider setup that supports both workspace mode and onboarding mode.
5
+ * - Workspace mode: optionally route back to a specific workspace
6
+ * - Onboarding mode: no workspaceId required; use auth mode "onboarding"
6
7
  */
7
8
 
8
9
  'use client';
9
10
 
10
- import React from 'react';
11
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
11
12
  import { useRouter, useSearchParams } from 'next/navigation';
12
13
  import { LogoIcon } from '../../../../components/Logo';
13
14
  import { TerminalProviderSetup } from '../../../../components/TerminalProviderSetup';
@@ -23,12 +24,13 @@ const PROVIDER_AUTH_CONFIG: Record<string, {
23
24
  codex: { authMethod: 'oauth', requiresUrlCopy: true },
24
25
  openai: { authMethod: 'oauth', requiresUrlCopy: true },
25
26
  cursor: { authMethod: 'oauth', requiresUrlCopy: true },
26
- // Gemini uses terminal - CLI shows interactive menu for OAuth vs API key
27
27
  google: { authMethod: 'terminal' },
28
28
  opencode: { authMethod: 'terminal' },
29
29
  droid: { authMethod: 'terminal' },
30
30
  };
31
31
 
32
+ type SetupMode = 'api_key' | 'cli';
33
+
32
34
  export interface ProviderSetupClientProps {
33
35
  provider: string;
34
36
  }
@@ -36,10 +38,122 @@ export interface ProviderSetupClientProps {
36
38
  export function ProviderSetupClient({ provider }: ProviderSetupClientProps) {
37
39
  const router = useRouter();
38
40
  const searchParams = useSearchParams();
39
- const workspaceId = searchParams.get('workspace');
41
+ const workspaceId = searchParams.get('workspace') || undefined;
42
+
43
+ const [csrfToken, setCsrfToken] = useState<string | null>(null);
44
+ const [setupMode, setSetupMode] = useState<SetupMode>(workspaceId ? 'cli' : 'api_key');
45
+ const [apiKey, setApiKey] = useState('');
46
+ const [apiKeyError, setApiKeyError] = useState<string | null>(null);
47
+ const [apiKeySuccess, setApiKeySuccess] = useState<string | null>(null);
48
+ const [isSubmittingApiKey, setIsSubmittingApiKey] = useState(false);
40
49
 
41
50
  const config = PROVIDER_CONFIGS[provider];
42
51
 
52
+ useEffect(() => {
53
+ fetch('/api/auth/session', { credentials: 'include' })
54
+ .then((res) => {
55
+ const token = res.headers.get('X-CSRF-Token');
56
+ if (token) {
57
+ setCsrfToken(token);
58
+ }
59
+ })
60
+ .catch(() => {
61
+ // Ignore CSRF bootstrap errors here; requests still run without token if server allows.
62
+ });
63
+ }, []);
64
+
65
+ const returnPath = workspaceId ? `/app?workspace=${workspaceId}` : '/app/onboarding';
66
+
67
+ const handleSuccess = useCallback(() => {
68
+ router.push(returnPath);
69
+ }, [router, returnPath]);
70
+
71
+ const handleCancel = useCallback(() => {
72
+ router.push(returnPath);
73
+ }, [router, returnPath]);
74
+
75
+ const handleConnectAnother = useCallback(() => {
76
+ if (workspaceId) {
77
+ router.push(`/providers?workspace=${workspaceId}`);
78
+ return;
79
+ }
80
+
81
+ router.push('/app/onboarding');
82
+ }, [router, workspaceId]);
83
+
84
+ const supportsApiKey = useMemo(
85
+ () => ['anthropic', 'codex', 'openai', 'google'].includes(config?.name || ''),
86
+ [config?.name]
87
+ );
88
+
89
+ const authConfig = config ? PROVIDER_AUTH_CONFIG[config.name] : undefined;
90
+ const isOAuthProvider = authConfig?.authMethod === 'oauth';
91
+
92
+ const submitApiKey = useCallback(async () => {
93
+ if (!config) {
94
+ return;
95
+ }
96
+
97
+ if (!apiKey.trim()) {
98
+ setApiKeyError('Please enter an API key');
99
+ return;
100
+ }
101
+
102
+ setIsSubmittingApiKey(true);
103
+ setApiKeyError(null);
104
+ setApiKeySuccess(null);
105
+
106
+ try {
107
+ const headers: Record<string, string> = { 'Content-Type': 'application/json' };
108
+ if (csrfToken) {
109
+ headers['X-CSRF-Token'] = csrfToken;
110
+ }
111
+
112
+ const providerCandidates = config.name === 'codex'
113
+ ? ['openai', 'codex']
114
+ : [config.name];
115
+
116
+ let connected = false;
117
+ let lastError = 'Failed to connect API key';
118
+
119
+ for (const providerName of providerCandidates) {
120
+ const body: Record<string, string> = { apiKey: apiKey.trim() };
121
+ if (workspaceId) {
122
+ body.workspaceId = workspaceId;
123
+ }
124
+
125
+ const res = await fetch(`/api/providers/${providerName}/api-key`, {
126
+ method: 'POST',
127
+ credentials: 'include',
128
+ headers,
129
+ body: JSON.stringify(body),
130
+ });
131
+
132
+ if (res.ok) {
133
+ connected = true;
134
+ break;
135
+ }
136
+
137
+ const data = await res.json().catch(() => ({}));
138
+ lastError = data.error || data.message || 'Failed to connect API key';
139
+ }
140
+
141
+ if (!connected) {
142
+ throw new Error(lastError);
143
+ }
144
+
145
+ setApiKey('');
146
+ setApiKeySuccess(`${config.displayName} connected successfully.`);
147
+ setTimeout(() => {
148
+ handleSuccess();
149
+ }, 800);
150
+ } catch (err) {
151
+ setApiKeyError(err instanceof Error ? err.message : 'Failed to connect API key');
152
+ } finally {
153
+ setIsSubmittingApiKey(false);
154
+ }
155
+ }, [apiKey, config, csrfToken, handleSuccess, workspaceId]);
156
+
43
157
  if (!config) {
44
158
  return (
45
159
  <div className="min-h-screen bg-gradient-to-br from-[#0a0a0f] via-[#0d1117] to-[#0a0a0f] flex items-center justify-center">
@@ -53,36 +167,6 @@ export function ProviderSetupClient({ provider }: ProviderSetupClientProps) {
53
167
  );
54
168
  }
55
169
 
56
- if (!workspaceId) {
57
- return (
58
- <div className="min-h-screen bg-gradient-to-br from-[#0a0a0f] via-[#0d1117] to-[#0a0a0f] flex items-center justify-center">
59
- <div className="text-center">
60
- <p className="text-error">No workspace specified</p>
61
- <a href="/app" className="mt-4 text-accent-cyan hover:underline">
62
- Back to dashboard
63
- </a>
64
- </div>
65
- </div>
66
- );
67
- }
68
-
69
- const handleSuccess = () => {
70
- router.push(`/app?workspace=${workspaceId}`);
71
- };
72
-
73
- const handleCancel = () => {
74
- router.push(`/app?workspace=${workspaceId}`);
75
- };
76
-
77
- const handleConnectAnother = () => {
78
- // Navigate to providers page to select another provider
79
- router.push(`/providers?workspace=${workspaceId}`);
80
- };
81
-
82
- // Get auth configuration for this provider
83
- const authConfig = PROVIDER_AUTH_CONFIG[config.name];
84
- const isOAuthProvider = authConfig?.authMethod === 'oauth';
85
-
86
170
  return (
87
171
  <div className="min-h-screen bg-gradient-to-br from-[#0a0a0f] via-[#0d1117] to-[#0a0a0f] p-8">
88
172
  <div className="max-w-4xl mx-auto">
@@ -93,7 +177,7 @@ export function ProviderSetupClient({ provider }: ProviderSetupClientProps) {
93
177
  <span className="text-lg font-bold text-white">Agent Relay</span>
94
178
  </a>
95
179
  <a
96
- href={`/app?workspace=${workspaceId}`}
180
+ href={returnPath}
97
181
  className="px-4 py-2 text-sm text-text-secondary hover:text-text-primary transition-colors"
98
182
  >
99
183
  Skip for now →
@@ -117,29 +201,93 @@ export function ProviderSetupClient({ provider }: ProviderSetupClientProps) {
117
201
  </h1>
118
202
  </div>
119
203
  <p className="text-text-muted">
120
- {isOAuthProvider
121
- ? 'Follow the instructions below to authenticate using your local terminal'
122
- : 'Complete the authentication flow in the interactive terminal below'
123
- }
204
+ {workspaceId
205
+ ? 'Authenticate provider access for this workspace.'
206
+ : 'Authenticate provider access during onboarding before workspace provisioning.'}
124
207
  </p>
125
208
  </div>
126
209
 
127
- {/* Auth Component - OAuth or Terminal based on provider config */}
128
- {isOAuthProvider ? (
210
+ {/* Setup mode selector */}
211
+ {supportsApiKey && (
212
+ <div className="mb-6 flex gap-2 p-1 bg-bg-primary/80 backdrop-blur-sm border border-border-subtle rounded-xl">
213
+ <button
214
+ onClick={() => setSetupMode('api_key')}
215
+ className={`flex-1 py-2.5 px-4 rounded-lg text-sm transition-colors ${
216
+ setupMode === 'api_key'
217
+ ? 'bg-accent-cyan text-bg-deep font-semibold'
218
+ : 'text-text-muted hover:text-white'
219
+ }`}
220
+ >
221
+ API Key Input
222
+ </button>
223
+ <button
224
+ onClick={() => setSetupMode('cli')}
225
+ className={`flex-1 py-2.5 px-4 rounded-lg text-sm transition-colors ${
226
+ setupMode === 'cli'
227
+ ? 'bg-accent-cyan text-bg-deep font-semibold'
228
+ : 'text-text-muted hover:text-white'
229
+ }`}
230
+ >
231
+ Authenticate via CLI
232
+ </button>
233
+ </div>
234
+ )}
235
+
236
+ {/* API key setup */}
237
+ {supportsApiKey && setupMode === 'api_key' ? (
238
+ <div className="bg-bg-primary/80 backdrop-blur-sm border border-border-subtle rounded-2xl p-6 shadow-2xl space-y-4">
239
+ <p className="text-sm text-text-muted">
240
+ Enter your {config.displayName} API key. In onboarding mode, this is stored at the user level and reused during workspace provisioning.
241
+ </p>
242
+ <div className="flex gap-3">
243
+ <input
244
+ type="password"
245
+ value={apiKey}
246
+ onChange={(event) => setApiKey(event.target.value)}
247
+ placeholder={`Enter ${config.displayName} API key`}
248
+ className="flex-1 px-4 py-3 bg-bg-tertiary border border-border-subtle rounded-lg text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:border-accent-cyan focus:ring-1 focus:ring-accent-cyan/30 transition-all"
249
+ />
250
+ <button
251
+ onClick={submitApiKey}
252
+ disabled={isSubmittingApiKey || !apiKey.trim()}
253
+ className="px-5 py-3 bg-accent-cyan text-bg-deep font-semibold rounded-lg text-sm hover:bg-accent-cyan/90 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
254
+ >
255
+ {isSubmittingApiKey ? 'Connecting...' : 'Connect'}
256
+ </button>
257
+ </div>
258
+
259
+ {apiKeyError && (
260
+ <div className="p-3 bg-error/10 border border-error/30 rounded-lg text-sm text-error">
261
+ {apiKeyError}
262
+ </div>
263
+ )}
264
+
265
+ {apiKeySuccess && (
266
+ <div className="p-3 bg-success/10 border border-success/30 rounded-lg text-sm text-success">
267
+ {apiKeySuccess}
268
+ </div>
269
+ )}
270
+ </div>
271
+ ) : isOAuthProvider ? (
129
272
  <ProviderAuthFlow
130
273
  provider={{
131
274
  id: config.name,
132
275
  name: config.name,
133
276
  displayName: config.displayName,
134
277
  color: config.color,
135
- requiresUrlCopy: authConfig.requiresUrlCopy,
278
+ requiresUrlCopy: authConfig?.requiresUrlCopy,
136
279
  }}
137
280
  workspaceId={workspaceId}
281
+ mode={workspaceId ? 'workspace' : 'onboarding'}
282
+ csrfToken={csrfToken || undefined}
283
+ showManualDone={!workspaceId}
138
284
  onSuccess={handleSuccess}
139
285
  onCancel={handleCancel}
140
- onError={(err) => console.error('Setup error:', err)}
286
+ onError={(err) => {
287
+ setApiKeyError(err);
288
+ }}
141
289
  />
142
- ) : (
290
+ ) : workspaceId ? (
143
291
  <TerminalProviderSetup
144
292
  provider={{
145
293
  id: config.id,
@@ -153,29 +301,31 @@ export function ProviderSetupClient({ provider }: ProviderSetupClientProps) {
153
301
  onSuccess={handleSuccess}
154
302
  onCancel={handleCancel}
155
303
  onConnectAnother={handleConnectAnother}
156
- onError={(err) => console.error('Setup error:', err)}
304
+ onError={(err) => setApiKeyError(err)}
157
305
  className="shadow-2xl"
158
306
  />
307
+ ) : (
308
+ <div className="bg-bg-primary/80 backdrop-blur-sm border border-border-subtle rounded-2xl p-6 text-center">
309
+ <p className="text-text-muted text-sm">
310
+ Interactive terminal setup requires a workspace. Use API key input or CLI authentication for onboarding mode.
311
+ </p>
312
+ </div>
159
313
  )}
160
314
 
161
315
  {/* Help text */}
162
316
  <div className="mt-6 p-4 bg-bg-primary/80 backdrop-blur-sm border border-border-subtle rounded-xl">
163
317
  <h3 className="text-white font-medium mb-2">How this works:</h3>
164
- {isOAuthProvider ? (
318
+ {setupMode === 'api_key' && supportsApiKey ? (
165
319
  <ol className="text-sm text-text-muted space-y-1 list-decimal list-inside">
166
- <li>Copy the command shown above and run it in your terminal</li>
167
- <li>The CLI will open your browser automatically for OAuth login</li>
168
- <li>Complete the authentication in your browser</li>
169
- <li>The CLI will capture the callback and complete the setup</li>
170
- <li>Return here to continue to the dashboard</li>
320
+ <li>Paste your provider API key</li>
321
+ <li>Click connect to store credentials securely</li>
322
+ <li>Continue back to onboarding or dashboard</li>
171
323
  </ol>
172
324
  ) : (
173
325
  <ol className="text-sm text-text-muted space-y-1 list-decimal list-inside">
174
- <li>The terminal above is interactive - respond to any prompts by typing directly</li>
175
- <li>When a login URL appears, copy and paste it into your browser</li>
176
- <li>Complete the login in your browser, then return here</li>
177
- <li>Answer any remaining prompts (skills, permissions, etc.) in the terminal</li>
178
- <li>Once connected, click &quot;Done - Continue&quot; to go to the dashboard</li>
326
+ <li>Copy the command shown and run it in your terminal</li>
327
+ <li>Complete the authentication flow in browser/CLI</li>
328
+ <li>Return here and continue once connected</li>
179
329
  </ol>
180
330
  )}
181
331
  </div>
@@ -183,10 +333,10 @@ export function ProviderSetupClient({ provider }: ProviderSetupClientProps) {
183
333
  {/* Fallback link */}
184
334
  <div className="mt-4 text-center">
185
335
  <a
186
- href={`/providers?connect=${config.id}&workspace=${workspaceId}`}
336
+ href={workspaceId ? `/providers?connect=${config.id}&workspace=${workspaceId}` : '/app/onboarding'}
187
337
  className="text-sm text-text-muted hover:text-accent-cyan transition-colors"
188
338
  >
189
- Having trouble? Try the popup-based login instead
339
+ Having trouble? Try another connection method
190
340
  </a>
191
341
  </div>
192
342
  </div>
@@ -185,7 +185,7 @@ export function AgentCard({
185
185
  <span
186
186
  className="inline-flex items-center px-1.5 py-0.5 rounded text-[9px] font-medium uppercase tracking-wider
187
187
  bg-warning-light text-warning border border-warning/30"
188
- title={`Local agent from ${agent.daemonName || 'linked daemon'}`}
188
+ title={`Local agent from ${agent.brokerName || 'linked broker'}`}
189
189
  >
190
190
  Local
191
191
  </span>
@@ -329,7 +329,7 @@ export function AgentCard({
329
329
  <span
330
330
  className="inline-flex items-center px-1.5 py-0.5 rounded text-[9px] font-medium uppercase tracking-wider
331
331
  bg-warning-light text-warning border border-warning/30"
332
- title={`Local agent from ${agent.daemonName || 'linked daemon'}`}
332
+ title={`Local agent from ${agent.brokerName || 'linked broker'}`}
333
333
  >
334
334
  Local
335
335
  </span>
@@ -352,9 +352,9 @@ export function AgentCard({
352
352
  )}
353
353
  </div>
354
354
  {showBreadcrumb ? (
355
- <span className="text-xs text-text-muted truncate block">{agent.isLocal ? agent.daemonName || agent.machineId : getAgentBreadcrumb(agent.name)}</span>
355
+ <span className="text-xs text-text-muted truncate block">{agent.isLocal ? agent.brokerName || agent.machineId : getAgentBreadcrumb(agent.name)}</span>
356
356
  ) : (
357
- <span className="text-xs text-text-muted truncate block">{agent.isLocal ? agent.daemonName || agent.machineId : agent.name}</span>
357
+ <span className="text-xs text-text-muted truncate block">{agent.isLocal ? agent.brokerName || agent.machineId : agent.name}</span>
358
358
  )}
359
359
  {agent.agentId && (
360
360
  <span className="text-[10px] text-text-muted font-mono opacity-70" title="Agent ID (use to resume)">
@@ -8,6 +8,7 @@
8
8
  import React, { useMemo } from 'react';
9
9
  import { useAgentLogs } from './hooks/useAgentLogs';
10
10
  import { getAgentColor } from '../lib/colors';
11
+ import { sanitizeLogContent, isSpinnerFragment } from '../lib/sanitize-logs';
11
12
 
12
13
  export interface AgentLogPreviewProps {
13
14
  agentName: string;
@@ -33,8 +34,6 @@ export function AgentLogPreview({
33
34
  const colors = getAgentColor(agentName);
34
35
 
35
36
  const previewLines = useMemo(() => {
36
- const spinnerPattern = /^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣾⣽⣻⢿⡿⣟⣯⣷◐◓◑◒●○◉◎|\\\/\-*.\u2800-\u28FF]+$/;
37
-
38
37
  const allLines: string[] = [];
39
38
  for (const log of logs) {
40
39
  if (log.type === 'system') continue;
@@ -42,7 +41,7 @@ export function AgentLogPreview({
42
41
  for (const rawLine of sanitized.split('\n')) {
43
42
  const trimmed = rawLine.trim();
44
43
  if (trimmed.length === 0) continue;
45
- if (trimmed.length <= 2 && spinnerPattern.test(trimmed)) continue;
44
+ if (isSpinnerFragment(trimmed)) continue;
46
45
  allLines.push(rawLine.replace(/\s+$/g, ''));
47
46
  }
48
47
  }
@@ -136,38 +135,3 @@ function ExpandIcon() {
136
135
  );
137
136
  }
138
137
 
139
- /**
140
- * Strip ANSI escape codes (including degraded sequences like "[38;5;216m")
141
- * and control characters so logs render as clean text.
142
- */
143
- function sanitizeLogContent(text: string): string {
144
- if (!text) return '';
145
-
146
- let result = text;
147
-
148
- // Remove OSC sequences (like window title): \x1b]...(\x07|\x1b\\)
149
- result = result.replace(/\x1b\].*?(?:\x07|\x1b\\)/gs, '');
150
-
151
- // Remove DCS (Device Control String) sequences: \x1bP...\x1b\\
152
- result = result.replace(/\x1bP.*?\x1b\\/gs, '');
153
-
154
- // Remove standard ANSI escape sequences (CSI, SGR, etc.)
155
- result = result.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, '');
156
-
157
- // Remove single-character escapes
158
- result = result.replace(/\x1b[@-Z\\-_]/g, '');
159
-
160
- // Remove orphaned CSI sequences that lost their escape byte
161
- result = result.replace(/^\[\??\d+[hlKJHfABCDGPXsu]/gm, '');
162
-
163
- // Remove literal SGR sequences that show up without ESC (e.g. "[38;5;216m")
164
- result = result.replace(/\[\d+(?:;\d+)*m/g, '');
165
-
166
- // Remove carriage returns/backspaces and other control chars (except newline/tab)
167
- result = result.replace(/\r/g, '');
168
- result = result.replace(/.\x08/g, '');
169
- result = result.replace(/\x08+/g, '');
170
- result = result.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
171
-
172
- return result;
173
- }