@juspay/shooter 1.18.0 → 1.20.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 (367) hide show
  1. package/build/client/_app/immutable/assets/{0.NV8k8wxG.css → 0.BwNtE8TX.css} +1 -1
  2. package/build/client/_app/immutable/assets/0.BwNtE8TX.css.br +0 -0
  3. package/build/client/_app/immutable/assets/{0.NV8k8wxG.css.gz → 0.BwNtE8TX.css.gz} +0 -0
  4. package/build/client/_app/immutable/assets/11.F10lvwyh.css +1 -0
  5. package/build/client/_app/immutable/assets/11.F10lvwyh.css.br +0 -0
  6. package/build/client/_app/immutable/assets/11.F10lvwyh.css.gz +0 -0
  7. package/build/client/_app/immutable/assets/8.BYgAX7hR.css +1 -0
  8. package/build/client/_app/immutable/assets/8.BYgAX7hR.css.br +0 -0
  9. package/build/client/_app/immutable/assets/8.BYgAX7hR.css.gz +0 -0
  10. package/build/client/_app/immutable/assets/9.DV6pZunn.css +1 -0
  11. package/build/client/_app/immutable/assets/9.DV6pZunn.css.br +0 -0
  12. package/build/client/_app/immutable/assets/9.DV6pZunn.css.gz +0 -0
  13. package/build/client/_app/immutable/chunks/{DZQMsHM5.js → 2rBV5OkJ.js} +1 -1
  14. package/build/client/_app/immutable/chunks/2rBV5OkJ.js.br +0 -0
  15. package/build/client/_app/immutable/chunks/2rBV5OkJ.js.gz +0 -0
  16. package/build/client/_app/immutable/chunks/{B9WQy_3X.js → BB2l8o4X.js} +1 -1
  17. package/build/client/_app/immutable/chunks/BB2l8o4X.js.br +0 -0
  18. package/build/client/_app/immutable/chunks/BB2l8o4X.js.gz +0 -0
  19. package/build/client/_app/immutable/chunks/{Cg3dlX05.js → BPDiEZo0.js} +2 -2
  20. package/build/client/_app/immutable/chunks/BPDiEZo0.js.br +0 -0
  21. package/build/client/_app/immutable/chunks/BPDiEZo0.js.gz +0 -0
  22. package/build/client/_app/immutable/chunks/{C_9BZILB.js → BcpydfqI.js} +1 -1
  23. package/build/client/_app/immutable/chunks/BcpydfqI.js.br +0 -0
  24. package/build/client/_app/immutable/chunks/BcpydfqI.js.gz +0 -0
  25. package/build/client/_app/immutable/chunks/{BRqaaL5D.js → BvmdJful.js} +1 -1
  26. package/build/client/_app/immutable/chunks/BvmdJful.js.br +0 -0
  27. package/build/client/_app/immutable/chunks/BvmdJful.js.gz +0 -0
  28. package/build/client/_app/immutable/chunks/C_YNQL8b.js +3 -0
  29. package/build/client/_app/immutable/chunks/C_YNQL8b.js.br +0 -0
  30. package/build/client/_app/immutable/chunks/C_YNQL8b.js.gz +0 -0
  31. package/build/client/_app/immutable/chunks/{C5VOyQCG.js → ClIPTXf3.js} +1 -1
  32. package/build/client/_app/immutable/chunks/ClIPTXf3.js.br +0 -0
  33. package/build/client/_app/immutable/chunks/ClIPTXf3.js.gz +0 -0
  34. package/build/client/_app/immutable/chunks/{8lO1IL7u.js → DIZ3Qst5.js} +1 -1
  35. package/build/client/_app/immutable/chunks/DIZ3Qst5.js.br +0 -0
  36. package/build/client/_app/immutable/chunks/{8lO1IL7u.js.gz → DIZ3Qst5.js.gz} +0 -0
  37. package/build/client/_app/immutable/chunks/{DJvX78LW.js → DT4H19pV.js} +1 -1
  38. package/build/client/_app/immutable/chunks/DT4H19pV.js.br +0 -0
  39. package/build/client/_app/immutable/chunks/DT4H19pV.js.gz +0 -0
  40. package/build/client/_app/immutable/chunks/{DYuMZGL5.js → DWmC0QM7.js} +1 -1
  41. package/build/client/_app/immutable/chunks/DWmC0QM7.js.br +0 -0
  42. package/build/client/_app/immutable/chunks/DWmC0QM7.js.gz +0 -0
  43. package/build/client/_app/immutable/chunks/J5-Cr5oR.js +6 -0
  44. package/build/client/_app/immutable/chunks/J5-Cr5oR.js.br +0 -0
  45. package/build/client/_app/immutable/chunks/J5-Cr5oR.js.gz +0 -0
  46. package/build/client/_app/immutable/chunks/{DZvnhU_8.js → ZS5XYDx_.js} +2 -2
  47. package/build/client/_app/immutable/chunks/ZS5XYDx_.js.br +0 -0
  48. package/build/client/_app/immutable/chunks/ZS5XYDx_.js.gz +0 -0
  49. package/build/client/_app/immutable/entry/app.Bd-DfeJi.js +2 -0
  50. package/build/client/_app/immutable/entry/app.Bd-DfeJi.js.br +0 -0
  51. package/build/client/_app/immutable/entry/app.Bd-DfeJi.js.gz +0 -0
  52. package/build/client/_app/immutable/entry/start.evvp4tX7.js +1 -0
  53. package/build/client/_app/immutable/entry/start.evvp4tX7.js.br +2 -0
  54. package/build/client/_app/immutable/entry/start.evvp4tX7.js.gz +0 -0
  55. package/build/client/_app/immutable/nodes/0.Bl-1LQWM.js +10 -0
  56. package/build/client/_app/immutable/nodes/0.Bl-1LQWM.js.br +0 -0
  57. package/build/client/_app/immutable/nodes/0.Bl-1LQWM.js.gz +0 -0
  58. package/build/client/_app/immutable/nodes/{1.C4eFlqSB.js → 1.DT4dq6Ay.js} +1 -1
  59. package/build/client/_app/immutable/nodes/1.DT4dq6Ay.js.br +0 -0
  60. package/build/client/_app/immutable/nodes/1.DT4dq6Ay.js.gz +0 -0
  61. package/build/client/_app/immutable/nodes/{8.Bs362gyb.js → 10.CF7RGXpe.js} +2 -2
  62. package/build/client/_app/immutable/nodes/10.CF7RGXpe.js.br +0 -0
  63. package/build/client/_app/immutable/nodes/10.CF7RGXpe.js.gz +0 -0
  64. package/build/client/_app/immutable/nodes/11.BV_G7yLI.js +2 -0
  65. package/build/client/_app/immutable/nodes/11.BV_G7yLI.js.br +0 -0
  66. package/build/client/_app/immutable/nodes/11.BV_G7yLI.js.gz +0 -0
  67. package/build/client/_app/immutable/nodes/{2.CdC092Za.js → 2.DcRhsjYp.js} +2 -2
  68. package/build/client/_app/immutable/nodes/2.DcRhsjYp.js.br +0 -0
  69. package/build/client/_app/immutable/nodes/2.DcRhsjYp.js.gz +0 -0
  70. package/build/client/_app/immutable/nodes/{3.Dhf4ZWW0.js → 3.0MMe3oxR.js} +3 -3
  71. package/build/client/_app/immutable/nodes/3.0MMe3oxR.js.br +0 -0
  72. package/build/client/_app/immutable/nodes/3.0MMe3oxR.js.gz +0 -0
  73. package/build/client/_app/immutable/nodes/{4.BSVqdrrD.js → 4.CBX9A3ka.js} +2 -2
  74. package/build/client/_app/immutable/nodes/4.CBX9A3ka.js.br +0 -0
  75. package/build/client/_app/immutable/nodes/4.CBX9A3ka.js.gz +0 -0
  76. package/build/client/_app/immutable/nodes/{5.Cfj35gpY.js → 5.DIVKuZc9.js} +1 -1
  77. package/build/client/_app/immutable/nodes/5.DIVKuZc9.js.br +0 -0
  78. package/build/client/_app/immutable/nodes/5.DIVKuZc9.js.gz +0 -0
  79. package/build/client/_app/immutable/nodes/{6.B3SEB_li.js → 6.ComiWlV6.js} +1 -1
  80. package/build/client/_app/immutable/nodes/6.ComiWlV6.js.br +0 -0
  81. package/build/client/_app/immutable/nodes/6.ComiWlV6.js.gz +0 -0
  82. package/build/client/_app/immutable/nodes/{7.DV8cJ1lX.js → 7.vkPx1kVP.js} +1 -1
  83. package/build/client/_app/immutable/nodes/7.vkPx1kVP.js.br +0 -0
  84. package/build/client/_app/immutable/nodes/7.vkPx1kVP.js.gz +0 -0
  85. package/build/client/_app/immutable/nodes/8.Bmr3sWbS.js +1 -0
  86. package/build/client/_app/immutable/nodes/8.Bmr3sWbS.js.br +0 -0
  87. package/build/client/_app/immutable/nodes/8.Bmr3sWbS.js.gz +0 -0
  88. package/build/client/_app/immutable/nodes/9.CAJucyeI.js +2 -0
  89. package/build/client/_app/immutable/nodes/9.CAJucyeI.js.br +0 -0
  90. package/build/client/_app/immutable/nodes/9.CAJucyeI.js.gz +0 -0
  91. package/build/client/_app/version.json +1 -1
  92. package/build/client/_app/version.json.br +0 -0
  93. package/build/client/_app/version.json.gz +0 -0
  94. package/build/server/chunks/{0-Cd7jY0a7.js → 0-DDGB6CRT.js} +4 -4
  95. package/build/server/chunks/{0-Cd7jY0a7.js.map → 0-DDGB6CRT.js.map} +1 -1
  96. package/build/server/chunks/1-DEjonQXD.js +9 -0
  97. package/build/server/chunks/{1-C4BOGoJY.js.map → 1-DEjonQXD.js.map} +1 -1
  98. package/build/server/chunks/10-BK1kiiiw.js +9 -0
  99. package/build/server/chunks/10-BK1kiiiw.js.map +1 -0
  100. package/build/server/chunks/11-CJPjkEF3.js +9 -0
  101. package/build/server/chunks/11-CJPjkEF3.js.map +1 -0
  102. package/build/server/chunks/{2-Ba0mNwJ6.js → 2-RLnhlWh5.js} +3 -3
  103. package/build/server/chunks/{2-Ba0mNwJ6.js.map → 2-RLnhlWh5.js.map} +1 -1
  104. package/build/server/chunks/{3-Pg8t1uJU.js → 3-Dd4pJBqZ.js} +3 -3
  105. package/build/server/chunks/{3-Pg8t1uJU.js.map → 3-Dd4pJBqZ.js.map} +1 -1
  106. package/build/server/chunks/{4-BtYdKCVW.js → 4-Bb5VFhsO.js} +3 -3
  107. package/build/server/chunks/{4-BtYdKCVW.js.map → 4-Bb5VFhsO.js.map} +1 -1
  108. package/build/server/chunks/{5-CvJK3PiH.js → 5-oNoWuIsn.js} +3 -3
  109. package/build/server/chunks/{5-CvJK3PiH.js.map → 5-oNoWuIsn.js.map} +1 -1
  110. package/build/server/chunks/6-DdRMnKNa.js +9 -0
  111. package/build/server/chunks/{6-D8xbnTSo.js.map → 6-DdRMnKNa.js.map} +1 -1
  112. package/build/server/chunks/7-vLOMMetm.js +9 -0
  113. package/build/server/chunks/{7-CkVK06S0.js.map → 7-vLOMMetm.js.map} +1 -1
  114. package/build/server/chunks/8-rJyiQLFs.js +9 -0
  115. package/build/server/chunks/8-rJyiQLFs.js.map +1 -0
  116. package/build/server/chunks/9-CVSNNYED.js +9 -0
  117. package/build/server/chunks/9-CVSNNYED.js.map +1 -0
  118. package/build/server/chunks/Banner-BgaAs1rs.js +90 -0
  119. package/build/server/chunks/Banner-BgaAs1rs.js.map +1 -0
  120. package/build/server/chunks/{Button-B5dU-ntz.js → Button-D0hZ7JYt.js} +2 -2
  121. package/build/server/chunks/Button-D0hZ7JYt.js.map +1 -0
  122. package/build/server/chunks/{Icon-C7Ml3GX6.js → Icon-D0GBnDcs.js} +3 -3
  123. package/build/server/chunks/Icon-D0GBnDcs.js.map +1 -0
  124. package/build/server/chunks/{Input-CPGO0sbS.js → Input-OmIiydSx.js} +2 -2
  125. package/build/server/chunks/Input-OmIiydSx.js.map +1 -0
  126. package/build/server/chunks/{Pill-CcrtCejm.js → Pill-4xJ-VhAA.js} +3 -3
  127. package/build/server/chunks/Pill-4xJ-VhAA.js.map +1 -0
  128. package/build/server/chunks/{Shimmer-C5jkvGr1.js → Shimmer-Dw2uvTC1.js} +2 -2
  129. package/build/server/chunks/Shimmer-Dw2uvTC1.js.map +1 -0
  130. package/build/server/chunks/{_error.svelte-CSIxs-ab.js → _error.svelte-CZnkxeLr.js} +8 -8
  131. package/build/server/chunks/_error.svelte-CZnkxeLr.js.map +1 -0
  132. package/build/server/chunks/{_layout.svelte-noB4j-v2.js → _layout.svelte-DfgNGGiM.js} +16 -11
  133. package/build/server/chunks/_layout.svelte-DfgNGGiM.js.map +1 -0
  134. package/build/server/chunks/{_page.svelte-DnTpPnPR.js → _page.svelte-BLo2v_8E.js} +7 -88
  135. package/build/server/chunks/_page.svelte-BLo2v_8E.js.map +1 -0
  136. package/build/server/chunks/_page.svelte-BTlfUsBp.js +43 -0
  137. package/build/server/chunks/_page.svelte-BTlfUsBp.js.map +1 -0
  138. package/build/server/chunks/{_page.svelte-BV0XyYJZ.js → _page.svelte-BX2FMgSg.js} +4 -4
  139. package/build/server/chunks/_page.svelte-BX2FMgSg.js.map +1 -0
  140. package/build/server/chunks/{_page.svelte-BUkm2304.js → _page.svelte-C7B0qdrC.js} +5 -5
  141. package/build/server/chunks/_page.svelte-C7B0qdrC.js.map +1 -0
  142. package/build/server/chunks/{_page.svelte-Dmg-RFCg.js → _page.svelte-CE7COWnF.js} +7 -7
  143. package/build/server/chunks/_page.svelte-CE7COWnF.js.map +1 -0
  144. package/build/server/chunks/{_page.svelte-BfB8maoc.js → _page.svelte-CWsjjd4l.js} +9 -9
  145. package/build/server/chunks/_page.svelte-CWsjjd4l.js.map +1 -0
  146. package/build/server/chunks/_page.svelte-D5S2hkBk.js +104 -0
  147. package/build/server/chunks/_page.svelte-D5S2hkBk.js.map +1 -0
  148. package/build/server/chunks/{_page.svelte-B6qyh-K-.js → _page.svelte-D_Ey8QRG.js} +11 -11
  149. package/build/server/chunks/_page.svelte-D_Ey8QRG.js.map +1 -0
  150. package/build/server/chunks/{_page.svelte-C60lAagP.js → _page.svelte-dabsQl9c.js} +210 -9
  151. package/build/server/chunks/_page.svelte-dabsQl9c.js.map +1 -0
  152. package/build/server/chunks/{_page.svelte-DuzZr5dA.js → _page.svelte-tBuIq8Pg.js} +11 -11
  153. package/build/server/chunks/_page.svelte-tBuIq8Pg.js.map +1 -0
  154. package/build/server/chunks/{_server.ts-CyjDrcZN.js → _server.ts-AnBXfZXh.js} +10 -2
  155. package/build/server/chunks/_server.ts-AnBXfZXh.js.map +1 -0
  156. package/build/server/chunks/_server.ts-B-evHL2q.js +13 -0
  157. package/build/server/chunks/_server.ts-B-evHL2q.js.map +1 -0
  158. package/build/server/chunks/_server.ts-B2wIgsW4.js +95 -0
  159. package/build/server/chunks/_server.ts-B2wIgsW4.js.map +1 -0
  160. package/build/server/chunks/_server.ts-BaaY7Z9D.js +77 -0
  161. package/build/server/chunks/_server.ts-BaaY7Z9D.js.map +1 -0
  162. package/build/server/chunks/_server.ts-C0317RBD.js +57 -0
  163. package/build/server/chunks/_server.ts-C0317RBD.js.map +1 -0
  164. package/build/server/chunks/{_server.ts-Bu3s5hfv.js → _server.ts-CJGyN8mw.js} +18 -10
  165. package/build/server/chunks/_server.ts-CJGyN8mw.js.map +1 -0
  166. package/build/server/chunks/_server.ts-CVPZOpiv.js +23 -0
  167. package/build/server/chunks/_server.ts-CVPZOpiv.js.map +1 -0
  168. package/build/server/chunks/{_server.ts-DZgfQKiH.js → _server.ts-D9ir7u24.js} +2 -2
  169. package/build/server/chunks/{_server.ts-DZgfQKiH.js.map → _server.ts-D9ir7u24.js.map} +1 -1
  170. package/build/server/chunks/{_server.ts-DZP2lhaY.js → _server.ts-DEx9-epI.js} +20 -8
  171. package/build/server/chunks/_server.ts-DEx9-epI.js.map +1 -0
  172. package/build/server/chunks/{_server.ts-BA_uWcPw.js → _server.ts-DKNIsQeH.js} +6 -4
  173. package/build/server/chunks/_server.ts-DKNIsQeH.js.map +1 -0
  174. package/build/server/chunks/_server.ts-DkZX_O9a.js +39 -0
  175. package/build/server/chunks/_server.ts-DkZX_O9a.js.map +1 -0
  176. package/build/server/chunks/_server.ts-DpRr0Tfh.js +68 -0
  177. package/build/server/chunks/_server.ts-DpRr0Tfh.js.map +1 -0
  178. package/build/server/chunks/{_server.ts-CwAjt91u.js → _server.ts-Dz9Jd9Jh.js} +6 -4
  179. package/build/server/chunks/{_server.ts-CwAjt91u.js.map → _server.ts-Dz9Jd9Jh.js.map} +1 -1
  180. package/build/server/chunks/_server.ts-Mttr0-Sl.js +48 -0
  181. package/build/server/chunks/_server.ts-Mttr0-Sl.js.map +1 -0
  182. package/build/server/chunks/{_server.ts-Bjbr7glm.js → _server.ts-QN-Bo5ql.js} +12 -5
  183. package/build/server/chunks/_server.ts-QN-Bo5ql.js.map +1 -0
  184. package/build/server/chunks/{_server.ts-BrqaMMAa.js → _server.ts-W6i3EnGX.js} +29 -6
  185. package/build/server/chunks/_server.ts-W6i3EnGX.js.map +1 -0
  186. package/build/server/chunks/{_server.ts-DZ5naqSL.js → _server.ts-bk_EeAdY.js} +6 -2
  187. package/build/server/chunks/_server.ts-bk_EeAdY.js.map +1 -0
  188. package/build/server/chunks/_server.ts-jtqWDWcf.js +45 -0
  189. package/build/server/chunks/_server.ts-jtqWDWcf.js.map +1 -0
  190. package/build/server/chunks/{cache-Me3zUAaD.js → cache-BlMaDsHi.js} +2 -2
  191. package/build/server/chunks/cache-BlMaDsHi.js.map +1 -0
  192. package/build/server/chunks/{client-CfNnl32g.js → client-Ds1brw-8.js} +4 -4
  193. package/build/server/chunks/{client-CfNnl32g.js.map → client-Ds1brw-8.js.map} +1 -1
  194. package/build/server/chunks/client2-DngLdcUc.js +7 -0
  195. package/build/server/chunks/{client2-DDP30_vY.js.map → client2-DngLdcUc.js.map} +1 -1
  196. package/build/server/chunks/coordinator-DMU_ADXf.js +530 -0
  197. package/build/server/chunks/coordinator-DMU_ADXf.js.map +1 -0
  198. package/build/server/chunks/guest-registry-t0-7Zv5q.js +39 -0
  199. package/build/server/chunks/guest-registry-t0-7Zv5q.js.map +1 -0
  200. package/build/server/chunks/{index-CJrGuxuM.js → index-CoYB03g7.js} +2 -2
  201. package/build/server/chunks/{index-CJrGuxuM.js.map → index-CoYB03g7.js.map} +1 -1
  202. package/build/server/chunks/{index-server--49oHtA0.js → index-server-Bq3cnK69.js} +2 -2
  203. package/build/server/chunks/{index-server--49oHtA0.js.map → index-server-Bq3cnK69.js.map} +1 -1
  204. package/build/server/chunks/{index2-MY7PXeAc.js → index2-dSGQ9Eaa.js} +2 -2
  205. package/build/server/chunks/{index2-MY7PXeAc.js.map → index2-dSGQ9Eaa.js.map} +1 -1
  206. package/build/server/chunks/{pty-manager-DmNSCKAr.js → pty-manager-CkZNoW1t.js} +7 -2
  207. package/build/server/chunks/pty-manager-CkZNoW1t.js.map +1 -0
  208. package/build/server/chunks/qwen-reader-DGfUbKaJ.js.map +1 -1
  209. package/build/server/chunks/{registry-Kcw2UCMv.js → registry-D4J_CuzW.js} +2 -2
  210. package/build/server/chunks/registry-D4J_CuzW.js.map +1 -0
  211. package/build/server/chunks/{root-xvQIR1Bt.js → root-D4IoFC8F.js} +2 -2
  212. package/build/server/chunks/root-D4IoFC8F.js.map +1 -0
  213. package/build/server/chunks/share-auth-BS7JuiHf.js +27 -0
  214. package/build/server/chunks/share-auth-BS7JuiHf.js.map +1 -0
  215. package/build/server/chunks/share-store-B9jMpVg0.js +127 -0
  216. package/build/server/chunks/share-store-B9jMpVg0.js.map +1 -0
  217. package/build/server/chunks/{state.svelte-RCtlkrNH.js → state.svelte-CmHqngc_.js} +3 -3
  218. package/build/server/chunks/{state.svelte-RCtlkrNH.js.map → state.svelte-CmHqngc_.js.map} +1 -1
  219. package/build/server/chunks/{stores-C-LqoonT.js → stores-CRYxfF0o.js} +4 -4
  220. package/build/server/chunks/stores-CRYxfF0o.js.map +1 -0
  221. package/build/server/chunks/super-session-handler-DPyxFgmz.js +22 -0
  222. package/build/server/chunks/super-session-handler-DPyxFgmz.js.map +1 -0
  223. package/build/server/index.js +4 -4
  224. package/build/server/index.js.map +1 -1
  225. package/build/server/manifest.js +101 -22
  226. package/build/server/manifest.js.map +1 -1
  227. package/package.json +2 -2
  228. package/scripts/e2e-all-features.sh +41 -2
  229. package/server.ts +33 -3
  230. package/src/lib/modules/client/terminal/ShareGate.svelte +96 -0
  231. package/src/lib/modules/client/terminal/ShareSheet.svelte +395 -0
  232. package/src/lib/modules/client/terminal/xterm-wrapper.ts +19 -2
  233. package/src/lib/modules/server/sos/coordinator.ts +492 -0
  234. package/src/lib/modules/server/sos/policy-gate.ts +56 -0
  235. package/src/lib/modules/server/sos/relay-store.ts +159 -0
  236. package/src/lib/modules/server/terminal/pty-input.ts +37 -0
  237. package/src/lib/modules/server/terminal/pty-manager.ts +6 -0
  238. package/src/lib/modules/server/terminal/share-auth.ts +37 -0
  239. package/src/lib/modules/server/terminal/share-store.ts +172 -0
  240. package/src/lib/modules/server/ws/guest-registry.ts +49 -0
  241. package/src/lib/modules/server/ws/server.ts +28 -4
  242. package/src/lib/modules/server/ws/session-handler.ts +24 -5
  243. package/src/lib/modules/server/ws/super-session-handler.ts +200 -0
  244. package/src/lib/modules/server/ws/terminal-handler.ts +21 -2
  245. package/src/lib/modules/server/ws/ticket-store.ts +18 -10
  246. package/src/lib/types/generated/Client.ts +25 -1
  247. package/src/lib/types/generated/Share.ts +404 -0
  248. package/src/lib/types/generated/WsProtocol.ts +73 -2
  249. package/src/lib/types/generated/index.ts +1 -0
  250. package/src/lib/types/index.ts +2 -1
  251. package/src/lib/types/sos.ts +134 -0
  252. package/src/lib/types/terminal-client.ts +19 -2
  253. package/src/lib/types/ws.ts +1 -0
  254. package/src/routes/+layout.svelte +9 -2
  255. package/src/routes/api/sos/+server.ts +36 -0
  256. package/src/routes/api/sos/[id]/+server.ts +55 -0
  257. package/src/routes/api/sos/[id]/inject/+server.ts +44 -0
  258. package/src/routes/api/sos/[id]/members/+server.ts +47 -0
  259. package/src/routes/api/sos/[id]/members/[mid]/+server.ts +17 -0
  260. package/src/routes/api/sos/[id]/rules/+server.ts +85 -0
  261. package/src/routes/api/terminals/[id]/+server.ts +14 -3
  262. package/src/routes/api/terminals/[id]/paste-image/+server.ts +8 -4
  263. package/src/routes/api/terminals/[id]/resize/+server.ts +8 -4
  264. package/src/routes/api/terminals/[id]/share/+server.ts +98 -0
  265. package/src/routes/api/terminals/[id]/share/auth/+server.ts +81 -0
  266. package/src/routes/api/terminals/[id]/share/status/+server.ts +11 -0
  267. package/src/routes/api/ws-ticket/+server.ts +26 -5
  268. package/src/routes/sos/+page.svelte +195 -0
  269. package/src/routes/sos/[id]/+page.svelte +677 -0
  270. package/src/routes/terminals/[id]/+page.svelte +184 -43
  271. package/build/client/_app/immutable/assets/0.NV8k8wxG.css.br +0 -0
  272. package/build/client/_app/immutable/assets/9.v5KA95xm.css +0 -1
  273. package/build/client/_app/immutable/assets/9.v5KA95xm.css.br +0 -0
  274. package/build/client/_app/immutable/assets/9.v5KA95xm.css.gz +0 -0
  275. package/build/client/_app/immutable/chunks/8lO1IL7u.js.br +0 -0
  276. package/build/client/_app/immutable/chunks/B9WQy_3X.js.br +0 -0
  277. package/build/client/_app/immutable/chunks/B9WQy_3X.js.gz +0 -0
  278. package/build/client/_app/immutable/chunks/BRqaaL5D.js.br +0 -0
  279. package/build/client/_app/immutable/chunks/BRqaaL5D.js.gz +0 -0
  280. package/build/client/_app/immutable/chunks/C5VOyQCG.js.br +0 -0
  281. package/build/client/_app/immutable/chunks/C5VOyQCG.js.gz +0 -0
  282. package/build/client/_app/immutable/chunks/CR6bkGJW.js +0 -6
  283. package/build/client/_app/immutable/chunks/CR6bkGJW.js.br +0 -0
  284. package/build/client/_app/immutable/chunks/CR6bkGJW.js.gz +0 -0
  285. package/build/client/_app/immutable/chunks/C_9BZILB.js.br +0 -0
  286. package/build/client/_app/immutable/chunks/C_9BZILB.js.gz +0 -0
  287. package/build/client/_app/immutable/chunks/Cg3dlX05.js.br +0 -0
  288. package/build/client/_app/immutable/chunks/Cg3dlX05.js.gz +0 -0
  289. package/build/client/_app/immutable/chunks/DJvX78LW.js.br +0 -0
  290. package/build/client/_app/immutable/chunks/DJvX78LW.js.gz +0 -0
  291. package/build/client/_app/immutable/chunks/DYuMZGL5.js.br +0 -0
  292. package/build/client/_app/immutable/chunks/DYuMZGL5.js.gz +0 -0
  293. package/build/client/_app/immutable/chunks/DZQMsHM5.js.br +0 -0
  294. package/build/client/_app/immutable/chunks/DZQMsHM5.js.gz +0 -0
  295. package/build/client/_app/immutable/chunks/DZvnhU_8.js.br +0 -0
  296. package/build/client/_app/immutable/chunks/DZvnhU_8.js.gz +0 -0
  297. package/build/client/_app/immutable/chunks/nWG9RHyB.js +0 -3
  298. package/build/client/_app/immutable/chunks/nWG9RHyB.js.br +0 -0
  299. package/build/client/_app/immutable/chunks/nWG9RHyB.js.gz +0 -0
  300. package/build/client/_app/immutable/entry/app.f46Ko1hu.js +0 -2
  301. package/build/client/_app/immutable/entry/app.f46Ko1hu.js.br +0 -0
  302. package/build/client/_app/immutable/entry/app.f46Ko1hu.js.gz +0 -0
  303. package/build/client/_app/immutable/entry/start.BVDjNnXt.js +0 -1
  304. package/build/client/_app/immutable/entry/start.BVDjNnXt.js.br +0 -2
  305. package/build/client/_app/immutable/entry/start.BVDjNnXt.js.gz +0 -0
  306. package/build/client/_app/immutable/nodes/0.D_9EwVmq.js +0 -7
  307. package/build/client/_app/immutable/nodes/0.D_9EwVmq.js.br +0 -0
  308. package/build/client/_app/immutable/nodes/0.D_9EwVmq.js.gz +0 -0
  309. package/build/client/_app/immutable/nodes/1.C4eFlqSB.js.br +0 -0
  310. package/build/client/_app/immutable/nodes/1.C4eFlqSB.js.gz +0 -0
  311. package/build/client/_app/immutable/nodes/2.CdC092Za.js.br +0 -0
  312. package/build/client/_app/immutable/nodes/2.CdC092Za.js.gz +0 -0
  313. package/build/client/_app/immutable/nodes/3.Dhf4ZWW0.js.br +0 -0
  314. package/build/client/_app/immutable/nodes/3.Dhf4ZWW0.js.gz +0 -0
  315. package/build/client/_app/immutable/nodes/4.BSVqdrrD.js.br +0 -0
  316. package/build/client/_app/immutable/nodes/4.BSVqdrrD.js.gz +0 -0
  317. package/build/client/_app/immutable/nodes/5.Cfj35gpY.js.br +0 -0
  318. package/build/client/_app/immutable/nodes/5.Cfj35gpY.js.gz +0 -0
  319. package/build/client/_app/immutable/nodes/6.B3SEB_li.js.br +0 -0
  320. package/build/client/_app/immutable/nodes/6.B3SEB_li.js.gz +0 -0
  321. package/build/client/_app/immutable/nodes/7.DV8cJ1lX.js.br +0 -0
  322. package/build/client/_app/immutable/nodes/7.DV8cJ1lX.js.gz +0 -0
  323. package/build/client/_app/immutable/nodes/8.Bs362gyb.js.br +0 -0
  324. package/build/client/_app/immutable/nodes/8.Bs362gyb.js.gz +0 -0
  325. package/build/client/_app/immutable/nodes/9.Cf7_3uqT.js +0 -2
  326. package/build/client/_app/immutable/nodes/9.Cf7_3uqT.js.br +0 -0
  327. package/build/client/_app/immutable/nodes/9.Cf7_3uqT.js.gz +0 -0
  328. package/build/server/chunks/1-C4BOGoJY.js +0 -9
  329. package/build/server/chunks/6-D8xbnTSo.js +0 -9
  330. package/build/server/chunks/7-CkVK06S0.js +0 -9
  331. package/build/server/chunks/8-C8qVhrds.js +0 -9
  332. package/build/server/chunks/8-C8qVhrds.js.map +0 -1
  333. package/build/server/chunks/9-fL5zqN0T.js +0 -9
  334. package/build/server/chunks/9-fL5zqN0T.js.map +0 -1
  335. package/build/server/chunks/Button-B5dU-ntz.js.map +0 -1
  336. package/build/server/chunks/Icon-C7Ml3GX6.js.map +0 -1
  337. package/build/server/chunks/Input-CPGO0sbS.js.map +0 -1
  338. package/build/server/chunks/Pill-CcrtCejm.js.map +0 -1
  339. package/build/server/chunks/Shimmer-C5jkvGr1.js.map +0 -1
  340. package/build/server/chunks/_error.svelte-CSIxs-ab.js.map +0 -1
  341. package/build/server/chunks/_layout.svelte-noB4j-v2.js.map +0 -1
  342. package/build/server/chunks/_page.svelte-B6qyh-K-.js.map +0 -1
  343. package/build/server/chunks/_page.svelte-BUkm2304.js.map +0 -1
  344. package/build/server/chunks/_page.svelte-BV0XyYJZ.js.map +0 -1
  345. package/build/server/chunks/_page.svelte-BfB8maoc.js.map +0 -1
  346. package/build/server/chunks/_page.svelte-C60lAagP.js.map +0 -1
  347. package/build/server/chunks/_page.svelte-Dmg-RFCg.js.map +0 -1
  348. package/build/server/chunks/_page.svelte-DnTpPnPR.js.map +0 -1
  349. package/build/server/chunks/_page.svelte-DuzZr5dA.js.map +0 -1
  350. package/build/server/chunks/_server.ts-BA_uWcPw.js.map +0 -1
  351. package/build/server/chunks/_server.ts-Bjbr7glm.js.map +0 -1
  352. package/build/server/chunks/_server.ts-BrqaMMAa.js.map +0 -1
  353. package/build/server/chunks/_server.ts-Bu3s5hfv.js.map +0 -1
  354. package/build/server/chunks/_server.ts-CyjDrcZN.js.map +0 -1
  355. package/build/server/chunks/_server.ts-DZ5naqSL.js.map +0 -1
  356. package/build/server/chunks/_server.ts-DZP2lhaY.js.map +0 -1
  357. package/build/server/chunks/cache-Me3zUAaD.js.map +0 -1
  358. package/build/server/chunks/client2-DDP30_vY.js +0 -7
  359. package/build/server/chunks/events-handler-Dm1mNPQP.js +0 -20
  360. package/build/server/chunks/events-handler-Dm1mNPQP.js.map +0 -1
  361. package/build/server/chunks/pty-manager-DmNSCKAr.js.map +0 -1
  362. package/build/server/chunks/registry-Kcw2UCMv.js.map +0 -1
  363. package/build/server/chunks/root-xvQIR1Bt.js.map +0 -1
  364. package/build/server/chunks/stores-C-LqoonT.js.map +0 -1
  365. /package/build/client/_app/immutable/assets/{8.BhoBXADL.css → 10.BhoBXADL.css} +0 -0
  366. /package/build/client/_app/immutable/assets/{8.BhoBXADL.css.br → 10.BhoBXADL.css.br} +0 -0
  367. /package/build/client/_app/immutable/assets/{8.BhoBXADL.css.gz → 10.BhoBXADL.css.gz} +0 -0
