@juspay/shooter 1.1.0 → 1.2.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 (330) hide show
  1. package/build/client/_app/immutable/assets/{0.DC5pAwP3.css → 0.BhZOCxO4.css} +1 -1
  2. package/build/client/_app/immutable/assets/0.BhZOCxO4.css.br +0 -0
  3. package/build/client/_app/immutable/assets/0.BhZOCxO4.css.gz +0 -0
  4. package/build/client/_app/immutable/assets/1.BYutk3aU.css +1 -0
  5. package/build/client/_app/immutable/assets/1.BYutk3aU.css.br +0 -0
  6. package/build/client/_app/immutable/assets/1.BYutk3aU.css.gz +0 -0
  7. package/build/client/_app/immutable/assets/{3.BoXp0JoS.css → 3.DGDHCVnW.css} +1 -1
  8. package/build/client/_app/immutable/assets/3.DGDHCVnW.css.br +0 -0
  9. package/build/client/_app/immutable/assets/3.DGDHCVnW.css.gz +0 -0
  10. package/build/client/_app/immutable/assets/4.BFUut--w.css +1 -0
  11. package/build/client/_app/immutable/assets/4.BFUut--w.css.br +0 -0
  12. package/build/client/_app/immutable/assets/4.BFUut--w.css.gz +0 -0
  13. package/build/client/_app/immutable/assets/5.BTOx7yt7.css +1 -0
  14. package/build/client/_app/immutable/assets/5.BTOx7yt7.css.br +0 -0
  15. package/build/client/_app/immutable/assets/5.BTOx7yt7.css.gz +0 -0
  16. package/build/client/_app/immutable/assets/{7.PyEFVv_s.css → 7.DwS5ZHBh.css} +1 -1
  17. package/build/client/_app/immutable/assets/7.DwS5ZHBh.css.br +0 -0
  18. package/build/client/_app/immutable/assets/7.DwS5ZHBh.css.gz +0 -0
  19. package/build/client/_app/immutable/assets/ChatView.CwWbzIL-.css +1 -0
  20. package/build/client/_app/immutable/assets/ChatView.CwWbzIL-.css.br +0 -0
  21. package/build/client/_app/immutable/assets/ChatView.CwWbzIL-.css.gz +0 -0
  22. package/build/client/_app/immutable/chunks/B-K5Sh65.js +1 -0
  23. package/build/client/_app/immutable/chunks/B-K5Sh65.js.br +0 -0
  24. package/build/client/_app/immutable/chunks/B-K5Sh65.js.gz +0 -0
  25. package/build/client/_app/immutable/chunks/B5NAKyil.js +20 -0
  26. package/build/client/_app/immutable/chunks/B5NAKyil.js.br +0 -0
  27. package/build/client/_app/immutable/chunks/B5NAKyil.js.gz +0 -0
  28. package/build/client/_app/immutable/chunks/B8XegpSE.js +1 -0
  29. package/build/client/_app/immutable/chunks/B8XegpSE.js.br +0 -0
  30. package/build/client/_app/immutable/chunks/B8XegpSE.js.gz +0 -0
  31. package/build/client/_app/immutable/chunks/{50RPd5u3.js → B8zoBsv3.js} +1 -1
  32. package/build/client/_app/immutable/chunks/B8zoBsv3.js.br +0 -0
  33. package/build/client/_app/immutable/chunks/B8zoBsv3.js.gz +0 -0
  34. package/build/client/_app/immutable/chunks/BLszSzTT.js +1 -0
  35. package/build/client/_app/immutable/chunks/BLszSzTT.js.br +0 -0
  36. package/build/client/_app/immutable/chunks/BLszSzTT.js.gz +0 -0
  37. package/build/client/_app/immutable/chunks/BOYo8yTr.js +1 -0
  38. package/build/client/_app/immutable/chunks/BOYo8yTr.js.br +0 -0
  39. package/build/client/_app/immutable/chunks/BOYo8yTr.js.gz +0 -0
  40. package/build/client/_app/immutable/chunks/{CdL99jkG.js → BYqGCrTe.js} +1 -1
  41. package/build/client/_app/immutable/chunks/BYqGCrTe.js.br +0 -0
  42. package/build/client/_app/immutable/chunks/BYqGCrTe.js.gz +0 -0
  43. package/build/client/_app/immutable/chunks/Bu1aqm5j.js +1 -0
  44. package/build/client/_app/immutable/chunks/Bu1aqm5j.js.br +0 -0
  45. package/build/client/_app/immutable/chunks/Bu1aqm5j.js.gz +0 -0
  46. package/build/client/_app/immutable/chunks/C4mLaWWx.js +1 -0
  47. package/build/client/_app/immutable/chunks/C4mLaWWx.js.br +0 -0
  48. package/build/client/_app/immutable/chunks/C4mLaWWx.js.gz +0 -0
  49. package/build/client/_app/immutable/chunks/{ClK4wZbC.js → CQjSATpv.js} +16 -16
  50. package/build/client/_app/immutable/chunks/CQjSATpv.js.br +0 -0
  51. package/build/client/_app/immutable/chunks/CQjSATpv.js.gz +0 -0
  52. package/build/client/_app/immutable/chunks/{CojFyTPp.js → CSoRdFvv.js} +1 -1
  53. package/build/client/_app/immutable/chunks/CSoRdFvv.js.br +0 -0
  54. package/build/client/_app/immutable/chunks/CSoRdFvv.js.gz +0 -0
  55. package/build/client/_app/immutable/chunks/{tjbnEgXP.js → CZHsSL_X.js} +1 -1
  56. package/build/client/_app/immutable/chunks/CZHsSL_X.js.br +0 -0
  57. package/build/client/_app/immutable/chunks/CZHsSL_X.js.gz +0 -0
  58. package/build/client/_app/immutable/chunks/DVkn4r72.js +1 -0
  59. package/build/client/_app/immutable/chunks/DVkn4r72.js.br +0 -0
  60. package/build/client/_app/immutable/chunks/DVkn4r72.js.gz +0 -0
  61. package/build/client/_app/immutable/chunks/DjsDGxCa.js +41 -0
  62. package/build/client/_app/immutable/chunks/DjsDGxCa.js.br +0 -0
  63. package/build/client/_app/immutable/chunks/DjsDGxCa.js.gz +0 -0
  64. package/build/client/_app/immutable/chunks/UJOiqIYE.js +1 -0
  65. package/build/client/_app/immutable/chunks/UJOiqIYE.js.br +0 -0
  66. package/build/client/_app/immutable/chunks/UJOiqIYE.js.gz +0 -0
  67. package/build/client/_app/immutable/chunks/r0JawsZc.js +2 -0
  68. package/build/client/_app/immutable/chunks/r0JawsZc.js.br +0 -0
  69. package/build/client/_app/immutable/chunks/r0JawsZc.js.gz +0 -0
  70. package/build/client/_app/immutable/entry/app.Z3zMnuSx.js +2 -0
  71. package/build/client/_app/immutable/entry/app.Z3zMnuSx.js.br +0 -0
  72. package/build/client/_app/immutable/entry/app.Z3zMnuSx.js.gz +0 -0
  73. package/build/client/_app/immutable/entry/start.Dd-gIP4y.js +1 -0
  74. package/build/client/_app/immutable/entry/start.Dd-gIP4y.js.br +2 -0
  75. package/build/client/_app/immutable/entry/start.Dd-gIP4y.js.gz +0 -0
  76. package/build/client/_app/immutable/nodes/0.D2YR8tTD.js +1 -0
  77. package/build/client/_app/immutable/nodes/0.D2YR8tTD.js.br +0 -0
  78. package/build/client/_app/immutable/nodes/0.D2YR8tTD.js.gz +0 -0
  79. package/build/client/_app/immutable/nodes/1.B3m6rO4C.js +1 -0
  80. package/build/client/_app/immutable/nodes/1.B3m6rO4C.js.br +0 -0
  81. package/build/client/_app/immutable/nodes/1.B3m6rO4C.js.gz +0 -0
  82. package/build/client/_app/immutable/nodes/2.CyRB2euU.js +1 -0
  83. package/build/client/_app/immutable/nodes/2.CyRB2euU.js.br +0 -0
  84. package/build/client/_app/immutable/nodes/2.CyRB2euU.js.gz +0 -0
  85. package/build/client/_app/immutable/nodes/3.3yohCM25.js +3 -0
  86. package/build/client/_app/immutable/nodes/3.3yohCM25.js.br +0 -0
  87. package/build/client/_app/immutable/nodes/3.3yohCM25.js.gz +0 -0
  88. package/build/client/_app/immutable/nodes/4.DEAcwl7l.js +1 -0
  89. package/build/client/_app/immutable/nodes/4.DEAcwl7l.js.br +0 -0
  90. package/build/client/_app/immutable/nodes/4.DEAcwl7l.js.gz +0 -0
  91. package/build/client/_app/immutable/nodes/5.C6bLGWQR.js +4 -0
  92. package/build/client/_app/immutable/nodes/5.C6bLGWQR.js.br +0 -0
  93. package/build/client/_app/immutable/nodes/5.C6bLGWQR.js.gz +0 -0
  94. package/build/client/_app/immutable/nodes/6.ByTzlA2D.js +2 -0
  95. package/build/client/_app/immutable/nodes/6.ByTzlA2D.js.br +0 -0
  96. package/build/client/_app/immutable/nodes/6.ByTzlA2D.js.gz +0 -0
  97. package/build/client/_app/immutable/nodes/7.BPMfwzd2.js +2 -0
  98. package/build/client/_app/immutable/nodes/7.BPMfwzd2.js.br +0 -0
  99. package/build/client/_app/immutable/nodes/7.BPMfwzd2.js.gz +0 -0
  100. package/build/client/_app/version.json +1 -1
  101. package/build/client/_app/version.json.br +0 -0
  102. package/build/client/_app/version.json.gz +0 -0
  103. package/build/client/manifest.json +13 -0
  104. package/build/client/manifest.json.br +0 -0
  105. package/build/client/manifest.json.gz +0 -0
  106. package/build/server/chunks/0-Vk38tI2J.js +9 -0
  107. package/build/server/chunks/0-Vk38tI2J.js.map +1 -0
  108. package/build/server/chunks/1-BvYQX5MR.js +9 -0
  109. package/build/server/chunks/1-BvYQX5MR.js.map +1 -0
  110. package/build/server/chunks/2-Cl7R4Qk2.js +9 -0
  111. package/build/server/chunks/2-Cl7R4Qk2.js.map +1 -0
  112. package/build/server/chunks/3-Ck7ewhOX.js +9 -0
  113. package/build/server/chunks/3-Ck7ewhOX.js.map +1 -0
  114. package/build/server/chunks/4-CnDeRm2Z.js +9 -0
  115. package/build/server/chunks/4-CnDeRm2Z.js.map +1 -0
  116. package/build/server/chunks/5-IxitzEvN.js +9 -0
  117. package/build/server/chunks/{5-D0wB7nfE.js.map → 5-IxitzEvN.js.map} +1 -1
  118. package/build/server/chunks/6-CyZ3r1iS.js +9 -0
  119. package/build/server/chunks/6-CyZ3r1iS.js.map +1 -0
  120. package/build/server/chunks/7-BmI7du46.js +9 -0
  121. package/build/server/chunks/7-BmI7du46.js.map +1 -0
  122. package/build/server/chunks/{Button-C7D5W6wV.js → Button-Cs1aE6ka.js} +2 -2
  123. package/build/server/chunks/{Button-C7D5W6wV.js.map → Button-Cs1aE6ka.js.map} +1 -1
  124. package/build/server/chunks/{EmptyState-Ci4pSpmY.js → EmptyState-DDFH1K8g.js} +10 -4
  125. package/build/server/chunks/EmptyState-DDFH1K8g.js.map +1 -0
  126. package/build/server/chunks/{Icon-DyrkHVnV.js → Icon-CEUrotA6.js} +3 -3
  127. package/build/server/chunks/{Icon-DyrkHVnV.js.map → Icon-CEUrotA6.js.map} +1 -1
  128. package/build/server/chunks/{Shimmer-BITK6wrm.js → Shimmer-DB8W1zt6.js} +2 -2
  129. package/build/server/chunks/{Shimmer-BITK6wrm.js.map → Shimmer-DB8W1zt6.js.map} +1 -1
  130. package/build/server/chunks/_error.svelte-uCOJNxvr.js +39 -0
  131. package/build/server/chunks/_error.svelte-uCOJNxvr.js.map +1 -0
  132. package/build/server/chunks/{_layout.svelte-CO4f8UD7.js → _layout.svelte-CtWmEJwe.js} +5 -25
  133. package/build/server/chunks/_layout.svelte-CtWmEJwe.js.map +1 -0
  134. package/build/server/chunks/{_page.svelte-C6bns9aQ.js → _page.svelte-BcZaKdX9.js} +13 -9
  135. package/build/server/chunks/_page.svelte-BcZaKdX9.js.map +1 -0
  136. package/build/server/chunks/{_page.svelte-3Cc3NMAP.js → _page.svelte-BdYynOck.js} +18 -14
  137. package/build/server/chunks/_page.svelte-BdYynOck.js.map +1 -0
  138. package/build/server/chunks/{_page.svelte-CFCONiDK.js → _page.svelte-BgevQjq1.js} +16 -12
  139. package/build/server/chunks/_page.svelte-BgevQjq1.js.map +1 -0
  140. package/build/server/chunks/{_page.svelte-DskND_G9.js → _page.svelte-CVq6tRb3.js} +8 -8
  141. package/build/server/chunks/_page.svelte-CVq6tRb3.js.map +1 -0
  142. package/build/server/chunks/{_page.svelte-rTrWmhOp.js → _page.svelte-CxWcQ0Am.js} +8 -8
  143. package/build/server/chunks/_page.svelte-CxWcQ0Am.js.map +1 -0
  144. package/build/server/chunks/{_page.svelte-C0p3HsIW.js → _page.svelte-DO4oa_LY.js} +11 -13
  145. package/build/server/chunks/_page.svelte-DO4oa_LY.js.map +1 -0
  146. package/build/server/chunks/{_server.ts-CS5H5klP.js → _server.ts-CAxsWKvS.js} +14 -10
  147. package/build/server/chunks/_server.ts-CAxsWKvS.js.map +1 -0
  148. package/build/server/chunks/{_server.ts-C8slOHB0.js → _server.ts-CTpcLUH8.js} +3 -2
  149. package/build/server/chunks/_server.ts-CTpcLUH8.js.map +1 -0
  150. package/build/server/chunks/_server.ts-CtH0dhUp.js +71 -0
  151. package/build/server/chunks/_server.ts-CtH0dhUp.js.map +1 -0
  152. package/build/server/chunks/{_server.ts-DhTrdlWH.js → _server.ts-DB_Kg97c.js} +14 -3
  153. package/build/server/chunks/_server.ts-DB_Kg97c.js.map +1 -0
  154. package/build/server/chunks/_server.ts-DYpJImqd.js +99 -0
  155. package/build/server/chunks/_server.ts-DYpJImqd.js.map +1 -0
  156. package/build/server/chunks/{_server.ts-DpEVfp8W.js → _server.ts-Deok2y88.js} +58 -40
  157. package/build/server/chunks/_server.ts-Deok2y88.js.map +1 -0
  158. package/build/server/chunks/{_server.ts-uHUi-4cd.js → _server.ts-vekTmWAx.js} +3 -2
  159. package/build/server/chunks/_server.ts-vekTmWAx.js.map +1 -0
  160. package/build/server/chunks/{client2-BjxIYuno.js → client-BdGHe_hY.js} +4 -4
  161. package/build/server/chunks/client-BdGHe_hY.js.map +1 -0
  162. package/build/server/chunks/client2-CCBGA-2V.js +7 -0
  163. package/build/server/chunks/client2-CCBGA-2V.js.map +1 -0
  164. package/build/server/chunks/{index-Cnl871UW.js → index-DwaY1cAm.js} +2 -2
  165. package/build/server/chunks/{index-Cnl871UW.js.map → index-DwaY1cAm.js.map} +1 -1
  166. package/build/server/chunks/{index-server-CUC9Jt7r.js → index-server-CrDaL06Y.js} +2 -2
  167. package/build/server/chunks/{index-server-CUC9Jt7r.js.map → index-server-CrDaL06Y.js.map} +1 -1
  168. package/build/server/chunks/{index2-HA3VTH7y.js → index2-CgclKpUj.js} +2 -2
  169. package/build/server/chunks/{index2-HA3VTH7y.js.map → index2-CgclKpUj.js.map} +1 -1
  170. package/build/server/chunks/opencode-db-path-DcfhJtJy.js +15 -0
  171. package/build/server/chunks/opencode-db-path-DcfhJtJy.js.map +1 -0
  172. package/build/server/chunks/{pty-manager-DR0Wt2Ac.js → pty-manager-BQVB7IVj.js} +17 -15
  173. package/build/server/chunks/pty-manager-BQVB7IVj.js.map +1 -0
  174. package/build/server/chunks/{root-DhswcH6o.js → root-DDSnEAZv.js} +2 -2
  175. package/build/server/chunks/{root-DhswcH6o.js.map → root-DDSnEAZv.js.map} +1 -1
  176. package/build/server/chunks/{state.svelte-2Lellg7t.js → state.svelte-hBbXlUak.js} +3 -3
  177. package/build/server/chunks/{state.svelte-2Lellg7t.js.map → state.svelte-hBbXlUak.js.map} +1 -1
  178. package/build/server/chunks/stores-DHNzYNpX.js +28 -0
  179. package/build/server/chunks/stores-DHNzYNpX.js.map +1 -0
  180. package/build/server/index.js +5 -5
  181. package/build/server/index.js.map +1 -1
  182. package/build/server/manifest.js +30 -16
  183. package/build/server/manifest.js.map +1 -1
  184. package/package.json +1 -1
  185. package/scripts/setup.cjs +87 -13
  186. package/server.ts +3 -0
  187. package/src/app.html +3 -0
  188. package/src/lib/modules/client/common/cache.ts +10 -2
  189. package/src/lib/modules/client/common/index.ts +1 -1
  190. package/src/lib/modules/client/common/markdown.ts +22 -1
  191. package/src/lib/modules/client/common/time.ts +13 -11
  192. package/src/lib/modules/client/terminal/ChatView.svelte +324 -67
  193. package/src/lib/modules/client/terminal/ConnectionStatus.svelte +1 -0
  194. package/src/lib/modules/client/terminal/xterm-wrapper.ts +4 -1
  195. package/src/lib/modules/server/sessions/jsonl-reader.ts +43 -28
  196. package/src/lib/modules/server/sessions/opencode-db-path.ts +26 -0
  197. package/src/lib/modules/server/sessions/opencode-reader.ts +13 -15
  198. package/src/lib/modules/server/sessions/process-detector.ts +103 -0
  199. package/src/lib/modules/server/terminal/opencode-watcher.ts +3 -14
  200. package/src/lib/modules/server/terminal/pty-manager.ts +20 -8
  201. package/src/lib/modules/server/ws/session-handler.ts +282 -104
  202. package/src/lib/theme.css +30 -0
  203. package/src/routes/+error.svelte +94 -0
  204. package/src/routes/+page.svelte +18 -8
  205. package/src/routes/api/health/+server.ts +14 -2
  206. package/src/routes/api/sessions/+server.ts +12 -4
  207. package/src/routes/api/sessions/connect/+server.ts +125 -0
  208. package/src/routes/api/sessions/detect/+server.ts +27 -0
  209. package/src/routes/api/terminals/+server.ts +12 -9
  210. package/src/routes/config/+page.svelte +22 -1
  211. package/src/routes/project/+page.svelte +159 -8
  212. package/src/routes/session/[id]/+page.svelte +477 -299
  213. package/src/routes/terminals/+page.svelte +14 -3
  214. package/src/routes/terminals/[id]/+page.svelte +25 -0
  215. package/build/client/_app/immutable/assets/0.DC5pAwP3.css.br +0 -0
  216. package/build/client/_app/immutable/assets/0.DC5pAwP3.css.gz +0 -0
  217. package/build/client/_app/immutable/assets/3.BoXp0JoS.css.br +0 -0
  218. package/build/client/_app/immutable/assets/3.BoXp0JoS.css.gz +0 -0
  219. package/build/client/_app/immutable/assets/4.cJuCkJKZ.css +0 -1
  220. package/build/client/_app/immutable/assets/4.cJuCkJKZ.css.br +0 -0
  221. package/build/client/_app/immutable/assets/4.cJuCkJKZ.css.gz +0 -0
  222. package/build/client/_app/immutable/assets/5.DRjApZQW.css +0 -1
  223. package/build/client/_app/immutable/assets/5.DRjApZQW.css.br +0 -0
  224. package/build/client/_app/immutable/assets/5.DRjApZQW.css.gz +0 -0
  225. package/build/client/_app/immutable/assets/7.PyEFVv_s.css.br +0 -0
  226. package/build/client/_app/immutable/assets/7.PyEFVv_s.css.gz +0 -0
  227. package/build/client/_app/immutable/assets/ChatView.CsdBAOKx.css +0 -1
  228. package/build/client/_app/immutable/assets/ChatView.CsdBAOKx.css.br +0 -0
  229. package/build/client/_app/immutable/assets/ChatView.CsdBAOKx.css.gz +0 -0
  230. package/build/client/_app/immutable/chunks/50RPd5u3.js.br +0 -0
  231. package/build/client/_app/immutable/chunks/50RPd5u3.js.gz +0 -0
  232. package/build/client/_app/immutable/chunks/BDC7XD4o.js +0 -1
  233. package/build/client/_app/immutable/chunks/BDC7XD4o.js.br +0 -0
  234. package/build/client/_app/immutable/chunks/BDC7XD4o.js.gz +0 -0
  235. package/build/client/_app/immutable/chunks/BeONA6_G.js +0 -2
  236. package/build/client/_app/immutable/chunks/BeONA6_G.js.br +0 -0
  237. package/build/client/_app/immutable/chunks/BeONA6_G.js.gz +0 -0
  238. package/build/client/_app/immutable/chunks/BygiiMA0.js +0 -1
  239. package/build/client/_app/immutable/chunks/BygiiMA0.js.br +0 -0
  240. package/build/client/_app/immutable/chunks/BygiiMA0.js.gz +0 -0
  241. package/build/client/_app/immutable/chunks/CGLrx-H5.js +0 -1
  242. package/build/client/_app/immutable/chunks/CGLrx-H5.js.br +0 -0
  243. package/build/client/_app/immutable/chunks/CGLrx-H5.js.gz +0 -0
  244. package/build/client/_app/immutable/chunks/CHvUpVYv.js +0 -1
  245. package/build/client/_app/immutable/chunks/CHvUpVYv.js.br +0 -0
  246. package/build/client/_app/immutable/chunks/CHvUpVYv.js.gz +0 -0
  247. package/build/client/_app/immutable/chunks/CbqC9BW7.js +0 -1
  248. package/build/client/_app/immutable/chunks/CbqC9BW7.js.br +0 -2
  249. package/build/client/_app/immutable/chunks/CbqC9BW7.js.gz +0 -0
  250. package/build/client/_app/immutable/chunks/CdL99jkG.js.br +0 -0
  251. package/build/client/_app/immutable/chunks/CdL99jkG.js.gz +0 -0
  252. package/build/client/_app/immutable/chunks/ClK4wZbC.js.br +0 -0
  253. package/build/client/_app/immutable/chunks/ClK4wZbC.js.gz +0 -0
  254. package/build/client/_app/immutable/chunks/CojFyTPp.js.br +0 -0
  255. package/build/client/_app/immutable/chunks/CojFyTPp.js.gz +0 -0
  256. package/build/client/_app/immutable/chunks/DREFAyhX.js +0 -60
  257. package/build/client/_app/immutable/chunks/DREFAyhX.js.br +0 -0
  258. package/build/client/_app/immutable/chunks/DREFAyhX.js.gz +0 -0
  259. package/build/client/_app/immutable/chunks/K_aHH2KN.js +0 -1
  260. package/build/client/_app/immutable/chunks/K_aHH2KN.js.br +0 -0
  261. package/build/client/_app/immutable/chunks/K_aHH2KN.js.gz +0 -0
  262. package/build/client/_app/immutable/chunks/Ona8oofC.js +0 -1
  263. package/build/client/_app/immutable/chunks/Ona8oofC.js.br +0 -0
  264. package/build/client/_app/immutable/chunks/Ona8oofC.js.gz +0 -0
  265. package/build/client/_app/immutable/chunks/tjbnEgXP.js.br +0 -0
  266. package/build/client/_app/immutable/chunks/tjbnEgXP.js.gz +0 -0
  267. package/build/client/_app/immutable/entry/app.anqwe7ZL.js +0 -2
  268. package/build/client/_app/immutable/entry/app.anqwe7ZL.js.br +0 -0
  269. package/build/client/_app/immutable/entry/app.anqwe7ZL.js.gz +0 -0
  270. package/build/client/_app/immutable/entry/start.BV2VRv6h.js +0 -1
  271. package/build/client/_app/immutable/entry/start.BV2VRv6h.js.br +0 -2
  272. package/build/client/_app/immutable/entry/start.BV2VRv6h.js.gz +0 -0
  273. package/build/client/_app/immutable/nodes/0.D0i5MqcI.js +0 -1
  274. package/build/client/_app/immutable/nodes/0.D0i5MqcI.js.br +0 -0
  275. package/build/client/_app/immutable/nodes/0.D0i5MqcI.js.gz +0 -0
  276. package/build/client/_app/immutable/nodes/1.DN5n5c6F.js +0 -1
  277. package/build/client/_app/immutable/nodes/1.DN5n5c6F.js.br +0 -0
  278. package/build/client/_app/immutable/nodes/1.DN5n5c6F.js.gz +0 -0
  279. package/build/client/_app/immutable/nodes/2.BTjkNKZJ.js +0 -1
  280. package/build/client/_app/immutable/nodes/2.BTjkNKZJ.js.br +0 -0
  281. package/build/client/_app/immutable/nodes/2.BTjkNKZJ.js.gz +0 -0
  282. package/build/client/_app/immutable/nodes/3.CEkigHDv.js +0 -3
  283. package/build/client/_app/immutable/nodes/3.CEkigHDv.js.br +0 -0
  284. package/build/client/_app/immutable/nodes/3.CEkigHDv.js.gz +0 -0
  285. package/build/client/_app/immutable/nodes/4.B-8Zsc9k.js +0 -1
  286. package/build/client/_app/immutable/nodes/4.B-8Zsc9k.js.br +0 -0
  287. package/build/client/_app/immutable/nodes/4.B-8Zsc9k.js.gz +0 -0
  288. package/build/client/_app/immutable/nodes/5.shitbtHc.js +0 -1
  289. package/build/client/_app/immutable/nodes/5.shitbtHc.js.br +0 -0
  290. package/build/client/_app/immutable/nodes/5.shitbtHc.js.gz +0 -0
  291. package/build/client/_app/immutable/nodes/6.CB8Q6eH8.js +0 -2
  292. package/build/client/_app/immutable/nodes/6.CB8Q6eH8.js.br +0 -0
  293. package/build/client/_app/immutable/nodes/6.CB8Q6eH8.js.gz +0 -0
  294. package/build/client/_app/immutable/nodes/7.D0CrR6pl.js +0 -2
  295. package/build/client/_app/immutable/nodes/7.D0CrR6pl.js.br +0 -0
  296. package/build/client/_app/immutable/nodes/7.D0CrR6pl.js.gz +0 -0
  297. package/build/server/chunks/0-Cs1dzfRz.js +0 -9
  298. package/build/server/chunks/0-Cs1dzfRz.js.map +0 -1
  299. package/build/server/chunks/1-BCSX7oED.js +0 -9
  300. package/build/server/chunks/1-BCSX7oED.js.map +0 -1
  301. package/build/server/chunks/2-6gkeO8b4.js +0 -9
  302. package/build/server/chunks/2-6gkeO8b4.js.map +0 -1
  303. package/build/server/chunks/3-DH9J9Vsc.js +0 -9
  304. package/build/server/chunks/3-DH9J9Vsc.js.map +0 -1
  305. package/build/server/chunks/4-Rzy5TYjl.js +0 -9
  306. package/build/server/chunks/4-Rzy5TYjl.js.map +0 -1
  307. package/build/server/chunks/5-D0wB7nfE.js +0 -9
  308. package/build/server/chunks/6-DR2ABDPq.js +0 -9
  309. package/build/server/chunks/6-DR2ABDPq.js.map +0 -1
  310. package/build/server/chunks/7-D-oQJsia.js +0 -9
  311. package/build/server/chunks/7-D-oQJsia.js.map +0 -1
  312. package/build/server/chunks/EmptyState-Ci4pSpmY.js.map +0 -1
  313. package/build/server/chunks/_layout.svelte-CO4f8UD7.js.map +0 -1
  314. package/build/server/chunks/_page.svelte-3Cc3NMAP.js.map +0 -1
  315. package/build/server/chunks/_page.svelte-C0p3HsIW.js.map +0 -1
  316. package/build/server/chunks/_page.svelte-C6bns9aQ.js.map +0 -1
  317. package/build/server/chunks/_page.svelte-CFCONiDK.js.map +0 -1
  318. package/build/server/chunks/_page.svelte-DskND_G9.js.map +0 -1
  319. package/build/server/chunks/_page.svelte-rTrWmhOp.js.map +0 -1
  320. package/build/server/chunks/_server.ts-C8slOHB0.js.map +0 -1
  321. package/build/server/chunks/_server.ts-CS5H5klP.js.map +0 -1
  322. package/build/server/chunks/_server.ts-DhTrdlWH.js.map +0 -1
  323. package/build/server/chunks/_server.ts-DpEVfp8W.js.map +0 -1
  324. package/build/server/chunks/_server.ts-uHUi-4cd.js.map +0 -1
  325. package/build/server/chunks/client-BYT9c0ig.js +0 -7
  326. package/build/server/chunks/client-BYT9c0ig.js.map +0 -1
  327. package/build/server/chunks/client2-BjxIYuno.js.map +0 -1
  328. package/build/server/chunks/error.svelte-BG_yE-Wt.js +0 -27
  329. package/build/server/chunks/error.svelte-BG_yE-Wt.js.map +0 -1
  330. package/build/server/chunks/pty-manager-DR0Wt2Ac.js.map +0 -1
