@agent-relay/dashboard-server 2.0.83-beta.0 → 2.0.85

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 (397) hide show
  1. package/dist/index.d.ts +3 -21
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +3 -23
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/attachment-storage.d.ts +10 -0
  6. package/dist/lib/attachment-storage.d.ts.map +1 -0
  7. package/dist/lib/attachment-storage.js +53 -0
  8. package/dist/lib/attachment-storage.js.map +1 -0
  9. package/dist/lib/broadcast.d.ts +18 -0
  10. package/dist/lib/broadcast.d.ts.map +1 -0
  11. package/dist/lib/broadcast.js +118 -0
  12. package/dist/lib/broadcast.js.map +1 -0
  13. package/dist/lib/channel-state.d.ts +32 -0
  14. package/dist/lib/channel-state.d.ts.map +1 -0
  15. package/dist/lib/channel-state.js +146 -0
  16. package/dist/lib/channel-state.js.map +1 -0
  17. package/dist/lib/cli-auth.d.ts +40 -0
  18. package/dist/lib/cli-auth.d.ts.map +1 -0
  19. package/dist/lib/cli-auth.js +144 -0
  20. package/dist/lib/cli-auth.js.map +1 -0
  21. package/dist/lib/cloud-persistence.d.ts +6 -0
  22. package/dist/lib/cloud-persistence.d.ts.map +1 -0
  23. package/dist/lib/cloud-persistence.js +130 -0
  24. package/dist/lib/cloud-persistence.js.map +1 -0
  25. package/dist/lib/data-assembly.d.ts +136 -0
  26. package/dist/lib/data-assembly.d.ts.map +1 -0
  27. package/dist/lib/data-assembly.js +550 -0
  28. package/dist/lib/data-assembly.js.map +1 -0
  29. package/dist/lib/file-search.d.ts +8 -0
  30. package/dist/lib/file-search.d.ts.map +1 -0
  31. package/dist/lib/file-search.js +90 -0
  32. package/dist/lib/file-search.js.map +1 -0
  33. package/dist/lib/identity.d.ts +54 -0
  34. package/dist/lib/identity.d.ts.map +1 -0
  35. package/dist/lib/identity.js +124 -0
  36. package/dist/lib/identity.js.map +1 -0
  37. package/dist/lib/log-line-cleaner.d.ts +20 -0
  38. package/dist/lib/log-line-cleaner.d.ts.map +1 -0
  39. package/dist/lib/log-line-cleaner.js +117 -0
  40. package/dist/lib/log-line-cleaner.js.map +1 -0
  41. package/dist/lib/log-reader.d.ts +14 -0
  42. package/dist/lib/log-reader.d.ts.map +1 -0
  43. package/dist/lib/log-reader.js +80 -0
  44. package/dist/lib/log-reader.js.map +1 -0
  45. package/dist/lib/message-id.d.ts +30 -0
  46. package/dist/lib/message-id.d.ts.map +1 -0
  47. package/dist/lib/message-id.js +50 -0
  48. package/dist/lib/message-id.js.map +1 -0
  49. package/dist/lib/process-metrics.d.ts +11 -0
  50. package/dist/lib/process-metrics.d.ts.map +1 -0
  51. package/dist/lib/process-metrics.js +252 -0
  52. package/dist/lib/process-metrics.js.map +1 -0
  53. package/dist/lib/proxy-route-table.d.ts +16 -0
  54. package/dist/lib/proxy-route-table.d.ts.map +1 -0
  55. package/dist/lib/proxy-route-table.js +73 -0
  56. package/dist/lib/proxy-route-table.js.map +1 -0
  57. package/dist/lib/send-strategy.d.ts +62 -0
  58. package/dist/lib/send-strategy.d.ts.map +1 -0
  59. package/dist/lib/send-strategy.js +136 -0
  60. package/dist/lib/send-strategy.js.map +1 -0
  61. package/dist/lib/server-state.d.ts +115 -0
  62. package/dist/lib/server-state.d.ts.map +1 -0
  63. package/dist/lib/server-state.js +138 -0
  64. package/dist/lib/server-state.js.map +1 -0
  65. package/dist/lib/spawned-agents.d.ts +35 -0
  66. package/dist/lib/spawned-agents.d.ts.map +1 -0
  67. package/dist/lib/spawned-agents.js +340 -0
  68. package/dist/lib/spawned-agents.js.map +1 -0
  69. package/dist/lib/types.d.ts +123 -0
  70. package/dist/lib/types.d.ts.map +1 -0
  71. package/dist/lib/types.js +12 -0
  72. package/dist/lib/types.js.map +1 -0
  73. package/dist/lib/utils.d.ts +71 -0
  74. package/dist/lib/utils.d.ts.map +1 -0
  75. package/dist/lib/utils.js +332 -0
  76. package/dist/lib/utils.js.map +1 -0
  77. package/dist/lib/websocket-runtime.d.ts +17 -0
  78. package/dist/lib/websocket-runtime.d.ts.map +1 -0
  79. package/dist/lib/websocket-runtime.js +76 -0
  80. package/dist/lib/websocket-runtime.js.map +1 -0
  81. package/dist/mocks/fixtures.d.ts +2 -2
  82. package/dist/mocks/fixtures.js +1 -1
  83. package/dist/mocks/routes.d.ts.map +1 -1
  84. package/dist/mocks/routes.js +30 -3
  85. package/dist/mocks/routes.js.map +1 -1
  86. package/dist/mocks/types.d.ts +1 -1
  87. package/dist/mocks/types.js +1 -1
  88. package/dist/proxy-server.d.ts +7 -33
  89. package/dist/proxy-server.d.ts.map +1 -1
  90. package/dist/proxy-server.js +353 -201
  91. package/dist/proxy-server.js.map +1 -1
  92. package/dist/relaycast-provider-helpers.d.ts +41 -0
  93. package/dist/relaycast-provider-helpers.d.ts.map +1 -0
  94. package/dist/relaycast-provider-helpers.js +285 -0
  95. package/dist/relaycast-provider-helpers.js.map +1 -0
  96. package/dist/relaycast-provider-types.d.ts +117 -0
  97. package/dist/relaycast-provider-types.d.ts.map +1 -0
  98. package/dist/relaycast-provider-types.js +6 -0
  99. package/dist/relaycast-provider-types.js.map +1 -0
  100. package/dist/relaycast-provider.d.ts +48 -0
  101. package/dist/relaycast-provider.d.ts.map +1 -0
  102. package/dist/relaycast-provider.js +293 -0
  103. package/dist/relaycast-provider.js.map +1 -0
  104. package/dist/routes/agents.d.ts +7 -0
  105. package/dist/routes/agents.d.ts.map +1 -0
  106. package/dist/routes/agents.js +180 -0
  107. package/dist/routes/agents.js.map +1 -0
  108. package/dist/routes/auth.d.ts +45 -0
  109. package/dist/routes/auth.d.ts.map +1 -0
  110. package/dist/routes/auth.js +261 -0
  111. package/dist/routes/auth.js.map +1 -0
  112. package/dist/routes/broker-proxy.d.ts +7 -0
  113. package/dist/routes/broker-proxy.d.ts.map +1 -0
  114. package/dist/routes/broker-proxy.js +114 -0
  115. package/dist/routes/broker-proxy.js.map +1 -0
  116. package/dist/routes/channels-integrated.d.ts +84 -0
  117. package/dist/routes/channels-integrated.d.ts.map +1 -0
  118. package/dist/routes/channels-integrated.js +644 -0
  119. package/dist/routes/channels-integrated.js.map +1 -0
  120. package/dist/routes/channels.d.ts +7 -0
  121. package/dist/routes/channels.d.ts.map +1 -0
  122. package/dist/routes/channels.js +457 -0
  123. package/dist/routes/channels.js.map +1 -0
  124. package/dist/routes/data.d.ts +7 -0
  125. package/dist/routes/data.d.ts.map +1 -0
  126. package/dist/routes/data.js +108 -0
  127. package/dist/routes/data.js.map +1 -0
  128. package/dist/routes/decisions.d.ts +31 -0
  129. package/dist/routes/decisions.d.ts.map +1 -0
  130. package/dist/routes/decisions.js +109 -0
  131. package/dist/routes/decisions.js.map +1 -0
  132. package/dist/routes/fleet.d.ts +24 -0
  133. package/dist/routes/fleet.d.ts.map +1 -0
  134. package/dist/routes/fleet.js +131 -0
  135. package/dist/routes/fleet.js.map +1 -0
  136. package/dist/routes/health.d.ts +7 -0
  137. package/dist/routes/health.d.ts.map +1 -0
  138. package/dist/routes/health.js +55 -0
  139. package/dist/routes/health.js.map +1 -0
  140. package/dist/routes/history-relaycast.d.ts +8 -0
  141. package/dist/routes/history-relaycast.d.ts.map +1 -0
  142. package/dist/routes/history-relaycast.js +165 -0
  143. package/dist/routes/history-relaycast.js.map +1 -0
  144. package/dist/routes/history.d.ts +13 -0
  145. package/dist/routes/history.d.ts.map +1 -0
  146. package/dist/routes/history.js +205 -0
  147. package/dist/routes/history.js.map +1 -0
  148. package/dist/routes/messaging.d.ts +34 -0
  149. package/dist/routes/messaging.d.ts.map +1 -0
  150. package/dist/routes/messaging.js +306 -0
  151. package/dist/routes/messaging.js.map +1 -0
  152. package/dist/routes/metrics.d.ts +26 -0
  153. package/dist/routes/metrics.d.ts.map +1 -0
  154. package/dist/routes/metrics.js +276 -0
  155. package/dist/routes/metrics.js.map +1 -0
  156. package/dist/routes/reactions.d.ts +9 -0
  157. package/dist/routes/reactions.d.ts.map +1 -0
  158. package/dist/routes/reactions.js +76 -0
  159. package/dist/routes/reactions.js.map +1 -0
  160. package/dist/routes/relay-config.d.ts +4 -0
  161. package/dist/routes/relay-config.d.ts.map +1 -0
  162. package/dist/routes/relay-config.js +81 -0
  163. package/dist/routes/relay-config.js.map +1 -0
  164. package/dist/routes/settings.d.ts +6 -0
  165. package/dist/routes/settings.d.ts.map +1 -0
  166. package/dist/routes/settings.js +119 -0
  167. package/dist/routes/settings.js.map +1 -0
  168. package/dist/routes/spawn.d.ts +74 -0
  169. package/dist/routes/spawn.d.ts.map +1 -0
  170. package/dist/routes/spawn.js +520 -0
  171. package/dist/routes/spawn.js.map +1 -0
  172. package/dist/routes/system.d.ts +21 -0
  173. package/dist/routes/system.d.ts.map +1 -0
  174. package/dist/routes/system.js +186 -0
  175. package/dist/routes/system.js.map +1 -0
  176. package/dist/routes/tasks.d.ts +27 -0
  177. package/dist/routes/tasks.d.ts.map +1 -0
  178. package/dist/routes/tasks.js +103 -0
  179. package/dist/routes/tasks.js.map +1 -0
  180. package/dist/routes/thread-replies.d.ts +7 -0
  181. package/dist/routes/thread-replies.d.ts.map +1 -0
  182. package/dist/routes/thread-replies.js +178 -0
  183. package/dist/routes/thread-replies.js.map +1 -0
  184. package/dist/routes/ui.d.ts +6 -0
  185. package/dist/routes/ui.d.ts.map +1 -0
  186. package/dist/routes/ui.js +114 -0
  187. package/dist/routes/ui.js.map +1 -0
  188. package/dist/server.d.ts.map +1 -1
  189. package/dist/server.js +304 -6152
  190. package/dist/server.js.map +1 -1
  191. package/dist/services/broker-spawn-reader.d.ts +1 -1
  192. package/dist/services/broker-spawn-reader.d.ts.map +1 -1
  193. package/dist/services/broker-spawn-reader.js +3 -2
  194. package/dist/services/broker-spawn-reader.js.map +1 -1
  195. package/dist/services/health-worker-manager.d.ts +9 -60
  196. package/dist/services/health-worker-manager.d.ts.map +1 -1
  197. package/dist/services/health-worker-manager.js +18 -148
  198. package/dist/services/health-worker-manager.js.map +1 -1
  199. package/dist/services/index.d.ts +3 -3
  200. package/dist/services/index.d.ts.map +1 -1
  201. package/dist/services/index.js +3 -3
  202. package/dist/services/index.js.map +1 -1
  203. package/dist/services/metrics.d.ts +11 -104
  204. package/dist/services/metrics.d.ts.map +1 -1
  205. package/dist/services/metrics.js +21 -190
  206. package/dist/services/metrics.js.map +1 -1
  207. package/dist/services/needs-attention.d.ts +21 -22
  208. package/dist/services/needs-attention.d.ts.map +1 -1
  209. package/dist/services/needs-attention.js +46 -71
  210. package/dist/services/needs-attention.js.map +1 -1
  211. package/dist/services/user-bridge.d.ts +0 -3
  212. package/dist/services/user-bridge.d.ts.map +1 -1
  213. package/dist/services/user-bridge.js +0 -5
  214. package/dist/services/user-bridge.js.map +1 -1
  215. package/dist/start.d.ts +4 -4
  216. package/dist/start.js +97 -89
  217. package/dist/start.js.map +1 -1
  218. package/dist/types/index.d.ts +11 -21
  219. package/dist/types/index.d.ts.map +1 -1
  220. package/dist/websocket/bridge.d.ts +12 -0
  221. package/dist/websocket/bridge.d.ts.map +1 -0
  222. package/dist/websocket/bridge.js +33 -0
  223. package/dist/websocket/bridge.js.map +1 -0
  224. package/dist/websocket/logs.d.ts +30 -0
  225. package/dist/websocket/logs.d.ts.map +1 -0
  226. package/dist/websocket/logs.js +577 -0
  227. package/dist/websocket/logs.js.map +1 -0
  228. package/dist/websocket/main.d.ts +15 -0
  229. package/dist/websocket/main.d.ts.map +1 -0
  230. package/dist/websocket/main.js +84 -0
  231. package/dist/websocket/main.js.map +1 -0
  232. package/dist/websocket/mock.d.ts +6 -0
  233. package/dist/websocket/mock.d.ts.map +1 -0
  234. package/dist/websocket/mock.js +49 -0
  235. package/dist/websocket/mock.js.map +1 -0
  236. package/dist/websocket/presence.d.ts +74 -0
  237. package/dist/websocket/presence.d.ts.map +1 -0
  238. package/dist/websocket/presence.js +330 -0
  239. package/dist/websocket/presence.js.map +1 -0
  240. package/dist/websocket/proxy.d.ts +6 -0
  241. package/dist/websocket/proxy.d.ts.map +1 -0
  242. package/dist/websocket/proxy.js +39 -0
  243. package/dist/websocket/proxy.js.map +1 -0
  244. package/dist/websocket/standalone.d.ts +17 -0
  245. package/dist/websocket/standalone.d.ts.map +1 -0
  246. package/dist/websocket/standalone.js +268 -0
  247. package/dist/websocket/standalone.js.map +1 -0
  248. package/out/404.html +1 -1
  249. package/out/_next/static/{TnAI-TAQ4-bNJRL3Ln3NB → 49XhcS8k_e8607S7KBMwA}/_buildManifest.js +1 -1
  250. package/out/_next/static/chunks/1028-53c5a7e2453505f8.js +1 -0
  251. package/out/_next/static/chunks/1528-78b17000a7e10bc6.js +2 -0
  252. package/out/_next/static/chunks/1695-4a5d33ba715e09b4.js +1 -0
  253. package/out/_next/static/chunks/1705-36c2180d00a4a569.js +1 -0
  254. package/out/_next/static/chunks/1dd3208c-e1f87c7b3dc1a820.js +1 -0
  255. package/out/_next/static/chunks/3663-47290254b8f6f5dd.js +1 -0
  256. package/out/_next/static/chunks/3677-4b225baf4801d9b9.js +73 -0
  257. package/out/_next/static/chunks/5118-de1c8f968feab8e2.js +1 -0
  258. package/out/_next/static/chunks/5888-15cbe97c90ed5fae.js +1 -0
  259. package/out/_next/static/chunks/6773-a45343a98df3abb5.js +1 -0
  260. package/out/_next/static/chunks/6940-b824612b605e79b3.js +9 -0
  261. package/out/_next/static/chunks/7894-f4a15249082a680d.js +1 -0
  262. package/out/_next/static/chunks/9175-b3617c1e5cbfed0e.js +1 -0
  263. package/out/_next/static/chunks/9372-1a804b8d08c7a236.js +1 -0
  264. package/out/_next/static/chunks/{ab6c8a12-0a58072fbb505134.js → ab6c8a12-91438a812d94ecf0.js} +1 -1
  265. package/out/_next/static/chunks/app/_not-found/page-8e8842f82d204726.js +1 -0
  266. package/out/_next/static/chunks/app/about/page-b78577a7da8fa459.js +1 -0
  267. package/out/_next/static/chunks/app/app/[[...slug]]/page-3dffd65b6344f53e.js +1 -0
  268. package/out/_next/static/chunks/app/app/onboarding/page-b89be9aa6264a5e1.js +1 -0
  269. package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-fbd00893ef69e499.js +1 -0
  270. package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-de2ea13649d0b6d3.js +1 -0
  271. package/out/_next/static/chunks/app/blog/page-a08e263c57a156fa.js +1 -0
  272. package/out/_next/static/chunks/app/careers/page-02228e1d6969b232.js +1 -0
  273. package/out/_next/static/chunks/app/changelog/page-1b5c1d79efc6e53a.js +1 -0
  274. package/out/_next/static/chunks/app/cloud/link/page-99654edffffb3af2.js +1 -0
  275. package/out/_next/static/chunks/app/complete-profile/page-59d146e5ddeafc5c.js +1 -0
  276. package/out/_next/static/chunks/app/connect-repos/page-995e16a976a6632c.js +1 -0
  277. package/out/_next/static/chunks/app/contact/page-273396a5ad57bcee.js +1 -0
  278. package/out/_next/static/chunks/app/dev/cli-tools/page-a71b80dcb2d5fc8d.js +1 -0
  279. package/out/_next/static/chunks/app/dev/log-viewer/page-46a6151ae1be0796.js +1 -0
  280. package/out/_next/static/chunks/app/docs/page-7c7cb603b24b7c40.js +1 -0
  281. package/out/_next/static/chunks/app/history/page-0c5cab1dab4e8886.js +1 -0
  282. package/out/_next/static/chunks/app/layout-96d72ba8ef8a43a0.js +1 -0
  283. package/out/_next/static/chunks/app/login/page-0ccbab34213df842.js +1 -0
  284. package/out/_next/static/chunks/app/metrics/page-8616272aeab9c8b0.js +1 -0
  285. package/out/_next/static/chunks/app/page-09ce10603ad9a251.js +1 -0
  286. package/out/_next/static/chunks/app/pricing/page-91c975079120c941.js +1 -0
  287. package/out/_next/static/chunks/app/privacy/{page-c21d51ac2dee3a88.js → page-a49ab271cc686644.js} +1 -1
  288. package/out/_next/static/chunks/app/providers/{page-59114505f4353512.js → page-d775d6eb5bc29e96.js} +1 -1
  289. package/out/_next/static/chunks/app/providers/setup/[provider]/page-ec4ef3cd80de807e.js +1 -0
  290. package/out/_next/static/chunks/app/security/page-d9da9bd9191e8f95.js +1 -0
  291. package/out/_next/static/chunks/app/signup/page-930eca0bf5fd299d.js +1 -0
  292. package/out/_next/static/chunks/app/terms/page-3e4827620b98613c.js +1 -0
  293. package/out/_next/static/chunks/framework-648e1ae7da590300.js +1 -0
  294. package/out/_next/static/chunks/{main-acb1b24265295d6a.js → main-2b1990080c292d92.js} +1 -1
  295. package/out/_next/static/chunks/main-app-9f6b7ff9e754a8f5.js +1 -0
  296. package/out/_next/static/chunks/pages/_app-a077b72e02273ab1.js +1 -0
  297. package/out/_next/static/chunks/pages/_error-84001666436a04e4.js +1 -0
  298. package/out/_next/static/chunks/{webpack-dd93b81e2659669c.js → webpack-7586035f1585f2db.js} +1 -1
  299. package/out/_next/static/css/eb9fc69d1e3d2bed.css +1 -0
  300. package/out/about.html +2 -2
  301. package/out/about.txt +2 -2
  302. package/out/app/onboarding.html +1 -1
  303. package/out/app/onboarding.txt +2 -2
  304. package/out/app.html +1 -1
  305. package/out/app.txt +2 -2
  306. package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +3 -3
  307. package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
  308. package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
  309. package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
  310. package/out/blog.html +2 -2
  311. package/out/blog.txt +1 -1
  312. package/out/careers.html +2 -2
  313. package/out/careers.txt +2 -2
  314. package/out/changelog.html +2 -2
  315. package/out/changelog.txt +2 -2
  316. package/out/cloud/link.html +1 -1
  317. package/out/cloud/link.txt +2 -2
  318. package/out/complete-profile.html +2 -2
  319. package/out/complete-profile.txt +2 -2
  320. package/out/connect-repos.html +1 -1
  321. package/out/connect-repos.txt +2 -2
  322. package/out/contact.html +2 -2
  323. package/out/contact.txt +2 -2
  324. package/out/dev/cli-tools.html +1 -0
  325. package/out/dev/cli-tools.txt +7 -0
  326. package/out/dev/log-viewer.html +23 -0
  327. package/out/dev/log-viewer.txt +7 -0
  328. package/out/docs.html +2 -2
  329. package/out/docs.txt +2 -2
  330. package/out/history.html +1 -1
  331. package/out/history.txt +2 -2
  332. package/out/index.html +1 -1
  333. package/out/index.txt +2 -2
  334. package/out/login.html +2 -2
  335. package/out/login.txt +2 -2
  336. package/out/metrics.html +1 -1
  337. package/out/metrics.txt +2 -2
  338. package/out/pricing.html +2 -2
  339. package/out/pricing.txt +2 -2
  340. package/out/privacy.html +2 -2
  341. package/out/privacy.txt +2 -2
  342. package/out/providers/setup/claude.html +1 -1
  343. package/out/providers/setup/claude.txt +2 -2
  344. package/out/providers/setup/codex.html +1 -1
  345. package/out/providers/setup/codex.txt +2 -2
  346. package/out/providers/setup/cursor.html +1 -1
  347. package/out/providers/setup/cursor.txt +2 -2
  348. package/out/providers.html +1 -1
  349. package/out/providers.txt +2 -2
  350. package/out/security.html +2 -2
  351. package/out/security.txt +2 -2
  352. package/out/signup.html +2 -2
  353. package/out/signup.txt +2 -2
  354. package/out/terms.html +2 -2
  355. package/out/terms.txt +2 -2
  356. package/package.json +10 -11
  357. package/out/_next/static/chunks/11-9a2993a37266dcb3.js +0 -9
  358. package/out/_next/static/chunks/118-ae2b650136a5a5fc.js +0 -1
  359. package/out/_next/static/chunks/1dd3208c-40ab0fc0f60392b8.js +0 -1
  360. package/out/_next/static/chunks/202-fc0763dd7488e58f.js +0 -1
  361. package/out/_next/static/chunks/259-83b77fa1b91ba5aa.js +0 -1
  362. package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +0 -1
  363. package/out/_next/static/chunks/528-f5f676996d613c25.js +0 -2
  364. package/out/_next/static/chunks/663-ddb04081febc3678.js +0 -1
  365. package/out/_next/static/chunks/687-88b6b139a6bb0e2e.js +0 -1
  366. package/out/_next/static/chunks/695-51d25b1988644374.js +0 -1
  367. package/out/_next/static/chunks/773-54a2641043c81e55.js +0 -1
  368. package/out/_next/static/chunks/app/_not-found/page-6da9b72091e5b511.js +0 -1
  369. package/out/_next/static/chunks/app/about/page-fff7c6457683f243.js +0 -1
  370. package/out/_next/static/chunks/app/app/[[...slug]]/page-f7eca1b66fb4249b.js +0 -1
  371. package/out/_next/static/chunks/app/app/onboarding/page-129abc5da2e67971.js +0 -1
  372. package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-5d5f28fd126b692f.js +0 -1
  373. package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-b194f207fbd91862.js +0 -1
  374. package/out/_next/static/chunks/app/blog/page-b9bd9d8703fca76a.js +0 -1
  375. package/out/_next/static/chunks/app/careers/page-a4bd8d5f4de8f4eb.js +0 -1
  376. package/out/_next/static/chunks/app/changelog/page-9a1f6ad1743d63c5.js +0 -1
  377. package/out/_next/static/chunks/app/cloud/link/page-0844c5699b027c3b.js +0 -1
  378. package/out/_next/static/chunks/app/complete-profile/page-39ed5a67916beb87.js +0 -1
  379. package/out/_next/static/chunks/app/connect-repos/page-297eddee0c39f2a3.js +0 -1
  380. package/out/_next/static/chunks/app/contact/page-3c1dd8690217fade.js +0 -1
  381. package/out/_next/static/chunks/app/docs/page-1875e981f2c3fd13.js +0 -1
  382. package/out/_next/static/chunks/app/history/page-2d5c5695c9e8b40c.js +0 -1
  383. package/out/_next/static/chunks/app/layout-0a4b99656da25511.js +0 -1
  384. package/out/_next/static/chunks/app/login/page-f69c076f5a6fc520.js +0 -1
  385. package/out/_next/static/chunks/app/metrics/page-bebbee055669a17e.js +0 -1
  386. package/out/_next/static/chunks/app/page-0ee604f7070d14c0.js +0 -1
  387. package/out/_next/static/chunks/app/pricing/page-eeae7d594af333b6.js +0 -1
  388. package/out/_next/static/chunks/app/providers/setup/[provider]/page-daf9b3e05e77ae19.js +0 -1
  389. package/out/_next/static/chunks/app/security/page-cd562730fe84a0a2.js +0 -1
  390. package/out/_next/static/chunks/app/signup/page-c242ca08101a84ff.js +0 -1
  391. package/out/_next/static/chunks/app/terms/page-c7001720e7941dc6.js +0 -1
  392. package/out/_next/static/chunks/framework-3664cab31236a9fa.js +0 -1
  393. package/out/_next/static/chunks/main-app-7f73a939a312a228.js +0 -1
  394. package/out/_next/static/chunks/pages/_app-10a93ab5b7c32eb3.js +0 -1
  395. package/out/_next/static/chunks/pages/_error-2d792b2a41857be4.js +0 -1
  396. package/out/_next/static/css/8968d98ed4c4d33f.css +0 -1
  397. /package/out/_next/static/{TnAI-TAQ4-bNJRL3Ln3NB → 49XhcS8k_e8607S7KBMwA}/_ssgManifest.js +0 -0
