@aoagents/ao-web 0.2.3 → 0.2.5

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 (265) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +228 -221
  3. package/.next/app-path-routes-manifest.json +7 -6
  4. package/.next/build-manifest.json +14 -14
  5. package/.next/next-minimal-server.js.nft.json +1 -1
  6. package/.next/next-server.js.nft.json +1 -1
  7. package/.next/prerender-manifest.json +31 -31
  8. package/.next/react-loadable-manifest.json +14 -14
  9. package/.next/required-server-files.json +7 -4
  10. package/.next/server/app/_not-found/page.js +2 -2
  11. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  12. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/server/app/_not-found.html +1 -1
  14. package/.next/server/app/_not-found.rsc +15 -15
  15. package/.next/server/app/api/backlog/route.js +1 -1
  16. package/.next/server/app/api/backlog/route.js.nft.json +1 -1
  17. package/.next/server/app/api/backlog/route_client-reference-manifest.js +1 -1
  18. package/.next/server/app/api/events/route.js +2 -2
  19. package/.next/server/app/api/events/route.js.nft.json +1 -1
  20. package/.next/server/app/api/events/route_client-reference-manifest.js +1 -1
  21. package/.next/server/app/api/issues/route.js +1 -1
  22. package/.next/server/app/api/issues/route.js.nft.json +1 -1
  23. package/.next/server/app/api/issues/route_client-reference-manifest.js +1 -1
  24. package/.next/server/app/api/observability/route.js +1 -1
  25. package/.next/server/app/api/observability/route.js.nft.json +1 -1
  26. package/.next/server/app/api/observability/route_client-reference-manifest.js +1 -1
  27. package/.next/server/app/api/orchestrators/route.js +1 -1
  28. package/.next/server/app/api/orchestrators/route.js.nft.json +1 -1
  29. package/.next/server/app/api/orchestrators/route_client-reference-manifest.js +1 -1
  30. package/.next/server/app/api/projects/route.js +1 -1
  31. package/.next/server/app/api/projects/route.js.nft.json +1 -1
  32. package/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  33. package/.next/server/app/api/prs/[id]/merge/route.js +1 -1
  34. package/.next/server/app/api/prs/[id]/merge/route.js.nft.json +1 -1
  35. package/.next/server/app/api/prs/[id]/merge/route_client-reference-manifest.js +1 -1
  36. package/.next/server/app/api/runtime/terminal/route.js +1 -1
  37. package/.next/server/app/api/runtime/terminal/route.js.nft.json +1 -1
  38. package/.next/server/app/api/runtime/terminal/route_client-reference-manifest.js +1 -1
  39. package/.next/server/app/api/sessions/[id]/kill/route.js +1 -1
  40. package/.next/server/app/api/sessions/[id]/kill/route.js.nft.json +1 -1
  41. package/.next/server/app/api/sessions/[id]/kill/route_client-reference-manifest.js +1 -1
  42. package/.next/server/app/api/sessions/[id]/message/route.js +1 -1
  43. package/.next/server/app/api/sessions/[id]/message/route.js.nft.json +1 -1
  44. package/.next/server/app/api/sessions/[id]/message/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/sessions/[id]/remap/route.js +1 -1
  46. package/.next/server/app/api/sessions/[id]/remap/route.js.nft.json +1 -1
  47. package/.next/server/app/api/sessions/[id]/remap/route_client-reference-manifest.js +1 -1
  48. package/.next/server/app/api/sessions/[id]/restore/route.js +1 -1
  49. package/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -1
  50. package/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +1 -1
  51. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  52. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  53. package/.next/server/app/api/sessions/[id]/route_client-reference-manifest.js +1 -1
  54. package/.next/server/app/api/sessions/[id]/send/route.js +1 -1
  55. package/.next/server/app/api/sessions/[id]/send/route.js.nft.json +1 -1
  56. package/.next/server/app/api/sessions/[id]/send/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/sessions/patches/route.js +1 -0
  58. package/.next/server/app/api/sessions/patches/route.js.nft.json +1 -0
  59. package/.next/server/app/api/sessions/patches/route_client-reference-manifest.js +1 -0
  60. package/.next/server/app/api/sessions/route.js +1 -1
  61. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  62. package/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
  63. package/.next/server/app/api/setup-labels/route.js +1 -1
  64. package/.next/server/app/api/setup-labels/route.js.nft.json +1 -1
  65. package/.next/server/app/api/setup-labels/route_client-reference-manifest.js +1 -1
  66. package/.next/server/app/api/spawn/route.js +1 -1
  67. package/.next/server/app/api/spawn/route.js.nft.json +1 -1
  68. package/.next/server/app/api/spawn/route_client-reference-manifest.js +1 -1
  69. package/.next/server/app/api/verify/route.js +1 -1
  70. package/.next/server/app/api/verify/route.js.nft.json +1 -1
  71. package/.next/server/app/api/verify/route_client-reference-manifest.js +1 -1
  72. package/.next/server/app/api/webhooks/[...slug]/route.js +1 -1
  73. package/.next/server/app/api/webhooks/[...slug]/route.js.nft.json +1 -1
  74. package/.next/server/app/api/webhooks/[...slug]/route_client-reference-manifest.js +1 -1
  75. package/.next/server/app/apple-icon/route.js +1 -1
  76. package/.next/server/app/apple-icon/route.js.nft.json +1 -1
  77. package/.next/server/app/apple-icon/route_client-reference-manifest.js +1 -1
  78. package/.next/server/app/dev/terminal-test/page.js +3 -3
  79. package/.next/server/app/dev/terminal-test/page.js.nft.json +1 -1
  80. package/.next/server/app/dev/terminal-test/page_client-reference-manifest.js +1 -1
  81. package/.next/server/app/dev/terminal-test.html +1 -1
  82. package/.next/server/app/dev/terminal-test.rsc +17 -17
  83. package/.next/server/app/icon/route.js +1 -1
  84. package/.next/server/app/icon/route.js.nft.json +1 -1
  85. package/.next/server/app/icon/route_client-reference-manifest.js +1 -1
  86. package/.next/server/app/icon-192/route.js +1 -1
  87. package/.next/server/app/icon-192/route.js.nft.json +1 -1
  88. package/.next/server/app/icon-192/route_client-reference-manifest.js +1 -1
  89. package/.next/server/app/icon-512/route.js +1 -1
  90. package/.next/server/app/icon-512/route.js.nft.json +1 -1
  91. package/.next/server/app/icon-512/route_client-reference-manifest.js +1 -1
  92. package/.next/server/app/manifest.webmanifest/route.js +2 -2
  93. package/.next/server/app/manifest.webmanifest/route.js.nft.json +1 -1
  94. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  95. package/.next/server/app/manifest.webmanifest.body +1 -1
  96. package/.next/server/app/orchestrators/page.js +2 -2
  97. package/.next/server/app/orchestrators/page.js.nft.json +1 -1
  98. package/.next/server/app/orchestrators/page_client-reference-manifest.js +1 -1
  99. package/.next/server/app/page.js +2 -2
  100. package/.next/server/app/page.js.nft.json +1 -1
  101. package/.next/server/app/page_client-reference-manifest.js +1 -1
  102. package/.next/server/app/prs/page.js +2 -2
  103. package/.next/server/app/prs/page.js.nft.json +1 -1
  104. package/.next/server/app/prs/page_client-reference-manifest.js +1 -1
  105. package/.next/server/app/sessions/[id]/page.js +5 -5
  106. package/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  107. package/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  108. package/.next/server/app/test-direct/page.js +2 -2
  109. package/.next/server/app/test-direct/page.js.nft.json +1 -1
  110. package/.next/server/app/test-direct/page_client-reference-manifest.js +1 -1
  111. package/.next/server/app/test-direct.html +1 -1
  112. package/.next/server/app/test-direct.rsc +17 -17
  113. package/.next/server/app-paths-manifest.json +7 -6
  114. package/.next/server/chunks/100.js +1 -0
  115. package/.next/server/chunks/106.js +1 -0
  116. package/.next/server/chunks/172.js +9 -0
  117. package/.next/server/chunks/180.js +25 -0
  118. package/.next/server/chunks/333.js +1 -0
  119. package/.next/server/chunks/367.js +3 -0
  120. package/.next/server/chunks/561.js +22 -0
  121. package/.next/server/chunks/627.js +380 -363
  122. package/.next/server/chunks/803.js +6 -0
  123. package/.next/server/chunks/886.js +384 -0
  124. package/.next/server/chunks/907.js +1 -0
  125. package/.next/server/middleware-build-manifest.js +1 -1
  126. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  127. package/.next/server/next-font-manifest.js +1 -1
  128. package/.next/server/next-font-manifest.json +1 -1
  129. package/.next/server/pages/404.html +1 -1
  130. package/.next/server/pages/500.html +1 -1
  131. package/.next/server/pages/_app.js +1 -1
  132. package/.next/server/pages/_app.js.nft.json +1 -1
  133. package/.next/server/pages/_document.js +1 -1
  134. package/.next/server/pages/_document.js.nft.json +1 -1
  135. package/.next/server/pages/_error.js +3 -3
  136. package/.next/server/pages/_error.js.nft.json +1 -1
  137. package/.next/server/server-reference-manifest.json +1 -1
  138. package/.next/static/OyzSw2fQpoJHXyoBWoZ2X/_buildManifest.js +1 -0
  139. package/.next/static/chunks/1461-af7c54935f21d56d.js +1 -0
  140. package/.next/static/chunks/{9393.acf1934a190d793b.js → 2529.32352c1ce5253e3e.js} +1 -1
  141. package/.next/static/chunks/{7411.ecda44797fb514a0.js → 5491.fd98884d48631149.js} +1 -1
  142. package/.next/static/chunks/6923-ef8434dfec6f2a3c.js +1 -0
  143. package/.next/static/chunks/7097.fc904e5f313c4994.js +1 -0
  144. package/.next/static/chunks/7317.685aa5231218e8d3.js +1 -0
  145. package/.next/static/chunks/8713-d3d663f55dc00e48.js +1 -0
  146. package/.next/static/chunks/8785-6b4b3ff260617997.js +1 -0
  147. package/.next/static/chunks/88a6fc35-f836b4b72df5eafa.js +1 -0
  148. package/.next/static/chunks/9478.656c9c263bc55893.js +1 -0
  149. package/.next/static/chunks/app/_not-found/page-019391920005654f.js +1 -0
  150. package/.next/static/chunks/app/api/backlog/route-019391920005654f.js +1 -0
  151. package/.next/static/chunks/app/api/events/route-019391920005654f.js +1 -0
  152. package/.next/static/chunks/app/api/issues/route-019391920005654f.js +1 -0
  153. package/.next/static/chunks/app/api/observability/route-019391920005654f.js +1 -0
  154. package/.next/static/chunks/app/api/orchestrators/route-019391920005654f.js +1 -0
  155. package/.next/static/chunks/app/api/projects/route-019391920005654f.js +1 -0
  156. package/.next/static/chunks/app/api/prs/[id]/merge/route-019391920005654f.js +1 -0
  157. package/.next/static/chunks/app/api/runtime/terminal/route-019391920005654f.js +1 -0
  158. package/.next/static/chunks/app/api/sessions/[id]/kill/route-019391920005654f.js +1 -0
  159. package/.next/static/chunks/app/api/sessions/[id]/message/route-019391920005654f.js +1 -0
  160. package/.next/static/chunks/app/api/sessions/[id]/remap/route-019391920005654f.js +1 -0
  161. package/.next/static/chunks/app/api/sessions/[id]/restore/route-019391920005654f.js +1 -0
  162. package/.next/static/chunks/app/api/sessions/[id]/route-019391920005654f.js +1 -0
  163. package/.next/static/chunks/app/api/sessions/[id]/send/route-019391920005654f.js +1 -0
  164. package/.next/static/chunks/app/api/sessions/patches/route-019391920005654f.js +1 -0
  165. package/.next/static/chunks/app/api/sessions/route-019391920005654f.js +1 -0
  166. package/.next/static/chunks/app/api/setup-labels/route-019391920005654f.js +1 -0
  167. package/.next/static/chunks/app/api/spawn/route-019391920005654f.js +1 -0
  168. package/.next/static/chunks/app/api/verify/route-019391920005654f.js +1 -0
  169. package/.next/static/chunks/app/api/webhooks/[...slug]/route-019391920005654f.js +1 -0
  170. package/.next/static/chunks/app/apple-icon/route-019391920005654f.js +1 -0
  171. package/.next/static/chunks/app/dev/terminal-test/page-4912fa9e6459f124.js +1 -0
  172. package/.next/static/chunks/app/error-670f1d8bf2b6859c.js +1 -0
  173. package/.next/static/chunks/app/{global-error-7b5c8ae45329c659.js → global-error-ca06d2b1be2d4ae0.js} +1 -1
  174. package/.next/static/chunks/app/icon/route-019391920005654f.js +1 -0
  175. package/.next/static/chunks/app/icon-192/route-019391920005654f.js +1 -0
  176. package/.next/static/chunks/app/icon-512/route-019391920005654f.js +1 -0
  177. package/.next/static/chunks/app/layout-ddf79478d9a673bb.js +1 -0
  178. package/.next/static/chunks/app/loading-019391920005654f.js +1 -0
  179. package/.next/static/chunks/app/manifest.webmanifest/route-019391920005654f.js +1 -0
  180. package/.next/static/chunks/app/{not-found-3772c2e09c29d80f.js → not-found-824d5d3c6e296eeb.js} +1 -1
  181. package/.next/static/chunks/app/orchestrators/page-4da04720a09483d2.js +1 -0
  182. package/.next/static/chunks/app/page-abf3e3c98cac072f.js +1 -0
  183. package/.next/static/chunks/app/prs/page-46455c406183ffbf.js +1 -0
  184. package/.next/static/chunks/app/sessions/[id]/error-eb0973907da68a37.js +1 -0
  185. package/.next/static/chunks/app/sessions/[id]/loading-019391920005654f.js +1 -0
  186. package/.next/static/chunks/app/sessions/[id]/{not-found-3772c2e09c29d80f.js → not-found-824d5d3c6e296eeb.js} +1 -1
  187. package/.next/static/chunks/app/sessions/[id]/page-7eb67fd32f39b15f.js +1 -0
  188. package/.next/static/chunks/app/test-direct/page-1fd63edb9a9ca8be.js +1 -0
  189. package/.next/static/chunks/{df4ed4d4.6a752eba3933e9a8.js → df4ed4d4.b6997d8b8ce9d79b.js} +1 -1
  190. package/.next/static/chunks/framework-7060e2ac4971c604.js +1 -0
  191. package/.next/static/chunks/main-app-690acf9d5d2050c9.js +1 -0
  192. package/.next/static/chunks/main-ed1610689fbd6f0d.js +1 -0
  193. package/.next/static/chunks/pages/_app-f4baf4dbe88f6f54.js +1 -0
  194. package/.next/static/chunks/pages/_error-a7f6723f42093f29.js +1 -0
  195. package/.next/static/chunks/{webpack-e12ceebeb7a1cc7e.js → webpack-ab6c08c78ffc8113.js} +1 -1
  196. package/.next/static/css/a5398483e6b75ad3.css +1 -0
  197. package/LICENSE +21 -0
  198. package/dist-server/direct-terminal-ws.js +33 -236
  199. package/dist-server/mux-websocket.js +516 -0
  200. package/dist-server/start-all.js +0 -2
  201. package/next.config.js +1 -0
  202. package/package.json +27 -27
  203. package/.next/server/chunks/27.js +0 -438
  204. package/.next/server/chunks/377.js +0 -1
  205. package/.next/server/chunks/393.js +0 -1
  206. package/.next/server/chunks/639.js +0 -6
  207. package/.next/server/chunks/693.js +0 -1
  208. package/.next/server/chunks/705.js +0 -22
  209. package/.next/server/chunks/787.js +0 -29
  210. package/.next/server/chunks/796.js +0 -1
  211. package/.next/server/chunks/934.js +0 -1
  212. package/.next/server/chunks/956.js +0 -9
  213. package/.next/static/chunks/1250-e7cf6b069fbb03ed.js +0 -1
  214. package/.next/static/chunks/2205.498806f73783aa54.js +0 -1
  215. package/.next/static/chunks/3698-9c12c45b8184022c.js +0 -1
  216. package/.next/static/chunks/6381.1541d5695a727108.js +0 -1
  217. package/.next/static/chunks/7505-2d2422d31862995f.js +0 -1
  218. package/.next/static/chunks/8597-1385f90ec1cebf47.js +0 -1
  219. package/.next/static/chunks/8762.f3d526855363db16.js +0 -1
  220. package/.next/static/chunks/a51c26f2-a21e680a5df6764e.js +0 -1
  221. package/.next/static/chunks/app/_not-found/page-2224bc1d3dce1b3e.js +0 -1
  222. package/.next/static/chunks/app/api/backlog/route-2224bc1d3dce1b3e.js +0 -1
  223. package/.next/static/chunks/app/api/events/route-2224bc1d3dce1b3e.js +0 -1
  224. package/.next/static/chunks/app/api/issues/route-2224bc1d3dce1b3e.js +0 -1
  225. package/.next/static/chunks/app/api/observability/route-2224bc1d3dce1b3e.js +0 -1
  226. package/.next/static/chunks/app/api/orchestrators/route-2224bc1d3dce1b3e.js +0 -1
  227. package/.next/static/chunks/app/api/projects/route-2224bc1d3dce1b3e.js +0 -1
  228. package/.next/static/chunks/app/api/prs/[id]/merge/route-2224bc1d3dce1b3e.js +0 -1
  229. package/.next/static/chunks/app/api/runtime/terminal/route-2224bc1d3dce1b3e.js +0 -1
  230. package/.next/static/chunks/app/api/sessions/[id]/kill/route-2224bc1d3dce1b3e.js +0 -1
  231. package/.next/static/chunks/app/api/sessions/[id]/message/route-2224bc1d3dce1b3e.js +0 -1
  232. package/.next/static/chunks/app/api/sessions/[id]/remap/route-2224bc1d3dce1b3e.js +0 -1
  233. package/.next/static/chunks/app/api/sessions/[id]/restore/route-2224bc1d3dce1b3e.js +0 -1
  234. package/.next/static/chunks/app/api/sessions/[id]/route-2224bc1d3dce1b3e.js +0 -1
  235. package/.next/static/chunks/app/api/sessions/[id]/send/route-2224bc1d3dce1b3e.js +0 -1
  236. package/.next/static/chunks/app/api/sessions/route-2224bc1d3dce1b3e.js +0 -1
  237. package/.next/static/chunks/app/api/setup-labels/route-2224bc1d3dce1b3e.js +0 -1
  238. package/.next/static/chunks/app/api/spawn/route-2224bc1d3dce1b3e.js +0 -1
  239. package/.next/static/chunks/app/api/verify/route-2224bc1d3dce1b3e.js +0 -1
  240. package/.next/static/chunks/app/api/webhooks/[...slug]/route-2224bc1d3dce1b3e.js +0 -1
  241. package/.next/static/chunks/app/apple-icon/route-2224bc1d3dce1b3e.js +0 -1
  242. package/.next/static/chunks/app/dev/terminal-test/page-ac0ce5b046fcad82.js +0 -1
  243. package/.next/static/chunks/app/error-4896c9d3b7681a80.js +0 -1
  244. package/.next/static/chunks/app/icon/route-2224bc1d3dce1b3e.js +0 -1
  245. package/.next/static/chunks/app/icon-192/route-2224bc1d3dce1b3e.js +0 -1
  246. package/.next/static/chunks/app/icon-512/route-2224bc1d3dce1b3e.js +0 -1
  247. package/.next/static/chunks/app/layout-023f2083204e4f68.js +0 -1
  248. package/.next/static/chunks/app/loading-2224bc1d3dce1b3e.js +0 -1
  249. package/.next/static/chunks/app/manifest.webmanifest/route-2224bc1d3dce1b3e.js +0 -1
  250. package/.next/static/chunks/app/orchestrators/page-df85236df674fc5a.js +0 -1
  251. package/.next/static/chunks/app/page-e97da633b7ef25f2.js +0 -1
  252. package/.next/static/chunks/app/prs/page-8cc0fce584d238c5.js +0 -1
  253. package/.next/static/chunks/app/sessions/[id]/error-90bc99b777fecabf.js +0 -1
  254. package/.next/static/chunks/app/sessions/[id]/loading-2224bc1d3dce1b3e.js +0 -1
  255. package/.next/static/chunks/app/sessions/[id]/page-c9c95a8604786cff.js +0 -1
  256. package/.next/static/chunks/app/test-direct/page-16ceeb9f7664394a.js +0 -1
  257. package/.next/static/chunks/framework-f8b8ba0f71d38056.js +0 -1
  258. package/.next/static/chunks/main-2bc85c765bf1fc49.js +0 -1
  259. package/.next/static/chunks/main-app-93fb36c3bd1a6739.js +0 -1
  260. package/.next/static/chunks/pages/_app-a0b975794f15bd68.js +0 -1
  261. package/.next/static/chunks/pages/_error-5f4e3b5eea57917d.js +0 -1
  262. package/.next/static/css/908f93fdd7ffba42.css +0 -1
  263. package/.next/static/oB_TdR1wCu7ot1D0AG_cw/_buildManifest.js +0 -1
  264. package/dist-server/terminal-websocket.js +0 -375
  265. /package/.next/static/{oB_TdR1wCu7ot1D0AG_cw → OyzSw2fQpoJHXyoBWoZ2X}/_ssgManifest.js +0 -0
