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
@@ -6,9 +6,11 @@ import { generateId } from '../utils/id-generator.js';
6
6
  import { PROTOCOL_VERSION, } from '../protocol/types.js';
7
7
  import { routerLog } from '../utils/logger.js';
8
8
  import { RateLimiter, NoOpRateLimiter } from './rate-limiter.js';
9
+ import * as crypto from 'node:crypto';
9
10
  import { DeliveryTracker, } from './delivery-tracker.js';
10
11
  export class Router {
11
12
  storage;
13
+ channelMembershipStore;
12
14
  connections = new Map(); // connectionId -> Connection
13
15
  agents = new Map(); // agentName -> Connection
14
16
  subscriptions = new Map(); // topic -> Set<agentName>
@@ -34,6 +36,7 @@ export class Router {
34
36
  rateLimiter;
35
37
  constructor(options = {}) {
36
38
  this.storage = options.storage;
39
+ this.channelMembershipStore = options.channelMembershipStore;
37
40
  this.registry = options.registry;
38
41
  this.onProcessingStateChange = options.onProcessingStateChange;
39
42
  this.crossMachineHandler = options.crossMachineHandler;
@@ -47,6 +50,45 @@ export class Router {
47
50
  ? new NoOpRateLimiter()
48
51
  : new RateLimiter(options.rateLimit);
49
52
  }
53
+ /**
54
+ * Restore channel memberships from persisted storage.
55
+ */
56
+ async restoreChannelMemberships() {
57
+ if (!this.storage && !this.channelMembershipStore)
58
+ return;
59
+ try {
60
+ if (this.channelMembershipStore) {
61
+ const memberships = await this.channelMembershipStore.loadMemberships();
62
+ for (const membership of memberships) {
63
+ this.handleMembershipUpdate({
64
+ channel: membership.channel,
65
+ member: membership.member,
66
+ action: 'join',
67
+ });
68
+ }
69
+ }
70
+ if (this.storage) {
71
+ const messages = await this.storage.getMessages({ order: 'asc' });
72
+ for (const msg of messages) {
73
+ const channel = msg.to;
74
+ const data = msg.data;
75
+ const membership = data?._channelMembership;
76
+ if (!channel || !membership?.member) {
77
+ continue;
78
+ }
79
+ const action = membership.action ?? 'join';
80
+ this.handleMembershipUpdate({
81
+ channel,
82
+ member: membership.member,
83
+ action,
84
+ });
85
+ }
86
+ }
87
+ }
88
+ catch (err) {
89
+ routerLog.error('Failed to restore channel memberships', { error: String(err) });
90
+ }
91
+ }
50
92
  /**
51
93
  * Set or update the cross-machine handler.
52
94
  */
@@ -96,28 +138,36 @@ export class Router {
96
138
  this.connections.delete(connection.id);
97
139
  if (connection.agentName) {
98
140
  const isUser = connection.entityType === 'user';
141
+ let wasCurrentConnection = false;
99
142
  if (isUser) {
100
143
  const currentUser = this.users.get(connection.agentName);
101
144
  if (currentUser?.id === connection.id) {
102
145
  this.users.delete(connection.agentName);
146
+ wasCurrentConnection = true;
103
147
  }
104
148
  }
105
149
  else {
106
150
  const current = this.agents.get(connection.agentName);
107
151
  if (current?.id === connection.id) {
108
152
  this.agents.delete(connection.agentName);
153
+ wasCurrentConnection = true;
109
154
  }
110
155
  }
111
- // Remove from all subscriptions
112
- for (const subscribers of this.subscriptions.values()) {
113
- subscribers.delete(connection.agentName);
156
+ // Only clean up channel/subscription state if this was the current connection.
157
+ // If a new connection replaced this one, we don't want to remove channel memberships
158
+ // that the new connection should inherit.
159
+ if (wasCurrentConnection) {
160
+ // Remove from all subscriptions
161
+ for (const subscribers of this.subscriptions.values()) {
162
+ subscribers.delete(connection.agentName);
163
+ }
164
+ // Remove from all channels and notify remaining members
165
+ this.removeFromAllChannels(connection.agentName);
166
+ // Clean up shadow relationships
167
+ this.unbindShadow(connection.agentName);
168
+ // Clear processing state
169
+ this.clearProcessing(connection.agentName);
114
170
  }
115
- // Remove from all channels and notify remaining members
116
- this.removeFromAllChannels(connection.agentName);
117
- // Clean up shadow relationships
118
- this.unbindShadow(connection.agentName);
119
- // Clear processing state
120
- this.clearProcessing(connection.agentName);
121
171
  }
122
172
  this.clearPendingForConnection(connection.id);
123
173
  }
@@ -303,7 +353,7 @@ export class Router {
303
353
  this.registry?.recordSend(senderName);
304
354
  const to = envelope.to;
305
355
  const topic = envelope.topic;
306
- routerLog.debug(`${senderName} -> ${to}`, { preview: envelope.payload.body?.substring(0, 50) });
356
+ routerLog.info(`Route ${senderName} -> ${to}`, { preview: envelope.payload.body?.substring(0, 50) });
307
357
  if (to === '*') {
308
358
  // Broadcast to all (except sender)
309
359
  this.broadcast(senderName, envelope, topic);
@@ -382,7 +432,7 @@ export class Router {
382
432
  }
383
433
  const deliver = this.createDeliverEnvelope(from, to, envelope, target);
384
434
  const sent = target.send(deliver);
385
- routerLog.debug(`Delivered to ${to}`, { success: sent });
435
+ routerLog.info(`Delivered ${from} -> ${to}`, { success: sent, preview: envelope.payload.body?.substring(0, 40) });
386
436
  this.persistDeliverEnvelope(deliver);
387
437
  if (sent) {
388
438
  this.trackDelivery(target, deliver);
@@ -694,14 +744,18 @@ export class Router {
694
744
  /**
695
745
  * Handle a CHANNEL_JOIN message.
696
746
  * Adds the member to the channel and notifies existing members.
747
+ * If payload.member is set, adds that member (admin mode).
748
+ * Otherwise, adds the connection's agent name.
697
749
  */
698
750
  handleChannelJoin(connection, envelope) {
699
- const memberName = connection.agentName;
751
+ // Use payload.member if provided (admin mode), otherwise use connection's name
752
+ const memberName = envelope.payload.member ?? connection.agentName;
700
753
  if (!memberName) {
701
- routerLog.warn('CHANNEL_JOIN from connection without name');
754
+ routerLog.warn('CHANNEL_JOIN from connection without name and no member specified');
702
755
  return;
703
756
  }
704
757
  const channel = envelope.payload.channel;
758
+ const isAdminJoin = Boolean(envelope.payload.member);
705
759
  // Get or create channel
706
760
  let members = this.channels.get(channel);
707
761
  if (!members) {
@@ -713,79 +767,78 @@ export class Router {
713
767
  routerLog.debug(`${memberName} already in ${channel}`);
714
768
  return;
715
769
  }
716
- // Notify existing members about the new joiner
717
- for (const existingMember of members) {
718
- const memberConn = this.getConnectionByName(existingMember);
719
- if (memberConn) {
720
- const joinNotification = {
721
- v: PROTOCOL_VERSION,
722
- type: 'CHANNEL_JOIN',
723
- id: generateId(),
724
- ts: Date.now(),
725
- from: memberName,
726
- payload: envelope.payload,
727
- };
728
- memberConn.send(joinNotification);
770
+ // Only notify existing members for non-admin joins (agents joining themselves)
771
+ // Admin joins are silent to avoid spamming notifications when syncing
772
+ if (!isAdminJoin) {
773
+ const existingMembers = members ? Array.from(members) : [];
774
+ for (const existingMember of existingMembers) {
775
+ const memberConn = this.getConnectionByName(existingMember);
776
+ if (memberConn) {
777
+ const joinNotification = {
778
+ v: PROTOCOL_VERSION,
779
+ type: 'CHANNEL_JOIN',
780
+ id: generateId(),
781
+ ts: Date.now(),
782
+ from: memberName,
783
+ payload: envelope.payload,
784
+ };
785
+ memberConn.send(joinNotification);
786
+ }
729
787
  }
730
788
  }
731
- // Add the new member
732
- members.add(memberName);
733
- // Track which channels this member is in
734
- let memberChannelSet = this.memberChannels.get(memberName);
735
- if (!memberChannelSet) {
736
- memberChannelSet = new Set();
737
- this.memberChannels.set(memberName, memberChannelSet);
789
+ const added = this.addChannelMember(channel, memberName, { persist: true });
790
+ if (!added) {
791
+ routerLog.debug(`${memberName} already in ${channel}`);
792
+ return;
738
793
  }
739
- memberChannelSet.add(channel);
740
- routerLog.info(`${memberName} joined ${channel} (${members.size} members)`);
794
+ routerLog.info(`${memberName} joined ${channel} (${this.channels.get(channel)?.size ?? 0} members)${isAdminJoin ? ' [admin]' : ''}`);
741
795
  }
742
796
  /**
743
797
  * Handle a CHANNEL_LEAVE message.
744
798
  * Removes the member from the channel and notifies remaining members.
799
+ * If payload.member is provided, removes that member instead (admin mode).
745
800
  */
746
801
  handleChannelLeave(connection, envelope) {
747
- const memberName = connection.agentName;
802
+ // Use payload.member if provided (admin mode), otherwise use connection's name
803
+ const memberName = envelope.payload.member ?? connection.agentName;
748
804
  if (!memberName) {
749
- routerLog.warn('CHANNEL_LEAVE from connection without name');
805
+ routerLog.warn('CHANNEL_LEAVE from connection without name and no member specified');
750
806
  return;
751
807
  }
752
808
  const channel = envelope.payload.channel;
809
+ const isAdminRemove = Boolean(envelope.payload.member);
753
810
  const members = this.channels.get(channel);
754
811
  if (!members || !members.has(memberName)) {
755
812
  routerLog.debug(`${memberName} not in ${channel}, ignoring leave`);
756
813
  return;
757
814
  }
758
- // Remove from channel
759
- members.delete(memberName);
760
- // Remove from member's channel list
761
- const memberChannelSet = this.memberChannels.get(memberName);
762
- if (memberChannelSet) {
763
- memberChannelSet.delete(channel);
764
- if (memberChannelSet.size === 0) {
765
- this.memberChannels.delete(memberName);
766
- }
815
+ const removed = this.removeChannelMember(channel, memberName, { persist: true });
816
+ if (!removed) {
817
+ routerLog.debug(`${memberName} not in ${channel}, ignoring leave`);
818
+ return;
767
819
  }
768
- // Notify remaining members
769
- for (const remainingMember of members) {
770
- const memberConn = this.getConnectionByName(remainingMember);
771
- if (memberConn) {
772
- const leaveNotification = {
773
- v: PROTOCOL_VERSION,
774
- type: 'CHANNEL_LEAVE',
775
- id: generateId(),
776
- ts: Date.now(),
777
- from: memberName,
778
- payload: envelope.payload,
779
- };
780
- memberConn.send(leaveNotification);
820
+ // Only notify remaining members for non-admin removes
821
+ // Admin removes are silent to avoid spamming notifications
822
+ if (!isAdminRemove) {
823
+ const remainingMembers = this.channels.get(channel);
824
+ if (remainingMembers) {
825
+ for (const remainingMember of remainingMembers) {
826
+ const memberConn = this.getConnectionByName(remainingMember);
827
+ if (memberConn) {
828
+ const leaveNotification = {
829
+ v: PROTOCOL_VERSION,
830
+ type: 'CHANNEL_LEAVE',
831
+ id: generateId(),
832
+ ts: Date.now(),
833
+ from: memberName,
834
+ payload: envelope.payload,
835
+ };
836
+ memberConn.send(leaveNotification);
837
+ }
838
+ }
781
839
  }
782
840
  }
783
- // Clean up empty channels
784
- if (members.size === 0) {
785
- this.channels.delete(channel);
786
- routerLog.debug(`Channel ${channel} deleted (empty)`);
787
- }
788
- routerLog.info(`${memberName} left ${channel}`);
841
+ routerLog.info(`${memberName} left ${channel}${isAdminRemove ? ' [admin]' : ''}`);
789
842
  }
790
843
  /**
791
844
  * Route a channel message to all members except the sender.
@@ -798,18 +851,33 @@ export class Router {
798
851
  }
799
852
  const channel = envelope.payload.channel;
800
853
  const members = this.channels.get(channel);
854
+ routerLog.info(`routeChannelMessage: channel=${channel} sender=${senderName} members=${members ? Array.from(members).join(',') : 'NONE'}`);
801
855
  if (!members) {
802
- routerLog.warn(`Message to non-existent channel ${channel}`);
856
+ routerLog.warn(`Message to non-existent channel ${channel} (available channels: ${Array.from(this.channels.keys()).join(', ')})`);
803
857
  return;
804
858
  }
805
- if (!members.has(senderName)) {
806
- routerLog.warn(`${senderName} not a member of ${channel}`);
859
+ // Case-insensitive membership check
860
+ const senderMemberName = this.findMemberInSet(members, senderName);
861
+ if (!senderMemberName) {
862
+ routerLog.warn(`${senderName} not a member of ${channel} (members: ${Array.from(members).join(', ')})`);
807
863
  return;
808
864
  }
809
- // Route to all members except sender
865
+ // Route to all members except the sender (no echo)
866
+ const allMembers = Array.from(members);
867
+ routerLog.info(`Routing channel message from ${senderName} to ${channel}`, {
868
+ totalMembers: allMembers.length,
869
+ members: allMembers,
870
+ });
871
+ let deliveredCount = 0;
872
+ const undeliveredMembers = [];
873
+ const connectedAgents = Array.from(this.agents.keys());
874
+ const connectedUsers = Array.from(this.users.keys());
875
+ routerLog.info(`Connected entities: agents=[${connectedAgents.join(',')}] users=[${connectedUsers.join(',')}]`);
810
876
  for (const memberName of members) {
811
- if (memberName === senderName)
877
+ // Case-insensitive comparison to skip sender
878
+ if (this.namesMatch(memberName, senderName)) {
812
879
  continue;
880
+ }
813
881
  const memberConn = this.getConnectionByName(memberName);
814
882
  if (memberConn) {
815
883
  const deliverEnvelope = {
@@ -820,12 +888,29 @@ export class Router {
820
888
  from: senderName,
821
889
  payload: envelope.payload,
822
890
  };
823
- memberConn.send(deliverEnvelope);
891
+ const sent = memberConn.send(deliverEnvelope);
892
+ if (sent) {
893
+ deliveredCount++;
894
+ routerLog.info(`Delivered to ${memberName} (${memberConn.entityType || 'agent'})`);
895
+ }
896
+ else {
897
+ routerLog.warn(`Failed to send to ${memberName}`);
898
+ undeliveredMembers.push(memberName);
899
+ }
900
+ }
901
+ else {
902
+ routerLog.warn(`Member ${memberName} is registered in channel but NOT connected to daemon - message not delivered`);
903
+ undeliveredMembers.push(memberName);
824
904
  }
825
905
  }
826
906
  // Persist channel message
827
907
  this.persistChannelMessage(envelope, senderName);
828
- routerLog.debug(`${senderName} -> ${channel}: ${envelope.payload.body.substring(0, 50)}`);
908
+ const recipientCount = allMembers.length - 1; // Exclude sender
909
+ routerLog.info(`${senderName} -> ${channel}: delivered to ${deliveredCount}/${recipientCount} members`);
910
+ // Log warning if some members didn't receive the message
911
+ if (undeliveredMembers.length > 0) {
912
+ routerLog.warn(`Channel message undelivered to: [${undeliveredMembers.join(', ')}] - these agents may need to reconnect to the relay daemon`);
913
+ }
829
914
  }
830
915
  /**
831
916
  * Persist a channel message to storage.
@@ -833,6 +918,12 @@ export class Router {
833
918
  persistChannelMessage(envelope, from) {
834
919
  if (!this.storage)
835
920
  return;
921
+ const payloadData = {
922
+ ...envelope.payload.data,
923
+ _isChannelMessage: true,
924
+ _channel: envelope.payload.channel,
925
+ _mentions: envelope.payload.mentions,
926
+ };
836
927
  this.storage.saveMessage({
837
928
  id: envelope.id,
838
929
  ts: envelope.ts,
@@ -841,12 +932,7 @@ export class Router {
841
932
  topic: undefined,
842
933
  kind: 'message',
843
934
  body: envelope.payload.body,
844
- data: {
845
- ...envelope.payload.data,
846
- _isChannelMessage: true,
847
- _channel: envelope.payload.channel,
848
- _mentions: envelope.payload.mentions,
849
- },
935
+ data: payloadData,
850
936
  thread: envelope.payload.thread,
851
937
  status: 'unread',
852
938
  is_urgent: false,
@@ -855,6 +941,44 @@ export class Router {
855
941
  routerLog.error('Failed to persist channel message', { error: String(err) });
856
942
  });
857
943
  }
944
+ persistChannelMembership(channel, member, action, opts) {
945
+ if (this.storage) {
946
+ this.storage.saveMessage({
947
+ id: crypto.randomUUID(),
948
+ ts: Date.now(),
949
+ from: '__system__',
950
+ to: channel,
951
+ topic: undefined,
952
+ kind: 'state', // membership events stored as state
953
+ body: `${action}:${member}`,
954
+ data: {
955
+ _channelMembership: {
956
+ member,
957
+ action,
958
+ invitedBy: opts?.invitedBy,
959
+ },
960
+ },
961
+ status: 'read',
962
+ is_urgent: false,
963
+ is_broadcast: true,
964
+ }).catch((err) => {
965
+ routerLog.error('Failed to persist channel membership', { error: String(err) });
966
+ });
967
+ }
968
+ if (this.channelMembershipStore) {
969
+ const persistPromise = action === 'leave'
970
+ ? this.channelMembershipStore.removeMember(channel, member)
971
+ : this.channelMembershipStore.addMember(channel, member);
972
+ persistPromise.catch((err) => {
973
+ routerLog.error('Failed to sync channel membership to cloud store', {
974
+ channel,
975
+ member,
976
+ action,
977
+ error: err instanceof Error ? err.message : String(err),
978
+ });
979
+ });
980
+ }
981
+ }
858
982
  /**
859
983
  * Get all members of a channel.
860
984
  */
@@ -895,9 +1019,124 @@ export class Router {
895
1019
  }
896
1020
  /**
897
1021
  * Get a connection by name (checks both agents and users).
1022
+ * Uses case-insensitive lookup to handle mismatched casing.
898
1023
  */
899
1024
  getConnectionByName(name) {
900
- return this.agents.get(name) ?? this.users.get(name);
1025
+ // Try exact match first
1026
+ const exact = this.agents.get(name) ?? this.users.get(name);
1027
+ if (exact)
1028
+ return exact;
1029
+ // Fall back to case-insensitive search
1030
+ const lowerName = name.toLowerCase();
1031
+ for (const [key, conn] of this.agents) {
1032
+ if (key.toLowerCase() === lowerName)
1033
+ return conn;
1034
+ }
1035
+ for (const [key, conn] of this.users) {
1036
+ if (key.toLowerCase() === lowerName)
1037
+ return conn;
1038
+ }
1039
+ return undefined;
1040
+ }
1041
+ /**
1042
+ * Check if a member is in a Set (case-insensitive).
1043
+ * Returns the actual stored name if found, undefined otherwise.
1044
+ */
1045
+ findMemberInSet(members, name) {
1046
+ // Try exact match first
1047
+ if (members.has(name))
1048
+ return name;
1049
+ // Fall back to case-insensitive search
1050
+ const lowerName = name.toLowerCase();
1051
+ for (const member of members) {
1052
+ if (member.toLowerCase() === lowerName)
1053
+ return member;
1054
+ }
1055
+ return undefined;
1056
+ }
1057
+ /**
1058
+ * Check if two names match (case-insensitive).
1059
+ */
1060
+ namesMatch(a, b) {
1061
+ return a.toLowerCase() === b.toLowerCase();
1062
+ }
1063
+ /**
1064
+ * Auto-join a member to a channel without notifications.
1065
+ * Used for default channel membership (e.g., #general).
1066
+ * @param memberName - The agent or user name to add
1067
+ * @param channel - The channel to join (e.g., '#general')
1068
+ */
1069
+ autoJoinChannel(memberName, channel, options) {
1070
+ // Get or create channel
1071
+ let members = this.channels.get(channel);
1072
+ if (!members) {
1073
+ members = new Set();
1074
+ this.channels.set(channel, members);
1075
+ }
1076
+ // Check if already a member
1077
+ const added = this.addChannelMember(channel, memberName, { persist: options?.persist });
1078
+ if (added) {
1079
+ routerLog.debug(`Auto-joined ${memberName} to ${channel}`);
1080
+ }
1081
+ }
1082
+ addChannelMember(channel, memberName, options) {
1083
+ let members = this.channels.get(channel);
1084
+ if (!members) {
1085
+ members = new Set();
1086
+ this.channels.set(channel, members);
1087
+ }
1088
+ // Case-insensitive check for existing membership
1089
+ const existingMember = this.findMemberInSet(members, memberName);
1090
+ if (existingMember) {
1091
+ return false;
1092
+ }
1093
+ members.add(memberName);
1094
+ const memberChannelSet = this.memberChannels.get(memberName) ?? new Set();
1095
+ memberChannelSet.add(channel);
1096
+ this.memberChannels.set(memberName, memberChannelSet);
1097
+ if (options?.persist ?? true) {
1098
+ this.persistChannelMembership(channel, memberName, 'join');
1099
+ }
1100
+ return true;
1101
+ }
1102
+ removeChannelMember(channel, memberName, options) {
1103
+ const members = this.channels.get(channel);
1104
+ if (!members) {
1105
+ return false;
1106
+ }
1107
+ // Case-insensitive lookup to find actual stored name
1108
+ const actualMemberName = this.findMemberInSet(members, memberName);
1109
+ if (!actualMemberName) {
1110
+ return false;
1111
+ }
1112
+ members.delete(actualMemberName);
1113
+ if (members.size === 0) {
1114
+ this.channels.delete(channel);
1115
+ }
1116
+ // Also try case-insensitive for memberChannels cleanup
1117
+ const memberChannelSet = this.memberChannels.get(actualMemberName) ?? this.memberChannels.get(memberName);
1118
+ if (memberChannelSet) {
1119
+ memberChannelSet.delete(channel);
1120
+ if (memberChannelSet.size === 0) {
1121
+ this.memberChannels.delete(actualMemberName);
1122
+ this.memberChannels.delete(memberName); // Clean up both potential keys
1123
+ }
1124
+ }
1125
+ if (options?.persist ?? true) {
1126
+ this.persistChannelMembership(channel, actualMemberName, 'leave');
1127
+ }
1128
+ return true;
1129
+ }
1130
+ handleMembershipUpdate(update) {
1131
+ if (!update.channel || !update.member) {
1132
+ return;
1133
+ }
1134
+ if (update.action === 'leave') {
1135
+ this.removeChannelMember(update.channel, update.member, { persist: false });
1136
+ }
1137
+ else {
1138
+ this.addChannelMember(update.channel, update.member, { persist: false });
1139
+ }
901
1140
  }
902
1141
  }
903
1142
  //# sourceMappingURL=router.js.map
@@ -37,6 +37,7 @@ export declare class Daemon {
37
37
  private cloudSync?;
38
38
  private remoteAgents;
39
39
  private consensus?;
40
+ private cloudSyncDebounceTimer?;
40
41
  /** Callback for log output from agents (used by dashboard for streaming) */
41
42
  onLogOutput?: (agentName: string, data: string, timestamp: number) => void;
42
43
  /** Interval for writing processing state file (500ms for responsive UI) */
@@ -51,6 +52,11 @@ export declare class Daemon {
51
52
  * This file contains agents currently processing/thinking after receiving a message.
52
53
  */
53
54
  private writeProcessingStateFile;
55
+ /**
56
+ * Write currently connected agents to connected-agents.json for CLI consumption.
57
+ * This file contains agents with active socket connections (vs agents.json which is historical).
58
+ */
59
+ private writeConnectedAgentsFile;
54
60
  /**
55
61
  * Initialize storage adapter (called during start).
56
62
  */
@@ -85,8 +91,17 @@ export declare class Daemon {
85
91
  isRemoteAgent(agentName: string): RemoteAgent | undefined;
86
92
  /**
87
93
  * Notify cloud sync about local agent changes.
94
+ * Debounced to prevent flooding the cloud API with rapid connect/disconnect events.
88
95
  */
89
96
  private notifyCloudSync;
97
+ /**
98
+ * Actually perform the cloud sync (called after debounce).
99
+ */
100
+ private doCloudSync;
101
+ /**
102
+ * Check if an agent is internal (should be hidden from cloud sync and listings).
103
+ */
104
+ private isInternalAgent;
90
105
  /**
91
106
  * Stop the daemon.
92
107
  */