agent-relay 1.3.2 → 1.5.0

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 (318) hide show
  1. package/README.md +130 -158
  2. package/bin/relay-pty +0 -0
  3. package/bin/relay-pty-darwin-arm64 +0 -0
  4. package/bin/relay-pty-darwin-x64 +0 -0
  5. package/bin/relay-pty-linux-x64 +0 -0
  6. package/deploy/workspace/entrypoint.sh +9 -0
  7. package/dist/bridge/spawner.d.ts +4 -4
  8. package/dist/bridge/spawner.js +58 -92
  9. package/dist/cli/index.d.ts +8 -6
  10. package/dist/cli/index.js +282 -47
  11. package/dist/cloud/api/daemons.js +13 -32
  12. package/dist/cloud/api/onboarding.js +2 -4
  13. package/dist/cloud/api/providers.js +6 -0
  14. package/dist/cloud/config.d.ts +1 -0
  15. package/dist/cloud/config.js +2 -0
  16. package/dist/cloud/db/bulk-ingest.d.ts +2 -1
  17. package/dist/cloud/db/drizzle.d.ts +21 -26
  18. package/dist/cloud/db/drizzle.js +87 -100
  19. package/dist/cloud/db/index.d.ts +6 -5
  20. package/dist/cloud/db/index.js +9 -8
  21. package/dist/cloud/db/schema.d.ts +1049 -1076
  22. package/dist/cloud/db/schema.js +59 -71
  23. package/dist/cloud/server.js +854 -18
  24. package/dist/cloud/services/persistence.d.ts +15 -15
  25. package/dist/cloud/services/persistence.js +14 -14
  26. package/dist/daemon/agent-manager.d.ts +6 -5
  27. package/dist/daemon/agent-manager.js +12 -8
  28. package/dist/daemon/channel-membership-store.d.ts +48 -0
  29. package/dist/daemon/channel-membership-store.js +149 -0
  30. package/dist/daemon/cloud-sync.d.ts +2 -0
  31. package/dist/daemon/cloud-sync.js +4 -0
  32. package/dist/daemon/connection.js +17 -9
  33. package/dist/daemon/router.d.ts +37 -0
  34. package/dist/daemon/router.js +318 -79
  35. package/dist/daemon/server.d.ts +15 -0
  36. package/dist/daemon/server.js +141 -3
  37. package/dist/dashboard/out/404.html +1 -0
  38. package/dist/dashboard/out/_next/static/IxxVRv94L1w3ReRGAiI-k/_buildManifest.js +1 -0
  39. package/dist/dashboard/out/_next/static/IxxVRv94L1w3ReRGAiI-k/_ssgManifest.js +1 -0
  40. package/dist/dashboard/out/_next/static/chunks/116-eacf84a131b80db9.js +1 -0
  41. package/dist/dashboard/out/_next/static/chunks/117-c8afed19e821a35d.js +2 -0
  42. package/dist/dashboard/out/_next/static/chunks/282-980c2eb8fff20123.js +1 -0
  43. package/dist/dashboard/out/_next/static/chunks/532-bace199897eeab37.js +9 -0
  44. package/dist/dashboard/out/_next/static/chunks/64-87ab9cd6bcf2f737.js +1 -0
  45. package/dist/dashboard/out/_next/static/chunks/648-acb2ff9f77cbfbd3.js +1 -0
  46. package/dist/dashboard/out/_next/static/chunks/766-aa7c8c9900ff5f53.js +1 -0
  47. package/dist/dashboard/out/_next/static/chunks/83-4f08122d4e7e79a6.js +1 -0
  48. package/dist/dashboard/out/_next/static/chunks/847-f1f467060f32afff.js +1 -0
  49. package/dist/dashboard/out/_next/static/chunks/891-a024fbe4b619cf6f.js +1 -0
  50. package/dist/dashboard/out/_next/static/chunks/app/_not-found/page-60501fddbafba9dc.js +1 -0
  51. package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-f746f29e01fffc43.js +1 -0
  52. package/dist/dashboard/out/_next/static/chunks/app/app/page-ffad986adfcc8b31.js +1 -0
  53. package/dist/dashboard/out/_next/static/chunks/app/cloud/link/page-cfeb437f08a12ed9.js +1 -0
  54. package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-03ac6f35a6654ea6.js +1 -0
  55. package/dist/dashboard/out/_next/static/chunks/app/history/page-240f91e8b06ba8ac.js +1 -0
  56. package/dist/dashboard/out/_next/static/chunks/app/layout-c0d118c0f92d969c.js +1 -0
  57. package/dist/dashboard/out/_next/static/chunks/app/login/page-6ec54eee75877971.js +1 -0
  58. package/dist/dashboard/out/_next/static/chunks/app/metrics/page-82938ab8fcf44694.js +1 -0
  59. package/dist/dashboard/out/_next/static/chunks/app/page-671037943b2f2e43.js +1 -0
  60. package/dist/dashboard/out/_next/static/chunks/app/pricing/page-0efa024c28ba4597.js +1 -0
  61. package/dist/dashboard/out/_next/static/chunks/app/providers/page-57cbd738c6a73859.js +1 -0
  62. package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/page-5ab0854472b402b0.js +1 -0
  63. package/dist/dashboard/out/_next/static/chunks/app/signup/page-18a4665665f6be11.js +1 -0
  64. package/dist/dashboard/out/_next/static/chunks/e868780c-48e5f147c90a3a41.js +18 -0
  65. package/dist/dashboard/out/_next/static/chunks/fd9d1056-609918ca7b6280bb.js +1 -0
  66. package/dist/dashboard/out/_next/static/chunks/framework-f66176bb897dc684.js +1 -0
  67. package/dist/dashboard/out/_next/static/chunks/main-5a40a5ae29646e1b.js +1 -0
  68. package/dist/dashboard/out/_next/static/chunks/main-app-6e8e8d3ef4e0192a.js +1 -0
  69. package/dist/dashboard/out/_next/static/chunks/pages/_app-72b849fbd24ac258.js +1 -0
  70. package/dist/dashboard/out/_next/static/chunks/pages/_error-7ba65e1336b92748.js +1 -0
  71. package/dist/dashboard/out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  72. package/dist/dashboard/out/_next/static/chunks/webpack-1cdd8ed57114d5e1.js +1 -0
  73. package/dist/dashboard/out/_next/static/css/4034f236dd1a3178.css +1 -0
  74. package/dist/dashboard/out/_next/static/css/8f9ed310f454e5a5.css +1 -0
  75. package/dist/dashboard/out/alt-logos/agent-relay-logo-128.png +0 -0
  76. package/dist/dashboard/out/alt-logos/agent-relay-logo-256.png +0 -0
  77. package/dist/dashboard/out/alt-logos/agent-relay-logo-32.png +0 -0
  78. package/dist/dashboard/out/alt-logos/agent-relay-logo-512.png +0 -0
  79. package/dist/dashboard/out/alt-logos/agent-relay-logo-64.png +0 -0
  80. package/dist/dashboard/out/alt-logos/agent-relay-logo.svg +45 -0
  81. package/dist/dashboard/out/alt-logos/logo.svg +38 -0
  82. package/dist/dashboard/out/alt-logos/monogram-logo-128.png +0 -0
  83. package/dist/dashboard/out/alt-logos/monogram-logo-256.png +0 -0
  84. package/dist/dashboard/out/alt-logos/monogram-logo-32.png +0 -0
  85. package/dist/dashboard/out/alt-logos/monogram-logo-512.png +0 -0
  86. package/dist/dashboard/out/alt-logos/monogram-logo-64.png +0 -0
  87. package/dist/dashboard/out/alt-logos/monogram-logo.svg +38 -0
  88. package/dist/dashboard/out/app/onboarding.html +1 -0
  89. package/dist/dashboard/out/app/onboarding.txt +7 -0
  90. package/dist/dashboard/out/app.html +1 -0
  91. package/dist/dashboard/out/app.txt +7 -0
  92. package/dist/dashboard/out/apple-icon.png +0 -0
  93. package/dist/dashboard/out/cloud/link.html +1 -0
  94. package/dist/dashboard/out/cloud/link.txt +7 -0
  95. package/dist/dashboard/out/connect-repos.html +1 -0
  96. package/dist/dashboard/out/connect-repos.txt +7 -0
  97. package/dist/dashboard/out/history.html +1 -0
  98. package/dist/dashboard/out/history.txt +7 -0
  99. package/dist/dashboard/out/index.html +1 -0
  100. package/dist/dashboard/out/index.txt +7 -0
  101. package/dist/dashboard/out/login.html +5 -0
  102. package/dist/dashboard/out/login.txt +7 -0
  103. package/dist/dashboard/out/metrics.html +1 -0
  104. package/dist/dashboard/out/metrics.txt +7 -0
  105. package/dist/dashboard/out/pricing.html +13 -0
  106. package/dist/dashboard/out/pricing.txt +7 -0
  107. package/dist/dashboard/out/providers/setup/claude.html +1 -0
  108. package/dist/dashboard/out/providers/setup/claude.txt +8 -0
  109. package/dist/dashboard/out/providers/setup/codex.html +1 -0
  110. package/dist/dashboard/out/providers/setup/codex.txt +8 -0
  111. package/dist/dashboard/out/providers.html +1 -0
  112. package/dist/dashboard/out/providers.txt +7 -0
  113. package/dist/dashboard/out/signup.html +6 -0
  114. package/dist/dashboard/out/signup.txt +7 -0
  115. package/dist/dashboard-server/metrics.d.ts +105 -0
  116. package/dist/dashboard-server/metrics.js +193 -0
  117. package/dist/dashboard-server/needs-attention.d.ts +24 -0
  118. package/dist/dashboard-server/needs-attention.js +78 -0
  119. package/dist/dashboard-server/server.d.ts +15 -0
  120. package/dist/dashboard-server/server.js +4753 -0
  121. package/dist/dashboard-server/start.d.ts +6 -0
  122. package/dist/dashboard-server/start.js +13 -0
  123. package/dist/dashboard-server/user-bridge.d.ts +132 -0
  124. package/dist/dashboard-server/user-bridge.js +317 -0
  125. package/dist/protocol/channels.d.ts +14 -8
  126. package/dist/protocol/channels.js +1 -1
  127. package/dist/protocol/index.d.ts +1 -0
  128. package/dist/protocol/index.js +1 -0
  129. package/dist/protocol/relay-pty-schemas.d.ts +209 -0
  130. package/dist/protocol/relay-pty-schemas.js +60 -0
  131. package/dist/wrapper/auth-detection.js +8 -1
  132. package/dist/wrapper/base-wrapper.d.ts +11 -1
  133. package/dist/wrapper/base-wrapper.js +67 -6
  134. package/dist/wrapper/client.d.ts +49 -1
  135. package/dist/wrapper/client.js +167 -0
  136. package/dist/wrapper/parser.d.ts +0 -4
  137. package/dist/wrapper/parser.js +38 -10
  138. package/dist/wrapper/pty-wrapper.d.ts +12 -1
  139. package/dist/wrapper/pty-wrapper.js +104 -5
  140. package/dist/wrapper/relay-pty-orchestrator.d.ts +270 -0
  141. package/dist/wrapper/relay-pty-orchestrator.js +970 -0
  142. package/dist/wrapper/shared.d.ts +1 -1
  143. package/dist/wrapper/shared.js +14 -4
  144. package/dist/wrapper/tmux-wrapper.d.ts +13 -1
  145. package/dist/wrapper/tmux-wrapper.js +143 -29
  146. package/package.json +9 -4
  147. package/scripts/postinstall.js +101 -11
  148. package/.trajectories/active/traj_3yx9dy148mge.json +0 -42
  149. package/.trajectories/agent-relay-322-324.md +0 -17
  150. package/.trajectories/completed/2026-01/traj_03zupyv1s7b9.json +0 -49
  151. package/.trajectories/completed/2026-01/traj_03zupyv1s7b9.md +0 -31
  152. package/.trajectories/completed/2026-01/traj_0zacdjl1g4ht.json +0 -125
  153. package/.trajectories/completed/2026-01/traj_0zacdjl1g4ht.md +0 -62
  154. package/.trajectories/completed/2026-01/traj_1dviorhnkcb5.json +0 -65
  155. package/.trajectories/completed/2026-01/traj_1dviorhnkcb5.md +0 -37
  156. package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.json +0 -49
  157. package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.md +0 -31
  158. package/.trajectories/completed/2026-01/traj_1k5if5snst2e.json +0 -65
  159. package/.trajectories/completed/2026-01/traj_1k5if5snst2e.md +0 -37
  160. package/.trajectories/completed/2026-01/traj_1rp3rges5811.json +0 -49
  161. package/.trajectories/completed/2026-01/traj_1rp3rges5811.md +0 -31
  162. package/.trajectories/completed/2026-01/traj_22bhyulruouw.json +0 -113
  163. package/.trajectories/completed/2026-01/traj_22bhyulruouw.md +0 -57
  164. package/.trajectories/completed/2026-01/traj_2dao7ddgnta0.json +0 -53
  165. package/.trajectories/completed/2026-01/traj_2dao7ddgnta0.md +0 -32
  166. package/.trajectories/completed/2026-01/traj_33iuy72sezbk.json +0 -49
  167. package/.trajectories/completed/2026-01/traj_33iuy72sezbk.md +0 -31
  168. package/.trajectories/completed/2026-01/traj_3t0440mjeunc.json +0 -26
  169. package/.trajectories/completed/2026-01/traj_3t0440mjeunc.md +0 -6
  170. package/.trajectories/completed/2026-01/traj_45x9494d9xnr.json +0 -47
  171. package/.trajectories/completed/2026-01/traj_45x9494d9xnr.md +0 -32
  172. package/.trajectories/completed/2026-01/traj_4aa0bb77s4nh.json +0 -53
  173. package/.trajectories/completed/2026-01/traj_4aa0bb77s4nh.md +0 -32
  174. package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.json +0 -49
  175. package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.md +0 -31
  176. package/.trajectories/completed/2026-01/traj_5ammh5qtvklq.json +0 -77
  177. package/.trajectories/completed/2026-01/traj_5ammh5qtvklq.md +0 -42
  178. package/.trajectories/completed/2026-01/traj_5lhmzq8rxpqv.json +0 -59
  179. package/.trajectories/completed/2026-01/traj_5lhmzq8rxpqv.md +0 -33
  180. package/.trajectories/completed/2026-01/traj_5vr4e9erb1fs.json +0 -53
  181. package/.trajectories/completed/2026-01/traj_5vr4e9erb1fs.md +0 -32
  182. package/.trajectories/completed/2026-01/traj_6fgiwdoklvym.json +0 -48
  183. package/.trajectories/completed/2026-01/traj_6fgiwdoklvym.md +0 -24
  184. package/.trajectories/completed/2026-01/traj_6mieijqyvaag.json +0 -77
  185. package/.trajectories/completed/2026-01/traj_6mieijqyvaag.md +0 -42
  186. package/.trajectories/completed/2026-01/traj_6unwwmgyj5sq.json +0 -109
  187. package/.trajectories/completed/2026-01/traj_78ffm31jn3uk.json +0 -77
  188. package/.trajectories/completed/2026-01/traj_78ffm31jn3uk.md +0 -42
  189. package/.trajectories/completed/2026-01/traj_7ludwvz45veh.json +0 -209
  190. package/.trajectories/completed/2026-01/traj_7ludwvz45veh.md +0 -97
  191. package/.trajectories/completed/2026-01/traj_94gnp3k30goq.json +0 -66
  192. package/.trajectories/completed/2026-01/traj_94gnp3k30goq.md +0 -36
  193. package/.trajectories/completed/2026-01/traj_9921cuhel0pj.json +0 -48
  194. package/.trajectories/completed/2026-01/traj_9921cuhel0pj.md +0 -24
  195. package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.json +0 -49
  196. package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.md +0 -31
  197. package/.trajectories/completed/2026-01/traj_ajs7zqfux4wc.json +0 -49
  198. package/.trajectories/completed/2026-01/traj_ajs7zqfux4wc.md +0 -23
  199. package/.trajectories/completed/2026-01/traj_avqeghu6pz5a.json +0 -40
  200. package/.trajectories/completed/2026-01/traj_avqeghu6pz5a.md +0 -22
  201. package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.json +0 -66
  202. package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.md +0 -36
  203. package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.json +0 -49
  204. package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.md +0 -31
  205. package/.trajectories/completed/2026-01/traj_cpn70dw066nt.json +0 -65
  206. package/.trajectories/completed/2026-01/traj_cpn70dw066nt.md +0 -37
  207. package/.trajectories/completed/2026-01/traj_cvtqhlwcq9s0.json +0 -53
  208. package/.trajectories/completed/2026-01/traj_cvtqhlwcq9s0.md +0 -32
  209. package/.trajectories/completed/2026-01/traj_cxofprm2m2en.json +0 -49
  210. package/.trajectories/completed/2026-01/traj_cxofprm2m2en.md +0 -31
  211. package/.trajectories/completed/2026-01/traj_d2hhz3k0vrhn.json +0 -26
  212. package/.trajectories/completed/2026-01/traj_d2hhz3k0vrhn.md +0 -6
  213. package/.trajectories/completed/2026-01/traj_dcsp9s8y01ra.json +0 -121
  214. package/.trajectories/completed/2026-01/traj_dcsp9s8y01ra.md +0 -29
  215. package/.trajectories/completed/2026-01/traj_dfuvww9pege5.json +0 -59
  216. package/.trajectories/completed/2026-01/traj_dfuvww9pege5.md +0 -37
  217. package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.json +0 -36
  218. package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.md +0 -21
  219. package/.trajectories/completed/2026-01/traj_fhx9irlckht6.json +0 -53
  220. package/.trajectories/completed/2026-01/traj_fhx9irlckht6.md +0 -32
  221. package/.trajectories/completed/2026-01/traj_fqduidx3xbtp.json +0 -101
  222. package/.trajectories/completed/2026-01/traj_fqduidx3xbtp.md +0 -52
  223. package/.trajectories/completed/2026-01/traj_g0fisy9h51mf.json +0 -77
  224. package/.trajectories/completed/2026-01/traj_g0fisy9h51mf.md +0 -42
  225. package/.trajectories/completed/2026-01/traj_gjdre5voouod.json +0 -53
  226. package/.trajectories/completed/2026-01/traj_gjdre5voouod.md +0 -32
  227. package/.trajectories/completed/2026-01/traj_gtlyqtta3x8l.json +0 -25
  228. package/.trajectories/completed/2026-01/traj_gtlyqtta3x8l.md +0 -15
  229. package/.trajectories/completed/2026-01/traj_h4xijiuip3w4.json +0 -101
  230. package/.trajectories/completed/2026-01/traj_h4xijiuip3w4.md +0 -44
  231. package/.trajectories/completed/2026-01/traj_he75f24d1xfm.json +0 -101
  232. package/.trajectories/completed/2026-01/traj_he75f24d1xfm.md +0 -52
  233. package/.trajectories/completed/2026-01/traj_hf81ey93uz6t.json +0 -49
  234. package/.trajectories/completed/2026-01/traj_hf81ey93uz6t.md +0 -31
  235. package/.trajectories/completed/2026-01/traj_hfmki2jr9d4r.json +0 -65
  236. package/.trajectories/completed/2026-01/traj_hfmki2jr9d4r.md +0 -37
  237. package/.trajectories/completed/2026-01/traj_hhxte7w4gjjx.json +0 -22
  238. package/.trajectories/completed/2026-01/traj_hhxte7w4gjjx.md +0 -5
  239. package/.trajectories/completed/2026-01/traj_hpungyhoj6v5.json +0 -53
  240. package/.trajectories/completed/2026-01/traj_hpungyhoj6v5.md +0 -32
  241. package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.json +0 -61
  242. package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.md +0 -36
  243. package/.trajectories/completed/2026-01/traj_lq450ly148uw.json +0 -49
  244. package/.trajectories/completed/2026-01/traj_lq450ly148uw.md +0 -31
  245. package/.trajectories/completed/2026-01/traj_m2xkjv0w2sq7.json +0 -25
  246. package/.trajectories/completed/2026-01/traj_m2xkjv0w2sq7.md +0 -15
  247. package/.trajectories/completed/2026-01/traj_multi_server_arch.md +0 -101
  248. package/.trajectories/completed/2026-01/traj_noq5zbvnrdvz.json +0 -53
  249. package/.trajectories/completed/2026-01/traj_noq5zbvnrdvz.md +0 -32
  250. package/.trajectories/completed/2026-01/traj_ntbs6ppopf46.json +0 -53
  251. package/.trajectories/completed/2026-01/traj_ntbs6ppopf46.md +0 -32
  252. package/.trajectories/completed/2026-01/traj_oszg9flv74pk.json +0 -73
  253. package/.trajectories/completed/2026-01/traj_oszg9flv74pk.md +0 -41
  254. package/.trajectories/completed/2026-01/traj_ozd98si6a7ns.json +0 -48
  255. package/.trajectories/completed/2026-01/traj_ozd98si6a7ns.md +0 -24
  256. package/.trajectories/completed/2026-01/traj_prdza7a5cxp5.json +0 -53
  257. package/.trajectories/completed/2026-01/traj_prdza7a5cxp5.md +0 -32
  258. package/.trajectories/completed/2026-01/traj_psd9ob0j2ru3.json +0 -27
  259. package/.trajectories/completed/2026-01/traj_psd9ob0j2ru3.md +0 -14
  260. package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.json +0 -77
  261. package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.md +0 -42
  262. package/.trajectories/completed/2026-01/traj_qb3twvvywfwi.json +0 -77
  263. package/.trajectories/completed/2026-01/traj_qb3twvvywfwi.md +0 -42
  264. package/.trajectories/completed/2026-01/traj_qft54mi7nfor.json +0 -53
  265. package/.trajectories/completed/2026-01/traj_qft54mi7nfor.md +0 -32
  266. package/.trajectories/completed/2026-01/traj_qx9uhf8whhxo.json +0 -83
  267. package/.trajectories/completed/2026-01/traj_qx9uhf8whhxo.md +0 -47
  268. package/.trajectories/completed/2026-01/traj_rd9toccj18a0.json +0 -59
  269. package/.trajectories/completed/2026-01/traj_rd9toccj18a0.md +0 -37
  270. package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.json +0 -109
  271. package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.md +0 -56
  272. package/.trajectories/completed/2026-01/traj_rt4fiw3ecp50.json +0 -48
  273. package/.trajectories/completed/2026-01/traj_rt4fiw3ecp50.md +0 -16
  274. package/.trajectories/completed/2026-01/traj_st8j35b0hrlc.json +0 -59
  275. package/.trajectories/completed/2026-01/traj_st8j35b0hrlc.md +0 -37
  276. package/.trajectories/completed/2026-01/traj_t1yy8m7hbuxp.json +0 -53
  277. package/.trajectories/completed/2026-01/traj_t1yy8m7hbuxp.md +0 -32
  278. package/.trajectories/completed/2026-01/traj_tmux_orchestrator_analysis.json +0 -84
  279. package/.trajectories/completed/2026-01/traj_tmux_orchestrator_analysis.md +0 -109
  280. package/.trajectories/completed/2026-01/traj_u9n9eqasw16k.json +0 -53
  281. package/.trajectories/completed/2026-01/traj_u9n9eqasw16k.md +0 -32
  282. package/.trajectories/completed/2026-01/traj_ub8csuv3lcv4.json +0 -53
  283. package/.trajectories/completed/2026-01/traj_ub8csuv3lcv4.md +0 -32
  284. package/.trajectories/completed/2026-01/traj_uc29tlso8i9s.json +0 -186
  285. package/.trajectories/completed/2026-01/traj_uc29tlso8i9s.md +0 -86
  286. package/.trajectories/completed/2026-01/traj_ui9b4tqxoa7j.json +0 -77
  287. package/.trajectories/completed/2026-01/traj_ui9b4tqxoa7j.md +0 -42
  288. package/.trajectories/completed/2026-01/traj_v87hypnongqx.json +0 -71
  289. package/.trajectories/completed/2026-01/traj_v87hypnongqx.md +0 -42
  290. package/.trajectories/completed/2026-01/traj_v9dkdoxylyid.json +0 -89
  291. package/.trajectories/completed/2026-01/traj_v9dkdoxylyid.md +0 -47
  292. package/.trajectories/completed/2026-01/traj_wkp2fgzdyinb.json +0 -53
  293. package/.trajectories/completed/2026-01/traj_wkp2fgzdyinb.md +0 -32
  294. package/.trajectories/completed/2026-01/traj_x14t8w8rn7xg.json +0 -20
  295. package/.trajectories/completed/2026-01/traj_x14t8w8rn7xg.md +0 -6
  296. package/.trajectories/completed/2026-01/traj_x721m1j9rzup.json +0 -113
  297. package/.trajectories/completed/2026-01/traj_x721m1j9rzup.md +0 -57
  298. package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.json +0 -61
  299. package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.md +0 -36
  300. package/.trajectories/completed/2026-01/traj_xnwbznkvv8ua.json +0 -175
  301. package/.trajectories/completed/2026-01/traj_xnwbznkvv8ua.md +0 -82
  302. package/.trajectories/completed/2026-01/traj_xy9vifpqet80.json +0 -65
  303. package/.trajectories/completed/2026-01/traj_xy9vifpqet80.md +0 -37
  304. package/.trajectories/completed/2026-01/traj_y7aiwijyfmmv.json +0 -49
  305. package/.trajectories/completed/2026-01/traj_y7aiwijyfmmv.md +0 -31
  306. package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.json +0 -49
  307. package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.md +0 -31
  308. package/.trajectories/completed/2026-01/traj_ysjc8zaeqtd3.json +0 -47
  309. package/.trajectories/completed/2026-01/traj_ysjc8zaeqtd3.md +0 -32
  310. package/.trajectories/completed/2026-01/traj_yvdadtvdgnz3.json +0 -59
  311. package/.trajectories/completed/2026-01/traj_yvdadtvdgnz3.md +0 -37
  312. package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.json +0 -49
  313. package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.md +0 -31
  314. package/.trajectories/completed/2026-01/traj_z0vcw1wrzide.json +0 -53
  315. package/.trajectories/completed/2026-01/traj_z0vcw1wrzide.md +0 -32
  316. package/.trajectories/consolidate-settings-panel.md +0 -24
  317. package/.trajectories/gh-cli-user-token.md +0 -26
  318. package/.trajectories/index.json +0 -607
