@juspay/shooter 1.20.0 → 1.22.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 (275) hide show
  1. package/.claude/hooks/notifier.cjs +94 -1
  2. package/build/client/_app/immutable/assets/2.JWRrnR-w.css +1 -0
  3. package/build/client/_app/immutable/assets/2.JWRrnR-w.css.br +0 -0
  4. package/build/client/_app/immutable/assets/2.JWRrnR-w.css.gz +0 -0
  5. package/build/client/_app/immutable/chunks/{ZS5XYDx_.js → B1bOvemT.js} +1 -1
  6. package/build/client/_app/immutable/chunks/B1bOvemT.js.br +0 -0
  7. package/build/client/_app/immutable/chunks/{ZS5XYDx_.js.gz → B1bOvemT.js.gz} +0 -0
  8. package/build/client/_app/immutable/chunks/BfbPKMXz.js +3 -0
  9. package/build/client/_app/immutable/chunks/BfbPKMXz.js.br +0 -0
  10. package/build/client/_app/immutable/chunks/BfbPKMXz.js.gz +0 -0
  11. package/build/client/_app/immutable/chunks/C4Hns_Wl.js +1 -0
  12. package/build/client/_app/immutable/chunks/C4Hns_Wl.js.br +0 -0
  13. package/build/client/_app/immutable/chunks/C4Hns_Wl.js.gz +0 -0
  14. package/build/client/_app/immutable/chunks/C87ZRWX0.js +1 -0
  15. package/build/client/_app/immutable/chunks/C87ZRWX0.js.br +0 -0
  16. package/build/client/_app/immutable/chunks/C87ZRWX0.js.gz +0 -0
  17. package/build/client/_app/immutable/chunks/{BvmdJful.js → CJulw9ux.js} +1 -1
  18. package/build/client/_app/immutable/chunks/CJulw9ux.js.br +0 -0
  19. package/build/client/_app/immutable/chunks/CJulw9ux.js.gz +0 -0
  20. package/build/client/_app/immutable/chunks/{DT4H19pV.js → CZg4kn4E.js} +1 -1
  21. package/build/client/_app/immutable/chunks/CZg4kn4E.js.br +0 -0
  22. package/build/client/_app/immutable/chunks/CZg4kn4E.js.gz +0 -0
  23. package/build/client/_app/immutable/chunks/{DIZ3Qst5.js → DhK7PwI_.js} +1 -1
  24. package/build/client/_app/immutable/chunks/DhK7PwI_.js.br +0 -0
  25. package/build/client/_app/immutable/chunks/{DIZ3Qst5.js.gz → DhK7PwI_.js.gz} +0 -0
  26. package/build/client/_app/immutable/chunks/{ClIPTXf3.js → DomZZqvG.js} +1 -1
  27. package/build/client/_app/immutable/chunks/DomZZqvG.js.br +0 -0
  28. package/build/client/_app/immutable/chunks/DomZZqvG.js.gz +0 -0
  29. package/build/client/_app/immutable/chunks/{BB2l8o4X.js → i5iZvmIH.js} +1 -1
  30. package/build/client/_app/immutable/chunks/i5iZvmIH.js.br +0 -0
  31. package/build/client/_app/immutable/chunks/i5iZvmIH.js.gz +0 -0
  32. package/build/client/_app/immutable/entry/{app.Bd-DfeJi.js → app.CTqz33nP.js} +2 -2
  33. package/build/client/_app/immutable/entry/app.CTqz33nP.js.br +0 -0
  34. package/build/client/_app/immutable/entry/app.CTqz33nP.js.gz +0 -0
  35. package/build/client/_app/immutable/entry/start.Dj-Kvgwo.js +1 -0
  36. package/build/client/_app/immutable/entry/start.Dj-Kvgwo.js.br +2 -0
  37. package/build/client/_app/immutable/entry/start.Dj-Kvgwo.js.gz +0 -0
  38. package/build/client/_app/immutable/nodes/0.Qn7Ktiht.js +10 -0
  39. package/build/client/_app/immutable/nodes/0.Qn7Ktiht.js.br +0 -0
  40. package/build/client/_app/immutable/nodes/0.Qn7Ktiht.js.gz +0 -0
  41. package/build/client/_app/immutable/nodes/{1.DT4dq6Ay.js → 1.BxWOfNlo.js} +1 -1
  42. package/build/client/_app/immutable/nodes/1.BxWOfNlo.js.br +0 -0
  43. package/build/client/_app/immutable/nodes/1.BxWOfNlo.js.gz +0 -0
  44. package/build/client/_app/immutable/nodes/{10.CF7RGXpe.js → 10.BGPYD1s1.js} +1 -1
  45. package/build/client/_app/immutable/nodes/10.BGPYD1s1.js.br +0 -0
  46. package/build/client/_app/immutable/nodes/10.BGPYD1s1.js.gz +0 -0
  47. package/build/client/_app/immutable/nodes/{11.BV_G7yLI.js → 11.BxY1PUjC.js} +1 -1
  48. package/build/client/_app/immutable/nodes/11.BxY1PUjC.js.br +0 -0
  49. package/build/client/_app/immutable/nodes/11.BxY1PUjC.js.gz +0 -0
  50. package/build/client/_app/immutable/nodes/2.Bc2qALkX.js +23 -0
  51. package/build/client/_app/immutable/nodes/2.Bc2qALkX.js.br +0 -0
  52. package/build/client/_app/immutable/nodes/2.Bc2qALkX.js.gz +0 -0
  53. package/build/client/_app/immutable/nodes/{3.0MMe3oxR.js → 3.N2-A8noI.js} +1 -1
  54. package/build/client/_app/immutable/nodes/3.N2-A8noI.js.br +0 -0
  55. package/build/client/_app/immutable/nodes/3.N2-A8noI.js.gz +0 -0
  56. package/build/client/_app/immutable/nodes/{4.CBX9A3ka.js → 4.BFYS2g9C.js} +3 -3
  57. package/build/client/_app/immutable/nodes/4.BFYS2g9C.js.br +0 -0
  58. package/build/client/_app/immutable/nodes/4.BFYS2g9C.js.gz +0 -0
  59. package/build/client/_app/immutable/nodes/{5.DIVKuZc9.js → 5.DziEu9rx.js} +1 -1
  60. package/build/client/_app/immutable/nodes/5.DziEu9rx.js.br +0 -0
  61. package/build/client/_app/immutable/nodes/5.DziEu9rx.js.gz +0 -0
  62. package/build/client/_app/immutable/nodes/{6.ComiWlV6.js → 6.BWF9Qx6F.js} +1 -1
  63. package/build/client/_app/immutable/nodes/6.BWF9Qx6F.js.br +0 -0
  64. package/build/client/_app/immutable/nodes/6.BWF9Qx6F.js.gz +0 -0
  65. package/build/client/_app/immutable/nodes/{7.vkPx1kVP.js → 7.DHuDIdpz.js} +1 -1
  66. package/build/client/_app/immutable/nodes/7.DHuDIdpz.js.br +0 -0
  67. package/build/client/_app/immutable/nodes/7.DHuDIdpz.js.gz +0 -0
  68. package/build/client/_app/immutable/nodes/{8.Bmr3sWbS.js → 8.D0Ijt9Vv.js} +1 -1
  69. package/build/client/_app/immutable/nodes/8.D0Ijt9Vv.js.br +0 -0
  70. package/build/client/_app/immutable/nodes/8.D0Ijt9Vv.js.gz +0 -0
  71. package/build/client/_app/immutable/nodes/{9.CAJucyeI.js → 9.2Piwo35J.js} +1 -1
  72. package/build/client/_app/immutable/nodes/9.2Piwo35J.js.br +0 -0
  73. package/build/client/_app/immutable/nodes/9.2Piwo35J.js.gz +0 -0
  74. package/build/client/_app/version.json +1 -1
  75. package/build/client/_app/version.json.br +0 -0
  76. package/build/client/_app/version.json.gz +0 -0
  77. package/build/pty-holder.cjs +6 -0
  78. package/build/server/chunks/{0-DDGB6CRT.js → 0-CVGsyVKN.js} +4 -2
  79. package/build/server/chunks/0-CVGsyVKN.js.map +1 -0
  80. package/build/server/chunks/{1-DEjonQXD.js → 1-BAlAsKdp.js} +2 -2
  81. package/build/server/chunks/{1-DEjonQXD.js.map → 1-BAlAsKdp.js.map} +1 -1
  82. package/build/server/chunks/{10-BK1kiiiw.js → 10-BUCX7Aqz.js} +2 -2
  83. package/build/server/chunks/{10-BK1kiiiw.js.map → 10-BUCX7Aqz.js.map} +1 -1
  84. package/build/server/chunks/{11-CJPjkEF3.js → 11-DHPvc2yA.js} +2 -2
  85. package/build/server/chunks/{11-CJPjkEF3.js.map → 11-DHPvc2yA.js.map} +1 -1
  86. package/build/server/chunks/{2-RLnhlWh5.js → 2-DLOMdCHW.js} +4 -4
  87. package/build/server/chunks/{2-RLnhlWh5.js.map → 2-DLOMdCHW.js.map} +1 -1
  88. package/build/server/chunks/{3-Dd4pJBqZ.js → 3-DCf69LYo.js} +2 -2
  89. package/build/server/chunks/{3-Dd4pJBqZ.js.map → 3-DCf69LYo.js.map} +1 -1
  90. package/build/server/chunks/{4-Bb5VFhsO.js → 4-D92KnTmb.js} +3 -3
  91. package/build/server/chunks/{4-Bb5VFhsO.js.map → 4-D92KnTmb.js.map} +1 -1
  92. package/build/server/chunks/{5-oNoWuIsn.js → 5-D-Uv1voC.js} +2 -2
  93. package/build/server/chunks/{5-oNoWuIsn.js.map → 5-D-Uv1voC.js.map} +1 -1
  94. package/build/server/chunks/{6-DdRMnKNa.js → 6-DUrC2Naz.js} +2 -2
  95. package/build/server/chunks/{6-DdRMnKNa.js.map → 6-DUrC2Naz.js.map} +1 -1
  96. package/build/server/chunks/{7-vLOMMetm.js → 7-TXwjMHt2.js} +2 -2
  97. package/build/server/chunks/{7-vLOMMetm.js.map → 7-TXwjMHt2.js.map} +1 -1
  98. package/build/server/chunks/{8-rJyiQLFs.js → 8-D2X_jBsT.js} +2 -2
  99. package/build/server/chunks/{8-rJyiQLFs.js.map → 8-D2X_jBsT.js.map} +1 -1
  100. package/build/server/chunks/{9-CVSNNYED.js → 9-DK0hH5Xa.js} +2 -2
  101. package/build/server/chunks/{9-CVSNNYED.js.map → 9-DK0hH5Xa.js.map} +1 -1
  102. package/build/server/chunks/Banner-BgaAs1rs.js.map +1 -1
  103. package/build/server/chunks/Button-D0hZ7JYt.js.map +1 -1
  104. package/build/server/chunks/Icon-D0GBnDcs.js.map +1 -1
  105. package/build/server/chunks/Input-OmIiydSx.js.map +1 -1
  106. package/build/server/chunks/Pill-4xJ-VhAA.js.map +1 -1
  107. package/build/server/chunks/Shimmer-Dw2uvTC1.js.map +1 -1
  108. package/build/server/chunks/_error.svelte-CZnkxeLr.js.map +1 -1
  109. package/build/server/chunks/_layout.svelte-DfgNGGiM.js.map +1 -1
  110. package/build/server/chunks/_page.svelte-8OFzwdNA.js +758 -0
  111. package/build/server/chunks/_page.svelte-8OFzwdNA.js.map +1 -0
  112. package/build/server/chunks/_page.svelte-BTlfUsBp.js.map +1 -1
  113. package/build/server/chunks/_page.svelte-C7B0qdrC.js.map +1 -1
  114. package/build/server/chunks/{_page.svelte-BLo2v_8E.js → _page.svelte-Gv9p8nlS.js} +3 -4
  115. package/build/server/chunks/_page.svelte-Gv9p8nlS.js.map +1 -0
  116. package/build/server/chunks/_page.svelte-dabsQl9c.js.map +1 -1
  117. package/build/server/chunks/{_server.ts-bk_EeAdY.js → _server.ts-05JJOdcX.js} +20 -14
  118. package/build/server/chunks/_server.ts-05JJOdcX.js.map +1 -0
  119. package/build/server/chunks/{_server.ts-DEx9-epI.js → _server.ts-B54Pvhgc.js} +4 -3
  120. package/build/server/chunks/_server.ts-B54Pvhgc.js.map +1 -0
  121. package/build/server/chunks/_server.ts-BB46Fbqn.js +59 -0
  122. package/build/server/chunks/_server.ts-BB46Fbqn.js.map +1 -0
  123. package/build/server/chunks/{_server.ts-BRAzC6W1.js → _server.ts-BCljU9Sg.js} +33 -8
  124. package/build/server/chunks/_server.ts-BCljU9Sg.js.map +1 -0
  125. package/build/server/chunks/{_server.ts-D-vgx5UZ.js → _server.ts-BTmknWpO.js} +2 -2
  126. package/build/server/chunks/{_server.ts-D-vgx5UZ.js.map → _server.ts-BTmknWpO.js.map} +1 -1
  127. package/build/server/chunks/{_server.ts-tChyh9FX.js → _server.ts-BXhmLZwN.js} +4 -2
  128. package/build/server/chunks/{_server.ts-tChyh9FX.js.map → _server.ts-BXhmLZwN.js.map} +1 -1
  129. package/build/server/chunks/{_server.ts-CvJKTS3Z.js → _server.ts-BbRSpB74.js} +4 -2
  130. package/build/server/chunks/{_server.ts-CvJKTS3Z.js.map → _server.ts-BbRSpB74.js.map} +1 -1
  131. package/build/server/chunks/{_server.ts-DKNIsQeH.js → _server.ts-Bol54_Qo.js} +4 -3
  132. package/build/server/chunks/_server.ts-Bol54_Qo.js.map +1 -0
  133. package/build/server/chunks/{_server.ts-Dz9Jd9Jh.js → _server.ts-C0PO_cAu.js} +4 -3
  134. package/build/server/chunks/{_server.ts-Dz9Jd9Jh.js.map → _server.ts-C0PO_cAu.js.map} +1 -1
  135. package/build/server/chunks/_server.ts-C6NRpe7e.js +33 -0
  136. package/build/server/chunks/_server.ts-C6NRpe7e.js.map +1 -0
  137. package/build/server/chunks/_server.ts-CGqCOCdK.js +53 -0
  138. package/build/server/chunks/_server.ts-CGqCOCdK.js.map +1 -0
  139. package/build/server/chunks/{_server.ts-B2wIgsW4.js → _server.ts-CZb-BI5H.js} +4 -3
  140. package/build/server/chunks/_server.ts-CZb-BI5H.js.map +1 -0
  141. package/build/server/chunks/_server.ts-DPHRUFYS.js +159 -0
  142. package/build/server/chunks/_server.ts-DPHRUFYS.js.map +1 -0
  143. package/build/server/chunks/{_server.ts-AnBXfZXh.js → _server.ts-D_WRex0k.js} +5 -3
  144. package/build/server/chunks/_server.ts-D_WRex0k.js.map +1 -0
  145. package/build/server/chunks/{_server.ts-CJGyN8mw.js → _server.ts-DiBMY7Ho.js} +4 -3
  146. package/build/server/chunks/_server.ts-DiBMY7Ho.js.map +1 -0
  147. package/build/server/chunks/cache-BlMaDsHi.js.map +1 -1
  148. package/build/server/chunks/{guest-registry-t0-7Zv5q.js → guest-registry-Dxvd7p-g.js} +10 -1
  149. package/build/server/chunks/guest-registry-Dxvd7p-g.js.map +1 -0
  150. package/build/server/chunks/index-CoYB03g7.js.map +1 -1
  151. package/build/server/chunks/index2-dSGQ9Eaa.js.map +1 -1
  152. package/build/server/chunks/{library-apns-Dl3iRE2h.js → library-apns-D8RPINlv.js} +62 -7
  153. package/build/server/chunks/library-apns-D8RPINlv.js.map +1 -0
  154. package/build/server/chunks/{pending-requests-C9p57WoU.js → pending-requests-8rWjrF6d.js} +3 -2
  155. package/build/server/chunks/pending-requests-8rWjrF6d.js.map +1 -0
  156. package/build/server/chunks/presence-store-Bx_g0-Gd.js +23 -0
  157. package/build/server/chunks/presence-store-Bx_g0-Gd.js.map +1 -0
  158. package/build/server/chunks/{pty-manager-CkZNoW1t.js → pty-manager-CoWVT56F.js} +27 -6
  159. package/build/server/chunks/pty-manager-CoWVT56F.js.map +1 -0
  160. package/build/server/chunks/root-D4IoFC8F.js.map +1 -1
  161. package/build/server/chunks/shooter-home-4f_HkdGI.js +10 -0
  162. package/build/server/chunks/shooter-home-4f_HkdGI.js.map +1 -0
  163. package/build/server/chunks/state.svelte-CmHqngc_.js.map +1 -1
  164. package/build/server/index.js +1 -1
  165. package/build/server/index.js.map +1 -1
  166. package/build/server/manifest.js +52 -24
  167. package/build/server/manifest.js.map +1 -1
  168. package/package.json +2 -2
  169. package/server.ts +2 -0
  170. package/src/lib/modules/client/common/index.ts +1 -0
  171. package/src/lib/modules/client/common/presence.ts +47 -0
  172. package/src/lib/modules/client/dashboard/AutopilotPanel.svelte +584 -0
  173. package/src/lib/modules/client/dashboard/autopilot-driver.svelte.ts +681 -0
  174. package/src/lib/modules/client/dashboard/decide-injection.ts +127 -0
  175. package/src/lib/modules/client/dashboard/index.ts +1 -0
  176. package/src/lib/modules/client/dashboard/store.svelte.ts +65 -24
  177. package/src/lib/modules/client/neurolink/fetch-proxy.ts +38 -1
  178. package/src/lib/modules/client/neurolink/provider-config.ts +13 -37
  179. package/src/lib/modules/server/apn/apns-payload.ts +50 -0
  180. package/src/lib/modules/server/apn/library-apns.ts +50 -8
  181. package/src/lib/modules/server/apn/pending-requests.ts +3 -1
  182. package/src/lib/modules/server/sessions/autopilot-context.ts +57 -0
  183. package/src/lib/modules/server/sessions/autopilot-engine.ts +451 -0
  184. package/src/lib/modules/server/sessions/litellm-client.ts +171 -0
  185. package/src/lib/modules/server/sessions/next-step-consensus.ts +210 -0
  186. package/src/lib/modules/server/sessions/summary-store.ts +113 -0
  187. package/src/lib/modules/server/terminal/agent-launch.ts +26 -0
  188. package/src/lib/modules/server/terminal/pty-holder.cjs +6 -0
  189. package/src/lib/modules/server/terminal/pty-manager.ts +13 -3
  190. package/src/lib/modules/server/terminal/session-watcher.ts +12 -2
  191. package/src/lib/modules/server/terminal/terminal-store.ts +3 -1
  192. package/src/lib/modules/server/utils/shooter-home.ts +16 -0
  193. package/src/lib/modules/server/ws/events-handler.ts +32 -0
  194. package/src/lib/modules/server/ws/presence-store.ts +50 -0
  195. package/src/lib/types/autopilot.ts +138 -0
  196. package/src/lib/types/index.ts +1 -0
  197. package/src/lib/types/terminal-client.ts +2 -0
  198. package/src/routes/+layout.server.ts +2 -0
  199. package/src/routes/+layout.svelte +19 -4
  200. package/src/routes/+page.svelte +9 -1
  201. package/src/routes/api/autopilot/+server.ts +74 -0
  202. package/src/routes/api/autopilot/goal/+server.ts +72 -0
  203. package/src/routes/api/neurolink-proxy/+server.ts +39 -8
  204. package/src/routes/api/notify/+server.ts +38 -14
  205. package/src/routes/api/presence/+server.ts +39 -0
  206. package/src/routes/api/summaries/+server.ts +99 -0
  207. package/src/routes/api/ws-status/+server.ts +8 -5
  208. package/build/client/_app/immutable/assets/2.DjiwkLqE.css +0 -1
  209. package/build/client/_app/immutable/assets/2.DjiwkLqE.css.br +0 -0
  210. package/build/client/_app/immutable/assets/2.DjiwkLqE.css.gz +0 -0
  211. package/build/client/_app/immutable/chunks/BB2l8o4X.js.br +0 -0
  212. package/build/client/_app/immutable/chunks/BB2l8o4X.js.gz +0 -0
  213. package/build/client/_app/immutable/chunks/BvmdJful.js.br +0 -0
  214. package/build/client/_app/immutable/chunks/BvmdJful.js.gz +0 -0
  215. package/build/client/_app/immutable/chunks/CRkG7oE4.js +0 -1
  216. package/build/client/_app/immutable/chunks/CRkG7oE4.js.br +0 -0
  217. package/build/client/_app/immutable/chunks/CRkG7oE4.js.gz +0 -0
  218. package/build/client/_app/immutable/chunks/C_YNQL8b.js +0 -3
  219. package/build/client/_app/immutable/chunks/C_YNQL8b.js.br +0 -0
  220. package/build/client/_app/immutable/chunks/C_YNQL8b.js.gz +0 -0
  221. package/build/client/_app/immutable/chunks/ClIPTXf3.js.br +0 -0
  222. package/build/client/_app/immutable/chunks/ClIPTXf3.js.gz +0 -0
  223. package/build/client/_app/immutable/chunks/DIZ3Qst5.js.br +0 -0
  224. package/build/client/_app/immutable/chunks/DT4H19pV.js.br +0 -0
  225. package/build/client/_app/immutable/chunks/DT4H19pV.js.gz +0 -0
  226. package/build/client/_app/immutable/chunks/ZS5XYDx_.js.br +0 -0
  227. package/build/client/_app/immutable/chunks/pRcLbE0d.js +0 -1
  228. package/build/client/_app/immutable/chunks/pRcLbE0d.js.br +0 -0
  229. package/build/client/_app/immutable/chunks/pRcLbE0d.js.gz +0 -0
  230. package/build/client/_app/immutable/entry/app.Bd-DfeJi.js.br +0 -0
  231. package/build/client/_app/immutable/entry/app.Bd-DfeJi.js.gz +0 -0
  232. package/build/client/_app/immutable/entry/start.evvp4tX7.js +0 -1
  233. package/build/client/_app/immutable/entry/start.evvp4tX7.js.br +0 -2
  234. package/build/client/_app/immutable/entry/start.evvp4tX7.js.gz +0 -0
  235. package/build/client/_app/immutable/nodes/0.Bl-1LQWM.js +0 -10
  236. package/build/client/_app/immutable/nodes/0.Bl-1LQWM.js.br +0 -0
  237. package/build/client/_app/immutable/nodes/0.Bl-1LQWM.js.gz +0 -0
  238. package/build/client/_app/immutable/nodes/1.DT4dq6Ay.js.br +0 -0
  239. package/build/client/_app/immutable/nodes/1.DT4dq6Ay.js.gz +0 -0
  240. package/build/client/_app/immutable/nodes/10.CF7RGXpe.js.br +0 -0
  241. package/build/client/_app/immutable/nodes/10.CF7RGXpe.js.gz +0 -0
  242. package/build/client/_app/immutable/nodes/11.BV_G7yLI.js.br +0 -0
  243. package/build/client/_app/immutable/nodes/11.BV_G7yLI.js.gz +0 -0
  244. package/build/client/_app/immutable/nodes/2.DcRhsjYp.js +0 -13
  245. package/build/client/_app/immutable/nodes/2.DcRhsjYp.js.br +0 -0
  246. package/build/client/_app/immutable/nodes/2.DcRhsjYp.js.gz +0 -0
  247. package/build/client/_app/immutable/nodes/3.0MMe3oxR.js.br +0 -0
  248. package/build/client/_app/immutable/nodes/3.0MMe3oxR.js.gz +0 -0
  249. package/build/client/_app/immutable/nodes/4.CBX9A3ka.js.br +0 -0
  250. package/build/client/_app/immutable/nodes/4.CBX9A3ka.js.gz +0 -0
  251. package/build/client/_app/immutable/nodes/5.DIVKuZc9.js.br +0 -0
  252. package/build/client/_app/immutable/nodes/5.DIVKuZc9.js.gz +0 -0
  253. package/build/client/_app/immutable/nodes/6.ComiWlV6.js.br +0 -0
  254. package/build/client/_app/immutable/nodes/6.ComiWlV6.js.gz +0 -0
  255. package/build/client/_app/immutable/nodes/7.vkPx1kVP.js.br +0 -0
  256. package/build/client/_app/immutable/nodes/7.vkPx1kVP.js.gz +0 -0
  257. package/build/client/_app/immutable/nodes/8.Bmr3sWbS.js.br +0 -0
  258. package/build/client/_app/immutable/nodes/8.Bmr3sWbS.js.gz +0 -0
  259. package/build/client/_app/immutable/nodes/9.CAJucyeI.js.br +0 -0
  260. package/build/client/_app/immutable/nodes/9.CAJucyeI.js.gz +0 -0
  261. package/build/server/chunks/0-DDGB6CRT.js.map +0 -1
  262. package/build/server/chunks/_page.svelte-BLo2v_8E.js.map +0 -1
  263. package/build/server/chunks/_page.svelte-tBuIq8Pg.js +0 -159
  264. package/build/server/chunks/_page.svelte-tBuIq8Pg.js.map +0 -1
  265. package/build/server/chunks/_server.ts-AnBXfZXh.js.map +0 -1
  266. package/build/server/chunks/_server.ts-B2wIgsW4.js.map +0 -1
  267. package/build/server/chunks/_server.ts-BRAzC6W1.js.map +0 -1
  268. package/build/server/chunks/_server.ts-CJGyN8mw.js.map +0 -1
  269. package/build/server/chunks/_server.ts-DEx9-epI.js.map +0 -1
  270. package/build/server/chunks/_server.ts-DKNIsQeH.js.map +0 -1
  271. package/build/server/chunks/_server.ts-bk_EeAdY.js.map +0 -1
  272. package/build/server/chunks/guest-registry-t0-7Zv5q.js.map +0 -1
  273. package/build/server/chunks/library-apns-Dl3iRE2h.js.map +0 -1
  274. package/build/server/chunks/pending-requests-C9p57WoU.js.map +0 -1
  275. package/build/server/chunks/pty-manager-CkZNoW1t.js.map +0 -1
