@juspay/shooter 1.19.0 → 1.21.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/build/client/_app/immutable/assets/11.F10lvwyh.css +1 -0
- package/build/client/_app/immutable/assets/11.F10lvwyh.css.br +0 -0
- package/build/client/_app/immutable/assets/11.F10lvwyh.css.gz +0 -0
- package/build/client/_app/immutable/assets/2.BHi6pjT2.css +1 -0
- package/build/client/_app/immutable/assets/2.BHi6pjT2.css.br +0 -0
- package/build/client/_app/immutable/assets/2.BHi6pjT2.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/{DCDL_9ys.js → BmfLecb1.js} +1 -1
- package/build/client/_app/immutable/chunks/BmfLecb1.js.br +0 -0
- package/build/client/_app/immutable/chunks/BmfLecb1.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/DOEXXmsh.js +3 -0
- package/build/client/_app/immutable/chunks/DOEXXmsh.js.br +0 -0
- package/build/client/_app/immutable/chunks/DOEXXmsh.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/{DA4Zt9Me.js → EqMAkEha.js} +1 -1
- package/build/client/_app/immutable/chunks/EqMAkEha.js.br +0 -0
- package/build/client/_app/immutable/chunks/EqMAkEha.js.gz +0 -0
- package/build/client/_app/immutable/chunks/J5-Cr5oR.js +6 -0
- package/build/client/_app/immutable/chunks/J5-Cr5oR.js.br +0 -0
- package/build/client/_app/immutable/chunks/J5-Cr5oR.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.D4TXlu7A.js → app.CeSxgGat.js} +2 -2
- package/build/client/_app/immutable/entry/app.CeSxgGat.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CeSxgGat.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DrnJFwxA.js +1 -0
- package/build/client/_app/immutable/entry/start.DrnJFwxA.js.br +2 -0
- package/build/client/_app/immutable/entry/start.DrnJFwxA.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.oaPwxh1O.js +10 -0
- package/build/client/_app/immutable/nodes/0.oaPwxh1O.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.oaPwxh1O.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.BVnLUSs-.js → 1.DMPyoM-M.js} +1 -1
- package/build/client/_app/immutable/nodes/1.DMPyoM-M.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.DMPyoM-M.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{10.D1wl2wPX.js → 10.Cbm7nQKK.js} +1 -1
- package/build/client/_app/immutable/nodes/10.Cbm7nQKK.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.Cbm7nQKK.js.gz +0 -0
- package/build/client/_app/immutable/nodes/11.CKmZjP_a.js +2 -0
- package/build/client/_app/immutable/nodes/11.CKmZjP_a.js.br +0 -0
- package/build/client/_app/immutable/nodes/11.CKmZjP_a.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.zlrdNFtH.js +13 -0
- package/build/client/_app/immutable/nodes/2.zlrdNFtH.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.zlrdNFtH.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{3.Wfz3TcJd.js → 3.BgLpGnzb.js} +1 -1
- package/build/client/_app/immutable/nodes/3.BgLpGnzb.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.BgLpGnzb.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.Avc1-gVb.js} +1 -1
- package/build/client/_app/immutable/nodes/5.Avc1-gVb.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.Avc1-gVb.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{6.DtZAEPXb.js → 6.Dw2wEssJ.js} +1 -1
- package/build/client/_app/immutable/nodes/6.Dw2wEssJ.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.Dw2wEssJ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{7.MfBRh32I.js → 7.DwKZjoBg.js} +1 -1
- package/build/client/_app/immutable/nodes/7.DwKZjoBg.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.DwKZjoBg.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{8.DVE6LnOC.js → 8.ZUAI6g5E.js} +1 -1
- package/build/client/_app/immutable/nodes/8.ZUAI6g5E.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.ZUAI6g5E.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{9.BCel5OqI.js → 9.I_KGXPwB.js} +1 -1
- package/build/client/_app/immutable/nodes/9.I_KGXPwB.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.I_KGXPwB.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/server/chunks/{0-DJqyZZTr.js → 0-vrTNAfZB.js} +4 -2
- package/build/server/chunks/0-vrTNAfZB.js.map +1 -0
- package/build/server/chunks/{1-2YUVen1F.js → 1-nbr-bOoF.js} +2 -2
- package/build/server/chunks/{1-2YUVen1F.js.map → 1-nbr-bOoF.js.map} +1 -1
- package/build/server/chunks/{10-D1X7LB3v.js → 10-ChyvvJ6w.js} +2 -2
- package/build/server/chunks/{10-D1X7LB3v.js.map → 10-ChyvvJ6w.js.map} +1 -1
- package/build/server/chunks/{11-qXSPdF5j.js → 11-6ZAjL3uU.js} +4 -4
- package/build/server/chunks/11-6ZAjL3uU.js.map +1 -0
- package/build/server/chunks/{2-BD7kj1mt.js → 2-DWFRVDWJ.js} +3 -3
- package/build/server/chunks/{2-BD7kj1mt.js.map → 2-DWFRVDWJ.js.map} +1 -1
- package/build/server/chunks/{3-oNjv-BhZ.js → 3-CKANM_WM.js} +2 -2
- package/build/server/chunks/{3-oNjv-BhZ.js.map → 3-CKANM_WM.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-BxVjs2qi.js} +2 -2
- package/build/server/chunks/{5-oNoWuIsn.js.map → 5-BxVjs2qi.js.map} +1 -1
- package/build/server/chunks/{6-DRJGUqHG.js → 6-Cbf1AAMQ.js} +2 -2
- package/build/server/chunks/{6-DRJGUqHG.js.map → 6-Cbf1AAMQ.js.map} +1 -1
- package/build/server/chunks/{7-_giJiu0L.js → 7-CMK2quEf.js} +2 -2
- package/build/server/chunks/{7-_giJiu0L.js.map → 7-CMK2quEf.js.map} +1 -1
- package/build/server/chunks/{8-zvWAVNT5.js → 8-DhdfkfDM.js} +2 -2
- package/build/server/chunks/{8-zvWAVNT5.js.map → 8-DhdfkfDM.js.map} +1 -1
- package/build/server/chunks/{9-DVyDL445.js → 9-CPpxtRM5.js} +2 -2
- package/build/server/chunks/{9-DVyDL445.js.map → 9-CPpxtRM5.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-BTlfUsBp.js.map +1 -1
- package/build/server/chunks/_page.svelte-BX2FMgSg.js.map +1 -1
- package/build/server/chunks/_page.svelte-C7B0qdrC.js.map +1 -1
- package/build/server/chunks/_page.svelte-CE7COWnF.js.map +1 -1
- package/build/server/chunks/_page.svelte-CWsjjd4l.js.map +1 -1
- package/build/server/chunks/_page.svelte-D5S2hkBk.js.map +1 -1
- package/build/server/chunks/_page.svelte-D_Ey8QRG.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-BUBLUSGo.js → _page.svelte-dabsQl9c.js} +206 -5
- package/build/server/chunks/_page.svelte-dabsQl9c.js.map +1 -0
- package/build/server/chunks/_page.svelte-tBuIq8Pg.js.map +1 -1
- package/build/server/chunks/_server.ts-B-evHL2q.js +13 -0
- package/build/server/chunks/_server.ts-B-evHL2q.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-DMm0hBP4.js → _server.ts-BWVlO8iV.js} +8 -5
- package/build/server/chunks/_server.ts-BWVlO8iV.js.map +1 -0
- package/build/server/chunks/{_server.ts-BRAzC6W1.js → _server.ts-BevnuePu.js} +27 -6
- package/build/server/chunks/_server.ts-BevnuePu.js.map +1 -0
- package/build/server/chunks/_server.ts-CA5KUENM.js +95 -0
- package/build/server/chunks/_server.ts-CA5KUENM.js.map +1 -0
- package/build/server/chunks/{_server.ts-DhJx0DLr.js → _server.ts-CC2K8-L2.js} +16 -7
- package/build/server/chunks/_server.ts-CC2K8-L2.js.map +1 -0
- package/build/server/chunks/{_server.ts-Bi0Oe4PF.js → _server.ts-CD7JP3fz.js} +14 -9
- package/build/server/chunks/_server.ts-CD7JP3fz.js.map +1 -0
- package/build/server/chunks/{_server.ts-C_OOUqsd.js → _server.ts-D0zRDSx0.js} +2 -2
- package/build/server/chunks/{_server.ts-C_OOUqsd.js.map → _server.ts-D0zRDSx0.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DxT9IlZF.js → _server.ts-Dp-hXW_I.js} +3 -3
- package/build/server/chunks/{_server.ts-DxT9IlZF.js.map → _server.ts-Dp-hXW_I.js.map} +1 -1
- package/build/server/chunks/_server.ts-DpRr0Tfh.js +68 -0
- package/build/server/chunks/_server.ts-DpRr0Tfh.js.map +1 -0
- package/build/server/chunks/{_server.ts-Bjbr7glm.js → _server.ts-QN-Bo5ql.js} +12 -5
- package/build/server/chunks/_server.ts-QN-Bo5ql.js.map +1 -0
- package/build/server/chunks/_server.ts-VzDcFFgy.js +157 -0
- package/build/server/chunks/_server.ts-VzDcFFgy.js.map +1 -0
- package/build/server/chunks/{_server.ts-BrqaMMAa.js → _server.ts-W6i3EnGX.js} +29 -6
- package/build/server/chunks/_server.ts-W6i3EnGX.js.map +1 -0
- package/build/server/chunks/{_server.ts-CRVNEOd2.js → _server.ts-X1R7L_QI.js} +3 -3
- package/build/server/chunks/{_server.ts-CRVNEOd2.js.map → _server.ts-X1R7L_QI.js.map} +1 -1
- package/build/server/chunks/cache-BlMaDsHi.js.map +1 -1
- package/build/server/chunks/guest-registry-Dxvd7p-g.js +48 -0
- 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/{pty-manager-41h3IK8K.js → pty-manager-ZqXqa-6A.js} +6 -2
- package/build/server/chunks/pty-manager-ZqXqa-6A.js.map +1 -0
- package/build/server/chunks/root-D4IoFC8F.js.map +1 -1
- package/build/server/chunks/share-auth-BS7JuiHf.js +27 -0
- package/build/server/chunks/share-auth-BS7JuiHf.js.map +1 -0
- package/build/server/chunks/share-store-B9jMpVg0.js +127 -0
- package/build/server/chunks/share-store-B9jMpVg0.js.map +1 -0
- package/build/server/chunks/state.svelte-CmHqngc_.js.map +1 -1
- package/build/server/chunks/stores-CRYxfF0o.js.map +1 -1
- package/build/server/index.js +1 -1
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +57 -22
- package/build/server/manifest.js.map +1 -1
- package/package.json +2 -2
- package/server.ts +10 -3
- package/src/lib/modules/client/dashboard/AutopilotPanel.svelte +400 -0
- package/src/lib/modules/client/dashboard/index.ts +1 -0
- package/src/lib/modules/client/neurolink/provider-config.ts +13 -37
- package/src/lib/modules/client/terminal/ShareGate.svelte +96 -0
- package/src/lib/modules/client/terminal/ShareSheet.svelte +395 -0
- package/src/lib/modules/client/terminal/xterm-wrapper.ts +19 -2
- package/src/lib/modules/server/sessions/autopilot-engine.ts +346 -0
- package/src/lib/modules/server/sessions/litellm-client.ts +115 -0
- package/src/lib/modules/server/sessions/next-step-consensus.ts +185 -0
- package/src/lib/modules/server/sessions/summary-store.ts +111 -0
- package/src/lib/modules/server/terminal/pty-manager.ts +6 -0
- package/src/lib/modules/server/terminal/share-auth.ts +37 -0
- package/src/lib/modules/server/terminal/share-store.ts +172 -0
- package/src/lib/modules/server/ws/events-handler.ts +32 -0
- package/src/lib/modules/server/ws/guest-registry.ts +49 -0
- package/src/lib/modules/server/ws/server.ts +22 -3
- package/src/lib/modules/server/ws/session-handler.ts +18 -4
- package/src/lib/modules/server/ws/terminal-handler.ts +21 -2
- package/src/lib/modules/server/ws/ticket-store.ts +18 -10
- package/src/lib/types/autopilot.ts +73 -0
- package/src/lib/types/generated/Client.ts +25 -1
- package/src/lib/types/generated/Share.ts +404 -0
- package/src/lib/types/generated/WsProtocol.ts +73 -2
- package/src/lib/types/generated/index.ts +1 -0
- package/src/lib/types/index.ts +1 -0
- package/src/lib/types/terminal-client.ts +21 -2
- package/src/lib/types/ws.ts +1 -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/neurolink-proxy/+server.ts +31 -6
- package/src/routes/api/notify/+server.ts +20 -3
- package/src/routes/api/summaries/+server.ts +99 -0
- package/src/routes/api/terminals/[id]/+server.ts +14 -3
- package/src/routes/api/terminals/[id]/paste-image/+server.ts +8 -4
- package/src/routes/api/terminals/[id]/resize/+server.ts +8 -4
- package/src/routes/api/terminals/[id]/share/+server.ts +98 -0
- package/src/routes/api/terminals/[id]/share/auth/+server.ts +81 -0
- package/src/routes/api/terminals/[id]/share/status/+server.ts +11 -0
- package/src/routes/api/ws-ticket/+server.ts +26 -5
- package/src/routes/terminals/[id]/+page.svelte +184 -43
- package/build/client/_app/immutable/assets/11.v5KA95xm.css +0 -1
- package/build/client/_app/immutable/assets/11.v5KA95xm.css.br +0 -0
- package/build/client/_app/immutable/assets/11.v5KA95xm.css.gz +0 -0
- 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/BcqA7eKM.js +0 -3
- package/build/client/_app/immutable/chunks/BcqA7eKM.js.br +0 -0
- package/build/client/_app/immutable/chunks/BcqA7eKM.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/CR6bkGJW.js +0 -6
- package/build/client/_app/immutable/chunks/CR6bkGJW.js.br +0 -0
- package/build/client/_app/immutable/chunks/CR6bkGJW.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/DA4Zt9Me.js.br +0 -0
- package/build/client/_app/immutable/chunks/DA4Zt9Me.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DCDL_9ys.js.br +0 -0
- package/build/client/_app/immutable/chunks/DCDL_9ys.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.D4TXlu7A.js.br +0 -0
- package/build/client/_app/immutable/entry/app.D4TXlu7A.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BBQhtURO.js +0 -1
- package/build/client/_app/immutable/entry/start.BBQhtURO.js.br +0 -0
- package/build/client/_app/immutable/entry/start.BBQhtURO.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.1zylwAPT.js +0 -10
- package/build/client/_app/immutable/nodes/0.1zylwAPT.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.1zylwAPT.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.BVnLUSs-.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.BVnLUSs-.js.gz +0 -0
- package/build/client/_app/immutable/nodes/10.D1wl2wPX.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.D1wl2wPX.js.gz +0 -0
- package/build/client/_app/immutable/nodes/11.C18nMGmp.js +0 -2
- package/build/client/_app/immutable/nodes/11.C18nMGmp.js.br +0 -0
- package/build/client/_app/immutable/nodes/11.C18nMGmp.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.D1Mm0DUX.js +0 -13
- package/build/client/_app/immutable/nodes/2.D1Mm0DUX.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.D1Mm0DUX.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.Wfz3TcJd.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.Wfz3TcJd.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.DtZAEPXb.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.DtZAEPXb.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.MfBRh32I.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.MfBRh32I.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.DVE6LnOC.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.DVE6LnOC.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.BCel5OqI.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.BCel5OqI.js.gz +0 -0
- package/build/server/chunks/0-DJqyZZTr.js.map +0 -1
- package/build/server/chunks/11-qXSPdF5j.js.map +0 -1
- package/build/server/chunks/_page.svelte-BLo2v_8E.js.map +0 -1
- package/build/server/chunks/_page.svelte-BUBLUSGo.js.map +0 -1
- package/build/server/chunks/_server.ts-BRAzC6W1.js.map +0 -1
- package/build/server/chunks/_server.ts-Bi0Oe4PF.js.map +0 -1
- package/build/server/chunks/_server.ts-Bjbr7glm.js.map +0 -1
- package/build/server/chunks/_server.ts-BrqaMMAa.js.map +0 -1
- package/build/server/chunks/_server.ts-DMm0hBP4.js.map +0 -1
- package/build/server/chunks/_server.ts-DhJx0DLr.js.map +0 -1
- package/build/server/chunks/events-handler-Dm1mNPQP.js +0 -20
- package/build/server/chunks/events-handler-Dm1mNPQP.js.map +0 -1
- package/build/server/chunks/pty-manager-41h3IK8K.js.map +0 -1
|
@@ -0,0 +1,346 @@
|
|
|
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 { homedir } from 'os';
|
|
16
|
+
import { join } from 'path';
|
|
17
|
+
|
|
18
|
+
import { ptyManager } from '../terminal/pty-manager.js';
|
|
19
|
+
import { onShooterEvent } from '../ws/events-handler.js';
|
|
20
|
+
import { isLiteLLMConfigured, litellmJson } from './litellm-client.js';
|
|
21
|
+
import { mergeNextStepConsensus } from './next-step-consensus.js';
|
|
22
|
+
import { summaryStore } from './summary-store.js';
|
|
23
|
+
|
|
24
|
+
// ── Constants ────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const HIGH_SIGNAL = new Set(['agent-idle', 'agent-question', 'tool-failed']);
|
|
27
|
+
const GRACE_MS = 4_000; // fire this long after the FIRST qualifying event (fast, not resetting)
|
|
28
|
+
const MIN_INTERVAL_MS = 30_000; // minimum gap between pipeline runs per session (cost bound)
|
|
29
|
+
const PERIODIC_EVERY = 20;
|
|
30
|
+
const ERROR_THRESHOLD = 3;
|
|
31
|
+
const MAX_EVENTS = 60;
|
|
32
|
+
const SUMMARY_MAX_TOKENS = 220;
|
|
33
|
+
const STEPS_MAX_TOKENS = 320;
|
|
34
|
+
const STATE_FILE = join(homedir(), '.shooter', 'autopilot.json');
|
|
35
|
+
|
|
36
|
+
/** Five DISTINCT lenses — each a different perspective, so votes mean real agreement. */
|
|
37
|
+
const LENSES = [
|
|
38
|
+
'You are a BLOCKER-detection agent. Identify what is currently preventing progress or is most likely to fail next.',
|
|
39
|
+
'You are a NEXT-COMMAND planner. Propose the exact shell commands or file edits the agent should run next, in order.',
|
|
40
|
+
'You are a RISK analyst. Identify what could go wrong if the agent continues on its current path, and what to validate first.',
|
|
41
|
+
'You are a VALIDATION agent. Propose how to verify the work so far is correct — the tests, checks, or inspections to run.',
|
|
42
|
+
'You are a PROGRESS agent. Propose the single most direct next step toward completing the session goal.',
|
|
43
|
+
] as const;
|
|
44
|
+
|
|
45
|
+
// ── Per-session state (globalThis-shared) ───────────────────────────
|
|
46
|
+
|
|
47
|
+
// eslint-disable-next-line no-restricted-syntax -- internal engine state, never exported
|
|
48
|
+
interface EngineSession {
|
|
49
|
+
errorCount: number;
|
|
50
|
+
eventCount: number;
|
|
51
|
+
events: string[];
|
|
52
|
+
graceTimer: null | ReturnType<typeof setTimeout>;
|
|
53
|
+
lastRunAt: number;
|
|
54
|
+
projectName: string;
|
|
55
|
+
running: boolean;
|
|
56
|
+
status: string;
|
|
57
|
+
terminalId: string;
|
|
58
|
+
toolCallCount: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const SESSIONS_KEY = '__shooter_autopilot_sessions';
|
|
62
|
+
const sessions: Map<string, EngineSession> =
|
|
63
|
+
((globalThis as Record<string, unknown>)[SESSIONS_KEY] as Map<string, EngineSession>) ||
|
|
64
|
+
new Map<string, EngineSession>();
|
|
65
|
+
(globalThis as Record<string, unknown>)[SESSIONS_KEY] = sessions;
|
|
66
|
+
|
|
67
|
+
let enabled = readPersistedEnabled();
|
|
68
|
+
let unsubscribe: (() => void) | null = null;
|
|
69
|
+
|
|
70
|
+
// ── Control (exposed on globalThis so the /api/autopilot route can reach it) ──
|
|
71
|
+
|
|
72
|
+
export function isAutopilotEnabled(): boolean {
|
|
73
|
+
return enabled;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function setAutopilotEnabled(value: boolean): void {
|
|
77
|
+
enabled = value;
|
|
78
|
+
try {
|
|
79
|
+
mkdirSync(join(homedir(), '.shooter'), { recursive: true });
|
|
80
|
+
writeFileSync(STATE_FILE, JSON.stringify({ enabled: value }), 'utf-8');
|
|
81
|
+
} catch {
|
|
82
|
+
// best-effort persistence
|
|
83
|
+
}
|
|
84
|
+
if (!value) {
|
|
85
|
+
for (const s of sessions.values()) {
|
|
86
|
+
if (s.graceTimer) {
|
|
87
|
+
clearTimeout(s.graceTimer);
|
|
88
|
+
s.graceTimer = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Start the engine: subscribe to the event stream. Idempotent. */
|
|
95
|
+
export function startAutopilotEngine(): void {
|
|
96
|
+
if (unsubscribe) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const control = { isEnabled: isAutopilotEnabled, setEnabled: setAutopilotEnabled };
|
|
100
|
+
(globalThis as Record<string, unknown>).__shooter_autopilot = control;
|
|
101
|
+
unsubscribe = onShooterEvent(handleEvent);
|
|
102
|
+
console.log(
|
|
103
|
+
`[autopilot] engine started (enabled=${enabled}, litellm=${isLiteLLMConfigured() ? 'configured' : 'absent'})`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── Event handling ───────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
function applyEvent(session: EngineSession, event: WireShooterEvent): void {
|
|
110
|
+
const parts: string[] = [event.type];
|
|
111
|
+
if ('tool' in event && event.tool) {
|
|
112
|
+
parts.push(`tool=${event.tool}`);
|
|
113
|
+
}
|
|
114
|
+
if ('error' in event && event.error) {
|
|
115
|
+
parts.push(`error=${event.error.slice(0, 140)}`);
|
|
116
|
+
}
|
|
117
|
+
if ('command' in event && event.command) {
|
|
118
|
+
parts.push(`cmd=${event.command.slice(0, 80)}`);
|
|
119
|
+
}
|
|
120
|
+
if ('message' in event && event.message) {
|
|
121
|
+
parts.push(`msg=${event.message.slice(0, 120)}`);
|
|
122
|
+
}
|
|
123
|
+
session.events.push(parts.join(' '));
|
|
124
|
+
if (session.events.length > MAX_EVENTS) {
|
|
125
|
+
session.events.shift();
|
|
126
|
+
}
|
|
127
|
+
switch (event.type) {
|
|
128
|
+
case 'agent-idle':
|
|
129
|
+
case 'agent-question':
|
|
130
|
+
session.status = 'idle';
|
|
131
|
+
break;
|
|
132
|
+
case 'tool-completed':
|
|
133
|
+
session.toolCallCount += 1;
|
|
134
|
+
break;
|
|
135
|
+
case 'tool-failed':
|
|
136
|
+
session.errorCount += 1;
|
|
137
|
+
break;
|
|
138
|
+
case 'tool-started':
|
|
139
|
+
session.toolCallCount += 1;
|
|
140
|
+
session.status = 'running';
|
|
141
|
+
break;
|
|
142
|
+
default:
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function createSession(terminalId: string): EngineSession {
|
|
148
|
+
const session: EngineSession = {
|
|
149
|
+
errorCount: 0,
|
|
150
|
+
eventCount: 0,
|
|
151
|
+
events: [],
|
|
152
|
+
graceTimer: null,
|
|
153
|
+
lastRunAt: 0,
|
|
154
|
+
projectName: projectNameFor(terminalId),
|
|
155
|
+
running: false,
|
|
156
|
+
status: 'running',
|
|
157
|
+
terminalId,
|
|
158
|
+
toolCallCount: 0,
|
|
159
|
+
};
|
|
160
|
+
sessions.set(terminalId, session);
|
|
161
|
+
return session;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function fallbackSummary(session: EngineSession): string {
|
|
165
|
+
return `${session.status} — ${session.toolCallCount} tool calls, ${session.errorCount} errors`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── Pipeline ─────────────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
function handleEvent(event: WireShooterEvent): void {
|
|
171
|
+
const terminalId = 'terminalId' in event ? event.terminalId : undefined;
|
|
172
|
+
if (!terminalId) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (event.type === 'terminal-exited') {
|
|
177
|
+
const s = sessions.get(terminalId);
|
|
178
|
+
if (s?.graceTimer) {
|
|
179
|
+
clearTimeout(s.graceTimer);
|
|
180
|
+
}
|
|
181
|
+
sessions.delete(terminalId);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const session = sessions.get(terminalId) ?? createSession(terminalId);
|
|
186
|
+
session.eventCount += 1;
|
|
187
|
+
applyEvent(session, event);
|
|
188
|
+
|
|
189
|
+
if (!enabled || session.running) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const isHigh = HIGH_SIGNAL.has(event.type);
|
|
193
|
+
const isPeriodic = session.eventCount % PERIODIC_EVERY === 0;
|
|
194
|
+
const isErrorThreshold = session.errorCount >= ERROR_THRESHOLD;
|
|
195
|
+
if (!isHigh && !isPeriodic && !isErrorThreshold) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (Date.now() - session.lastRunAt < MIN_INTERVAL_MS) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
// Schedule once; do NOT reset on every event (so a burst doesn't delay the run).
|
|
202
|
+
if (!session.graceTimer) {
|
|
203
|
+
session.graceTimer = setTimeout(() => {
|
|
204
|
+
session.graceTimer = null;
|
|
205
|
+
void runPipeline(session, event.type, isHigh);
|
|
206
|
+
}, GRACE_MS);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function persist(
|
|
211
|
+
session: EngineSession,
|
|
212
|
+
summary: string,
|
|
213
|
+
steps: NextStep[],
|
|
214
|
+
trigger: string
|
|
215
|
+
): void {
|
|
216
|
+
const record: SessionSummaryRecord = {
|
|
217
|
+
createdAt: new Date().toISOString(),
|
|
218
|
+
id: `auto-${session.terminalId}-${Date.now()}`,
|
|
219
|
+
nextSteps: JSON.stringify(steps),
|
|
220
|
+
projectName: session.projectName,
|
|
221
|
+
// No separate JSONL session UUID server-side; key on terminalId so
|
|
222
|
+
// GET /api/summaries?sessionId=<terminalId> works (was previously null).
|
|
223
|
+
sessionId: session.terminalId,
|
|
224
|
+
summary,
|
|
225
|
+
terminalId: session.terminalId,
|
|
226
|
+
trigger,
|
|
227
|
+
};
|
|
228
|
+
try {
|
|
229
|
+
summaryStore.insert(record);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
console.error('[autopilot] persist failed:', err instanceof Error ? err.message : String(err));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function projectNameFor(terminalId: string): string {
|
|
236
|
+
try {
|
|
237
|
+
const term = ptyManager.list().find((t) => t.id === terminalId);
|
|
238
|
+
if (term?.cwd) {
|
|
239
|
+
const segs = term.cwd.split('/').filter(Boolean);
|
|
240
|
+
return segs.slice(-2).join('/') || terminalId;
|
|
241
|
+
}
|
|
242
|
+
} catch {
|
|
243
|
+
// ptyManager unavailable — fall through
|
|
244
|
+
}
|
|
245
|
+
return terminalId;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function push(session: EngineSession, summary: string, steps: NextStep[]): Promise<void> {
|
|
249
|
+
const top = steps[0];
|
|
250
|
+
if (!top) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const port = process.env.PORT || '54007';
|
|
254
|
+
const apiKey = process.env.API_KEY;
|
|
255
|
+
if (!apiKey) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
await fetch(`http://127.0.0.1:${port}/api/notify`, {
|
|
260
|
+
body: JSON.stringify({
|
|
261
|
+
data: {
|
|
262
|
+
category: session.terminalId,
|
|
263
|
+
dedupKey: `${session.terminalId}|${top.text.slice(0, 60)}`,
|
|
264
|
+
sessionId: session.terminalId,
|
|
265
|
+
source: 'autopilot',
|
|
266
|
+
},
|
|
267
|
+
message: `${summary.slice(0, 80)} — Next: ${top.text.slice(0, 60)}`,
|
|
268
|
+
title: `Autopilot: ${session.projectName}`,
|
|
269
|
+
}),
|
|
270
|
+
headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
|
|
271
|
+
method: 'POST',
|
|
272
|
+
});
|
|
273
|
+
} catch (err) {
|
|
274
|
+
console.warn('[autopilot] push failed:', err instanceof Error ? err.message : String(err));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
function readPersistedEnabled(): boolean {
|
|
281
|
+
try {
|
|
282
|
+
if (existsSync(STATE_FILE)) {
|
|
283
|
+
const parsed: unknown = JSON.parse(readFileSync(STATE_FILE, 'utf-8'));
|
|
284
|
+
return Boolean((parsed as { enabled?: unknown })?.enabled);
|
|
285
|
+
}
|
|
286
|
+
} catch {
|
|
287
|
+
// corrupt / unreadable
|
|
288
|
+
}
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function runPipeline(
|
|
293
|
+
session: EngineSession,
|
|
294
|
+
trigger: string,
|
|
295
|
+
isHigh: boolean
|
|
296
|
+
): Promise<void> {
|
|
297
|
+
if (!enabled || session.running) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
session.running = true;
|
|
301
|
+
session.lastRunAt = Date.now();
|
|
302
|
+
try {
|
|
303
|
+
const context =
|
|
304
|
+
`Project: ${session.projectName}\nStatus: ${session.status}\nErrors: ${session.errorCount}\n` +
|
|
305
|
+
`Tool calls: ${session.toolCallCount}\nRecent events: ${session.events.slice(-12).join('; ')}\n` +
|
|
306
|
+
`Trigger: ${trigger}`;
|
|
307
|
+
|
|
308
|
+
const summaryResult = await litellmJson<{ summary: string }>({
|
|
309
|
+
maxTokens: SUMMARY_MAX_TOKENS,
|
|
310
|
+
systemInstruction:
|
|
311
|
+
'You are a coding-session monitor. Respond ONLY with valid JSON: {"summary":"<one sentence, max 120 chars>"}. No markdown, no prose outside the JSON.',
|
|
312
|
+
userPrompt: `${context}\n\nSummarise what is happening in this coding session in ONE sentence (max 120 chars).`,
|
|
313
|
+
});
|
|
314
|
+
const summary = summaryResult?.summary?.trim() || fallbackSummary(session);
|
|
315
|
+
|
|
316
|
+
const lensResults = await Promise.allSettled(
|
|
317
|
+
LENSES.map((lens) =>
|
|
318
|
+
litellmJson<{ steps: AgentProposal[] }>({
|
|
319
|
+
maxTokens: STEPS_MAX_TOKENS,
|
|
320
|
+
systemInstruction: `${lens} Respond ONLY with valid JSON: {"steps":[{"text":"<short action>","confidence":0.9}]}. Up to 3 steps, confidence 0-1. No markdown.`,
|
|
321
|
+
userPrompt: `${context}\n\nGiven the session above, what should happen next from your perspective?`,
|
|
322
|
+
})
|
|
323
|
+
)
|
|
324
|
+
);
|
|
325
|
+
// Keep all 5 lists (including empty on failure) so quorum stays 3-of-5.
|
|
326
|
+
const agentLists: AgentProposal[][] = lensResults.map((r) =>
|
|
327
|
+
r.status === 'fulfilled' ? (r.value?.steps ?? []) : []
|
|
328
|
+
);
|
|
329
|
+
const consensus = mergeNextStepConsensus(agentLists);
|
|
330
|
+
|
|
331
|
+
if (!enabled) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
persist(session, summary, consensus.steps, trigger);
|
|
335
|
+
if (isHigh && consensus.steps.length > 0 && !consensus.steps[0].tentative) {
|
|
336
|
+
await push(session, summary, consensus.steps);
|
|
337
|
+
}
|
|
338
|
+
} catch (err) {
|
|
339
|
+
console.error(
|
|
340
|
+
`[autopilot] pipeline failed for ${session.terminalId}:`,
|
|
341
|
+
err instanceof Error ? err.message : String(err)
|
|
342
|
+
);
|
|
343
|
+
} finally {
|
|
344
|
+
session.running = false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
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 = 20_000;
|
|
6
|
+
|
|
7
|
+
/** Pull the model's JSON out of an OpenAI-shaped chat-completion response. */
|
|
8
|
+
export function extractJsonContent(data: unknown): unknown {
|
|
9
|
+
const content = (data as { choices?: { message?: { content?: string } }[] })?.choices?.[0]
|
|
10
|
+
?.message?.content;
|
|
11
|
+
if (typeof content !== 'string') {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return parseJsonResponse(content);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** True when LITELLM_BASE_URL and LITELLM_API_KEY are both configured server-side. */
|
|
18
|
+
export function isLiteLLMConfigured(): boolean {
|
|
19
|
+
return litellmConfig() !== null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Run a structured-JSON prompt against LiteLLM. Returns the parsed object, or
|
|
24
|
+
* null when LiteLLM is unconfigured / unreachable / the response is not JSON.
|
|
25
|
+
*/
|
|
26
|
+
export async function litellmJson<T>(opts: {
|
|
27
|
+
maxTokens?: number;
|
|
28
|
+
systemInstruction: string;
|
|
29
|
+
userPrompt: string;
|
|
30
|
+
}): Promise<null | T> {
|
|
31
|
+
const cfg = litellmConfig();
|
|
32
|
+
if (!cfg) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const controller = new AbortController();
|
|
36
|
+
const timer = setTimeout(() => {
|
|
37
|
+
controller.abort();
|
|
38
|
+
}, REQUEST_TIMEOUT_MS);
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetch(`${cfg.base}/chat/completions`, {
|
|
41
|
+
body: JSON.stringify({
|
|
42
|
+
max_tokens: opts.maxTokens ?? 400,
|
|
43
|
+
messages: [
|
|
44
|
+
{ content: opts.systemInstruction, role: 'system' },
|
|
45
|
+
{ content: opts.userPrompt, role: 'user' },
|
|
46
|
+
],
|
|
47
|
+
model: cfg.model,
|
|
48
|
+
}),
|
|
49
|
+
headers: { Authorization: `Bearer ${cfg.key}`, 'Content-Type': 'application/json' },
|
|
50
|
+
method: 'POST',
|
|
51
|
+
signal: controller.signal,
|
|
52
|
+
});
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const data: unknown = await res.json();
|
|
57
|
+
return extractJsonContent(data) as null | T;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
} finally {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Strip optional markdown fences and parse JSON. If the whole string is not
|
|
67
|
+
* valid JSON, extract the FIRST balanced {...} object (a single forward scan
|
|
68
|
+
* tracking brace depth — correct across nested braces and multiple objects,
|
|
69
|
+
* unlike a greedy regex which would span to the last closing brace).
|
|
70
|
+
*/
|
|
71
|
+
export function parseJsonResponse(raw: string): unknown {
|
|
72
|
+
const cleaned = raw
|
|
73
|
+
.replace(/^```(?:json)?\s*/i, '')
|
|
74
|
+
.replace(/```\s*$/, '')
|
|
75
|
+
.trim();
|
|
76
|
+
if (!cleaned) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(cleaned);
|
|
81
|
+
} catch {
|
|
82
|
+
const start = cleaned.indexOf('{');
|
|
83
|
+
if (start !== -1) {
|
|
84
|
+
let depth = 0;
|
|
85
|
+
for (let i = start; i < cleaned.length; i++) {
|
|
86
|
+
if (cleaned[i] === '{') {
|
|
87
|
+
depth++;
|
|
88
|
+
} else if (cleaned[i] === '}') {
|
|
89
|
+
depth--;
|
|
90
|
+
if (depth === 0) {
|
|
91
|
+
try {
|
|
92
|
+
return JSON.parse(cleaned.slice(start, i + 1));
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function litellmConfig(): null | { base: string; key: string; model: string } {
|
|
105
|
+
const base = process.env.LITELLM_BASE_URL?.trim();
|
|
106
|
+
const key = process.env.LITELLM_API_KEY?.trim();
|
|
107
|
+
if (!base || !key) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
base: base.replace(/\/+$/, ''),
|
|
112
|
+
key,
|
|
113
|
+
model: process.env.LITELLM_MODEL?.trim() || 'open-large',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Pure, deterministic next-step consensus algorithm.
|
|
2
|
+
// No I/O, no LLM. Fully unit-testable.
|
|
3
|
+
// See spec docs/superpowers/specs/2026-05-29-autonomous-summarization-engine-design.md §4
|
|
4
|
+
|
|
5
|
+
import type { AgentProposal, ConsensusResult, MergeOptions, NextStep } from '$lib/types';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_K = 3;
|
|
8
|
+
const DEFAULT_QUORUM = 3;
|
|
9
|
+
const JACCARD_THRESHOLD = 0.6;
|
|
10
|
+
|
|
11
|
+
/** A grouped cluster of proposals that all map to the same semantic step. @internal */
|
|
12
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
13
|
+
interface Cluster {
|
|
14
|
+
/** Distinct agent indices that proposed a member of this cluster. */
|
|
15
|
+
agentIndices: Set<number>;
|
|
16
|
+
/** All individual proposals that map into this cluster (for label + confidence). */
|
|
17
|
+
members: { confidence: number; text: string }[];
|
|
18
|
+
/** Normalized representative text (first normalized form in cluster). */
|
|
19
|
+
normalized: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Merge up to 5 agent next-step lists into a consensus result.
|
|
24
|
+
*
|
|
25
|
+
* Algorithm (per spec §4):
|
|
26
|
+
* 1. Cap each agent list at K items.
|
|
27
|
+
* 2. Normalize each text (lowercase, trim, collapse whitespace, strip trailing punct).
|
|
28
|
+
* 3. Group near-duplicates: same normalized string OR Jaccard token-overlap ≥ 0.6.
|
|
29
|
+
* Track distinct agent indices per group for vote counting.
|
|
30
|
+
* 4. Score each group by vote count = |distinct agent indices|.
|
|
31
|
+
* 5. Consensus = groups with votes ≥ quorum, sorted by votes desc then mean confidence desc.
|
|
32
|
+
* 6. If no group meets quorum, return the single highest-vote group flagged tentative:true.
|
|
33
|
+
* 7. Label each output step with the highest-confidence original phrasing in the group.
|
|
34
|
+
*/
|
|
35
|
+
export function mergeNextStepConsensus(
|
|
36
|
+
lists: readonly (readonly AgentProposal[])[],
|
|
37
|
+
opts?: MergeOptions
|
|
38
|
+
): ConsensusResult {
|
|
39
|
+
const k = opts?.k ?? DEFAULT_K;
|
|
40
|
+
const quorum = opts?.quorum ?? DEFAULT_QUORUM;
|
|
41
|
+
const agentCount = lists.length;
|
|
42
|
+
|
|
43
|
+
if (agentCount === 0) {
|
|
44
|
+
return { agentCount: 0, quorum, steps: [] };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Step 1 + 2: cap each agent list and normalize proposals.
|
|
48
|
+
const clusters: Cluster[] = [];
|
|
49
|
+
|
|
50
|
+
for (let agentIndex = 0; agentIndex < lists.length; agentIndex++) {
|
|
51
|
+
const list = lists[agentIndex];
|
|
52
|
+
const capped = list.slice(0, k);
|
|
53
|
+
|
|
54
|
+
for (const proposal of capped) {
|
|
55
|
+
const norm = normalize(proposal.text);
|
|
56
|
+
if (norm.length === 0) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Step 3: find an existing cluster to join.
|
|
61
|
+
const targetCluster = findOrCreateCluster(clusters, norm);
|
|
62
|
+
targetCluster.agentIndices.add(agentIndex);
|
|
63
|
+
targetCluster.members.push({ confidence: proposal.confidence, text: proposal.text });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (clusters.length === 0) {
|
|
68
|
+
return { agentCount, quorum, steps: [] };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Step 4: compute vote count and mean confidence per cluster.
|
|
72
|
+
const scored = clusters.map((cluster) => {
|
|
73
|
+
const votes = cluster.agentIndices.size;
|
|
74
|
+
const meanConf =
|
|
75
|
+
cluster.members.reduce((sum, m) => sum + m.confidence, 0) / cluster.members.length;
|
|
76
|
+
|
|
77
|
+
// Label = original phrasing from the highest-confidence member.
|
|
78
|
+
const bestMember = cluster.members.reduce((best, m) =>
|
|
79
|
+
m.confidence > best.confidence ? m : best
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return { cluster, meanConf, text: bestMember.text, votes };
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Step 5: separate consensus groups from sub-quorum groups.
|
|
86
|
+
const consensusGroups = scored
|
|
87
|
+
.filter((s) => s.votes >= quorum)
|
|
88
|
+
.sort((a, b) => b.votes - a.votes || b.meanConf - a.meanConf);
|
|
89
|
+
|
|
90
|
+
if (consensusGroups.length > 0) {
|
|
91
|
+
const steps: NextStep[] = consensusGroups.map((s) => ({
|
|
92
|
+
confidence: s.meanConf,
|
|
93
|
+
text: s.text,
|
|
94
|
+
votes: s.votes,
|
|
95
|
+
}));
|
|
96
|
+
return { agentCount, quorum, steps };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Step 6: no group reached quorum — tentative fallback (single highest-vote group).
|
|
100
|
+
const best = scored.reduce((prev, curr) => {
|
|
101
|
+
if (curr.votes > prev.votes) {
|
|
102
|
+
return curr;
|
|
103
|
+
}
|
|
104
|
+
if (curr.votes === prev.votes && curr.meanConf > prev.meanConf) {
|
|
105
|
+
return curr;
|
|
106
|
+
}
|
|
107
|
+
return prev;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const steps: NextStep[] = [
|
|
111
|
+
{
|
|
112
|
+
confidence: best.meanConf,
|
|
113
|
+
tentative: true,
|
|
114
|
+
text: best.text,
|
|
115
|
+
votes: best.votes,
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
return { agentCount, quorum, steps };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Find a cluster whose normalized key matches `norm` exactly or has Jaccard ≥ threshold,
|
|
123
|
+
* or create and register a new one.
|
|
124
|
+
*/
|
|
125
|
+
function findOrCreateCluster(clusters: Cluster[], norm: string): Cluster {
|
|
126
|
+
for (const cluster of clusters) {
|
|
127
|
+
if (cluster.normalized === norm) {
|
|
128
|
+
return cluster;
|
|
129
|
+
}
|
|
130
|
+
if (jaccard(cluster.normalized, norm) >= JACCARD_THRESHOLD) {
|
|
131
|
+
return cluster;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const newCluster: Cluster = {
|
|
135
|
+
agentIndices: new Set(),
|
|
136
|
+
members: [],
|
|
137
|
+
normalized: norm,
|
|
138
|
+
};
|
|
139
|
+
clusters.push(newCluster);
|
|
140
|
+
return newCluster;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Compute the Jaccard token-overlap coefficient between two normalized strings.
|
|
145
|
+
* J(A,B) = |A ∩ B| / |A ∪ B|
|
|
146
|
+
*/
|
|
147
|
+
function jaccard(a: string, b: string): number {
|
|
148
|
+
const setA = tokenSet(a);
|
|
149
|
+
const setB = tokenSet(b);
|
|
150
|
+
if (setA.size === 0 && setB.size === 0) {
|
|
151
|
+
return 1;
|
|
152
|
+
}
|
|
153
|
+
if (setA.size === 0 || setB.size === 0) {
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
let intersection = 0;
|
|
157
|
+
for (const token of setA) {
|
|
158
|
+
if (setB.has(token)) {
|
|
159
|
+
intersection++;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const union = setA.size + setB.size - intersection;
|
|
163
|
+
return intersection / union;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Normalize a step text for grouping:
|
|
168
|
+
* lowercase, trim, collapse internal whitespace, strip trailing punctuation.
|
|
169
|
+
*/
|
|
170
|
+
function normalize(text: string): string {
|
|
171
|
+
return text
|
|
172
|
+
.toLowerCase()
|
|
173
|
+
.trim()
|
|
174
|
+
.replace(/\s+/g, ' ')
|
|
175
|
+
.replace(/[.,;:!?]+$/, '');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── Private helper ──────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Tokenize a normalized string into a set of tokens.
|
|
182
|
+
*/
|
|
183
|
+
function tokenSet(normalized: string): Set<string> {
|
|
184
|
+
return new Set(normalized.split(/\s+/).filter((t) => t.length > 0));
|
|
185
|
+
}
|