package/dist/cli/index.js CHANGED
@@ -3,17 +3,21 @@
3
3
  * Agent Relay CLI
4
4
  *
5
5
  * Commands:
6
- * relay <cmd> - Wrap agent with real-time messaging (default)
7
- * relay -n Name cmd - Wrap with specific agent name
8
- * relay up - Start daemon
9
- * relay read <id> - Read full message by ID
10
- * relay agents - List connected agents
11
- * relay who - Show currently active agents
6
+ * relay claude - Start daemon + Dashboard coordinator with Claude
7
+ * relay codex - Start daemon + Dasbboard coordinator with Codex
8
+ * relay create-agent <cmd> - Wrap agent with real-time messaging
9
+ * relay create-agent -n Name cmd - Wrap with specific agent name
10
+ * relay up - Start daemon + dashboard
11
+ * relay read <id> - Read full message by ID
12
+ * relay agents - List connected agents
13
+ * relay who - Show currently active agents
12
14
  */
13
15
  import { Command } from 'commander';
14
16
  import { config as dotenvConfig } from 'dotenv';
15
17
  import { Daemon } from '../daemon/server.js';
16
18
  import { RelayClient } from '../wrapper/client.js';
19
+ import { RelayPtyOrchestrator } from '../wrapper/relay-pty-orchestrator.js';
20
+ import { AgentSpawner } from '../bridge/spawner.js';
17
21
  import { generateAgentName } from '../utils/name-generator.js';