@@ -1,63 +1,113 @@
1
1
  /**
2
2
  * Relay Dashboard Server
3
3
  *
4
- * A flexible server that can operate in two modes:
5
- * 1. Proxy mode (default): Proxies API/WebSocket requests to a relay daemon
6
- * 2. Mock mode: Returns fixture data for standalone testing/demos
7
- *
8
- * This allows the dashboard to run independently without any external dependencies.
4
+ * A flexible server that can operate in three modes:
5
+ * 1. Proxy mode (default): static files + Relaycast data + broker proxy
6
+ * 2. Standalone mode: static files + Relaycast data (no broker proxy)
7
+ * 3. Mock mode: fixture-backed standalone mode for demos/tests
9
8
  */
10
9
  import express from 'express';
11
10
  import { createServer as createHttpServer } from 'http';
12
- import { createProxyMiddleware } from 'http-proxy-middleware';
13
- import { WebSocketServer, WebSocket } from 'ws';
11
+ import { WebSocketServer } from 'ws';
12
+ import fs from 'fs';
14
13
  import path from 'path';
15
14
  import { fileURLToPath } from 'url';
16
15
  import { registerMockRoutes } from './mocks/routes.js';
17
- import { mockAgents, mockMessages, mockSessions, } from './mocks/fixtures.js';
16
+ import { fetchAgents, fetchChannels, loadRelaycastConfig, } from './relaycast-provider.js';
17
+ import { createSendStrategy } from './lib/send-strategy.js';
18
+ import { DASHBOARD_DISPLAY_NAME } from './relaycast-provider-types.js';
19
+ import { resolveIdentity } from './lib/identity.js';
20
+ import { EMPTY_DASHBOARD_SNAPSHOT } from './lib/types.js';
21
+ import { normalizeRelayUrl, normalizeName, isDirectRecipient, sendHtmlFileOrFallback, getBindHost, mapChannelForDashboard, } from './lib/utils.js';
22
+ import { filterPhantomAgents, mergeBrokerSpawnedAgents, createSpawnedAgentsCaches, } from './lib/spawned-agents.js';
23
+ import { handleMockWebSocket } from './websocket/mock.js';
24
+ import { handleStandaloneWebSocket, handleHybridWebSocket } from './websocket/standalone.js';
25
+ import { handleStandaloneLogWebSocket } from './websocket/logs.js';
26
+ import { registerHealthRoutes } from './routes/health.js';
27
+ import { registerDataRoutes } from './routes/data.js';
28
+ import { registerAgentRoutes } from './routes/agents.js';
29
+ import { registerChannelRoutes } from './routes/channels.js';
30
+ import { registerBrokerProxyRoutes } from './routes/broker-proxy.js';
31
+ import { registerMetricsRoutes } from './routes/metrics.js';
32
+ import { registerReactionRoutes } from './routes/reactions.js';
33
+ import { registerThreadReplyRoutes } from './routes/thread-replies.js';
34
+ import { registerRelayConfigRoutes } from './routes/relay-config.js';
35
+ import { registerRelaycastHistoryRoutes } from './routes/history-relaycast.js';
18
36
  const __filename = fileURLToPath(import.meta.url);
