@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,11 +1,11 @@
1
1
  /**
2
2
  * Channels API Service
3
3
  *
4
- * Channels are handled entirely by the daemon (not cloud).
5
- * Real-time messaging uses the daemon's CHANNEL_* protocol while the HTTP API now reads from daemon storage.
4
+ * Channels are handled entirely by the broker (not cloud).
5
+ * Real-time messaging uses the broker's CHANNEL_* protocol while the HTTP API now reads from broker storage.
6
6
  *
7
7
  * Cloud channels were removed because:
8
- * - Daemon already has full channel protocol support (CHANNEL_JOIN, CHANNEL_MESSAGE, etc.)
8
+ * - Broker already has full channel protocol support (CHANNEL_JOIN, CHANNEL_MESSAGE, etc.)
9
9
  * - Having two parallel implementations caused confusion
10
10
  * - See trajectory traj_fnmapojrllau for architectural decision
11
11
  */
@@ -50,7 +50,7 @@ export class ApiError extends Error {
50
50
  }
51
51
 
52
52
  // =============================================================================
53
- // Channel API Functions - daemon-backed with minimal placeholders
53
+ // Channel API Functions - broker-backed with minimal placeholders
54
54
  // =============================================================================
55
55
 
56
56
  /**
@@ -96,7 +96,7 @@ export async function getChannel(
96
96
  _workspaceId: string,
97
97
  channelId: string
98
98
  ): Promise<GetChannelResponse> {
99
- // Minimal channel details until daemon exposes metadata
99
+ // Minimal channel details until broker exposes metadata
100
100
  return {
101
101
  channel: {
102
102
  id: channelId,
@@ -229,7 +229,7 @@ export async function createChannel(
229
229
  }
230
230
 
231
231
  /**
232
- * Send a message to a channel via daemon API
232
+ * Send a message to a channel via broker API
233
233
  */
234
234
  export async function sendMessage(
235
235
  workspaceId: string,
@@ -249,6 +249,7 @@ export async function sendMessage(
249
249
  channel: channelId,
250
250
  body: request.content,
251
251
  thread: request.threadId,
252
+ attachmentIds: request.attachmentIds,
252
253
  workspaceId,
253
254
  }),
254
255
  });
@@ -279,7 +280,7 @@ export async function sendMessage(
279
280
  }
280
281
 
281
282
  /**
282
- * Join a channel via daemon API
283
+ * Join a channel via broker API
283
284
  */