@@ -0,0 +1,138 @@
1
+ // Hand-written autopilot types — union/record types not expressible in YAML.
2
+
3
+ /** A raw proposal from a single agent. */
4
+ export interface AgentProposal {
5
+ confidence: number;
6
+ text: string;
7
+ }
8
+
9
+ /** Autopilot module-level state (not a Svelte store — held in the autopilot module). */
10
+ export interface AutopilotState {
11
+ enabled: boolean;
12
+ status: Record<string, AutopilotStatus>;
13
+ }
14
+
15
+ /** Per-session autopilot lifecycle state. */
16
+ export type AutopilotStatus = 'error' | 'idle' | 'running';
17
+
18
+ /** Verdict from guardCommand() — whether a concrete command is safe to write to the PTY. */
19
+ export interface CommandVerdict {
20
+ /** The trimmed command (echoed back for convenience). */
21
+ command: string;
22
+ /** Human-readable reason for the verdict. */
23
+ reason: string;
24
+ /** True when the command passed all guards and may be injected. */
25
+ safe: boolean;
26
+ }
27
+
28
+ /** Result from mergeNextStepConsensus(). */
29
+ export interface ConsensusResult {
30
+ /** Number of agent lists passed in. */
31
+ agentCount: number;
32
+ /** Quorum threshold used. */
33
+ quorum: number;
34
+ /** Consensus (or tentative) next-step list, sorted by votes desc then confidence desc. */
35
+ steps: NextStep[];
36
+ }
37
+
38
+ /** A record of one autonomous-loop decision, surfaced in the dashboard panel. */
39
+ export interface DriverAction {
40
+ /** ms timestamp of the action. */
41
+ at: number;
42
+ /** The command injected, or a short reason it was skipped / failed. */
43
+ detail: string;
44
+ /** What happened. */
45
+ kind: DriverActionKind;
46
+ /** The terminal acted on. */
47
+ terminalId: string;
48
+ }
49
+
50
+ /** Outcome of one driver evaluation. */
51
+ export type DriverActionKind = 'error' | 'injected' | 'skipped';
52
+
53
+ /** Decision from decideInjection() — the gate deciding whether to auto-act. */
54
+ export interface GateDecision {
55
+ /** True when all safety gates pass and the loop should produce + inject a command. */
56
+ act: boolean;
57
+ /** Human-readable reason (always set, for logging + the phone UI). */
58
+ reason: string;
59
+ /** The consensus step being acted on (present when act is true). */
60
+ step?: NextStep;
61
+ }
62
+
63
+ /** Tunable thresholds for the auto-inject safety gate. */
64
+ export interface InjectionPolicy {
65
+ /** Suppress injection within this many ms of observed human / output activity. */
66
+ humanGraceMs: number;
67
+ /** Minimum confidence of the top consensus step required to inject. */
68
+ injectConfidence: number;
69
+ /** Stop auto-injecting a terminal after this many consecutive actions without progress. */
70
+ maxAutoActions: number;
71
+ /** Minimum gap in ms between injections into the same terminal. */
72
+ minIntervalMs: number;
73
+ }
74
+
75
+ /** Per-terminal snapshot the driver passes to decideInjection(). */
76
+ export interface InjectionState {
77
+ /** Consecutive auto-injections without a human touch or successful tool completion. */
78
+ autoActionCount: number;
79
+ /** True only for terminals Shooter created (POST /api/terminals); external sessions are read-only. */
80
+ isManaged: boolean;
81
+ /** Normalized text of the last consensus step acted on (dedup guard). */
82
+ lastActedStep: null | string;
83
+ /** ms timestamp of the last observed human input / terminal output activity. */
84
+ lastActivityAt: number;
85
+ /** The most recent WireShooterEvent type seen for this terminal. */
86
+ lastEventType: string;
87
+ /** ms timestamp of the last command injection into this terminal. */
88
+ lastInjectedAt: number;
89
+ /** The terminal id. */
90
+ terminalId: string;
91
+ }
92
+
93
+ /** Options for mergeNextStepConsensus(). */
94
+ export interface MergeOptions {
95
+ /** Max steps taken from each agent list (default 3). */
96
+ k?: number;
97
+ /** Minimum vote count for a group to reach consensus (default 3). */
98
+ quorum?: number;
99
+ }
100
+
101
+ /** A single proposed next step from one agent or the merged consensus. */
102
+ export interface NextStep {
103
+ /** Confidence score in [0, 1]. For consensus steps this is the mean across proposers. */
104
+ confidence: number;
105
+ /** Present and true when no group reached quorum; indicates low-certainty result. */
106
+ tentative?: boolean;
107
+ /** The original (highest-confidence) phrasing of the step. */
108
+ text: string;
109
+ /** Number of distinct agents that proposed this step (consensus only). */
110
+ votes?: number;
111
+ }
112
+
113
+ /**
114
+ * Additional per-session fields added by the autopilot engine.
115
+ * These are optional so that SessionState objects (which lack them by default)
116
+ * remain assignable; the engine writes them via Svelte 5's reactive proxy.
117
+ */
118
+ export interface SessionAutopilotFields {
119
+ /** ISO 8601 timestamp of the last completed autopilot pipeline run. */
120
+ autopilotLastRun?: null | string;
121
+ /** Current autopilot pipeline status for this session. */
122
+ autopilotStatus?: AutopilotStatus;
123
+ /** Consensus next-step list from the last pipeline run. */
124
+ nextSteps?: NextStep[];
125
+ }
126
+
127
+ /** A persisted summary record stored in session_summaries. */
128
+ export interface SessionSummaryRecord {
129
+ createdAt: string;
130
+ id: string;
131
+ /** JSON-serialised NextStep[] */
132
+ nextSteps: string;
133
+ projectName: null | string;
134
+ sessionId: null | string;
135
+ summary: string;
136
+ terminalId: null | string;
137
+ trigger: string;
138
+ }
@@ -3,6 +3,7 @@
3
3
 