@@ -9,300 +9,400 @@
9
9
 
10
10
  import { browser } from '$app/environment';
11
11
  import { page } from '$app/state';
12
- import { EmptyState, getCached, setCache } from '$lib/modules/client/common';
12
+ import { getCached, setCache } from '$lib/modules/client/common';
13
13
  import ChatView from '$lib/modules/client/terminal/ChatView.svelte';
14
- import { Banner, Pill, Shimmer } from '@juspay/svelte-ui-components';
14
+ import { Shimmer } from '@juspay/svelte-ui-components';
15
15
  import { onMount } from 'svelte';
16
16
 
17
+ // --- State ---
18
+
17
19
  let session = $state<null | SessionInfo>(null);
18
20
  let messages = $state<ConversationMessage[]>([]);
19
21
  let loading = $state(true);
20
22
  let error = $state<null | string>(null);
21
-
22
- // Live streaming state
23
- let isSessionActive = $state(false);
24
- let connectionState = $state<'connected' | 'connecting' | 'disconnected' | 'idle'>('idle');
25
- let ws = $state<null | WebSocket>(null);
26
- let wsReconnectAttempts = 0;
27
- let wsReconnectTimer: null | number = null;
23
+ let hasMoreMessages = $state(false);
24
+ let loadingMore = $state(false);
25
+ let currentLimit = $state(200);
26
+
27
+ // Terminal + WS connection state
28
+ let connectedTerminalId = $state<null | string>(null);
29
+ let chatConnectionState = $state<'connected' | 'connecting' | 'disconnected' | 'idle' | 'reconnecting'>('idle');
30
+ // sendStatus:
31
+ // 'idle' = no terminal yet; input ready for first send
32
+ // 'connecting' = calling /api/sessions/connect
33
+ // 'resuming' = waiting for session history (claude loading)
34
+ // 'ready' = history received; input goes straight to PTY
35
+ let sendStatus = $state<'connecting' | 'idle' | 'ready' | 'resuming'>('idle');
36
+ let pendingMessage: null | string = null;
37
+
38
+ // Non-reactive WS refs
39
+ let terminalWs: null | WebSocket = null;
40
+ let sessionWs: null | WebSocket = null;
41
+ let sessionReconnectTimer: null | ReturnType<typeof setTimeout> = null;
28
42
  let disposed = false;