@@ -0,0 +1,37 @@
1
+ /**
2
+ * PTY input helpers.
3
+ *
4
+ * Submitting text to an interactive agent TUI (codex, claude, gemini, qwen, …)
5
+ * is not as simple as appending a newline. These TUIs read the PTY in raw mode:
6
+ *
7
+ * - A bare LF (`\n`) is NOT the Enter key — it types a literal newline into
8
+ * the prompt and never submits. (Verified: LF leaves codex sitting on its
9
+ * prompt; the message just accumulates.)
10
+ * - Even `"<text>\r"` written as a SINGLE chunk is treated as a bracketed
11
+ * paste by the TUI, so the trailing CR is absorbed into the pasted body
12
+ * instead of submitting. (Verified: codex types the text but does not run.)
13
+ *
14
+ * The reliable approach — the same bytes a real terminal emulator sends when a
15
+ * human pastes and presses Enter — is to wrap the body in an explicit bracketed
16
+ * paste (`ESC[200~` … `ESC[201~`) and then send a CR. The paste-end marker
17
+ * closes the paste unambiguously, so the following CR is a real Enter. This
18
+ * also preserves embedded newlines in multi-line messages (the whole point of
19
+ * bracketed paste) and submits correctly in modern interactive shells, where
20
+ * bracketed paste is enabled by default.
21
+ *
22
+ * Verified empirically against codex 0.136 and claude 2.1 — both receive the
23
+ * message and complete a turn.
24
+ */
25
+
26
+ const PASTE_START = '\x1b[200~';
27
+ const PASTE_END = '\x1b[201~';
28
+
29
+ /**
30
+ * Build the PTY byte sequence that delivers `text` to an interactive terminal
31
+ * and submits it (presses Enter). Any trailing newline the caller added is
32
+ * stripped — the CR after the paste-end marker is what submits.
33
+ */
34
+ export function ptySubmitSequence(text: string): string {
35
+ const body = text.replace(/[\r\n]+$/, '');
36
+ return `${PASTE_START}${body}${PASTE_END}\r`;
37
+ }
@@ -483,6 +483,12 @@ class PtyManager {
483
483
  terminal.pty.resize(cols, rows);
484
484
  terminal.cols = cols;
485
485
  terminal.rows = rows;
486
+ // Broadcast the new PTY size so attached clients (e.g. view-only
487
+ // guests) can follow the terminal dimensions.
488
+ const msg = JSON.stringify({ cols, rows, type: 'resize' });
489
+ for (const ws of terminal.clients) {
490
+ this.safeSend(ws, msg);
491
+ }
486
492
  return true;
487
493
  } catch {
488
494
  return false;
@@ -0,0 +1,37 @@
1
+ // Resolves a terminal-scoped request to owner (API key) or guest (share token).
2
+ // Kept separate from auth.ts so routes without share semantics don't pull in SQLite.
3
+
4
+ import type { AccessContext } from '$lib/types';
5
+
6
+ import { validateAuth } from '../auth';
7
+ import { shareStore } from './share-store';
8
+
9
+ /** Extract the Bearer token from a request, or null. */
10
+ export function bearerToken(request: Request): null | string {
11
+ const auth = request.headers.get('Authorization') || request.headers.get('authorization');
12
+ if (!auth?.startsWith('Bearer ')) {
13
+ return null;
14
+ }
15
+ return auth.slice(7).trim();
16
+ }
17
+
18
+ /**
19
+ * Resolve access for a request targeting one terminal.
20
+ * Owner (valid API key) → { level: 'owner' }.
21
+ * Guest (valid share session for THIS terminal) → { level: 'guest', mode }.
22
+ * Anything else → null.
23
+ */
24
+ export function resolveAccess(request: Request, terminalId: string): AccessContext | null {
25
+ if (validateAuth(request) === null) {
26
+ return { level: 'owner', mode: null };
27
+ }
28
+ const token = bearerToken(request);
29
+ if (!token) {
30
+ return null;
31
+ }
32
+ const session = shareStore.resolveToken(token);
33
+ if (session?.terminalId !== terminalId) {
34
+ return null;
35
+ }
36
+ return { level: 'guest', mode: session.mode };
37
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Share Store — SQLite persistence for terminal sharing.
3
+ *
4
+ * terminal_shares: one share per terminal (scrypt password hash + mode).
5
+ * share_sessions: guest sessions keyed by sha256(token), 7-day TTL.
6
+ *
7
+ * Database location: ~/.shooter/shooter.db (same file as terminal-store).
8
+ */
9
+
10
+ import type { ShareMode, TerminalShareRecord } from '$lib/types';
11
+
12
+ import Database from 'better-sqlite3';
13
+ import { createHash, randomBytes, scryptSync, timingSafeEqual } from 'crypto';
14
+ import * as fs from 'fs';
15
+ import * as path from 'path';
16
+
17
+ const DB_DIR = path.join(process.env.HOME || '', '.shooter');
18
+ const DB_PATH = path.join(DB_DIR, 'shooter.db');
19
+
20
+ const SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
21
+
22
+ // ── Password hashing (scrypt, per-share random salt) ─────────────────
23
+
24
+ export class ShareStore {
25
+ private db: Database.Database;
26
+
27
+ constructor() {
28
+ fs.mkdirSync(DB_DIR, { recursive: true });
29
+ this.db = new Database(DB_PATH);
30
+ this.db.pragma('journal_mode = WAL');
31
+
32
+ this.db.exec(`
33
+ CREATE TABLE IF NOT EXISTS terminal_shares (
34
+ terminal_id TEXT PRIMARY KEY,
35
+ password_hash TEXT NOT NULL,
36
+ mode TEXT NOT NULL,
37
+ created_at INTEGER NOT NULL,
38
+ updated_at INTEGER NOT NULL
39
+ );
40
+ CREATE TABLE IF NOT EXISTS share_sessions (
41
+ token_hash TEXT PRIMARY KEY,
42
+ terminal_id TEXT NOT NULL,
43
+ created_at INTEGER NOT NULL,
44
+ expires_at INTEGER NOT NULL
45
+ )
46
+ `);
47
+
48
+ this.cleanup();
49
+ }
50
+
51
+ /** Purge expired sessions and shares whose terminal no longer exists. */
52
+ cleanup(): void {
53
+ this.db.prepare('DELETE FROM share_sessions WHERE expires_at < ?').run(Date.now());
54
+ try {
55
+ this.db
56
+ .prepare('DELETE FROM terminal_shares WHERE terminal_id NOT IN (SELECT id FROM terminals)')
57
+ .run();
58
+ this.db
59
+ .prepare('DELETE FROM share_sessions WHERE terminal_id NOT IN (SELECT id FROM terminals)')
60
+ .run();
61
+ } catch {
62
+ // terminals table may not exist yet on a fresh database — skip orphan cleanup.
63
+ }
64
+ }
65
+
66
+ /** Issue a new guest session for a shared terminal. Returns the raw token (stored hashed). */
67
+ createSession(terminalId: string): { expiresAt: number; token: string } {
68
+ const token = randomBytes(32).toString('hex');
69
+ const now = Date.now();
70
+ const expiresAt = now + SESSION_TTL_MS;
71
+ this.db
72
+ .prepare(
73
+ 'INSERT INTO share_sessions (token_hash, terminal_id, created_at, expires_at) VALUES (?, ?, ?, ?)'
74
+ )
75
+ .run(hashToken(token), terminalId, now, expiresAt);
76
+ return { expiresAt, token };
77
+ }
78
+
79
+ /** Delete all guest sessions for a terminal (password change / revoke). */
80
+ deleteSessions(terminalId: string): void {
81
+ this.db.prepare('DELETE FROM share_sessions WHERE terminal_id = ?').run(terminalId);
82
+ }
83
+
84
+ /** Revoke a share: delete the share row and every guest session for it. */
85
+ deleteShare(terminalId: string): void {
86
+ this.db.prepare('DELETE FROM terminal_shares WHERE terminal_id = ?').run(terminalId);
87
+ this.deleteSessions(terminalId);
88
+ }
89
+
90
+ getShare(terminalId: string): null | TerminalShareRecord {
91
+ const row = this.db
92
+ .prepare('SELECT * FROM terminal_shares WHERE terminal_id = ?')
93
+ .get(terminalId) as Record<string, unknown> | undefined;
94
+ return row ? rowToShare(row) : null;
95
+ }
96
+
97
+ /**
98
+ * Resolve a guest bearer token to its terminal + mode.
99
+ * Returns null if unknown, expired, or the share was revoked.
100
+ */
101
+ resolveToken(token: string): null | { mode: ShareMode; terminalId: string } {
102
+ if (!token) {
103
+ return null;
104
+ }
105
+ const row = this.db
106
+ .prepare(
107
+ `SELECT s.terminal_id, s.expires_at, sh.mode
108
+ FROM share_sessions s
109
+ JOIN terminal_shares sh ON sh.terminal_id = s.terminal_id
110
+ WHERE s.token_hash = ?`
111
+ )
112
+ .get(hashToken(token)) as Record<string, unknown> | undefined;
113
+ if (!row) {
114
+ return null;
115
+ }
116
+ if ((row.expires_at as number) < Date.now()) {
117
+ this.db.prepare('DELETE FROM share_sessions WHERE token_hash = ?').run(hashToken(token));
118
+ return null;
119
+ }
120
+ return { mode: row.mode as ShareMode, terminalId: row.terminal_id as string };
121
+ }
122
+
123
+ /** Create or replace the share for a terminal. */
124
+ setShare(record: TerminalShareRecord): void {
125
+ this.db
126
+ .prepare(
127
+ `INSERT OR REPLACE INTO terminal_shares
128
+ (terminal_id, password_hash, mode, created_at, updated_at)
129
+ VALUES (?, ?, ?, ?, ?)`
130
+ )
131
+ .run(record.terminalId, record.passwordHash, record.mode, record.createdAt, record.updatedAt);
132
+ }
133
+ }
134
+
135
+ export function hashPassword(password: string): string {
136
+ const salt = randomBytes(16).toString('hex');
137
+ const hash = scryptSync(password, salt, 64).toString('hex');
138
+ return `scrypt:${salt}:${hash}`;
139
+ }
140
+
141
+ export function verifyPassword(password: string, stored: string): boolean {
142
+ const parts = stored.split(':');
143
+ if (parts.length !== 3 || parts[0] !== 'scrypt') {
144
+ return false;
145
+ }
146
+ const expected = Buffer.from(parts[2], 'hex');
147
+ const actual = scryptSync(password, parts[1], 64);
148
+ return expected.length === actual.length && timingSafeEqual(actual, expected);
149
+ }
150
+
151
+ // ── Row mapping ──────────────────────────────────────────────────────
152
+
153
+ function hashToken(token: string): string {
154
+ return createHash('sha256').update(token).digest('hex');
155
+ }
156
+
157
+ function rowToShare(row: Record<string, unknown>): TerminalShareRecord {
158
+ return {
159
+ createdAt: row.created_at as number,
160
+ mode: row.mode as ShareMode,
161
+ passwordHash: row.password_hash as string,
162
+ terminalId: row.terminal_id as string,
163
+ updatedAt: row.updated_at as number,
164
+ };
165
+ }
166
+
167
+ // ── Singleton (globalThis bridges tsx server.ts + SvelteKit handler) ─
168
+
169
+ const SS_GLOBAL_KEY = '__shooter_share_store';
170
+ export const shareStore: ShareStore =
171
+ ((globalThis as Record<string, unknown>)[SS_GLOBAL_KEY] as ShareStore) || new ShareStore();
172
+ (globalThis as Record<string, unknown>)[SS_GLOBAL_KEY] = shareStore;
@@ -0,0 +1,49 @@
1
+ // Tracks WebSocket connections opened with a guest (scoped) ticket, per terminal,
2
+ // so revoke / mode-change / password-change can force-close them immediately.
3
+ // globalThis bridges the tsx server.ts and SvelteKit handler module scopes.
4
+
5
+ import type { WebSocket } from 'ws';
6
+
7
+ const GUESTS_KEY = '__shooter_ws_guest_conns';
8
+ const guests: Map<string, Set<WebSocket>> = ((globalThis as Record<string, unknown>)[
9
+ GUESTS_KEY
10
+ ] as Map<string, Set<WebSocket>>) || new Map<string, Set<WebSocket>>();
11
+ (globalThis as Record<string, unknown>)[GUESTS_KEY] = guests;
12
+
13
+ /** Force-close every guest connection for a terminal. Returns the number closed. */
14
+ export function closeGuests(terminalId: string): number {
15
+ const set = guests.get(terminalId);
16
+ if (!set) {
17
+ return 0;
18
+ }
19
+ let closed = 0;
20
+ for (const ws of set) {
21
+ try {
22
+ ws.close(4001, 'Share revoked');
23
+ closed++;
24
+ } catch {
25
+ // Already closing/closed.
26
+ }
27
+ }
28
+ guests.delete(terminalId);
29
+ return closed;
30
+ }
31
+
32
+ /** Register a guest connection; auto-removes itself on close. */
33
+ export function registerGuest(terminalId: string, ws: WebSocket): void {
34
+ let set = guests.get(terminalId);
35
+ if (!set) {
36
+ set = new Set<WebSocket>();
37
+ guests.set(terminalId, set);
38
+ }
39
+ set.add(ws);
40
+ ws.on('close', () => {
41
+ const current = guests.get(terminalId);
42
+ if (current) {
43
+ current.delete(ws);
44
+ if (current.size === 0) {
45
+ guests.delete(terminalId);
46
+ }
47
+ }
48
+ });
49
+ }
@@ -5,6 +5,7 @@
5
5
  // /ws/session/:id -> Live structured session stream
6
6
  // /ws/events -> Global event bus (broadcasts)
7
7
 
8
+ import type { TicketScope } from '$lib/types';
8
9
  import type { IncomingMessage } from 'http';
9
10
  import type { Duplex } from 'stream';
10
11
  import type { WebSocket, WebSocketServer } from 'ws';
@@ -14,7 +15,9 @@ import {
14
15
  getEventsClientCount,
15
16
  handleEventsConnection,
16
17
  } from './events-handler.js';
18
+ import { registerGuest } from './guest-registry.js';
17
19
  import { handleSessionConnection } from './session-handler.js';
20
+ import { handleSuperSessionConnection } from './super-session-handler.js';
18
21
  import { handleTerminalConnection } from './terminal-handler.js';
19
22
  export type { WireShooterEvent as ShooterEvent } from '$lib/types';
20
23
 
@@ -48,7 +51,8 @@ export function setupWebSocketHandlers(
48
51
  wss: WebSocketServer,
49
52
  request: IncomingMessage,
50
53
  socket: Duplex,
51
- head: Buffer
54
+ head: Buffer,
55
+ scope?: TicketScope
52
56
  ): void {
53
57
  const host = request.headers.host ?? 'localhost';
54
58
  let pathname: string;
@@ -61,17 +65,34 @@ export function setupWebSocketHandlers(
61
65
 
62
66
  // Route matching
63
67
  const terminalMatch = /^\/ws\/terminal\/(.+)$/.exec(pathname);
68
+ const superSessionMatch = /^\/ws\/super-session\/(.+)$/.exec(pathname);
64
69
  const sessionMatch = /^\/ws\/session\/(.+)$/.exec(pathname);
65
70
  const isEvents = pathname === '/ws/events';
66
71
 
67
- if (!terminalMatch && !sessionMatch && !isEvents) {
72
+ if (!terminalMatch && !superSessionMatch && !sessionMatch && !isEvents) {
68
73
  socket.destroy();
69
74
  return;
70
75
  }
71
76
 
77
+ // Scoped (guest) tickets may only open the terminal/session channels of
78
+ // their own terminal. Events and super-session channels broadcast global
79
+ // data, so they are denied outright.
80
+ if (scope) {
81
+ const target = terminalMatch?.[1] ?? sessionMatch?.[1];
82
+ if (!target || superSessionMatch || target !== scope.terminalId) {
83
+ socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
84
+ socket.destroy();
85
+ return;
86
+ }
87
+ }
88
+
72
89
  wss.handleUpgrade(request, socket, head, (ws: WebSocket) => {
73
90
  allConnections.add(ws);
74
91
 
92
+ if (scope) {
93
+ registerGuest(scope.terminalId, ws);
94
+ }
95
+
75
96
  ws.on('close', () => {
76
97
  allConnections.delete(ws);
77
98
  });
@@ -82,10 +103,13 @@ export function setupWebSocketHandlers(
82
103
 
83
104
  if (terminalMatch) {
84
105
  const terminalId = terminalMatch[1];
85
- handleTerminalConnection(ws, terminalId);
106
+ handleTerminalConnection(ws, terminalId, scope);
107
+ } else if (superSessionMatch) {
108
+ const superSessionId = superSessionMatch[1];
109
+ handleSuperSessionConnection(ws, superSessionId);
86
110
  } else if (sessionMatch) {
87
111
  const sessionId = sessionMatch[1];
88
- handleSessionConnection(ws, sessionId);
112
+ handleSessionConnection(ws, sessionId, scope);
89
113
  } else if (isEvents) {
90
114
  handleEventsConnection(ws);
91
115
  }
@@ -16,6 +16,7 @@ import type {
16
16
  WireSessionServerMessage as ServerMessage,
17
17
  SessionWatcherLike,
18
18
  TextContentBlock,
19
+ TicketScope,
19
20
  } from '$lib/types';
20
21
  import type { WebSocket } from 'ws';
21
22
 
@@ -23,6 +24,7 @@ import * as fs from 'fs';
23
24
  import * as path from 'path';
24
25
 
25
26
  import { findCodexRolloutById } from '../sessions/codex-reader';
27
+ import { ptySubmitSequence } from '../terminal/pty-input';
26
28
 
27
29
  // ── Module-level references ──────────────────────────────────────────
28
30
 
@@ -40,7 +42,7 @@ let _sessionWatcher: null | SessionWatcherLike = null;
40
42
  * 3. If still no terminal, treat as an external session — find the JSONL
41
43
  * file directly and stream it via the session watcher.
42
44
  */
43
- export function handleSessionConnection(ws: WebSocket, id: string): void {
45
+ export function handleSessionConnection(ws: WebSocket, id: string, scope?: TicketScope): void {
44
46
  const state: ConnectionState = {
45
47
  isExternalSession: false,
46
48
  retryInterval: null,
@@ -65,7 +67,7 @@ export function handleSessionConnection(ws: WebSocket, id: string): void {
65
67
  if (terminal) {
66
68
  state.terminalId = terminal.id;
67
69
  subscribeToSession(ws, state, terminal);
68
- wireClientMessages(ws, state);
70
+ wireClientMessages(ws, state, scope);
69
71
  wireCleanup(ws, state);
70
72
  return;
71
73
  }
@@ -75,7 +77,7 @@ export function handleSessionConnection(ws: WebSocket, id: string): void {
75
77
  if (jsonlPath) {
76
78
  state.isExternalSession = true;
77
79
  subscribeToExternalSession(ws, state, jsonlPath);
78
- wireClientMessages(ws, state);
80
+ wireClientMessages(ws, state, scope);
79
81
  wireCleanup(ws, state);
80
82
  return;
81
83
  }
@@ -506,7 +508,7 @@ function wireCleanup(ws: WebSocket, state: ConnectionState): void {
506
508
  * Extracted so both terminal-backed and external sessions share the same
507
509
  * message loop.
508
510
  */
509
- function wireClientMessages(ws: WebSocket, state: ConnectionState): void {
511
+ function wireClientMessages(ws: WebSocket, state: ConnectionState, scope?: TicketScope): void {
510
512
  ws.on('message', (raw: Buffer | string) => {
511
513
  const data = typeof raw === 'string' ? raw : raw.toString('utf-8');
512
514
  const msg = parseClientMessage(data);
@@ -517,6 +519,10 @@ function wireClientMessages(ws: WebSocket, state: ConnectionState): void {
517
519
  try {
518
520
  switch (msg.type) {
519
521
  case 'cancel': {
522
+ if (scope?.readOnly) {
523
+ safeSend(ws, { message: 'This shared terminal is view-only.', type: 'error' });
524
+ return;
525
+ }
520
526
  if (state.isExternalSession) {
521
527
  safeSend(ws, {
522
528
  message: 'Cannot cancel — this is a read-only session. Connect to a terminal first.',
@@ -534,6 +540,10 @@ function wireClientMessages(ws: WebSocket, state: ConnectionState): void {
534
540
  }
535
541
 
536
542
  case 'send-input': {
543
+ if (scope?.readOnly) {
544
+ safeSend(ws, { message: 'This shared terminal is view-only.', type: 'error' });
545
+ return;
546
+ }
537
547
  if (state.isExternalSession) {
538
548
  safeSend(ws, {
539
549
  message:
@@ -547,11 +557,20 @@ function wireClientMessages(ws: WebSocket, state: ConnectionState): void {
547
557
  safeSend(ws, { message: 'Terminal has exited', type: 'error' });
548
558
  return;
549
559
  }
550
- currentTerminal.pty.write(`${msg.text}\n`);
560
+ // Deliver + submit via a bracketed paste (see pty-input.ts): a bare
561
+ // LF never submits to an agent TUI and a trailing CR in the same
562
+ // write is absorbed as paste content. Bracketed paste closes the
563
+ // paste explicitly so the following CR is a real Enter.
564
+ currentTerminal.pty.write(ptySubmitSequence(msg.text));
551
565
  break;
552
566
  }
553
567
 
554
568
  case 'subscribe': {
569
+ // Scoped guests may only (re)subscribe to their own terminal's session.
570
+ if (scope && msg.sessionId !== scope.terminalId) {
571
+ safeSend(ws, { message: 'Not authorized for this session.', type: 'error' });
572
+ return;
573
+ }
555
574
  // (Re)subscribe to a different session. Clean up the old subscription.
556
575
  if (state.retryInterval) {
557
576
  clearInterval(state.retryInterval);
@@ -0,0 +1,200 @@
1
+ // WebSocket handler for /ws/super-session/:id — the merged Session-Over-Sessions
2
+ // stream. On connect it replays the merged transcript (sos-history) and then
3
+ // streams live tagged messages from every member. Accepts human-driven control
4
+ // messages: relay-forward (inject into a member's terminal), member-add,
5
+ // member-remove. Mirrors session-handler.ts in shape.
6
+
7
+ import type { SessionSource, SosClientMessage, SosServerMessage } from '$lib/types';
8
+ import type { WebSocket } from 'ws';
9
+
10
+ import * as path from 'path';
11
+
12
+ import { PROVIDERS } from '../sessions/registry';
13
+ import { sosCoordinator } from '../sos/coordinator';
14
+
15
+ const MAX_RELAY_TEXT = 10240; // 10 KB, same cap as /ws/session send-input
16
+ // Stop streaming to a client whose outbound buffer exceeds this — bounds memory
17
+ // growth when a slow consumer can't drain sos-history replay or a live burst.
18
+ const MAX_WS_BUFFERED_BYTES = 1_000_000;
19
+ const VALID_SOURCES = new Set<string>(PROVIDERS.map((p) => p.source));
20
+
21
+ export function handleSuperSessionConnection(ws: WebSocket, id: string): void {
22
+ const session = sosCoordinator.getSuperSession(id);
23
+ if (!session) {
24
+ safeSend(ws, { message: `Super-session not found: ${id}`, type: 'sos-error' });
25
+ ws.close(1008, 'Super-session not found');
26
+ return;
27
+ }
28
+
29
+ // subscribe() replays the current transcript as an sos-history message, then
30
+ // streams live sos-message / sos-member-* events. If a send fails (socket
31
+ // closed or buffer over cap), stop feeding this client to bound memory.
32
+ let unsubscribe: (() => void) | null = null;
33
+ unsubscribe = sosCoordinator.subscribe(id, (msg) => {
34
+ if (!safeSend(ws, msg)) {
35
+ unsubscribe?.();
36
+ unsubscribe = null;
37
+ }
38
+ });
39
+
40
+ ws.on('message', (raw: Buffer | string) => {
41
+ const data = typeof raw === 'string' ? raw : raw.toString('utf-8');
42
+ const msg = parseClientMessage(data);
43
+ if (!msg) {
44
+ // Surface schema drift instead of silently dropping the frame — a dead
45
+ // control in the UI is much harder to debug than an explicit error.
46
+ safeSend(ws, { message: 'Invalid super-session control message', type: 'sos-error' });
47
+ return;
48
+ }
49
+ handleClientMessage(ws, id, msg);
50
+ });
51
+
52
+ ws.on('close', () => {
53
+ unsubscribe?.();
54
+ });
55
+ ws.on('error', () => {
56
+ // cleanup happens in 'close'
57
+ });
58
+ }
59
+
60
+ /** True when the string is a known provider source. */
61
+ export function isValidProvider(value: string): value is SessionSource {
62
+ return VALID_SOURCES.has(value);
63
+ }
64
+
65
+ /** True for a bare session id (OpenCode) or an absolute path under HOME. */
66
+ export function isValidSessionKey(key: string): boolean {
67
+ if (/^[A-Za-z0-9_-]+$/.test(key)) {
68
+ return true;
69
+ }
70
+ const home = process.env.HOME || '';
71
+ if (home === '' || !path.isAbsolute(key)) {
72
+ return false;
73
+ }
74
+ // Resolve before comparing so `..` segments cannot escape the HOME sandbox
75
+ // (e.g. /home/user/../etc/passwd passes a raw prefix check but not this).
76
+ const relative = path.relative(path.resolve(home), path.resolve(key));
77
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
78
+ }
79
+
80
+ function handleClientMessage(ws: WebSocket, superSessionId: string, msg: SosClientMessage): void {
81
+ switch (msg.type) {
82
+ case 'member-add': {
83
+ const member = sosCoordinator.addMember(superSessionId, {
84
+ capability: msg.capability,
85
+ provider: msg.provider,
86
+ sessionKey: msg.sessionKey,
87
+ terminalId: msg.terminalId ?? null,
88
+ });
89
+ if (!member) {
90
+ safeSend(ws, { message: 'Failed to add member', type: 'sos-error' });
91
+ }
92
+ break;
93
+ }
94
+ case 'member-remove': {
95
+ const ok = sosCoordinator.removeMember(superSessionId, msg.memberId);
96
+ if (!ok) {
97
+ safeSend(ws, { message: 'Member not found', type: 'sos-error' });
98
+ }
99
+ break;
100
+ }
101
+ case 'relay-approve': {
102
+ if (!sosCoordinator.approveRelay(superSessionId, msg.relayId)) {
103
+ safeSend(ws, { message: 'Pending relay not found', type: 'sos-error' });
104
+ }
105
+ break;
106
+ }
107
+ case 'relay-deny': {
108
+ if (!sosCoordinator.denyRelay(superSessionId, msg.relayId)) {
109
+ safeSend(ws, { message: 'Pending relay not found', type: 'sos-error' });
110
+ }
111
+ break;
112
+ }
113
+ case 'relay-forward': {
114
+ const error = sosCoordinator.relayForward(superSessionId, msg.toMemberId, msg.text);
115
+ if (error) {
116
+ safeSend(ws, { message: error, type: 'sos-error' });
117
+ }
118
+ break;
119
+ }
120
+ }
121
+ }
122
+
123
+ function parseClientMessage(raw: string): null | SosClientMessage {
124
+ let parsed: unknown;
125
+ try {
126
+ parsed = JSON.parse(raw);
127
+ } catch {
128
+ return null;
129
+ }
130
+ if (typeof parsed !== 'object' || parsed === null) {
131
+ return null;
132
+ }
133
+ const msg = parsed as Record<string, unknown>;
134
+
135
+ switch (msg.type) {
136
+ case 'member-add': {
137
+ if (
138
+ typeof msg.sessionKey !== 'string' ||
139
+ !isValidSessionKey(msg.sessionKey) ||
140
+ typeof msg.provider !== 'string' ||
141
+ !isValidProvider(msg.provider)
142
+ ) {
143
+ return null;
144
+ }
145
+ return {
146
+ capability: typeof msg.capability === 'string' ? msg.capability : undefined,
147
+ provider: msg.provider,
148
+ sessionKey: msg.sessionKey,
149
+ terminalId: typeof msg.terminalId === 'string' ? msg.terminalId : undefined,
150
+ type: 'member-add',
151
+ };
152
+ }
153
+ case 'member-remove': {
154
+ if (typeof msg.memberId !== 'string' || msg.memberId.length === 0) {
155
+ return null;
156
+ }
157
+ return { memberId: msg.memberId, type: 'member-remove' };
158
+ }
159
+ case 'relay-approve': {
160
+ if (typeof msg.relayId !== 'string' || msg.relayId.length === 0) {
161
+ return null;
162
+ }
163
+ return { relayId: msg.relayId, type: 'relay-approve' };
164
+ }
165
+ case 'relay-deny': {
166
+ if (typeof msg.relayId !== 'string' || msg.relayId.length === 0) {
167
+ return null;
168
+ }
169
+ return { relayId: msg.relayId, type: 'relay-deny' };
170
+ }
171
+ case 'relay-forward': {
172
+ if (
173
+ typeof msg.toMemberId !== 'string' ||
174
+ typeof msg.text !== 'string' ||
175
+ msg.text.length === 0 ||
176
+ msg.text.length > MAX_RELAY_TEXT
177
+ ) {
178
+ return null;
179
+ }
180
+ return { text: msg.text, toMemberId: msg.toMemberId, type: 'relay-forward' };
181
+ }
182
+ default:
183
+ return null;
184
+ }
185
+ }
186
+
187
+ function safeSend(ws: WebSocket, msg: SosServerMessage): boolean {
188
+ try {
189
+ if (ws.readyState !== 1 /* OPEN */) {
190
+ return false;
191
+ }
192
+ if (ws.bufferedAmount > MAX_WS_BUFFERED_BYTES) {
193
+ return false;
194
+ }
195
+ ws.send(JSON.stringify(msg));
196
+ return true;
197
+ } catch {
198
+ return false;
199
+ }
200
+ }