4
4
  export type * from './activity';
5
5
  export type * from './apn';
6
+ export type * from './autopilot';
6
7
  export type * from './cli';
7
8
  export type * from './codex';
8
9
  export type * from './common';
@@ -56,6 +56,8 @@ export interface LaunchSheetProps {
56
56
 
57
57
  export interface LayoutData {
58
58
  aiProviders: Record<string, boolean>;
59
+ litellmBaseUrl: string;
60
+ litellmModel: string;
59
61
  neurolinkProvider: string;
60
62
  }
61
63
 
@@ -5,5 +5,7 @@ import type { LayoutServerLoad } from './$types';
5
5
 
6
6
  export const load: LayoutServerLoad = () => ({
7
7
  aiProviders: getProviderAvailability(env),
8
+ litellmBaseUrl: env.LITELLM_BASE_URL ?? '',
9
+ litellmModel: env.LITELLM_MODEL ?? 'open-large',
8
10
  neurolinkProvider: env.NEUROLINK_PROVIDER ?? '',
9
11
  });
@@ -22,11 +22,26 @@
22
22
  return;
23
23
  }
24
24
  (window as unknown as Record<string, unknown>).__aiProviders = data.aiProviders;
25
+
26
+ // Ensure window.process.env exists minimally so env vars can be injected.
27
+ const win = window as unknown as Record<string, unknown>;
28
+ if (!win.process || typeof win.process !== 'object') {
29
+ win.process = { env: {} };
30
+ }
31
+ const proc = win.process as Record<string, unknown>;
32
+ if (!proc.env || typeof proc.env !== 'object') {
33
+ proc.env = {};
34
+ }
35
+ const procEnv = proc.env as Record<string, string>;
36
+
25
37
  if (data.neurolinkProvider) {
26
- const proc = (window as unknown as { process?: { env?: Record<string, string> } }).process;
27
- if (proc?.env) {
28
- proc.env.NEUROLINK_PROVIDER = data.neurolinkProvider;
29
- }
38
+ procEnv.NEUROLINK_PROVIDER = data.neurolinkProvider;
39
+ }
40
+ if (data.litellmBaseUrl) {
41
+ procEnv.LITELLM_BASE_URL = data.litellmBaseUrl;
42
+ }
43
+ if (data.litellmModel) {
44
+ procEnv.LITELLM_MODEL = data.litellmModel;
30
45
  }