29
43
 
30
44
  const sessionId = $derived(page.params.id);
31
45
  const projectId = $derived(page.url.searchParams.get('project') || '');
32
46
 
33
- // Check if a session was modified within the last 5 minutes
34
- function checkSessionActive(sess: SessionInfo): boolean {
35
- const modifiedTime = new Date(sess.modified).getTime();
36
- const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
37
- return modifiedTime > fiveMinutesAgo;
38
- }
47
+ // Disable send button while a message is in-flight
48
+ const sendDisabled = $derived(sendStatus === 'connecting' || sendStatus === 'resuming');
39
49
 
40
- // Generate a unique message ID for WebSocket-delivered messages
41
- let wsMessageCounter = 0;
42
- function nextWsMessageId(): string {
43
- wsMessageCounter++;
44
- return `ws-msg-${Date.now()}-${wsMessageCounter}`;
50
+ // --- Helpers ---
51
+
52
+ function getConfig(): null | ShooterConfig {
53
+ try {
54
+ const saved = localStorage.getItem('shooter_config');
55
+ if (!saved) { return null; }
56
+ return JSON.parse(saved) as ShooterConfig;
57
+ } catch {
58
+ return null;
59
+ }
45
60
  }
46
61
 
47
- // Handle incoming WebSocket messages
48
- function handleWsMessage(event: MessageEvent): void {
49
- let data: {
50
- content?: MessagePart[];
51
- id?: string;
52
- input?: Record<string, unknown>;
53
- isError?: boolean;
54
- message?: string;
55
- name?: string;
56
- output?: string;
57
- role?: string;
58
- status?: string;
59
- text?: string;
60
- timestamp?: string;
61
- type: string;
62
- };
62
+ async function getWsTicket(): Promise<null | string> {
63
+ const config = getConfig();
64
+ if (!config) { return null; }
63
65
  try {
64
- data = JSON.parse(event.data);
66
+ const res = await fetch('/api/ws-ticket', {
67
+ headers: { Authorization: `Bearer ${config.apiKey}` },
68
+ method: 'POST',
69
+ });
70
+ if (!res.ok) { return null; }
71
+ const data: { ticket: string } = await res.json();
72
+ return data.ticket;
65
73
  } catch {
74
+ return null;
75
+ }
76
+ }
77
+
78
+ // --- Session WS (receive chat messages) ---
79
+
80
+ async function connectSessionWs(sessionWsPath: string, termId: string): Promise<void> {
81
+ if (disposed) { return; }
82
+
83
+ // Avoid stacking connections
84
+ if (
85
+ sessionWs &&
86
+ (sessionWs.readyState === WebSocket.OPEN || sessionWs.readyState === WebSocket.CONNECTING)
87
+ ) {
66
88
  return;
67
89
  }
68
90
 
69
- if (data.type === 'message') {
70
- // Full message (user or assistant)
71
- const newMsg: ConversationMessage = {
72
- id: nextWsMessageId(),
73
- parts: data.content || [],
74
- role: (data.role as 'assistant' | 'system' | 'user') || 'assistant',
75
- timestamp: data.timestamp || new Date().toISOString(),
76
- };
77
- messages = [...messages, newMsg];
78
- } else if (data.type === 'tool-use') {
79
- // Append tool_use block to the last assistant message
80
- const toolPart: ToolUsePart = {
81
- id: data.id || nextWsMessageId(),
82
- input: data.input || {},
83
- toolName: data.name || 'Unknown',
91
+ const ticket = await getWsTicket();
92
+ if (!ticket || disposed) { return; }
93
+
94
+ chatConnectionState = 'connecting';
95
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
96
+ const socket = new WebSocket(
97
+ `${protocol}//${window.location.host}${sessionWsPath}?ticket=${ticket}`
98
+ );
99
+
100
+ socket.onopen = () => {
101
+ if (disposed) { socket.close(); return; }
102
+ chatConnectionState = 'connected';
103
+ socket.send(JSON.stringify({ sessionId: termId, type: 'subscribe' }));
104
+ };
105
+
106
+ socket.onmessage = (event) => {
107
+ if (disposed) { return; }
108
+ try { handleSessionMessage(JSON.parse(event.data as string)); } catch { /* ignore */ }
109
+ };
110
+
111
+ socket.onclose = () => {
112
+ if (disposed) { return; }
113
+ chatConnectionState = 'disconnected';
114
+ sessionWs = null;
115
+ if (connectedTerminalId) {
116
+ if (sessionReconnectTimer) { clearTimeout(sessionReconnectTimer); }
117
+ sessionReconnectTimer = setTimeout(() => {
118
+ sessionReconnectTimer = null;
119
+ if (!disposed && connectedTerminalId) {
120
+ void connectSessionWs(`/ws/session/${connectedTerminalId}`, connectedTerminalId);
121
+ }
122
+ }, 2000);
123
+ }
124
+ };
125
+
126
+ socket.onerror = () => {
127
+ if (!disposed) { chatConnectionState = 'disconnected'; }
128
+ };
129
+
130
+ sessionWs = socket;
131
+ }
132
+
133
+ // --- Terminal WS (send PTY input only) ---
134
+
135
+ async function connectTerminalWs(wsPath: string): Promise<void> {
136
+ if (disposed) { return; }
137
+
138
+ const ticket = await getWsTicket();
139
+ if (!ticket || disposed) { return; }
140
+
141
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
142
+ const ws = new WebSocket(`${protocol}//${window.location.host}${wsPath}?ticket=${ticket}`);
143
+
144
+ ws.onopen = () => {
145
+ if (disposed) { ws.close(); return; }
146
+ terminalWs = ws;
147
+ if (sendStatus === 'ready' && pendingMessage) {
148
+ ws.send(JSON.stringify({ data: `${pendingMessage}\n`, type: 'input' }));
149
+ pendingMessage = null;
150
+ }
151
+ };
152
+
153
+ ws.onclose = () => {
154
+ if (terminalWs === ws) { terminalWs = null; }
155
+ };
156
+ }
157
+
158
+ // --- Message handler (mirrors terminal page) ---
159
+
160
+ function handleSessionMessage(msg: Record<string, unknown>): void {
161
+ if (msg.type === 'history') {
162
+ const historyRaw = (msg.messages || []) as {
163
+ content: MessagePart[];
164
+ id: string;
165
+ role: string;
166
+ timestamp: string;
167
+ }[];
168
+ messages = historyRaw.map((m) => ({
169
+ id: m.id,
170
+ parts: m.content,
171
+ role: m.role as ConversationMessage['role'],
172
+ timestamp: m.timestamp,
173
+ }));
174
+ // WS history is always complete
175
+ hasMoreMessages = false;
176
+
177
+ if (sendStatus === 'resuming') {
178
+ sendStatus = 'ready';
179
+ if (pendingMessage && terminalWs?.readyState === WebSocket.OPEN) {
180
+ terminalWs.send(JSON.stringify({ data: `${pendingMessage}\n`, type: 'input' }));
181
+ pendingMessage = null;
182
+ }
183
+ }
184
+ } else if (msg.type === 'message') {
185
+ messages = [
186
+ ...messages,
187
+ {
188
+ id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
189
+ parts: (msg.content as MessagePart[]) || [],
190
+ role: (msg.role as ConversationMessage['role']) || 'assistant',
191
+ timestamp: (msg.timestamp as string) || new Date().toISOString(),
192
+ },
193
+ ];
194
+ } else if (msg.type === 'tool-use') {
195
+ const toolUsePart: ToolUsePart = {
196
+ id: (msg.id as string) || `tool-${Date.now()}`,
197
+ input: (msg.input as Record<string, unknown>) || {},
198
+ toolName: msg.name as string,
84
199
  type: 'tool_use',
85
200
  };
86
- appendToLastAssistant(toolPart);
87
- } else if (data.type === 'tool-result') {
88
- // Append a system message with the tool result
89
- const resultMsg: ConversationMessage = {
90
- id: nextWsMessageId(),
91
- parts: [
201
+ const lastToolMsg = messages.length > 0 ? messages[messages.length - 1] : null;
202
+ if (lastToolMsg?.role === 'assistant') {
203
+ messages = [
204
+ ...messages.slice(0, -1),
205
+ { ...lastToolMsg, parts: [...lastToolMsg.parts, toolUsePart] },
206
+ ];
207
+ } else {
208
+ messages = [
209
+ ...messages,
92
210
  {
93
- isError: data.isError || false,
94
- output: data.output || '',
95
- toolUseId: data.id || '',
96
- type: 'tool_result',
211
+ id: `tool-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
212
+ parts: [toolUsePart],
213
+ role: 'assistant',
214
+ timestamp: new Date().toISOString(),
97
215
  },
98
- ],
99
- role: 'system',
100
- timestamp: new Date().toISOString(),
101
- };
102
- messages = [...messages, resultMsg];
103
- } else if (data.type === 'thinking') {
104
- // Append thinking block to the last assistant message
105
- const thinkPart: MessagePart = {
106
- content: data.text || '',
107
- type: 'thinking',
108
- };
109
- appendToLastAssistant(thinkPart);
110
- } else if (data.type === 'error') {
111
- // Server-sent error -- display in the UI
112
- const errorMsg: ConversationMessage = {
113
- id: nextWsMessageId(),
114
- parts: [{ content: data.text || data.message || 'Unknown error', type: 'text' }],
115
- role: 'system',
116
- timestamp: new Date().toISOString(),
216
+ ];
217
+ }
218
+ } else if (msg.type === 'tool-result') {
219
+ const toolResultPart: MessagePart = {
220
+ isError: (msg.isError as boolean) || false,
221
+ output: (msg.output as string) || '',
222
+ toolUseId: msg.id as string,
223
+ type: 'tool_result',
117
224
  };
118
- messages = [...messages, errorMsg];
119
- } else if (data.type === 'permission-requested') {
120
- // Permission request -- log for now (full UI integration would go here)
121
- console.log('[session] Permission requested:', data);
122
- } else if (data.type === 'session-end') {
123
- // Session has ended -- mark as inactive
124
- isSessionActive = false;
125
- connectionState = 'disconnected';
126
- if (ws) {
127
- ws.close();
128
- ws = null;
225
+ const lastResultMsg = messages.length > 0 ? messages[messages.length - 1] : null;
226
+ if (lastResultMsg?.role === 'system') {
227
+ messages = [
228
+ ...messages.slice(0, -1),
229
+ { ...lastResultMsg, parts: [...lastResultMsg.parts, toolResultPart] },
230
+ ];
231
+ } else {
232
+ messages = [
233
+ ...messages,
234
+ {
235
+ id: `result-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
236
+ parts: [toolResultPart],
237
+ role: 'system',
238
+ timestamp: new Date().toISOString(),
239
+ },
240
+ ];
241
+ }
242
+ } else if (msg.type === 'thinking') {
243
+ const thinkPart: MessagePart = { content: (msg.text as string) || '', type: 'thinking' };
244
+ const lastMsg = messages.length > 0 ? messages[messages.length - 1] : null;
245
+ if (lastMsg?.role === 'assistant') {
246
+ messages = [
247
+ ...messages.slice(0, -1),
248
+ { ...lastMsg, parts: [...lastMsg.parts, thinkPart] },
249
+ ];
250
+ } else {
251
+ messages = [
252
+ ...messages,
253
+ {
254
+ id: `think-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
255
+ parts: [thinkPart],
256
+ role: 'assistant',
257
+ timestamp: new Date().toISOString(),
258
+ },
259
+ ];
129
260
  }
261
+ } else if (msg.type === 'session-end') {
262
+ sendStatus = 'idle';
130
263
  }
131
264
  }
132
265
 
133
- // Append a part to the last assistant message, or create one
134
- function appendToLastAssistant(part: MessagePart): void {
135
- const lastMsg = messages.length > 0 ? messages[messages.length - 1] : null;
136
- if (lastMsg?.role === 'assistant') {
137
- // Mutate the parts array and reassign to trigger reactivity
138
- const updatedParts = [...lastMsg.parts, part];
139
- const updatedMsg = { ...lastMsg, parts: updatedParts };
140
- messages = [...messages.slice(0, -1), updatedMsg];
141
- } else {
142
- // Create a new assistant message to hold this part
143
- const newMsg: ConversationMessage = {
144
- id: nextWsMessageId(),
145
- parts: [part],
146
- role: 'assistant',
147
- timestamp: new Date().toISOString(),
148
- };
149
- messages = [...messages, newMsg];
150
- }
151
- }
266
+ // --- Connect + send flow ---
152
267
 
153
- // Connect to the live session WebSocket
154
- async function connectWebSocket(): Promise<void> {
155
- if (!browser || !isSessionActive || disposed) {
156
- return;
157
- }
268
+ async function connectAndSend(): Promise<void> {
269
+ if (!session || disposed) { sendStatus = 'idle'; return; }
158
270
 
159
- const saved = localStorage.getItem('shooter_config');
160
- if (!saved) {
161
- return;
162
- }
163
- let config: ShooterConfig;
164
- try {
165
- config = JSON.parse(saved) as ShooterConfig;
166
- } catch {
167
- return;
168
- }
271
+ const config = getConfig();
272
+ if (!config) { sendStatus = 'idle'; return; }
169
273
 
170
- connectionState = 'connecting';
274
+ sendStatus = 'connecting';
171
275
 
172
276
  try {
173
- // Obtain a short-lived ticket
174
- const ticketRes = await fetch('/api/ws-ticket', {
175
- headers: { Authorization: `Bearer ${config.apiKey}` },
277
+ const command = session.source === 'opencode' ? 'opencode' : 'claude';
278
+ const res = await fetch('/api/sessions/connect', {
279
+ body: JSON.stringify({ command, cwd: session.projectPath, sessionId }),
280
+ headers: {
281
+ Authorization: `Bearer ${config.apiKey}`,
282
+ 'Content-Type': 'application/json',
283
+ },
176
284
  method: 'POST',
177
285
  });
178
- if (!ticketRes.ok || disposed) {
179
- connectionState = 'disconnected';
180
- // Schedule retry with same backoff logic as onclose
181
- if (isSessionActive && !disposed && wsReconnectAttempts < 5) {
182
- wsReconnectAttempts++;
183
- wsReconnectTimer = window.setTimeout(() => {
184
- void connectWebSocket();
185
- }, 2000);
186
- }
187
- return;
188
- }
189
- const { ticket } = await ticketRes.json();
190
-
191
- if (disposed) {
192
- connectionState = 'disconnected';
193
- return;
194
- }
195
-
196
- // Build WebSocket URL
197
- const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
198
- const wsUrl = `${wsProtocol}//${window.location.host}/ws/session/${sessionId}?ticket=${ticket}`;
199
-
200
- const socket = new WebSocket(wsUrl);
201
286
 
202
- socket.onopen = () => {
203
- if (disposed) {
204
- socket.close();
205
- return;
206
- }
207
- connectionState = 'connected';
208
- ws = socket;
209
- wsReconnectAttempts = 0; // Reset on successful connection
210
- };
287
+ if (!res.ok || disposed) { sendStatus = 'idle'; return; }
211
288
 
212
- socket.onmessage = handleWsMessage;
213
-
214
- socket.onclose = () => {
215
- connectionState = 'disconnected';
216
- ws = null;
217
- // Reconnect with backoff (max 5 retries)
218
- if (isSessionActive && !disposed && wsReconnectAttempts < 5) {
219
- wsReconnectAttempts++;
220
- wsReconnectTimer = window.setTimeout(() => {
221
- void connectWebSocket();
222
- }, 2000);
223
- }
224
- };
289
+ const result: { sessionWs: string; terminalId: string; ws: string } = await res.json();
290
+ connectedTerminalId = result.terminalId;
291
+ sendStatus = 'resuming';
225
292
 
226
- socket.onerror = () => {
227
- connectionState = 'disconnected';
228
- ws = null;
229
- };
293
+ void connectSessionWs(result.sessionWs, result.terminalId);
294
+ void connectTerminalWs(result.ws);
230
295
  } catch {
231
- connectionState = 'disconnected';
296
+ sendStatus = 'idle';
232
297
  }
233
298
  }
234
299
 
235
- function formatDate(ts: string): string {
236
- return new Date(ts).toLocaleDateString([], {
237
- day: 'numeric',
238
- month: 'short',
239
- year: 'numeric',
240
- });
241
- }
300
+ // --- Called by ChatView on submit ---
242
301
 
243
- function shortPath(p: string): string {
244
- const parts = p.split('/');
245
- return parts.slice(-2).join('/');
246
- }
302
+ function sendMessage(text: string): void {
303
+ if (!text.trim()) { return; }
247
304
 
248
- async function fetchSession(): Promise<void> {
249
- if (!browser) {
305
+ if (sendStatus === 'ready' && terminalWs?.readyState === WebSocket.OPEN) {
306
+ terminalWs.send(JSON.stringify({ data: `${text}\n`, type: 'input' }));
250
307
  return;
251
308
  }
252
309
 
253
- // Don't show loading spinner if we already have cached data
254
- if (messages.length === 0) {
255
- loading = true;
310
+ // Already connecting just update queued message
311
+ if (sendStatus === 'connecting' || sendStatus === 'resuming') {
312
+ pendingMessage = text;
313
+ return;
256
314
  }
257
315
 
316
+ // Not connected yet — queue and connect
317
+ pendingMessage = text;
318
+ void connectAndSend();
319
+ }
320
+
321
+ // --- REST fetch (initial messages) ---
322
+
323
+ async function fetchSession(): Promise<void> {
324
+ if (!browser) { return; }
325
+ if (messages.length === 0) { loading = true; }
326
+
258
327
  try {
259
- const saved = localStorage.getItem('shooter_config');
260
- if (!saved) {
328
+ const config = getConfig();
329
+ if (!config) {
261
330
  error = 'No configuration found. Please configure settings first.';
262
331
  loading = false;
263
332
  return;
264
333
  }
265
- let config: ShooterConfig;
266
- try {
267
- config = JSON.parse(saved) as ShooterConfig;
268
- } catch {
269
- error = 'Invalid saved configuration. Please reconfigure in settings.';
270
- loading = false;
271
- return;
272
- }
273
334
 
274
335
  const sid = sessionId || '';
275
336
  const pid = projectId || '';
276
337
  const queryStr = pid
277
- ? `id=${encodeURIComponent(sid)}&project=${encodeURIComponent(pid)}`
278
- : `id=${encodeURIComponent(sid)}`;
279
- const res = await fetch(`/api/sessions?${queryStr}`, {
338
+ ? `id=${encodeURIComponent(sid)}&project=${encodeURIComponent(pid)}&limit=${currentLimit}`
339
+ : `id=${encodeURIComponent(sid)}&limit=${currentLimit}`;
340
+
341
+ let res = await fetch(`/api/sessions?${queryStr}`, {
280
342
  headers: { Authorization: `Bearer ${config.apiKey}` },
281
343
  });
282
- if (!res.ok) {
283
- error = 'Session not found';
284
- loading = false;
285
- return;
344
+
345
+ // Fallback: search across all projects if not found
346
+ if (res.status === 404 && !pid) {
347
+ const projectsRes = await fetch('/api/sessions', {
348
+ headers: { Authorization: `Bearer ${config.apiKey}` },
349
+ });
350
+ if (projectsRes.ok) {
351
+ const projectsData: { projects?: { id: string; sessions?: { id: string }[] }[] } =
352
+ await projectsRes.json();
353
+ for (const project of projectsData.projects || []) {
354
+ if ((project.sessions || []).find((s) => s.id === sid)) {
355
+ res = await fetch(
356
+ `/api/sessions?id=${encodeURIComponent(sid)}&project=${encodeURIComponent(project.id)}`,
357
+ { headers: { Authorization: `Bearer ${config.apiKey}` } }
358
+ );
359
+ break;
360
+ }
361
+ }
362
+ }
286
363
  }
364
+
365
+ if (!res.ok) { error = 'Session not found'; loading = false; return; }
366
+
287
367
  const data: SessionViewResponse = await res.json();
288
368
  session = data.session as SessionInfo;
289
- const rawMessages: unknown[] = Array.isArray(data.messages) ? data.messages : [];
290
- messages = [...(rawMessages as ConversationMessage[])].reverse();
369
+ const rawMessages = Array.isArray(data.messages) ? (data.messages as unknown as ConversationMessage[]) : [];
370
+ messages = rawMessages;
371
+ hasMoreMessages = rawMessages.length >= currentLimit;
291
372
  setCache(`shooter_session_${sid}`, { messages: rawMessages, session: data.session });
292
-
293
- // Check if session is active and connect WebSocket if so
294
- if (data.session) {
295
- isSessionActive = checkSessionActive(data.session as SessionInfo);
296
- if (isSessionActive) {
297
- void connectWebSocket();
298
- }
299
- }
300
373
  } catch {
301
374
  error = 'Failed to load session';
302
375
  }
303
376
  loading = false;
304
377
  }
305
378
 
379
+ async function loadEarlierMessages(): Promise<void> {
380
+ if (!browser || loadingMore) { return; }
381
+ const config = getConfig();
382
+ if (!config) { return; }
383
+
384
+ loadingMore = true;
385
+ try {
386
+ const newLimit = currentLimit + 200;
387
+ const sid = sessionId || '';
388
+ const pid = projectId || '';
389
+ const queryParts = [`id=${encodeURIComponent(sid)}`, `limit=${newLimit}`];
390
+ if (pid) { queryParts.push(`project=${encodeURIComponent(pid)}`); }
391
+ const res = await fetch(`/api/sessions?${queryParts.join('&')}`, {
392
+ headers: { Authorization: `Bearer ${config.apiKey}` },
393
+ });
394
+ if (!res.ok) { return; }
395
+ const data: SessionViewResponse = await res.json();
396
+ const rawMessages = Array.isArray(data.messages) ? (data.messages as unknown as ConversationMessage[]) : [];
397
+ messages = rawMessages;
398
+ currentLimit = newLimit;
399
+ hasMoreMessages = rawMessages.length >= newLimit;
400
+ setCache(`shooter_session_${sid}`, { messages: rawMessages, session: data.session });
401
+ } catch { /* best effort */ } finally { loadingMore = false; }
402
+ }
403
+
404
+ // --- Lifecycle ---
405
+
306
406
  onMount(() => {
307
407
  // Show cached data immediately
308
408
  const cached = getCached(`shooter_session_${page.params.id}`) as null | {
@@ -311,23 +411,49 @@
311
411
  };
312
412
  if (cached) {
313
413
  session = cached.session;
314
- messages = Array.isArray(cached.messages) ? [...cached.messages].reverse() : [];
414
+ messages = Array.isArray(cached.messages) ? cached.messages : [];
315
415
  loading = false;
316
416
  }
317
417
 
318
- void fetchSession();
418
+ void fetchSession().then(async () => {
419
+ if (!session || disposed) { return; }
420
+
421
+ // Check if there's already a running terminal for this session
422
+ const config = getConfig();
423
+ if (!config) { return; }
424
+
425
+ try {
426
+ const command = session.source === 'opencode' ? 'opencode' : 'claude';
427
+ const res = await fetch('/api/sessions/connect', {
428
+ body: JSON.stringify({
429
+ command,
430
+ cwd: session.projectPath,
431
+ noCreate: true,
432
+ sessionId,
433
+ }),
434
+ headers: {
435
+ Authorization: `Bearer ${config.apiKey}`,
436
+ 'Content-Type': 'application/json',
437
+ },
438
+ method: 'POST',
439
+ });
440
+
441
+ if (res.ok && !disposed) {
442
+ const result: { sessionWs: string; terminalId: string; ws: string } = await res.json();
443
+ connectedTerminalId = result.terminalId;
444
+ sendStatus = 'resuming';
445
+ void connectSessionWs(result.sessionWs, result.terminalId);
446
+ void connectTerminalWs(result.ws);
447
+ }
448
+ // 404 = no existing terminal, that's fine
449
+ } catch { /* ignore */ }
450
+ });
319
451
 
320
- // Cleanup WebSocket and reconnect timer on component destroy
321
452
  return () => {
322
453
  disposed = true;
323
- if (wsReconnectTimer) {
324
- clearTimeout(wsReconnectTimer);
325
- wsReconnectTimer = null;
326
- }
327
- if (ws) {
328
- ws.close();
329
- ws = null;
330
- }
454
+ if (sessionReconnectTimer) { clearTimeout(sessionReconnectTimer); }
455
+ sessionWs?.close();
456
+ terminalWs?.close();
331
457
  };
332
458
  });
333
459
  </script>
@@ -346,107 +472,159 @@
346
472
  <Shimmer classes="shimmer-bubble shimmer-bubble-assistant" />
347
473
  <Shimmer classes="shimmer-bubble shimmer-bubble-user-short" />
348
474
  <Shimmer classes="shimmer-bubble shimmer-bubble-assistant-wide" />
349
- <Shimmer classes="shimmer-bubble shimmer-bubble-assistant-short" />
350
475
  </div>
351
476
  </div>
352
477
  {:else if error}
353
478
  <div class="session-back-row">
354
- <a href="/" class="back-link">
355
- <span class="back-arrow">&larr;</span>
356
- Back
357
- </a>
479
+ <a href={projectId ? `/project?id=${projectId}` : '/'} class="back-link">← Back</a>
358
480
  </div>
359
- <EmptyState icon="alert-triangle" title="Error" description={error} />
481
+ <p class="error-text">{error}</p>
360
482
  {:else if session}
361
- <!-- Session Header -->
362
- <div class="chat-session-header">
363
- <div class="chat-session-header-top">
364
- <a href="/project?id={projectId}" class="back-link">&#8592; Back to Project</a>
365
- {#if isSessionActive || connectionState === 'connected' || connectionState === 'connecting'}
366
- <div class="live-connection-row">
367
- {#if connectionState === 'connected'}
368
- <span class="connection-status connected">
369
- <span class="connection-status-dot"></span>
370
- </span>
371
- {:else if connectionState === 'connecting'}
372
- <span class="connection-status reconnecting">
373
- <span class="connection-status-dot"></span>
374
- </span>
375
- {:else if connectionState === 'disconnected'}
376
- <span class="connection-status disconnected">
377
- <span class="connection-status-dot"></span>
378
- </span>
379
- {/if}
380
- </div>
381
- {/if}
382
- </div>
383
- <div class="chat-session-title-row">
384
- <h1 class="chat-session-title">{session.title}</h1>
385
- {#if isSessionActive}
386
- <Pill text="LIVE" classes="pill-live" />
483
+ <!-- Compact header -->
484
+ <div class="session-header">
485
+ <div class="session-header-row">
486
+ <a href={projectId ? `/project?id=${projectId}` : '/'} class="back-link">← Back</a>
487
+ {#if sendStatus === 'connecting'}
488
+ <span class="resume-status">Connecting...</span>
489
+ {:else if sendStatus === 'resuming'}
490
+ <span class="resume-status">Resuming session...</span>
491
+ {:else if chatConnectionState === 'connected'}
492
+ <span class="connection-dot connected"></span>
493
+ {:else if chatConnectionState === 'reconnecting'}
494
+ <span class="connection-dot reconnecting"></span>
387
495
  {/if}
388
496
  </div>
389
- <div class="chat-session-meta">
390
- <span class="chat-session-meta-item">&#128193; {shortPath(session.projectPath)}</span>
391
- <span class="chat-session-meta-item">&#127807; {session.gitBranch}</span>
392
- <span class="chat-session-meta-item">&#128172; {session.messageCount} messages</span>
393
- <span class="chat-session-meta-item">&#128197; {formatDate(session.created)}</span>
394
- </div>
395
- {#if connectionState === 'disconnected' && isSessionActive}
396
- <Banner text="Live updates paused" classes="banner-warning" />
397
- {/if}
497
+ <h1 class="session-title">{session.title}</h1>
398
498
  </div>
399
499
 
400
- <!-- Chat Container -->
500
+ <!-- Chat -->
401
501
  <div class="session-chat-container">
402
- <ChatView {messages} {connectionState} showInput={false} sessionEnded={!isSessionActive} />
502
+ {#if hasMoreMessages}
503
+ <div class="load-earlier-row">
504
+ <button
505
+ class="load-earlier-btn"
506
+ onclick={loadEarlierMessages}
507
+ disabled={loadingMore}
508
+ >
509
+ {loadingMore ? 'Loading...' : 'Load earlier messages'}
510
+ </button>
511
+ </div>
512
+ {/if}
513
+ <ChatView
514
+ messages={[...messages].reverse()}
515
+ newestFirst={true}
516
+ connectionState={chatConnectionState}
517
+ showInput={true}
518
+ {sendDisabled}
519
+ onSendInput={sendMessage}
520
+ sessionEnded={false}
521
+ />
403
522
  </div>
404
523
  {/if}
405
524
  </main>
406
525
 
407
526
  <style>
408
- /* Make the main element a flex column so the chat fills remaining space */
409
527
  .session-page-main {
410
528
  display: flex;
411
529
  flex-direction: column;
412
530
  overflow: hidden;
413
531
  }
414
532
 
415
- /* Chat container fills remaining space */
416
- .session-chat-container {
417
- flex: 1;
418
- min-height: 0;
419
- display: flex;
420
- flex-direction: column;
421
- }
422
-
423
- /* Back link (used in error state) */
424
533
  .session-back-row {
425
534
  margin-bottom: var(--space-5);
426
535
  }
427
536
 
428
- /* Header top row: back link + connection status */
429
- .chat-session-header-top {
537
+ .error-text {
538
+ color: var(--text-secondary);
539
+ padding: var(--space-4);
540
+ }
541
+
542
+ /* Compact header */
543
+ .session-header {
544
+ padding: var(--space-3) var(--space-4);
545
+ border-bottom: 1px solid var(--border);
546
+ flex-shrink: 0;
547
+ }
548
+
549
+ .session-header-row {
430
550
  display: flex;
431
551
  align-items: center;
432
552
  justify-content: space-between;
553
+ margin-bottom: var(--space-1);
554
+ }
555
+
556
+ .session-title {
557
+ font-size: var(--text-base);
558
+ font-weight: 600;
559
+ color: var(--text-primary);
560
+ margin: 0;
561
+ white-space: nowrap;
562
+ overflow: hidden;
563
+ text-overflow: ellipsis;
564
+ }
565
+
566
+ .resume-status {
567
+ font-size: 0.75rem;
568
+ color: var(--text-tertiary, #888);
569
+ animation: pulse 1.5s ease-in-out infinite;
570
+ }
571
+
572
+ @keyframes pulse {
573
+ 0%, 100% { opacity: 1; }
574
+ 50% { opacity: 0.4; }
575
+ }
576
+
577
+ .connection-dot {
578
+ display: inline-block;
579
+ width: 7px;
580
+ height: 7px;
581
+ border-radius: 50%;
582
+ flex-shrink: 0;
433
583
  }
434
584
 
435
- /* Title row: title + LIVE badge */
436
- .chat-session-title-row {
585
+ .connection-dot.connected {
586
+ background: #4ade80;
587
+ }
588
+
589
+ .connection-dot.reconnecting {
590
+ background: #f59e0b;
591
+ animation: pulse 1s ease-in-out infinite;
592
+ }
593
+
594
+ /* Chat container fills remaining space */
595
+ .session-chat-container {
596
+ flex: 1;
597
+ min-height: 0;
437
598
  display: flex;
438
- align-items: center;
439
- gap: 0.5rem;
599
+ flex-direction: column;
440
600
  }
441
601
 
442
- /* Connection status row */
443
- .live-connection-row {
602
+ /* Load earlier row */
603
+ .load-earlier-row {
444
604
  display: flex;
445
- align-items: center;
605
+ justify-content: center;
606
+ padding: var(--space-2) var(--space-3);
607
+ flex-shrink: 0;
608
+ border-bottom: 1px solid var(--border);
609
+ }
610
+
611
+ .load-earlier-btn {
612
+ font-size: 0.8rem;
613
+ color: var(--text-secondary);
614
+ background: none;
615
+ border: 1px solid var(--border);
616
+ border-radius: var(--radius-sm, 4px);
617
+ padding: 4px 12px;
618
+ cursor: pointer;
619
+ }
620
+
621
+ .load-earlier-btn:disabled {
622
+ opacity: 0.5;
623
+ cursor: not-allowed;
446
624
  }
447
625
 
448
- /* Paused banner override */
449
- :global(.banner-warning) {
450
- margin-top: 0.5rem;
626
+ .load-earlier-btn:hover:not(:disabled) {
627
+ color: var(--text-primary);
628
+ border-color: var(--text-tertiary, #888);
451
629
  }
452
630
  </style>