@@ -1,53 +1,28 @@
1
1
  /**
2
- * Direct WebSocket terminal server using node-pty.
3
- * Connects browser xterm.js directly to tmux sessions via WebSocket.
4
- *
5
- * This bypasses ttyd and gives us control over terminal initialization,
6
- * allowing us to implement the XDA (Extended Device Attributes) handler
7
- * that tmux requires for clipboard support.
2
+ * Direct WebSocket terminal server.
3
+ * Hosts the multiplexed /mux WebSocket endpoint for all terminal connections.
8
4
  */
9
5
  import { createServer } from "node:http";
10
- import { spawn } from "node:child_process";
11
- import { WebSocketServer, WebSocket } from "ws";
12
- import { homedir, userInfo } from "node:os";
13
- import { createCorrelationId } from "@aoagents/ao-core";
14
- let ptySpawn;
15
- /* eslint-enable @typescript-eslint/consistent-type-imports */
16
- try {
17
- const nodePty = await import("node-pty");
18
- ptySpawn = nodePty.spawn;
19
- }
20
- catch {
21
- console.warn("[DirectTerminal] node-pty not available — direct terminal will be disabled.");
22
- console.warn("[DirectTerminal] Install it with: npm install node-pty");
23
- }
24
- import { findTmux, resolveTmuxSession, validateSessionId } from "./tmux-utils.js";
25
- import { createObserverContext, inferProjectId } from "./terminal-observability.js";
6
+ import { findTmux } from "./tmux-utils.js";
7
+ import { createMuxWebSocket } from "./mux-websocket.js";
26
8
  /**
27
9
  * Create the direct terminal WebSocket server.
28
10
  * Separated from listen() so tests can control lifecycle.
29
11
  */
