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
@@ -0,0 +1,970 @@
1
+ /**
2
+ * RelayPtyOrchestrator - Orchestrates the relay-pty Rust binary
3
+ *
4
+ * This wrapper spawns the relay-pty binary and communicates via Unix socket.
5
+ * It provides the same interface as PtyWrapper but with improved latency
6
+ * (~550ms vs ~1700ms) by using direct PTY writes instead of tmux send-keys.
7
+ *
8
+ * Architecture:
9
+ * 1. Spawn relay-pty --name {agentName} -- {command} as child process
10
+ * 2. Connect to /tmp/relay-pty-{agentName}.sock for injection
11
+ * 3. Parse stdout for relay commands (relay-pty echoes all output)
12
+ * 4. Translate SEND envelopes → inject messages via socket
13
+ *
14
+ * @see docs/RUST_WRAPPER_DESIGN.md for protocol details
15
+ */
16
+ import { spawn } from 'node:child_process';
17
+ import { createConnection } from 'node:net';
18
+ import { join, dirname } from 'node:path';
19
+ import { existsSync, unlinkSync } from 'node:fs';
20
+ import { getProjectPaths } from '../utils/project-namespace.js';
21
+ import { fileURLToPath } from 'node:url';
22
+ // Get the directory where this module is located
23
+ const __filename = fileURLToPath(import.meta.url);
24
+ const __dirname = dirname(__filename);
25
+ import { BaseWrapper } from './base-wrapper.js';
26
+ import { parseSummaryWithDetails, parseSessionEndFromOutput } from './parser.js';
27
+ import { stripAnsi, sleep, buildInjectionString, INJECTION_CONSTANTS, } from './shared.js';
28
+ /**
29
+ * Orchestrator for relay-pty Rust binary
30
+ *
31
+ * Extends BaseWrapper to provide the same interface as PtyWrapper
32
+ * but uses the relay-pty binary for improved injection reliability.
33
+ */
34
+ export class RelayPtyOrchestrator extends BaseWrapper {
35
+ config;
36
+ // Process management
37
+ relayPtyProcess;
38
+ socketPath;
39
+ _logPath;
40
+ _outboxPath;
41
+ socket;
42
+ socketConnected = false;
43
+ // Output buffering
44
+ outputBuffer = '';
45
+ rawBuffer = '';
46
+ lastParsedLength = 0;
47
+ // Interactive mode (show output to terminal)
48
+ isInteractive = false;
49
+ // Injection state
50
+ pendingInjections = new Map();
51
+ backpressureActive = false;
52
+ readyForMessages = false;
53
+ // Unread message indicator state
54
+ lastUnreadIndicatorTime = 0;
55
+ UNREAD_INDICATOR_COOLDOWN_MS = 5000; // Don't spam indicators
56
+ // Note: sessionEndProcessed and lastSummaryRawContent are inherited from BaseWrapper
57
+ constructor(config) {
58
+ super(config);
59
+ this.config = config;
60
+ this.socketPath = `/tmp/relay-pty-${config.name}.sock`;
61
+ // Generate log path using same project paths as daemon
62
+ // Use cwd from config if specified, otherwise detect from current directory
63
+ const paths = getProjectPaths(config.cwd);
64
+ this._logPath = join(paths.teamDir, 'worker-logs', `${config.name}.log`);
65
+ // Outbox for file-based relay messages (agent writes here)
66
+ // Use fixed /tmp path so agents can find it without configuration
67
+ this._outboxPath = `/tmp/relay-outbox/${config.name}`;
68
+ // Check if we're running interactively (stdin is a TTY)
69
+ this.isInteractive = process.stdin.isTTY === true;
70
+ }
71
+ /**
72
+ * Debug log - only outputs when debug is enabled
73
+ */
74
+ log(message) {
75
+ if (this.config.debug) {
76
+ console.log(`[relay-pty-orchestrator:${this.config.name}] ${message}`);
77
+ }
78
+ }
79
+ /**
80
+ * Error log - always outputs (errors are important)
81
+ */
82
+ logError(message) {
83
+ console.error(`[relay-pty-orchestrator:${this.config.name}] ERROR: ${message}`);
84
+ }
85
+ /**
86
+ * Get the outbox path for this agent (for documentation purposes)
87
+ */
88
+ get outboxPath() {
89
+ return this._outboxPath;
90
+ }
91
+ // =========================================================================
92
+ // Abstract method implementations (required by BaseWrapper)
93
+ // =========================================================================
94
+ /**
95
+ * Start the relay-pty process and connect to socket
96
+ */
97
+ async start() {
98
+ if (this.running)
99
+ return;
100
+ this.log(` Starting...`);
101
+ // Clean up any stale socket from previous crashed process
102
+ try {
103
+ if (existsSync(this.socketPath)) {
104
+ this.log(` Removing stale socket: ${this.socketPath}`);
105
+ unlinkSync(this.socketPath);
106
+ }
107
+ }
108
+ catch (err) {
109
+ this.logError(` Failed to clean up socket: ${err.message}`);
110
+ }
111
+ // Find relay-pty binary
112
+ const binaryPath = this.findRelayPtyBinary();
113
+ if (!binaryPath) {
114
+ throw new Error('relay-pty binary not found. Build with: cd relay-pty && cargo build --release');
115
+ }
116
+ this.log(` Using binary: ${binaryPath}`);
117
+ // Connect to relay daemon first
118
+ try {
119
+ await this.client.connect();
120
+ this.log(` Relay daemon connected`);
121
+ }
122
+ catch (err) {
123
+ this.logError(` Relay connect failed: ${err.message}`);
124
+ }
125
+ // Spawn relay-pty process
126
+ await this.spawnRelayPty(binaryPath);
127
+ // Wait for socket to become available and connect
128
+ await this.connectToSocket();
129
+ this.running = true;
130
+ this.readyForMessages = true;
131
+ this.log(` Ready for messages`);
132
+ this.log(` Socket connected: ${this.socketConnected}`);
133
+ this.log(` Relay client state: ${this.client.state}`);
134
+ // Process any queued messages
135
+ this.processMessageQueue();
136
+ }
137
+ /**
138
+ * Stop the relay-pty process gracefully
139
+ */
140
+ async stop() {
141
+ if (!this.running)
142
+ return;
143
+ this.running = false;
144
+ this.log(` Stopping...`);
145
+ // Send shutdown command via socket
146
+ if (this.socket && this.socketConnected) {
147
+ try {
148
+ await this.sendSocketRequest({ type: 'shutdown' });
149
+ }
150
+ catch {
151
+ // Ignore errors during shutdown
152
+ }
153
+ }
154
+ // Close socket
155
+ this.disconnectSocket();
156
+ // Kill process if still running
157
+ if (this.relayPtyProcess && !this.relayPtyProcess.killed) {
158
+ this.relayPtyProcess.kill('SIGTERM');
159
+ // Force kill after timeout
160
+ await Promise.race([
161
+ new Promise((resolve) => {
162
+ this.relayPtyProcess?.on('exit', () => resolve());
163
+ }),
164
+ sleep(5000).then(() => {
165
+ if (this.relayPtyProcess && !this.relayPtyProcess.killed) {
166
+ this.relayPtyProcess.kill('SIGKILL');
167
+ }
168
+ }),
169
+ ]);
170
+ }
171
+ // Cleanup relay client
172
+ this.destroyClient();
173
+ // Clean up socket file
174
+ try {
175
+ if (existsSync(this.socketPath)) {
176
+ unlinkSync(this.socketPath);
177
+ this.log(` Cleaned up socket: ${this.socketPath}`);
178
+ }
179
+ }
180
+ catch (err) {
181
+ this.logError(` Failed to clean up socket: ${err.message}`);
182
+ }
183
+ this.log(` Stopped`);
184
+ }
185
+ /**
186
+ * Inject content into the agent via socket
187
+ */
188
+ async performInjection(content) {
189
+ // This is called by BaseWrapper but we handle injection differently
190
+ // via the socket protocol in processMessageQueue
191
+ throw new Error('Use injectMessage() instead of performInjection()');
192
+ }
193
+ /**
194
+ * Get cleaned output for parsing
195
+ */
196
+ getCleanOutput() {
197
+ return stripAnsi(this.rawBuffer);
198
+ }
199
+ // =========================================================================
200
+ // Process management
201
+ // =========================================================================
202
+ /**
203
+ * Find the relay-pty binary
204
+ */
205
+ findRelayPtyBinary() {
206
+ // Check config path first
207
+ if (this.config.relayPtyPath && existsSync(this.config.relayPtyPath)) {
208
+ return this.config.relayPtyPath;
209
+ }
210
+ // Get the package root (two levels up from dist/wrapper/)
211
+ const packageRoot = join(__dirname, '..', '..');
212
+ // Check common locations (ordered by priority)
213
+ const candidates = [
214
+ // Primary: installed by postinstall from platform-specific binary
215
+ join(packageRoot, 'bin', 'relay-pty'),
216
+ // Development: local Rust build
217
+ join(packageRoot, 'relay-pty', 'target', 'release', 'relay-pty'),
218
+ join(packageRoot, 'relay-pty', 'target', 'debug', 'relay-pty'),
219
+ // Local build in cwd (for development)
220
+ join(process.cwd(), 'relay-pty', 'target', 'release', 'relay-pty'),
221
+ join(process.cwd(), 'relay-pty', 'target', 'debug', 'relay-pty'),
222
+ // Installed globally
223
+ '/usr/local/bin/relay-pty',
224
+ // In node_modules (when installed as dependency)
225
+ join(process.cwd(), 'node_modules', 'agent-relay', 'bin', 'relay-pty'),
226
+ join(process.cwd(), 'node_modules', '.bin', 'relay-pty'),
227
+ ];
228
+ for (const candidate of candidates) {
229
+ if (existsSync(candidate)) {
230
+ return candidate;
231
+ }
232
+ }
233
+ return null;
234
+ }
235
+ /**
236
+ * Spawn the relay-pty process
237
+ */
238
+ async spawnRelayPty(binaryPath) {
239
+ // Get terminal dimensions for proper rendering
240
+ const rows = process.stdout.rows || 24;
241
+ const cols = process.stdout.columns || 80;
242
+ const args = [
243
+ '--name', this.config.name,
244
+ '--socket', this.socketPath,
245
+ '--idle-timeout', String(this.config.idleBeforeInjectMs ?? 500),
246
+ '--json-output', // Enable Rust parsing output
247
+ '--rows', String(rows),
248
+ '--cols', String(cols),
249
+ '--log-level', 'warn', // Only show warnings and errors
250
+ '--log-file', this._logPath, // Enable output logging
251
+ '--outbox', this._outboxPath, // Enable file-based relay messages
252
+ '--', this.config.command,
253
+ ...(this.config.args ?? []),
254
+ ];
255
+ this.log(` Spawning: ${binaryPath} ${args.join(' ')}`);
256
+ // For interactive mode, let Rust directly inherit stdin/stdout from the terminal
257
+ // This is more robust than manual forwarding through pipes
258
+ // We still pipe stderr to capture JSON parsed commands
259
+ const stdio = this.isInteractive
260
+ ? ['inherit', 'inherit', 'pipe'] // Rust handles terminal directly
261
+ : ['pipe', 'pipe', 'pipe']; // Headless mode - we handle I/O
262
+ const proc = spawn(binaryPath, args, {
263
+ cwd: this.config.cwd ?? process.cwd(),
264
+ env: {
265
+ ...process.env,
266
+ ...this.config.env,
267
+ AGENT_RELAY_NAME: this.config.name,
268
+ TERM: 'xterm-256color',
269
+ },
270
+ stdio,
271
+ });
272
+ this.relayPtyProcess = proc;
273
+ // Handle stdout (agent output) - only in headless mode
274
+ if (!this.isInteractive && proc.stdout) {
275
+ proc.stdout.on('data', (data) => {
276
+ const text = data.toString();
277
+ this.handleOutput(text);
278
+ });
279
+ }
280
+ // Handle stderr (relay-pty logs and JSON output) - always needed
281
+ if (proc.stderr) {
282
+ proc.stderr.on('data', (data) => {
283
+ const text = data.toString();
284
+ this.handleStderr(text);
285
+ });
286
+ }
287
+ // Handle exit
288
+ proc.on('exit', (code, signal) => {
289
+ const exitCode = code ?? (signal === 'SIGKILL' ? 137 : 1);
290
+ this.log(` Process exited: code=${exitCode} signal=${signal}`);
291
+ this.running = false;
292
+ this.emit('exit', exitCode);
293
+ this.config.onExit?.(exitCode);
294
+ });
295
+ // Handle error
296
+ proc.on('error', (err) => {
297
+ this.logError(` Process error: ${err.message}`);
298
+ this.emit('error', err);
299
+ });
300
+ // Wait for process to start
301
+ await sleep(500);
302
+ if (proc.exitCode !== null) {
303
+ throw new Error(`relay-pty exited immediately with code ${proc.exitCode}`);
304
+ }
305
+ }
306
+ /**
307
+ * Handle output from relay-pty stdout (headless mode only)
308
+ * In interactive mode, stdout goes directly to terminal via inherited stdio
309
+ */
310
+ handleOutput(data) {
311
+ this.rawBuffer += data;
312
+ this.outputBuffer += data;
313
+ // Feed to idle detector
314
+ this.feedIdleDetectorOutput(data);
315
+ // Check for unread messages and append indicator if needed
316
+ const indicator = this.formatUnreadIndicator();
317
+ const outputWithIndicator = indicator ? data + indicator : data;
318
+ // Emit output event (with indicator if present)
319
+ this.emit('output', outputWithIndicator);
320
+ // Stream to daemon if configured
321
+ if (this.config.streamLogs !== false && this.client.state === 'READY') {
322
+ this.client.sendLog(outputWithIndicator);
323
+ }
324
+ // Parse for relay commands
325
+ this.parseRelayCommands();
326
+ // Check for summary and session end
327
+ const cleanContent = stripAnsi(this.rawBuffer);
328
+ this.checkForSummary(cleanContent);
329
+ this.checkForSessionEnd(cleanContent);
330
+ }
331
+ /**
332
+ * Format an unread message indicator if there are pending messages.
333
+ * Returns empty string if no pending messages or within cooldown period.
334
+ *
335
+ * Example output:
336
+ * ───────────────────────────
337
+ * 📬 2 unread messages (from: Alice, Bob)
338
+ */
339
+ formatUnreadIndicator() {
340
+ const queueLength = this.messageQueue.length;
341
+ if (queueLength === 0) {
342
+ return '';
343
+ }
344
+ // Check cooldown to avoid spamming
345
+ const now = Date.now();
346
+ if (now - this.lastUnreadIndicatorTime < this.UNREAD_INDICATOR_COOLDOWN_MS) {
347
+ return '';
348
+ }
349
+ this.lastUnreadIndicatorTime = now;
350
+ // Collect unique sender names
351
+ const senders = [...new Set(this.messageQueue.map(m => m.from))];
352
+ const senderList = senders.slice(0, 3).join(', ');
353
+ const moreCount = senders.length > 3 ? ` +${senders.length - 3} more` : '';
354
+ const line = '─'.repeat(27);
355
+ const messageWord = queueLength === 1 ? 'message' : 'messages';
356
+ return `\n${line}\n📬 ${queueLength} unread ${messageWord} (from: ${senderList}${moreCount})\n`;
357
+ }
358
+ /**
359
+ * Handle stderr from relay-pty (logs and JSON parsed commands)
360
+ */
361
+ handleStderr(data) {
362
+ // relay-pty outputs JSON parsed commands to stderr with --json-output
363
+ const lines = data.split('\n').filter(l => l.trim());
364
+ for (const line of lines) {
365
+ if (line.startsWith('{')) {
366
+ // JSON output - parsed relay command from Rust
367
+ try {
368
+ const parsed = JSON.parse(line);
369
+ if (parsed.type === 'relay_command' && parsed.kind) {
370
+ this.log(`Rust parsed [${parsed.kind}]: ${parsed.from} -> ${parsed.to}`);
371
+ this.handleRustParsedCommand(parsed);
372
+ }
373
+ }
374
+ catch {
375
+ // Not JSON, just log (only in debug mode)
376
+ if (this.config.debug) {
377
+ console.error(`[relay-pty:${this.config.name}] ${line}`);
378
+ }
379
+ }
380
+ }
381
+ else {
382
+ // Non-JSON stderr - only show in debug mode (logs, info messages)
383
+ if (this.config.debug) {
384
+ console.error(`[relay-pty:${this.config.name}] ${line}`);
385
+ }
386
+ }
387
+ }
388
+ }
389
+ /**
390
+ * Handle a parsed command from Rust relay-pty
391
+ * Rust outputs structured JSON with 'kind' field: "message", "spawn", "release"
392
+ */
393
+ handleRustParsedCommand(parsed) {
394
+ switch (parsed.kind) {
395
+ case 'spawn':
396
+ if (parsed.spawn_name && parsed.spawn_cli) {
397
+ this.log(` Spawn detected: ${parsed.spawn_name} (${parsed.spawn_cli})`);
398
+ this.handleSpawnCommand(parsed.spawn_name, parsed.spawn_cli, parsed.spawn_task || '');
399
+ }
400
+ break;
401
+ case 'release':
402
+ if (parsed.release_name) {
403
+ this.log(`Release: ${parsed.release_name}`);
404
+ this.handleReleaseCommand(parsed.release_name);
405
+ }
406
+ else {
407
+ this.logError(`Missing release_name in parsed command: ${JSON.stringify(parsed)}`);
408
+ }
409
+ break;
410
+ case 'message':
411
+ default:
412
+ this.sendRelayCommand({
413
+ to: parsed.to,
414
+ kind: 'message',
415
+ body: parsed.body,
416
+ thread: parsed.thread,
417
+ raw: parsed.raw,
418
+ });
419
+ break;
420
+ }
421
+ }
422
+ /**
423
+ * Handle spawn command
424
+ */
425
+ handleSpawnCommand(name, cli, task) {
426
+ const key = `spawn:${name}:${cli}`;
427
+ if (this.processedSpawnCommands.has(key)) {
428
+ this.log(` Spawn already processed: ${key}`);
429
+ return;
430
+ }
431
+ this.processedSpawnCommands.add(key);
432
+ this.log(` Spawn: ${name} (${cli})`);
433
+ this.log(` dashboardPort=${this.config.dashboardPort}, onSpawn=${!!this.config.onSpawn}`);
434
+ // Try dashboard API first, fall back to callback
435
+ if (this.config.dashboardPort) {
436
+ this.log(` Calling dashboard API at port ${this.config.dashboardPort}`);
437
+ this.spawnViaDashboardApi(name, cli, task)
438
+ .then(() => {
439
+ this.log(` Dashboard spawn succeeded for ${name}`);
440
+ })
441
+ .catch(err => {
442
+ this.logError(` Dashboard spawn failed: ${err.message}`);
443
+ if (this.config.onSpawn) {
444
+ this.log(` Falling back to onSpawn callback`);
445
+ this.config.onSpawn(name, cli, task);
446
+ }
447
+ });
448
+ }
449
+ else if (this.config.onSpawn) {
450
+ this.log(` Using onSpawn callback directly`);
451
+ this.config.onSpawn(name, cli, task);
452
+ }
453
+ else {
454
+ this.logError(` No spawn mechanism available!`);
455
+ }
456
+ }
457
+ /**
458
+ * Handle release command
459
+ */
460
+ handleReleaseCommand(name) {
461
+ const key = `release:${name}`;
462
+ if (this.processedReleaseCommands.has(key)) {
463
+ return;
464
+ }
465
+ this.processedReleaseCommands.add(key);
466
+ this.log(` Release: ${name}`);
467
+ // Try dashboard API first, fall back to callback
468
+ if (this.config.dashboardPort) {
469
+ this.releaseViaDashboardApi(name).catch(err => {
470
+ this.logError(` Dashboard release failed: ${err.message}`);
471
+ this.config.onRelease?.(name);
472
+ });
473
+ }
474
+ else if (this.config.onRelease) {
475
+ this.config.onRelease(name);
476
+ }
477
+ }
478
+ /**
479
+ * Spawn agent via dashboard API
480
+ */
481
+ async spawnViaDashboardApi(name, cli, task) {
482
+ const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/spawn`, {
483
+ method: 'POST',
484
+ headers: { 'Content-Type': 'application/json' },
485
+ body: JSON.stringify({ name, cli, task }),
486
+ });
487
+ if (!response.ok) {
488
+ throw new Error(`HTTP ${response.status}`);
489
+ }
490
+ }
491
+ /**
492
+ * Release agent via dashboard API
493
+ */
494
+ async releaseViaDashboardApi(name) {
495
+ const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/spawned/${encodeURIComponent(name)}`, {
496
+ method: 'DELETE',
497
+ });
498
+ if (!response.ok) {
499
+ const body = await response.json().catch(() => ({ error: 'Unknown' }));
500
+ throw new Error(`HTTP ${response.status}: ${body.error || 'Unknown error'}`);
501
+ }
502
+ this.log(`Released ${name} via dashboard API`);
503
+ }
504
+ // =========================================================================
505
+ // Socket communication
506
+ // =========================================================================
507
+ /**
508
+ * Connect to the relay-pty socket
509
+ */
510
+ async connectToSocket() {
511
+ const timeout = this.config.socketConnectTimeoutMs ?? 5000;
512
+ const maxAttempts = this.config.socketReconnectAttempts ?? 3;
513
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
514
+ try {
515
+ await this.attemptSocketConnection(timeout);
516
+ this.log(` Socket connected`);
517
+ return;
518
+ }
519
+ catch (err) {
520
+ this.logError(` Socket connect attempt ${attempt}/${maxAttempts} failed: ${err.message}`);
521
+ if (attempt < maxAttempts) {
522
+ await sleep(1000 * attempt); // Exponential backoff
523
+ }
524
+ }
525
+ }
526
+ throw new Error(`Failed to connect to socket after ${maxAttempts} attempts`);
527
+ }
528
+ /**
529
+ * Attempt a single socket connection
530
+ */
531
+ attemptSocketConnection(timeout) {
532
+ return new Promise((resolve, reject) => {
533
+ const timer = setTimeout(() => {
534
+ reject(new Error('Socket connection timeout'));
535
+ }, timeout);
536
+ this.socket = createConnection(this.socketPath, () => {
537
+ clearTimeout(timer);
538
+ this.socketConnected = true;
539
+ resolve();
540
+ });
541
+ this.socket.on('error', (err) => {
542
+ clearTimeout(timer);
543
+ this.socketConnected = false;
544
+ reject(err);
545
+ });
546
+ this.socket.on('close', () => {
547
+ this.socketConnected = false;
548
+ this.log(` Socket closed`);
549
+ });
550
+ // Handle incoming data (responses)
551
+ let buffer = '';
552
+ this.socket.on('data', (data) => {
553
+ buffer += data.toString();
554
+ // Process complete lines
555
+ const lines = buffer.split('\n');
556
+ buffer = lines.pop() ?? ''; // Keep incomplete line in buffer
557
+ for (const line of lines) {
558
+ if (line.trim()) {
559
+ this.handleSocketResponse(line);
560
+ }
561
+ }
562
+ });
563
+ });
564
+ }
565
+ /**
566
+ * Disconnect from socket
567
+ */
568
+ disconnectSocket() {
569
+ if (this.socket) {
570
+ this.socket.destroy();
571
+ this.socket = undefined;
572
+ this.socketConnected = false;
573
+ }
574
+ // Reject all pending injections
575
+ for (const [id, pending] of this.pendingInjections) {
576
+ clearTimeout(pending.timeout);
577
+ pending.reject(new Error('Socket disconnected'));
578
+ }
579
+ this.pendingInjections.clear();
580
+ }
581
+ /**
582
+ * Send a request to the socket and optionally wait for response
583
+ */
584
+ sendSocketRequest(request) {
585
+ return new Promise((resolve, reject) => {
586
+ if (!this.socket || !this.socketConnected) {
587
+ reject(new Error('Socket not connected'));
588
+ return;
589
+ }
590
+ const json = JSON.stringify(request) + '\n';
591
+ this.socket.write(json, (err) => {
592
+ if (err) {
593
+ reject(err);
594
+ }
595
+ else {
596
+ resolve();
597
+ }
598
+ });
599
+ });
600
+ }
601
+ /**
602
+ * Handle a response from the socket
603
+ */
604
+ handleSocketResponse(line) {
605
+ try {
606
+ const response = JSON.parse(line);
607
+ switch (response.type) {
608
+ case 'inject_result':
609
+ this.handleInjectResult(response);
610
+ break;
611
+ case 'status':
612
+ // Status responses are typically requested explicitly
613
+ this.log(` Status: idle=${response.agent_idle} queue=${response.queue_length}`);
614
+ break;
615
+ case 'backpressure':
616
+ this.handleBackpressure(response);
617
+ break;
618
+ case 'error':
619
+ this.logError(` Socket error: ${response.message}`);
620
+ break;
621
+ case 'shutdown_ack':
622
+ this.log(` Shutdown acknowledged`);
623
+ break;
624
+ }
625
+ }
626
+ catch (err) {
627
+ this.logError(` Failed to parse socket response: ${err.message}`);
628
+ }
629
+ }
630
+ /**
631
+ * Handle injection result response
632
+ */
633
+ handleInjectResult(response) {
634
+ const pending = this.pendingInjections.get(response.id);
635
+ if (!pending) {
636
+ // Response for unknown message - might be from a previous session
637
+ return;
638
+ }
639
+ if (response.status === 'delivered') {
640
+ clearTimeout(pending.timeout);
641
+ this.pendingInjections.delete(response.id);
642
+ pending.resolve(true);
643
+ this.log(` Message ${response.id.substring(0, 8)} delivered`);
644
+ }
645
+ else if (response.status === 'failed') {
646
+ clearTimeout(pending.timeout);
647
+ this.pendingInjections.delete(response.id);
648
+ pending.resolve(false);
649
+ this.logError(` Message ${response.id.substring(0, 8)} failed: ${response.error}`);
650
+ this.emit('injection-failed', {
651
+ messageId: response.id,
652
+ from: 'unknown',
653
+ error: response.error ?? 'Unknown error',
654
+ });
655
+ }
656
+ // queued/injecting are intermediate states - wait for final status
657
+ }
658
+ /**
659
+ * Handle backpressure notification
660
+ */
661
+ handleBackpressure(response) {
662
+ const wasActive = this.backpressureActive;
663
+ this.backpressureActive = !response.accept;
664
+ if (this.backpressureActive !== wasActive) {
665
+ this.log(` Backpressure: ${this.backpressureActive ? 'ACTIVE' : 'cleared'} (queue=${response.queue_length})`);
666
+ this.emit('backpressure', { queueLength: response.queue_length, accept: response.accept });
667
+ // Resume processing if backpressure cleared
668
+ if (!this.backpressureActive) {
669
+ this.processMessageQueue();
670
+ }
671
+ }
672
+ }
673
+ // =========================================================================
674
+ // Message handling
675
+ // =========================================================================
676
+ /**
677
+ * Inject a message into the agent via socket
678
+ */
679
+ async injectMessage(msg) {
680
+ this.log(` === INJECT START: ${msg.messageId.substring(0, 8)} from ${msg.from} ===`);
681
+ if (!this.socket || !this.socketConnected) {
682
+ this.logError(` Cannot inject - socket not connected`);
683
+ return false;
684
+ }
685
+ // Build injection content
686
+ const content = buildInjectionString(msg);
687
+ this.log(` Injection content (${content.length} bytes): ${content.substring(0, 100)}...`);
688
+ // Create request
689
+ const request = {
690
+ type: 'inject',
691
+ id: msg.messageId,
692
+ from: msg.from,
693
+ body: content,
694
+ priority: msg.importance ?? 0,
695
+ };
696
+ this.log(` Sending inject request to socket...`);
697
+ // Create promise for result
698
+ return new Promise((resolve, reject) => {
699
+ const timeout = setTimeout(() => {
700
+ this.logError(` Inject timeout for ${msg.messageId.substring(0, 8)}`);
701
+ this.pendingInjections.delete(msg.messageId);
702
+ resolve(false); // Timeout = failure
703
+ }, 30000); // 30 second timeout for injection
704
+ this.pendingInjections.set(msg.messageId, { resolve, reject, timeout });
705
+ // Send request
706
+ this.sendSocketRequest(request)
707
+ .then(() => {
708
+ this.log(` Socket request sent successfully`);
709
+ })
710
+ .catch((err) => {
711
+ this.logError(` Socket request failed: ${err.message}`);
712
+ clearTimeout(timeout);
713
+ this.pendingInjections.delete(msg.messageId);
714
+ resolve(false);
715
+ });
716
+ });
717
+ }
718
+ /**
719
+ * Process queued messages
720
+ */
721
+ async processMessageQueue() {
722
+ if (!this.readyForMessages || this.backpressureActive || this.isInjecting) {
723
+ return;
724
+ }
725
+ if (this.messageQueue.length === 0) {
726
+ return;
727
+ }
728
+ this.isInjecting = true;
729
+ const msg = this.messageQueue.shift();
730
+ const bodyPreview = msg.body.substring(0, 50).replace(/\n/g, '\\n');
731
+ this.log(` Processing message from ${msg.from}: "${bodyPreview}..." (remaining=${this.messageQueue.length})`);
732
+ try {
733
+ const success = await this.injectMessage(msg);
734
+ if (!success) {
735
+ this.logError(` Injection failed for message ${msg.messageId.substring(0, 8)}`);
736
+ this.injectionMetrics.failed++;
737
+ this.config.onInjectionFailed?.(msg.messageId, 'Injection failed');
738
+ }
739
+ else {
740
+ this.injectionMetrics.successFirstTry++;
741
+ }
742
+ this.injectionMetrics.total++;
743
+ }
744
+ catch (err) {
745
+ this.logError(` Injection error: ${err.message}`);
746
+ this.injectionMetrics.failed++;
747
+ this.injectionMetrics.total++;
748
+ }
749
+ finally {
750
+ this.isInjecting = false;
751
+ // Process next message after delay
752
+ if (this.messageQueue.length > 0 && !this.backpressureActive) {
753
+ setTimeout(() => this.processMessageQueue(), INJECTION_CONSTANTS.QUEUE_PROCESS_DELAY_MS);
754
+ }
755
+ }
756
+ }
757
+ /**
758
+ * Override handleIncomingMessage to trigger queue processing
759
+ */
760
+ handleIncomingMessage(from, payload, messageId, meta, originalTo) {
761
+ this.log(` === MESSAGE RECEIVED: ${messageId.substring(0, 8)} from ${from} ===`);
762
+ this.log(` Body preview: ${payload.body?.substring(0, 100) ?? '(no body)'}...`);
763
+ super.handleIncomingMessage(from, payload, messageId, meta, originalTo);
764
+ this.log(` Queue length after add: ${this.messageQueue.length}`);
765
+ this.processMessageQueue();
766
+ }
767
+ // =========================================================================
768
+ // Output parsing
769
+ // =========================================================================
770
+ /**
771
+ * Parse relay commands from output
772
+ */
773
+ parseRelayCommands() {
774
+ const cleanContent = stripAnsi(this.rawBuffer);
775
+ if (cleanContent.length <= this.lastParsedLength) {
776
+ return;
777
+ }
778
+ // Parse new content with lookback for fenced messages
779
+ const lookbackStart = Math.max(0, this.lastParsedLength - 500);
780
+ const contentToParse = cleanContent.substring(lookbackStart);
781
+ // Parse fenced messages
782
+ this.parseFencedMessages(contentToParse);
783
+ // Parse single-line messages
784
+ this.parseSingleLineMessages(contentToParse);
785
+ // Parse spawn/release commands
786
+ this.parseSpawnReleaseCommands(contentToParse);
787
+ this.lastParsedLength = cleanContent.length;
788
+ }
789
+ /**
790
+ * Parse fenced multi-line messages
791
+ */
792
+ parseFencedMessages(content) {
793
+ const escapedPrefix = this.relayPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
794
+ const fencePattern = new RegExp(`${escapedPrefix}(\\S+)(?:\\s+\\[thread:([\\w-]+)\\])?\\s*<<<([\\s\\S]*?)>>>`, 'g');
795
+ let match;
796
+ while ((match = fencePattern.exec(content)) !== null) {
797
+ const target = match[1];
798
+ const thread = match[2];
799
+ const body = match[3].trim();
800
+ if (!body || target === 'spawn' || target === 'release') {
801
+ continue;
802
+ }
803
+ this.sendRelayCommand({
804
+ to: target,
805
+ kind: 'message',
806
+ body,
807
+ thread,
808
+ raw: match[0],
809
+ });
810
+ }
811
+ }
812
+ /**
813
+ * Parse single-line messages
814
+ */
815
+ parseSingleLineMessages(content) {
816
+ const lines = content.split('\n');
817
+ const escapedPrefix = this.relayPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
818
+ const pattern = new RegExp(`${escapedPrefix}(\\S+)(?:\\s+\\[thread:([\\w-]+)\\])?\\s+(.+)$`);
819
+ for (const line of lines) {
820
+ // Skip fenced messages
821
+ if (line.includes('<<<') || line.includes('>>>')) {
822
+ continue;
823
+ }
824
+ const match = line.match(pattern);
825
+ if (!match) {
826
+ continue;
827
+ }
828
+ const target = match[1];
829
+ const thread = match[2];
830
+ const body = match[3].trim();
831
+ if (!body || target === 'spawn' || target === 'release') {
832
+ continue;
833
+ }
834
+ this.sendRelayCommand({
835
+ to: target,
836
+ kind: 'message',
837
+ body,
838
+ thread,
839
+ raw: line,
840
+ });
841
+ }
842
+ }
843
+ // =========================================================================
844
+ // Summary and session end detection
845
+ // =========================================================================
846
+ /**
847
+ * Check for [[SUMMARY]] blocks
848
+ */
849
+ checkForSummary(content) {
850
+ const result = parseSummaryWithDetails(content);
851
+ if (!result.found || !result.valid) {
852
+ return;
853
+ }
854
+ if (result.rawContent === this.lastSummaryRawContent) {
855
+ return;
856
+ }
857
+ this.lastSummaryRawContent = result.rawContent ?? '';
858
+ this.emit('summary', {
859
+ agentName: this.config.name,
860
+ summary: result.summary,
861
+ });
862
+ }
863
+ /**
864
+ * Check for [[SESSION_END]] blocks
865
+ */
866
+ checkForSessionEnd(content) {
867
+ if (this.sessionEndProcessed) {
868
+ return;
869
+ }
870
+ const sessionEnd = parseSessionEndFromOutput(content);
871
+ if (!sessionEnd) {
872
+ return;
873
+ }
874
+ this.sessionEndProcessed = true;
875
+ this.emit('session-end', {
876
+ agentName: this.config.name,
877
+ marker: sessionEnd,
878
+ });
879
+ }
880
+ // =========================================================================
881
+ // Public API
882
+ // =========================================================================
883
+ /**
884
+ * Query status from relay-pty
885
+ */
886
+ async queryStatus() {
887
+ if (!this.socket || !this.socketConnected) {
888
+ return null;
889
+ }
890
+ try {
891
+ await this.sendSocketRequest({ type: 'status' });
892
+ // Response will come asynchronously via handleSocketResponse
893
+ // For now, return null - could implement request/response matching
894
+ return null;
895
+ }
896
+ catch {
897
+ return null;
898
+ }
899
+ }
900
+ /**
901
+ * Get raw output buffer
902
+ */
903
+ getRawOutput() {
904
+ return this.rawBuffer;
905
+ }
906
+ /**
907
+ * Check if backpressure is active
908
+ */
909
+ isBackpressureActive() {
910
+ return this.backpressureActive;
911
+ }
912
+ /**
913
+ * Get the socket path
914
+ */
915
+ getSocketPath() {
916
+ return this.socketPath;
917
+ }
918
+ /**
919
+ * Get the relay-pty process PID
920
+ */
921
+ get pid() {
922
+ return this.relayPtyProcess?.pid;
923
+ }
924
+ /**
925
+ * Get the log file path (not used by relay-pty, returns undefined)
926
+ */
927
+ get logPath() {
928
+ return this._logPath;
929
+ }
930
+ /**
931
+ * Kill the process forcefully
932
+ */
933
+ async kill() {
934
+ if (this.relayPtyProcess && !this.relayPtyProcess.killed) {
935
+ this.relayPtyProcess.kill('SIGKILL');
936
+ }
937
+ this.running = false;
938
+ this.disconnectSocket();
939
+ this.destroyClient();
940
+ }
941
+ /**
942
+ * Get output lines (for compatibility with PtyWrapper)
943
+ * @param limit Maximum number of lines to return
944
+ */
945
+ getOutput(limit) {
946
+ const lines = this.rawBuffer.split('\n');
947
+ if (limit && limit > 0) {
948
+ return lines.slice(-limit);
949
+ }
950
+ return lines;
951
+ }
952
+ /**
953
+ * Write data directly to the process stdin
954
+ * @param data Data to write
955
+ */
956
+ async write(data) {
957
+ if (!this.relayPtyProcess || !this.relayPtyProcess.stdin) {
958
+ throw new Error('Process not running');
959
+ }
960
+ const buffer = typeof data === 'string' ? Buffer.from(data) : data;
961
+ this.relayPtyProcess.stdin.write(buffer);
962
+ }
963
+ /**
964
+ * Get the agent ID (from continuity if available)
965
+ */
966
+ getAgentId() {
967
+ return this.agentId;
968
+ }
969
+ }
970
+ //# sourceMappingURL=relay-pty-orchestrator.js.map