31
46
  });
32
47
 
@@ -12,7 +12,13 @@
12
12
  isShooterConfig,
13
13
  setCache,
14
14
  } from '$lib/modules/client/common';
15
- import { connect, DashboardView, disconnect, getCards } from '$lib/modules/client/dashboard';
15
+ import {
16
+ AutopilotPanel,
17
+ connect,
18
+ DashboardView,
19
+ disconnect,
20
+ getCards,
21
+ } from '$lib/modules/client/dashboard';
16
22
  import { Banner, Button, EmptyState, Icon, Pill, Shimmer } from '@juspay/svelte-ui-components';
17
23
  import { onDestroy, onMount } from 'svelte';
18
24
 
@@ -203,6 +209,8 @@
203
209
  <Button classes="btn-primary" onclick={navigateToConfig} text="Configure Settings" />
204
210
  </EmptyState>
205
211
  {:else}
212
+ <AutopilotPanel />
213
+
206
214
  <!-- Dashboard section: active terminal sessions -->
207
215
  {#if cards.length > 0}
208
216
  <div class="dashboard-section">
@@ -0,0 +1,74 @@
1
+ // Control endpoint for the always-on server-side autopilot engine.
2
+ // GET returns the enabled flag; POST { enabled } toggles it.
3
+ //
4
+ // The engine runs in the server.ts module graph and exposes its control on
5
+ // globalThis.__shooter_autopilot. This route (bundled handler graph) reaches it
6
+ // there rather than importing the engine — importing it would start a second
7
+ // event subscriber. Falls back to the on-disk state file if the engine has not
8
+ // started yet.
9
+
10
+ import { validateAuth } from '$lib/modules/server/auth';
11
+ import { json } from '@sveltejs/kit';
12
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
13
+ import { homedir } from 'os';
14
+ import { join } from 'path';
15
+
16
+ import type { RequestHandler } from './$types';
17
+
18
+ const STATE_FILE = join(homedir(), '.shooter', 'autopilot.json');
19
+
20
+ function control(): undefined | { isEnabled: () => boolean; setEnabled: (v: boolean) => void } {
21
+ return (globalThis as Record<string, unknown>).__shooter_autopilot as
22
+ | undefined
23
+ | { isEnabled: () => boolean; setEnabled: (v: boolean) => void };
24
+ }
25
+
26
+ function readFileEnabled(): boolean {
27
+ try {
28
+ if (existsSync(STATE_FILE)) {
29
+ const parsed: unknown = JSON.parse(readFileSync(STATE_FILE, 'utf-8'));
30
+ return Boolean((parsed as { enabled?: unknown })?.enabled);
31
+ }
32
+ } catch {
33
+ // corrupt / unreadable
34
+ }
35
+ return false;
36
+ }
37
+
38
+ export const GET: RequestHandler = ({ request }) => {
39
+ const authError = validateAuth(request);
40
+ if (authError) {
41
+ return authError;
42
+ }
43
+ const ctrl = control();
44
+ return json({ enabled: ctrl ? ctrl.isEnabled() : readFileEnabled(), running: Boolean(ctrl) });
45
+ };
46
+
47
+ export const POST: RequestHandler = async ({ request }) => {
48
+ const authError = validateAuth(request);
49
+ if (authError) {
50
+ return authError;
51
+ }
52
+ let body: { enabled?: unknown };
53
+ try {
54
+ body = (await request.json()) as { enabled?: unknown };
55
+ } catch {
56
+ return json({ error: 'Invalid JSON body' }, { status: 400 });
57
+ }
58
+ if (typeof body.enabled !== 'boolean') {
59
+ return json({ error: 'enabled must be a boolean' }, { status: 400 });
60
+ }
61
+
62
+ const ctrl = control();
63
+ if (ctrl) {
64
+ ctrl.setEnabled(body.enabled);
65
+ } else {
66
+ try {
67
+ mkdirSync(join(homedir(), '.shooter'), { recursive: true });
68
+ writeFileSync(STATE_FILE, JSON.stringify({ enabled: body.enabled }), 'utf-8');
69
+ } catch {
70
+ // best-effort
71
+ }
72
+ }
73
+ return json({ enabled: body.enabled, running: Boolean(ctrl) });
74
+ };
@@ -0,0 +1,72 @@
1
+ // Set/read the per-terminal autopilot GOAL that anchors the engine's context every cycle.
2
+ //
3
+ // POST { terminalId, goal } pins the goal; GET ?terminalId=… reads it back. Like
4
+ // /api/autopilot, this reaches the engine via globalThis.__shooter_autopilot rather than
5
+ // importing it (importing would start a second event subscriber). Goals live in-memory in the
6
+ // engine (no file fallback): if the engine is not running there is nothing to set, so we 503.
7
+
8
+ import { validateAuth } from '$lib/modules/server/auth';
9
+ import { json } from '@sveltejs/kit';
10
+
11
+ import type { RequestHandler } from './$types';
12
+
13
+ function control():
14
+ | undefined
15
+ | {
16
+ getGoal?: (terminalId: string) => string | undefined;
17
+ setGoal?: (terminalId: string, goal: string) => void;
18
+ } {
19
+ return (globalThis as Record<string, unknown>).__shooter_autopilot as
20
+ | undefined
21
+ | {
22
+ getGoal?: (terminalId: string) => string | undefined;
23
+ setGoal?: (terminalId: string, goal: string) => void;
24
+ };
25
+ }
26
+
27
+ export const GET: RequestHandler = ({ request, url }) => {
28
+ const authError = validateAuth(request);
29
+ if (authError) {
30
+ return authError;
31
+ }
32
+ const terminalId = url.searchParams.get('terminalId') ?? '';
33
+ if (!terminalId) {
34
+ return json({ error: 'terminalId query param is required' }, { status: 400 });
35
+ }
36
+ const ctrl = control();
37
+ if (!ctrl?.getGoal) {
38
+ return json({ error: 'autopilot engine not running', running: false }, { status: 503 });
39
+ }
40
+ return json({ goal: ctrl.getGoal(terminalId) ?? null, running: true });
41
+ };
42
+
43
+ export const POST: RequestHandler = async ({ request }) => {
44
+ const authError = validateAuth(request);
45
+ if (authError) {
46
+ return authError;
47
+ }
48
+ let body: { goal?: unknown; terminalId?: unknown };
49
+ try {
50
+ body = (await request.json()) as { goal?: unknown; terminalId?: unknown };
51
+ } catch {
52
+ return json({ error: 'Invalid JSON body' }, { status: 400 });
53
+ }
54
+ if (typeof body.terminalId !== 'string' || body.terminalId.length === 0) {
55
+ return json({ error: 'terminalId must be a non-empty string' }, { status: 400 });
56
+ }
57
+ if (typeof body.goal !== 'string') {
58
+ return json({ error: 'goal must be a string' }, { status: 400 });
59
+ }
60
+ if (body.goal.length > 500) {
61
+ // The goal is prepended to EVERY engine LLM context, so an unbounded string would permanently
62
+ // bloat (and could dominate) the prompt. Cap it like the summaries route caps its fields.
63
+ return json({ error: 'goal must be 500 characters or fewer' }, { status: 400 });
64
+ }
65
+
66
+ const ctrl = control();
67
+ if (!ctrl?.setGoal) {
68
+ return json({ error: 'autopilot engine not running', running: false }, { status: 503 });
69
+ }
70
+ ctrl.setGoal(body.terminalId, body.goal);
71
+ return json({ goal: body.goal.trim() || null, running: true, terminalId: body.terminalId });
72
+ };
@@ -15,6 +15,7 @@ import type { RequestHandler } from './$types';
15
15
  const ALLOWED_CLIENT_HEADERS: Record<string, Set<string>> = {
16
16
  anthropic: new Set(['anthropic-beta', 'anthropic-version']),
17
17
  'google-ai': new Set<string>([]),
18
+ litellm: new Set<string>([]),
18
19
  mistral: new Set([]),
19
20
  openai: new Set(['openai-organization', 'openai-project']),
20
21
  };
@@ -63,15 +64,29 @@ export const POST: RequestHandler = async ({ request }) => {
63
64
  openai: 'https://api.openai.com/',
64
65
  };
65
66
 
66
- const allowedPrefix = ALLOWED_PREFIXES[provider];
67
- if (!allowedPrefix || !url.startsWith(allowedPrefix)) {
68
- return json({ error: `Provider "${provider}" or URL not allowed` }, { status: 403 });
67
+ if (provider === 'litellm') {
68
+ // SSRF-safe: only forward to the operator-configured LiteLLM endpoint.
69
+ // Trim to align with getProviderAvailability semantics (whitespace-only = not configured).
70
+ const rawBase = env.LITELLM_BASE_URL?.trim();
71
+ if (!rawBase) {
72
+ return json({ error: 'LiteLLM is not configured on this server' }, { status: 403 });
73
+ }
74
+ const litellmPrefix = `${rawBase.replace(/\/+$/, '')}/`;
75
+ if (!url.startsWith(litellmPrefix)) {
76
+ return json({ error: `Provider "${provider}" or URL not allowed` }, { status: 403 });
77
+ }
78
+ } else {
79
+ const allowedPrefix = ALLOWED_PREFIXES[provider];
80
+ if (!allowedPrefix || !url.startsWith(allowedPrefix)) {
81
+ return json({ error: `Provider "${provider}" or URL not allowed` }, { status: 403 });
82
+ }
69
83
  }
70
84
 
71
85
  // Inject the server-side API key so the browser never sees it
72
86
  const apiKeyEnv: Record<string, string> = {
73
87
  anthropic: env.ANTHROPIC_API_KEY ?? '',
74
88
  'google-ai': env.GOOGLE_AI_API_KEY ?? '',
89
+ litellm: env.LITELLM_API_KEY ?? '',
75
90
  mistral: env.MISTRAL_API_KEY ?? '',
76
91
  openai: env.OPENAI_API_KEY ?? '',
77
92
  };
@@ -93,16 +108,32 @@ export const POST: RequestHandler = async ({ request }) => {
93
108
  ...normalizedReqHeaders,
94
109
  };
95
110
 
96
- // Override / set auth header with server-side key
111
+ // Override / set auth header with server-side key.
112
+ // For Bearer-token providers, only inject the header when the key is non-empty
113
+ // to avoid sending a malformed `Authorization: Bearer ` to the upstream.
97
114
  if (provider === 'anthropic') {
98
- forwardHeaders['x-api-key'] = apiKeyEnv.anthropic;
115
+ // Only inject the key header when non-empty — sending `x-api-key: ` (empty) is a malformed
116
+ // header that the upstream rejects with a confusing error instead of a clean 401.
117
+ if (apiKeyEnv.anthropic) {
118
+ forwardHeaders['x-api-key'] = apiKeyEnv.anthropic;
119
+ }
99
120
  forwardHeaders['anthropic-version'] = forwardHeaders['anthropic-version'] ?? '2023-06-01';
100
121
  } else if (provider === 'google-ai') {
101
- forwardHeaders['x-goog-api-key'] = apiKeyEnv['google-ai'];
122
+ if (apiKeyEnv['google-ai']) {
123
+ forwardHeaders['x-goog-api-key'] = apiKeyEnv['google-ai'];
124
+ }
102
125
  } else if (provider === 'openai') {
103
- forwardHeaders.Authorization = `Bearer ${apiKeyEnv.openai}`;
126
+ if (apiKeyEnv.openai) {
127
+ forwardHeaders.Authorization = `Bearer ${apiKeyEnv.openai}`;
128
+ }
104
129
  } else if (provider === 'mistral') {
105
- forwardHeaders.Authorization = `Bearer ${apiKeyEnv.mistral}`;
130
+ if (apiKeyEnv.mistral) {
131
+ forwardHeaders.Authorization = `Bearer ${apiKeyEnv.mistral}`;
132
+ }
133
+ } else if (provider === 'litellm') {
134
+ if (apiKeyEnv.litellm) {
135
+ forwardHeaders.Authorization = `Bearer ${apiKeyEnv.litellm}`;
136
+ }
106
137
  }
107
138
 
108
139
  const controller = new AbortController();
@@ -173,13 +173,8 @@ function intelligentNotificationFilter(
173
173
  };
174
174
  }
175
175
 
176
- // Always allow Stop hook completion notifications
177
- if (source === 'stop-hook') {
178
- return {
179
- reason: 'Stop hook completion notification - session finished',
180
- send: true,
181
- };
182
- }
176
+ // (Removed a dead `source === 'stop-hook'` branch: the notifier emits
177
+ // 'shooter-completion-detector', never 'stop-hook', and the default below already allows it.)
183
178
 
184
179
  // Filter out only very specific spam patterns to be less restrictive
185
180
  const spamPatterns = [
@@ -238,7 +233,15 @@ function isDuplicateNotification(
238
233
  return false;
239
234
  }
240
235
 
241
- const key = `${title}|${message}|${data?.category || 'unknown'}`;
236
+ // Autopilot pushes include a stable dedupKey keyed on sessionId + top-step text
237
+ // (not the variable summary). Prefer it over the title|message|category key so
238
+ // two runs with the same top-step but different summary wording are correctly
239
+ // deduplicated.
240
+ const dataRecord = data as (NotificationData & { dedupKey?: string }) | undefined;
241
+ const key = dataRecord?.dedupKey
242
+ ? dataRecord.dedupKey
243
+ : `${title}|${message}|${data?.category || 'unknown'}`;
244
+
242
245
  const now = Date.now();
243
246
 
244
247
  if (notificationCache.has(key)) {
@@ -255,15 +258,28 @@ function isDuplicateNotification(
255
258
  }
256
259
  }
257
260
 
258
- // Do NOT record here -- caller must call recordNotification() after
259
- // successful delivery to avoid cache poisoning on send failure.
261
+ // RESERVE the slot atomically (check-and-set): a second concurrent request with the same key now
262
+ // sees it as a duplicate before either has delivered, closing the TOCTOU window that let two
263
+ // identical pushes through. The delivery path RELEASES the slot (releaseNotification) if the send
264
+ // fails, so a legitimate retry is not blocked — this replaces the old record-only-on-success
265
+ // scheme while still avoiding cache poisoning on failure.
266
+ notificationCache.set(key, now);
260
267
  return false;
261
268
  }
262
269
 
263
- /** Record a notification key in the dedup cache after successful delivery. */
270
+ function notificationKey(title: string, message: string, data?: NotificationData): string {
271
+ const dataRecord = data as (NotificationData & { dedupKey?: string }) | undefined;
272
+ return dataRecord?.dedupKey ?? `${title}|${message}|${data?.category || 'unknown'}`;
273
+ }
274
+
275
+ /** Refresh a reserved dedup key after successful delivery (keeps the window measured from send). */
264
276
  function recordNotification(title: string, message: string, data?: NotificationData): void {
265
- const key = `${title}|${message}|${data?.category || 'unknown'}`;
266
- notificationCache.set(key, Date.now());
277
+ notificationCache.set(notificationKey(title, message, data), Date.now());
278
+ }
279
+
280
+ /** Release a reserved dedup key when delivery failed, so a legitimate retry is not blocked. */
281
+ function releaseNotification(title: string, message: string, data?: NotificationData): void {
282
+ notificationCache.delete(notificationKey(title, message, data));
267
283
  }
268
284
 
269
285
  // TODO(refactor): extract body parsing, filtering, and platform routing into
@@ -302,6 +318,11 @@ export const POST: RequestHandler = async ({ request }) => {
302
318
  const waitForResponse =
303
319
  typeof body.waitForResponse === 'boolean' ? body.waitForResponse : false;
304
320
 
321
+ // forcePush: when true, send the push even if WS clients are connected.
322
+ // Used by the autopilot engine for high-signal notifications.
323
+ // Deduplication still applies — only the WS-client skip is bypassed.
324
+ const forcePush = typeof body.forcePush === 'boolean' ? body.forcePush : false;
325
+
305
326
  // Dynamic-options fields (PR-2/PR-3). When the caller wants to drive
306
327
  // a richer notification category — plan-mode approval, MCP
307
328
  // elicitation, AskUserQuestion choices — these arrive in the top
@@ -375,7 +396,8 @@ export const POST: RequestHandler = async ({ request }) => {
375
396
  // (for bidirectional permission polling) without actually sending a push
376
397
  // notification. This happens when WebSocket clients are connected and the
377
398
  // events channel will broadcast the permission-requested event instead.
378
- if (skipPush) {
399
+ // forcePush overrides this so high-signal autopilot pushes still reach the device.
400
+ if (skipPush && !forcePush) {
379
401
  if (waitForResponse) {
380
402
  createPendingRequest(canonicalRequestId, {
381
403
  options,
@@ -484,6 +506,7 @@ export const POST: RequestHandler = async ({ request }) => {
484
506
  });
485
507
  } else {
486
508
  console.error(`[notify] FCM delivery failed: ${fcmResult.error}`);
509
+ releaseNotification(title, message, data); // free the reserved dedup slot for a retry
487
510
 
488
511
  addNotification(
489
512
  buildNotificationRecord(
@@ -564,6 +587,7 @@ export const POST: RequestHandler = async ({ request }) => {
564
587
  } catch (notificationError) {
565
588
  const notifErrMsg = toErrorMessage(notificationError);
566
589
  console.error(`[notify] APNs delivery failed: ${notifErrMsg}`);
590
+ releaseNotification(title, message, data); // free the reserved dedup slot for a retry
567
591
 
568
592
  addNotification(
569
593
  buildNotificationRecord(canonicalRequestId, title, message, 'failed', data, notifErrMsg)
@@ -0,0 +1,39 @@
1
+ // Viewer-presence endpoint. The dashboard / phone posts a heartbeat so the autopilot
2
+ // engine can push only when the user is AWAY (not foregrounded) — see presence-store.ts.
3
+ // Distinct from raw WebSocket connection count, which the autonomous loop keeps open.
4
+
5
+ import { validateAuth } from '$lib/modules/server/auth';
6
+ import {
7
+ hasEverReported,
8
+ isViewerPresent,
9
+ reportPresence,
10
+ } from '$lib/modules/server/ws/presence-store';
11
+ import { json } from '@sveltejs/kit';
12
+
13
+ import type { RequestHandler } from './$types';
14
+
15
+ export const GET: RequestHandler = ({ request }) => {
16
+ const authError = validateAuth(request);
17
+ if (authError) {
18
+ return authError;
19
+ }
20
+ return json({ everReported: hasEverReported(), present: isViewerPresent() });
21
+ };
22
+
23
+ export const POST: RequestHandler = async ({ request }) => {
24
+ const authError = validateAuth(request);
25
+ if (authError) {
26
+ return authError;
27
+ }
28
+ let body: { state?: unknown };
29
+ try {
30
+ body = (await request.json()) as { state?: unknown };
31
+ } catch {
32
+ return json({ error: 'Invalid JSON body' }, { status: 400 });
33
+ }
34
+ if (body.state !== 'foreground' && body.state !== 'background') {
35
+ return json({ error: "state must be 'foreground' or 'background'" }, { status: 400 });
36
+ }
37
+ reportPresence(body.state);
38
+ return json({ everReported: hasEverReported(), present: isViewerPresent(), state: body.state });
39
+ };