30
12
  export function createDirectTerminalServer(tmuxPath) {
31
13
  const TMUX = tmuxPath ?? findTmux();
32
- const activeSessions = new Map();
33
- const { config, observer } = createObserverContext("terminal-direct-websocket");
14
+ let muxWss = null;
34
15
  const metrics = {
35
- activeConnections: 0,
36
16
  totalConnections: 0,
37
17
  totalDisconnects: 0,
38
18
  totalErrors: 0,
39
- lastConnectedAt: null,
40
- lastDisconnectedAt: null,
41
- lastErrorAt: null,
42
- lastDisconnectReason: null,
43
- lastErrorReason: null,
44
19
  };
45
20
  const server = createServer((req, res) => {
46
21
  if (req.url === "/health") {
47
22
  res.writeHead(200, { "Content-Type": "application/json" });
48
23
  res.end(JSON.stringify({
49
- active: activeSessions.size,
50
- sessions: Array.from(activeSessions.keys()),
24
+ status: "ok",
25
+ clients: muxWss?.clients.size ?? 0,
51
26
  metrics,
52
27
  }));
53
28
  return;
@@ -55,221 +30,43 @@ export function createDirectTerminalServer(tmuxPath) {
55
30
  res.writeHead(404);
56
31
  res.end("Not found");
57
32
  });
58
- const wss = new WebSocketServer({
59
- server,
60
- path: "/ws",
61
- });
62
- const recordWebsocketMetric = (input) => {
63
- if (!observer) {
64
- return;
65
- }
66
- const correlationId = createCorrelationId("ws");
67
- observer.recordOperation({
68
- metric: input.metric,
69
- operation: `terminal.websocket.${input.metric}`,
70
- outcome: input.outcome,
71
- correlationId,
72
- projectId: input.sessionId ? inferProjectId(config, input.sessionId) : undefined,
73
- sessionId: input.sessionId,
74
- reason: input.reason,
75
- data: input.data,
76
- level: input.outcome === "failure" ? "error" : "info",
77
- });
78
- };
79
- wss.on("connection", (ws, req) => {
80
- if (!ptySpawn) {
81
- ws.close(1011, "Direct terminal unavailable — node-pty not installed");
82
- return;
83
- }
84
- const url = new URL(req.url ?? "/", "ws://localhost");
85
- const sessionId = url.searchParams.get("session");
86
- if (!sessionId) {
87
- console.error("[DirectTerminal] Missing session parameter");
88
- recordWebsocketMetric({
89
- metric: "websocket_error",
90
- outcome: "failure",
91
- reason: "Missing session parameter",
33
+ muxWss = createMuxWebSocket(TMUX);
34
+ if (muxWss) {
35
+ muxWss.on("connection", (ws) => {
36
+ metrics.totalConnections++;
37
+ ws.on("close", () => {
38
+ metrics.totalDisconnects++;
92
39
  });
93
- ws.close(1008, "Missing session parameter");
94
- return;
95
- }
96
- // Validate session ID format
97
- if (!validateSessionId(sessionId)) {
98
- console.error("[DirectTerminal] Invalid session ID:", sessionId);
99
- recordWebsocketMetric({
100
- metric: "websocket_error",
101
- outcome: "failure",
102
- sessionId,
103
- reason: "Invalid session ID",
104
- });
105
- ws.close(1008, "Invalid session ID");
106
- return;
107
- }
108
- // Resolve tmux session name: try exact match first, then suffix match
109
- // (hash-prefixed sessions like "8474d6f29887-ao-15" are accessed by user-facing ID "ao-15")
110
- const tmuxSessionId = resolveTmuxSession(sessionId, TMUX);
111
- if (!tmuxSessionId) {
112
- console.error("[DirectTerminal] tmux session not found:", sessionId);
113
- recordWebsocketMetric({
114
- metric: "websocket_error",
115
- outcome: "failure",
116
- sessionId,
117
- reason: "Session not found",
40
+ ws.on("error", () => {
41
+ metrics.totalErrors++;
118
42
  });
119
- ws.close(1008, "Session not found");
120
- return;
121
- }
122
- console.log(`[DirectTerminal] New connection for session: ${tmuxSessionId}`);
123
- // Enable mouse mode for scrollback support
124
- const mouseProc = spawn(TMUX, ["set-option", "-t", tmuxSessionId, "mouse", "on"]);
125
- mouseProc.on("error", (err) => {
126
- console.error(`[DirectTerminal] Failed to set mouse mode for ${tmuxSessionId}:`, err.message);
127
43
  });
128
- // Hide the green status bar for cleaner appearance
129
- const statusProc = spawn(TMUX, ["set-option", "-t", tmuxSessionId, "status", "off"]);
130
- statusProc.on("error", (err) => {
131
- console.error(`[DirectTerminal] Failed to hide status bar for ${tmuxSessionId}:`, err.message);
132
- });
133
- // Build complete environment - node-pty requires proper env setup
134
- const homeDir = process.env.HOME || homedir();
135
- const currentUser = process.env.USER || userInfo().username;
136
- const env = {
137
- HOME: homeDir,
138
- SHELL: process.env.SHELL || "/bin/bash",
139
- USER: currentUser,
140
- PATH: process.env.PATH || "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin",
141
- TERM: "xterm-256color",
142
- LANG: process.env.LANG || "en_US.UTF-8",
143
- TMPDIR: process.env.TMPDIR || "/tmp",
144
- };
145
- let pty;
146
- try {
147
- console.log(`[DirectTerminal] Spawning PTY: tmux attach-session -t ${tmuxSessionId}`);
148
- pty = ptySpawn(TMUX, ["attach-session", "-t", tmuxSessionId], {
149
- name: "xterm-256color",
150
- cols: 80,
151
- rows: 24,
152
- cwd: homeDir,
153
- env,
44
+ }
45
+ // Manual upgrade routing ws library doesn't support multiple WebSocketServer
46
+ // instances with different `path` options on the same HTTP server.
47
+ server.on("upgrade", (request, socket, head) => {
48
+ const pathname = new URL(request.url ?? "/", "ws://localhost").pathname;
49
+ if (pathname === "/mux" && muxWss) {
50
+ muxWss.handleUpgrade(request, socket, head, (ws) => {
51
+ muxWss.emit("connection", ws, request);
154
52
  });
155
- console.log(`[DirectTerminal] PTY spawned successfully`);
156
53
  }
157
- catch (err) {
158
- console.error(`[DirectTerminal] Failed to spawn PTY:`, err);
159
- recordWebsocketMetric({
160
- metric: "websocket_error",
161
- outcome: "failure",
162
- sessionId,
163
- reason: err instanceof Error ? err.message : String(err),
164
- });
165
- ws.close(1011, `Failed to spawn terminal: ${err instanceof Error ? err.message : String(err)}`);
166
- return;
54
+ else {
55
+ socket.destroy();
167
56
  }
168
- const session = { sessionId, pty, ws };
169
- activeSessions.set(sessionId, session);
170
- metrics.totalConnections += 1;
171
- metrics.activeConnections = activeSessions.size;
172
- metrics.lastConnectedAt = new Date().toISOString();
173
- recordWebsocketMetric({
174
- metric: "websocket_connect",
175
- outcome: "success",
176
- sessionId,
177
- data: { activeConnections: metrics.activeConnections },
178
- });
179
- let disconnectRecorded = false;
180
- const recordDisconnect = (outcome, reason) => {
181
- if (disconnectRecorded)
182
- return;
183
- disconnectRecorded = true;
184
- const activeConnections = activeSessions.size;
185
- metrics.activeConnections = activeConnections;
186
- metrics.totalDisconnects += 1;
187
- metrics.lastDisconnectedAt = new Date().toISOString();
188
- metrics.lastDisconnectReason = reason;
189
- recordWebsocketMetric({
190
- metric: "websocket_disconnect",
191
- outcome,
192
- sessionId,
193
- reason,
194
- data: { activeConnections },
195
- });
196
- };
197
- // PTY -> WebSocket
198
- pty.onData((data) => {
199
- if (ws.readyState === WebSocket.OPEN) {
200
- ws.send(data);
201
- }
202
- });
203
- // PTY exit
204
- pty.onExit(({ exitCode }) => {
205
- console.log(`[DirectTerminal] PTY exited for ${sessionId} with code ${exitCode}`);
206
- // Guard against stale exits: only delete if this pty is still the active one.
207
- // A new connection may have already replaced this session entry.
208
- if (activeSessions.get(sessionId)?.pty === pty) {
209
- activeSessions.delete(sessionId);
210
- }
211
- recordDisconnect(exitCode === 0 ? "success" : "failure", `pty_exit:${exitCode}`);
212
- if (ws.readyState === WebSocket.OPEN) {
213
- ws.close(1000, "Terminal session ended");
214
- }
215
- });
216
- // WebSocket -> PTY
217
- ws.on("message", (data) => {
218
- const message = data.toString("utf8");
219
- // Handle resize messages (sent by xterm.js FitAddon)
220
- if (message.startsWith("{")) {
221
- try {
222
- const parsed = JSON.parse(message);
223
- if (parsed.type === "resize" && parsed.cols && parsed.rows) {
224
- pty.resize(parsed.cols, parsed.rows);
225
- return;
226
- }
227
- }
228
- catch {
229
- // Not JSON, treat as terminal input
230
- }
231
- }
232
- // Normal terminal input
233
- pty.write(message);
234
- });
235
- // WebSocket close
236
- ws.on("close", () => {
237
- console.log(`[DirectTerminal] WebSocket closed for ${sessionId}`);
238
- // Guard against stale closes replacing a newer session's entry
239
- if (activeSessions.get(sessionId)?.pty === pty) {
240
- activeSessions.delete(sessionId);
241
- }
242
- recordDisconnect("success", "ws_close");
243
- pty.kill();
244
- });
245
- // WebSocket error
246
- ws.on("error", (err) => {
247
- console.error(`[DirectTerminal] WebSocket error for ${sessionId}:`, err.message);
248
- // Guard against stale error handlers replacing a newer session's entry
249
- if (activeSessions.get(sessionId)?.pty === pty) {
250
- activeSessions.delete(sessionId);
251
- }
252
- recordDisconnect("failure", `ws_error:${err.message}`);
253
- metrics.totalErrors += 1;
254
- metrics.lastErrorAt = new Date().toISOString();
255
- metrics.lastErrorReason = err.message;
256
- recordWebsocketMetric({
257
- metric: "websocket_error",
258
- outcome: "failure",
259
- sessionId,
260
- reason: err.message,
261
- });
262
- pty.kill();
263
- });
264
57
  });
265
58
  function shutdown() {
266
- for (const [, session] of activeSessions) {
267
- session.pty.kill();
268
- session.ws.close(1001, "Server shutting down");
59
+ // Terminate all connected mux clients — this triggers their 'close' events
60
+ // which unsubscribe terminal callbacks and kill PTY processes.
61
+ if (muxWss) {
62
+ for (const client of muxWss.clients) {
63
+ client.terminate();
64
+ }
65
+ muxWss.close();
269
66
  }
270
67
  server.close();
271
68
  }
272
- return { server, wss, activeSessions, shutdown };
69
+ return { server, shutdown };
273
70
  }
274
71
  // --- Run as standalone script ---
275
72
  // Only start the server when executed directly (not imported by tests)