284
285
  export async function joinChannel(
285
286
  workspaceId: string,
@@ -320,7 +321,7 @@ export async function joinChannel(
320
321
  }
321
322
 
322
323
  /**
323
- * Leave a channel via daemon API
324
+ * Leave a channel via broker API
324
325
  */
325
326
  export async function leaveChannel(
326
327
  workspaceId: string,
@@ -416,7 +417,7 @@ export async function deleteChannel(
416
417
  _workspaceId: string,
417
418
  _channelId: string
418
419
  ): Promise<void> {
419
- // Daemon deletes automatically when empty; nothing to do client-side
420
+ // Broker deletes automatically when empty; nothing to do client-side
420
421
  return;
421
422
  }
422
423
 
@@ -428,31 +429,31 @@ export async function markRead(
428
429
  _channelId: string,
429
430
  _upToMessageId?: string
430
431
  ): Promise<void> {
431
- // TODO: add mark-read to daemon; no-op for now
432
+ // TODO: add mark-read to broker; no-op for now
432
433
  return;
433
434
  }
434
435
 
435
436
  /**
436
- * Pin a message (no-op in daemon mode)
437
+ * Pin a message (no-op in broker mode)
437
438
  */
438
439
  export async function pinMessage(
439
440
  _workspaceId: string,
440
441
  _channelId: string,
441
442
  _messageId: string
442
443
  ): Promise<void> {
443
- // Pinning not supported in daemon mode
444
+ // Pinning not supported in broker mode
444
445
  return;
445
446
  }
446
447
 
447
448
  /**
448
- * Unpin a message (no-op in daemon mode)
449
+ * Unpin a message (no-op in broker mode)
449
450
  */
450
451
  export async function unpinMessage(
451
452
  _workspaceId: string,
452
453
  _channelId: string,
453
454
  _messageId: string
454
455
  ): Promise<void> {
455
- // Unpinning not supported in daemon mode
456
+ // Unpinning not supported in broker mode
456
457
  return;
457
458
  }
458
459
 
@@ -478,7 +479,7 @@ export interface AvailableMember {
478
479
 
479
480
  /**
480
481
  * Get available members for channel invites
481
- * Returns workspace members (humans) and agents from linked daemons
482
+ * Returns workspace members (humans) and agents from linked brokers
482
483
  */
483
484
  export async function getAvailableMembers(
484
485
  workspaceId?: string
@@ -519,14 +520,14 @@ export async function getAvailableMembers(
519
520
  // =============================================================================
520
521
 
521
522
  /**
522
- * Search messages (returns empty in daemon mode - search via relay)
523
+ * Search messages (returns empty in broker mode - search via relay)
523
524
  */
524
525
  export async function searchMessages(
525
526
  _workspaceId: string,
526
527
  query: string,
527
528
  _options?: { channelId?: string; limit?: number; offset?: number }
528
529
  ): Promise<SearchResponse> {
529
- // Search not implemented in daemon mode
530
+ // Search not implemented in broker mode
530
531
  return {
531
532
  results: [],
532
533
  total: 0,
@@ -685,17 +686,17 @@ export async function getChannelMembers(
685
686
  // =============================================================================
686
687
 
687
688
  /**
688
- * Always returns true - channels now only use daemon/relay
689
+ * Always returns true - channels now only use broker/relay
689
690
  */
690
691
  export function isRealApiEnabled(): boolean {
691
692
  return true;
692
693
  }
693
694
 
694
695
  /**
695
- * No-op - API mode is fixed to daemon/local
696
+ * No-op - API mode is fixed to broker/local
696
697
  */
697
698
  export function setApiMode(_useReal: boolean): void {
698
- console.log('[ChannelsAPI] Mode is fixed to daemon-based implementation');
699
+ console.log('[ChannelsAPI] Mode is fixed to broker-based implementation');
699
700
  }
700
701
 
701
702
  export function getApiMode(): 'real' | 'mock' {
@@ -6,6 +6,9 @@
6
6
  */
7
7
 
8
8
  import type { ThreadMetadata } from '../../types/threading';
9
+ import type { Agent } from '../../types';
10
+ import type { HumanUser } from '../MentionAutocomplete';
11
+ import type { UserPresence } from '../hooks/usePresence';
9
12
 
10
13
  /**
11
14
  * Channel visibility types.
@@ -296,6 +299,17 @@ export interface ChannelMessageListProps {
296
299
  unreadState?: UnreadState;
297
300
  /** Current user name */
298
301
  currentUser: string;
302
+ /** Current user info (for avatar fallback) */
303
+ currentUserInfo?: {
304
+ displayName: string;
305
+ avatarUrl?: string;
306
+ };
307
+ /** Online users for avatar fallback */
308
+ onlineUsers?: UserPresence[];
309
+ /** Agents for avatar fallback */
310
+ agents?: Agent[];
311
+ /** Known human users for avatar fallback */
312
+ humanUsers?: HumanUser[];
299
313
  /** Whether loading more messages */
300
314
  isLoadingMore?: boolean;
301
315
  /** Whether there are more messages to load */
@@ -306,6 +320,8 @@ export interface ChannelMessageListProps {
306
320
  onThreadClick?: (messageId: string) => void;
307
321
  /** Callback when clicking on a member name (for DM navigation) */
308
322
  onMemberClick?: (memberId: string, entityType: 'user' | 'agent') => void;
323
+ /** Callback when toggling a reaction on a message */
324
+ onReaction?: (messageId: string, emoji: string, hasReacted: boolean) => void;
309
325
  }
310
326
 
311
327
  /**
@@ -13,13 +13,6 @@ export {
13
13
  type OrchestratorEvent,
14
14
  } from './useOrchestrator';
15
15
  export { useAgentLogs, type UseAgentLogsOptions, type UseAgentLogsReturn, type LogLine, type LogConnectionState } from './useAgentLogs';
16
- export {
17
- useSession,
18
- type UseSessionOptions,
19
- type UseSessionReturn,
20
- type SessionError,
21
- type CloudUser,
22
- } from './useSession';
23
16
  export { useTrajectory } from './useTrajectory';
24
17
  export {
25
18
  useRecentRepos,
@@ -27,12 +20,6 @@ export {
27
20
  type UseRecentReposReturn,
28
21
  type RecentRepo,
29
22
  } from './useRecentRepos';
30
- export {
31
- useWorkspaceStatus,
32
- type UseWorkspaceStatusOptions,
33
- type UseWorkspaceStatusReturn,
34
- type WorkspaceStatus,
35
- } from './useWorkspaceStatus';
36
23
  export {
37
24
  useWorkspaceRepos,
38
25
  type UseWorkspaceReposOptions,
@@ -46,12 +33,6 @@ export {
46
33
  type ChannelMessage,
47
34
  type ChannelConnectionState,
48
35
  } from './useChannels';
49
- export {
50
- useWorkspaceMembers,
51
- filterOnlineUsersByWorkspace,
52
- type UseWorkspaceMembersOptions,
53
- type UseWorkspaceMembersReturn,
54
- } from './useWorkspaceMembers';
55
36
  export {
56
37
  usePinnedAgents,
57
38
  type UsePinnedAgentsReturn,
@@ -0,0 +1,80 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
3
+ import { renderHook, act } from '@testing-library/react';
4
+ import { useMessages } from './useMessages';
5
+
6
+ const mockSendMessage = vi.fn();
7
+
8
+ vi.mock('../../lib/api', () => ({
9
+ api: {
10
+ sendMessage: (...args: unknown[]) => mockSendMessage(...args),
11
+ },
12
+ }));
13
+
14
+ describe('useMessages', () => {
15
+ beforeEach(() => {
16
+ mockSendMessage.mockReset();
17
+ });
18
+
19
+ it('keeps optimistic messages in sending status after successful send', async () => {
20
+ mockSendMessage.mockResolvedValue({ success: true });
21
+ const baseMessages: Parameters<typeof useMessages>[0]['messages'] = [];
22
+
23
+ const { result } = renderHook(() =>
24
+ useMessages({
25
+ messages: baseMessages,
26
+ senderName: 'Dashboard',
27
+ })
28
+ );
29
+
30
+ let ok = false;
31
+ await act(async () => {
32
+ ok = await result.current.sendMessage('Lead', 'hello world');
33
+ });
34
+
35
+ expect(ok).toBe(true);
36
+ expect(result.current.messages).toHaveLength(1);
37
+ expect(result.current.messages[0]?.status).toBe('sending');
38
+ expect(result.current.messages[0]?.to).toBe('Lead');
39
+ });
40
+
41
+ it('uses canonical message id from send response when available', async () => {
42
+ mockSendMessage.mockResolvedValue({ success: true, data: { messageId: 'evt_123' } });
43
+ const baseMessages: Parameters<typeof useMessages>[0]['messages'] = [];
44
+
45
+ const { result } = renderHook(() =>
46
+ useMessages({
47
+ messages: baseMessages,
48
+ senderName: 'Dashboard',
49
+ })
50
+ );
51
+
52
+ await act(async () => {
53
+ await result.current.sendMessage('Lead', 'hello world');
54
+ });
55
+
56
+ expect(result.current.messages[0]?.id).toBe('evt_123');
57
+ expect(result.current.messages[0]?.status).toBe('sending');
58
+ });
59
+
60
+ it('removes optimistic message and surfaces error on failed send', async () => {
61
+ mockSendMessage.mockResolvedValue({ success: false, error: 'send failed' });
62
+ const baseMessages: Parameters<typeof useMessages>[0]['messages'] = [];
63
+
64
+ const { result } = renderHook(() =>
65
+ useMessages({
66
+ messages: baseMessages,
67
+ senderName: 'Dashboard',
68
+ })
69
+ );
70
+
71
+ let ok = true;
72
+ await act(async () => {
73
+ ok = await result.current.sendMessage('Lead', 'hello world');
74
+ });
75
+
76
+ expect(ok).toBe(false);
77
+ expect(result.current.messages).toHaveLength(0);
78
+ expect(result.current.sendError).toBe('send failed');
79
+ });
80
+ });
@@ -64,8 +64,11 @@ export function useMessages({
64
64
  // This allows us to show new messages that arrive after viewing
65
65
  const [seenThreads, setSeenThreads] = useState<Map<string, number>>(new Map());
66
66
 
67
- // Effective sender name for the current user (used for filtering own messages)
68
- const effectiveSenderName = senderName || 'Dashboard';
67
+ // Effective sender name for the current user (used for filtering own messages).
68
+ // Read localStorage directly as a fallback so we never display "Dashboard".
69
+ const effectiveSenderName = senderName
70
+ || (typeof window !== 'undefined' ? localStorage.getItem('relay_username') : null)
71
+ || 'You';
69
72
 
70
73
  // Optimistic messages: shown immediately before server confirms
71
74
  // These have status='sending' and a temp ID prefixed with 'optimistic-'
@@ -272,8 +275,14 @@ export function useMessages({
272
275
  const result = await api.sendMessage(request);
273
276
 
274
277
  if (result.success) {
275
- // Success! The optimistic message will be cleaned up when
276
- // the real message arrives via WebSocket
278
+ // If the server returned a canonical message ID, update the optimistic
279
+ // message so dedup logic can match it when the real event arrives.
280
+ const canonicalId = result.data?.messageId;
281
+ if (canonicalId) {
282
+ setOptimisticMessages((prev) =>
283
+ prev.map((m) => (m.id === optimisticId ? { ...m, id: canonicalId } : m)),
284
+ );
285
+ }
277
286
  return true;
278
287
  }
279
288
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * useOrchestrator Hook
3
3
  *
4
- * Connects to the daemon orchestrator for workspace and agent management.
4
+ * Connects to the broker orchestrator for workspace and agent management.
5
5
  * Provides real-time updates via WebSocket.
6
6
  */
7
7
 
@@ -7,8 +7,10 @@
7
7
  * - Handles user presence announcements
8
8
  */
9
9
 
10
- import { useState, useEffect, useCallback, useRef } from 'react';
10
+ import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
11
11
  import { getWebSocketUrl } from '../../lib/config';
12
+ import { usePresence as useRelayPresence } from '@relaycast/react';
13
+ import { useRelayConfigStatus } from '../../providers/RelayConfigProvider';
12
14
 
13
15
  /** User presence information */
14
16
  export interface UserPresence {
@@ -75,6 +77,9 @@ function getPresenceUrl(): string {
75
77
 
76
78
  export function usePresence(options: UsePresenceOptions = {}): UsePresenceReturn {
77
79
  const { currentUser, wsUrl, autoConnect = true, onEvent, workspaceId } = options;
80
+ const relayPresenceState = useRelayPresence();
81
+ const { configured: relayConfigured } = useRelayConfigStatus();
82
+ const usingRelayPresence = relayConfigured;
78
83
 
79
84
  const [onlineUsers, setOnlineUsers] = useState<UserPresence[]>([]);
80
85
  const [typingUsers, setTypingUsers] = useState<TypingIndicator[]>([]);
@@ -94,6 +99,16 @@ export function usePresence(options: UsePresenceOptions = {}): UsePresenceReturn
94
99
  const onEventRef = useRef(onEvent);
95
100
  onEventRef.current = onEvent; // Keep ref in sync with callback prop
96
101
 
102
+ const relayOnlineUsers = useMemo<UserPresence[]>(() => {
103
+ return relayPresenceState.agents
104
+ .filter((agent) => agent.status === 'online' && agent.type === 'human')
105
+ .map((agent) => ({
106
+ username: agent.name,
107
+ connectedAt: agent.createdAt ?? agent.lastSeen,
108
+ lastSeen: agent.lastSeen,
109
+ }));
110
+ }, [relayPresenceState]);
111
+
97
112
  // Clear stale typing indicators (after 3 seconds of no update)
98
113
  useEffect(() => {
99
114
  const interval = setInterval(() => {
@@ -107,6 +122,7 @@ export function usePresence(options: UsePresenceOptions = {}): UsePresenceReturn
107
122
  }, []);
108
123
 
109
124
  const connect = useCallback(() => {
125
+ if (usingRelayPresence) return;
110
126
  const user = currentUserRef.current;
111
127
  if (!user) return; // Don't connect without user info
112
128
  if (wsRef.current?.readyState === WebSocket.OPEN) return;
@@ -251,7 +267,7 @@ export function usePresence(options: UsePresenceOptions = {}): UsePresenceReturn
251
267
  } catch (e) {
252
268
  console.error('[usePresence] Failed to create WebSocket:', e);
253
269
  }
254
- }, [wsUrl]); // Use ref for currentUser to avoid dependency
270
+ }, [wsUrl, usingRelayPresence]); // Use refs for currentUser/workspace/onEvent to avoid dependencies
255
271
 
256
272
  const disconnect = useCallback(() => {
257
273
  // Clear reconnect timeout first
@@ -288,6 +304,7 @@ export function usePresence(options: UsePresenceOptions = {}): UsePresenceReturn
288
304
 
289
305
  // Send typing indicator
290
306
  const sendTyping = useCallback((isTyping: boolean) => {
307
+ if (usingRelayPresence) return;
291
308
  if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return;
292
309
  const user = currentUserRef.current;
293
310
  if (!user) return;
@@ -312,10 +329,11 @@ export function usePresence(options: UsePresenceOptions = {}): UsePresenceReturn
312
329
  sendTyping(false);
313
330
  }, 3000);
314
331
  }
315
- }, []); // Use ref for currentUser to avoid dependency
332
+ }, [usingRelayPresence]); // Use ref for currentUser to avoid dependency
316
333
 
317
334
  // Connect when user is available
318
335
  useEffect(() => {
336
+ if (usingRelayPresence) return;
319
337
  if (!autoConnect || !currentUserRef.current) return;
320
338
 
321
339
  // Prevent connecting if already connected or connecting
@@ -329,10 +347,11 @@ export function usePresence(options: UsePresenceOptions = {}): UsePresenceReturn
329
347
  disconnect();
330
348
  };
331
349
  // Callbacks are now stable (use refs internally), so only need to depend on user identity
332
- }, [autoConnect, currentUser?.username, connect, disconnect]);
350
+ }, [autoConnect, currentUser?.username, connect, disconnect, usingRelayPresence]);
333
351
 
334
352
  // Send leave on page unload
335
353
  useEffect(() => {
354
+ if (usingRelayPresence) return;
336
355
  const handleUnload = () => {
337
356
  const user = currentUserRef.current;
338
357
  if (wsRef.current?.readyState === WebSocket.OPEN && user) {
@@ -346,10 +365,11 @@ export function usePresence(options: UsePresenceOptions = {}): UsePresenceReturn
346
365
 
347
366
  window.addEventListener('beforeunload', handleUnload);
348
367
  return () => window.removeEventListener('beforeunload', handleUnload);
349
- }, []); // Use ref for currentUser to avoid dependency
368
+ }, [usingRelayPresence]); // Use ref for currentUser to avoid dependency
350
369
 
351
370
  // Visibility change listener: reconnect when tab becomes visible
352
371
  useEffect(() => {
372
+ if (usingRelayPresence) return;
353
373
  const handleVisibilityChange = () => {
354
374
  if (document.visibilityState === 'visible') {
355
375
  // Check if connection is dead and reconnect
@@ -365,7 +385,26 @@ export function usePresence(options: UsePresenceOptions = {}): UsePresenceReturn
365
385
  return () => {
366
386
  document.removeEventListener('visibilitychange', handleVisibilityChange);
367
387
  };
368
- }, [connect]);
388
+ }, [connect, usingRelayPresence]);
389
+
390
+ // If Relay SDK presence is active, ensure any legacy presence socket is closed.
391
+ useEffect(() => {
392
+ if (!usingRelayPresence) return;
393
+ disconnect();
394
+ }, [disconnect, usingRelayPresence]);
395
+
396
+ if (usingRelayPresence) {
397
+ const relayConnected = !relayPresenceState.loading && !relayPresenceState.error;
398
+ return {
399
+ onlineUsers: relayOnlineUsers,
400
+ typingUsers: [],
401
+ sendTyping: () => undefined,
402
+ isConnected: relayConnected,
403
+ connectionState: relayPresenceState.loading
404
+ ? 'reconnecting'
405
+ : (relayPresenceState.error ? 'disconnected' : 'connected'),
406
+ };
407
+ }
369
408
 
370
409
  return {
371
410
  onlineUsers,