@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
@@ -6,8 +6,57 @@
6
6
  */
7
7
 
8
8
  import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
9
+ import { useDashboardConfig } from '../adapters';
10
+
11
+ /**
12
+ * Model options inlined from @agent-relay/config (cli-registry.yaml).
13
+ * Inlined to avoid importing Node.js dependencies into the browser bundle.
14
+ */
15
+ const RegistryModelOptions = {
16
+ Claude: [
17
+ { value: 'sonnet', label: 'Sonnet' },
18
+ { value: 'opus', label: 'Opus' },
19
+ { value: 'haiku', label: 'Haiku' },
20
+ ],
21
+ Codex: [
22
+ { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex — Frontier agentic coding model' },
23
+ { value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex — Latest frontier agentic coding model' },
24
+ { value: 'gpt-5.3-codex-spark', label: 'GPT-5.3 Codex Spark — Ultra-fast coding model' },
25
+ { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max — Deep and fast reasoning' },
26
+ { value: 'gpt-5.2', label: 'GPT-5.2 — Frontier model, knowledge & reasoning' },
27
+ { value: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini — Cheaper, faster' },
28
+ ],
29
+ Gemini: [
30
+ { value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro Preview' },
31
+ { value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' },
32
+ { value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
33
+ { value: 'gemini-2.5-flash-lite', label: 'Gemini 2.5 Flash Lite' },
34
+ ],
35
+ Cursor: [
36
+ { value: 'opus-4.5-thinking', label: 'Claude 4.5 Opus (Thinking)' },
37
+ { value: 'opus-4.5', label: 'Claude 4.5 Opus' },
38
+ { value: 'sonnet-4.5', label: 'Claude 4.5 Sonnet' },
39
+ { value: 'sonnet-4.5-thinking', label: 'Claude 4.5 Sonnet (Thinking)' },
40
+ { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex' },
41
+ { value: 'gpt-5.2-codex-high', label: 'GPT-5.2 Codex High' },
42
+ { value: 'gpt-5.2-codex-low', label: 'GPT-5.2 Codex Low' },
43
+ { value: 'gpt-5.2-codex-xhigh', label: 'GPT-5.2 Codex Extra High' },
44
+ { value: 'gpt-5.2-codex-fast', label: 'GPT-5.2 Codex Fast' },
45
+ { value: 'gpt-5.2-codex-high-fast', label: 'GPT-5.2 Codex High Fast' },
46
+ { value: 'gpt-5.2-codex-low-fast', label: 'GPT-5.2 Codex Low Fast' },
47
+ { value: 'gpt-5.2-codex-xhigh-fast', label: 'GPT-5.2 Codex Extra High Fast' },
48
+ { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
49
+ { value: 'gpt-5.1-codex-max-high', label: 'GPT-5.1 Codex Max High' },
50
+ { value: 'gpt-5.2', label: 'GPT-5.2' },
51
+ { value: 'gpt-5.2-high', label: 'GPT-5.2 High' },
52
+ { value: 'gpt-5.1-high', label: 'GPT-5.1 High' },
53
+ { value: 'gemini-3-pro', label: 'Gemini 3 Pro' },
54
+ { value: 'gemini-3-flash', label: 'Gemini 3 Flash' },
55
+ { value: 'composer-1', label: 'Composer 1' },
56
+ { value: 'grok', label: 'Grok' },
57
+ ],
58
+ };
9
59
  import { getAgentColor, getAgentInitials } from '../lib/colors';
10
- import { cloudApi } from '../lib/cloudApi';
11
60
 
12
61
  export type SpeakOnTrigger = 'SESSION_END' | 'CODE_WRITTEN' | 'REVIEW_REQUEST' | 'EXPLICIT_ASK' | 'ALL_MESSAGES';
13
62
 
@@ -21,6 +70,7 @@ export interface SpawnConfig {
21
70
  shadowAgent?: string;
22
71
  shadowTriggers?: SpeakOnTrigger[];
23
72
  shadowSpeakOn?: SpeakOnTrigger[];
73
+ continueFrom?: string;
24
74
  }
25
75
 
26
76
  function deriveShadowMode(command: string): 'subagent' | 'process' {
@@ -36,8 +86,6 @@ export interface SpawnModalProps {
36
86
  existingAgents: string[];
37
87
  isSpawning?: boolean;
38
88
  error?: string | null;
39
- /** Whether running in cloud mode (enables credentials check) */
40
- isCloudMode?: boolean;
41
89
  /** Active workspace ID for provider setup redirect */
42
90
  workspaceId?: string;
43
91
  /** Agent defaults from settings */
@@ -54,64 +102,31 @@ export interface SpawnModalProps {
54
102
  repos?: Array<{ id: string; githubFullName: string }>;
55
103
  /** Currently active repo ID (cloud mode) */
56
104
  activeRepoId?: string;
105
+ /** Connected provider IDs (cloud mode) - used to disable unconnected providers */
106
+ connectedProviders?: string[];
107
+ /** Model options per agent type — provided by the host app */
108
+ modelOptions?: {
109
+ claude?: ModelOption[];
110
+ cursor?: ModelOption[];
111
+ codex?: ModelOption[];
112
+ gemini?: ModelOption[];
113
+ };
57
114
  }
58
115
 
59
- /** Model options for Claude agents */
60
- export const CLAUDE_MODEL_OPTIONS: { value: string; label: string }[] = [
61
- { value: 'sonnet', label: 'Sonnet' },
62
- { value: 'opus', label: 'Opus' },
63
- { value: 'haiku', label: 'Haiku' },
64
- ];
65
-
66
- type ClaudeModel = string;
67
-
68
- /** Model options for Cursor agents */
69
- export const CURSOR_MODEL_OPTIONS: { value: string; label: string }[] = [
70
- { value: 'opus-4.5-thinking', label: 'Claude 4.5 Opus (Thinking)' },
71
- { value: 'opus-4.5', label: 'Claude 4.5 Opus' },
72
- { value: 'sonnet-4.5', label: 'Claude 4.5 Sonnet' },
73
- { value: 'sonnet-4.5-thinking', label: 'Claude 4.5 Sonnet (Thinking)' },
74
- { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex' },
75
- { value: 'gpt-5.2-codex-high', label: 'GPT-5.2 Codex High' },
76
- { value: 'gpt-5.2-codex-low', label: 'GPT-5.2 Codex Low' },
77
- { value: 'gpt-5.2-codex-xhigh', label: 'GPT-5.2 Codex Extra High' },
78
- { value: 'gpt-5.2-codex-fast', label: 'GPT-5.2 Codex Fast' },
79
- { value: 'gpt-5.2-codex-high-fast', label: 'GPT-5.2 Codex High Fast' },
80
- { value: 'gpt-5.2-codex-low-fast', label: 'GPT-5.2 Codex Low Fast' },
81
- { value: 'gpt-5.2-codex-xhigh-fast', label: 'GPT-5.2 Codex Extra High Fast' },
82
- { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
83
- { value: 'gpt-5.1-codex-max-high', label: 'GPT-5.1 Codex Max High' },
84
- { value: 'gpt-5.2', label: 'GPT-5.2' },
85
- { value: 'gpt-5.2-high', label: 'GPT-5.2 High' },
86
- { value: 'gpt-5.1-high', label: 'GPT-5.1 High' },
87
- { value: 'gemini-3-pro', label: 'Gemini 3 Pro' },
88
- { value: 'gemini-3-flash', label: 'Gemini 3 Flash' },
89
- { value: 'composer-1', label: 'Composer 1' },
90
- { value: 'grok', label: 'Grok' },
91
- ];
92
-
93
- type CursorModel = string;
94
-
95
- /** Model options for Codex agents */
96
- export const CODEX_MODEL_OPTIONS: { value: string; label: string }[] = [
97
- { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex — Frontier agentic coding model' },
98
- { value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex — Latest frontier agentic coding model' },
99
- { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max — Deep and fast reasoning' },
100
- { value: 'gpt-5.2', label: 'GPT-5.2 — Frontier model, knowledge & reasoning' },
101
- { value: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini — Cheaper, faster' },
102
- ];
103
-
104
- type CodexModel = string;
116
+ export interface ModelOption {
117
+ value: string;
118
+ label: string;
119
+ }
105
120
 
106
- /** Model options for Gemini agents */
107
- export const GEMINI_MODEL_OPTIONS: { value: string; label: string }[] = [
108
- { value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro Preview' },
109
- { value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' },
110
- { value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
111
- { value: 'gemini-2.5-flash-lite', label: 'Gemini 2.5 Flash Lite' },
112
- ];
121
+ const EMPTY_MODEL_OPTIONS: ModelOption[] = [];
113
122
 
114
- type GeminiModel = string;
123
+ /** Built-in model options sourced from @agent-relay/config (cli-registry.yaml) */
124
+ export const DEFAULT_MODEL_OPTIONS: Record<string, ModelOption[]> = {
125
+ claude: RegistryModelOptions.Claude,
126
+ cursor: RegistryModelOptions.Cursor,
127
+ codex: RegistryModelOptions.Codex,
128
+ gemini: RegistryModelOptions.Gemini,
129
+ };
115
130
 
116
131
  const AGENT_TEMPLATES = [
117
132
  {
@@ -185,22 +200,33 @@ export function SpawnModal({
185
200
  existingAgents,
186
201
  isSpawning = false,
187
202
  error,
188
- isCloudMode = false,
189
203
  workspaceId,
190
204
  agentDefaults,
191
205
  repos,
192
206
  activeRepoId,
207
+ connectedProviders,
208
+ modelOptions,
193
209
  }: SpawnModalProps) {
210
+ const { features } = useDashboardConfig();
211
+ const hasWorkspaceFeature = features.workspaces;
212
+ const canUseWorkspaceRepoSelection = hasWorkspaceFeature && !!repos?.length;
213
+
214
+ const claudeModels = modelOptions?.claude ?? DEFAULT_MODEL_OPTIONS.claude ?? EMPTY_MODEL_OPTIONS;
215
+ const cursorModels = modelOptions?.cursor ?? DEFAULT_MODEL_OPTIONS.cursor ?? EMPTY_MODEL_OPTIONS;
216
+ const codexModels = modelOptions?.codex ?? DEFAULT_MODEL_OPTIONS.codex ?? EMPTY_MODEL_OPTIONS;
217
+ const geminiModels = modelOptions?.gemini ?? DEFAULT_MODEL_OPTIONS.gemini ?? EMPTY_MODEL_OPTIONS;
218
+
194
219
  const [selectedTemplate, setSelectedTemplate] = useState(AGENT_TEMPLATES[0]);
195
220
  const [name, setName] = useState('');
196
221
  const [customCommand, setCustomCommand] = useState('');
197
- const [selectedModel, setSelectedModel] = useState<ClaudeModel>('sonnet');
198
- const [selectedCursorModel, setSelectedCursorModel] = useState<CursorModel>('opus-4.5-thinking');
199
- const [selectedCodexModel, setSelectedCodexModel] = useState<CodexModel>('gpt-5.2-codex');
200
- const [selectedGeminiModel, setSelectedGeminiModel] = useState<GeminiModel>('gemini-2.5-pro');
222
+ const [selectedModel, setSelectedModel] = useState(agentDefaults?.defaultModels?.claude ?? claudeModels[0]?.value ?? '');
223
+ const [selectedCursorModel, setSelectedCursorModel] = useState(agentDefaults?.defaultModels?.cursor ?? cursorModels[0]?.value ?? '');
224
+ const [selectedCodexModel, setSelectedCodexModel] = useState(agentDefaults?.defaultModels?.codex ?? codexModels[0]?.value ?? '');
225
+ const [selectedGeminiModel, setSelectedGeminiModel] = useState(agentDefaults?.defaultModels?.gemini ?? geminiModels[0]?.value ?? '');
201
226
  const [cwd, setCwd] = useState('');
202
227
  const [selectedRepoId, setSelectedRepoId] = useState<string | undefined>(activeRepoId);
203
228
  const [team, setTeam] = useState('');
229
+ const [continueFromPrevious, setContinueFromPrevious] = useState(false);
204
230
  const [isShadow, setIsShadow] = useState(false);
205
231
  const [shadowOf, setShadowOf] = useState('');
206
232
  const [shadowAgent, setShadowAgent] = useState('');
@@ -234,69 +260,6 @@ export function SpawnModal({
234
260
 
235
261
  const shadowMode = useMemo(() => deriveShadowMode(effectiveCommand), [effectiveCommand]);
236
262
 
237
- // Provider credentials state (for cloud mode)
238
- const [connectedProviders, setConnectedProviders] = useState<Set<string>>(new Set());
239
- const [isLoadingCredentials, setIsLoadingCredentials] = useState(false);
240
-
241
- // Check if selected provider has active credentials
242
- const hasActiveCredentials = useMemo(() => {
243
- // Non-cloud mode or custom template: no credentials check needed
244
- if (!isCloudMode || !selectedTemplate.providerId) {
245
- return true;
246
- }
247
- // Check if provider is connected (handle both 'openai' and 'codex' for OpenAI)
248
- if (selectedTemplate.providerId === 'codex') {
249
- return connectedProviders.has('codex') || connectedProviders.has('openai');
250
- }
251
- return connectedProviders.has(selectedTemplate.providerId);
252
- }, [isCloudMode, selectedTemplate.providerId, connectedProviders]);
253
-
254
- // Provider setup URL for CTA
255
- // Note: /providers/setup/ only supports 'claude' and 'codex'
256
- // Other providers should go to /providers page
257
- const providerSetupUrl = useMemo(() => {
258
- if (!selectedTemplate.providerId) return null;
259
- const command = selectedTemplate.command;
260
- const supportedSetupPages = ['claude', 'codex'];
261
-
262
- if (supportedSetupPages.includes(command)) {
263
- const base = `/providers/setup/${command}`;
264
- return workspaceId ? `${base}?workspace=${workspaceId}` : base;
265
- } else {
266
- // For other providers, go to main providers page
267
- return workspaceId ? `/providers?workspace=${workspaceId}` : '/providers';
268
- }
269
- }, [selectedTemplate, workspaceId]);
270
-
271
- // Fetch connected providers when modal opens in cloud mode
272
- useEffect(() => {
273
- if (!isOpen || !isCloudMode || !workspaceId) {
274
- return;
275
- }
276
-
277
- const fetchProviders = async () => {
278
- setIsLoadingCredentials(true);
279
- try {
280
- // Get workspace-specific provider connection status
281
- const result = await cloudApi.getProviders(workspaceId);
282
- if (result.success && result.data.providers) {
283
- const providers = new Set(
284
- result.data.providers
285
- .filter(p => p.isConnected)
286
- .map(p => p.id)
287
- );
288
- setConnectedProviders(providers);
289
- }
290
- } catch (err) {
291
- console.error('Failed to fetch provider credentials:', err);
292
- } finally {
293
- setIsLoadingCredentials(false);
294
- }
295
- };
296
-
297
- fetchProviders();
298
- }, [isOpen, isCloudMode, workspaceId]);
299
-
300
263
  const SPEAK_ON_OPTIONS: { value: SpeakOnTrigger; label: string; description: string }[] = [
301
264
  { value: 'EXPLICIT_ASK', label: 'Explicit Ask', description: 'When directly asked' },
302
265
  { value: 'SESSION_END', label: 'Session End', description: 'When session ends' },
@@ -317,22 +280,30 @@ export function SpawnModal({
317
280
  useEffect(() => {
318
281
  if (isOpen) {
319
282
  // Determine default template based on settings
283
+ // In cloud mode, also skip templates whose provider isn't connected
284
+ const isTemplateAvailable = (t: typeof AGENT_TEMPLATES[number]) => {
285
+ if (t.comingSoon && hasWorkspaceFeature) return false;
286
+ if (connectedProviders && t.providerId && !connectedProviders.includes(t.providerId)) return false;
287
+ return true;
288
+ };
320
289
  const defaultTemplateId = agentDefaults?.defaultCliType;
321
290
  const defaultTemplate = defaultTemplateId
322
- ? AGENT_TEMPLATES.find(t => t.id === defaultTemplateId && !t.comingSoon) ?? AGENT_TEMPLATES[0]
323
- : AGENT_TEMPLATES[0];
291
+ ? AGENT_TEMPLATES.find(t => t.id === defaultTemplateId && isTemplateAvailable(t))
292
+ ?? AGENT_TEMPLATES.find(t => isTemplateAvailable(t))
293
+ ?? AGENT_TEMPLATES[0]
294
+ : AGENT_TEMPLATES.find(t => isTemplateAvailable(t)) ?? AGENT_TEMPLATES[0];
324
295
 
325
296
  setSelectedTemplate(defaultTemplate);
326
297
  setName('');
327
298
  setCustomCommand('');
328
- // Use settings-based model defaults with fallbacks
329
- setSelectedModel(agentDefaults?.defaultModels?.claude ?? 'sonnet');
330
- setSelectedCursorModel(agentDefaults?.defaultModels?.cursor ?? 'opus-4.5-thinking');
331
- setSelectedCodexModel(agentDefaults?.defaultModels?.codex ?? 'gpt-5.2-codex');
332
- setSelectedGeminiModel(agentDefaults?.defaultModels?.gemini ?? 'gemini-2.5-pro');
299
+ setSelectedModel(agentDefaults?.defaultModels?.claude ?? claudeModels[0]?.value ?? '');
300
+ setSelectedCursorModel(agentDefaults?.defaultModels?.cursor ?? cursorModels[0]?.value ?? '');
301
+ setSelectedCodexModel(agentDefaults?.defaultModels?.codex ?? codexModels[0]?.value ?? '');
302
+ setSelectedGeminiModel(agentDefaults?.defaultModels?.gemini ?? geminiModels[0]?.value ?? '');
333
303
  setCwd('');
334
304
  setSelectedRepoId(activeRepoId);
335
305
  setTeam('');
306
+ setContinueFromPrevious(false);
336
307
  setIsShadow(false);
337
308
  setShadowOf('');
338
309
  setShadowAgent('');
@@ -340,7 +311,7 @@ export function SpawnModal({
340
311
  setLocalError(null);
341
312
  setTimeout(() => nameInputRef.current?.focus(), 100);
342
313
  }
343
- }, [isOpen, agentDefaults, activeRepoId, repos]);
314
+ }, [isOpen, agentDefaults, activeRepoId, repos, connectedProviders, hasWorkspaceFeature, claudeModels, cursorModels, codexModels, geminiModels]);
344
315
 
345
316
  const validateName = useCallback(
346
317
  (value: string): string | null => {
@@ -383,7 +354,7 @@ export function SpawnModal({
383
354
 
384
355
  // Derive cwd: in cloud mode with repos, use selected repo name; otherwise use text input
385
356
  let effectiveCwd: string | undefined;
386
- if (isCloudMode && repos && repos.length > 0 && selectedRepoId) {
357
+ if (canUseWorkspaceRepoSelection && selectedRepoId) {
387
358
  if (selectedRepoId === '__all__') {
388
359
  // Coordinator mode: no cwd, agent starts at workspace root with access to all repos
389
360
  effectiveCwd = undefined;
@@ -407,6 +378,7 @@ export function SpawnModal({
407
378
  shadowAgent: shadowAgent.trim() || undefined,
408
379
  shadowTriggers: isShadow ? shadowSpeakOn : undefined,
409
380
  shadowSpeakOn: isShadow ? shadowSpeakOn : undefined,
381
+ continueFrom: continueFromPrevious ? finalName : undefined,
410
382
  });
411
383
 
412
384
  if (success) {
@@ -454,13 +426,34 @@ export function SpawnModal({
454
426
  </div>
455
427
 
456
428
  <form onSubmit={handleSubmit} className="p-6">
429
+ {/* No providers connected warning */}
430
+ {connectedProviders && connectedProviders.length === 0 && (
431
+ <div className="mb-5 p-4 rounded-lg border border-red-400/30 bg-red-400/10">
432
+ <p className="text-sm text-red-400 font-medium mb-1">No AI providers connected</p>
433
+ <p className="text-xs text-text-muted">
434
+ Connect an AI provider in your{' '}
435
+ <a
436
+ href={workspaceId ? `/app?workspace=${workspaceId}&tab=providers` : '/providers'}
437
+ className="text-accent underline"
438
+ >
439
+ workspace settings
440
+ </a>
441
+ {' '}to spawn agents.
442
+ </p>
443
+ </div>
444
+ )}
457
445
  {/* Agent Type Selection */}
458
446
  <div className="mb-5">
459
447
  <label className="block text-sm font-semibold text-text-primary mb-2">Agent Type</label>
460
448
  <div className="grid grid-cols-3 gap-2">
461
449
  {AGENT_TEMPLATES.map((template) => {
462
450
  // Only disable "coming soon" providers in cloud mode - locally they might be available
463
- const isDisabled = template.comingSoon && isCloudMode;
451
+ const isComingSoon = template.comingSoon && hasWorkspaceFeature;
452
+ // In cloud mode, disable providers that aren't connected (skip for custom/null providerId)
453
+ const isProviderMissing = connectedProviders && template.providerId
454
+ ? !connectedProviders.includes(template.providerId)
455
+ : false;
456
+ const isDisabled = isComingSoon || isProviderMissing;
464
457
  return (
465
458
  <button
466
459
  key={template.id}
@@ -477,11 +470,16 @@ export function SpawnModal({
477
470
  `}
478
471
  onClick={() => !isDisabled && setSelectedTemplate(template)}
479
472
  >
480
- {isDisabled && (
473
+ {isComingSoon && (
481
474
  <span className="absolute top-1 right-1 px-1.5 py-0.5 bg-amber-400/20 text-amber-400 text-[10px] font-medium rounded">
482
475
  Soon
483
476
  </span>
484
477
  )}
478
+ {isProviderMissing && !isComingSoon && (
479
+ <span className="absolute top-1 right-1 px-1.5 py-0.5 bg-red-400/20 text-red-400 text-[10px] font-medium rounded">
480
+ Not Connected
481
+ </span>
482
+ )}
485
483
  <span className={`text-2xl ${isDisabled ? 'grayscale' : ''}`}>{template.icon}</span>
486
484
  <span className="text-sm font-semibold text-text-primary">{template.name}</span>
487
485
  <span className="text-xs text-text-muted text-center">{template.description}</span>
@@ -501,10 +499,10 @@ export function SpawnModal({
501
499
  id="claude-model"
502
500
  className="w-full py-2.5 px-3.5 border border-border rounded-md text-sm font-sans outline-none bg-bg-primary text-text-primary transition-colors duration-150 focus:border-accent disabled:bg-bg-hover disabled:text-text-muted"
503
501
  value={selectedModel}
504
- onChange={(e) => setSelectedModel(e.target.value as ClaudeModel)}
502
+ onChange={(e) => setSelectedModel(e.target.value)}
505
503
  disabled={isSpawning}
506
504
  >
507
- {CLAUDE_MODEL_OPTIONS.map((model) => (
505
+ {claudeModels.map((model) => (
508
506
  <option key={model.value} value={model.value}>
509
507
  {model.label}
510
508
  </option>
@@ -523,10 +521,10 @@ export function SpawnModal({
523
521
  id="cursor-model"
524
522
  className="w-full py-2.5 px-3.5 border border-border rounded-md text-sm font-sans outline-none bg-bg-primary text-text-primary transition-colors duration-150 focus:border-accent disabled:bg-bg-hover disabled:text-text-muted"
525
523
  value={selectedCursorModel}
526
- onChange={(e) => setSelectedCursorModel(e.target.value as CursorModel)}
524
+ onChange={(e) => setSelectedCursorModel(e.target.value)}
527
525
  disabled={isSpawning}
528
526
  >
529
- {CURSOR_MODEL_OPTIONS.map((model) => (
527
+ {cursorModels.map((model) => (
530
528
  <option key={model.value} value={model.value}>
531
529
  {model.label}
532
530
  </option>
@@ -545,10 +543,10 @@ export function SpawnModal({
545
543
  id="codex-model"
546
544
  className="w-full py-2.5 px-3.5 border border-border rounded-md text-sm font-sans outline-none bg-bg-primary text-text-primary transition-colors duration-150 focus:border-accent disabled:bg-bg-hover disabled:text-text-muted"
547
545
  value={selectedCodexModel}
548
- onChange={(e) => setSelectedCodexModel(e.target.value as CodexModel)}
546
+ onChange={(e) => setSelectedCodexModel(e.target.value)}
549
547
  disabled={isSpawning}
550
548
  >
551
- {CODEX_MODEL_OPTIONS.map((model) => (
549
+ {codexModels.map((model) => (
552
550
  <option key={model.value} value={model.value}>
553
551
  {model.label}
554
552
  </option>
@@ -567,10 +565,10 @@ export function SpawnModal({
567
565
  id="gemini-model"
568
566
  className="w-full py-2.5 px-3.5 border border-border rounded-md text-sm font-sans outline-none bg-bg-primary text-text-primary transition-colors duration-150 focus:border-accent disabled:bg-bg-hover disabled:text-text-muted"
569
567
  value={selectedGeminiModel}
570
- onChange={(e) => setSelectedGeminiModel(e.target.value as GeminiModel)}
568
+ onChange={(e) => setSelectedGeminiModel(e.target.value)}
571
569
  disabled={isSpawning}
572
570
  >
573
- {GEMINI_MODEL_OPTIONS.map((model) => (
571
+ {geminiModels.map((model) => (
574
572
  <option key={model.value} value={model.value}>
575
573
  {model.label}
576
574
  </option>
@@ -642,7 +640,7 @@ export function SpawnModal({
642
640
  )}
643
641
 
644
642
  {/* Repository (cloud) / Working Directory (local) */}
645
- {isCloudMode && repos && repos.length > 0 ? (
643
+ {canUseWorkspaceRepoSelection ? (
646
644
  <div className="mb-5">
647
645
  <label className="block text-sm font-semibold text-text-primary mb-2" htmlFor="agent-repo">
648
646
  Repository
@@ -686,6 +684,37 @@ export function SpawnModal({
686
684
  </div>
687
685
  )}
688
686
 
687
+ {/* Resume from Previous Session */}
688
+ <div className="mb-5 p-4 border border-border rounded-lg bg-bg-hover/50">
689
+ <div className="flex items-center justify-between">
690
+ <div>
691
+ <label className="block text-sm font-semibold text-text-primary">
692
+ Resume Previous Session
693
+ </label>
694
+ <span className="text-xs text-text-muted">
695
+ Inject context from this agent's last session
696
+ </span>
697
+ </div>
698
+ <button
699
+ type="button"
700
+ className={`
701
+ relative w-11 h-6 rounded-full transition-colors duration-200
702
+ ${continueFromPrevious ? 'bg-accent' : 'bg-bg-active'}
703
+ `}
704
+ onClick={() => setContinueFromPrevious(!continueFromPrevious)}
705
+ disabled={isSpawning}
706
+ aria-pressed={continueFromPrevious}
707
+ >
708
+ <span
709
+ className={`
710
+ absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full transition-transform duration-200 shadow-sm
711
+ ${continueFromPrevious ? 'translate-x-5' : 'translate-x-0'}
712
+ `}
713
+ />
714
+ </button>
715
+ </div>
716
+ </div>
717
+
689
718
  {/* Shadow Agent Configuration */}
690
719
  <div className="mb-5 p-4 border border-border rounded-lg bg-bg-hover/50">
691
720
  <div className="flex items-center justify-between mb-3">
@@ -791,40 +820,6 @@ export function SpawnModal({
791
820
  )}
792
821
  </div>
793
822
 
794
- {/* Credentials CTA - shown when provider is not connected */}
795
- {isCloudMode && !hasActiveCredentials && !isLoadingCredentials && selectedTemplate.providerId && (
796
- <div className="p-4 bg-amber-400/10 border border-amber-400/30 rounded-lg mb-5">
797
- <div className="flex items-start gap-3">
798
- <div className="shrink-0 w-8 h-8 rounded-lg bg-amber-400/20 flex items-center justify-center">
799
- <LockIcon />
800
- </div>
801
- <div className="flex-1">
802
- <h4 className="text-sm font-semibold text-amber-400 mb-1">
803
- {selectedTemplate.name} credentials required
804
- </h4>
805
- <p className="text-xs text-text-secondary mb-3">
806
- Connect your {selectedTemplate.name} account to spawn {selectedTemplate.name} agents.
807
- This enables secure access to the AI provider's API.
808
- </p>
809
- <a
810
- href={providerSetupUrl || '#'}
811
- className="inline-flex items-center gap-2 py-2 px-4 bg-amber-400 text-bg-deep font-semibold rounded-md text-sm hover:bg-amber-500 transition-colors"
812
- >
813
- <LockIcon />
814
- Connect {selectedTemplate.name}
815
- </a>
816
- </div>
817
- </div>
818
- </div>
819
- )}
820
-
821
- {/* Loading credentials indicator */}
822
- {isCloudMode && isLoadingCredentials && (
823
- <div className="flex items-center gap-2 p-3 bg-bg-hover rounded-md text-text-muted text-sm mb-5">
824
- <Spinner />
825
- <span>Checking provider credentials...</span>
826
- </div>
827
- )}
828
823
 
829
824
  {/* Error Display */}
830
825
  {displayError && (
@@ -847,8 +842,7 @@ export function SpawnModal({
847
842
  <button
848
843
  type="submit"
849
844
  className="flex items-center gap-1.5 py-2.5 px-4 border-none rounded-md text-sm font-medium cursor-pointer font-sans transition-all duration-150 bg-accent text-white hover:bg-accent-hover disabled:opacity-50 disabled:cursor-not-allowed"
850
- disabled={isSpawning || (isCloudMode && !hasActiveCredentials)}
851
- title={!hasActiveCredentials && isCloudMode ? `Connect ${selectedTemplate.name} credentials first` : undefined}
845
+ disabled={isSpawning}
852
846
  >
853
847
  <RocketIcon />
854
848
  Spawn Agent
@@ -890,23 +884,6 @@ function RocketIcon() {
890
884
  );
891
885
  }
892
886
 
893
- function Spinner() {
894
- return (
895
- <svg className="animate-spin" width="16" height="16" viewBox="0 0 24 24">
896
- <circle
897
- cx="12"
898
- cy="12"
899
- r="10"
900
- stroke="currentColor"
901
- strokeWidth="2"
902
- fill="none"
903
- strokeDasharray="32"
904
- strokeLinecap="round"
905
- />
906
- </svg>
907
- );
908
- }
909
-
910
887
  const SPAWNING_MESSAGES = [
911
888
  'Initializing agent environment...',
912
889
  'Loading model configuration...',
@@ -990,12 +967,3 @@ function SpawningOverlay({ agentName, colors }: { agentName: string; colors: { p
990
967
  </div>
991
968
  );
992
969
  }
993
-
994
- function LockIcon() {
995
- return (
996
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
997
- <rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
998
- <path d="M7 11V7a5 5 0 0 1 10 0v4" />
999
- </svg>
1000
- );
1001
- }
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Used in:
9
9
  * - /providers/setup/[provider] page (full-page setup)
10
- * - WorkspaceSettingsPanel (embedded setup)
10
+ * - Settings workspace slot implementations (embedded setup)
11
11
  */
12
12
 
13
13
  import React, { useRef, useEffect, useCallback, useState } from 'react';
@@ -371,7 +371,9 @@ function ThreadAttachments({ attachments }: ThreadAttachmentsProps) {
371
371
  }
372
372
 
373
373
  function formatTimestamp(timestamp: string | number): string {
374
+ if (!timestamp) return '';
374
375
  const date = new Date(timestamp);
376
+ if (isNaN(date.getTime())) return '';
375
377
  const now = new Date();
376
378
  const isToday = date.toDateString() === now.toDateString();
377
379
 
@@ -3,15 +3,17 @@
3
3
  *
4
4
  * Provides the current workspace's base URL for WebSocket connections.
5
5
  * Used by LogViewer and other components that need to connect to workspace-specific endpoints.
6
+ * Cloud mode is sourced from DashboardConfig instead of runtime hostname/env detection.
6
7
  */
7
8
 
8
9
  import React, { createContext, useContext, useMemo } from 'react';
10
+ import { useDashboardConfig } from '../adapters';
9
11
  import { getWebSocketUrl } from '../lib/config';
10
12
 
11
13
  interface WorkspaceContextValue {
12
14
  /** Base WebSocket URL for the workspace (e.g., wss://workspace-abc.agentrelay.dev) */
13
15
  wsBaseUrl: string | null;
14
- /** Whether we're in cloud mode (workspace URL is different from page host) */
16
+ /** Whether cloud mode is enabled in dashboard configuration */
15
17
  isCloudMode: boolean;
16
18
  }
17
19
 
@@ -40,26 +42,12 @@ function getBaseUrl(wsUrl: string): string {
40
42
  }
41
43
 
42
44
  export function WorkspaceProvider({ children, wsUrl }: WorkspaceProviderProps) {
43
- const value = useMemo(() => {
44
- if (!wsUrl) {
45
- return { wsBaseUrl: null, isCloudMode: false };
46
- }
47
-
48
- const wsBaseUrl = getBaseUrl(wsUrl);
49
-
50
- // Check if we're in cloud mode by comparing the workspace URL host with the current page host
51
- let isCloudMode = false;
52
- if (typeof window !== 'undefined') {
53
- try {
54
- const wsHost = new URL(wsUrl).host;
55
- isCloudMode = wsHost !== window.location.host;
56
- } catch {
57
- // Ignore parse errors
58
- }
59
- }
45
+ const { isCloudMode } = useDashboardConfig();
60
46
 
47
+ const value = useMemo(() => {
48
+ const wsBaseUrl = wsUrl ? getBaseUrl(wsUrl) : null;
61
49
  return { wsBaseUrl, isCloudMode };
62
- }, [wsUrl]);
50
+ }, [wsUrl, isCloudMode]);
63
51
 
64
52
  return (
65
53
  <WorkspaceContext.Provider value={value}>