@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.
- package/.claude/hooks/notifier.cjs +94 -1
- package/build/client/_app/immutable/assets/2.JWRrnR-w.css +1 -0
- package/build/client/_app/immutable/assets/2.JWRrnR-w.css.br +0 -0
- package/build/client/_app/immutable/assets/2.JWRrnR-w.css.gz +0 -0
- package/build/client/_app/immutable/chunks/{ZS5XYDx_.js → B1bOvemT.js} +1 -1
- package/build/client/_app/immutable/chunks/B1bOvemT.js.br +0 -0
- package/build/client/_app/immutable/chunks/{ZS5XYDx_.js.gz → B1bOvemT.js.gz} +0 -0
- package/build/client/_app/immutable/chunks/BfbPKMXz.js +3 -0
- package/build/client/_app/immutable/chunks/BfbPKMXz.js.br +0 -0
- package/build/client/_app/immutable/chunks/BfbPKMXz.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C4Hns_Wl.js +1 -0
- package/build/client/_app/immutable/chunks/C4Hns_Wl.js.br +0 -0
- package/build/client/_app/immutable/chunks/C4Hns_Wl.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C87ZRWX0.js +1 -0
- package/build/client/_app/immutable/chunks/C87ZRWX0.js.br +0 -0
- package/build/client/_app/immutable/chunks/C87ZRWX0.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{BvmdJful.js → CJulw9ux.js} +1 -1
- package/build/client/_app/immutable/chunks/CJulw9ux.js.br +0 -0
- package/build/client/_app/immutable/chunks/CJulw9ux.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DT4H19pV.js → CZg4kn4E.js} +1 -1
- package/build/client/_app/immutable/chunks/CZg4kn4E.js.br +0 -0
- package/build/client/_app/immutable/chunks/CZg4kn4E.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DIZ3Qst5.js → DhK7PwI_.js} +1 -1
- package/build/client/_app/immutable/chunks/DhK7PwI_.js.br +0 -0
- package/build/client/_app/immutable/chunks/{DIZ3Qst5.js.gz → DhK7PwI_.js.gz} +0 -0
- package/build/client/_app/immutable/chunks/{ClIPTXf3.js → DomZZqvG.js} +1 -1
- package/build/client/_app/immutable/chunks/DomZZqvG.js.br +0 -0
- package/build/client/_app/immutable/chunks/DomZZqvG.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{BB2l8o4X.js → i5iZvmIH.js} +1 -1
- package/build/client/_app/immutable/chunks/i5iZvmIH.js.br +0 -0
- package/build/client/_app/immutable/chunks/i5iZvmIH.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.Bd-DfeJi.js → app.CTqz33nP.js} +2 -2
- package/build/client/_app/immutable/entry/app.CTqz33nP.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CTqz33nP.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.Dj-Kvgwo.js +1 -0
- package/build/client/_app/immutable/entry/start.Dj-Kvgwo.js.br +2 -0
- package/build/client/_app/immutable/entry/start.Dj-Kvgwo.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.Qn7Ktiht.js +10 -0
- package/build/client/_app/immutable/nodes/0.Qn7Ktiht.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Qn7Ktiht.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.DT4dq6Ay.js → 1.BxWOfNlo.js} +1 -1
- package/build/client/_app/immutable/nodes/1.BxWOfNlo.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.BxWOfNlo.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{10.CF7RGXpe.js → 10.BGPYD1s1.js} +1 -1
- package/build/client/_app/immutable/nodes/10.BGPYD1s1.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.BGPYD1s1.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{11.BV_G7yLI.js → 11.BxY1PUjC.js} +1 -1
- package/build/client/_app/immutable/nodes/11.BxY1PUjC.js.br +0 -0
- package/build/client/_app/immutable/nodes/11.BxY1PUjC.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.Bc2qALkX.js +23 -0
- package/build/client/_app/immutable/nodes/2.Bc2qALkX.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.Bc2qALkX.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{3.0MMe3oxR.js → 3.N2-A8noI.js} +1 -1
- package/build/client/_app/immutable/nodes/3.N2-A8noI.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.N2-A8noI.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{4.CBX9A3ka.js → 4.BFYS2g9C.js} +3 -3
- package/build/client/_app/immutable/nodes/4.BFYS2g9C.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.BFYS2g9C.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{5.DIVKuZc9.js → 5.DziEu9rx.js} +1 -1
- package/build/client/_app/immutable/nodes/5.DziEu9rx.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.DziEu9rx.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{6.ComiWlV6.js → 6.BWF9Qx6F.js} +1 -1
- package/build/client/_app/immutable/nodes/6.BWF9Qx6F.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.BWF9Qx6F.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{7.vkPx1kVP.js → 7.DHuDIdpz.js} +1 -1
- package/build/client/_app/immutable/nodes/7.DHuDIdpz.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.DHuDIdpz.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{8.Bmr3sWbS.js → 8.D0Ijt9Vv.js} +1 -1
- package/build/client/_app/immutable/nodes/8.D0Ijt9Vv.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.D0Ijt9Vv.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{9.CAJucyeI.js → 9.2Piwo35J.js} +1 -1
- package/build/client/_app/immutable/nodes/9.2Piwo35J.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.2Piwo35J.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/pty-holder.cjs +6 -0
- package/build/server/chunks/{0-DDGB6CRT.js → 0-CVGsyVKN.js} +4 -2
- package/build/server/chunks/0-CVGsyVKN.js.map +1 -0
- package/build/server/chunks/{1-DEjonQXD.js → 1-BAlAsKdp.js} +2 -2
- package/build/server/chunks/{1-DEjonQXD.js.map → 1-BAlAsKdp.js.map} +1 -1
- package/build/server/chunks/{10-BK1kiiiw.js → 10-BUCX7Aqz.js} +2 -2
- package/build/server/chunks/{10-BK1kiiiw.js.map → 10-BUCX7Aqz.js.map} +1 -1
- package/build/server/chunks/{11-CJPjkEF3.js → 11-DHPvc2yA.js} +2 -2
- package/build/server/chunks/{11-CJPjkEF3.js.map → 11-DHPvc2yA.js.map} +1 -1
- package/build/server/chunks/{2-RLnhlWh5.js → 2-DLOMdCHW.js} +4 -4
- package/build/server/chunks/{2-RLnhlWh5.js.map → 2-DLOMdCHW.js.map} +1 -1
- package/build/server/chunks/{3-Dd4pJBqZ.js → 3-DCf69LYo.js} +2 -2
- package/build/server/chunks/{3-Dd4pJBqZ.js.map → 3-DCf69LYo.js.map} +1 -1
- package/build/server/chunks/{4-Bb5VFhsO.js → 4-D92KnTmb.js} +3 -3
- package/build/server/chunks/{4-Bb5VFhsO.js.map → 4-D92KnTmb.js.map} +1 -1
- package/build/server/chunks/{5-oNoWuIsn.js → 5-D-Uv1voC.js} +2 -2
- package/build/server/chunks/{5-oNoWuIsn.js.map → 5-D-Uv1voC.js.map} +1 -1
- package/build/server/chunks/{6-DdRMnKNa.js → 6-DUrC2Naz.js} +2 -2
- package/build/server/chunks/{6-DdRMnKNa.js.map → 6-DUrC2Naz.js.map} +1 -1
- package/build/server/chunks/{7-vLOMMetm.js → 7-TXwjMHt2.js} +2 -2
- package/build/server/chunks/{7-vLOMMetm.js.map → 7-TXwjMHt2.js.map} +1 -1
- package/build/server/chunks/{8-rJyiQLFs.js → 8-D2X_jBsT.js} +2 -2
- package/build/server/chunks/{8-rJyiQLFs.js.map → 8-D2X_jBsT.js.map} +1 -1
- package/build/server/chunks/{9-CVSNNYED.js → 9-DK0hH5Xa.js} +2 -2
- package/build/server/chunks/{9-CVSNNYED.js.map → 9-DK0hH5Xa.js.map} +1 -1
- package/build/server/chunks/Banner-BgaAs1rs.js.map +1 -1
- package/build/server/chunks/Button-D0hZ7JYt.js.map +1 -1
- package/build/server/chunks/Icon-D0GBnDcs.js.map +1 -1
- package/build/server/chunks/Input-OmIiydSx.js.map +1 -1
- package/build/server/chunks/Pill-4xJ-VhAA.js.map +1 -1
- package/build/server/chunks/Shimmer-Dw2uvTC1.js.map +1 -1
- package/build/server/chunks/_error.svelte-CZnkxeLr.js.map +1 -1
- package/build/server/chunks/_layout.svelte-DfgNGGiM.js.map +1 -1
- package/build/server/chunks/_page.svelte-8OFzwdNA.js +758 -0
- package/build/server/chunks/_page.svelte-8OFzwdNA.js.map +1 -0
- package/build/server/chunks/_page.svelte-BTlfUsBp.js.map +1 -1
- package/build/server/chunks/_page.svelte-C7B0qdrC.js.map +1 -1
- package/build/server/chunks/{_page.svelte-BLo2v_8E.js → _page.svelte-Gv9p8nlS.js} +3 -4
- package/build/server/chunks/_page.svelte-Gv9p8nlS.js.map +1 -0
- package/build/server/chunks/_page.svelte-dabsQl9c.js.map +1 -1
- package/build/server/chunks/{_server.ts-bk_EeAdY.js → _server.ts-05JJOdcX.js} +20 -14
- package/build/server/chunks/_server.ts-05JJOdcX.js.map +1 -0
- package/build/server/chunks/{_server.ts-DEx9-epI.js → _server.ts-B54Pvhgc.js} +4 -3
- package/build/server/chunks/_server.ts-B54Pvhgc.js.map +1 -0
- package/build/server/chunks/_server.ts-BB46Fbqn.js +59 -0
- package/build/server/chunks/_server.ts-BB46Fbqn.js.map +1 -0
- package/build/server/chunks/{_server.ts-BRAzC6W1.js → _server.ts-BCljU9Sg.js} +33 -8
- package/build/server/chunks/_server.ts-BCljU9Sg.js.map +1 -0
- package/build/server/chunks/{_server.ts-D-vgx5UZ.js → _server.ts-BTmknWpO.js} +2 -2
- package/build/server/chunks/{_server.ts-D-vgx5UZ.js.map → _server.ts-BTmknWpO.js.map} +1 -1
- package/build/server/chunks/{_server.ts-tChyh9FX.js → _server.ts-BXhmLZwN.js} +4 -2
- package/build/server/chunks/{_server.ts-tChyh9FX.js.map → _server.ts-BXhmLZwN.js.map} +1 -1
- package/build/server/chunks/{_server.ts-CvJKTS3Z.js → _server.ts-BbRSpB74.js} +4 -2
- package/build/server/chunks/{_server.ts-CvJKTS3Z.js.map → _server.ts-BbRSpB74.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DKNIsQeH.js → _server.ts-Bol54_Qo.js} +4 -3
- package/build/server/chunks/_server.ts-Bol54_Qo.js.map +1 -0
- package/build/server/chunks/{_server.ts-Dz9Jd9Jh.js → _server.ts-C0PO_cAu.js} +4 -3
- package/build/server/chunks/{_server.ts-Dz9Jd9Jh.js.map → _server.ts-C0PO_cAu.js.map} +1 -1
- package/build/server/chunks/_server.ts-C6NRpe7e.js +33 -0
- package/build/server/chunks/_server.ts-C6NRpe7e.js.map +1 -0
- package/build/server/chunks/_server.ts-CGqCOCdK.js +53 -0
- package/build/server/chunks/_server.ts-CGqCOCdK.js.map +1 -0
- package/build/server/chunks/{_server.ts-B2wIgsW4.js → _server.ts-CZb-BI5H.js} +4 -3
- package/build/server/chunks/_server.ts-CZb-BI5H.js.map +1 -0
- package/build/server/chunks/_server.ts-DPHRUFYS.js +159 -0
- package/build/server/chunks/_server.ts-DPHRUFYS.js.map +1 -0
- package/build/server/chunks/{_server.ts-AnBXfZXh.js → _server.ts-D_WRex0k.js} +5 -3
- package/build/server/chunks/_server.ts-D_WRex0k.js.map +1 -0
- package/build/server/chunks/{_server.ts-CJGyN8mw.js → _server.ts-DiBMY7Ho.js} +4 -3
- package/build/server/chunks/_server.ts-DiBMY7Ho.js.map +1 -0
- package/build/server/chunks/cache-BlMaDsHi.js.map +1 -1
- package/build/server/chunks/{guest-registry-t0-7Zv5q.js → guest-registry-Dxvd7p-g.js} +10 -1
- package/build/server/chunks/guest-registry-Dxvd7p-g.js.map +1 -0
- package/build/server/chunks/index-CoYB03g7.js.map +1 -1
- package/build/server/chunks/index2-dSGQ9Eaa.js.map +1 -1
- package/build/server/chunks/{library-apns-Dl3iRE2h.js → library-apns-D8RPINlv.js} +62 -7
- package/build/server/chunks/library-apns-D8RPINlv.js.map +1 -0
- package/build/server/chunks/{pending-requests-C9p57WoU.js → pending-requests-8rWjrF6d.js} +3 -2
- package/build/server/chunks/pending-requests-8rWjrF6d.js.map +1 -0
- package/build/server/chunks/presence-store-Bx_g0-Gd.js +23 -0
- package/build/server/chunks/presence-store-Bx_g0-Gd.js.map +1 -0
- package/build/server/chunks/{pty-manager-CkZNoW1t.js → pty-manager-CoWVT56F.js} +27 -6
- package/build/server/chunks/pty-manager-CoWVT56F.js.map +1 -0
- package/build/server/chunks/root-D4IoFC8F.js.map +1 -1
- package/build/server/chunks/shooter-home-4f_HkdGI.js +10 -0
- package/build/server/chunks/shooter-home-4f_HkdGI.js.map +1 -0
- package/build/server/chunks/state.svelte-CmHqngc_.js.map +1 -1
- package/build/server/index.js +1 -1
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +52 -24
- package/build/server/manifest.js.map +1 -1
- package/package.json +2 -2
- package/server.ts +2 -0
- package/src/lib/modules/client/common/index.ts +1 -0
- package/src/lib/modules/client/common/presence.ts +47 -0
- package/src/lib/modules/client/dashboard/AutopilotPanel.svelte +584 -0
- package/src/lib/modules/client/dashboard/autopilot-driver.svelte.ts +681 -0
- package/src/lib/modules/client/dashboard/decide-injection.ts +127 -0
- package/src/lib/modules/client/dashboard/index.ts +1 -0
- package/src/lib/modules/client/dashboard/store.svelte.ts +65 -24
- package/src/lib/modules/client/neurolink/fetch-proxy.ts +38 -1
- package/src/lib/modules/client/neurolink/provider-config.ts +13 -37
- package/src/lib/modules/server/apn/apns-payload.ts +50 -0
- package/src/lib/modules/server/apn/library-apns.ts +50 -8
- package/src/lib/modules/server/apn/pending-requests.ts +3 -1
- package/src/lib/modules/server/sessions/autopilot-context.ts +57 -0
- package/src/lib/modules/server/sessions/autopilot-engine.ts +451 -0
- package/src/lib/modules/server/sessions/litellm-client.ts +171 -0
- package/src/lib/modules/server/sessions/next-step-consensus.ts +210 -0
- package/src/lib/modules/server/sessions/summary-store.ts +113 -0
- package/src/lib/modules/server/terminal/agent-launch.ts +26 -0
- package/src/lib/modules/server/terminal/pty-holder.cjs +6 -0
- package/src/lib/modules/server/terminal/pty-manager.ts +13 -3
- package/src/lib/modules/server/terminal/session-watcher.ts +12 -2
- package/src/lib/modules/server/terminal/terminal-store.ts +3 -1
- package/src/lib/modules/server/utils/shooter-home.ts +16 -0
- package/src/lib/modules/server/ws/events-handler.ts +32 -0
- package/src/lib/modules/server/ws/presence-store.ts +50 -0
- package/src/lib/types/autopilot.ts +138 -0
- package/src/lib/types/index.ts +1 -0
- package/src/lib/types/terminal-client.ts +2 -0
- package/src/routes/+layout.server.ts +2 -0
- package/src/routes/+layout.svelte +19 -4
- package/src/routes/+page.svelte +9 -1
- package/src/routes/api/autopilot/+server.ts +74 -0
- package/src/routes/api/autopilot/goal/+server.ts +72 -0
- package/src/routes/api/neurolink-proxy/+server.ts +39 -8
- package/src/routes/api/notify/+server.ts +38 -14
- package/src/routes/api/presence/+server.ts +39 -0
- package/src/routes/api/summaries/+server.ts +99 -0
- package/src/routes/api/ws-status/+server.ts +8 -5
- package/build/client/_app/immutable/assets/2.DjiwkLqE.css +0 -1
- package/build/client/_app/immutable/assets/2.DjiwkLqE.css.br +0 -0
- package/build/client/_app/immutable/assets/2.DjiwkLqE.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BB2l8o4X.js.br +0 -0
- package/build/client/_app/immutable/chunks/BB2l8o4X.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BvmdJful.js.br +0 -0
- package/build/client/_app/immutable/chunks/BvmdJful.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CRkG7oE4.js +0 -1
- package/build/client/_app/immutable/chunks/CRkG7oE4.js.br +0 -0
- package/build/client/_app/immutable/chunks/CRkG7oE4.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C_YNQL8b.js +0 -3
- package/build/client/_app/immutable/chunks/C_YNQL8b.js.br +0 -0
- package/build/client/_app/immutable/chunks/C_YNQL8b.js.gz +0 -0
- package/build/client/_app/immutable/chunks/ClIPTXf3.js.br +0 -0
- package/build/client/_app/immutable/chunks/ClIPTXf3.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DIZ3Qst5.js.br +0 -0
- package/build/client/_app/immutable/chunks/DT4H19pV.js.br +0 -0
- package/build/client/_app/immutable/chunks/DT4H19pV.js.gz +0 -0
- package/build/client/_app/immutable/chunks/ZS5XYDx_.js.br +0 -0
- package/build/client/_app/immutable/chunks/pRcLbE0d.js +0 -1
- package/build/client/_app/immutable/chunks/pRcLbE0d.js.br +0 -0
- package/build/client/_app/immutable/chunks/pRcLbE0d.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.Bd-DfeJi.js.br +0 -0
- package/build/client/_app/immutable/entry/app.Bd-DfeJi.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.evvp4tX7.js +0 -1
- package/build/client/_app/immutable/entry/start.evvp4tX7.js.br +0 -2
- package/build/client/_app/immutable/entry/start.evvp4tX7.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.Bl-1LQWM.js +0 -10
- package/build/client/_app/immutable/nodes/0.Bl-1LQWM.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Bl-1LQWM.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.DT4dq6Ay.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.DT4dq6Ay.js.gz +0 -0
- package/build/client/_app/immutable/nodes/10.CF7RGXpe.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.CF7RGXpe.js.gz +0 -0
- package/build/client/_app/immutable/nodes/11.BV_G7yLI.js.br +0 -0
- package/build/client/_app/immutable/nodes/11.BV_G7yLI.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.DcRhsjYp.js +0 -13
- package/build/client/_app/immutable/nodes/2.DcRhsjYp.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DcRhsjYp.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.0MMe3oxR.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.0MMe3oxR.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.CBX9A3ka.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.CBX9A3ka.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.DIVKuZc9.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.DIVKuZc9.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.ComiWlV6.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.ComiWlV6.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.vkPx1kVP.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.vkPx1kVP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.Bmr3sWbS.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.Bmr3sWbS.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.CAJucyeI.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.CAJucyeI.js.gz +0 -0
- package/build/server/chunks/0-DDGB6CRT.js.map +0 -1
- package/build/server/chunks/_page.svelte-BLo2v_8E.js.map +0 -1
- package/build/server/chunks/_page.svelte-tBuIq8Pg.js +0 -159
- package/build/server/chunks/_page.svelte-tBuIq8Pg.js.map +0 -1
- package/build/server/chunks/_server.ts-AnBXfZXh.js.map +0 -1
- package/build/server/chunks/_server.ts-B2wIgsW4.js.map +0 -1
- package/build/server/chunks/_server.ts-BRAzC6W1.js.map +0 -1
- package/build/server/chunks/_server.ts-CJGyN8mw.js.map +0 -1
- package/build/server/chunks/_server.ts-DEx9-epI.js.map +0 -1
- package/build/server/chunks/_server.ts-DKNIsQeH.js.map +0 -1
- package/build/server/chunks/_server.ts-bk_EeAdY.js.map +0 -1
- package/build/server/chunks/guest-registry-t0-7Zv5q.js.map +0 -1
- package/build/server/chunks/library-apns-Dl3iRE2h.js.map +0 -1
- package/build/server/chunks/pending-requests-C9p57WoU.js.map +0 -1
- package/build/server/chunks/pty-manager-CkZNoW1t.js.map +0 -1
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
// Always-on autopilot engine (server-side).
|
|
2
|
+
//
|
|
3
|
+
// Subscribes to the global ShooterEvent stream, tracks per-session state across
|
|
4
|
+
// ALL threads, and on a meaningful trigger runs: one LiteLLM summary + five
|
|
5
|
+
// DISTINCT-LENS next-step agents (blocker / next-command / risk / validation /
|
|
6
|
+
// progress) -> consensus merge -> persist to SQLite -> push to the phone (using
|
|
7
|
+
// the normal skip logic, so it only pushes when no dashboard is watching).
|
|
8
|
+
//
|
|
9
|
+
// Lives in the server.ts module graph (started from server.ts). Cross-graph
|
|
10
|
+
// state (events, store, control) is shared via globalThis singletons.
|
|
11
|
+
|
|
12
|
+
import type { AgentProposal, NextStep, SessionSummaryRecord, WireShooterEvent } from '$lib/types';
|
|
13
|
+
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
|
|
17
|
+
import { ptyManager } from '../terminal/pty-manager.js';
|
|
18
|
+
import { shooterDataDir } from '../utils/shooter-home.js';
|
|
19
|
+
import { onShooterEvent } from '../ws/events-handler.js';
|
|
20
|
+
import { isViewerPresent } from '../ws/presence-store.js';
|
|
21
|
+
import {
|
|
22
|
+
buildEngineContext,
|
|
23
|
+
clearEngineGoal,
|
|
24
|
+
getEngineGoal,
|
|
25
|
+
setEngineGoal,
|
|
26
|
+
} from './autopilot-context.js';
|
|
27
|
+
import { isLiteLLMConfigured, litellmJson } from './litellm-client.js';
|
|
28
|
+
import { mergeNextStepConsensus } from './next-step-consensus.js';
|
|
29
|
+
import { summaryStore } from './summary-store.js';
|
|
30
|
+
|
|
31
|
+
// ── Constants ────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
const HIGH_SIGNAL = new Set(['agent-idle', 'agent-question', 'tool-failed']);
|
|
34
|
+
const GRACE_MS = 4_000; // fire this long after the FIRST qualifying event (fast, not resetting)
|
|
35
|
+
const MIN_INTERVAL_MS = 30_000; // minimum gap between pipeline runs per session (cost bound)
|
|
36
|
+
const PERIODIC_EVERY = 20;
|
|
37
|
+
const ERROR_THRESHOLD = 3;
|
|
38
|
+
const MAX_EVENTS = 60;
|
|
39
|
+
const SUMMARY_MAX_TOKENS = 400;
|
|
40
|
+
const STEPS_MAX_TOKENS = 400;
|
|
41
|
+
// How many of the five lenses run concurrently. Kept BELOW the typical LiteLLM key parallel cap so
|
|
42
|
+
// the engine never saturates the key by itself: firing all five at once exhausted a
|
|
43
|
+
// max_parallel_requests=5 key and 429'd every call (empty consensus, silent stall). Default 3
|
|
44
|
+
// leaves headroom for the summary's retry + other callers sharing the key. Tune per key via env.
|
|
45
|
+
const LENS_CONCURRENCY = Math.max(1, Number(process.env.AUTOPILOT_LENS_CONCURRENCY) || 3);
|
|
46
|
+
// Consensus quorum: how many of the 5 distinct lenses must agree for a step to be non-tentative
|
|
47
|
+
// (and thus auto-injectable). The lenses are DIFFERENT perspectives that phrase the same action
|
|
48
|
+
// differently, so the Jaccard clustering systematically undercounts true agreement (observed: 4/5
|
|
49
|
+
// lenses proposed the same fix but only 2 clustered). 2-of-5 + the confidence floor + the eight
|
|
50
|
+
// driver guards (idle-only, managed-only, rate-limit, dedup, circuit-breaker, command-guard,
|
|
51
|
+
// kill-switch, human-grace) is the practical bar; quorum 3 left the autopilot almost never firing.
|
|
52
|
+
const CONSENSUS_QUORUM = 2;
|
|
53
|
+
const STATE_FILE = join(shooterDataDir(), 'autopilot.json');
|
|
54
|
+
|
|
55
|
+
// The engine does STRUCTURED extraction (one summary + five voting lenses). A reasoning model like
|
|
56
|
+
// `open-large` does this badly: it reasons out loud and ignores response_format, so the JSON never
|
|
57
|
+
// parses and the consensus collapses to tentative garbage. Pin a fast NON-reasoning model for the
|
|
58
|
+
// pipeline, independent of the user's chat model (LITELLM_MODEL). Overridable via AUTOPILOT_MODEL.
|
|
59
|
+
const ENGINE_MODEL = process.env.AUTOPILOT_MODEL?.trim() || 'open-fast';
|
|
60
|
+
|
|
61
|
+
// Lead with the JSON-only contract; the per-lens perspective is a TRAILING modifier so the model
|
|
62
|
+
// doesn't start "thinking" about a role. No copyable placeholder value in the schema — reasoning
|
|
63
|
+
// models echo it verbatim ("a real shell command or instruction" leaked into real consensus).
|
|
64
|
+
const JSON_API_RULES =
|
|
65
|
+
'You are a JSON API. Output ONLY one JSON object and nothing else — no prose, no reasoning, no ' +
|
|
66
|
+
'markdown, no code fences. The first character MUST be { and the last MUST be }.';
|
|
67
|
+
|
|
68
|
+
/** Five DISTINCT perspectives — each a different angle, so converging votes mean real agreement. */
|
|
69
|
+
const LENSES = [
|
|
70
|
+
'what is currently blocking progress, or is most likely to fail next',
|
|
71
|
+
'the exact shell commands or file edits the agent should run next, in order',
|
|
72
|
+
'what could go wrong if the agent continues on its current path, and what to validate first',
|
|
73
|
+
'how to verify the work so far is correct — the tests, checks, or inspections to run',
|
|
74
|
+
'the single most direct next step toward completing the session goal',
|
|
75
|
+
] as const;
|
|
76
|
+
|
|
77
|
+
// ── Per-session state (globalThis-shared) ───────────────────────────
|
|
78
|
+
|
|
79
|
+
// eslint-disable-next-line no-restricted-syntax -- internal engine state, never exported
|
|
80
|
+
interface EngineSession {
|
|
81
|
+
cancelled: boolean;
|
|
82
|
+
errorCount: number;
|
|
83
|
+
eventCount: number;
|
|
84
|
+
events: string[];
|
|
85
|
+
// Highest signal + latest trigger seen during the OPEN grace window — evaluated at FIRE time, not
|
|
86
|
+
// schedule time, so an agent-idle arriving after a low-signal event still runs as a high trigger.
|
|
87
|
+
graceIsHigh: boolean;
|
|
88
|
+
graceTimer: null | ReturnType<typeof setTimeout>;
|
|
89
|
+
graceTrigger: string;
|
|
90
|
+
lastRunAt: number;
|
|
91
|
+
projectName: string;
|
|
92
|
+
running: boolean;
|
|
93
|
+
status: string;
|
|
94
|
+
terminalId: string;
|
|
95
|
+
toolCallCount: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const SESSIONS_KEY = '__shooter_autopilot_sessions';
|
|
99
|
+
const sessions: Map<string, EngineSession> =
|
|
100
|
+
((globalThis as Record<string, unknown>)[SESSIONS_KEY] as Map<string, EngineSession>) ||
|
|
101
|
+
new Map<string, EngineSession>();
|
|
102
|
+
(globalThis as Record<string, unknown>)[SESSIONS_KEY] = sessions;
|
|
103
|
+
|
|
104
|
+
let enabled = readPersistedEnabled();
|
|
105
|
+
let unsubscribe: (() => void) | null = null;
|
|
106
|
+
|
|
107
|
+
// ── Control (exposed on globalThis so the /api/autopilot route can reach it) ──
|
|
108
|
+
|
|
109
|
+
export function isAutopilotEnabled(): boolean {
|
|
110
|
+
return enabled;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function setAutopilotEnabled(value: boolean): void {
|
|
114
|
+
enabled = value;
|
|
115
|
+
try {
|
|
116
|
+
mkdirSync(shooterDataDir(), { recursive: true });
|
|
117
|
+
writeFileSync(STATE_FILE, JSON.stringify({ enabled: value }), 'utf-8');
|
|
118
|
+
} catch {
|
|
119
|
+
// best-effort persistence
|
|
120
|
+
}
|
|
121
|
+
if (!value) {
|
|
122
|
+
for (const s of sessions.values()) {
|
|
123
|
+
if (s.graceTimer) {
|
|
124
|
+
clearTimeout(s.graceTimer);
|
|
125
|
+
s.graceTimer = null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Start the engine: subscribe to the event stream. Idempotent. */
|
|
132
|
+
export function startAutopilotEngine(): void {
|
|
133
|
+
if (unsubscribe) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const control = {
|
|
137
|
+
getGoal: getEngineGoal,
|
|
138
|
+
isEnabled: isAutopilotEnabled,
|
|
139
|
+
setEnabled: setAutopilotEnabled,
|
|
140
|
+
setGoal: setEngineGoal,
|
|
141
|
+
};
|
|
142
|
+
(globalThis as Record<string, unknown>).__shooter_autopilot = control;
|
|
143
|
+
unsubscribe = onShooterEvent(handleEvent);
|
|
144
|
+
// Pre-track sessions for terminals that already exist at startup (e.g. a server restart that
|
|
145
|
+
// reconnected persisted terminals). Without this a session is only created lazily on its NEXT
|
|
146
|
+
// event, so an agent that went idle before the engine attached would be invisible until it emits
|
|
147
|
+
// again. We deliberately do NOT force a pipeline run here — that would risk spurious LLM spend on
|
|
148
|
+
// a genuinely quiet terminal; we just ensure the session is tracked so the next event triggers.
|
|
149
|
+
try {
|
|
150
|
+
for (const term of ptyManager.list()) {
|
|
151
|
+
if (!sessions.has(term.id)) {
|
|
152
|
+
createSession(term.id);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
// ptyManager not ready yet — sessions will be created lazily on first event
|
|
157
|
+
}
|
|
158
|
+
console.log(
|
|
159
|
+
`[autopilot] engine started (enabled=${enabled}, litellm=${isLiteLLMConfigured() ? 'configured' : 'absent'}, lensConcurrency=${LENS_CONCURRENCY})`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── Event handling ───────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
function applyEvent(session: EngineSession, event: WireShooterEvent): void {
|
|
166
|
+
const parts: string[] = [event.type];
|
|
167
|
+
if ('tool' in event && event.tool) {
|
|
168
|
+
parts.push(`tool=${event.tool}`);
|
|
169
|
+
}
|
|
170
|
+
if ('error' in event && event.error) {
|
|
171
|
+
parts.push(`error=${event.error.slice(0, 140)}`);
|
|
172
|
+
}
|
|
173
|
+
if ('command' in event && event.command) {
|
|
174
|
+
parts.push(`cmd=${event.command.slice(0, 80)}`);
|
|
175
|
+
}
|
|
176
|
+
if ('message' in event && event.message) {
|
|
177
|
+
// The agent's last message on idle (the OUTCOME — e.g. "the test failed because…") is the
|
|
178
|
+
// richest signal the lenses have; keep enough of it to actually ground the next-step votes.
|
|
179
|
+
parts.push(`msg=${event.message.slice(0, 400)}`);
|
|
180
|
+
}
|
|
181
|
+
session.events.push(parts.join(' '));
|
|
182
|
+
if (session.events.length > MAX_EVENTS) {
|
|
183
|
+
session.events.shift();
|
|
184
|
+
}
|
|
185
|
+
switch (event.type) {
|
|
186
|
+
case 'agent-idle':
|
|
187
|
+
case 'agent-question':
|
|
188
|
+
session.status = 'idle';
|
|
189
|
+
break;
|
|
190
|
+
case 'tool-completed':
|
|
191
|
+
session.toolCallCount += 1;
|
|
192
|
+
break;
|
|
193
|
+
case 'tool-failed':
|
|
194
|
+
session.errorCount += 1;
|
|
195
|
+
break;
|
|
196
|
+
case 'tool-started':
|
|
197
|
+
session.toolCallCount += 1;
|
|
198
|
+
session.status = 'running';
|
|
199
|
+
break;
|
|
200
|
+
default:
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function createSession(terminalId: string): EngineSession {
|
|
206
|
+
const session: EngineSession = {
|
|
207
|
+
cancelled: false,
|
|
208
|
+
errorCount: 0,
|
|
209
|
+
eventCount: 0,
|
|
210
|
+
events: [],
|
|
211
|
+
graceIsHigh: false,
|
|
212
|
+
graceTimer: null,
|
|
213
|
+
graceTrigger: '',
|
|
214
|
+
lastRunAt: 0,
|
|
215
|
+
projectName: projectNameFor(terminalId),
|
|
216
|
+
running: false,
|
|
217
|
+
status: 'running',
|
|
218
|
+
terminalId,
|
|
219
|
+
toolCallCount: 0,
|
|
220
|
+
};
|
|
221
|
+
sessions.set(terminalId, session);
|
|
222
|
+
return session;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function fallbackSummary(session: EngineSession): string {
|
|
226
|
+
return `${session.status} — ${session.toolCallCount} tool calls, ${session.errorCount} errors`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function handleEvent(event: WireShooterEvent): void {
|
|
230
|
+
const terminalId = 'terminalId' in event ? event.terminalId : undefined;
|
|
231
|
+
if (!terminalId) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (event.type === 'terminal-exited') {
|
|
236
|
+
const s = sessions.get(terminalId);
|
|
237
|
+
if (s) {
|
|
238
|
+
s.cancelled = true; // signal any in-flight pipeline to stop before persist/push
|
|
239
|
+
if (s.graceTimer) {
|
|
240
|
+
clearTimeout(s.graceTimer);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
sessions.delete(terminalId);
|
|
244
|
+
clearEngineGoal(terminalId);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const session = sessions.get(terminalId) ?? createSession(terminalId);
|
|
249
|
+
session.eventCount += 1;
|
|
250
|
+
applyEvent(session, event);
|
|
251
|
+
|
|
252
|
+
if (!enabled || session.running) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const isHigh = HIGH_SIGNAL.has(event.type);
|
|
256
|
+
const isPeriodic = session.eventCount % PERIODIC_EVERY === 0;
|
|
257
|
+
const isErrorThreshold = session.errorCount >= ERROR_THRESHOLD;
|
|
258
|
+
if (!isHigh && !isPeriodic && !isErrorThreshold) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (Date.now() - session.lastRunAt < MIN_INTERVAL_MS) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
// Track the strongest signal + latest trigger across the whole window (so a high-signal event
|
|
265
|
+
// arriving after the timer was armed by a low-signal one is not lost). Schedule the timer ONCE;
|
|
266
|
+
// do NOT reset it on every event (so a burst doesn't keep delaying the run).
|
|
267
|
+
if (!session.graceTimer) {
|
|
268
|
+
session.graceIsHigh = isHigh;
|
|
269
|
+
session.graceTrigger = event.type;
|
|
270
|
+
session.graceTimer = setTimeout(() => {
|
|
271
|
+
session.graceTimer = null;
|
|
272
|
+
const trigger = session.graceTrigger;
|
|
273
|
+
const wasHigh = session.graceIsHigh;
|
|
274
|
+
void runPipeline(session, trigger, wasHigh);
|
|
275
|
+
}, GRACE_MS);
|
|
276
|
+
} else {
|
|
277
|
+
session.graceIsHigh = session.graceIsHigh || isHigh;
|
|
278
|
+
session.graceTrigger = event.type;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ── Pipeline ─────────────────────────────────────────────────────────
|
|
283
|
+
|
|
284
|
+
/** Run `fn` over `items` with at most `limit` in flight; preserves input order. */
|
|
285
|
+
async function mapLimit<T, R>(
|
|
286
|
+
items: readonly T[],
|
|
287
|
+
limit: number,
|
|
288
|
+
fn: (item: T) => Promise<R>
|
|
289
|
+
): Promise<R[]> {
|
|
290
|
+
const results: R[] = new Array<R>(items.length);
|
|
291
|
+
let next = 0;
|
|
292
|
+
const worker = async (): Promise<void> => {
|
|
293
|
+
while (next < items.length) {
|
|
294
|
+
const idx = next++;
|
|
295
|
+
results[idx] = await fn(items[idx]);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
|
|
299
|
+
return results;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function persist(
|
|
303
|
+
session: EngineSession,
|
|
304
|
+
summary: string,
|
|
305
|
+
steps: NextStep[],
|
|
306
|
+
trigger: string
|
|
307
|
+
): void {
|
|
308
|
+
const record: SessionSummaryRecord = {
|
|
309
|
+
createdAt: new Date().toISOString(),
|
|
310
|
+
id: `auto-${session.terminalId}-${Date.now()}`,
|
|
311
|
+
nextSteps: JSON.stringify(steps),
|
|
312
|
+
projectName: session.projectName,
|
|
313
|
+
// No separate JSONL session UUID server-side; key on terminalId so
|
|
314
|
+
// GET /api/summaries?sessionId=<terminalId> works (was previously null).
|
|
315
|
+
sessionId: session.terminalId,
|
|
316
|
+
summary,
|
|
317
|
+
terminalId: session.terminalId,
|
|
318
|
+
trigger,
|
|
319
|
+
};
|
|
320
|
+
try {
|
|
321
|
+
summaryStore.insert(record);
|
|
322
|
+
} catch (err) {
|
|
323
|
+
console.error('[autopilot] persist failed:', err instanceof Error ? err.message : String(err));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function projectNameFor(terminalId: string): string {
|
|
328
|
+
try {
|
|
329
|
+
const term = ptyManager.list().find((t) => t.id === terminalId);
|
|
330
|
+
if (term?.cwd) {
|
|
331
|
+
const segs = term.cwd.split('/').filter(Boolean);
|
|
332
|
+
return segs.slice(-2).join('/') || terminalId;
|
|
333
|
+
}
|
|
334
|
+
} catch {
|
|
335
|
+
// ptyManager unavailable — fall through
|
|
336
|
+
}
|
|
337
|
+
return terminalId;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function push(session: EngineSession, summary: string, steps: NextStep[]): Promise<void> {
|
|
341
|
+
const top = steps[0];
|
|
342
|
+
if (!top) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
// Presence-aware: when a viewer is foregrounded (watching the live dashboard) skip the
|
|
346
|
+
// push — they see it in-app. Push only when away. If no presence-aware client ever
|
|
347
|
+
// reported, isViewerPresent() is false → push proceeds (prior always-push behavior).
|
|
348
|
+
if (isViewerPresent()) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const port = process.env.PORT || '54007';
|
|
352
|
+
const apiKey = process.env.API_KEY;
|
|
353
|
+
if (!apiKey) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
await fetch(`http://127.0.0.1:${port}/api/notify`, {
|
|
358
|
+
body: JSON.stringify({
|
|
359
|
+
data: {
|
|
360
|
+
category: session.terminalId,
|
|
361
|
+
dedupKey: `${session.terminalId}|${top.text.slice(0, 60)}`,
|
|
362
|
+
sessionId: session.terminalId,
|
|
363
|
+
source: 'autopilot',
|
|
364
|
+
},
|
|
365
|
+
message: `${summary.slice(0, 80)} — Next: ${top.text.slice(0, 60)}`,
|
|
366
|
+
title: `Autopilot: ${session.projectName}`,
|
|
367
|
+
}),
|
|
368
|
+
headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
|
|
369
|
+
method: 'POST',
|
|
370
|
+
});
|
|
371
|
+
} catch (err) {
|
|
372
|
+
console.warn('[autopilot] push failed:', err instanceof Error ? err.message : String(err));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
377
|
+
|
|
378
|
+
function readPersistedEnabled(): boolean {
|
|
379
|
+
try {
|
|
380
|
+
if (existsSync(STATE_FILE)) {
|
|
381
|
+
const parsed: unknown = JSON.parse(readFileSync(STATE_FILE, 'utf-8'));
|
|
382
|
+
return Boolean((parsed as { enabled?: unknown })?.enabled);
|
|
383
|
+
}
|
|
384
|
+
} catch {
|
|
385
|
+
// corrupt / unreadable
|
|
386
|
+
}
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function runPipeline(
|
|
391
|
+
session: EngineSession,
|
|
392
|
+
trigger: string,
|
|
393
|
+
isHigh: boolean
|
|
394
|
+
): Promise<void> {
|
|
395
|
+
if (!enabled || session.running) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
session.running = true;
|
|
399
|
+
session.lastRunAt = Date.now();
|
|
400
|
+
try {
|
|
401
|
+
const context = buildEngineContext({
|
|
402
|
+
errorCount: session.errorCount,
|
|
403
|
+
events: session.events,
|
|
404
|
+
goal: getEngineGoal(session.terminalId),
|
|
405
|
+
projectName: session.projectName,
|
|
406
|
+
status: session.status,
|
|
407
|
+
toolCallCount: session.toolCallCount,
|
|
408
|
+
trigger,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const summaryResult = await litellmJson<{ summary: string }>({
|
|
412
|
+
maxTokens: SUMMARY_MAX_TOKENS,
|
|
413
|
+
model: ENGINE_MODEL,
|
|
414
|
+
systemInstruction: `${JSON_API_RULES} The object has one key "summary": a single sentence (max 120 characters) describing the current status of this coding session.`,
|
|
415
|
+
userPrompt: `${context}\n\nSummarise what is happening in this coding session in ONE sentence (max 120 chars).`,
|
|
416
|
+
});
|
|
417
|
+
const summary = summaryResult?.summary?.trim() || fallbackSummary(session);
|
|
418
|
+
|
|
419
|
+
// Run the five lenses at most LENS_CONCURRENCY in flight (default 3) so the engine never
|
|
420
|
+
// saturates the LiteLLM key's parallel cap by itself. Each failed lens yields an empty list;
|
|
421
|
+
// a step needs CONSENSUS_QUORUM (2) of the 5 to be non-tentative.
|
|
422
|
+
const agentLists: AgentProposal[][] = await mapLimit(LENSES, LENS_CONCURRENCY, async (lens) => {
|
|
423
|
+
const r = await litellmJson<{ steps: AgentProposal[] }>({
|
|
424
|
+
maxTokens: STEPS_MAX_TOKENS,
|
|
425
|
+
model: ENGINE_MODEL,
|
|
426
|
+
systemInstruction: `${JSON_API_RULES} The object has one key "steps": an array of 1 to 3 objects, each {"text": the concrete next action for THIS exact session written in full as a string, "confidence": a number between 0 and 1}. Choose the actions from THIS perspective: ${lens}.`,
|
|
427
|
+
userPrompt: `${context}\n\nGiven the session above, what should happen next?`,
|
|
428
|
+
});
|
|
429
|
+
return r?.steps ?? [];
|
|
430
|
+
});
|
|
431
|
+
const consensus = mergeNextStepConsensus(agentLists, { quorum: CONSENSUS_QUORUM });
|
|
432
|
+
|
|
433
|
+
if (!enabled || session.cancelled) {
|
|
434
|
+
return; // engine disabled or terminal exited mid-pipeline — don't persist/push a dead session
|
|
435
|
+
}
|
|
436
|
+
persist(session, summary, consensus.steps, trigger);
|
|
437
|
+
// A run consumed the accumulated errors — reset so the error-threshold trigger (errorCount >= 3)
|
|
438
|
+
// does not stick and re-fire on every subsequent event for the rest of the session's life.
|
|
439
|
+
session.errorCount = 0;
|
|
440
|
+
if (isHigh && consensus.steps.length > 0 && !consensus.steps[0].tentative) {
|
|
441
|
+
await push(session, summary, consensus.steps);
|
|
442
|
+
}
|
|
443
|
+
} catch (err) {
|
|
444
|
+
console.error(
|
|
445
|
+
`[autopilot] pipeline failed for ${session.terminalId}:`,
|
|
446
|
+
err instanceof Error ? err.message : String(err)
|
|
447
|
+
);
|
|
448
|
+
} finally {
|
|
449
|
+
session.running = false;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Server-side LiteLLM client. Calls the OpenAI-compatible chat-completions API
|
|
2
|
+
// directly with the server-side key (process.env) — no proxy, no browser
|
|
3
|
+
// exposure. Used by the always-on autopilot engine.
|
|
4
|
+
|
|
5
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
6
|
+
const MAX_ATTEMPTS = 2;
|
|
7
|
+
const BACKOFF_MS = [800]; // backoff before the single retry (rate-limit / transient failures)
|
|
8
|
+
|
|
9
|
+
/** Pull the model's JSON out of an OpenAI-shaped chat-completion response. */
|
|
10
|
+
export function extractJsonContent(data: unknown): unknown {
|
|
11
|
+
const content = (data as { choices?: { message?: { content?: string } }[] })?.choices?.[0]
|
|
12
|
+
?.message?.content;
|
|
13
|
+
if (typeof content !== 'string') {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return parseJsonResponse(content);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** True when LITELLM_BASE_URL and LITELLM_API_KEY are both configured server-side. */
|
|
20
|
+
export function isLiteLLMConfigured(): boolean {
|
|
21
|
+
return litellmConfig() !== null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Run a structured-JSON prompt against LiteLLM. Returns the parsed object, or
|
|
26
|
+
* null when LiteLLM is unconfigured / unreachable / the response is not JSON.
|
|
27
|
+
*/
|
|
28
|
+
export async function litellmJson<T>(opts: {
|
|
29
|
+
maxTokens?: number;
|
|
30
|
+
/** Override the model for this call (e.g. the autopilot engine pins a non-reasoning model). */
|
|
31
|
+
model?: string;
|
|
32
|
+
systemInstruction: string;
|
|
33
|
+
userPrompt: string;
|
|
34
|
+
}): Promise<null | T> {
|
|
35
|
+
const cfg = litellmConfig();
|
|
36
|
+
if (!cfg) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
// Attempt with JSON mode (forces the model to emit ONLY valid JSON — verbose/reasoning models
|
|
40
|
+
// otherwise wrap prose around it), retrying with backoff for rate-limit / transient burst
|
|
41
|
+
// failures. If every JSON-mode attempt fails, fall back ONCE without response_format in case the
|
|
42
|
+
// gateway rejects it — so a non-supporting backend degrades to prior behavior instead of breaking.
|
|
43
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
44
|
+
const result = await attemptCompletion<T>(cfg, opts, true);
|
|
45
|
+
if (result !== null) {
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
if (attempt < MAX_ATTEMPTS - 1) {
|
|
49
|
+
await delay(BACKOFF_MS[attempt]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return attemptCompletion<T>(cfg, opts, false);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Strip optional markdown fences and parse JSON. If the whole string is not
|
|
57
|
+
* valid JSON, extract the FIRST balanced {...} object (a single forward scan
|
|
58
|
+
* tracking brace depth — correct across nested braces and multiple objects,
|
|
59
|
+
* unlike a greedy regex which would span to the last closing brace).
|
|
60
|
+
*/
|
|
61
|
+
export function parseJsonResponse(raw: string): unknown {
|
|
62
|
+
const cleaned = raw
|
|
63
|
+
.replace(/^```(?:json)?\s*/i, '')
|
|
64
|
+
.replace(/```\s*$/, '')
|
|
65
|
+
.trim();
|
|
66
|
+
if (!cleaned) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(cleaned);
|
|
71
|
+
} catch {
|
|
72
|
+
// Scan for the FIRST balanced {...} region that actually parses. A reasoning model often emits
|
|
73
|
+
// an invalid brace region (e.g. "{not: valid}") in its preamble BEFORE the real JSON, so on a
|
|
74
|
+
// failed candidate we must advance to the next "{" and keep scanning — not give up (the old
|
|
75
|
+
// `return null` here discarded every later region and collapsed consensus to empty).
|
|
76
|
+
let start = cleaned.indexOf('{');
|
|
77
|
+
while (start !== -1) {
|
|
78
|
+
let depth = 0;
|
|
79
|
+
let end = -1;
|
|
80
|
+
for (let i = start; i < cleaned.length; i++) {
|
|
81
|
+
if (cleaned[i] === '{') {
|
|
82
|
+
depth++;
|
|
83
|
+
} else if (cleaned[i] === '}') {
|
|
84
|
+
depth--;
|
|
85
|
+
if (depth === 0) {
|
|
86
|
+
end = i;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (end === -1) {
|
|
92
|
+
return null; // unbalanced through end of string — nothing more to try
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(cleaned.slice(start, end + 1));
|
|
96
|
+
} catch {
|
|
97
|
+
start = cleaned.indexOf('{', start + 1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function attemptCompletion<T>(
|
|
105
|
+
cfg: { base: string; key: string; model: string },
|
|
106
|
+
opts: { maxTokens?: number; model?: string; systemInstruction: string; userPrompt: string },
|
|
107
|
+
jsonMode: boolean
|
|
108
|
+
): Promise<null | T> {
|
|
109
|
+
const controller = new AbortController();
|
|
110
|
+
const timer = setTimeout(() => {
|
|
111
|
+
controller.abort();
|
|
112
|
+
}, REQUEST_TIMEOUT_MS);
|
|
113
|
+
try {
|
|
114
|
+
const body: Record<string, unknown> = {
|
|
115
|
+
max_tokens: opts.maxTokens ?? 400,
|
|
116
|
+
messages: [
|
|
117
|
+
{ content: opts.systemInstruction, role: 'system' },
|
|
118
|
+
{ content: opts.userPrompt, role: 'user' },
|
|
119
|
+
],
|
|
120
|
+
model: opts.model?.trim() || cfg.model,
|
|
121
|
+
temperature: 0,
|
|
122
|
+
};
|
|
123
|
+
if (jsonMode) {
|
|
124
|
+
body.response_format = { type: 'json_object' };
|
|
125
|
+
}
|
|
126
|
+
const res = await fetch(`${cfg.base}/chat/completions`, {
|
|
127
|
+
body: JSON.stringify(body),
|
|
128
|
+
headers: { Authorization: `Bearer ${cfg.key}`, 'Content-Type': 'application/json' },
|
|
129
|
+
method: 'POST',
|
|
130
|
+
signal: controller.signal,
|
|
131
|
+
});
|
|
132
|
+
if (!res.ok) {
|
|
133
|
+
// Surface the failure — silently returning null here made the whole autopilot pipeline
|
|
134
|
+
// produce empty consensus invisibly (no log, no signal). 429 = the LiteLLM key's
|
|
135
|
+
// max_parallel_requests is exhausted (often by other processes sharing the key).
|
|
136
|
+
const detail = await res.text().catch(() => '');
|
|
137
|
+
console.warn(
|
|
138
|
+
`[litellm] HTTP ${res.status} (model=${body.model as string}, jsonMode=${jsonMode}): ${detail.slice(0, 200)}`
|
|
139
|
+
);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const data: unknown = await res.json();
|
|
143
|
+
return extractJsonContent(data) as null | T;
|
|
144
|
+
} catch (err) {
|
|
145
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
146
|
+
// AbortError = our REQUEST_TIMEOUT_MS fired (the gateway hung). Anything else = network/parse.
|
|
147
|
+
console.warn(`[litellm] request failed (model=${opts.model?.trim() || cfg.model}): ${reason}`);
|
|
148
|
+
return null;
|
|
149
|
+
} finally {
|
|
150
|
+
clearTimeout(timer);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function delay(ms: number): Promise<void> {
|
|
155
|
+
return new Promise((resolve) => {
|
|
156
|
+
setTimeout(resolve, ms);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function litellmConfig(): null | { base: string; key: string; model: string } {
|
|
161
|
+
const base = process.env.LITELLM_BASE_URL?.trim();
|
|
162
|
+
const key = process.env.LITELLM_API_KEY?.trim();
|
|
163
|
+
if (!base || !key) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
base: base.replace(/\/+$/, ''),
|
|
168
|
+
key,
|
|
169
|
+
model: process.env.LITELLM_MODEL?.trim() || 'open-large',
|
|
170
|
+
};
|
|
171
|
+
}
|