18
22
  import { getTmuxPath } from '../utils/tmux-resolver.js';
19
23
  import { readWorkersMetadata, getWorkerLogsDir } from '../bridge/spawner.js';
@@ -26,6 +30,7 @@ import { promisify } from 'node:util';
26
30
  import { exec } from 'node:child_process';
27
31
  import { fileURLToPath } from 'node:url';
28
32
  dotenvConfig();
33
+ const DEFAULT_DASHBOARD_PORT = process.env.AGENT_RELAY_DASHBOARD_PORT || '3888';
29
34
  // Read version from package.json
30
35
  const __filename = fileURLToPath(import.meta.url);
31
36
  const __dirname = path.dirname(__filename);
@@ -35,7 +40,7 @@ const VERSION = packageJson.version;
35
40
  const execAsync = promisify(exec);
36
41
  // Check for updates in background (non-blocking)
37
42
  // Only show notification for interactive commands, not when wrapping agents or running update
38
- const interactiveCommands = ['up', 'down', 'status', 'agents', 'who', 'version', '--version', '-V', '--help', '-h'];
43
+ const interactiveCommands = ['up', 'down', 'status', 'agents', 'who', 'version', '--version', '-V', '--help', '-h', 'create-agent', 'claude', 'codex'];
39
44
  const shouldCheckUpdates = process.argv.length > 2 &&