19
37
  const __dirname = path.dirname(__filename);
20
- /**
21
- * Get the host to bind to.
22
- * In cloud environments, bind to '::' (IPv6 any) which also accepts IPv4 on dual-stack.
23
- * This is required for Fly.io's internal IPv6 network (6PN) connectivity.
24
- * Locally, let Node.js use its default behavior.
25
- */
26
- function getBindHost() {
27
- // Explicit override via env var
28
- if (process.env.BIND_HOST) {
29
- return process.env.BIND_HOST;
38
+ function resolveMetricsPagePath(staticDir) {
39
+ const candidates = [
40
+ path.join(staticDir, 'metrics.html'),
41
+ path.join(staticDir, 'metrics', 'index.html'),
42
+ path.join(staticDir, 'app.html'),
43
+ path.join(staticDir, 'index.html'),
44
+ ];
45
+ for (const candidate of candidates) {
46
+ if (fs.existsSync(candidate)) {
47
+ return candidate;
48
+ }
30
49
  }
31
- // Cloud environment detection - bind to :: for IPv6 + IPv4 dual-stack
32
- // Fly.io internal network uses IPv6 (fdaa:...), so 0.0.0.0 won't work
33
- const isCloudEnvironment = process.env.FLY_APP_NAME || // Fly.io
34
- process.env.WORKSPACE_ID || // Agent Relay workspace
35
- process.env.RELAY_WORKSPACE_ID || // Alternative workspace ID
36
- process.env.RUNNING_IN_DOCKER === 'true'; // Docker container
37
- return isCloudEnvironment ? '::' : undefined;
50
+ return candidates[0];
38
51
  }
52
+ const asString = (value) => {
53
+ if (typeof value === 'string' && value.length > 0)
54
+ return value;
55
+ if (Array.isArray(value)) {
56
+ for (const item of value) {
57
+ if (typeof item === 'string' && item.length > 0)
58
+ return item;
59
+ }
60
+ }
61
+ return undefined;
62
+ };
63
+ const getWorkspaceHeader = (headers) => {
64
+ if (!headers)
65
+ return undefined;
66
+ const direct = headers['x-workspace-id'];
67
+ if (typeof direct === 'string' && direct.length > 0)
68
+ return direct;
69
+ for (const [key, value] of Object.entries(headers)) {
70
+ if (key.toLowerCase() === 'x-workspace-id' && typeof value === 'string' && value.length > 0) {
71
+ return value;
72
+ }
73
+ }
74
+ return undefined;
75
+ };
39
76
  /**
40
77
  * Create the dashboard server without starting it
41
78
  */
42
79
  export function createServer(options = {}) {
43
- const { port = parseInt(process.env.PORT || '3888', 10), relayUrl = process.env.RELAY_URL || 'http://localhost:3889', staticDir = process.env.STATIC_DIR || path.join(__dirname, '..', 'out'), verbose = process.env.VERBOSE === 'true', mock = process.env.MOCK === 'true', corsOrigins = process.env.CORS_ORIGINS || '', requestTimeout = parseInt(process.env.REQUEST_TIMEOUT || '30000', 10), } = options;
80
+ const { relayUrl: relayUrlOption, staticDir = process.env.STATIC_DIR || path.join(__dirname, '..', 'out'), dataDir = process.env.DATA_DIR || path.join(process.cwd(), '.agent-relay'), verbose = process.env.VERBOSE === 'true', mock = process.env.MOCK === 'true', corsOrigins = process.env.CORS_ORIGINS || '', requestTimeout = parseInt(process.env.REQUEST_TIMEOUT || '60000', 10), relayApiKey: relayApiKeyOption, } = options;
81
+ // In-memory API key — highest priority, avoids any file I/O.
82
+ // Seeded from the option or RELAY_API_KEY env var, then updated dynamically
83
+ // via setRelayApiKey() when a workflow creates a new Relaycast workspace.
84
+ let inMemoryRelayApiKey = relayApiKeyOption?.trim() || process.env.RELAY_API_KEY?.trim() || undefined;
85
+ const resolvedDataDir = path.resolve(dataDir);
86
+ if (!process.env.AGENT_RELAY_PROJECT) {
87
+ process.env.AGENT_RELAY_PROJECT = path.dirname(resolvedDataDir);
88
+ }
89
+ const relayUrl = normalizeRelayUrl(relayUrlOption ?? process.env.RELAY_URL);
90
+ const mode = mock ? 'mock' : (relayUrl ? 'proxy' : 'standalone');
91
+ const brokerProxyEnabled = mode === 'proxy' && Boolean(relayUrl);
92
+ const defaultWorkspaceId = process.env.RELAY_WORKSPACE_ID ?? process.env.AGENT_RELAY_WORKSPACE_ID;
93
+ const resolveWorkspaceId = (req) => {
94
+ const fromQuery = asString(req.query?.workspaceId);
95
+ const fromBody = asString(req.body?.workspaceId);
96
+ const fromHeader = getWorkspaceHeader(req.headers);
97
+ return fromQuery || fromBody || fromHeader || defaultWorkspaceId;
98
+ };
44
99
  const app = express();
45
100
  const server = createHttpServer(app);
46
- const mode = mock ? 'mock' : 'proxy';
47
- // Set request timeout
48
101
  server.timeout = requestTimeout;
49
- // Parse JSON bodies
50
102
  app.use(express.json({ limit: '10mb' }));
51
- // CORS middleware - configurable for cross-origin deployments
52
103
  if (corsOrigins) {
53
104
  app.use((req, res, next) => {
54
105
  const origin = req.headers.origin;
55
- // Check if origin is allowed
56
106
  if (corsOrigins === '*') {
57
107
  res.header('Access-Control-Allow-Origin', '*');
58
108
  }
59
109
  else if (origin) {
60
- const allowedOrigins = corsOrigins.split(',').map(o => o.trim());
110
+ const allowedOrigins = corsOrigins.split(',').map((value) => value.trim());
61
111
  if (allowedOrigins.includes(origin)) {
62
112
  res.header('Access-Control-Allow-Origin', origin);
63
113
  }
@@ -66,7 +116,6 @@ export function createServer(options = {}) {
66
116
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-CSRF-Token');
67
117
  res.header('Access-Control-Allow-Credentials', 'true');
68
118
  res.header('Access-Control-Expose-Headers', 'X-CSRF-Token');
69
- // Handle preflight requests
70
119
  if (req.method === 'OPTIONS') {
71
120
  res.sendStatus(204);
72
121
  return;
@@ -74,99 +123,268 @@ export function createServer(options = {}) {
74
123
  next();
75
124
  });
76
125
  }
77
- // Logging middleware
78
126
  if (verbose) {
79
127
  app.use((req, _res, next) => {
80
128
  console.log(`[dashboard] ${req.method} ${req.url}`);
81
129
  next();
82
130
  });
83
131
  }
84
- // Health check endpoint (always local)
85
- app.get('/health', (_req, res) => {
86
- res.json({
87
- status: 'ok',
88
- service: 'relay-dashboard',
89
- mode,
90
- uptime: process.uptime(),
91
- });
92
- });
93
- // Keep-alive endpoint
94
- app.get('/keep-alive', (_req, res) => {
95
- res.json({ ok: true });
132
+ // --- Build shared context ---
133
+ const resolveRelaycastConfig = () => {
134
+ // In-memory key (from option, env var, or dynamic update) takes priority over the file.
135
+ if (inMemoryRelayApiKey) {
136
+ const baseUrl = process.env.RELAYCAST_API_URL || 'https://api.relaycast.dev';
137
+ const projectDir = path.basename(path.resolve(dataDir, '..'));
138
+ return { apiKey: inMemoryRelayApiKey, baseUrl, projectIdentity: projectDir };
139
+ }
140
+ return loadRelaycastConfig(dataDir);
141
+ };
142
+ const setRelayApiKey = (apiKey) => {
143
+ inMemoryRelayApiKey = apiKey.trim();
144
+ };
145
+ const { getSpawnedAgents, getLocalAgentNames } = createSpawnedAgentsCaches({
146
+ brokerProxyEnabled,
147
+ relayUrl,
148
+ dataDir,
149
+ verbose,
96
150
  });
151
+ const getRelaycastSnapshot = async () => {
152
+ const config = resolveRelaycastConfig();
153
+ if (!config) {
154
+ return { ...EMPTY_DASHBOARD_SNAPSHOT };
155
+ }
156
+ const [agents, spawnedAgents, localAgentNames] = await Promise.all([
157
+ fetchAgents(config),
158
+ brokerProxyEnabled ? getSpawnedAgents() : Promise.resolve({ names: null, agents: null }),
159
+ brokerProxyEnabled ? Promise.resolve(null) : Promise.resolve(getLocalAgentNames()),
160
+ ]);
161
+ const filteredAgents = filterPhantomAgents(agents, spawnedAgents.names, localAgentNames);
162
+ const mergedAgents = mergeBrokerSpawnedAgents(filteredAgents, spawnedAgents.agents);
163
+ return {
164
+ agents: mergedAgents,
165
+ users: [],
166
+ messages: [],
167
+ activity: [],
168
+ sessions: [],
169
+ summaries: [],
170
+ };
171
+ };
172
+ const getRelaycastChannels = async () => {
173
+ const config = resolveRelaycastConfig();
174
+ if (!config) {
175
+ return { channels: [], archivedChannels: [] };
176
+ }
177
+ const channels = await fetchChannels(config);
178
+ const activeChannels = [];
179
+ const archivedChannels = [];
180
+ for (const channel of channels) {
181
+ const mapped = mapChannelForDashboard({ ...channel, is_archived: channel.is_archived ?? false });
182
+ if (mapped.status === 'archived') {
183
+ archivedChannels.push(mapped);
184
+ }
185
+ else {
186
+ activeChannels.push(mapped);
187
+ }
188
+ }
189
+ activeChannels.sort((a, b) => a.name.localeCompare(b.name));
190
+ archivedChannels.sort((a, b) => a.name.localeCompare(b.name));
191
+ return { channels: activeChannels, archivedChannels };
192
+ };
193
+ const sendRelaycastMessage = async (params) => {
194
+ const sendTimeout = Math.max(requestTimeout - 5000, 10000);
195
+ const sendStart = Date.now();
196
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Send timed out')), sendTimeout));
197
+ try {
198
+ return await Promise.race([
199
+ (async () => {
200
+ const config = resolveRelaycastConfig();
201
+ const rawTarget = params.to.trim();
202
+ const message = params.message.trim();
203
+ let resolvedTarget = rawTarget;
204
+ if (isDirectRecipient(rawTarget) && config) {
205
+ const relayAgents = await fetchAgents(config);
206
+ const relayMatch = relayAgents.find((agent) => normalizeName(agent.name) === normalizeName(rawTarget));
207
+ if (relayMatch) {
208
+ resolvedTarget = relayMatch.name;
209
+ }
210
+ }
211
+ const projectIdentity = config?.agentName?.trim()
212
+ || path.basename(path.resolve(dataDir, '..'))
213
+ || DASHBOARD_DISPLAY_NAME;
214
+ const senderInput = params.from?.trim() ?? '';
215
+ const senderName = mode === 'proxy'
216
+ ? resolveIdentity(senderInput || projectIdentity, {
217
+ projectIdentity: projectIdentity.trim(),
218
+ relayAgentName: config?.agentName?.trim(),
219
+ })
220
+ : (senderInput || projectIdentity);
221
+ const strategy = createSendStrategy({
222
+ brokerProxyEnabled,
223
+ brokerUrl: relayUrl,
224
+ relaycastConfig: config,
225
+ dataDir,
226
+ });
227
+ if (!strategy) {
228
+ return {
229
+ success: false,
230
+ status: 503,
231
+ error: `Relaycast credentials not found in ${path.join(dataDir, 'relaycast.json')}`,
232
+ };
233
+ }
234
+ console.log(`[dashboard] /api/send request: to=${resolvedTarget}, from=${senderName}, relayUrl=${relayUrl}, timeoutMs=${sendTimeout}`);
235
+ const outcome = await strategy.send({
236
+ to: resolvedTarget,
237
+ message,
238
+ from: senderName,
239
+ thread: params.thread,
240
+ });
241
+ console.log(`[dashboard] /api/send completed in ${Date.now() - sendStart}ms with status=${outcome.success ? 200 : outcome.status}`);
242
+ // Enrich "agent not found" errors with available agent names
243
+ if (!outcome.success && isDirectRecipient(params.to) && config) {
244
+ if (/agent\s+\".+\"\s+not\s+found/i.test(outcome.error)) {
245
+ const relayAgents = await fetchAgents(config);
246
+ const available = relayAgents.map((agent) => agent.name).sort();
247
+ const suffix = available.length > 0
248
+ ? ` Available relay agents: ${available.join(', ')}.`
249
+ : ' No relay agents are currently online.';
250
+ return {
251
+ success: false,
252
+ status: 404,
253
+ error: `${outcome.error}.${suffix}`,
254
+ };
255
+ }
256
+ }
257
+ return outcome;
258
+ })(),
259
+ timeoutPromise,
260
+ ]);
261
+ }
262
+ catch (err) {
263
+ console.error(`[dashboard] /api/send failed after ${Date.now() - sendStart}ms: ${err.message}`);
264
+ return {
265
+ success: false,
266
+ status: 504,
267
+ error: err.message || 'Send request timed out',
268
+ };
269
+ }
270
+ };
271
+ const ctx = {
272
+ mode,
273
+ dataDir,
274
+ staticDir,
275
+ verbose,
276
+ relayUrl,
277
+ brokerProxyEnabled,
278
+ resolveRelaycastConfig,
279
+ setRelayApiKey,
280
+ getRelaycastSnapshot,
281
+ getRelaycastChannels,
282
+ sendRelaycastMessage,
283
+ getSpawnedAgents,
284
+ getLocalAgentNames,
285
+ filterPhantomAgents,
286
+ };
287
+ // --- Register routes ---
288
+ registerHealthRoutes(app, ctx);
97
289
  if (mock) {
98
- // ===== MOCK MODE =====
99
- // Register mock API routes for standalone operation
100
- console.log('[dashboard] Running in MOCK mode - no relay daemon required');
290
+ console.log('[dashboard] Running in MOCK mode - no relay broker required');
101
291
  registerMockRoutes(app, verbose);
102
292
  }
103
293
  else {
104
- // ===== PROXY MODE =====
105
- // Proxy all API requests to the relay daemon
106
- console.log(`[dashboard] Running in PROXY mode - forwarding to ${relayUrl}`);
107
- const apiProxyOptions = {
108
- target: relayUrl,
109
- changeOrigin: true,
110
- ws: false, // WebSocket handled separately
111
- logger: verbose ? console : undefined,
112
- on: {
113
- error: (err, _req, res) => {
114
- console.error('[dashboard] API proxy error:', err.message);
115
- if (res && 'writeHead' in res && typeof res.writeHead === 'function') {
116
- res.writeHead(502, { 'Content-Type': 'application/json' });
117
- res.end(JSON.stringify({
118
- error: 'Relay daemon unavailable',
119
- message: err.message,
120
- }));
121
- }
122
- },
123
- },
124
- };
125
- app.use('/api', createProxyMiddleware(apiProxyOptions));
126
- app.use('/auth', createProxyMiddleware(apiProxyOptions));
127
- app.use('/metrics', createProxyMiddleware(apiProxyOptions));
294
+ if (mode === 'proxy' && relayUrl) {
295
+ console.log(`[dashboard] Running in PROXY mode - relaycast + broker proxy (${relayUrl})`);
296
+ }
297
+ else {
298
+ console.log('[dashboard] Running in STANDALONE mode - relaycast only (read-only broker surface)');
299
+ }
300
+ registerAgentRoutes(app, ctx);
301
+ registerDataRoutes(app, ctx);
302
+ registerRelayConfigRoutes(app, ctx);
303
+ registerChannelRoutes(app, ctx);
304
+ registerReactionRoutes(app, ctx);
305
+ registerThreadReplyRoutes(app, ctx);
306
+ registerMetricsRoutes(app, {
307
+ teamDir: path.join(dataDir, 'team'),
308
+ resolveWorkspaceId,
309
+ });
310
+ registerRelaycastHistoryRoutes(app, ctx);
311
+ registerBrokerProxyRoutes(app, ctx);
128
312
  }
129
- // Serve static files
313
+ // --- Static files and SPA fallback ---
314
+ const fallbackHtml = `<!doctype html>
315
+ <html lang="en">
316
+ <head>
317
+ <meta charset="utf-8" />
318
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
319
+ <title>Relay Dashboard</title>
320
+ </head>
321
+ <body>
322
+ <h1>Relay Dashboard</h1>
323
+ <p>Dashboard static build not found.</p>
324
+ </body>
325
+ </html>`;
326
+ app.get('/metrics', (_req, res) => {
327
+ const metricsPath = resolveMetricsPagePath(staticDir);
328
+ sendHtmlFileOrFallback(res, metricsPath, fallbackHtml, 200);
329
+ });
330
+ app.get('/app', (_req, res) => {
331
+ const appHtmlPath = path.join(staticDir, 'app.html');
332
+ sendHtmlFileOrFallback(res, appHtmlPath, fallbackHtml, 200);
333
+ });
334
+ app.get('/app/{*path}', (_req, res) => {
335
+ const appHtmlPath = path.join(staticDir, 'app.html');
336
+ sendHtmlFileOrFallback(res, appHtmlPath, fallbackHtml, 200);
337
+ });
130
338
  app.use(express.static(staticDir, {
131
339
  extensions: ['html'],
132
340
  }));
133
- // SPA fallback - serve appropriate HTML file for unmatched routes
134
- // Express 5 requires named parameter instead of bare *
341
+ app.get('/', (_req, res) => {
342
+ const indexPath = path.join(staticDir, 'index.html');
343
+ sendHtmlFileOrFallback(res, indexPath, fallbackHtml, 200);
344
+ });
135
345
  app.get('/{*path}', (req, res) => {
136
- // Don't serve HTML for API routes or static assets
346
+ // WebSocket endpoints require upgrade - return 426 for regular HTTP requests
347
+ if (req.path === '/ws' || req.path.startsWith('/ws/')) {
348
+ res.status(426).json({ error: 'Upgrade Required', message: 'WebSocket upgrade required' });
349
+ return;
350
+ }
137
351
  if (req.path.startsWith('/api') || req.path.startsWith('/auth') || req.path.includes('.')) {
138
352
  res.status(404).json({ error: 'Not found' });
139
353
  return;
140
354
  }
141
- // For /app/* routes (including /app/channel/*, /app/agent/*, /app/dm/*, /app/settings/*),
142
- // serve the app.html which handles client-side routing
143
355
  if (req.path.startsWith('/app')) {
144
- res.sendFile(path.join(staticDir, 'app.html'));
356
+ const appHtmlPath = path.join(staticDir, 'app.html');
357
+ sendHtmlFileOrFallback(res, appHtmlPath, fallbackHtml, 200);
145
358
  return;
146
359
  }
147
- // For other routes, serve index.html
148
- res.sendFile(path.join(staticDir, 'index.html'));
360
+ const indexPath = path.join(staticDir, 'index.html');
361
+ sendHtmlFileOrFallback(res, indexPath, fallbackHtml, 200);
149
362
  });
150
- // WebSocket server
363
+ // --- WebSocket ---
151
364
  const wss = new WebSocketServer({ noServer: true });
152
- // Handle WebSocket upgrade
153
365
  server.on('upgrade', (request, socket, head) => {
154
366
  const pathname = request.url ? new URL(request.url, `http://${request.headers.host}`).pathname : '';
155
367
  if (pathname === '/ws') {
156
368
  wss.handleUpgrade(request, socket, head, (ws) => {
157
- if (mock) {
158
- // Mock WebSocket - send periodic updates with fixture data
369
+ if (mode === 'mock') {
159
370
  handleMockWebSocket(ws, verbose);
160
371
  }
372
+ else if (mode === 'proxy' && relayUrl) {
373
+ handleHybridWebSocket(ws, getRelaycastSnapshot, relayUrl, verbose);
374
+ }
161
375
  else {
162
- // Proxy WebSocket to relay daemon
163
- handleProxyWebSocket(ws, relayUrl, verbose);
376
+ handleStandaloneWebSocket(ws, getRelaycastSnapshot, verbose);
164
377
  }
165
378
  });
379
+ return;
166
380
  }
167
- else {
168
- socket.destroy();
381
+ if (mode !== 'mock' && (pathname === '/ws/logs' || pathname.startsWith('/ws/logs/'))) {
382
+ wss.handleUpgrade(request, socket, head, (ws) => {
383
+ handleStandaloneLogWebSocket(ws, pathname, dataDir, getLocalAgentNames, verbose);
384
+ });
385
+ return;
169
386
  }
387
+ socket.destroy();
170
388
  });
171
389
  const close = () => {
172
390
  return new Promise((resolve) => {
@@ -177,113 +395,10 @@ export function createServer(options = {}) {
177
395
  });
178
396
  });
179
397
  };
180
- return { app, server, wss, close, mode };
181
- }
182
- /**
183
- * Handle mock WebSocket connections
184
- * Sends periodic updates with fixture data
185
- */
186
- function handleMockWebSocket(ws, verbose) {
187
- if (verbose) {
188
- console.log('[dashboard] Mock WebSocket client connected');
189
- }
190
- // Send initial data
191
- const sendData = () => {
192
- if (ws.readyState === WebSocket.OPEN) {
193
- ws.send(JSON.stringify({
194
- agents: mockAgents,
195
- messages: mockMessages,
196
- sessions: mockSessions,
197
- }));
198
- }
199
- };
200
- // Send initial data immediately
201
- sendData();
202
- // Send updates every 5 seconds
203
- const interval = setInterval(sendData, 5000);
204
- // Handle messages from client
205
- ws.on('message', (data) => {
206
- try {
207
- const msg = JSON.parse(data.toString());
208
- if (verbose) {
209
- console.log('[dashboard] Mock WS received:', msg);
210
- }
211
- // Echo back acknowledgment for certain message types
212
- if (msg.type === 'ping') {
213
- ws.send(JSON.stringify({ type: 'pong' }));
214
- }
215
- else if (msg.type === 'subscribe') {
216
- // Send current data when client subscribes
217
- sendData();
218
- }
219
- }
220
- catch {
221
- // Ignore parse errors
222
- }
223
- });
224
- ws.on('close', () => {
225
- if (verbose) {
226
- console.log('[dashboard] Mock WebSocket client disconnected');
227
- }
228
- clearInterval(interval);
229
- });
230
- ws.on('error', (err) => {
231
- console.error('[dashboard] Mock WebSocket error:', err.message);
232
- clearInterval(interval);
233
- });
398
+ return { app, server, wss, close, mode, setRelayApiKey };
234
399
  }
235
400
  /**
236
- * Handle proxy WebSocket connections
237
- * Forwards messages bidirectionally to relay daemon
238
- */
239
- function handleProxyWebSocket(ws, relayUrl, verbose) {
240
- const relayUrlObj = new URL(relayUrl);
241
- const wsRelayUrl = `ws://${relayUrlObj.host}`;
242
- // Create connection to relay daemon
243
- const relayWs = new WebSocket(`${wsRelayUrl}/ws`);
244
- relayWs.on('open', () => {
245
- if (verbose) {
246
- console.log('[dashboard] WebSocket connected to relay daemon');
247
- }
248
- });
249
- // Forward messages from client to relay
250
- ws.on('message', (data) => {
251
- if (relayWs.readyState === WebSocket.OPEN) {
252
- relayWs.send(data);
253
- }
254
- });
255
- // Forward messages from relay to client
256
- relayWs.on('message', (data) => {
257
- if (ws.readyState === WebSocket.OPEN) {
258
- ws.send(data);
259
- }
260
- });
261
- // Handle client disconnect
262
- ws.on('close', () => {
263
- if (verbose) {
264
- console.log('[dashboard] Client WebSocket closed');
265
- }
266
- relayWs.close();
267
- });
268
- // Handle relay disconnect
269
- relayWs.on('close', () => {
270
- if (verbose) {
271
- console.log('[dashboard] Relay WebSocket closed');
272
- }
273
- ws.close();
274
- });
275
- // Handle errors
276
- ws.on('error', (err) => {
277
- console.error('[dashboard] Client WebSocket error:', err.message);
278
- relayWs.close();
279
- });
280
- relayWs.on('error', (err) => {
281
- console.error('[dashboard] Relay WebSocket error:', err.message);
282
- ws.close();
283
- });
284
- }
285
- /**
286
- * Try to listen on a port, returns the port if successful or null if in use
401
+ * Try to listen on a port, returns the port if successful or null if in use.
287
402
  */
288
403
  function tryListen(server, port) {
289
404
  return new Promise((resolve) => {
@@ -310,7 +425,7 @@ function tryListen(server, port) {
310
425
  });
311
426
  }
312
427
  /**
313
- * Find an available port starting from the preferred port
428
+ * Find an available port starting from the preferred port.
314
429
  */
315
430
  async function findAvailablePort(server, preferredPort, maxAttempts = 10) {
316
431
  for (let i = 0; i < maxAttempts; i++) {
@@ -319,19 +434,47 @@ async function findAvailablePort(server, preferredPort, maxAttempts = 10) {
319
434
  if (result !== null) {
320
435
  return result;
321
436
  }
322
- // Close and recreate listener for next attempt
323
437
  server.close();
324
438
  }
325
439
  throw new Error(`Could not find available port after ${maxAttempts} attempts starting from ${preferredPort}`);
326
440
  }
327
441
  /**
328
- * Start the dashboard server
329
- * Automatically finds an available port if the preferred port is in use
442
+ * Bootstrap Relaycast credentials from the broker's authenticated /api/config
443
+ * endpoint. The workspace key is on an authenticated route (not /health) so it
444
+ * is not exposed to unauthenticated callers.
445
+ */
446
+ async function bootstrapRelayApiKeyFromBroker(relayUrl, setRelayApiKey) {
447
+ const brokerApiKey = process.env.RELAY_BROKER_API_KEY?.trim();
448
+ const headers = {};
449
+ if (brokerApiKey) {
450
+ headers['x-api-key'] = brokerApiKey;
451
+ }
452
+ // Retry a few times to allow the broker to fully start up.
453
+ for (let attempt = 0; attempt < 5; attempt++) {
454
+ try {
455
+ const res = await fetch(`${relayUrl}/api/config`, { headers });
456
+ if (!res.ok)
457
+ break;
458
+ const json = await res.json();
459
+ const key = typeof json.workspaceKey === 'string' ? json.workspaceKey.trim() : '';
460
+ if (key) {
461
+ setRelayApiKey(key);
462
+ console.log('[dashboard] Relaycast workspace key bootstrapped from broker');
463
+ return;
464
+ }
465
+ }
466
+ catch {
467
+ // Broker not yet ready — wait and retry.
468
+ }
469
+ await new Promise((r) => setTimeout(r, 500));
470
+ }
471
+ }
472
+ /**
473
+ * Start the dashboard server.
330
474
  */
331
475
  export async function startServer(options = {}) {
332
476
  const preferredPort = options.port || parseInt(process.env.PORT || '3888', 10);
333
477
  const dashboard = createServer(options);
334
- // Try preferred port first, then search for available port
335
478
  const actualPort = await findAvailablePort(dashboard.server, preferredPort);
336
479
  if (actualPort !== preferredPort) {
337
480
  console.log(`[dashboard] Port ${preferredPort} in use, using port ${actualPort}`);
@@ -340,8 +483,17 @@ export async function startServer(options = {}) {
340
483
  if (dashboard.mode === 'mock') {
341
484
  console.log('[dashboard] Using mock data - ready for standalone testing');
342
485
  }
486
+ else if (dashboard.mode === 'proxy') {
487
+ console.log(`[dashboard] Proxy mode enabled - broker URL ${normalizeRelayUrl(options.relayUrl ?? process.env.RELAY_URL)}`);
488
+ }
343
489
  else {
344
- console.log(`[dashboard] Proxying to relay daemon at ${options.relayUrl || process.env.RELAY_URL || 'http://localhost:3889'}`);
490
+ console.log('[dashboard] Standalone mode enabled - relaycast data only');
491
+ }
492
+ // Best-effort: fetch workspace key from the broker health endpoint so the
493
+ // dashboard uses the same Relaycast workspace as the broker (no relaycast.json needed).
494
+ const resolvedRelayUrl = normalizeRelayUrl(options.relayUrl ?? process.env.RELAY_URL);
495
+ if (resolvedRelayUrl && !options.mock) {
496
+ bootstrapRelayApiKeyFromBroker(resolvedRelayUrl, dashboard.setRelayApiKey).catch(() => { });
345
497
  }
346
498
  return dashboard;
347
499
  }