40
45
  interactiveCommands.includes(process.argv[2]);
41
46
  if (shouldCheckUpdates) {
@@ -49,20 +54,19 @@ program
49
54
  .name('agent-relay')
50
55
  .description('Agent-to-agent messaging')
51
56
  .version(VERSION, '-V, --version', 'Output the version number');
52
- // Default action = wrap agent
57
+ // create-agent - Wrap agent with real-time messaging
53
58
  program
59
+ .command('create-agent')
60
+ .description('Wrap an agent with real-time messaging')
54
61
  .option('-n, --name <name>', 'Agent name (auto-generated if not set)')
55
- .option('-q, --quiet', 'Disable debug output', false)
62
+ .option('-d, --debug', 'Enable debug output')
56
63
  .option('--prefix <pattern>', 'Relay prefix pattern (default: ->relay:)')
64
+ .option('--dashboard-port <port>', 'Dashboard port for spawn/release API (auto-detected if not set)')
57
65
  .option('--shadow <name>', 'Spawn a shadow agent with this name that monitors the primary')
58
66
  .option('--shadow-role <role>', 'Shadow role: reviewer, auditor, or triggers (comma-separated: SESSION_END,CODE_WRITTEN,REVIEW_REQUEST,EXPLICIT_ASK,ALL_MESSAGES)')
59
- .argument('[command...]', 'Command to wrap (e.g., claude)')
67
+ .option('--skip-instructions', 'Skip initial instruction injection (use with --append-system-prompt)')
68
+ .argument('<command...>', 'Command to wrap (e.g., claude)')
60
69
  .action(async (commandParts, options) => {
61
- // If no command provided, show help
62
- if (!commandParts || commandParts.length === 0) {
63
- program.help();
64
- return;
65
- }
66
70
  const { getProjectPaths } = await import('../utils/project-namespace.js');
67
71
  const { findAgentConfig, isClaudeCli, buildClaudeArgs } = await import('../utils/agent-config.js');
68
72
  const paths = getProjectPaths();
@@ -82,20 +86,52 @@ program
82
86
  finalArgs = buildClaudeArgs(agentName, commandArgs, paths.projectRoot);
83
87
  }
84
88
  }
85
- const { TmuxWrapper } = await import('../wrapper/tmux-wrapper.js');
86
- const { AgentSpawner } = await import('../bridge/spawner.js');
87
- // Create spawner for spawn/release callbacks
88
- const spawner = new AgentSpawner(paths.projectRoot);
89
- const wrapper = new TmuxWrapper({
89
+ // Determine dashboard port for spawn/release API
90
+ // Priority: CLI flag > env var > auto-detect default port
91
+ let dashboardPort;
92
+ if (options.dashboardPort) {
93
+ dashboardPort = parseInt(options.dashboardPort, 10);
94
+ }
95
+ else {
96
+ // Try to detect if dashboard is running at common ports
97
+ const portsToTry = [
98
+ parseInt(DEFAULT_DASHBOARD_PORT, 10),
99
+ 3889, 3890, 3891, // Common fallback ports when default is in use
100
+ ];
101
+ for (const port of portsToTry) {
102
+ try {
103
+ const response = await fetch(`http://localhost:${port}/api/health`, {
104
+ method: 'GET',
105
+ signal: AbortSignal.timeout(300), // Quick timeout for detection
106
+ });
107
+ if (response.ok) {
108
+ const health = await response.json();
109
+ if (health.status === 'healthy') {
110
+ dashboardPort = port;
111
+ console.error(`Dashboard detected: http://localhost:${dashboardPort}`);
112
+ break;
113
+ }
114
+ }
115
+ }
116
+ catch {
117
+ // Try next port
118
+ }
119
+ }
120
+ }
121
+ // Create spawner as fallback for direct spawn (if dashboard API not available)
122
+ const spawner = new AgentSpawner(paths.projectRoot, undefined, dashboardPort);
123
+ const wrapper = new RelayPtyOrchestrator({
90
124
  name: agentName,
91
125
  command: mainCommand,
92
126
  args: finalArgs,
93
127
  socketPath: paths.socketPath,
94
- debug: false, // Use -q to keep quiet (debug off by default)
128
+ cwd: paths.projectRoot,
95
129
  relayPrefix: options.prefix,
96
- useInbox: true,
97
- inboxDir: paths.dataDir, // Use the project-specific data directory for the inbox
98
- // Wire up spawn/release callbacks
130
+ skipInstructions: options.skipInstructions,
131
+ streamLogs: true,
132
+ // Use dashboard API for spawn/release when available (preferred - works from any context)
133
+ dashboardPort,
134
+ // Wire up spawn/release callbacks as fallback (if no dashboardPort)
99
135
  onSpawn: async (workerName, workerCli, task) => {
100
136
  console.error(`[${agentName}] Spawning ${workerName} (${workerCli})...`);
101
137
  const result = await spawner.spawn({
@@ -199,10 +235,12 @@ program
199
235
  }
200
236
  }
201
237
  });
202
- // up - Start daemon
238
+ // up - Start daemon + dashboard
203
239
  program
204
240
  .command('up')
205
- .description('Start daemon')
241
+ .description('Start daemon + dashboard')
242
+ .option('--no-dashboard', 'Disable web dashboard')
243
+ .option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
206
244
  .option('--spawn', 'Force spawn all agents from teams.json')
207
245
  .option('--no-spawn', 'Do not auto-spawn agents (just start daemon)')
208
246
  .option('--watch', 'Auto-restart daemon on crash (supervisor mode)')
@@ -219,6 +257,10 @@ program
219
257
  const startDaemon = () => {
220
258
  // Build args without --watch to prevent infinite recursion
221
259
  const args = ['up'];
260
+ if (options.dashboard === false)
261
+ args.push('--no-dashboard');
262
+ if (options.port)
263
+ args.push('--port', options.port);
222
264
  if (options.spawn === true)
223
265
  args.push('--spawn');
224
266
  if (options.spawn === false)
@@ -330,6 +372,28 @@ program
330
372
  try {
331
373
  await daemon.start();
332
374
  console.log('Daemon started.');
375
+ let dashboardPort;
376
+ // Dashboard starts by default (use --no-dashboard to disable)
377
+ if (options.dashboard !== false) {
378
+ const port = parseInt(options.port, 10);
379
+ const { startDashboard } = await import('../dashboard-server/server.js');
380
+ dashboardPort = await startDashboard({
381
+ port,
382
+ dataDir: paths.dataDir,
383
+ teamDir: paths.teamDir,
384
+ dbPath,
385
+ enableSpawner: true,
386
+ projectRoot: paths.projectRoot,
387
+ });
388
+ console.log(`Dashboard: http://localhost:${dashboardPort}`);
389
+ // Hook daemon log output to dashboard WebSocket
390
+ daemon.onLogOutput = (agentName, data, _timestamp) => {
391
+ const broadcast = global.__broadcastLogOutput;
392
+ if (broadcast) {
393
+ broadcast(agentName, data);
394
+ }
395
+ };
396
+ }
333
397
  // Determine if we should auto-spawn agents
334
398
  // --spawn: force spawn
335
399
  // --no-spawn: never spawn
@@ -342,7 +406,7 @@ program
342
406
  if (shouldSpawn && teamsConfig && teamsConfig.agents.length > 0) {
343
407
  console.log('');
344
408
  console.log('Auto-spawning agents from teams.json...');
345
- spawner = new AgentSpawner(paths.projectRoot);
409
+ spawner = new AgentSpawner(paths.projectRoot, undefined, dashboardPort);
346
410
  for (const agent of teamsConfig.agents) {
347
411
  console.log(` Spawning ${agent.name} (${agent.cli})...`);
348
412
  const result = await spawner.spawn({
@@ -393,6 +457,153 @@ program
393
457
  console.log('Cleaned up stale pid');
394
458
  }
395
459
  });
460
+ // System prompt for Dashboard agent - plain text to avoid shell escaping issues
461
+ const MEGA_SYSTEM_PROMPT = [
462
+ 'You are Dashboard, a lead coordinator in agent-relay.',
463
+ 'Your PRIMARY job is to delegate - you should almost NEVER do implementation work yourself.',
464
+ 'ALWAYS SPAWN AGENTS: For any non-trivial task, spawn specialized workers.',
465
+ ].join(' ');
466
+ // Helper function for starting Dashboard coordinator with a specific provider
467
+ async function startDashboardCoordinator(operator) {
468
+ const { spawn } = await import('node:child_process');
469
+ const { getProjectPaths } = await import('../utils/project-namespace.js');
470
+ const paths = getProjectPaths();
471
+ console.log(`Starting Dashboard with ${operator}...`);
472
+ console.log(`Project: ${paths.projectRoot}`);
473
+ // Step 1: Check if daemon is already running, start if needed
474
+ console.log('\n[1/3] Checking daemon...');
475
+ // Check if socket exists (daemon running)
476
+ const socketExists = fs.existsSync(paths.socketPath);
477
+ // Ports to try for dashboard detection
478
+ const portsToTry = [
479
+ parseInt(DEFAULT_DASHBOARD_PORT, 10),
480
+ 3889, 3890, 3891,
481
+ ];
482
+ // Check if dashboard is responding for THIS project
483
+ let dashboardReady = false;
484
+ let detectedPort;
485
+ // Helper to check health at a port
486
+ const checkPort = async (port) => {
487
+ try {
488
+ const response = await fetch(`http://localhost:${port}/api/health`, {
489
+ signal: AbortSignal.timeout(500),
490
+ });
491
+ if (response.ok) {
492
+ const health = await response.json();
493
+ return health.status === 'healthy';
494
+ }
495
+ }
496
+ catch {
497
+ // Port not responding
498
+ }
499
+ return false;
500
+ };
501
+ if (socketExists) {
502
+ for (const port of portsToTry) {
503
+ if (await checkPort(port)) {
504
+ dashboardReady = true;
505
+ detectedPort = port;
506
+ break;
507
+ }
508
+ }
509
+ }
510
+ if (dashboardReady && detectedPort) {
511
+ console.log(`Daemon already running at port ${detectedPort}, reusing...`);
512
+ }
513
+ else {
514
+ console.log('Starting daemon...');
515
+ const daemonProc = spawn(process.execPath, [process.argv[1], 'up'], {
516
+ stdio: 'ignore',
517
+ detached: true,
518
+ });
519
+ daemonProc.unref();
520
+ // Wait for dashboard to be ready (up to 10 seconds)
521
+ const maxWait = 10000;
522
+ const startTime = Date.now();
523
+ while (Date.now() - startTime < maxWait) {
524
+ await new Promise((resolve) => setTimeout(resolve, 500));
525
+ for (const port of portsToTry) {
526
+ if (await checkPort(port)) {
527
+ dashboardReady = true;
528
+ detectedPort = port;
529
+ break;
530
+ }
531
+ }
532
+ if (dashboardReady)
533
+ break;
534
+ }
535
+ if (!dashboardReady) {
536
+ console.error('Warning: Dashboard may not be fully ready. Spawn might not work.');
537
+ detectedPort = parseInt(DEFAULT_DASHBOARD_PORT, 10); // Fallback
538
+ }
539
+ }
540
+ const dashboardPort = detectedPort || parseInt(DEFAULT_DASHBOARD_PORT, 10);
541
+ // Step 2: Install prpm snippet via npx
542
+ console.log('[2/3] Installing agent-relay snippet...');
543
+ const prpmArgs = operator.toLowerCase() === 'claude'
544
+ ? ['prpm', 'install', '@agent-relay/agent-relay-snippet', '--location', 'CLAUDE.md']
545
+ : ['prpm', 'install', '@agent-relay/agent-relay-snippet'];
546
+ try {
547
+ await new Promise((resolve, reject) => {
548
+ const prpmProc = spawn('npx', prpmArgs, {
549
+ stdio: 'inherit',
550
+ });
551
+ prpmProc.on('close', (code) => {
552
+ if (code === 0)
553
+ resolve();
554
+ else
555
+ reject(new Error(`npx prpm exited with code ${code}`));
556
+ });
557
+ prpmProc.on('error', reject);
558
+ });
559
+ }
560
+ catch (err) {
561
+ console.warn(`Warning: prpm install failed: ${err.message}`);
562
+ console.warn('Continuing without snippet installation...');
563
+ }
564
+ // Step 3: Start Dashboard agent with system prompt
565
+ console.log(`[3/3] Starting Dashboard agent with ${operator}...`);
566
+ console.log('');
567
+ const op = operator.toLowerCase();
568
+ // Build CLI-specific arguments for system prompt
569
+ // These args go AFTER the operator command, passed through to the CLI
570
+ let cliArgs = [];
571
+ if (op === 'claude') {
572
+ // Claude: --append-system-prompt <content> (takes content directly, not file)
573
+ cliArgs = ['--append-system-prompt', MEGA_SYSTEM_PROMPT];
574
+ }
575
+ else if (op === 'codex') {
576
+ // Codex: --config developer_instructions="<content>"
577
+ cliArgs = ['--config', `developer_instructions=${MEGA_SYSTEM_PROMPT}`];
578
+ }
579
+ // Use '--' to separate agent-relay options from the command + its args
580
+ // Format: agent-relay create-agent -n Dashboard --skip-instructions --dashboard-port <port> -- claude --append-system-prompt "..."
581
+ const agentProc = spawn(process.execPath, [process.argv[1], 'create-agent', '-n', 'Dashboard', '--skip-instructions', '--dashboard-port', String(dashboardPort), '--', operator, ...cliArgs], { stdio: 'inherit' });
582
+ // Forward signals to agent process
583
+ process.on('SIGINT', () => {
584
+ agentProc.kill('SIGINT');
585
+ });
586
+ process.on('SIGTERM', () => {
587
+ agentProc.kill('SIGTERM');
588
+ });
589
+ agentProc.on('close', (code) => {
590
+ process.exit(code ?? 0);
591
+ });
592
+ }
593
+ // claude - Start daemon and spawn Dashboard coordinator with Claude
594
+ program
595
+ .command('claude')
596
+ .description('Start daemon and Dashboard coordinator with Claude')
597
+ .action(async () => {
598
+ await startDashboardCoordinator('claude');
599
+ });
600
+ // codex - Start daemon and spawn Dashboard coordinator with Codex
601
+ program
602
+ .command('codex')
603
+ .description('Start daemon and Dashboard coordinator with Codex')
604
+ .action(async () => {
605
+ await startDashboardCoordinator('codex');
606
+ });
396
607
  // status - Check daemon status
397
608
  program
398
609
  .command('status')
@@ -435,6 +646,7 @@ program
435
646
  const os = await import('node:os');
436
647
  const paths = getProjectPaths();
437
648
  const agentsPath = path.join(paths.teamDir, 'agents.json');
649
+ const connectedAgentsPath = path.join(paths.teamDir, 'connected-agents.json');
438
650
  // Load registered agents
439
651
  const allAgents = loadAgents(agentsPath);
440
652
  const agents = options.all
@@ -442,13 +654,41 @@ program
442
654
  : allAgents.filter(isVisibleAgent);
443
655
  // Load spawned workers
444
656
  const workers = readWorkersMetadata(paths.projectRoot);
657
+ // Load currently connected agents (agents with active socket connections)
658
+ let connectedAgentNames = new Set();
659
+ let connectedUpdatedAt = 0;
660
+ try {
661
+ if (fs.existsSync(connectedAgentsPath)) {
662
+ const connectedData = JSON.parse(fs.readFileSync(connectedAgentsPath, 'utf-8'));
663
+ connectedAgentNames = new Set([
664
+ ...(connectedData.agents || []),
665
+ ...(connectedData.users || []),
666
+ ]);
667
+ connectedUpdatedAt = connectedData.updatedAt || 0;
668
+ }
669
+ }
670
+ catch {
671
+ // If file doesn't exist or is invalid, fall back to registry-based status
672
+ }
673
+ // Check if connected-agents.json is fresh (within 30 seconds)
674
+ const isConnectedAgentsStale = Date.now() - connectedUpdatedAt > 30_000;
445
675
  const combined = [];
446
676
  // Add registered agents
447
677
  agents.forEach((agent) => {
448
678
  const worker = workers.find(w => w.name === agent.name);
679
+ // Use connected-agents.json if fresh, otherwise fall back to registry lastSeen
680
+ let status;
681
+ if (!isConnectedAgentsStale && connectedAgentNames.size > 0) {
682
+ // We have fresh connected-agents data - use actual connection status
683
+ status = connectedAgentNames.has(agent.name ?? '') ? 'ONLINE' : 'OFFLINE';
684
+ }
685
+ else {
686
+ // Fall back to registry-based status
687
+ status = getAgentStatus(agent);
688
+ }
449
689
  combined.push({
450
690
  name: agent.name ?? 'unknown',
451
- status: getAgentStatus(agent),
691
+ status,
452
692
  cli: agent.cli ?? '-',
453
693
  lastSeen: agent.lastSeen,
454
694
  team: worker?.team,
@@ -856,7 +1096,6 @@ program
856
1096
  // Spawn architect agent if --architect flag is set
857
1097
  let architectWrapper = null;
858
1098
  if (options.architect !== undefined) {
859
- const { TmuxWrapper } = await import('../wrapper/tmux-wrapper.js');
860
1099
  // Determine CLI to use (default to claude)
861
1100
  const architectCli = typeof options.architect === 'string' ? options.architect : 'claude';
862
1101
  // Use first project as the base for the architect
@@ -923,14 +1162,13 @@ Start by greeting the project leads and asking for status updates.`;
923
1162
  command = architectCli;
924
1163
  }
925
1164
  try {
926
- architectWrapper = new TmuxWrapper({
1165
+ architectWrapper = new RelayPtyOrchestrator({
927
1166
  name: 'Architect',
928
1167
  command,
929
1168
  args,
930
1169
  socketPath: basePaths.socketPath,
931
- debug: false,
932
- useInbox: true,
933
- inboxDir: basePaths.dataDir,
1170
+ cwd: baseProject.path,
1171
+ streamLogs: true,
934
1172
  });
935
1173
  await architectWrapper.start();
936
1174
  // Wait for agent to be ready, then inject the prompt
@@ -938,7 +1176,6 @@ Start by greeting the project leads and asking for status updates.`;
938
1176
  try {
939
1177
  await architectWrapper.injectMessage(architectPrompt);
940
1178
  console.log('Architect agent started and initialized.');
941
- console.log('Attach to session: tmux attach -t relay-Architect');
942
1179
  console.log('');
943
1180
  }
944
1181
  catch (err) {
@@ -1293,7 +1530,7 @@ program
1293
1530
  .argument('<name>', 'Agent name')
1294
1531
  .argument('<cli>', 'CLI to use (claude, codex, gemini, etc.)')
1295
1532
  .argument('[task]', 'Task description (can also be piped via stdin)')
1296
- .option('--port <port>', 'Dashboard port', '3888')
1533
+ .option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
1297
1534
  .option('--team <team>', 'Team name for the agent')
1298
1535
  .option('--spawner <name>', 'Name of the agent requesting the spawn (for policy enforcement)')
1299
1536
  .option('--interactive', 'Disable auto-accept of permission prompts (for auth setup flows)')
@@ -1304,7 +1541,7 @@ program
1304
1541
  .option('--shadow-triggers <triggers>', 'When to trigger shadow (comma-separated: SESSION_END,CODE_WRITTEN,REVIEW_REQUEST,EXPLICIT_ASK,ALL_MESSAGES)')
1305
1542
  .option('--shadow-speak-on <triggers>', 'When shadow should speak (comma-separated, same values as --shadow-triggers)')
1306
1543
  .action(async (name, cli, task, options) => {
1307
- const port = options.port || '3888';
1544
+ const port = options.port || DEFAULT_DASHBOARD_PORT;
1308
1545
  // Read task from stdin if not provided as argument
1309
1546
  let finalTask = task;
1310
1547
  if (!finalTask && !process.stdin.isTTY) {
@@ -1390,9 +1627,9 @@ program
1390
1627
  .command('release')
1391
1628
  .description('Release a spawned agent via API (no terminal required)')
1392
1629
  .argument('<name>', 'Agent name to release')
1393
- .option('--port <port>', 'Dashboard port', '3888')
1630
+ .option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
1394
1631
  .action(async (name, options) => {
1395
- const port = options.port || '3888';
1632
+ const port = options.port || DEFAULT_DASHBOARD_PORT;
1396
1633
  try {
1397
1634
  const response = await fetch(`http://localhost:${port}/api/spawned/${encodeURIComponent(name)}`, {
1398
1635
  method: 'DELETE',
@@ -1960,12 +2197,12 @@ program
1960
2197
  .command('metrics')
1961
2198
  .description('Show agent memory metrics and resource usage')
1962
2199
  .option('--agent <name>', 'Show metrics for specific agent')
1963
- .option('--port <port>', 'Dashboard port', '3888')
2200
+ .option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
1964
2201
  .option('--json', 'Output as JSON')
1965
2202
  .option('--watch', 'Continuously update metrics')
1966
2203
  .option('--interval <ms>', 'Update interval for watch mode', '5000')
1967
2204
  .action(async (options) => {
1968
- const port = options.port || '3888';
2205
+ const port = options.port || DEFAULT_DASHBOARD_PORT;
1969
2206
  const fetchMetrics = async () => {
1970
2207
  try {
1971
2208
  const response = await fetch(`http://localhost:${port}/api/metrics/agents`);
@@ -2079,12 +2316,12 @@ program
2079
2316
  program
2080
2317
  .command('health')
2081
2318
  .description('Show system health, crash insights, and recommendations')
2082
- .option('--port <port>', 'Dashboard port', '3888')
2319
+ .option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
2083
2320
  .option('--json', 'Output as JSON')
2084
2321
  .option('--crashes', 'Show recent crash history')
2085
2322
  .option('--alerts', 'Show unacknowledged alerts')
2086
2323
  .action(async (options) => {
2087
- const port = options.port || '3888';
2324
+ const port = options.port || DEFAULT_DASHBOARD_PORT;
2088
2325
  try {
2089
2326
  const response = await fetch(`http://localhost:${port}/api/metrics/health`);
2090
2327
  if (!response.ok) {
@@ -2227,16 +2464,14 @@ program
2227
2464
  console.log('');
2228
2465
  // Use the regular wrapper but with profiling environment
2229
2466
  const paths = getProjectPaths();
2230
- const { TmuxWrapper } = await import('../wrapper/tmux-wrapper.js');
2231
- const wrapper = new TmuxWrapper({
2467
+ const wrapper = new RelayPtyOrchestrator({
2232
2468
  name: agentName,
2233
2469
  command: cmd,
2234
2470
  args,
2235
2471
  socketPath: paths.socketPath,
2236
- debug: true,
2472
+ cwd: paths.projectRoot,
2237
2473
  env: profileEnv,
2238
- useInbox: true,
2239
- inboxDir: paths.dataDir,
2474
+ streamLogs: true,
2240
2475
  });
2241
2476
  // Start memory sampling
2242
2477
  const sampleInterval = setInterval(() => {
@@ -161,6 +161,8 @@ daemonsRouter.get('/workspace/:workspaceId/agents', requireAuth, async (req, res
161
161
  name: agent.name,
162
162
  status: agent.status,
163
163
  isLocal: true,
164
+ isHuman: agent.isHuman,
165
+ avatarUrl: agent.avatarUrl,
164
166
  daemonId: daemon.id,
165
167
  daemonName: daemon.name,
166
168
  daemonStatus: daemon.status,
@@ -394,22 +396,8 @@ daemonsRouter.get('/messages', requireDaemonAuth, async (req, res) => {
394
396
  });
395
397
  /**
396
398
  * POST /api/daemons/messages/sync
397
- * Sync messages from daemon to cloud storage
398
- *
399
- * Accepts batches of messages and stores them in agent_messages table.
400
- * Uses upsert logic to handle duplicates (based on workspace_id + original_id).
401
- *
402
- * Request body:
403
- * {
404
- * messages: SyncMessageInput[]
405
- * }
406
- *
407
- * Response:
408
- * {
409
- * success: true,
410
- * synced: number, // Count of messages synced
411
- * duplicates: number // Count of messages skipped (already existed)
412
- * }
399
+ * Bulk sync messages from local daemon to cloud storage.
400
+ * Messages are stored for backup/history - local SQLite is used for display.
413
401
  */
414
402
  daemonsRouter.post('/messages/sync', requireDaemonAuth, async (req, res) => {
415
403
  const daemon = req.daemon;
@@ -420,7 +408,6 @@ daemonsRouter.post('/messages/sync', requireDaemonAuth, async (req, res) => {
420
408
  if (messages.length === 0) {
421
409
  return res.json({ success: true, synced: 0, duplicates: 0 });
422
410
  }
423
- // Limit batch size to prevent abuse
424
411
  if (messages.length > 500) {
425
412
  return res.status(400).json({ error: 'Maximum batch size is 500 messages' });
426
413
  }
@@ -444,19 +431,15 @@ daemonsRouter.post('/messages/sync', requireDaemonAuth, async (req, res) => {
444
431
  return res.status(400).json({ error: hint });
445
432
  }
446
433
  try {
447
- // Get user plan to determine retention policy
448
434
  const user = await db.users.findById(daemon.userId);
449
435
  const plan = user?.plan || 'free';
450
- // Calculate expires_at based on plan
451
436
  let expiresAt = null;
452
437
  if (plan === 'free') {
453
- expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days
438
+ expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
454
439
  }
455
440
  else if (plan === 'pro') {
456
- expiresAt = new Date(Date.now() + 90 * 24 * 60 * 60 * 1000); // 90 days
441
+ expiresAt = new Date(Date.now() + 90 * 24 * 60 * 60 * 1000);
457
442
  }
458
- // Enterprise: null (never expires)
459
- // Transform to NewAgentMessage format
460
443
  const dbMessages = messages.map((msg) => ({
461
444
  workspaceId,
462
445
  daemonId: daemon.id,
@@ -483,11 +466,7 @@ daemonsRouter.post('/messages/sync', requireDaemonAuth, async (req, res) => {
483
466
  const synced = result.inserted;
484
467
  const duplicates = result.duplicates;
485
468
  console.log(`[message-sync] Synced ${synced} messages for daemon ${daemon.id}, ${duplicates} duplicates skipped (${result.durationMs}ms)`);
486
- res.json({
487
- success: true,
488
- synced,
489
- duplicates,
490
- });
469
+ res.json({ success: true, synced, duplicates });
491
470
  }
492
471
  catch (error) {
493
472
  console.error('Error syncing messages:', error);
@@ -496,7 +475,7 @@ daemonsRouter.post('/messages/sync', requireDaemonAuth, async (req, res) => {
496
475
  });
497
476
  /**
498
477
  * GET /api/daemons/messages/stats
499
- * Get message sync statistics for this daemon's workspace
478
+ * Get message sync statistics and database health.
500
479
  */
501
480
  daemonsRouter.get('/messages/stats', requireDaemonAuth, async (req, res) => {
502
481
  const daemon = req.daemon;
@@ -504,12 +483,14 @@ daemonsRouter.get('/messages/stats', requireDaemonAuth, async (req, res) => {
504
483
  return res.status(400).json({ error: 'Daemon must be linked to a workspace' });
505
484
  }
506
485
  try {
507
- // Get message count and pool health in parallel
508
- const [count, poolHealth, poolStats] = await Promise.all([
509
- db.agentMessages.countByWorkspace(daemon.workspaceId),
486
+ // Get message count via raw query and pool health in parallel
487
+ const pool = db.getRawPool();
488
+ const [countResult, poolHealth, poolStats] = await Promise.all([
489
+ pool.query('SELECT COUNT(*) FROM agent_messages WHERE workspace_id = $1', [daemon.workspaceId]),
510
490
  db.bulk.checkHealth(),
511
491
  Promise.resolve(db.bulk.getPoolStats()),
512
492
  ]);
493
+ const count = parseInt(countResult.rows[0]?.count || '0', 10);
513
494
  res.json({
514
495
  workspaceId: daemon.workspaceId,
515
496
  messageCount: count,
@@ -637,10 +637,8 @@ async function validateProviderToken(provider, token) {
637
637
  },
638
638
  },
639
639
  google: {
640
- url: 'https://generativelanguage.googleapis.com/v1/models',
641
- headers: {
642
- Authorization: `Bearer ${token}`,
643
- },
640
+ url: `https://generativelanguage.googleapis.com/v1/models?key=${encodeURIComponent(token)}`,
641
+ headers: {},
644
642
  },
645
643
  };
646
644
  const config = endpoints[provider];
@@ -298,6 +298,12 @@ providersRouter.post('/:provider/api-key', async (req, res) => {
298
298
  // 200 = valid, 401 = invalid key, 400/other = might still be valid key
299
299
  isValid = testRes.status !== 401;
300
300
  }
301
+ else if (provider === 'google') {
302
+ // Test Google/Gemini API key (uses query param auth, not Bearer token)
303
+ const testRes = await fetch(`https://generativelanguage.googleapis.com/v1/models?key=${encodeURIComponent(apiKey)}`);
304
+ // 200 = valid, 400/401/403 = invalid key
305
+ isValid = testRes.status === 200;
306
+ }
301
307
  else {
302
308
  // For other providers, just accept the key
303
309
  isValid = true;
@@ -6,6 +6,7 @@ export interface CloudConfig {
6
6
  publicUrl: string;
7
7
  appUrl: string;
8
8
  sessionSecret: string;
9
+ localDashboardUrl?: string;
9
10
  databaseUrl: string;
10
11
  redisUrl: string;
11
12
  github: {
@@ -17,6 +17,8 @@ export function loadConfig() {
17
17
  publicUrl: process.env.PUBLIC_URL || 'http://localhost:4567',
18
18
  appUrl: process.env.APP_URL || process.env.PUBLIC_URL || 'http://localhost:4567',
19
19
  sessionSecret: requireEnv('SESSION_SECRET'),
20
+ // Local dashboard for channel API (auto-detected if not set)
21
+ localDashboardUrl: optionalEnv('LOCAL_DASHBOARD_URL'),
20
22
  databaseUrl: requireEnv('DATABASE_URL'),
21
23
  redisUrl: process.env.REDIS_URL || 'redis://localhost:6379',
22
24
  github: {